去年聊 RAG,大家还挤在“Embedding 模型选哪个、TopK 怎么设、文档怎么切”这些入门问题上。到了今年再聊,问题已经悄悄变成了另一副模样:同样一堆文档,为什么有的系统只会“查一下就答”,有的系统却会在答案不够时自动换个查法、拆出子问题、对比多份材料再下结论。这种从“单次检索”升级成“多步智能决策”的 RAG 形态,就是 Agentic RAG。
这篇是 Agentic RAG 系列的第 2 篇,上一篇讲的是怎么把基础链路跑通,这一篇直接聊生产级改造:解决什么问题、零件怎么拆、代码怎么写、成本怎么控、坑怎么避。适合正在做知识库问答、智能客服、数据分析助手,以及想把 RAG 从 Demo 推向真实业务的工程师和算法同学。我会把能落地的细节都写出来,也把那些文档里不写的东西一并说完。
1. Agentic RAG 到底改了什么:三个“传统 RAG 搞不定”的场景
1.1 传统 RAG 的三个硬伤
先泼一盆冷水。传统 RAG 的链路看起来没问题:文档切块、向量化、召回、拼接、让大模型生成答案。但只要你把它扔到真实业务里,很快就会碰到三堵墙。
第一堵墙是“问题拆不开”。用户真实提问往往是复合型的,比如某公司内部知识库收到一个问题:“华东区三季度销量掉得最狠的品类是哪个,它到底是受物流影响还是受价格策略影响,跟华北区比差异有多大?”传统 RAG 只会拿着这句话去向量库做一次相似度检索,召回的全是提到“华东”“销量”“物流”的碎片,模型很难自己把问题拆成“先定位品类→再找影响因素→再做区域对比”三个步骤。结果就是答案听起来像模像样,实际根本没有覆盖完整问题。
第二堵墙是“一锤子买卖,错了不回头”。第一次检索结果差,后续没有补救机制。我见过不少系统,用户在页面上怎么问都答不对,但把同样的问题稍微改写一下、换个措辞再问,答案就对了。这说明问题不在生成模型,而在检索前面少了“探测和纠错”的环节。
第三堵墙是“只会翻文档,不会用工具”。业务系统里除了文档,还有 SQL 数据库、时间序列接口、权限系统、内部知识图谱。传统 RAG 是文本库专用的,但真实世界的答案往往要跨多个数据源才能拼出来。让一个只读文档的 RAG 去回答“按当前库存,某款物料能撑几天”,它做不到。
这三堵墙指向同一个结论:检索这件事,得从“一次查询动作”升级成“一连串有策略的决策动作”。
1.2 Agentic RAG 的核心循环:观察、决策、行动
Agentic RAG 并不是什么神秘的新架构,它本质上是把大模型 Agent 的能力与检索增强结合起来。大模型不再是“收到问题直接答”,而是进入一个循环:观察当前状态 → 决定下一步动作 → 执行动作 → 看到结果 → 再次决定。
这个循环里,检索器、重排器、数据库查询、网页搜索、知识图谱查询,全都被封装成可调用的“工具”。模型自己判断该调用哪个工具、按什么顺序调用、结果不够用怎么办、什么时候该停止并给出结论。
打个比方。传统 RAG 像图书馆管理员,你念完一句话,他跑去拿一本书回来;Agentic RAG 更像一个分析师,他会先确认需求、把问题拆成几个子问题、分别查阅资料、发现两个材料说法矛盾时再查第三份、实在查不到就直接告诉你“这部分我还找不到可靠依据”。
这种设计带来的直接收益是:复杂问题可以按步骤求解,检索质量差时能做二次检索,并且可以跨工具组合出答案。代价则是调用次数变多、延迟变高、成本线性上涨。所以 Agentic RAG 不是把简单 RAG 完全替换掉,而是应该作为一套“能上能下”的系统:简单问题走快速通道,复杂问题才进入多步循环。
1.3 它适合谁,不适合谁
我做了几个项目之后,总结出来一个判断标准:如果你的用户问题大多是“某名词是什么意思”“某文档里怎么写的”,传统 RAG 就够了,别上 Agent,徒增成本;如果问题是“综合 A 和 B 去推断 C”“对比多个区域/多个时间段”“信息不足时需要继续追问”,那传统 RAG 大概率会让你在质检环节吃尽苦头,这时候再上 Agentic 改造。
还有一个典型的适合场景是“工具装配型 RAG”:文档只是数据来源之一,旁边还有结构化数据接口和业务 API。Agent 能根据问题决定查文档还是查数据库,甚至先查文档拿到实体、再拿着实体去查数据库。
2. 核心零件拆解:路由、改写、检索工具、反思与记忆
2.1 查询侧的“前置处理”:重写与路由
多数人做 Agentic RAG 喜欢先把注意力放在工具调用上,我反而建议先检查两条前置链路:查询改写和路由。这两步决定了后续所有动作的质量。
查询改写要解决的是“用户的一句话不适合直接拿去检索”。口语化的表达、指代不明的说法、跨越多种语言的混杂表达,都需要先被整理成更适合检索的形式。举个例子,上一轮聊了某报表,下一轮用户问“那它上个月的情况呢”,这里的“它”如果不做指代消解,直接检索会完全跑偏。我的做法是在对话入口后挂一个改写模型,输入是“历史对话摘要 + 当前问题”,输出是“改写后的独立检索查询”。
这里的工程细节是改写模型不需要用最强模型,一个几十亿参数的开源模型就能做得很好,关键是温度设低一点、输出格式用强约束。我给过一个模板:“请把用户问题改写成适合搜索引擎和向量数据库检索的查询句,只输出改写结果,不输出解释。保留时间、地点、指标名等关键实体。”实测这样写比泛泛地“改写一下”效果稳定得多。
路由则是下一步过滤。路由模型判断当前问题应该走哪个通道:简单事实问题走“轻量单跳 RAG”、对比分析问题走“多跳检索工作流”、需要实时数据的问题走“SQL + 文档混合通道”、完全跟知识库无关的闲聊走“直答通道”。路由的控制有三种常见形式:让模型输出结构化 JSON、让模型调用一个专门的“选择工具”、或者用一个分类模型直接做 Softmax。我现在的项目里用的是结构化 JSON 输出,因为可解释性强,出了问题能直接查日志。
路由的成败关键有两个。一是要给每个通道写清楚“什么时候用它、什么时候别用它”的描述,最好还带上反面案例;二是要设置置信度阈值,比如路由模型输出“多跳检索”的概率小于 0.7 时,默认回退到轻量 RAG。不然会出现糟糕的后果:一个“什么是某某算法”的简单问题,被路由丢进了一个要调用三四个工具的多跳流程,白白花掉十几倍 token 和时间。
2.2 检索侧的工具化改造:不只是把向量库包一层
很多人以为把向量检索封装成一个函数就叫“检索工具”,这是不够的。工具化改造的关键在于:让模型能够理解这个工具的能力边界、输入输出格式、以及返回结果怎么被使用。
一个合格的检索工具定义至少要包含这几段:工具名称、一句话说明、适用场景、不适用场景、输入参数列表、输出结构。我见过太多失败的案例,工具描述只写“检索相似文档”,模型根本不知道这个工具搜的是什么库、返回的是片段还是完整文档、能不能用来找结构化数据。结果模型乱调一气。
检索工具内部也要做文章。我建议把重排环节放进工具内部,而不是放在 Agent 外部。流程是:向量召回 Top 100 → 交叉编码器重排取 Top 10 → 拼装成带“文档来源+原文片段+相关性分数”的结构返回给模型。这样模型看到的检索结果不是一堆裸片段,而是带着旁证材料的证据块。
另外我强烈建议在检索工具里做“双模式检索”。第一种是精确模式,阈值高、TopK 小,适合回答事实性问题;第二种是探索模式,阈值低、TopK 大,适合回答综述型、对比型问题。Agent 可以自己根据问题类型选择调用哪一种模式,这样做比单一阈值硬切效果好得多。实现也不难,就是在工具参数里加一个mode字段。
2.3 反思与记忆:让 Agent 能自我纠错
Agentic RAG 和普通 RAG 拉开差距的一个核心机制是“反思”。反思节点的职责是:在 Agent 给出答案之前,先拿答案跟检索到的证据做一次核对,判断是否存在无中生有、证据不足、与既有事实矛盾等情况。如果发现不对,Agent 可以选择重新检索、换一种检索策略、或者坦白承认无法回答。
我见过的反思实现有两种,建议根据你的预算选。轻量版是“生成后反思”:先生成一版回答,再用一个评判模型打分,分数低于阈值就触发第二轮检索。重量版是“生成前反思”:检索完先让模型判断“这批证据是否足以回答问题”,不足就直接重新规划检索,而不是先生成答案再返工。重量版效果更好,但多了一次大模型调用,成本也更高。
记忆机制同样不可绕过。Agentic RAG 的应用场景往往有明确身份边界。这里有两层记忆要处理:短期记忆记录当前问题拆出来的子任务、已经尝试过的检索路径、已经排除掉的方向,避免同一个检索动作反复执行;长期记忆则保留历史会话中的用户偏好、实体关系、业务规则,让 Agent 在多次对话里保持一致的口径。
我自己调试时遇到过最难受的死循环:Agent 发现证据不足,于是再次检索同一个关键词,又拿到同样的结果,再抱怨一次证据不足,然后又检索。问题就是短记忆里没有“这个关键词已经查过了”的记录。后来我在状态里加了一个searched_queries列表,每次检索前都检查一遍是否重复,循环问题立刻缓解。
3. 实操:从基础 RAG 升级到 Agentic RAG
3.1 编排框架选型:为什么我推荐“状态图”思路
开始动手之前,先选编排框架。目前行业里做 Agentic RAG 主要有三种思路:一是用表达式链把固定的检索步骤串起来,这种方式适合流程永远不变的情况,但这恰恰是 Agentic RAG 最不希望的;二是在自定义的 ReAct 循环里手动控制工具调用,灵活但状态管理混乱,一旦要加子问题分支,代码很快变成一坨意大利面;三是状态图思路,把整个流程建模成“节点 + 边”,节点执行具体动作,边根据状态跳转,循环和分支都是图结构的一部分。
我推荐第三种,尤其在有复杂条件分支和循环需求的时候。因为可观测性最强:每步停在哪个节点、状态里多了什么字段,都清清楚楚。下面示例我用的是一个常见的“图编排框架”语法,具体框架不同 API 有差异,但逻辑是通用的:定义状态 → 定义节点函数 → 连接边 → 编译执行。
3.2 最小可用版本的状态设计与节点实现
先定义状态。状态是整个流程的“随身笔记本”,每个节点都能读写:
from typing import TypedDict, List, Optional class AgentState(TypedDict): question: str # 原始问题 rewritten_query: Optional[str] # 改写后的查询 route: Optional[str] # 路由结果 sub_questions: List[str] # 拆解出的子问题 search_results: List[dict] # 检索证据集合 searched_queries: List[str] # 已经查过的 query,防重复 answer: Optional[str] # 最终答案 iterations: int # 循环计数 status: str # running / done / failed然后定义几个核心节点。第一个是改写节点,它把原始问题和历史摘要整理成检索查询;第二个是路由节点,决定走哪个分支;第三个是检索节点,负责调用混合检索工具;第四个是反思节点,检查证据充分性;最后是生成节点,输出答案。
def rewrite_node(state: AgentState) -> AgentState: # 用大模型做改写,示例省略 API 细节 new_query = llm_rewrite(state["question"], dialogue_history) state["rewritten_query"] = new_query return state def route_node(state: AgentState) -> AgentState: route = route_llm(state["rewritten_query"] or state["question"]) state["route"] = route return state def retrieve_node(state: AgentState) -> AgentState: query = state["rewritten_query"] or state["question"] if query in state["searched_queries"]: # 防止重复检索 return state docs = hybrid_search(query, mode="explore") state["search_results"].extend(docs) state["searched_queries"].append(query) state["iterations"] += 1 return state图构建的写法重点在于“条件边”。路由节点根据state["route"]决定下一步是走轻量单跳、还是走子问题分解、还是直接回答;反思节点则根据证据充分性决定是回到检索节点,还是前进到生成节点:
from agent_workflow import StateGraph graph = StateGraph(AgentState) graph.add_node("rewrite", rewrite_node) graph.add_node("route", route_node) graph.add_node("retrieve", retrieve_node) graph.add_node("reflect", reflect_node) graph.add_node("answer", answer_node) graph.set_entry_point("rewrite") graph.add_edge("rewrite", "route") graph.add_conditional_edges("route", route_branch, { "simple_rag": "retrieve", "multi_hop": "decompose", "direct": "answer" }) graph.add_conditional_edges("reflect", should_continue, { "retry": "retrieve", "synthesize": "answer" }) graph.add_edge("answer", END)这里有一个很实用的约束:循环必须有终止条件。我在should_continue里检查iterations >= 4就直接返回answer,哪怕证据不足,也让生成节点回退到“基于现有材料回答并标注不确定”。宁可给一个低置信度的答案,不能让系统卡死在流程里。
3.3 Prompt 设计才是效果上限
代码框架定完之后,真正拉开效果差距的是 Prompt 设计。我做了几个项目后的体会是:Prompt 是 Agentic RAG 效果的上限,模型和检索只是地板。
每个节点都应该有自己的专用 Prompt。路由节点的 Prompt 要给出每个通道的描述、示例问题和输出格式;改写节点的 Prompt 要强调“不要增删事实”;反思节点的 Prompt 要引导模型检查“证据是否说了这句话、证据是否自相矛盾、证据是否只能部分支持结论”;生成节点的 Prompt 要强调“只能使用检索返回的证据,禁止用自己的知识补全,引用必须带来源编号”。
给个实用的反思 Prompt 骨架:
你现在是质检员。请检查以下回答是否完全被检索证据支持。 如果存在“证据不支持”“证据矛盾”“关键信息缺失”三类问题, 请输出 REFLECT_AND_RETRY 并说明缺什么; 如果证据足够,输出 ACCEPT。 注意:不确定时不强行通过。我还建议在工具描述里写明“反面触发条件”。例如检索工具描述里写:“当用户问题涉及实时价格、库存、数据库联合查询时,不要调用本工具,应该调用 SQL 查询工具。”这比只写正面描述效果好得多,模型对“什么时候不调用”的认知往往比“什么时候调用”更重要。
4. 生产落地:成本、延迟、评估,一个都不能少
4.1 成本与延迟控制
Agentic RAG 最大的生产阻力不是效果,而是成本和延迟。一次复杂问题的多跳流程,可能要调用 5 到 10 次大模型接口,再加上多次向量检索和重排。相比单次 RAG,成本上浮 3 到 10 倍,延迟从一秒级涨到五秒甚至十几秒。这让很多团队做完 Demo 之后卡在上线前。
我的成本控制三板斧:第一板斧是“路由降级”,简单问题永远走轻量通道。我在线上系统里加了一个统计面板,结果发现接近 60% 的问题其实是“某某是什么”“某文档第几节怎么说”,这些完全不需要 Agent 循环。路由模型把它们拦下来走单跳 RAG,整体成本直接砍掉一半。第二板斧是“小模型做中间步骤”。改写、路由、甚至反思都可以用轻量模型完成,把最强的模型只留给最终答案生成。我试过把一个 7B 级别的模型用来做路由和改写,效果跟顶级模型差异不大,但成本和延迟都大幅下降。第三板斧是“缓存”。改写结果做精确缓存,一模一样的问题直接跳过改写;检索结果做语义缓存,相近问题在短时间内复用同一批证据块。
延迟优化的核心是并行化。子问题分解后,多个检索任务可以并行执行,上游等待时间会从“串行累加”变成“单步最大耗时”。用异步调用把三个子问题的检索并发跑,整体延迟大约能下降 40% 到 60%。
4.2 三层评估指标如何搭
Agentic RAG 的评估不能只看“回答得好不好”,太笼统。我习惯拆成三层:检索质量、流程质量、生成质量。
检索质量沿用传统的 Recall@K、MRR、Hit Rate,主要看“该召回的证据有没有被找回来”。流程质量要看工具调用有效率:“调了多少次工具”“有没有重复调用”“子问题完成率”“循环收敛率”。生成质量要看事实一致性、回答相关性、引用准确性。事实一致性是 Agentic RAG 最容易爆的指标,因为多跳流程中一个环节的信息被模型“自由发挥”,最后生成的答案可能整体流畅但局部错误。
具体做法是准备一个黄金评测集:从真实用户日志里抽取种子问题,手工标注正确答案和支撑证据,再扩展成几百条覆盖简单/复杂/歧义/无答案四类问题的评测集。跑完一批改动后,用脚本统计三层指标,再人工抽检 20 条看典型案例。工具调用次数和无效检索率这类流程指标在一些开源评估组件里也能自动算,但生成质量的判定我建议用“强模型 + 规则”双重校验,不能只信单一打分模型。
4.3 日志与可观测性
Agentic RAG 的调试难度比传统 RAG 高一个量级,因为问题可能出在任意一个环节:改写改歪了、路由判错了、检索结果不行、反思没拦住幻觉。没有可观测性,排查就是瞎子摸象。
我要求线上系统必须记录以下几类日志:每次调用大模型的输入输出和 token 数、每个工具的执行时间与返回摘要、每次循环的决策路径、以及最终答案引用的证据 ID 列表。有了这些,才能快速定位一个坏案例到底是在哪一步变坏的。
这里一个常被忽略的坑是:光记录不行,还必须把“决策路径”可视化。否则一条 8 步的执行链路,靠翻 JSON 日志能把你逼疯。我现在的做法是执行完把链路整理成文本树,展示“问题 → 路由结果 → 子问题A → 检索K条 → 反思结论 → 最终答案”,出现问题直接看树,比翻日志高效得多。
5. 常见问题排查与避坑实录
5.1 问题排查速查表
把我在不同项目里遇到频率最高的问题整理成了一张表,基本能覆盖上线初期的绝大多数状况:
| 症状 | 常见原因 | 处理方式 |
|---|---|---|
| Agent 反复调用同一个检索工具 | 状态里没有记录已查过的 query,反思节点反复触发同一动作 | 在状态里维护searched_queries列表,检索前检查重复 |
| 简单问题被丢进复杂多跳流程 | 路由描述不清晰,或置信度阈值设置过高 | 给路由加反面示例,低置信度回退到轻量 RAG |
| 答案内容流畅但与文档矛盾 | 反思节点只做“走流程”而没真正判断证据一致性 | 加强反思 Prompt,强制模型逐条核对证据引用 |
| 延迟偶尔飙升到 15 秒以上 | 子问题串行检索 | 把子问题检索改成并发执行 |
| 工具调用报错但 Agent 继续硬编答案 | 工具异常没有进入状态流转 | 给工具调用加 try/except,异常时返回结构化错误信息,触发重试 |
| 成本一天跑下来吓人 | 所有问题都走 Agent 全流程 | 路由前置,简单问题走单跳;小模型承担中间步骤;加强缓存 |
| 检索结果跟问题完全不沾边 | 改写后的 query 引入了错误实体 | 检查改写环节输出,必要时约束改写只做语句整理,禁止新增内容 |
5.2 我踩过的三个典型的坑
第一个坑是工具描述写得太“教科书”。我一开始给检索工具写的是“Retrieve relevant documents based on the query”,结果模型什么场景都调它,把实时数据的问题也往文档库里怼。后来我把描述改成“用于从企业内部知识库中查找非结构化文本资料;不适合用来查询实时库存、价格、订单状态,此类数据请调用 SQL 查询工具”。效果立刻改善,工具误调率降了一大截。
第二个坑是“反思节点形同虚设”。一开始的反思节点只是让模型“判断是否通过”,模型为了省事经常一路 ACCEPT,幻觉流到用户面前才发现。后来我改成强制模型输出“支持证据列表 + 缺失证据列表”,必须具体指出缺什么、哪个结论没有被覆盖,才允许 ACCEPT。这一改,质检效果明显提升,但也暴露出另一个问题:反思太严格导致大量问题进入重试,成本和延迟上来不少。最后我加了权衡策略——只有涉及数值、对比、因果判断的问题才启用严格反思,纯常识问答不启用。
第三个坑是“多智能体过度设计”。我看过一些项目,把检索、重排、生成、反思各拆成一个独立 Agent,还搞了“Agent 之间的消息通信”。听起来很酷,实际调试成本几何级上升。我的经验是:Agentic RAG 优先做“单 Agent 多工具”,工具之间用状态共享而不是消息通信。只有当任务边界足够清晰、比如“对话管理”和“数据查询”完全分属两个系统时,才值得拆多 Agent。
最后的个人体会
一个 Agentic RAG 系统从能跑通到能上线,中间的差距几乎全在控制:控制循环次数、控制成本、控制模型自由发挥的空间、控制工具调用的边界。我个人的建议是不要一上来就做一个庞大的多智能体系统,而是先把“改写→路由→检索→反思→生成”这条最小链路跑明白,用日志观察每条问题到底卡在哪一步,然后逐步加能力。我们团队现网稳定运行的版本,核心节点不算多,但每一步都有明确的进入和退出条件,每个工具都有反面触发描述,每条答案都能回溯到证据。做到这个程度,用户感受到的就不是“模型变聪明了”,而是“这个系统终于知道自己什么时候该停下来、什么时候该换个方式再试一次”。