RAG 優化實戰:先建立評測,再調整檢索管線

我開始接手處理銀行內部的 RAG 系統時,第一個問題不是 Embedding 模型不夠新,而是沒有人能回答「現在到底準不準」。embedding、chunk 大小與 top-k 多半沿用預設值,調整後也沒有固定題目可以比較。

在這種情況下直接換模型,很容易只得到主觀印象。某幾題變好,不代表整體檢索改善;某位使用者覺得答案讀起來比較順,也不代表正確文件被找回來的機率真的提高。

我的調校順序後來固定成:先建立評測集與基準,再一次調整一個變數。以下是幾個在中文金融文件上影響較大的項目,以及實作時需要保留的限制。

先建立一組能重複執行的評測題

第一版評測集不必很大,但題目要來自真實工作。可以先整理 50 到 200 題常見查詢,為每題標記一份或多份相關文件,再用固定版本的知識庫重複執行。

檢索階段至少可以觀察兩個指標:

  • Recall@k:前 k 筆結果中,是否包含應該找到的文件。
  • MRR:第一份相關文件出現在多前面。

Recall@k 適合檢查「有沒有找回來」,MRR 則能看出正確文件是否總是排在後面。若系統還會生成答案,應另加答案正確性、引用品質與拒答能力等評測,不能只用檢索指標代表整個 RAG 系統。

第一版評測腳本通常不必做得太複雜。重要的是保存題目版本、文件版本、模型與參數,讓每次調整都有可比較的紀錄。對金融環境來說,這些紀錄也能說明為什麼更換模型或設定,而不是只留下「感覺比較好」。

先確認 embedding 的使用方式正確

模型選對了,不代表呼叫方式一定正確。以 multilingual-e5-large 為例,官方 model card 說明,檢索任務的查詢與文件應分別加上 query:passage: 前綴;長文本也會在 512 tokens 截斷。若管線漏掉前綴或忽略長度限制,效能可能下降。

這類問題很容易被誤判成「模型不適合中文」。在更換模型前,應先核對以下項目:

  • 查詢與文件是否使用模型要求的前綴或 instruction。
  • pooling 與 vector normalization 是否符合 model card。
  • 實際輸入是否超過模型長度限制。
  • 查詢端與建索引端是否使用同一模型與處理流程。

我們後來也評估 BGE-M3。它支援多語言、最長 8,192 tokens,並提供 dense、sparse 與 multi-vector 等模式。但「支援更長輸入」不代表就適合把整份文件都塞進單一向量,實際 chunk 長度仍要用自己的資料與查詢評測。

另外,換 embedding 模型後,舊向量不能沿用。文件必須用新模型重新切分、重新編碼並建立索引,否則查詢向量與文件向量不在同一個表示空間,分數沒有可比性。

chunking 要以 token 與文件結構為準

中文的字數不能直接等同 token 數。實際比例會隨 tokenizer、文字內容、標點與英數混排而變化。如果系統只用字元數切塊,可能在送進 embedding 模型時被截斷,卻沒有留下明確錯誤。

比較可靠的做法,是用目標模型的 tokenizer 計算長度,並保留標題、章節與表格等結構。我的一組起始參數曾使用約 600 tokens、重疊 80 tokens,但這只是特定文件集的起點,不是通用答案。

chunk 太小,段落脈絡容易被切散;chunk 太大,單一向量會混入多個主題,也可能排擠真正相關的片段。評測時可以從幾組範圍開始,例如 300、600、900 tokens,固定其他條件後比較 Recall@k、MRR 與延遲。

對規章與作業文件,我也會避免把章節標題和正文分開。即使正文沒有重複標題,也可以在建立索引時把文件名稱與階層標題補到 chunk metadata 或檢索文字中,幫助模型理解片段位置。

reranker 通常比盲目增加 top-k 更有效

向量檢索適合快速找出候選文件,但對語意相近、措辭細微不同的條文,前幾名排序未必理想。這時可以在第一階段取回一批候選,例如 15 筆,再交給 cross-encoder reranker 重排,最後留下 5 筆給生成模型。

我使用過 bge-reranker-v2-m3,在當時的資料集上,精確度提升比單純提高 top-k 明顯。不過候選數、保留數、GPU 記憶體與延遲都和硬體、文件長度及併發量有關,不能把單一部署的數字當成固定配置。

要特別注意,reranker 無法補救第一階段完全沒有召回的文件。如果 Recall@15 已經不足,應先檢查 embedding、chunk、查詢改寫或混合檢索,再調整重排模型。

中文 BM25 需要合適的斷詞

BM25 對法規編號、產品名稱、專有名詞與精確詞彙很有價值,但很多預設實作以空白分詞。中文句子沒有天然空白,若直接套用,整段文字可能被當成一個 token,結果自然不理想。

是否加入 BM25,仍應由評測決定。若向量檢索對精確代碼、條號或罕見名詞的表現不足,可以加入中文斷詞或字詞 n-gram,再和 dense retrieval 做分數融合。若現有查詢多為語意型問題,混合檢索的額外複雜度未必有同等收益。

因此我不會把 BM25 當成 RAG 的必備元件,而是把它當成一項針對詞彙匹配缺口的工具。

OCR 品質會決定後續檢索的上限

掃描 PDF、表格與雙欄文件如果在匯入時就解析錯誤,後面的 embedding 和 reranker 都無法恢復不存在的文字。

Docling 等文件處理工具可以整合 OCR、版面分析與表格結構,但仍需要抽樣檢查。金融文件特別容易受小字、印章、跨頁表格與頁首頁尾干擾。除了整體字元正確率,我會另外檢查標題階層、條號、日期、金額與表格欄位是否被保留。

對重要文件,可以在匯入流程加入品質門檻:頁面文字量異常、OCR 信心過低或表格解析失敗時,先標記人工檢查,不直接進入正式索引。

頁碼不要拿去 embedding,要留在 metadata

即使 OCR 文字抽取得夠完整,RAG 做 citation 時,頁碼與定位資訊通常還是比檔名更難處理。embedding(chunk.text) 產生的是 vector,不會自動帶著原文來自哪個檔案、哪一頁或哪個座標。問題不是 vector database 存不下頁碼,而是頁碼和內文屬於不同種類的資訊:內文適合做 semantic search,頁碼則是 source location,應該用 metadata 保存與查詢。

如果只是把「第 37 頁」接在 chunk 前面一起做 embedding,也不會得到可靠的按頁定位結果。數字本身幾乎沒有足夠的語意,而且不同文件都可能有第 37 頁。當使用者明確要求「找第 37 頁」時,這類需求至少包含明確的結構化條件,不應只期待 vector similarity 自己猜中。

實務上還有幾個容易混在一起的頁碼:

  • PDF page index:頁面在 PDF page objects 中的順序,不一定等於 viewer 顯示的頁碼。
  • printed page label:文件頁尾實際印出的 1、2、3,或 i、ii、iii。
  • chunk page span:一個 chunk 實際跨到哪些頁面。

封面、目錄、附件與空白頁都可能讓 PDF page index 和 printed page label 不一致。文件改版後,只要前面多插一頁,後面的 page index 也會全部位移。因此,只存一個 page_number 往往不夠,還要知道它指的是哪一套頁碼,以及對應哪一個文件版本。

printed page label 本身也不一定容易取得。有些 PDF 有正式的 page label,有些只是在頁尾放一段文字,掃描件甚至只剩影像。parser 若把 header 和 footer 當成雜訊移除,頁面上的「37」可能在進入 chunk 之前就消失。若 printed page label 是 citation 的必要欄位,ingestion pipeline 必須另外抽取並驗證,不能假設每種 PDF 都會自動提供。

chunking 會再增加一層問題。為了保留完整段落或表格,一個 chunk 可能跨兩頁;token-aware chunker 也可能合併同一個 heading 下的相鄰內容。若匯入流程先把 PDF flatten 成 Markdown,再從純文字重新 chunk,原本的 page anchor、document item reference 與 bounding box 也可能在這一步遺失。

我的做法是不把頁碼當成 embedding text 的一部分,而是替每個 chunk 保留一組 provenance metadata:

{
"document_id": "credit-policy",
"document_version": "2026-08-20",
"source_hash": "...",
"chunk_id": "credit-policy-0042",
"doc_item_refs": ["#/texts/128", "#/tables/7"],
"page_anchors": [
{
"pdf_page_index": 40,
"printed_page_label": "36",
"bbox_xyxy_pt": [90, 280, 506, 306],
"bbox_origin": "top-left"
},
{
"pdf_page_index": 41,
"printed_page_label": "37",
"bbox_xyxy_pt": [88, 70, 510, 210],
"bbox_origin": "top-left"
}
]
}

欄位不一定要完全相同,但至少要能回答三件事:這段內容來自哪一版文件、跨到哪些頁面,以及能不能回到原始位置。pdf_page_index 採 0-based 或 1-based 必須在 schema 中明確定義;bbox 也要定義座標系統、單位、原點,以及同一頁有多個區塊時的資料結構。若 UI 需要在 PDF 上標示引用內容,只有頁碼還不夠,還要保留 bounding box 或相等精度的 source anchor。

查詢流程也要分流。一般語意問題走 vector search,取回 chunk 後再由 metadata 組出 citation;遇到「第幾頁」「第幾章」或特定文件版本,則先解析成 filter,以 document_iddocument_version 和 page fields 做精確篩選。Qdrant 這類 vector database 可以把這些欄位放在 payload,和 vector query 一起使用,但前提是 ingestion 時沒有把 provenance 丟掉。

最後顯示 citation 時,我會在兩套頁碼不一致時直接寫清楚,例如「文件標示第 37 頁(PDF 第 41 頁)」。這比只顯示一個無法對回原始文件的數字更容易驗證,也能避免文件改版後引用悄悄指到別頁。

跨語言與繁簡轉換要保持一致

多語 embedding 可以支援跨語言查詢,但不同模型和領域的效果差異很大,仍需放進評測集驗證。尤其是產品名稱、法規術語與台灣金融用語,不一定能由一般多語訓練資料完整處理。

若知識庫同時包含繁體與簡體中文,可以在查詢與建索引兩端採相同的正規化策略,例如使用 OpenCC 產生額外的檢索欄位。不過繁簡轉換可能出現一對多或語境差異,不應覆寫原文。較安全的做法是保留原始內容,將正規化版本只用於搜尋或比對,最後仍引用原文。

小型文件不一定需要 RAG,但 long context 也不是保證

如果知識範圍只有一份短文件,全文能穩定放進 context,直接提供全文可能比建立 retrieval pipeline 簡單。這樣可以移除「相關片段沒有被召回」這一類錯誤。

但 long context 不代表模型一定會正確使用每一段內容,也不保證答案百分之百正確。仍要測試 lost-in-the-middle、引用品質、拒答與成本。是否使用 RAG,應比較整體任務品質,而不是只看模型標示的最大 context window。

一次只改一個變數

RAG pipeline 包含文件 parsing、chunking、embedding、indexing、query rewriting、hybrid search、reranker 與生成模型。若一次同時更換三個元件,即使結果變好,也很難知道改善來自哪裡;結果變差時,更難回復。

我採用的順序通常是:

  1. 固定一組真實評測題與文件版本。
  2. 確認現有 embedding 的前綴、長度與 normalization 沒有用錯。
  3. 比較 embedding 模型,必要時完整重建索引。
  4. 調整 chunk 大小、重疊與文件結構。
  5. 加入 reranker,觀察品質、延遲與資源消耗。
  6. 針對詞彙型缺口評估中文 BM25。
  7. 確認 PDF page index、printed page label、chunk page span,以及回到原始位置所需的 source anchor(例如 bbox 或 document item reference)都有保存在 metadata。
  8. 把 OCR 與跨語言問題納入匯入品質檢查。

每一步都保留設定、指標與回復方式。這樣做不會讓 RAG 一夜之間變完美,但能讓每次調整都有證據,也比較適合需要稽核與長期維運的金融環境。

如果你正準備更換 embedding 或 reranker,先固定 50 題真實查詢並跑出目前基準。即使暫時不換任何元件,這組結果也會讓下一次調整更容易判斷。

常見問題

第一版 RAG 評測集需要多少題?

可以先從 50 到 200 題真實問題開始,確保涵蓋主要文件類型、常見問法與容易混淆的情境。比題數更重要的是標註品質與版本控管,之後再依錯誤紀錄持續擴充。

更換 embedding 模型一定要重建索引嗎?

是。不同模型產生的向量空間通常不相容,文件與查詢必須使用同一模型及相同前處理流程重新編碼。

OpenCC 可以解決所有繁簡中文檢索問題嗎?

不行。它可以降低字形差異,但專有名詞、語境與地區用語仍可能不同。應保留原文、雙邊使用一致策略,並以實際查詢評測效果。

資料來源與版本說明


關於作者 KJ Huang

KJ Huang(KJH)是來自台灣的軟體工程師與技術主管,擁有超過八年橫跨 AI、區塊鏈、遊戲化、資安與金融的開發及管理經驗,目前投入企業級自架 LLM 平台、RAG 檢索與 AI 治理的實務工作。更多內容請見 kjhuang.com

本文屬於《銀行裡的 AI 工程》系列。

系列文章:銀行為什麼自架 LLM?RAG 為什麼不適合計數

English version: RAG Optimization: Measure First.

Response

  1. […] 系列文章:RAG 優化實戰、RAG 為什麼不適合計數。 […]

Discover more from KJ Huang

Subscribe now to keep reading and get access to the full archive.

Continue reading