知识明明在库里,RAG 为什么还是答错?——多源知识库路由与融合的工程实践

知识明明在库里,RAG 为什么还是答错?——多源知识库路由与融合的工程实践
本文来自我们团队一次真实企业知识问答系统的改造实践。案例中的业务名称、产品名称、系统名称、数据规模、部署信息和代码标识均已匿名化只保留可复用的技术问题与解决思路。本文重点讲问题如何暴露、为什么常规修复无效以及设计决策是怎样一步步推导出来的。本文不展开 chunk 切分、embedding 模型选型、向量数据库对比、流式输出和多轮记忆管理。如果你只有 10 分钟建议先读本文如果你正在进行架构设计、代码审计或上线评审可查阅《多源 RAG 系统设计与排障方法论》。一、不是阈值也不是 Embedding一次反直觉的排障问题最初看起来很普通文档已经解析成功向量也写进了知识库单独调用检索工具能够命中但用户通过正式问答接口提问时系统没有使用这份资料。团队的第一反应和大多数 RAG 项目一样相似度阈值是不是太高embedding 模型是不是不够好chunk 切分是不是有问题rerank 是否没有生效提示词是否需要再次强调“优先使用知识库”但单独检索已经能够命中这说明数据、索引和基础召回并没有完全失效。真正值得追问的是正式请求到底有没有走到这个检索器我们顺着请求链路检查最终发现目标知识没有在召回阶段丢失而是在召回发生之前就被绕开了。二、核心根因选择业务 Agent被隐式等价成选择知识库原系统的链路大致是用户问题 → 路由模型选择业务 Agent → Agent 使用自己的固定工具和知识库 → 生成答案问题中带有明显的产品类别词因此被交给领域 Agent。这个选择并不荒谬领域 Agent 确实更适合组织专业表达。真正的问题是选择领域 Agent被系统隐式等价成只能查询历史产品库。于是一个路由标签同时决定了“谁来处理”“这是什么问题”“查哪些数据”和“如何输出”。产品词本应只影响任务执行者却间接屏蔽了保存目标答案的新知识源。这次故障让我们意识到至少要把四个决定分开任务执行者、问题类型、检索范围和输出策略。它们可以相互约束但不能被压缩成一个 Agent 名称。三、为什么只改提示词不够最直接的修复是在路由提示词中增加规则文档事实类问题应查询新知识源。这能改善模型判断却不能成为唯一保障。原因很现实同一问题换一种表达分类结果可能变化历史消息可能干扰当前意图模型升级后字段格式可能变化多意图拆分时新字段还可能在下游丢失。因此我们采用双层控制提示词负责语义判断程序负责业务不变量。defnormalize_route(route):ifroute.intent_kinddocument_fact:route.retrieval_scope.update({legacy_kb,approved_kb})returnroute模型判断“这是不是文档事实”代码保证“文档事实不能绕过规定知识源”。这不是削弱模型而是避免让概率输出承担确定性责任。四、查询了两个库为什么仍然不叫融合修复路由后两套知识源都能收到请求但第二个问题随即出现系统只是分别检索、分别排序再把两段文本拼给模型。来源 A 的结果文本 来源 B 的结果文本 → 直接拼接这种做法看起来接入了两个库实际上两边的候选没有共同竞争重复内容无法可靠去重低质量结果可能挤占上下文不同来源的分数也没有统一含义。我们最终把融合边界前移到“候选”阶段各来源先返回结构化候选再统一去重、融合排序和 rerank最后才格式化成模型上下文。同时多源检索必须允许局部失败asyncdefretrieve_sources(query):legacy,approvedawaitasyncio.gather(search_legacy(query),search_approved(query),return_exceptionsTrue,)legacy[]ifisinstance(legacy,Exception)elselegacy approved[]ifisinstance(approved,Exception)elseapprovedifnotlegacyandnotapproved:raiseRetrievalUnavailable(all sources failed)returnlegacy,approved这样一个来源故障不会直接拖垮整次问答但如果所有来源都失败系统也不会假装自己拿到了证据。五、0.86 和 12.4 谁更相关多源融合中很容易出现一个隐蔽错误把不同检索器返回的原始分数直接放在一起比较。某个向量检索器可能返回余弦相似度另一个全文检索器可能返回 BM25 分数还有的距离值越小越好。它们都叫score并不意味着处于同一量纲。我们最初看到“0.86”和“12.4”时无法仅凭数字判断谁更相关。正确做法不是强行归一化而是先利用各来源内部排名做稳定融合再让同一个 rerank 模型对跨源候选统一评分。另一个教训是只有当 rerank 确实成功、分数语义明确并经过校准时相关度阈值才有意义。若 rerank 超时并降级为来源排名继续套用原阈值可能把所有候选误删。六、事实问题必须先检索再调用模型即使知识源选择正确如果是否调用检索工具仍完全由模型决定正式链路仍可能跳过检索。对于标准条款、制度、合同、说明书等强事实问题检索不应是一项可选工具而应是业务流程的一部分asyncdefanswer_document_fact(route,query):evidenceawaitretrieve(route.retrieval_scope,query)ifnotevidence:returnabstain_or_clarify()returnanswer_with_evidence(query,evidence)这段逻辑解决的是“有没有依据”模型负责的是“如何理解和表达依据”。当证据为空时系统应拒答或澄清而不是让模型凭常识补全。七、语义很强但不能替代硬约束和输出策略改造过程中我们还遇到两类容易被混在检索里的问题。第一类是硬约束。例如用户要求某个精确规格、接口或范围语义相似只能说明“描述很像”不能证明参数满足。正确顺序应是先用结构化条件得到合法候选再在合法集合内做语义排序和解释。第二类是输出策略。系统内部是否保留来源、版本和证据与最终是否把文件名展示给用户是两回事隐藏来源 ≠ 不检索 隐藏来源 ≠ 删除内部证据 隐藏来源 最终回答不展示内部来源信息检索解决事实依据输出策略解决展示形式。两者耦合后很容易为了“不显示来源”而误删证据甚至连关键结论一起删掉。八、为什么单独问正常合起来就错在多意图工作流中上游通常会拆出多个子问题。如果分支只收到一句sub_query而没有收到问题类型、实体、限制条件、检索范围和输出策略下游就会退回默认行为。这解释了一种常见现象同一个问题单独问时能够命中和另一个问题一起问时却走错知识源。修复方式不是给模型再写一句提醒而是把子任务定义成完整上下文对象并为合并设置规则实体取并集硬条件不能丢任一子任务要求文档证据时必须保留对应检索范围输出策略采用更严格的限制。九、相关性高不代表内容当前有效多源候选统一排序后我们又发现一个比相关性更棘手的问题旧资料和新资料可能同时相关但结论不同。rerank 能判断哪段文字更贴近问题却不知道哪份文件已经审批、哪一版仍然有效、哪条规则只适用于特定范围。把这些判断塞进一个相似度分数会让系统在冲突时表现得不可解释。因此相关性、权威性和时效性必须分层处理召回寻找可能相关的内容rerank 判断问题相关度治理规则决定版本是否有效、适用范围是否匹配以及冲突时能否直接回答。这一阶段最重要的发现是authority_level不是一个随手填写的数字而是组织治理规则在检索系统中的映射。十、改造旧系统为什么没有直接重写历史检索链路已经服务现有业务直接改变返回类型或删除旧接口可能影响未被发现的调用方。我们采用了旁路扩展保留旧文本接口新增结构化候选能力让新融合层只使用候选接口并通过功能开关切换。这使改造具备三个条件新链路可以单独验证出现异常时能够回退旧调用方不需要同时迁移。兼容层不是永久包袱。它的价值是把一次高风险重写拆成可验证、可灰度、可撤销的阶段。十一、多查一个库会慢多少最初有人担心多查一个知识源会让检索时间翻倍。如果串行执行这种担心成立如果多个来源并行总检索耗时更接近最慢来源耗时加统一 rerank而不是所有来源耗时之和。真正需要控制的是慢来源、重复 embedding、过大的召回数量、过长的候选文本和无条件全库检索。因此上线前必须给路由、查询表示、来源检索、融合排序和模型生成分别设置预算并观察 P95而不能只看一次本地请求。这里没有给出项目的真实延迟数字因为当时没有统一压测口径。可以确认的是架构已经支持并行和降级具体预算仍需由真实流量与压测结果决定。十二、没有基准集就不要编一个“40%→90%”这次改造前没有建立可复现的标准问题集因此不能诚实地声称正确率从某个数字提升到了另一个数字。我们能验证的是架构行为发生了变化改造前目标类别问题只进入历史知识源。 改造后路由结果能够显式选择多个知识源。 改造前两路结果分别排序后直接拼接。 改造后候选统一去重并参加跨源排序。 改造前事实检索依赖模型决定是否调用工具。 改造后程序在生成答案前确定性执行检索。这些证明“系统开始按预期工作”但不能直接证明“真实问题正确率已经达到多少”。后者必须依赖人工标注问题集、离线回归和线上灰度且同时观察召回、答案忠实度、拒答和延迟。十三、我们最终怎样排查这条链路回顾整个过程最有效的排查顺序不是从模型参数开始而是顺着证据流逐层确认确认请求进入了哪个接口、实例和版本查看路由选择了谁、问题类型是什么、允许查哪些知识源确认目标检索器是否真的收到请求跟踪正确片段是否经历召回、去重、排序和截断确认最终证据是否进入模型上下文检查输出策略是否覆盖或误删了答案。每一层都要有日志或测试证据。只要某一层没有发生继续调后面的阈值、模型或提示词都不会解决根因。十四、从一个 Bug 到一条工程原则一次排障真正有价值的部分不是记住某个产品词要路由到哪个库而是完成三层抽象现象目标知识源有数据但正式请求没有使用。 机制任务执行者与知识源选择错误耦合事实检索缺乏确定性。 原则路由维度分离事实检索程序化多源结果在候选阶段融合。最终我们沉淀出六条原则Agent 路由和知识源路由是两个维度多源融合发生在结构化候选阶段而不是文本拼接阶段事实类任务的检索必须由程序保证结构化硬约束不能被语义相似度覆盖相关性、权威性和输出策略要分层处理单元测试证明代码行为真实问题集才能证明系统效果。真正可靠的 RAG不是单纯让模型变得更聪明而是把必须确定的事情交给程序把需要理解和表达的事情交给模型。