昨天談完文件匯入與索引流程,我們已經把 PDF、PowerPoint、表格與其他原始資料,轉換成可以被搜尋的 Chunk,也建立了關鍵字、向量或 Graph Index。
接下來看起來很簡單:把使用者的問題轉成 Embedding,從 Vector Database 找出最相似的幾個 Chunk,再交給 LLM 回答,不就完成了嗎?
但假設使用者問的是:
「升級到 3.2.1 之後出現 ORA-12541,要怎麼恢復?」
Vector Search 可能找到幾篇都在討論「資料庫連線異常」的文件,語意非常相似,卻沒有一篇包含 3.2.1 的已知問題與實際復原步驟;另一份標題看起來不太相似的 Release Note,反而同時記載了版本、錯誤碼與修正方式。
如果系統只取回前五個語意最相似的 Chunk,它找到的可能都是「相關內容」,卻沒有一份是「能回答問題的證據」。
這就是今天真正要處理的問題:
Retrieval 的目標不是找出最像問題的文字,而是以可接受的成本,找回足以支持答案的證據。

一條比較完整的 Retrieval Pipeline,可以拆成以下幾個責任不同的階段:
理解 Query → 套用 Metadata Filter → 多路召回 → 融合排序 → Reranking → Context Selection
第一階段的 Retriever,要在大量資料中快速找出候選,重點是不要漏掉答案,也就是提高 Recall。這一層可以同時使用 BM25、Dense Retrieval,甚至針對特定資料使用其他 Retriever。
候選集合產生之後,Fusion 負責把不同搜尋方式的結果合併;Reranker 再使用成本較高、但判斷更細緻的模型,重新評估每個候選與問題之間的關係,提升 Precision。最後的 Context Selection 還要排除重複內容、補回必要上下文,並在 Token Budget 內組成真正要交給 LLM 的證據。
因此,文章裡常看到的 Top-K,其實至少包含三個不同的 K:
把三者混成同一個數字,很容易造成錯誤調校。第一階段的 K 太小,正確證據可能根本進不了候選集;最後的 K 太大,又可能讓大量相似、過期或互相矛盾的內容一起進入 Prompt。
所以 Retrieval 並不是「找越多越好」,而是在 Recall、Precision、Latency、Cost 與 Context Quality 之間做取捨。
Dense Retrieval 會把 Query 與 Chunk 分別轉成向量,再尋找距離較近的內容。它最大的價值,是能跨越字面差異理解語意。例如使用者問「商品壞掉可以換新嗎?」,即使文件寫的是「瑕疵品更換政策」,仍有機會被找出來。
但一個向量必須把整段內容壓縮成固定維度。當 Chunk 同時包含多個主題,或問題依賴否定詞、版本、日期與精確編號時,重要細節可能被整體語意稀釋。Vector Search 找到的,是向量空間中的鄰近內容;鄰近不代表它包含答案,更不代表它是目前有效的答案。
傳統的關鍵字搜尋則有完全不同的優勢。BM25 不真正理解語意,但特別擅長辨識罕見且不能被模糊處理的字串,例如:
它的弱點也很明顯:使用者若採用同義詞、口語描述或不同語言,文件中又沒有出現相同詞彙,就可能完全找不到。
這兩種搜尋方式不是誰取代誰,而是觀察同一份資料的兩種視角:
| 搜尋方式 | 擅長的訊號 | 常見失敗 |
|---|---|---|
| BM25/Sparse Retrieval | 精確詞彙、稀有字串、代碼與名稱 | 不理解同義改寫與跨語言語意 |
| Dense Retrieval | 概念、語意、自然語言改寫 | 找到主題相似但不能回答的內容,精確細節可能被稀釋 |
| Metadata Filter | 權限、時間、版本、產品與資料範圍 | Metadata 錯誤或條件過窄會直接漏掉答案 |

當 BM25 與 Dense Retrieval 的錯誤具有互補性,最直覺的做法就是讓兩條路同時召回候選,這也是 Hybrid Search 的基本概念。現在許多搜尋服務都已提供不同形式的混合搜尋。
不過,「兩邊都查」只完成了一半。接下來還要回答:BM25 的分數與 Cosine Similarity 根本不在同一個尺度上,兩份結果到底如何合併?
一種常見做法是 Reciprocal Rank Fusion,簡稱 RRF。它不直接比較兩套系統的原始分數,而是根據文件在每份結果中的名次計分。某份文件如果在關鍵字與向量搜尋都名列前茅,融合後自然會得到較高順位。
RRF 的好處是簡單、容易解釋,也不需要先校準不同模型的分數,因此適合作為沒有大量標註資料時的 Baseline。Azure AI Search 與 Elasticsearch 都把它放在 Hybrid Search 的核心設計中。
但 RRF 也不是魔法:
例如 Weaviate 提供保留分數差距的 Relative Score Fusion;Pinecone 則能調整 Dense 與 Sparse 的權重。這些差異說明:融合策略本身就是 Retrieval Design 的一部分。 有了自己的 Relevance Dataset,才有條件比較不同方法。
在中文開源生態中,BGE-M3 讓同一個模型產生 Dense、Sparse 與 Token 級 Multi-vector 表示。值得借鏡的不是又多了一個模型名稱,而是背後的共同觀念:
不要要求單一相似度分數,同時理解語意、精確詞彙、文件範圍與答案相關性。
如果使用者問「目前台灣地區的企業方案價格」,只靠語意相似度,可能同時找回個人方案、其他國家、舊版本與已失效的促銷內容。這時候問題不是排序模型不夠強,而是搜尋範圍一開始就沒有被正確限制。
Metadata 可以包含產品、地區、語言、生效日期、文件版本、部門、Tenant 與 ACL。Filter 的角色不只是改善相關性,也關係到資料隔離與權限:某個 Chunk 即使和問題非常相關,只要使用者沒有權限,它就不應進入候選集合。
這些安全條件必須由可信任的 Application State 注入,不能請 LLM 自己判斷使用者有哪些權限。Query 中的「我是管理員」只是一段不可信的輸入,不是授權證明。
Filter 在什麼時候執行也會影響結果。以 Vector Search 為例:
Post-filter 如果只從很小的候選集合開始,可能把多數結果移除,最後甚至回傳零筆;Pre-filter 比較能維持條件內的 Recall,但條件高度選擇性時,也可能增加索引遍歷與延遲。Azure 的 Vector Filter 文件 就明確揭露了這項取捨。
因此,看到零結果時不應立刻把 Top-K 加大。也可能是 Metadata 不完整、版本標記錯誤,或 Filter 執行方式造成了 False Negative。
第一階段 Retriever 必須在大量資料中快速搜尋,因此通常使用 BM25、Bi-encoder 或其他可以預先建立索引的模型。它適合粗篩,卻無法花很多計算逐一細讀 Query 與每個 Chunk。
Cross-encoder Reranker 的做法不同。它把 Query 與候選文件一起交給模型,直接觀察兩者的 Token-level Interaction,再判斷這段內容是否真的回答問題。代價是每個候選都要重新推論,因此通常只用在第一階段找回的數十筆候選,而不是掃描整個 Corpus。
這形成一個常見的兩階段設計:
Retriever 先從數十萬筆資料找出數十筆候選,Reranker 再從候選中選出少量高品質證據。

但 Reranker 有一條不能被忽略的邊界:它只能重新排序已經收到的內容。如果第一階段 Candidate Recall 是零,再強的 Reranker 也無法找回答案。
另一個問題是 Domain Shift。在 BRIGHT 這個需要推理的 Retrieval Benchmark 中,研究者發現某些在一般 Web Search 資料上訓練的 Cross-encoder,重新排序後反而讓結果變差。這提醒我們,公開排行榜上的強模型,不一定理解企業內部的繁中術語、規格格式與「什麼才算回答這一題」。
Reranking 還可能遇到:
所以正確做法不是「打開 Reranker 就會更準」,而是分別量測 Rerank 前後的 Recall、MRR 或 nDCG、延遲,以及最後 Context 的有效比例。
即使 Reranker 已經把最相關的內容排到前面,直接拿前五名仍可能有問題。因為前五名也許都是同一段政策的重複 Chunk,卻漏掉例外條款與最新修訂公告。
Context Selection 應該組成一個 Evidence Portfolio,而不是相似 Chunk 清單。例如退貨問題可能需要:
這一層可以依 Parent Document 去重、限制同一來源數量、優先保留最新有效版本,並在 Token Budget 內維持證據多樣性。Google Vector Search 的 Crowding,就是限制同一類結果不要佔滿排行榜的一種產品化設計。
另一個方式是把 Retrieval Unit 與 Context Unit 解耦。小 Chunk 容易精準命中,卻可能缺少前後條件;大 Chunk 脈絡完整,卻會稀釋搜尋訊號。Parent-child Retrieval 用 Child Chunk 搜尋,命中後再取回較大的 Parent 交給 LLM。Dify Parent-child Retrieval
最適合被搜尋的單位,不一定等於最適合被模型閱讀的單位。
使用者輸入不一定是一個完整、乾淨的 Search Query。在多輪對話中,他可能只問「那第二個方案呢?」;比較題可能同時要求成本、權限與災難復原;同一個產品也可能有正式名稱、舊名稱與社群慣用縮寫。
因此,Retrieval 前也可能需要 Query Transformation:
補回對話中的指代、正規化名稱與錯字,讓 Query 更接近文件的語言。但原始 Query 必須保留,錯誤碼、版本、日期與專有名詞也不應被改寫掉。
從不同角度產生數個查詢,各自搜尋後再透過 RRF 等方法融合。它可以提高 Recall,也會讓搜尋與 Reranking 成本隨查詢數增加;生成的 Query 若偏離原意,還可能把整個候選集合帶往錯誤方向。
把「比較 A 與 B 的成本、權限和備援能力」拆成多個子問題。這對 Multi-facet 與 Multi-hop 問題很有用,但每個子查詢都拿一批 Top-K,也很容易讓 Context 爆量,因此最後仍需融合、去重與控制總 Budget。
HyDE 不只改寫問句,而是請 LLM 生成一份 Hypothetical Document,再把它轉成 Embedding,尋找真實 Corpus 中與它相近的文件。
它的洞見是:短問句與答案文件往往使用不同的表達方式。系統不是把假想內容當成事實,而是先猜「答案文件可能會出現哪些概念與詞彙」,再回到真正的資料來源尋找證據。
但生成內容也可能帶入錯誤假設。HyDE 比較適合短、模糊、缺少領域詞彙的問題;面對精確 ID、日期、版本或法條時,原始查詢與關鍵字路徑仍必須保留。
這些技術的共同風險是 Query Drift。因此系統應保留原始問題、改寫結果、子查詢與各自候選,不能只留下最後答案。
一個產品編號查詢,可能只需要 BM25、Filter 與一次 Reranking;一個跨三份政策的比較題,才需要拆解與多輪搜尋。如果所有 Query 都固定執行 Rewrite、五路搜尋、Graph Traversal 與 LLM Reranking,系統雖然看起來很完整,Latency、Cost 與除錯難度也會一起上升。
Adaptive-RAG 的核心觀念,是先依問題複雜度選擇不檢索、單次檢索或多輪檢索;CRAG 則不假設第一次 Retrieval 一定正確,而是評估候選品質,再決定是否改寫、切換資料來源或採取其他修正。較新的 FrugalRAG 又把另一個常被忽略的問題帶進來:系統除了知道怎麼繼續找,也要知道何時停止。
這些研究不代表 Production 系統一定要完整重現論文架構,但可以萃取成 Retrieval Router:
| 問題型態 | 優先路徑 | 不急著加入 |
|---|---|---|
| 錯誤碼、料號、法條 | BM25+Metadata Filter | 多輪 LLM Rewrite |
| 自然語言與同義改寫 | Dense+BM25+Fusion | Graph Retrieval |
| 比較題與多條件問題 | Query Decomposition+多路融合 | 固定單次 Top-K |
| 跨文件實體關係 | Iterative/Graph Retrieval | 只依 Chunk 相似度 |

系統也需要 Fallback:候選分數過低、證據不足或彼此矛盾時,可以擴大搜尋、換一條 Retriever、要求使用者澄清,或直接說資料不足。最危險的設計,是無論 Retrieval 品質如何,都強迫 LLM 根據手上的內容產生一個肯定答案。
標準的 Hybrid Search 加 Reranking,應該先成為可觀測、可評估的 Baseline。只有當錯誤分析顯示平面 Chunk 搜尋無法處理特定問題,才值得增加其他結構。
ColBERT 不把整段文件壓縮成一個向量,而是保留 Token-level Embeddings,查詢時再用 Late Interaction 進行細緻匹配。它位於單一向量與昂貴 Cross-encoder 之間:比 Dense Retrieval 保留更多細節,又能預先建立文件索引,但需要更大的儲存空間與更複雜的 Serving。ColBERTv2
RAPTOR 把原始 Chunk 聚類並逐層摘要,建立不同抽象層級的樹。查參數值時可以命中葉節點,詢問整份政策的主要變化時則可以先找到上層摘要。代價是摘要可能遺漏例外,文件更新時也要維護上層結構,因此摘要適合作為導航,不應取代原始證據。
Day 6 已經提過 GraphRAG。到了 Query 階段,更重要的判斷仍然是:問題是否需要跨文件串聯 Entity、Relation 與 Multi-hop Evidence。LightRAG 的雙層 Retrieval 與 HippoRAG 2 的 Knowledge Graph 路徑,都是在處理平面 Chunk 排行榜難以表達的關聯。
它們不是 Dense Retrieval 的進階模式,也不是所有 RAG 的終點。如果問題大多只是查一段規格,建立 Graph、處理 Entity Resolution 與更新關係,只會增加成本。只有當自己的測試題持續敗在跨文件關係與整體脈絡,才有導入價值。
很多團隊會直接比較「換模型後,回答看起來有沒有比較好」。但 Generation 可能掩蓋 Retrieval 問題:LLM 有時可以靠既有知識答對,也可能在拿錯 Context 時仍生成一段流暢答案。
所以 Retrieval 與 Generation 應該分開評估:
| 指標 | 想回答的問題 |
|---|---|
| Recall@K/Hit Rate | 正確證據有沒有進入第一階段候選? |
| MRR/nDCG@K | 正確或較重要的證據排得夠不夠前面? |
| Context Precision | 最後送給 LLM 的內容,有多少真的有用? |
| Duplicate Rate/Source Coverage | 結果是否被同一來源佔滿?是否涵蓋必要證據? |
| Citation Correctness | 回答引用的來源,是否真的支持該項主張? |
| Abstention Accuracy | 沒有答案時,系統能不能停止而不是硬答? |
| p50/p95 Latency 與 Cost | 品質提升付出了多少時間與費用? |
測試資料也不能只有一般問答,至少要分開觀察:精確錯誤碼、中文口語、跨語言、最新文件、比較題、多跳問題、權限限制,以及「資料裡根本沒有答案」的題目。平均分數很容易掩蓋某一類 Query 已經完全失效。
Google Cloud 的 Retrieval 優化建議也強調,應建立真實問題與 Golden Reference、一次只修改一個主要變數,並分開診斷 Nearest-neighbor Retrieval 與最後提供給 LLM 的 Context。Google Cloud:Optimizing RAG Retrieval
最小可行做法,是先收集 50~200 個來自真實使用情境的問題,標註哪些文件段落可以成為可接受證據。接著依序比較:
一次打開所有 Retrieval 功能,最後即使回答變好,也不知道是哪一層生效;回答變差時,更不知道該從哪裡修。
把今天的流程重新整理一次:
先理解資訊需求,利用 Metadata 限定正確範圍,再以多種互補訊號擴大召回;接著融合與 Reranking,最後組成少量、互補且可引用的證據。
BM25 與 Dense Retrieval 解決不同的語言落差;Hybrid Search 讓兩者互補;Metadata Filter 限定正確的資料與權限範圍;Reranker 判斷候選是否真的能回答問題;Context Selection 則決定 LLM 最後看見的是重複片段,還是一組完整證據。
Query Rewrite、Adaptive Retrieval、Hierarchy 與 Graph 都有價值,但它們不是應該一次開啟的功能清單。每增加一層,都應先回答三個問題:
Retrieval 的品質,不是由用了多少最新技術決定,而是由系統能否穩定地把問題轉成一組足以支持答案的證據決定。
下一步,即使資料已經找對,系統仍然要決定證據如何排序與組裝、遇到衝突版本怎麼處理、哪些內容可以被信任,以及如何避免取回的文件反過來操控模型。這會進一步把問題帶向 Context Engineering 與 RAG 的安全邊界。
