版RAG知識(shí)庫實(shí)戰(zhàn):混合檢索與重排序優(yōu)化)
1. 從零拆解這個(gè)增強(qiáng)版知識(shí)庫到底在解決什么問題做過RAG的人大概都有過這種體驗(yàn)demo跑起來驚艷一上真實(shí)文檔就露餡。用戶問“上季度的差旅報(bào)銷標(biāo)準(zhǔn)是多少”系統(tǒng)檢索回來三段話一段講的是差旅申請(qǐng)流程一段講的是辦公用品采購還有一段是兩年前的舊版制度。模型拿著這三段話硬編最后給出的答案似是而非用戶罵罵咧咧地關(guān)掉對(duì)話框。這就是基礎(chǔ)RAG的典型困境。樸素RAG的本質(zhì)是“向量相似度檢索拼接生成”它假設(shè)語義相近的文本塊就是用戶需要的答案。但真實(shí)場(chǎng)景里語義相近和邏輯相關(guān)是兩碼事。一個(gè)問“怎么辦”一個(gè)答“是什么”向量距離可能很近但答非所問。我這次做的增強(qiáng)版智能知識(shí)庫核心目標(biāo)就一個(gè)讓檢索結(jié)果真正對(duì)得上用戶的問題意圖而不是對(duì)得上字面相似度。具體來說它要解決四個(gè)層面的問題。第一層是檢索精度?;A(chǔ)RAG用單一向量檢索遇到專業(yè)術(shù)語、縮寫、多義詞就抓瞎。比如“Agent”這個(gè)詞在AI語境下是智能體在化學(xué)語境下是試劑在房地產(chǎn)語境下是代理人。純向量檢索分不清這些需要引入關(guān)鍵詞檢索做互補(bǔ)。第二層是上下文完整性。文檔切塊是個(gè)技術(shù)活。切得太碎一個(gè)完整邏輯被拆成三段檢索到其中一段也拼不出完整答案切得太粗一個(gè)塊里塞了五個(gè)主題向量被平均化反而什么都匹配不準(zhǔn)。這就是為什么需要語義分塊而不是簡單的按字?jǐn)?shù)切。第三層是知識(shí)時(shí)效性。企業(yè)知識(shí)庫不是靜態(tài)的制度在更新、產(chǎn)品在迭代、FAQ在增補(bǔ)?;A(chǔ)RAG每次更新都要重新 embedding 全量文檔成本高、周期長。增強(qiáng)版需要支持增量更新和版本管理。第四層是多模態(tài)內(nèi)容。熱詞里有人問“rag知識(shí)庫能存儲(chǔ)圖片嘛”答案是能但要看怎么存。圖片不能直接做向量檢索需要先做多模態(tài) embedding 或者 OCR 提取文字再入庫。這個(gè)鏈路設(shè)計(jì)不好圖片就是知識(shí)庫里的死數(shù)據(jù)。這個(gè)項(xiàng)目適合誰參考如果你已經(jīng)跑通過最基礎(chǔ)的 LangChain RAG demo但被真實(shí)場(chǎng)景的召回率折磨過那這篇內(nèi)容就是寫給你的。如果你還沒入門建議先補(bǔ)一下 LangChain 的基礎(chǔ)概念和向量數(shù)據(jù)庫的基本操作否則后面的一些設(shè)計(jì)取舍你可能體會(huì)不到痛點(diǎn)在哪。注意增強(qiáng)版RAG不是把組件堆得越多越好。每加一個(gè)環(huán)節(jié)延遲就漲一截維護(hù)成本就翻一倍。我的原則是先定位瓶頸再針對(duì)性增強(qiáng)不做無意義的炫技。2. 架構(gòu)選型為什么是這套組合而不是別的2.1 核心組件選型與取舍邏輯整個(gè)系統(tǒng)的骨架是LangChain 向量數(shù)據(jù)庫 混合檢索 重排序。這個(gè)組合不是拍腦袋定的每個(gè)選擇背后都有具體的考量。先說 LangChain。很多人吐槽它抽象層太厚、調(diào)試?yán)щy我承認(rèn)這是事實(shí)。但在這個(gè)項(xiàng)目里L(fēng)angChain 的價(jià)值在于它把文檔加載、分塊、embedding、檢索、生成這條鏈路的接口統(tǒng)一了。如果全部手寫光是適配不同格式的文檔加載器就要花掉大量時(shí)間。LangChain 的DocumentLoader生態(tài)覆蓋了 PDF、Word、Markdown、HTML、Notion 導(dǎo)出等常見格式省去了大量膠水代碼。而且它的Retriever接口設(shè)計(jì)得很干凈方便我在后面插入自定義的混合檢索邏輯。向量數(shù)據(jù)庫我選了Milvus而不是 Chroma 或 FAISS。原因很直接Chroma 適合原型驗(yàn)證但它的持久化和并發(fā)能力在真實(shí)負(fù)載下不夠看FAISS 是庫不是服務(wù)沒有內(nèi)置的增刪改查和權(quán)限管理。Milvus 支持標(biāo)量字段過濾、支持多向量字段、支持增量插入這些特性在增強(qiáng)版知識(shí)庫里都會(huì)用到。比如我要按文檔版本號(hào)過濾舊數(shù)據(jù)或者按部門標(biāo)簽限定檢索范圍Milvus 的標(biāo)量過濾能直接在向量檢索時(shí)完成不需要檢索后再過濾。Embedding 模型我用了BGE-M3。這個(gè)模型的特點(diǎn)是同時(shí)支持稠密向量、稀疏向量和多向量表示。稠密向量負(fù)責(zé)語義匹配稀疏向量負(fù)責(zé)關(guān)鍵詞匹配多向量表示可以處理長文本的細(xì)粒度匹配。一個(gè)模型搞定三種檢索模式比分別部署三個(gè)模型省事得多。而且 BGE-M3 對(duì)中文的支持相當(dāng)好這在處理中文企業(yè)文檔時(shí)很關(guān)鍵。重排序模型我選了BGE-Reranker-v2-M3。混合檢索會(huì)召回較多候選我一般設(shè) top 20但最終送給 LLM 的只能有 3 到 5 個(gè)塊。重排序的作用就是在這 20 個(gè)候選里用更精細(xì)的交叉注意力機(jī)制重新打分把真正相關(guān)的排到前面。實(shí)測(cè)下來加了重排序之后Top 3 的命中率能從 60% 左右提升到 85% 以上。2.2 語義分塊策略的設(shè)計(jì)考量分塊是 RAG 里最容易被低估的環(huán)節(jié)。我見過太多項(xiàng)目直接用RecursiveCharacterTextSplitter按 500 字切、50 字重疊然后抱怨檢索不準(zhǔn)。問題就出在固定長度切分不考慮語義邊界。舉個(gè)例子一份產(chǎn)品需求文檔里有一段話“用戶登錄后進(jìn)入首頁首頁展示推薦內(nèi)容。推薦算法基于協(xié)同過濾需要用戶行為數(shù)據(jù)。行為數(shù)據(jù)采集需要用戶授權(quán)?!比绻?500 字硬切很可能“推薦算法基于協(xié)同過濾”和“用戶登錄后進(jìn)入首頁”被切到兩個(gè)塊里。用戶問“推薦算法怎么做的”檢索到的是后半塊但前半塊的上下文丟了。我的做法是基于語義相似度的動(dòng)態(tài)分塊。具體來說先把文檔按段落拆開然后計(jì)算相鄰段落的 embedding 相似度。如果相似度高于閾值我設(shè)的是 0.75就合并成一個(gè)塊如果低于閾值就在此處斷開。這樣切出來的塊每個(gè)塊內(nèi)部的主題是一致的塊與塊之間的邊界是語義轉(zhuǎn)折點(diǎn)。但純語義分塊也有問題塊的長度不可控。有的塊可能只有一句話有的塊可能上千字。太短的塊信息量不足太長的塊向量被稀釋。所以我在語義分塊的基礎(chǔ)上加了長度約束最小 200 字最大 800 字。低于下限的塊嘗試與相鄰塊合并高于上限的塊在語義相似度最低的位置二次切分。還有一個(gè)細(xì)節(jié)塊與塊之間保留 15% 的重疊。這個(gè)重疊不是為了湊字?jǐn)?shù)而是為了防止答案剛好落在邊界上。比如一個(gè)問題的答案跨了兩個(gè)塊如果沒有重疊檢索到其中一個(gè)塊也拼不出完整答案。15% 是我實(shí)測(cè)下來比較平衡的值再高會(huì)引入冗余再低邊界保護(hù)不夠。2.3 混合檢索的權(quán)重分配與調(diào)參混合檢索的核心是稠密向量檢索和稀疏向量檢索的加權(quán)融合。稠密向量擅長語義匹配比如用戶問“怎么請(qǐng)假”能匹配到“休假申請(qǐng)流程”這樣的文檔稀疏向量擅長關(guān)鍵詞匹配比如用戶問“OA-2024-001 號(hào)文件”能精確匹配到文檔編號(hào)。權(quán)重怎么分我的經(jīng)驗(yàn)是不要固定權(quán)重按查詢類型動(dòng)態(tài)調(diào)整。具體做法是先對(duì)用戶查詢做一次輕量分類判斷它是“語義型查詢”還是“精確型查詢”。語義型查詢?nèi)纭叭绾翁嵘蛻魸M意度”給稠密向量更高權(quán)重0.7精確型查詢?nèi)纭癐SO9001 認(rèn)證流程”給稀疏向量更高權(quán)重0.6。分類器不需要很復(fù)雜我用了一個(gè)基于規(guī)則的判斷如果查詢里包含引號(hào)、編號(hào)、專有名詞通過詞性標(biāo)注識(shí)別就歸為精確型否則歸為語義型。這個(gè)規(guī)則簡單但有效實(shí)測(cè)準(zhǔn)確率在 80% 以上。如果不想自己做分類也可以用一個(gè)小型 LLM 做 zero-shot 分類但會(huì)增加延遲。融合算法我用的是RRFReciprocal Rank Fusion而不是簡單的加權(quán)求和。RRF 的好處是不需要?dú)w一化分?jǐn)?shù)直接基于排名融合。公式是score Σ(1/(k rank))k 一般取 60。這樣即使兩個(gè)檢索器的分?jǐn)?shù)尺度不同也能公平融合。3. 核心實(shí)現(xiàn)從文檔入庫到答案生成的完整鏈路3.1 文檔預(yù)處理與多模態(tài)內(nèi)容處理文檔入庫的第一步是格式解析。不同格式的文檔解析策略完全不同。PDF 是最麻煩的。掃描版 PDF 需要 OCR我用的方案是PaddleOCR它對(duì)中文的識(shí)別準(zhǔn)確率比 Tesseract 高不少。文本版 PDF 用PyMuPDF提取但要注意處理分欄、表格、頁眉頁腳。分欄如果不處理提取出來的文字會(huì)串行表格如果不特殊處理會(huì)變成一堆亂序的文字。Word 文檔用python-docx解析重點(diǎn)處理表格和嵌入圖片。表格我轉(zhuǎn)成 Markdown 格式保留結(jié)構(gòu)圖片走多模態(tài)處理鏈路。Markdown 和 HTML 相對(duì)簡單但要注意代碼塊和公式的處理。代碼塊要保留語言標(biāo)記公式要轉(zhuǎn)成 LaTeX 格式否則 embedding 模型理解不了。多模態(tài)內(nèi)容是增強(qiáng)版的重點(diǎn)。熱詞里有人問“rag知識(shí)庫能存儲(chǔ)圖片嘛”我的答案是能存但要分情況處理。如果圖片是流程圖、架構(gòu)圖用多模態(tài) embedding 模型如 CLIP直接編碼成向量檢索時(shí)用文本查圖片。如果圖片是截圖、掃描件先 OCR 提取文字文字入庫做文本檢索圖片本身作為附件存儲(chǔ)檢索到文字后返回圖片鏈接。如果圖片是裝飾性圖片直接跳過不入庫。我實(shí)測(cè)下來多模態(tài)檢索的召回率還不如純文本檢索穩(wěn)定。所以我的策略是圖片優(yōu)先 OCR 轉(zhuǎn)文字只有純圖形內(nèi)容才走多模態(tài)向量。這樣既保證了檢索效果又控制了系統(tǒng)復(fù)雜度。3.2 向量化與索引構(gòu)建的實(shí)操細(xì)節(jié)Embedding 這一步有幾個(gè)坑要注意。批量大小不要一次性把所有文檔塞進(jìn)去 embedding。BGE-M3 在 24G 顯存的卡上batch size 設(shè) 32 比較穩(wěn)。設(shè)太大容易 OOM設(shè)太小吞吐上不去。我一般用 16 做調(diào)試32 做生產(chǎn)。歸一化BGE-M3 輸出的稠密向量需要做 L2 歸一化否則內(nèi)積和余弦相似度不等價(jià)。LangChain 的HuggingFaceBgeEmbeddings默認(rèn)會(huì)做歸一化但如果你自己寫 embedding 邏輯記得手動(dòng)加normalize_embeddingsTrue。稀疏向量生成BGE-M3 的稀疏向量不是傳統(tǒng)的 BM25而是學(xué)習(xí)出來的詞權(quán)重。生成稀疏向量需要調(diào)用模型的encode方法并指定return_sparseTrue。這個(gè)稀疏向量的維度是詞表大小約 25 萬但大部分位置是 0存儲(chǔ)時(shí)用稀疏格式。Milvus 的 collection 設(shè)計(jì)我用了多向量字段from pymilvus import CollectionSchema, FieldSchema, DataType fields [ FieldSchema(nameid, dtypeDataType.INT64, is_primaryTrue, auto_idTrue), FieldSchema(namedense_vector, dtypeDataType.FLOAT_VECTOR, dim1024), FieldSchema(namesparse_vector, dtypeDataType.SPARSE_FLOAT_VECTOR), FieldSchema(nametext, dtypeDataType.VARCHAR, max_length65535), FieldSchema(namedoc_id, dtypeDataType.VARCHAR, max_length128), FieldSchema(nameversion, dtypeDataType.INT64), FieldSchema(namedept, dtypeDataType.VARCHAR, max_length64), ]version和dept是標(biāo)量字段用于過濾。比如檢索時(shí)加version 3只查最新版文檔或者dept finance只查財(cái)務(wù)部文檔。索引類型稠密向量用IVF_FLAT稀疏向量用SPARSE_INVERTED_INDEX。IVF_FLAT 的nlist我設(shè) 1024這個(gè)值跟數(shù)據(jù)量有關(guān)經(jīng)驗(yàn)公式是nlist 4 * sqrt(N)N 是向量總數(shù)。稀疏索引不需要調(diào)參Milvus 會(huì)自動(dòng)處理。3.3 檢索鏈路混合檢索重排序的完整實(shí)現(xiàn)檢索鏈路的代碼結(jié)構(gòu)如下def hybrid_retrieve(query, top_k20, alpha0.7): # 1. 查詢分類 query_type classify_query(query) if query_type precise: alpha 0.4 # 稀疏權(quán)重更高 # 2. 生成查詢向量 dense_vec embed_dense(query) sparse_vec embed_sparse(query) # 3. 并行檢索 dense_results milvus.search( collection, dense_vector, [dense_vec], limittop_k, output_fields[text, doc_id] ) sparse_results milvus.search( collection, sparse_vector, [sparse_vec], limittop_k, output_fields[text, doc_id] ) # 4. RRF融合 fused rrf_fusion(dense_results, sparse_results, k60) # 5. 重排序 reranked reranker.rerank(query, fused[:20], top_n5) return reranked這里有幾個(gè)實(shí)操細(xì)節(jié)值得展開。并行檢索稠密和稀疏檢索是獨(dú)立的可以用concurrent.futures并行執(zhí)行。實(shí)測(cè)下來并行比串行快 40% 左右。但要注意 Milvus 的連接池配置并發(fā)太高會(huì)打滿連接。RRF 的 k 值k60 是論文里的默認(rèn)值但我實(shí)測(cè)下來對(duì)于企業(yè)知識(shí)庫這種文檔量在萬級(jí)左右的場(chǎng)景k40 效果更好。k 越小排名靠前的結(jié)果權(quán)重越高適合精確檢索k 越大排名靠后的結(jié)果也有機(jī)會(huì)適合召回優(yōu)先。重排序的 batch sizeBGE-Reranker 的輸入是 query-doc 對(duì)20 個(gè)候選就是 20 個(gè)對(duì)。batch size 設(shè) 8 比較穩(wěn)設(shè)太大顯存扛不住。重排序的延遲大概在 200-500ms取決于候選數(shù)量和文本長度。過濾條件的下推Milvus 支持在向量檢索時(shí)直接加標(biāo)量過濾比如exprversion 3 dept finance。這個(gè)過濾是在向量檢索內(nèi)部完成的比檢索后再過濾效率高得多。但要注意過濾太嚴(yán)格可能導(dǎo)致召回不足我一般會(huì)先不加過濾檢索如果結(jié)果里舊版本太多再加。3.4 生成環(huán)節(jié)的提示詞設(shè)計(jì)與上下文組裝檢索到 5 個(gè)塊之后怎么組裝成 prompt 送給 LLM這步直接決定最終答案的質(zhì)量。我的 prompt 模板是這樣的你是一個(gè)企業(yè)知識(shí)庫助手。請(qǐng)基于以下參考資料回答用戶問題。 參考資料 [1] {chunk_1} [2] {chunk_2} ... 用戶問題{query} 回答要求 1. 只使用參考資料中的信息不要編造。 2. 如果參考資料不足以回答問題明確說根據(jù)現(xiàn)有資料無法回答。 3. 引用信息時(shí)標(biāo)注來源編號(hào)如[1]。 4. 回答要簡潔直接給出答案不要重復(fù)問題。這個(gè)模板有幾個(gè)設(shè)計(jì)點(diǎn)。編號(hào)引用讓模型標(biāo)注來源方便用戶核實(shí)。同時(shí)這也是一種約束模型知道自己在引用哪段話不容易跑偏。拒答機(jī)制明確告訴模型“資料不足就說不知道”。這比讓模型硬編要好得多。我實(shí)測(cè)下來加了拒答指令后幻覺率從 15% 降到了 5% 以下。簡潔要求企業(yè)用戶要的是答案不是論文。讓模型直接給結(jié)論不要鋪墊。上下文組裝時(shí)要注意塊之間的順序。我一般按重排序的分?jǐn)?shù)從高到低排列但會(huì)把同一文檔的塊放在一起保持邏輯連貫。如果兩個(gè)塊來自不同文檔但內(nèi)容相關(guān)會(huì)在中間加分隔符。還有一個(gè)細(xì)節(jié)上下文長度控制。5 個(gè)塊如果每個(gè) 800 字就是 4000 字加上 prompt 和問題大概 5000 token。這個(gè)長度對(duì)大多數(shù)模型來說沒問題但如果塊更長就要考慮截?cái)嗷蛘N业淖龇ㄊ侨绻傞L度超過 6000 token就對(duì)每個(gè)塊做一次抽取式摘要只保留與問題最相關(guān)的句子。4. 踩坑實(shí)錄那些文檔里不會(huì)寫的問題和排查方法4.1 檢索召回不準(zhǔn)的排查思路檢索不準(zhǔn)是最常見的問題但原因可能有很多種。我整理了一個(gè)排查流程按順序檢查。第一步檢查分塊質(zhì)量。隨機(jī)抽 10 個(gè)塊看它們是否語義完整。如果塊里出現(xiàn)半句話、表格串行、頁眉頁腳混入那就是解析或分塊的問題。我遇到過 PDF 分欄沒處理導(dǎo)致左右欄文字交替出現(xiàn)塊的內(nèi)容完全亂套。第二步檢查 embedding 是否正常。取兩個(gè)語義相似的句子算一下余弦相似度。正常應(yīng)該在 0.8 以上。如果低于 0.6可能是模型加載有問題或者文本預(yù)處理時(shí)把關(guān)鍵信息洗掉了。第三步檢查檢索結(jié)果。把 query 和檢索到的 top 10 塊打印出來人工判斷相關(guān)性。如果 top 10 里只有 2-3 個(gè)相關(guān)說明檢索環(huán)節(jié)有問題如果 top 10 都相關(guān)但 top 3 不相關(guān)說明排序有問題需要加重排序。第四步檢查重排序。把重排序前后的結(jié)果對(duì)比看排序是否有改善。如果重排序后反而更差可能是 reranker 模型和 embedding 模型不匹配或者輸入格式不對(duì)。常見問題速查表現(xiàn)象可能原因解決方法召回結(jié)果完全不相關(guān)embedding 模型加載失敗或維度不匹配檢查模型輸出維度與 collection 定義是否一致召回結(jié)果部分相關(guān)分塊太碎或太大調(diào)整分塊參數(shù)增加語義分塊精確查詢召回差稀疏檢索未生效檢查稀疏向量是否正確生成和索引舊版本文檔排在前面未加版本過濾在檢索時(shí)加 version 過濾條件重排序后效果變差reranker 輸入格式錯(cuò)誤檢查 query-doc 對(duì)的拼接格式多模態(tài)檢索召回低圖片 OCR 質(zhì)量差換 OCR 引擎或?qū)D片做預(yù)處理4.2 性能瓶頸的定位與優(yōu)化增強(qiáng)版 RAG 的延遲主要來自四個(gè)環(huán)節(jié)embedding、檢索、重排序、生成。我實(shí)測(cè)的延遲分布大概是Embedding50-100ms查詢短很快混合檢索100-200msMilvus 并行檢索重排序200-500ms20 個(gè)候選生成1-3s取決于 LLM 和輸出長度總延遲在 1.5-4s 之間。如果要優(yōu)化優(yōu)先級(jí)是生成 重排序 檢索 embedding。生成優(yōu)化用流式輸出讓用戶先看到部分結(jié)果?;蛘邠Q更小的模型比如 7B 級(jí)別的模型延遲能降到 500ms 以內(nèi)但質(zhì)量會(huì)下降。我的做法是簡單問題用小模型復(fù)雜問題用大模型通過查詢分類來路由。重排序優(yōu)化減少候選數(shù)量。如果混合檢索的 top 20 里前 10 已經(jīng)覆蓋了大部分相關(guān)結(jié)果可以把重排序的輸入減到 10。或者用更小的 reranker 模型比如 BGE-Reranker-Base延遲能減半。檢索優(yōu)化Milvus 的nprobe參數(shù)控制搜索的聚類數(shù)量設(shè)小一點(diǎn)速度快但召回低。我一般設(shè)nprobe16在召回和速度之間平衡。另外如果數(shù)據(jù)量不大10 萬可以用FLAT索引檢索速度極快但內(nèi)存占用高。并發(fā)處理熱詞里有人問“ai agent 怎么扛并發(fā)”RAG 系統(tǒng)的并發(fā)瓶頸主要在 LLM 調(diào)用和向量數(shù)據(jù)庫連接。LLM 調(diào)用可以用異步隊(duì)列向量數(shù)據(jù)庫要配連接池。Milvus 默認(rèn)連接數(shù)有限高并發(fā)時(shí)要調(diào)大max_connections。4.3 增量更新與版本管理的實(shí)現(xiàn)企業(yè)知識(shí)庫不是一次建好就完事的文檔會(huì)更新、新增、廢棄。全量重建索引成本太高必須支持增量更新。我的方案是基于文檔 ID 的增量插入軟刪除。每份文檔有唯一的doc_id更新時(shí)先插入新版本的塊然后把舊版本的塊標(biāo)記為is_deletedTrue。檢索時(shí)加過濾條件is_deleted False這樣舊數(shù)據(jù)不會(huì)被檢索到但也不會(huì)立即刪除方便回滾。版本管理用version字段每次更新遞增。檢索時(shí)可以指定version N只查最新版或者不加限制查所有版本適合需要?dú)v史對(duì)比的場(chǎng)景。增量更新的觸發(fā)方式有兩種定時(shí)同步和事件驅(qū)動(dòng)。定時(shí)同步適合文檔源是文件系統(tǒng)或網(wǎng)盤的場(chǎng)景每隔一段時(shí)間掃描變更。事件驅(qū)動(dòng)適合文檔源有 webhook 的場(chǎng)景文檔一更新就觸發(fā)入庫。注意增量更新時(shí)要注意 embedding 模型的一致性。如果更新時(shí)換了 embedding 模型新舊向量不在同一空間檢索會(huì)出問題。換模型必須全量重建。4.4 多輪對(duì)話與上下文記憶的處理單輪問答做好之后多輪對(duì)話是自然的延伸。但多輪對(duì)話有個(gè)核心問題用戶的問題可能依賴上一輪的上下文。比如用戶先問“差旅報(bào)銷標(biāo)準(zhǔn)是多少”系統(tǒng)回答了。用戶接著問“那住宿呢”這個(gè)問題單獨(dú)看是沒頭沒尾的必須結(jié)合上一輪才能理解。我的處理方式是查詢改寫把當(dāng)前問題和最近兩輪對(duì)話一起送給一個(gè)小 LLM讓它改寫成獨(dú)立完整的查詢。比如“那住宿呢”會(huì)被改寫成“差旅報(bào)銷中住宿費(fèi)用的標(biāo)準(zhǔn)是多少”。然后用改寫后的查詢?nèi)z索。查詢改寫會(huì)增加一次 LLM 調(diào)用延遲增加 200-500ms。但實(shí)測(cè)下來多輪場(chǎng)景的準(zhǔn)確率提升明顯這個(gè)開銷值得。對(duì)話歷史的管理要注意長度控制。不能把所有歷史都塞進(jìn)去一般保留最近 3-5 輪。更早的歷史可以做摘要或者直接丟棄。我的做法是保留最近 3 輪完整對(duì)話更早的做一句話摘要。5. 效果評(píng)估與持續(xù)迭代的實(shí)操方法5.1 評(píng)估指標(biāo)與測(cè)試集構(gòu)建RAG 系統(tǒng)的評(píng)估不能只看“感覺準(zhǔn)不準(zhǔn)”要有量化指標(biāo)。我用的核心指標(biāo)有三個(gè)召回率RecallK前 K 個(gè)檢索結(jié)果里包含正確答案的比例。這個(gè)指標(biāo)衡量檢索環(huán)節(jié)的效果。我一般看 Recall5 和 Recall10。命中率Hit RateK前 K 個(gè)結(jié)果里至少有一個(gè)相關(guān)的比例。這個(gè)指標(biāo)比召回率寬松適合評(píng)估整體可用性。答案準(zhǔn)確率最終生成的答案是否正確。這個(gè)需要人工評(píng)估或者用 LLM 做自動(dòng)評(píng)估讓 GPT-4 判斷答案是否基于參考資料且正確。測(cè)試集的構(gòu)建很關(guān)鍵。我從真實(shí)用戶問題里采樣了 200 個(gè)問題覆蓋事實(shí)型、流程型、對(duì)比型、多跳型四種類型。每個(gè)問題標(biāo)注了標(biāo)準(zhǔn)答案和相關(guān)的文檔塊 ID。這個(gè)測(cè)試集是迭代的基礎(chǔ)每次改動(dòng)都要跑一遍看指標(biāo)變化。5.2 基于 bad case 的迭代優(yōu)化評(píng)估的目的是發(fā)現(xiàn)問題然后針對(duì)性優(yōu)化。我一般按這個(gè)流程走跑測(cè)試集找出 bad case答案錯(cuò)誤或召回失敗的問題。分析 bad case 的原因是分塊問題、檢索問題、還是生成問題。針對(duì)原因做優(yōu)化然后重新跑測(cè)試集驗(yàn)證。舉幾個(gè)我實(shí)際遇到的 bad case 和解決方法。案例一用戶問“新員工入職需要哪些材料”召回的結(jié)果里有一份是“離職材料清單”。原因是“入職”和“離職”在向量空間里距離很近。解決方法在 embedding 前給文檔塊加上標(biāo)題前綴比如“入職材料...”這樣向量會(huì)偏向“入職”語義。案例二用戶問“2024 年 Q1 的銷售目標(biāo)是多少”召回的是 2023 年的數(shù)據(jù)。原因是版本過濾沒生效。解決方法在檢索時(shí)強(qiáng)制加version過濾只查最新版。案例三用戶問“怎么申請(qǐng)年假”召回的結(jié)果是“年假天數(shù)規(guī)定”沒有申請(qǐng)流程。原因是流程文檔的分塊把“申請(qǐng)步驟”和“審批權(quán)限”切開了檢索到的是天數(shù)規(guī)定那塊。解決方法調(diào)整分塊策略把流程相關(guān)的段落強(qiáng)制合并。5.3 持續(xù)迭代的工程化建議RAG 系統(tǒng)的優(yōu)化是個(gè)持續(xù)過程不是一次調(diào)好就完事。我的工程化建議是建立反饋閉環(huán)在界面上加“這個(gè)回答有幫助嗎”的按鈕收集用戶反饋。差評(píng)的回答自動(dòng)進(jìn)入待分析隊(duì)列。定期更新測(cè)試集業(yè)務(wù)在變用戶問題也在變。每季度補(bǔ)充一批新的測(cè)試問題保持測(cè)試集的代表性。A/B 測(cè)試改動(dòng)檢索策略或 prompt 時(shí)不要直接全量上線先做 A/B 測(cè)試。一半流量走舊策略一半走新策略對(duì)比指標(biāo)后再?zèng)Q定。監(jiān)控關(guān)鍵指標(biāo)線上要監(jiān)控召回率、延遲、拒答率。拒答率突然升高可能是檢索出了問題延遲突然升高可能是向量數(shù)據(jù)庫負(fù)載高了。日志記錄每次檢索都記錄 query、召回結(jié)果、重排序分?jǐn)?shù)、最終答案。出問題時(shí)可以回溯分析。提示不要過度優(yōu)化。RAG 系統(tǒng)的效果有上限這個(gè)上限由文檔質(zhì)量和 embedding 模型決定。如果文檔本身寫得含糊不清再好的檢索也救不了。先把文檔質(zhì)量搞好再談技術(shù)優(yōu)化。6. 一些個(gè)人體會(huì)和后續(xù)擴(kuò)展方向這套增強(qiáng)版知識(shí)庫我前后迭代了三個(gè)版本踩過的坑比寫過的代碼還多。最大的體會(huì)是RAG 的瓶頸往往不在模型而在數(shù)據(jù)。文檔解析、分塊、清洗這些“臟活累活”決定了系統(tǒng)的下限檢索和生成策略決定了上限。很多人一上來就調(diào)模型、換框架結(jié)果發(fā)現(xiàn)效果提升有限就是因?yàn)榈讓訑?shù)據(jù)沒處理好。另一個(gè)體會(huì)是不要追求一步到位。我第一版就是樸素 RAG跑通了再逐步加混合檢索、加重排序、加多模態(tài)。每加一個(gè)組件都要驗(yàn)證它是否真的帶來了提升。如果加了之后指標(biāo)沒變化或者變化在噪聲范圍內(nèi)那就果斷去掉。系統(tǒng)越簡單維護(hù)成本越低。后續(xù)我打算在這幾個(gè)方向繼續(xù)擴(kuò)展。一是引入知識(shí)圖譜把實(shí)體和關(guān)系抽出來做 ontology RAG。這樣能處理多跳推理問題比如“A 的上級(jí)的部門負(fù)責(zé)什么業(yè)務(wù)”純向量檢索很難搞定。二是做 Agent 化的知識(shí)庫讓系統(tǒng)不只是被動(dòng)檢索還能主動(dòng)追問、澄清、多步檢索。三是優(yōu)化多模態(tài)檢索目前圖片檢索的召回率還是不如文本需要更好的多模態(tài) embedding 模型。如果你也在做類似的項(xiàng)目我的建議是先把基礎(chǔ) RAG 跑通然后拿真實(shí)數(shù)據(jù)測(cè)找到瓶頸再針對(duì)性增強(qiáng)。不要一上來就堆組件那樣只會(huì)讓你在調(diào)試時(shí)懷疑人生。