ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

RAG 为什么要混合检索:从关键词、向量到重排与引用溯源

2026/8/12 11:50:29 拓冰建站 浏览量
RAG 为什么要混合检索:从关键词、向量到重排与引用溯源

本文定位:RAG 检索工程 / 搜索排序 / 企业知识库

示例环境:PostgreSQL + pgvector、Java 21、BM25 思路、Rerank 服务。指标阈值需要根据业务风险重新设定。

摘要

只使用向量相似度的 RAG,面对错误码、产品型号、合同编号和精确字段时往往不如关键词检索;只使用关键词,又无法很好理解同义表达和自然语言问题。混合检索的价值不是把两套结果简单拼起来,而是利用不同检索器的优势,再用统一的排序和证据治理把结果变成可解释的上下文。

本文从一个“设备故障知识库”出发,比较 BM25、向量检索、混合召回和 Rerank 的职责,给出候选集融合、引用元数据、Java 数据结构和评测方法,并说明为什么“召回更多”不一定代表回答更好。

一、不同检索器擅长什么

检索方式擅长不擅长
关键词/BM25错误码、编号、专有名词、精确短语同义改写、口语表达
向量检索语义相近、自然语言描述精确数字、罕见型号、否定条件
混合召回兼顾精确与语义参数更多,调试复杂
Rerank在候选集中判断问答相关性无法挽回第一阶段完全没召回的证据

例如用户问“告警 E102 连续出现三次后,先做什么”,关键词检索容易直接命中E102;用户问“采集链路不稳定时怎样处理”,向量检索更容易找到“检查采集链路”的段落。高质量系统通常让两者并行产生候选。

二、检索链路

用户问题

规范化/提取实体

关键词召回

向量召回

候选去重

Rerank

权限/版本/时间过滤

上下文压缩与引用编号

模型生成

注意权限和版本过滤既可以在第一阶段做,也应该在最终上下文组装前再做一次。多层过滤是为了防止缓存、重排服务或异步流程引入过期、越权文档。

三、关键词和向量结果如何融合

最简单的方式是给两类结果设置权重:

score = alpha * normalized_vector_score + (1 - alpha) * normalized_keyword_score

但两个检索器的分数分布往往不同,不能直接相加。更稳妥的做法是 Reciprocal Rank Fusion:

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

其中rank_i(d)是文档在第 i 个检索器中的排名,k是平滑常数。RRF 不依赖原始分数尺度,适合快速建立混合检索基线。

publicList<Candidate>rrfMerge(List<Candidate>keyword,List<Candidate>vector,intk,intlimit){Map<Long,Double>score=newHashMap<>();for(inti=0;i<keyword.size();i++){score.merge(keyword.get(i).chunkId(),1.0/(k+i+1),Double::sum);}for(inti=0;i<vector.size();i++){score.merge(vector.get(i).chunkId(),1.0/(k+i+1),Double::sum);}returnscore.entrySet().stream().sorted(Map.Entry.<Long,Double>comparingByValue().reversed()).limit(limit).map(e->Candidate.withFusionScore(e.getKey(),e.getValue())).toList();}

融合前要按 Chunk ID 去重,并保留每个候选来自哪些检索器、原始排名和分数。否则出了问题只能看到最终排序,无法判断是关键词错了、向量错了还是 Rerank 错了。

四、Rerank 应该放在哪里

Rerank 适合对 20~100 条候选做精细相关性判断,不适合直接扫全库。它可以理解问题和候选文本的交互关系,通常比单独的向量相似度更准确,但会增加网络调用、延迟和成本。

publicList<Candidate>retrieve(QueryContextquery){List<Candidate>candidates=merge(keyword.search(query.text(),30),vector.search(query.embedding(),30));List<Candidate>permitted=permissionFilter.filter(candidates,query.auth());List<RerankItem>items=permitted.stream().map(c->newRerankItem(c.chunkId(),c.content())).toList();returnreranker.rank(query.text(),items,8);}

Rerank 不能代替权限过滤。先发送越权文本给外部 Rerank 服务,再在返回结果里过滤,已经失去安全意义。更安全的顺序是先过滤租户和权限,再重排。

五、查询规范化要克制

可以让模型把问题拆成错误码、设备型号、时间范围和意图,但查询改写不能改变原始条件。建议同时保留原问题和规范化结果:

{"original":"E102 连续三次后先做什么?","keywords":["E102","连续三次"],"semantic_query":"错误码E102重复出现后的首要处理步骤","must_keep":["E102","三次"]}

如果改写模型把否定条件删掉,检索结果可能完全变质。例如“不是电源问题时如何排查”不能改成“电源问题如何排查”。对高风险检索,重要实体应通过规则或实体识别校验。

六、引用溯源:让每条结论都能回到原文

上下文组装时给每个 Chunk 生成稳定引用编号,编号和文档 ID、版本、页码、章节一一对应。模型只输出[C1][C2],前端再把编号渲染成可点击来源。

publicrecordEvidence(StringcitationId,longchunkId,Stringtitle,intversion,Integerpage,Stringcontent){}publicStringbuildContext(List<Candidate>candidates){returnIntStream.range(0,candidates.size()).mapToObj(i->{Candidatec=candidates.get(i);Stringid="C"+(i+1);return"["+id+"] 文档="+c.title()+" 版本="+c.version()+" 页码="+c.page()+"\n"+c.content();}).collect(Collectors.joining("\n\n"));}

生成后校验引用编号是否存在,且引用内容是否在当前候选集合中。引用了不存在的[C9],或者引用了一个没有支持该结论的 Chunk,都应视为回答质量问题。

七、评测必须拆分检索和生成

如果最终答案错了,不一定是模型生成能力差,也可能是正确证据根本没有被召回。评测分两层:

  • 检索评测:Recall@K、MRR、nDCG、过滤正确率。
  • 生成评测:引用支持率、答案覆盖率、拒答准确率、格式通过率。

可以把每条错误归因到以下类别:未召回、召回但排序靠后、证据冲突、上下文过长、模型推理错误、引用错误和权限错误。错误归因比只记录“答案不对”更有优化价值。

八、典型失败案例

错误码命中了,但处理步骤不对

可能是多个版本文档都包含同一错误码,旧版本排名更靠前。解决方案是把版本状态作为过滤条件,并让版本信息进入 Rerank 输入。

语义相似度很高,但没有关键数字

用户问“连续三次”,召回的内容只包含“重复告警”。可以对数字、错误码和型号做关键词增强,并要求证据覆盖这些实体。

候选变多,回答反而变差

召回了互相冲突的制度和历史记录。应按版本、发布日期和文档状态做治理,并在冲突时主动提示“存在多个版本,需要确认适用范围”。

引用很多,但不支持结论

模型为了显得可靠而堆引用。可以限制每个结论最多引用 2~3 条,并要求引用内容包含相关实体或条件。

九、性能和成本

建议先测四个阶段:关键词查询、向量查询、Rerank 调用、模型生成。Rerank 候选从 60 条增加到 200 条,准确率可能略有提升,但延迟和成本会显著增加。对高频固定问题可以缓存召回结果,但缓存键必须包含租户、权限、知识库版本和查询归一化结果。

可以采用分层策略:普通问题只做混合召回;高风险问题增加 Rerank 和引用校验;知识库外问题走拒答检测。不同场景使用同一套“最重链路”,通常会造成成本浪费。

十、上线检查清单

  • 关键词和向量结果是否都保存原始排名。
  • 融合时是否去重、归一化并保留来源。
  • 权限过滤是否发生在发送给 Rerank 之前。
  • 版本、状态和生效时间是否进入检索条件。
  • 生成答案中的每个引用是否可回溯。
  • 是否有知识库外问题和冲突文档测试。
  • Embedding 或 Rerank 模型更换后是否重新跑评测。
  • 缓存是否包含租户、权限和知识库版本。

十一、总结

混合检索不是“多调几个参数”,而是把精确匹配、语义理解、排序决策和证据治理组合起来。关键词擅长命中实体,向量擅长理解表达,Rerank 负责精排,引用溯源负责让答案可复核。

最有效的优化顺序通常是:先确认正确证据能被召回,再确认它排在前面,最后才调整模型回答。这样才能知道问题出在数据、检索还是生成,而不是在 Prompt 上反复试错。

读者讨论

如果你的知识库包含大量错误码、型号或版本号,建议先统计这些实体在查询中的占比,再决定关键词与向量的权重。