ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

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

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 FusionRRF(d) sum(1 / (k rank_i(d)))其中rank_i(d)是文档在第 i 个检索器中的排名k是平滑常数。RRF 不依赖原始分数尺度适合快速建立混合检索基线。publicListCandidaterrfMerge(ListCandidatekeyword,ListCandidatevector,intk,intlimit){MapLong,DoublescorenewHashMap();for(inti0;ikeyword.size();i){score.merge(keyword.get(i).chunkId(),1.0/(ki1),Double::sum);}for(inti0;ivector.size();i){score.merge(vector.get(i).chunkId(),1.0/(ki1),Double::sum);}returnscore.entrySet().stream().sorted(Map.Entry.Long,DoublecomparingByValue().reversed()).limit(limit).map(e-Candidate.withFusionScore(e.getKey(),e.getValue())).toList();}融合前要按 Chunk ID 去重并保留每个候选来自哪些检索器、原始排名和分数。否则出了问题只能看到最终排序无法判断是关键词错了、向量错了还是 Rerank 错了。四、Rerank 应该放在哪里Rerank 适合对 20100 条候选做精细相关性判断不适合直接扫全库。它可以理解问题和候选文本的交互关系通常比单独的向量相似度更准确但会增加网络调用、延迟和成本。publicListCandidateretrieve(QueryContextquery){ListCandidatecandidatesmerge(keyword.search(query.text(),30),vector.search(query.embedding(),30));ListCandidatepermittedpermissionFilter.filter(candidates,query.auth());ListRerankItemitemspermitted.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(ListCandidatecandidates){returnIntStream.range(0,candidates.size()).mapToObj(i-{Candidateccandidates.get(i);StringidC(i1);return[id] 文档c.title() 版本c.version() 页码c.page()\nc.content();}).collect(Collectors.joining(\n\n));}生成后校验引用编号是否存在且引用内容是否在当前候选集合中。引用了不存在的[C9]或者引用了一个没有支持该结论的 Chunk都应视为回答质量问题。七、评测必须拆分检索和生成如果最终答案错了不一定是模型生成能力差也可能是正确证据根本没有被召回。评测分两层检索评测RecallK、MRR、nDCG、过滤正确率。生成评测引用支持率、答案覆盖率、拒答准确率、格式通过率。可以把每条错误归因到以下类别未召回、召回但排序靠后、证据冲突、上下文过长、模型推理错误、引用错误和权限错误。错误归因比只记录“答案不对”更有优化价值。八、典型失败案例错误码命中了但处理步骤不对可能是多个版本文档都包含同一错误码旧版本排名更靠前。解决方案是把版本状态作为过滤条件并让版本信息进入 Rerank 输入。语义相似度很高但没有关键数字用户问“连续三次”召回的内容只包含“重复告警”。可以对数字、错误码和型号做关键词增强并要求证据覆盖这些实体。候选变多回答反而变差召回了互相冲突的制度和历史记录。应按版本、发布日期和文档状态做治理并在冲突时主动提示“存在多个版本需要确认适用范围”。引用很多但不支持结论模型为了显得可靠而堆引用。可以限制每个结论最多引用 23 条并要求引用内容包含相关实体或条件。九、性能和成本建议先测四个阶段关键词查询、向量查询、Rerank 调用、模型生成。Rerank 候选从 60 条增加到 200 条准确率可能略有提升但延迟和成本会显著增加。对高频固定问题可以缓存召回结果但缓存键必须包含租户、权限、知识库版本和查询归一化结果。可以采用分层策略普通问题只做混合召回高风险问题增加 Rerank 和引用校验知识库外问题走拒答检测。不同场景使用同一套“最重链路”通常会造成成本浪费。十、上线检查清单关键词和向量结果是否都保存原始排名。融合时是否去重、归一化并保留来源。权限过滤是否发生在发送给 Rerank 之前。版本、状态和生效时间是否进入检索条件。生成答案中的每个引用是否可回溯。是否有知识库外问题和冲突文档测试。Embedding 或 Rerank 模型更换后是否重新跑评测。缓存是否包含租户、权限和知识库版本。十一、总结混合检索不是“多调几个参数”而是把精确匹配、语义理解、排序决策和证据治理组合起来。关键词擅长命中实体向量擅长理解表达Rerank 负责精排引用溯源负责让答案可复核。最有效的优化顺序通常是先确认正确证据能被召回再确认它排在前面最后才调整模型回答。这样才能知道问题出在数据、检索还是生成而不是在 Prompt 上反复试错。读者讨论如果你的知识库包含大量错误码、型号或版本号建议先统计这些实体在查询中的占比再决定关键词与向量的权重。
返回列表