
混合搜尋: BM25 vs 向量搜尋(為何兩者都需要)
客服人員在你的 RAG 聊天機器人裡輸入「SKU-4471」,系統回傳四筆結果,四筆都錯得很有自信。通用嵌入模型沒有任何理由,把這串精確字串放到向量空間中靠近它自己的位置。正是這種失敗模式催生了混合搜尋,也是團隊反覆追問同一個問題的原因:到底要怎麼結合 BM25 與向量搜尋,才不用永遠守著調參旋鈕?
如果你正在評估更廣泛的 RAG 工具鏈,我們的最佳 RAG 工具總整理涵蓋了周邊的技術棧。
重點摘要
- BM25 找到精確的關鍵字匹配(SKU、錯誤代碼);向量搜尋找到概念相近的文字,而不是相同的字串。
- 混合搜尋結合兩者,通常透過 Reciprocal Rank Fusion(RRF),在混合型查詢負載上勝過任何單一方法。
- 在 WANDS 基準上,純 RRF 得到 0.7068 NDCG(BM25 為 0.6983);調參後提升到 0.7497,增幅 7.4%。
- Postgres/pgvector 可以透過
ts_rank+ pgvector 原生執行混合搜尋,不需要專用的向量資料庫。
什麼是混合搜尋?(BM25 + 向量的組合)
混合搜尋把 BM25 與向量搜尋當成兩趟獨立的檢索,對同一個查詢各跑一次,再用融合演算法(最常見的是 Reciprocal Rank Fusion)把兩份排名結果合併成單一輸出。它不是第三種檢索方法,而是蓋在兩種既有方法之上的編排層。
這個區別很重要,因為圍繞這個主題的搜尋流量中,有相當一部分把 BM25 和向量搜尋混為一談,彷彿它們是同一件事。其實不是。BM25 是稀疏的、以關鍵字為基礎的評分函數,根源可追溯到 1970 年代的資訊檢索研究。向量搜尋是密集的、以嵌入為基礎的相似度搜尋,直到最近十年才在大規模場景下具備實用性。混合搜尋把它們視為互補的輸入,而不是競爭的技術,融合兩者的輸出,而非事先挑出一個贏家。
BM25 vs 向量 vs 混合: 快速比較
| 維度 | BM25(稀疏/詞彙式) | 向量搜尋(密集/語義式) | 混合 |
|---|---|---|---|
| 擅長 | 精確詞項、罕見詞元、ID | 改述、同義詞、概念 | 兩種查詢類型 |
| 失靈場景 | 改述問題、同義詞 | SKU、錯誤代碼、縮寫 | 兩種模式都不存在的語料 |
| 處理精確匹配(SKU、ID、錯誤代碼) | 是 | 否 | 是 |
| 處理改述與同義詞 | 否 | 是 | 是 |
| 需要嵌入模型 | 否 | 是 | 是 |
| 需要調參 | k1、b 參數 | 分塊、模型選擇 | 融合方法(RRF/alpha) |
| 典型延遲特性 | 亞毫秒到低毫秒 | 低到中毫秒(取決於 ANN) | 兩者相加,再加融合開銷 |
| 原生支援範例 | Elasticsearch、Postgres ts_rank | Qdrant、Pinecone、pgvector | Weaviate、Qdrant、Elasticsearch |
在 WANDS 電商基準上,純 BM25 得到 0.6983 NDCG,純向量搜尋得到 0.6953(幾乎打平)。未經任何語料級調參的純 RRF 融合達到 0.7068,比純 BM25 高出溫和的 1.2%。Doug Turnbull 的基準還測試了一個調參變體,在 RRF 之上疊加產品名稱加權,該版本達到 0.7497,增幅 7.4%。值得誠實說明你引用的是哪個數字:純 RRF 開箱即有小而確實的優勢;較大的 7.4% 數字則需要額外的領域特定調參,多數團隊在上線第一天不會做這件事。BM25 和向量搜尋誰也無法單獨稱霸;它們覆蓋不同的失敗模式,融合兩者可以一次補上兩個缺口。
BM25 機制: 關鍵字搜尋實際上如何評分
BM25 以詞頻為文件評分,並用該詞在整個語料庫中的稀有程度加權,再對文件長度做正規化。Robertson 與 Zaragoza 在 2009 年的論文〈The Probabilistic Relevance Framework: BM25 and Beyond〉中提出了這個形式化定義。它是 TF-IDF 的精煉,而不是替代品。
兩個參數控制了 BM25 的大部分行為。k1(通常 1.2-2.0)控制詞頻飽和:它為重複出現的詞對分數的提升設下上限,讓一篇提到「invoice」40 次的文件,不會自動贏過一篇在更精準段落裡提到 4 次的文件。b(預設 0.75)控制文件長度正規化:它決定 BM25 對長文件(因為自然含有更多詞項匹配)懲罰得多嚴厲。
b 設錯是一個真實且常見的調參失誤。短的技術文件(錯誤日誌、產品標題)適合較低的 b,因為長度變異小;長篇內容(說明文件頁面、文章)通常適合接近預設值的 b。BM25 的核心弱點是詞彙不匹配:如果使用者問「我要怎麼拿回我的錢」,而文件裡只寫了「退款政策」,BM25 找不到任何共享詞元,就回傳不了有用的結果。
密集向量搜尋的機制(以及它何時失靈)
向量搜尋用模型把文字映射成固定維度的嵌入,再透過餘弦相似度或內積找到鄰近向量,通常由近似最近鄰索引加速。HNSW 是 Weaviate、Qdrant 與 Milvus 的主流演算法,用少量召回率換取大規模下的顯著速度提升。
這正是修復 BM25 詞彙不匹配問題的方法:「拿回我的錢」和「退款政策」即使沒有任何共享詞元,在嵌入空間中也會落得很近,因為模型捕捉的是意義,而不是表面形式。在這裡選對模型非常重要。參見我們的嵌入模型選擇指南,以及我們對 Voyage、OpenAI 與 Cohere 嵌入的實測比較(如果你正在權衡選項)。
但密集檢索有自己的盲點,而且恰好是 BM25 盲點的鏡像。我們為客戶建置 RAG 系統時,最常遇到的精確匹配失敗並不稀奇:客服人員查詢某個特定的訂單編號或 SKU,向量索引卻自信滿滿地回傳語義相近但錯誤的結果。通用嵌入模型沒有理由把「SKU-4471」或「ERR_CONN_RST」在向量空間中放到比相近但錯誤的詞元更靠近自己的位置,因為這類字串在訓練資料中很少以獨立、孤立的概念出現。BigData Boutique 用他們自己的 SKU 與錯誤代碼範例記錄了這個完全相同的失敗模式。這是 RAG 部署中一個確立已久、經多方獨立證實的現象,而不是偶發的怪癖。
如何結合 BM25 與向量搜尋: RRF vs Alpha 加權融合
融合 BM25 與向量結果有兩種真正可行的做法,而寫混合搜尋主題的人幾乎沒有人把它們清楚地對比。Reciprocal Rank Fusion(RRF) 出自 Cormack、Clarke 與 Buettcher 的 2009 年 SIGIR 論文,以排名為運算對象:對每份結果列表計算 score = sum(1 / (k + rank_i)),k 通常設為 60。因為它只關心位置、不關心原始分數,RRF 可以毫無問題地處理 BM25 無界分數與餘弦相似度 0 到 1 範圍之間的尺度差異,而且不需要語料級調參。
Alpha 加權(凸)融合的運作方式不同:final = alpha * dense_score + (1 - alpha) * sparse_score,以正規化後的 scores 而非排名為運算對象。它能更好地反映信心強度(相似度 0.95 的向量命中確實看起來比 0.61 的強),但它需要為每個語料庫調 alpha,而且當分數分布偏移時(新的嵌入模型、重建索引的語料、不同的查詢組合),這種調參會悄無聲息地失效。
實務上,選擇取決於你對分數校準的信任程度。用原生 BM25 搭配單一、穩定的嵌入模型時,alpha 加權可以勉強挤出略好的排名,因為它用了實際的分數差距,而不只是位置。但這種校準的漂移比多數人預期的更頻繁。換上新版本的嵌入模型、重新分塊文件,或在上游加一趟重新排序,密集分數的分布就會偏移。alpha=0.6 不再是正確值時,沒有人會被呼叫器叫醒;排名只是悄悄變差一點,除非你定期跑檢索評估,否則很容易錯過。RRF 完全繞開了這個問題,因為它從不看原始分數,只看排名位置,所以重建索引或更換模型不會像對 alpha 加權那樣悄悄地搞壞它。
RRF 不需要語料級調參;alpha 加權則需要隨著資料變動不斷看顧。
各引擎的預設值各不相同。Weaviate 同時提供 RRF 和一個由你明確設定的 alpha 參數。Elasticsearch 透過 retriever API 提供原生 RRF(請在你的部署中確認確切的版本門檻;這個功能在 8.x 系列落地)。Qdrant 透過 Query API 原生支援 RRF。Pinecone 的混合功能通常依賴 alpha 加權的凸組合,而不是直接提供 RRF。如果不確定該用哪個,就從 RRF 開始,它是維護成本較低的預設。
從零打造 RRF: 廠商中立的 Python 範例
我們在競爭對手的指南中找到的每一段 RRF 程式碼,都綁死在某一家廠商的 SDK 上:Weaviate 的客戶端、Qdrant 的客戶端、Pinecone 的客戶端。這裡有一個不依賴任何框架的版本,可以放進任何技術棧,k=60 是標準預設值:
def reciprocal_rank_fusion(result_lists, k=60):
"""
result_lists: list of ranked lists, each a list of document IDs
ordered from most to least relevant.
k: RRF constant (60 is the standard default from Cormack et al., 2009).
Returns: list of (doc_id, fused_score) sorted descending by score.
"""
scores = {}
for result_list in result_lists:
for rank, doc_id in enumerate(result_list, start=1):
scores[doc_id] = scores.get(doc_id, 0.0) + 1.0 / (k + rank)
return sorted(scores.items(), key=lambda item: item[1], reverse=True)
# Example usage:
bm25_hits = ["doc_9", "doc_2", "doc_14", "doc_1"]
vector_hits = ["doc_2", "doc_1", "doc_9", "doc_30"]
fused = reciprocal_rank_fusion([bm25_hits, vector_hits])
for doc_id, score in fused:
print(f"{doc_id}: {score:.4f}")這就是整個演算法。沒有 SDK、沒有廠商鎖定,無論你的兩份排名列表來自 Elasticsearch 和 Faiss 索引,還是來自 Postgres ts_rank 和 pgvector,它都能運作。如果你是自己跑模型而不是呼叫 API,參見用 Ollama 在本機執行嵌入模型。
Postgres + pgvector: 不需要專用向量資料庫的混合搜尋
執行混合搜尋不需要專用的向量資料庫。根據開發者 Pedro Alonso 的 pg_textsearch/pgvector 基準(Postgres 17、pg_textsearch 1.3.0、pgvector 0.8.2、nomic-embed-text 嵌入、BEIR SciFact 資料集),單一 Postgres 執行個體執行原生 ts_rank 只得到 0.07 NDCG@10,遠落後於 BM25 的 0.69、pgvector 的 0.66 與混合的 0.70,全部在同一個執行個體中完成,混合 RRF 的中位延遲約 11.5ms。
0.07 這個數字就是線索:Postgres 內建的 ts_rank 是覆蓋密度排序器,不是真正的 BM25。如果你要在 Postgres 裡得到真正的 BM25 評分,需要擴充套件。pg_textsearch、VectorChord 與 ParadeDB 都加入了原生 ts_rank 所沒有的正統 BM25 式排序。把其中之一與負責密集相似度的 pgvector 配對,用上面的 RRF 函數融合兩份排名列表,你就在單一 Postgres 執行個體中擁有混合搜尋,不需要額外維護任何基礎設施。
單一查詢中這個組合大致長這樣,結合來自 BM25 擴充套件的詞彙排名與來自 pgvector 的向量距離:
WITH lexical AS (
SELECT id, ts_rank_cd(body_tsv, query) AS rank
FROM documents, plainto_tsquery('english', 'refund policy') query
WHERE body_tsv @@ query
ORDER BY rank DESC LIMIT 50
),
semantic AS (
SELECT id, embedding <=> '[0.012, -0.045, ...]' AS distance
FROM documents
ORDER BY distance LIMIT 50
)
SELECT * FROM lexical
FULL OUTER JOIN semantic USING (id);把兩份結果集送進上面的 RRF 函數,你就在一個 Postgres 執行個體中擁有混合搜尋。誠實的極限:這個做法在數百萬列以內都站得住腳,但 Postgres 本來就不是為專用檢索引擎而生的。索引調參要你自己負責,沒有擴充套件時純 ts_rank_cd 仍然不是真正的 BM25,而且你得不到 Weaviate 或 Milvus 原生內建的重新排序或多向量支援。如果你的語料庫是中小型規模,而且你本來就在跑 Postgres,這能幫你省掉一整塊第二基礎設施。超過數千萬份文件,或需要進階重新排序時,專用引擎就值回票價。
如果你正在更廣泛地為技術棧權衡 Qdrant、Chroma 或 pgvector,那是與融合方法本身分開的另一個決策。參見我們的 Qdrant、Chroma 與 pgvector 比較,了解各方案的取捨。
哪些向量資料庫原生支援混合搜尋?
多數現代向量資料庫如今都開箱即用地提供混合搜尋,但它們預設的融合方法有實質差異。
| 引擎 | 原生混合支援 | 融合方法 | 備註 |
|---|---|---|---|
| Weaviate | 是 | RRF 或 alpha 加權 | 兩者都提供,逐查詢選擇 |
| Qdrant | 是 | RRF | 透過 Query API |
| Elasticsearch | 是 | RRF | 透過 retriever API |
| OpenSearch | 是 | 正規化 + 加權和 | 使用「normalization processors」 |
| Vespa | 是 | 原生融合 | 最早支援此功能的引擎之一 |
| Milvus | 是 | 多向量 + 稀疏 BM25 | 透過 combined search API 實現混合 |
| pgvector + Postgres | 是(需擴充套件) | 手動 RRF(見上文) | 需要 ts_rank/BM25 擴充套件才有真正的詞彙評分 |
下定決心之前,先確認確切的版本門檻。整個 2026 年,混合功能在這些引擎上快速落地,API 形狀每個版本都在變。如果要做超越融合機制本身的更廣泛採購決策,參見我們的最佳向量資料庫總整理。
混合搜尋值得引入這些複雜度嗎?
當你的語料庫同時具有精確匹配模式(SKU、ID、罕見詞項)與概念性、改述式查詢時,混合搜尋在架構上是正確的。如果你的語料庫兩者皆無(純敘事內容,沒有人會用精確字串搜尋的識別碼),你可能是為了一個幾乎感覺不到的提升,增加了融合的複雜度。
想想「純敘事」實際上是什麼樣子:公司部落格存檔、充滿長篇散文式 runbook 的內部工程 wiki、沒有人用產品 ID 或工單編號搜尋的說明文件站。在這些語料庫中,純向量搜尋通常就能拿到大部分價值,而融合步驟只是多了第二趟檢索和一個從此有人要負責的參數,換來一個四捨五入等於雜訊的提升。對照支援工單系統或電商目錄,那裡 SKU、訂單編號與型號代碼不斷出現在真實使用者查詢中。這才是真正的測試:從你自己的日誌中抽十筆真實查詢,數數有幾筆含有精確識別碼,是改述型嵌入模型永遠放不對位置的那種。零筆,跳過混合。超過一兩筆,就動手建。
混合搜尋不是萬能升級;如果你的語料庫沒有 SKU、ID 或罕見詞項查詢,你可能是在為一個永遠感覺不到的提升增加融合複雜度。
成本是真實的,但有限:第二趟檢索、一個融合步驟,以及一個從此有人要負責的加權參數。我們刻意不在這裡引用延遲數字,因為四處流傳的數字來自未具名的架構與未具名的硬體,而你的環境會不一樣。下決定之前,先在你自己的語料庫上量測。兩篇 Hacker News 討論串捕捉了實務工作者在這裡真正的拉扯:〈Hybrid Search Is Just the Beginning: Optimizing the R in RAG〉與〈Better RAG Results with Reciprocal Rank Fusion and Hybrid Search〉。兩篇討論串都反對把混合當成貨物崇拜式的最佳實踐直接採用,而不先檢查你的語料庫是否真的有它要解決的查詢模式。動手之前,值得先了解如何真正量測檢索品質:NDCG 和 recall@k 數字只有在對照你自己的語料庫時才有意義,而不是對照某個基準資料集。
我們的看法:任何面對使用者支援、電商或工單查詢的 RAG 系統,預設都該用混合。這些負載幾乎總是把識別碼和自然語言混在一起。純敘事語料(長篇文件、敘事型 wiki)則先跳過,等你量測出單一方法檢索確實留下了缺口再說。
關於作者
Mert Batur 是 Techsy.io 的共同創辦人,團隊為 B2B 客戶交付 AI 代理、自動化系統與語音/SDR 流水線。他寫的主題是 Techsy 團隊在生產環境中實際使用的 LLM 工具棧。LinkedIn 上聯繫。
常見問題
RAG 中的混合搜尋是什麼?
混合搜尋把 BM25(關鍵字)與向量(語義)檢索當成獨立的兩趟,對同一個查詢各跑一次,再用融合演算法(通常是 Reciprocal Rank Fusion)合併兩份排名列表。它同時接住精確匹配查詢與改述式概念查詢,而這兩類查詢任何單一方法都無法獨自處理。
BM25 和向量搜尋是一樣的東西嗎?
不是。BM25 是稀疏詞彙式搜尋,為精確的詞項重疊與稀有度評分。向量搜尋是使用嵌入與相似度運算的密集語義式搜尋。它們是兩種優勢正好相反的檢索方法,混合搜尋是結合,而不是取代其中任何一個。
如何結合 BM25 與向量搜尋?
對同一個查詢各自獨立執行兩種檢索方法,再融合兩份排名結果列表,最常見的做法是用 Reciprocal Rank Fusion,對每份列表加總 1 / (k + rank)。Alpha 加權分數組合是替代方案,但它需要 RRF 所不需要的語料級調參。
什麼是 Reciprocal Rank Fusion(RRF)?
RRF 是 Cormack、Clarke 與 Buettcher 2009 年 SIGIR 論文中的融合演算法,對每份文件加總 1 / (k + rank) 來結合多份排名列表,k 通常設為 60。它以排名位置而非原始分數為運算對象,因此在不同檢索方法的尺度差異之間保持穩定。
RRF 和 alpha 加權融合有什麼差別?
RRF 結合排名,不需要語料級調參。Alpha 加權融合用可調的 alpha 參數結合正規化後的 scores,能更好地反映信心強度,但只要分數分布偏移(例如重建索引或更換模型之後),就需要持續重新調參。
什麼時候該用混合搜尋,而不是純向量搜尋?
當你的查詢同時混有精確識別碼(SKU、訂單編號、錯誤代碼)與自然語言概念問題時,用混合搜尋,支援、電商與工單系統通常如此。純敘事、沒有識別碼的內容則跳過,那類內容增加的融合複雜度很可能看不到可量測的提升。
為什麼向量搜尋會漏掉 SKU 或錯誤代碼這類精確匹配?
嵌入模型從一般語言模式中學到東西,而「SKU-4471」或「ERR_CONN_RST」這類字串在訓練資料中很少以獨立、孤立的概念出現。模型沒有強烈的理由,把那串精確字串放到比語義相關但錯誤的詞元更靠近自己的位置。
哪些向量資料庫原生支援混合搜尋?
截至 2026 年,Weaviate、Qdrant、Elasticsearch、OpenSearch、Vespa 與 Milvus 都原生提供混合搜尋,不過預設融合方法各異(RRF vs alpha 加權 vs 正規化)。Postgres 搭配 pgvector 也能執行混合搜尋,但需要 BM25 擴充套件,因為原生 ts_rank 不是真正的 BM25。
混合搜尋值得引入額外的複雜度嗎?
對混合了精確匹配與概念查詢的語料庫來說,值得。在 WANDS 基準上,純 RRF 已經勝過任何單一方法(0.7068 vs BM25 的 0.6983),調參變體更達到 7.4% 的增幅(0.7497)。對沒有識別碼的純敘事語料庫來說,第二趟檢索與它所需的融合調參,可能超過你感覺不到的提升。下決定之前先量測。
Postgres/pgvector 能在沒有專用向量資料庫的情況下做混合搜尋嗎?
可以。把負責密集相似度的 pgvector,與 pg_textsearch、VectorChord 或 ParadeDB 這類真正的 BM25 擴充套件配對(Pedro Alonso 的 pg_textsearch/pgvector 基準中,純原生 ts_rank 只得到 0.07 NDCG@10,而混合為 0.70),再用 RRF 融合兩份排名列表,全部在一個 Postgres 執行個體中完成。
兩種檢索方法單獨執行時都留有真實的缺口:BM25 漏掉改述,向量搜尋漏掉精確識別碼,而用 RRF 融合兩者是維護成本較低的補齊方式。如果你正在權衡自己動手,還是找一個交付過 RAG 檢索的團隊合作,我們的RAG 應用建置完整指南涵蓋了下一步;如果你寧願讓 Techsy 和你一起建,歡迎聯繫。