RAG 混合检索与 RRF:向量检索、BM25 如何组合
单独使用向量检索,擅长理解“意思相近”的问题;单独使用 BM25,擅长匹配产品名、错误码、接口名和专有词。企业知识库往往同时包含自然语言说明和大量精确术语,因此 RAG 检索通常需要把两类信号组合起来。
混合检索不是把两个结果列表简单拼接,而是分别召回、统一过滤和去重,再用一个可解释的融合方法计算候选排序。RRF(Reciprocal Rank Fusion)是其中较容易落地的一种方案。
为什么向量检索和 BM25 互补
向量检索把 Query 和文档映射到向量空间,通过相似度寻找语义相关内容。它对同义表达和自然语言提问比较友好,但可能忽略低频关键词、版本号或精确错误码。
BM25 根据词项频率、逆文档频率和文档长度计算相关性。它对 ERR-4012、CreateOrder、产品型号等精确词很有效,但对改写、同义词和表达差异不够鲁棒。
可以这样理解两者的分工:
- 向量召回回答“这段内容是不是在讨论同一个问题”。
- BM25 召回回答“这段内容是否包含用户明确提到的词”。
- 混合融合在两种信号都可用时扩大候选覆盖,再交给重排模型或规则做最后筛选。
RRF 的计算方式
RRF 不要求不同检索器返回可直接比较的分数,只使用每个结果在各自列表中的名次。常见公式是:
RRF_score(d) = Σ 1 / (k + rank_i(d))其中 d 是文档或 Chunk,rank_i(d) 是它在第 i 个检索结果中的排名,k 是平滑常数。一个 Chunk 如果同时出现在向量和 BM25 的前列,会累积更高的融合分数;只在单个列表中靠后的 Chunk,得分会相对较低。
例如,设置 k = 60:
Chunk A: 向量排名 1,BM25 排名 4
score = 1 / 61 + 1 / 64
Chunk B: 只在向量排名 2 出现
score = 1 / 62RRF 的优点是简单、稳定、容易解释。它不会自动理解业务重要性,也不会替代重排模型。对于需要考虑时效、权限、文档质量或字段权重的系统,还要在融合前后加入明确规则。
一条可维护的混合检索链路
Query
-> 规范化与权限条件构建
-> 向量召回 topK_vector
-> BM25 召回 topK_bm25
-> 权限、租户、状态和版本过滤
-> 按稳定 chunk_id 去重
-> RRF 融合
-> 可选 Reranker 重排
-> 上下文压缩与生成权限过滤必须进入检索链路,而不是只在生成后隐藏引用。否则模型可能已经接收到用户无权访问的内容。文档状态、租户和版本也应尽可能在召回阶段过滤,减少无效候选进入上下文。
去重时不要用正文全文作为唯一键,因为相同正文可能来自不同文档版本。更适合使用稳定的 document_id + chunk_id + version,并在版本替换时明确是否保留旧版本。
topK 和融合参数怎么调
先确定目标是提高候选覆盖,还是降低上下文噪音。向量和 BM25 的候选数量可以不同:术语很多的知识库可以提高 BM25 候选数,长文档和自然语言问题则可能需要更多向量候选。
建议用固定评估集进行网格试验,至少记录:
- 向量召回 Top-K 的证据命中率。
- BM25 召回 Top-K 的证据命中率。
- 融合后的 Recall@K 和 MRR。
- 去重后候选数量与重复比例。
- 重排后的答案准确性、延迟和 Token 成本。
不要只用最终回答是否“看起来正确”来调参。先确认证据能否进入候选集,再观察重排和生成是否正确使用证据,这样才能定位问题属于召回、排序还是生成。
生产实现中的边界
查询词处理
BM25 对词法很敏感。可以根据业务维护同义词、别名和错误码词典,但不要无条件扩展 Query,否则会引入大量噪音。向量检索也可以使用查询改写,但应记录原始 Query 和改写结果,方便审计和评估。
文档质量与时效
融合排序不能弥补错误文档。索引任务要有文档版本、更新时间和删除状态;同一事实存在多个版本时,应明确最新版本策略,并避免旧版本在 BM25 中因词频高而长期占据前列。
超时和降级
向量服务或 BM25 服务不可用时,系统应有明确降级策略:允许单路检索、返回可解释的暂时不可用,或使用缓存结果。每次召回都应记录耗时、结果数和错误类型,而不是只记录最终 LLM 响应。
常见误区
- 直接比较向量距离和 BM25 分数,忽略两者量纲不同。
- 两路结果简单拼接,导致一路结果淹没另一路。
- 融合前不做权限和版本过滤,把无效内容交给模型。
- 用正文相等做去重,误删不同来源或不同版本的证据。
- 只调 RRF 的
k,却没有先建立可重复的评估集。
