WRITING / 2026.09.23

从知识库问答到 Agentic RAG:让 Agent 找到可靠答案

以结算服务超时排查为例,拆解文档切分、元数据过滤、关键词与向量检索、重排和引用,比较固定 RAG 与动态补充检索。通过离线可复现的证据链定位未召回、召回错误和生成错误,并明确教学向量与真实语义检索的区别。

排障助手拿到了状态快照,还需要知道这些指标可能意味着什么。团队的知识往往散落在事故复盘、服务手册和配置说明中。把这些材料全部塞进一次模型请求并不现实,而只检索一篇标题相似的文档,也容易把旧方案套到新版本上。

本篇在执行循环中加入知识检索。固定案例包含十二份小型文档:当前版本的连接池说明、旧版本的重试建议、已经过期的步骤、缓存与 DNS 故障资料、其他服务的记录、私有文档以及用于测试的冲突和注入样本。它们都是人为编写的教学数据,不含真实业务资料。

从用户问题到可以检索的查询

用户可能输入“CO 的长尾又上去了”,而手册使用“checkout timeout pool”。如果系统直接把前一句当成关键词,很可能完全匹配不到。缩写、指标别名和不完整的问题,使查询理解成为检索链路的一部分。

查询改写首先需要保持原始意图。把“延迟升高”直接改写成“连接池容量不足”,相当于在检索前就认定根因,检索结果也可能只强化这个猜测。更合适的改写会补全服务名、指标和版本等可核对条件,把原因留作待验证的假设。

本例使用两条预置查询:第一条是无法匹配文档的缩写表达,第二条加入规范服务名、超时和连接池信息。改写选择由固定样例提供,程序实际执行检索、记录结果并决定是否继续。它展示补充查询的流程,不证明真实模型能够自动生成正确改写。

查询记录还应保存来自哪里。用户原文、应用补全的服务标识、模型提出的扩展词,可信度与用途不同。排查召回错误时,如果只看到最终字符串,就很难知道是用户描述不清,还是改写阶段加入了错误假设。

文档切分为什么会影响后续答案

知识库不应只保存裸文本。每个片段最好能追溯到原始文档,并附带服务、版本、更新时间、访问范围等元数据。对于排障资料,适用环境和失效时间往往比文字相似程度更有决定性。旧版本文档即使命中全部关键词,也可能给出不适用的步骤。

切分粒度需要围绕信息完整性设计。把“仅适用于第一版客户端”与后面的操作步骤分到两个片段,会让单独召回的步骤失去前提。另一种极端是把整份手册当成一个片段,虽然上下文完整,但返回内容可能过多,影响后续定位与引用。

可采用标题、段落和语义边界组织片段,并给每个片段保留所属章节。适度重叠可以减少边界丢失,但也会增加重复证据。重叠片段若被当成多个独立来源,可能让系统误以为某个建议获得了更多支持。

示例为了保持下载包小巧,一份文档对应一个片段,没有实现生产级解析与切分器。读者可以直接打开 fixtures.json,检查每条资料的范围和适用条件。切分策略的讨论属于设计方法,不能据此声称程序已经验证了任意 PDF 或复杂表格的处理能力。

先过滤不该使用的资料

本例在评分前过滤服务、版本、过期时间和用户可见范围。这样做有两个目的:避免不适用的资料消耗候选名额,以及防止无权访问的数据进入后续模型上下文。权限过滤不能只在最终回答里删掉敏感词,因为模型在那之前已经可能读取了内容。

过滤条件同样可能导致漏召回。服务标识写错、版本字段缺失,都可能让本来有用的文档被排除。因此,元数据维护也是知识库质量的一部分。发现空结果时,应能够区分“没有匹配文本”与“候选被适用范围排除”,而不是立即把检索范围放宽到所有用户资料。

权限条件应由可信身份生成。模型可以提出“再查其他服务”,却不能自己把用户标识改成别人。生产检索系统需要在服务端执行授权,不能假定向量数据库里有一个标签就自然实现了租户隔离。本例用明确的用户字段展示这个边界。

测试会断言旧版本、过期文档、其他服务和另一用户的私有记录不出现在普通查询结果里。这些断言验证过滤行为,而不是证明知识库资料本身正确。即使过滤完全符合预期,仍可能召回一篇内容写错的当前文档。

关键词、向量与重排如何配合

关键词检索适合错误码、接口名、配置键和明确术语。向量检索用于表达语义相近但措辞不同的内容。两者常被组合使用,但“组合”不意味着把两个来源的原始分数直接相加就一定合理:分数分布、召回规模和模型版本都可能不同。

一种常见设计是分别召回候选,再进行合并、去重与重排。重排在较小候选集合中重新判断问题与片段的相关性。它不能找回从未进入候选集合的资料,因此,召回失败与重排失败应分别诊断。把所有问题归结为“换一个更好的模型”会掩盖这些边界。

教学代码使用词项重合数,加上很小权重的余弦相似度。文档向量是人工填写的三个维度,分别对应超时、缓存和 DNS;查询向量也通过固定词项构造。它们只用于演示评分、合并和排序,不是嵌入模型生成的语义向量,更不能作为语义召回质量的测试集。

示例还要求至少命中一个服务名之外的词项才进入候选集合,因此严格说来属于带教学向量加权的词法候选排序,而不是完整的双路混合召回。文章将生产设计与简化实现分开,避免让一个便于阅读的小函数承担它没有实现的能力。

代码只保留前三条结果,并使用文档编号处理同分排序,保证测试可重复。生产系统的候选数量要结合数据规模、上下文预算与评测结果选择,不能把示例中的三条当作通用最佳值。选择参数时应观察错误类型,而不是只看最终答案是否流畅。

固定 RAG 与 Agentic RAG 的分界

固定 RAG 通常执行查询、召回、组装上下文和生成回答这一条既定路径。它适合信息需求相对明确的问答,例如查询某个配置项的默认含义。生成模型使用外部资料,并不自动意味着整个系统已经是 Agent。

Agentic RAG 则允许根据当前结果决定是否继续获取信息。第一次查到连接池手册之后,可能需要核对服务版本;发现文档冲突之后,可能需要读取更新时间或请求人工判断。关键在于后续动作依赖证据缺口,而不是把固定检索节点循环两次就换一个名称。

检索闭环:问题经过范围过滤与召回,证据不足时补充查询,冲突时交由复核,足够时生成带引用的回答

这种灵活性也有代价。每次补充查询都会增加延迟和处理内容,错误改写还可能让搜索越来越偏离原问题。需要明确哪些信息缺口值得继续查,最多允许多少轮,以及什么时候应该停止并承认证据不足。

用同一个问题观察两轮检索

下载包中的 retrieve_until_supported 接受预置查询序列,实际执行检索并记录每轮命中的文档。它有明确的轮次上限,不会因为结果为空就无限遍历全部文档。

from demo import retrieve_until_supported

result = retrieve_until_supported(
    ["CO p99 regression", "checkout timeout pool"],
    max_rounds=2,
)
for item in result["rounds"]:
    print(item["verdict"], item["ids"])

第一轮得到 NO_EVIDENCE,第二轮得到 SUPPORTED,对应文档为 rb-01rb-02。这里的“支持”是一个教学判定:存在候选,且候选中明确填写的建议类别不冲突。它没有运行自然语言蕴含模型,也没有判断一整段回答是否忠实于所有证据。

把轮次上限改为一,程序仍然返回没有证据,不能借用第二轮结果伪装成成功。这个实验说明,预算和停止条件应当影响最终任务状态。如果系统只是减少执行次数,却仍输出同一段预写答案,就没有真正验证检索参与了决策。

完整 Agent 示例会把检索结果的编号加入可引用集合,最终回答只能引用实际观察到的编号。这个设计建立了最小的可追溯性,不过“编号存在”仍然弱于“资料支持结论”。例如引用一篇缓存文档去证明连接池配置错误,引用可能真实,推断却不成立。

把错误分成三个阶段

未召回指需要的材料没有进入候选集合。可能原因包括文档未入库、切分丢失前提、查询表达不匹配、元数据过滤错误或候选数量不足。此时首先检查资料是否存在及检索过程,不要只修改最终回答的提示词。

召回错误指候选中出现了不适用或干扰性资料。比如第二版服务召回第一版客户端的重试方案,或者同名配置在不同服务中含义不同。要检查适用范围、排序和去重,必要时补充更明确的元数据。更大的上下文窗口不能自动消除这种错误。

生成错误指证据已经足够,回答却曲解、遗漏或补充了没有依据的内容。可通过逐条核对结论与引用定位问题。此时优化检索可能帮助有限,需要调整回答契约、约束结论强度,或者引入人工与模型评审。

三种问题可能同时发生。一次回答引用了旧手册,又根据旧手册编造当前配置状态,既涉及召回错误,也涉及生成错误。评测报告应允许多标签归因,否则一个笼统的失败数字很难指导下一次修正。

空结果、冲突与过期信息怎么处理

空结果不等于业务没有问题,只意味着本轮没有取得支持材料。回答应该说明缺失的信息,并提出下一项可验证的查询。若已经达到预算,应终止检索并保留当前轨迹,让人能看见尝试过什么,而不是把同义句换一遍继续请求。

冲突证据需要保留来源与适用条件。案例中另有一份要求增加工作线程的文档,用于与连接池检查建议形成冲突。程序通过人工标注的建议类别识别这种差异并要求复核;它不会靠检索分数高低直接判断哪篇内容必然正确。

过期信息应当在读取时重新检查。文档入库时有效,不代表半年后的任务仍然适用;搜索缓存也不能让已经撤回的操作手册继续进入模型上下文。对于生产变更步骤,版本与有效时间最好同时参与检索和展示,使读者能够核对前提。

需要特别防止把资料中的指令当成系统指令。示例包含一条明确的注入样本,正文试图要求执行重启。它仍然只是检索得到的字符串,应用不会因为文档提出了命令就把该命令加入允许的工具集合。真实模型可能受不可信文本影响,因此还需要独立的授权边界与攻击测试。

检索指标怎样连接到任务结果

只看召回条数不能判断质量。召回十条资料但没有一条适用于当前服务,结果比召回两条准确资料更差。可以为固定问题标注所需证据,观察它们是否进入候选,再检查答案是否正确使用这些证据,把检索与任务结果联系起来。

同时要记录代价。补充一次查询是否找到了新的必要证据,还是只得到已有片段的改写?去重之后上下文是否仍然重复?这些问题决定多轮检索有没有实际价值。没有新增信息的轮次应成为停止依据,而不是用来显示 Agent 很忙。

本系列用固定查询与人工资料验证机制,没有用它们宣称任何嵌入模型或重排模型的效果。真实接入后,应另建包含自然问法、缩写、版本差异与冲突资料的评测集,重复运行并记录模型与索引版本。第六篇会讨论如何把这些结果组织成可比较的报告。

对 Java 后端实现的启发

检索服务可以看成一个带授权的读模型。身份与范围来自可信上下文,查询对象携带服务和版本,返回值包含证据编号、内容与适用条件。模型调用放在这个契约之外,使检索逻辑能够独立测试,也便于替换底层索引实现。

把“查询改写”“候选检索”“证据检查”“回答生成”分成可观察的阶段,有助于在日志中找到真正失败的位置。它不要求拆成四个微服务;在同一进程里保持清晰函数边界,也可以获得大部分调试收益。部署复杂度应由规模和职责决定,而不是由概念数量决定。

最后,可以用一个反向问题检查系统:如果知识库里完全没有答案,它是否仍然会自信地给出操作步骤?一个可用的研发助手应该在这种情况下保留证据缺口、限制动作并说明下一步。可靠检索不仅帮助找到答案,也帮助系统识别什么时候还不能回答。

让检索实验可以重复

固定检索实验应同时保存问题、索引输入、过滤条件和排序实现。只保存最终答案,无法判断下一次结果变化来自哪里。本例把十二份资料与人工向量放在同一个数据文件中,并固定测试时钟,所以读者可以逐项修改条件,观察旧版本文档或过期资料为什么被排除。

这里的词项提取针对教学数据中的英文标识,没有实现中文分词、拼写纠正或专业词典。真实中文知识库需要单独评估这些处理方式,并把错误码、配置键等精确标识保留下来。不要将小数据集上的可重复性误解为对任意语言和文档规模的适应性。

参考资料

AI Agent 工程实践:从原理到可靠运行

  1. AI Agent 是如何工作的:从一次模型调用到任务执行闭环
  2. 让 Agent 真正执行任务:Tool Calling 与 MCP 实战
  3. 从知识库问答到 Agentic RAG:让 Agent 找到可靠答案 · 当前文章
  4. Agent 的上下文与记忆:如何记住有用信息
  5. 复杂任务如何编排:工作流、单 Agent 与多 Agent 的取舍
  6. Agent 从 Demo 到上线:评测、可观测性与安全边界

按时间浏览