文章

LLM 混合检索路由生产实战:用查询分类、RRF 与回退策略稳住 RAG 召回

本文深入讲解大模型RAG系统中混合检索路由的生产实践,覆盖查询分类、BM25与向量检索融合、RRF排序算法、权重调节策略、回退治理与上线检查清单,帮助团队减少漏召回与检索策略失控。

背景问题:RAG 质量回退经常不是生成模型的问题

很多 RAG 系统上线后,团队习惯把答案质量问题归因到「模型不够强」。但在生产排查中,真正的根因常常更靠前:该找回来的证据根本没有被召回,或者召回结果顺序不稳定,导致模型拿到的上下文本身就偏了。

单纯依赖向量检索时,系统擅长理解语义相似问题,但可能漏掉错误码、产品型号、合同编号、API 名称、日期、版本号等需要精确匹配的内容。单纯依赖关键词检索时,系统对精确词更敏感,但可能漏掉同义表达、改写后的自然语言问题和跨语言表达。Azure AI Search 文档明确把混合检索定义为同时运行全文检索和向量查询,并用 RRF 合并结果;Pinecone 文档同样指出语义检索可能漏掉精确关键词,而词法检索可能漏掉同义词和转述表达。

因此,生产级 RAG 不应只问「用不用向量数据库」,而要问:不同查询应该走什么召回策略,如何融合结果,什么时候回退,如何证明这次召回足够支撑答案。


核心原理:混合检索不是简单多查一次

一个稳定的混合检索链路通常包含三层。

第一层:稀疏检索

典型实现是 BM25、BM25F 或稀疏向量。它对字面匹配、字段权重、编号、实体名和术语非常敏感,适合找「明确出现过的词」。

第二层:稠密检索

即 Embedding 向量检索。它把查询和文档映射到向量空间中,适合处理同义、改写、概念相近和自然语言问题。

第三层:融合层

融合层不是简单把两个分数相加,因为 BM25、稠密向量、稀疏向量的分数范围并不天然一致。Azure AI Search 的混合检索使用 Reciprocal Rank Fusion(RRF);Elasticsearch 文档也说明 RRF 可以把多个具有不同相关性指标的结果集组合成一个结果集,且不要求这些指标彼此相关。


RRF 为什么适合做默认融合策略

RRF 的直觉很简单:一个文档如果在多个召回列表中都排得靠前,就应该获得更高的融合排名。它不直接比较 BM25 分数和向量相似度分数,而是看每个结果在各自列表里的名次。

简化公式如下:

score(document) = Σ 1 / (rank_constant + rank_in_each_result_list)

其中 rank_constant 用来控制靠后结果对最终排名的影响。Elasticsearch 的 RRF 文档中提供了类似公式,并暴露了 rank_constantrank_window_size 等参数;Azure 的 RRF scoring 文档也说明,RRF 会对多个并行查询产生的排序结果重新赋分并合并。

这让 RRF 很适合作为混合检索的第一版默认策略:它比直接加权原始分数更稳,也比手工写一堆排序规则更容易解释。


查询路由比全量混合更重要

很多团队把「混合检索」理解成每个请求都同时跑 BM25 和向量检索,然后合并。这是可用的起点,但不是最优的生产形态

更稳的做法是增加一个轻量的查询分类器,把查询分成几类:

查询类型特征推荐策略
精确查询包含错误码、订单号、类名、函数名、产品型号、版本号优先 BM25 / 稀疏检索
语义查询完整自然语言问题,没有明确实体优先向量检索
混合查询既有实体又有自然语言意图BM25 + 向量 + RRF
低置信查询检索结果分数低、召回数量少、稀疏稠密分歧大触发回退或二次检索

这样做的目的不是减少一次查询成本,而是避免把所有请求都塞进同一个权重模板里


工程落地:从全量混合走向可解释路由

在线链路中可以把一次检索请求拆成一个 RetrievalPlan。它描述这次请求使用哪些检索器、各自 topK、融合方式、回退条件和是否进入重排。

type RetrievalMode = "keyword" | "vector" | "hybrid";

type RetrievalPlan = {
  mode: RetrievalMode;
  bm25TopK: number;
  vectorTopK: number;
  fusion: "rrf" | "weighted" | "none";
  rankWindowSize: number;
  useReranker: boolean;
  fallback: {
    minCandidates: number;
    maxVectorDistance?: number;
    retryWithHybrid: boolean;
  };
};

function buildRetrievalPlan(query: string): RetrievalPlan {
  const hasCodeLikeToken = /[A-Z]{2,}-\d+|v\d+\.\d+|[a-zA-Z_]+\(\)/.test(query);
  const isShortExactQuery = query.trim().split(/\s+/).length <= 3;
  const looksNaturalLanguage = /什么|如何|为什么|怎么|how|why|what/i.test(query);

  if (hasCodeLikeToken || isShortExactQuery) {
    return {
      mode: "keyword", bm25TopK: 50, vectorTopK: 0,
      fusion: "none", rankWindowSize: 50, useReranker: false,
      fallback: { minCandidates: 5, retryWithHybrid: true }
    };
  }

  if (looksNaturalLanguage) {
    return {
      mode: "hybrid", bm25TopK: 50, vectorTopK: 50,
      fusion: "rrf", rankWindowSize: 50, useReranker: true,
      fallback: { minCandidates: 8, maxVectorDistance: 0.45, retryWithHybrid: true }
    };
  }

  return {
    mode: "vector", bm25TopK: 0, vectorTopK: 40,
    fusion: "none", rankWindowSize: 40, useReranker: true,
    fallback: { minCandidates: 5, retryWithHybrid: true }
  };
}

这段代码只是示意。真正上线时,查询分类器可以先从规则开始,再逐步引入轻量分类模型。关键是把策略配置化,并把每次检索计划落日志,方便定位「为什么这次没有召回」。


RRF 参数要和召回窗口一起调

RRF 的效果高度依赖召回窗口。如果 BM25 只取 top 5,向量取 top 100,再用 RRF 融合,稀疏侧很容易失去足够候选。反过来,如果两个列表都取太大,延迟、内存和后续重排成本会上升。

一般可以从以下配置开始:

retrieval:
  default_plan:
    bm25_top_k: 50
    vector_top_k: 50
    fusion: rrf
    rank_constant: 60
    rank_window_size: 50
    final_top_k: 8

  exact_query_plan:
    bm25_top_k: 80
    vector_top_k: 20
    fusion: rrf
    final_top_k: 8

  semantic_query_plan:
    bm25_top_k: 30
    vector_top_k: 80
    fusion: rrf
    final_top_k: 8

Azure 文档中提到混合查询会并行执行全文和向量查询,并由 RRF 合并统一结果;Elasticsearch 文档也说明 rank_window_size 越大通常有利于相关性,但会带来性能成本。因此,调参不能只看最终答案,要同时看召回窗口、融合前候选、融合后候选和重排前后变化


权重不是全局常量

Weaviate 的混合检索提供 alpha 参数,用于调节关键词和向量组件的相对权重;Pinecone 文档则提醒,单索引同时存稠密和稀疏向量时,BM25 或稀疏分数与稠密向量范围并不天然归一,若没有显式权重,稀疏部分可能主导分数。

生产上更建议按查询类型设置权重,而不是全局固定:

场景推荐权重倾向
错误码、SKU、合同编号BM25 权重更高
「这段话是什么意思」「如何配置权限」向量权重更高
法务、医疗、保险条款先保证精确命中,再允许语义扩展
多语言知识库优先验证向量跨语言召回,再补充关键词回退

权重调节不是为了追求一个漂亮参数,而是为了让查询意图和召回策略一致


回退策略:召回不足时不要让模型硬答

混合检索的线上风险之一是「看起来有结果,但结果不够支撑答案」。因此需要在检索层定义回退条件。

常见回退信号

  • 候选数量低于阈值
  • 关键词检索命中很少,但查询包含明显实体
  • 向量检索距离过高或相似度过低
  • BM25 与向量结果几乎没有交集,且 RRF 后 top 结果分散
  • 重排器分数低,或 top1 与 top2 差距过小
  • 最终证据片段缺少标题、时间、来源或权限字段

触发回退后的处理顺序

  1. 放宽过滤条件
  2. 扩大 topK
  3. 切换权重
  4. 启用另一个字段索引
  5. 进入跨索引检索
  6. 返回「未找到足够证据」或要求用户补充关键词

核心原则:不要让 LLM 在证据不足时继续编一个答案。


适用场景

技术文档与知识库

开发者可能搜索 ERR_AUTH_401,也可能问「为什么登录后还是提示无权限」。前者需要精确匹配,后者需要语义理解。

企业制度、合同、保险条款、政策文件

用户可能输入条款号、产品名、日期,也可能问一个自然语言问题。纯向量检索容易错过条款编号,纯关键词检索又可能漏掉改写表达。

客服与工单

历史问题常常混合产品型号、错误现象、截图 OCR 文本和用户口语描述,需要把精确词和语义召回一起看。

电商、商品与库存问答

型号、品牌、规格、别名、用途会同时出现,单一检索策略很难稳定覆盖。


常见误区

误区一:混合检索等于质量一定提升

混合检索只是增加候选来源,并不自动保证答案更准。如果切块质量差、元数据缺失、权限过滤错误、topK 太小或融合参数不合理,混合检索同样会带来噪声。

误区二:把 BM25 分数和向量分数直接相加

不同检索器的分数范围不同,直接相加很容易让某一侧主导结果。Pinecone 文档中也特别指出稀疏和稠密分值形态不同,需要显式权重。RRF 的价值就在于降低这种分数不可比带来的风险。

误区三:只调 topK,不看召回分布

topK 变大可能提升召回,也可能只是增加重排噪声。要同时观察 BM25 命中、向量命中、融合交集、最终进入上下文的片段,以及最终答案引用的证据。

误区四:忽略过滤条件的位置

权限、时间、产品线、租户过滤放在检索前还是检索后,会影响召回范围和融合结果。Azure 文档中的混合查询也提醒过滤可以在处理流程的不同阶段应用,是否使用后过滤需要测试确认。生产环境里,权限过滤必须正确,相关性调优不能绕过权限边界。


上线检查清单

上线前至少准备四类查询集:

查询类型测试目标验证重点
精确查询错误码、编号、API 名、类名、配置项、合同条款号BM25 / 稀疏侧是否稳定命中
语义查询用户不使用文档原词但意图明确向量召回是否找到概念相近片段
混合查询同时包含实体和自然语言描述RRF 融合是否比单路召回更稳
失败查询知识库中无答案、权限不可见、时间范围不匹配系统是否能拒答、回退或提示补充信息

建议记录的指标:

  • Recall@K、MRR、NDCG
  • BM25 命中数、向量命中数、融合交集比例
  • RRF 前后 top 文档变化
  • 重排前后 top 文档变化
  • 最终进入 LLM 上下文的片段来源
  • 回退触发率、拒答率、人工复核通过率
  • p95 检索延迟和端到端回答延迟

参考资料

常见问题

混合检索是不是一定比纯向量检索好?
不是。混合检索更适合同时存在精确词、专有名词、编号、自然语言问题和同义表达的知识库。若数据短小、查询模式稳定、关键词非常明确,纯BM25或纯向量也可能更简单。
RRF和直接加权相加有什么区别?
RRF主要融合不同召回列表中的排名位置,不强依赖不同检索器原始分数的可比性;直接加权相加通常需要先处理BM25、稠密向量、稀疏向量等分数范围差异。
线上应该固定一个alpha权重吗?
不建议只依赖一个全局权重。更稳妥的方式是按查询类型、字段、租户和业务场景分层配置,并用离线回放和灰度流量验证。
混合检索是否必须配合重排器?
不一定。RRF可作为第一阶段融合策略,重排器适合用在候选已足够但排序仍不稳定的场景。若延迟预算紧,可只对高价值或低置信请求启用重排。