云选科普

RAG 混合检索落地指南:从召回到重排序

面向开发者和小团队,拆解稠密检索、BM25、RRF 与 Reranker 的职责,并给出评测、选型和生产上线的渐进路径。

很多知识库在演示阶段表现不错,一接入真实业务就开始答非所问。常见原因并不是大模型能力不足,而是检索层只采用稠密向量:它擅长理解同义表达,却可能忽略错误码、产品型号、年份和企业内部缩写。更稳妥的做法,是让关键词检索与向量检索分别召回候选,再经过融合和重排序,把真正有用的片段送入大模型。

本文从故障定位、架构设计、评测和成本四个角度,给出一条适合中小团队逐步落地的 RAG 混合检索路径。

先判断问题发生在哪一层

RAG 回答错误不等于检索错误。排查时应把链路拆成四段:文档解析与切块、候选召回、候选排序、答案生成。

可以先记录每次请求的查询词、召回文档 ID、各路排名、重排分数和最终引用片段,然后逐层判断:

  • 正确片段根本没有进入候选集:属于召回问题,应检查切块、分词、元数据过滤和每路 top_k
  • 正确片段进入候选集,却排不到前面:属于排序问题,适合引入融合策略或 Reranker。
  • 正确片段已排在前列,但答案仍错误:应检查提示词、上下文拼接、引用约束和生成模型。
  • 文档本身过期或互相冲突:需要先做内容治理,换模型通常解决不了。

这个分层很重要。Reranker 只能调整已有候选的顺序,无法找回召回阶段已经漏掉的内容。

为什么要同时保留稠密与稀疏检索

稠密向量检索把文本映射到语义空间,适合处理口语化问题、同义改写和表达差异。例如“公司邮箱进不去”和“企业邮件登录失败”在字面上不同,但语义接近。

BM25 一类稀疏检索依赖分词、倒排索引和词项统计,对精确字符更敏感,适合以下查询:

  • 错误码、工单号和接口名,如 E-503RequestId
  • 产品型号、版本号和日期;
  • 人名、部门缩写与企业内部术语;
  • 用户明确要求出现某个词的查询。

两者不是替代关系。稠密检索负责“意思相近”,稀疏检索负责“字面命中”。如果知识库既包含自然语言说明,也包含大量编号、配置项和专有名词,RAG 混合检索通常比单一路径更适合。

不过,稀疏检索的效果依赖分词和字段设计。中文知识库要检查产品名、连字符错误码和中英文混排是否被正确切分;标题、正文、标签也不应未经评估就赋予相同权重。

用 RRF 合并两路结果

稠密相似度与 BM25 分数不在同一尺度上,直接相加容易受模型、索引和数据分布影响。Reciprocal Rank Fusion(RRF)绕开原始分数,只根据文档在各结果列表中的名次融合:

RRF(d) = Σ 1 / (k + rank_i(d))

其中 rank_i(d) 是文档在第 i 路检索中的排名,k 是平滑常数。某篇文档如果在两路结果中都靠前,融合后通常也会获得更高名次。RRF 的优势是实现简单、无需先校准两类分数;不足是不能自动理解某一路在特定业务中更可靠。

一个产品无关的实现可以很短:

def rrf_fuse(result_lists, rank_constant=60, limit=40):
    scores = {}
    documents = {}

    for results in result_lists:
        for rank, item in enumerate(results, start=1):
            doc_id = item["id"]
            documents[doc_id] = item
            scores[doc_id] = scores.get(doc_id, 0.0) + 1.0 / (
                rank_constant + rank
            )

    ordered = sorted(scores, key=scores.get, reverse=True)
    return [documents[doc_id] for doc_id in ordered[:limit]]

rank_constant=60 只能作为实验起点,而不是通用最优值。团队应在自己的标注集上测试不同候选规模、融合方式和延迟,再确定生产参数。如果查询包含租户、权限、时间或文档状态限制,应在检索阶段应用一致的元数据过滤,避免融合后混入用户无权访问或已经失效的内容。

何时需要再加 Reranker

融合解决的是“如何合并候选”,重排序解决的是“哪些候选最能回答当前问题”。Cross-encoder 或生成式 Reranker 会同时读取查询和文档片段,通常能捕捉比向量相似度更细的关联,但也增加推理成本和延迟。

适合引入 Reranker 的场景包括:

  • 问答准确性要求较高,错误引用会产生明显业务风险;
  • 两路召回已有较高覆盖率,但前几名经常排序错误;
  • 文档主题相似,只有操作对象、条件或版本不同;
  • 可以接受增加一次模型调用,并有超时和降级机制。

更经济的链路是先扩大廉价召回,再对有限候选精排:

用户查询
  ├─ 稠密向量召回 ─┐
  └─ BM25 召回 ────┤
                    └─ RRF 去重融合
                          └─ Reranker 精排
                                └─ 取少量片段生成答案

不要把全部文档交给 Reranker。候选越多,延迟和费用越高;候选过少,又可能在进入精排前漏掉答案。合理范围应通过离线评测与线上延迟预算共同确定,而不是照搬固定数字。

用真实问题建立评测集

只凭几个演示问题判断效果,很容易得到错误结论。可以从脱敏后的搜索日志、客服工单和人工反馈中抽取代表性问题,由熟悉业务的人标注相关文档或片段,并覆盖以下类型:

  • 自然语言改写与同义问法;
  • 编号、型号、日期和内部缩写;
  • 多条件查询与否定表达;
  • 没有答案或权限不足的问题;
  • 内容过期、版本冲突和跨文档回答。

评测至少分三层:

  1. 召回层:使用 Recall@K 检查相关片段是否进入候选集。
  2. 排序层:使用 MRR 或 nDCG@K 判断相关结果是否足够靠前。
  3. 业务层:观察引用正确率、无答案识别率、人工转接率、端到端延迟和单次请求成本。

先提高召回覆盖,再优化排序;否则更换 Reranker 只是在错误候选中重新排队。上线前还应为纯向量、混合检索、混合检索加重排三套方案做同一数据集对照,避免只看单项指标。

中小团队的渐进式落地方案

不同阶段不必一次堆齐所有组件:

  • 数据量小、查询以口语为主:先用向量检索,重点做好切块、元数据和评测日志。
  • 错误码与专有名词频繁漏召回:补充 BM25,采用 RRF 或经过验证的加权融合。
  • 候选能召回但前列不准:再引入 Reranker,并为超时配置跳过精排的降级路径。
  • 进入生产环境:加入租户隔离、文档权限、敏感信息脱敏、索引版本、缓存和全链路观测。

选型时还要考虑运维边界。如果现有搜索引擎已经承担关键词检索,可以与向量库并行召回;如果使用的向量数据库原生支持全文检索与多路融合,则可减少一套系统,但仍需验证中文分析器、过滤语义、备份和扩容能力。架构越集中不代表效果自然更好,最终仍要以自己的评测集为准。

上线前容易忽略的细节

  • 对查询和文档采用兼容的规范化策略,保留有意义的大小写、连字符和版本号。
  • 文档更新或删除时,同步维护稠密索引、稀疏索引与权限元数据。
  • 缓存键应包含租户、权限范围和索引版本,避免跨用户复用敏感结果。
  • 记录检索与重排所用的模型版本,模型升级时进行回归评测。
  • 为 Reranker 设置超时、并发上限和降级策略,避免精排服务拖垮整条问答链路。
  • 对“没有可靠答案”的查询允许拒答,不要强迫模型从低相关片段中拼出结论。

RAG 检索优化的重点不是不断增加模型,而是明确每一层负责什么:稠密检索覆盖语义变化,稀疏检索捕获精确词项,RRF 合并不同排名,Reranker 优化前列顺序,评测体系验证收益。按“先定位、再补召回、后做精排”的顺序推进,中小团队更容易在准确率、延迟、成本和运维复杂度之间取得可控平衡。

继续浏览

还想继续看,可以再看这些文章

适合想继续在同一主题下横向阅读、对比不同切入角度的用户。