Agentic RAG这个名字,最近一年几乎成了RAG领域的显学。不管你是用LangChain、LlamaIndex还是自己从零搭流水线,讨论的焦点都已经从"怎么切分文档、怎么调embedding",转移到了"怎么让检索过程具备自主规划与决策能力"。作为这个系列的第三篇,我默认你已经对基础RAG有实操经验——向量库、召回、重排这些概念不再赘述,这篇文章聚焦一件事:把Agent的能力真正嵌入到RAG链路中,讲清楚架构怎么设计、代码怎么写、生产环境怎么调。
这个系列前两篇分别讲了RAG的基线架构和检索质量优化,这一篇想聊的是进阶形态:Agentic RAG。它适合谁看?适合那些已经跑通了基础RAG、但发现复杂问题答不好、多跳问题答不了、检索失败时系统还硬着头皮编答案的团队。你不需要是Agent方向的专家,但最好对LangGraph或类似的状态机框架有一点概念。文章里所有代码都是基于LangGraph的Python实现,我会把每一步为什么这么写讲透,而不是丢一堆demo了事。
1. Agentic RAG到底在解决什么问题
先说个我自己的观察。很多团队做RAG的方式,本质上还是"召回-重排-生成"三段式流水线。用户问一个问题,系统去向量库捞top-k文档,塞进prompt里让大模型回答。这种架构在"单点事实型问题"上表现不错,但一旦问题涉及多步推理、跨文档对比、或者检索结果本身不充分,整个系统就会暴露出非常明显的短板。
1.1 传统RAG的两堵墙
第一堵墙叫单次检索的天花板。用户问"张三在A公司期间参与的项目,后来被B公司收购后发生了哪些组织调整",这个问题至少要拆成三跳:先查张三在A公司的项目,再查B公司收购的细节,最后查组织调整。传统RAG只能做一次向量检索,它拿到的top-k文档大概率只覆盖其中一跳,后面的内容全靠大模型脑补。我见过太多项目卡在这一层,不断调chunk size、调embedding模型,但问题是结构性的,调参解决不了。
第二堵墙叫无反馈的盲答。向量检索这个环节太偷懒了——它不知道自己的结果够不够用,也不会对结果做任何质量判断。更麻烦的是,生成阶段的大模型也不判断检索到的东西能不能支撑回答,它只会顺着已有的材料"编"下去。结果就是:你问一个库里有明确答案的问题,系统给出的回答却和库里的事实对不上,而且它自己毫无察觉。
这两堵墙的本质,是检索与生成之间缺少一个反馈闭环。传统RAG假设"检索一次就够",但这个假设在真实业务里几乎不成立。用户问题千奇百怪,检索的召回率再高也总有漏网之鱼,模型自己对"证据是否充分"这件事的判断能力,比我们想象中弱得多。
1.2 Agentic RAG的核心转变:检索不再是终点
Agentic RAG做的事情,简单说就是让大模型在一个循环里自主决定"什么时候检索、检索什么、要不要继续检索、什么时候停止、最终怎么回答"。它不再把检索当成一个固定步骤,而是把一个检索器封装成Agent手里的工具,让Agent围绕问题展开多轮"思考-行动-观察"循环。
这个转变带来三个直接好处:
- 多跳问题被拆解:Agent先把问题拆成若干子问题,每个子问题单独检索,检索结果组合起来再生成答案。
- 检索失败有兜底:Agent检完一轮之后会自我检查,发现信息不够就换一种查询方式再检一轮,而不是硬着头皮乱答。
- 工具组合成为可能:检索只是工具之一,Agent还可以调用计算器、数据库查询、API接口等其他工具,检索结果和这些工具的输出一起参与推理。
我用一个生活化的类比帮你理解:传统RAG像一个只有"查词典"一个动作的实习生,你问"这个季度比上季度增长了多少",他直接翻开一页词典找答案。Agentic RAG则是那个会先看"你是要哪个季度、什么口径、增长率还是增长量"、发现词典缺页会主动去翻另一本参考书、最后还会自己核对一遍数字的成熟员工。
1.3 什么场景真正需要Agentic RAG
这里得泼一盆冷水:不是所有RAG场景都需要Agentic。如果你的业务问题80%以上是"XX产品的退货政策是什么""XX平台的客服电话是多少"这种单点事实查询,传统RAG配合好的rerank完全够用,硬上Agent只会增加延迟和成本。
根据我这大半年的落地经验,以下四类场景才真正需要Agentic化:
| 场景类型 | 典型问题 | 传统RAG表现 | Agentic RAG优势 |
|---|---|---|---|
| 多跳推理 | 某业务的负责人是否参与过收购案 | 只命中其中一跳,答错 | 拆解子问题,逐跳验证 |
| 对比分析 | 对比两款产品的退换货与售后政策 | 只覆盖一个产品 | 多路检索,组合对比 |
| 条件不足 | 用户问题缺少关键限定条件 | 按默认假设乱答 | 主动追问或按条件枚举 |
| 跨工具协作 | 算某个指标并解释计算依据 | 无法结合计算 | 检索+计算器协同 |
判断标准其实很简单:你拿10个真实用户问题去跑现有RAG,如果5个以上因为"问题太复杂"或"检索结果不全"导致答错,那就是该上Agentic RAG的时候了。如果错误主要来自chunk切分和embedding质量,先把基础优化做完再考虑Agent,别急着追热词。
2. 系统架构:Agentic RAG的核心模块怎么设计
想落地Agentic RAG,第一步不是写代码,而是想清楚架构。我见过不少人直接把"Agent"简单理解成"让大模型决定调几个函数",结果写出来的东西既没有状态管理,也没有退出条件,跑起来一团乱麻。下面按我实际验证过的方案拆开讲。
2.1 规划器:把大问题拆成检索任务
Agentic RAG的第一个核心模块是规划器(Planner)。它的职责是接收用户原始问题,输出一个检索执行计划。这个计划可以是一系列子问题,也可以是一系列检索指令,关键要满足三个条件:子问题之间不重叠、每个子问题能独立检索、子问题的组合能覆盖原问题。
这里有个容易踩的坑:很多人让规划器一次拆很多个子问题,结果拆出来的东西相互纠缠,检索时上下文搅在一起。我的经验是硬性限制子问题数量。一个复杂问题拆2-3个子问题就够了,超过3个建议改为"多轮交互"——先回答第一层,再根据结果决定要不要追问。因为拆得越细,单次检索的质量越难保证,组合时的误差也被放大。
规划器的输出格式建议用JSON,而不是自由文本。原因很实际:后续节点要解析子问题并逐一检索,结构化输出能省掉大量解析错误。我在生产环境里会要求模型严格输出{"sub_questions": ["...", "..."]},并在prompt里给两个few-shot示例,实测能把格式错误率压到1%以下。
2.2 验证与反思:让Agent知道自己"没查到"
这是Agentic RAG和传统RAG最本质的区别:系统必须有能力判断"当前检索到的信息够不够用"。这个模块我一般叫验证器(Verifier),也有些人叫反思器(Reflector)。
验证器的实现有两种路线。一种是把检索结果和问题一起交给大模型,让它判断"基于这些资料能否回答用户问题",不能回答就返回一个特定标记。另一种是做更细的逐条证据检验——把关键断言从待回答问题里抽出来,逐条核对检索结果中是否有对应事实。前者实现简单、速度更快,后者更严谨、能精确指出缺什么。
我实际用的是折中方案:第一轮验证用"能否回答"的粗粒度判断;如果判断为不能,再进入"缺什么信息"的细粒度分析,把缺失信息作为下一轮检索的查询词。这样既避免了每轮都做细粒度分析的延迟开销,又给第二轮检索指明了方向。
验证器的prompt里有一个特别重要的细节:必须让模型区分"资料里明确说了没有"和"资料里根本没提到"。前者是负证据,后者是缺失证据,处理方式完全不同——前者可能说明问题本身有问题(比如问了不存在的东西),后者才应该触发补充检索。
2.3 记忆管理:短期状态与上下文压缩
Agent在循环里会产生大量中间信息:子问题列表、每轮检索到的文档、验证结果、已经回答过的部分。这些信息如果全部堆在上下文里,很快会把大模型的context window撑爆。
所以要有一个状态管理的意识。我用LangGraph实现时,会把状态分为两类:
- 短期工作记忆:当前子问题、当前轮检索结果、验证结论,这部分在每轮循环结束后就清空或压缩。
- 长期累积信息:已经确认的事实、已经排除的方向、最终需要合成的证据列表,这部分贯穿整个Agent循环。
一个非常实用的技巧是每轮检索后做一次"提炼"——不是把原始文档塞进状态,而是让模型把文档里和当前子问题相关的部分提炼成两三句话存入记忆中。这样多轮循环下来,context增长是线性的而不是爆炸式的。我见过很多人忽略这个步骤,跑到第三轮就把context跑到接近上限,导致后面的生成质量急剧下降。
2.4 工具集设计:检索器不是唯一的工具
Agentic RAG虽然名字里带RAG,但真正生产化之后你会发现,检索器只是Agent工具集里的一把刀。以我做的企业知识库问答系统为例,最终给Agent配了四个工具:
- 向量检索器:处理语义相似度召回,适合开放式问题。
- 关键词检索器:处理专有名词、编号、精确术语,比如"工单编号为CS-2024-011的任务",向量检索经常翻车,BM25反而一查一个准。
- SQL查询器:处理结构化数据,比如"上季度各产品线的投诉率",这不该走向量库。
- 计算器/代码解释器:处理需要精确计算的数值问题。
工具选择本身也是一个Agent决策点。我在规划器之后加了一个工具路由节点,让模型判断当前子问题更适合哪个工具。这个节点的prompt会给每个工具附上适用场景示例,并明确规则:不确定时就选向量检索器,因为它容错性最好。
工具设计的核心原则是每个工具都要有清晰的边界描述。我曾经犯过的错是把工具描述写得太宽泛,导致Agent把"查一个专有编号"的问题错误地路由给了向量检索,结果一路跑偏。后来我重新改了工具描述,每个都加上"适用XX、不适用XX"的否定式描述,路由准确率提升非常明显。
3. 实操:基于LangGraph落地一个Agentic RAG
讲完架构,进入代码环节。我用LangGraph来演示,因为它把Agent循环建模成一张状态图,节点、边、条件路由都显式声明,比纯ReAct的prompt循环更容易控制和调试。这个项目我跑在Python 3.11 + LangChain 0.2环境上,如果你用的是旧版本,API细节可能略有差异。
3.1 技术选型与目录结构
先说明为什么选LangGraph而不是直接手写while循环调LLM。手写循环的问题是:状态全靠全局变量维护,没有清晰的执行轨迹,一旦循环次数多了或者某个节点出错,你很难定位问题。LangGraph把一切变成显式的图:每个节点是纯函数,状态在节点之间传递,图本身可以可视化、可以断点调试。生产环境里这个优势非常值钱。
项目目录结构参考:
agentic_rag/ ├── graph/ │ ├── __init__.py │ ├── state.py # 状态类型定义 │ ├── nodes.py # 各节点实现 │ ├── router.py # 条件路由函数 │ └── build_graph.py # 图编排入口 ├── tools/ │ ├── vector_store.py # 向量检索器 │ ├── keyword_store.py # 关键词检索器 │ ├── sql_tool.py # SQL查询工具 │ └── calculator.py # 计算工具 ├── llm.py # LLM统一封装 └── main.py # 对外调用入口3.2 状态定义与节点实现
状态是整个Agent循环的"共享工作台",我定义如下:
# graph/state.py from typing import Annotated, TypedDict from langgraph.graph import END, StateGraph import operator class AgentState(TypedDict): question: str # 用户原始问题 sub_questions: list[str] # 规划器拆解的子问题 current_index: int # 当前执行到第几个子问题 contexts: Annotated[list, operator.add] # 已确认的证据列表 memory: list[str] # 长期记忆(事实摘要) need_more: bool # 是否需要补充检索 missing_info: str # 缺失信息的描述 final_answer: str # 最终答案 max_rounds: int # 最大循环轮数,防止死循环注意contexts用operator.add做累加器——这意味着每次有新证据进来都追加到列表,而不是覆盖。这非常重要,因为多轮循环中各轮检索到的证据都需要保留到最终生成环节。
规划器节点的实现:
# graph/nodes.py import json from llm import llm PLANNER_PROMPT = """你是检索规划器。请将用户问题拆解为1-3个独立的子问题。 硬性要求: 1. 子问题之间不得重叠 2. 每个子问题必须能独立进行信息检索 3. 简单问题只输出1个子问题,不要过度拆分 4. 输出必须是JSON格式:{"sub_questions": ["子问题1", "子问题2"]} 示例1: 用户问题:张三在A公司期间负责的项目后来怎么样了? 输出:{"sub_questions": ["张三在A公司期间负责了哪些项目?", "这些项目后续的发展或结局是什么?"]} 用户问题:{question}""" def planner_node(state: AgentState): resp = llm.invoke(PLANNER_PROMPT.format(question=state["question"])) try: plan = json.loads(resp.content) sub_questions = plan["sub_questions"][:3] except Exception: # 解析失败时降级:把原问题当成唯一子问题 sub_questions = [state["question"]] return { "sub_questions": sub_questions, "current_index": 0, }这里加了一个降级逻辑:解析失败就把原问题当唯一子问题。我在这上面吃过亏,一开始解析失败会直接抛异常,结果整个Agent流程挂掉。生产环境里任何解析逻辑都要有降级路径,宁可退化成传统RAG,也不能让流程中断。
检索节点:
def retrieve_node(state: AgentState): sub_q = state["sub_questions"][state["current_index"]] # 1. 先走向量检索 vec_docs = vector_retriever.invoke(sub_q, top_k=6) # 2. 再走关键词检索,补充精确匹配结果 kw_docs = keyword_retriever.invoke(sub_q, top_k=3) # 3. 合并去重后用cross-encoder重排 merged = deduplicate(vec_docs + kw_docs) pairs = [[sub_q, doc.page_content] for doc in merged] scores = reranker.predict(pairs) ranked = [doc for _, doc in sorted(zip(scores, merged), reverse=True)] best_docs = ranked[:3] # 4. 提炼为短期记忆 content = "\n".join(d.page_content for d in best_docs) summary = llm.invoke(f"从以下资料中提炼与问题'{sub_q}'直接相关的要点,不超过100字:\n{content}") return { "contexts": [d.page_content for d in best_docs], "memory": [f"【{sub_q}】{summary.content}"], }我这个检索节点里其实藏了一个关键设计:向量+关键词双路召回。为什么这么做?因为向量检索对同义改写容忍度高,但对精确编号、人名、产品型号经常失手;关键词检索恰好相反。两条路召回的结果合并后重排,能显著提升召回质量。这个技巧只增加几十毫秒开销,收益却很实在。
验证节点:
VERIFY_PROMPT = """基于以下资料判断:能否回答用户问题? 资料: {contexts} 用户问题:{question} 判断规则: - 如果资料中包含回答问题的充分事实,输出 ANSWERABLE - 如果资料部分相关但缺少关键信息,输出 UNDERSPECIFIED,并在下一行用一句话说明缺少什么 - 如果资料与问题完全无关,输出 IRRELEVANT 只输出以上三种判断之一,不要输出其他内容。""" def verify_node(state: AgentState): contexts = "\n".join(state["contexts"]) resp = llm.invoke(VERIFY_PROMPT.format( contexts=contexts, question=state["sub_questions"][state["current_index"]] )) content = resp.content.strip() if content.startswith("ANSWERABLE"): return {"need_more": False} elif content.startswith("UNDERSPECIFIED"): missing = content.split("\n")[1] if "\n" in content else "需要补充更多细节信息" return {"need_more": True, "missing_info": missing} else: # IRRELEVANT return {"need_more": True, "missing_info": "检索结果与问题不相关,需要更换检索词"}这里有个细节值得强调:验证节点的判断对象是当前子问题,不是用户的完整问题。因为子问题已经拆得很细了,"资料能否支撑这个子问题"的判断会容易得多。如果你拿完整问题去验证,模型会被长问题干扰,经常误判。
3.3 图编排与条件路由
节点写完之后,用StateGraph把它们串起来。关键是定义"什么时候该走哪条边"的条件路由:
# graph/build_graph.py from graph.state import AgentState from graph.nodes import planner_node, retrieve_node, verify_node from graph.router import route_after_verify, route_after_planner def build_agentic_rag_graph(): g = StateGraph(AgentState) g.add_node("planner", planner_node) g.add_node("retrieve", retrieve_node) g.add_node("verify", verify_node) g.add_node("generate", generate_node) g.set_entry_point("planner") # 规划器执行完后,进入第一个子问题的检索 g.add_edge("planner", "retrieve") # 检索后进入验证 g.add_edge("retrieve", "verify") # 验证后的路由:决定继续检索、还是进入下一个子问题、还是直接生成 g.add_conditional_edges( "verify", route_after_verify, { "retry": "retrieve", # 补充检索 "next_sub": "retrieve", # 进入下一个子问题(先检索) "generate": "generate", # 所有子问题处理完,生成最终答案 } ) g.add_edge("generate", END) return g.compile()路由函数是整张图的决策中枢:
# graph/router.py def route_after_verify(state: AgentState): sub_qs = state["sub_questions"] idx = state["current_index"] # 轮数保护:超过最大循环就强制生成 if idx >= len(sub_qs): return "generate" # 当前子问题需要补充检索 if state["need_more"]: if state.get("retry_count", 0) >= 2: # 重试次数过多,跳过当前子问题 return "next_sub" return "retry" # 当前子问题完成,进入下一个 return "next_sub"注意我在路由里加了一个重试上限。这是个容易忽略的点:如果不设上限,某个子问题如果一直检索不到合适资料,Agent会陷入无限循环。我设了2次重试上限,超过就让Agent带病前进——去处理下一个子问题,最终生成时模型自己会知道某些部分证据不足。这比卡死整个流程划算得多。
最终生成节点:
GENERATE_PROMPT = """你是知识库问答助手。请基于以下证据回答用户问题。 证据: {memory} 用户问题: {question} 要求: 1. 严格基于证据回答,不要编造 2. 如果证据不足,明确说明"根据现有资料无法完整回答"... 3. 综合所有子问题的证据,给出连贯回答""" def generate_node(state: AgentState): memory = "\n".join(state["memory"]) resp = llm.invoke(GENERATE_PROMPT.format( memory=memory, question=state["question"] )) return {"final_answer": resp.content}3.4 关键参数与调优心得
这节全是真金白银的踩坑经验,我按重要性排序:
模型参数。规划器和验证器节点用温度0.1以下的采样,原因很简单:这些节点做的是解析和判断,不是创意生成,温度一高就容易"发挥",输出格式开始漂。生成节点可以用温度0.3左右,稍微给一点自由度,但别超过0.5,否则容易脱离证据自由发挥。另外,如果有预算,规划器用强一点的模型(比如更大的参数或更擅长指令遵循的那个),检索器和生成器用中等模型就行。
检索参数。top_k的设置我试过从3到10,最佳区间是5-8。太小了召回不够,验证阶段容易误判"资料不足";太大了噪声增多,重排的精度下降。另外一定要给检索器加上分数阈值——低于阈值的文档直接丢弃,宁缺毋滥。原因是低分文档通常是语义不相关的噪声,留它们在后边只会干扰验证和生成。
循环上限。我建议把整个Agent循环的步数上限设成子问题数乘以3。比如拆了3个子问题,最多允许9步。这个上限写在状态里,每个节点执行时都检查一次。别嫌这种保护多余,我在demo阶段就因为忘记设上限,让一个特别刁钻的问题跑了20多轮,烧了几十块钱的token才反应过来。
4. 生产环境的评测、成本与体验优化
写demo和上生产完全是两码事。Agentic RAG引入了一个传统RAG没有的问题:循环次数不确定,导致成本和延迟不确定。这一节聊怎么把这两个变量重新摁住。
4.1 评测:用回溯验证集兜底
很多团队对Agentic RAG的评测还在用"找几个样例跑一下看看"的方式,这在生产化阶段是远远不够的。因为Agent的行为是发散性的——同一个问题,今天跑和明天跑可能中间步骤都不一样。你需要一个结构化评测集来兜底。
我推荐"回溯验证集"方案:从线上日志里收集最近一周的真实用户问题,人工标注标准答案,组成一个200-500条的问题集。每次改代码、调prompt,都在这个集合上回归。评测指标我用四类:
| 指标 | 计算方式 | 关注什么 |
|---|---|---|
| 答案准确率 | 人工打分或LLM-as-judge评分 | 最终答案质量 |
| 关键点覆盖率 | 标准答案里的要点是否全部出现 | 回答完整性 |
| 平均步数 | 所有问题的Agent循环步数均值 | 系统效率 |
| 无效循环率 | 达到步数上限才结束的问题占比 | 路由与验证质量 |
这里有个重要经验:把"无效循环率"单列出来。传统RAG不存在这个问题,但Agentic RAG一旦验证器判断不准确,就会出现大量"检索-验证-再检索"的空转。我在早期版本里,无效循环率一度高达30%,后来把验证prompt改严谨、加了重试上限,才压到5%以下。这个指标是判断系统健康度的晴雨表。
4.2 控制成本与延迟
Agentic RAG的延迟比传统RAG高多少?我测过一组平均数据:传统RAG端到端延迟1.2秒,Agentic RAG平均3.8秒,复杂问题甚至到8秒。成本大约是2到5倍。如果你的产品对延迟敏感(比如在线客服、实时助手),必须做三件事:
第一,设置延迟预算。在Agent循环里记录每个节点耗时,如果某一步耗时超过设定值(比如检索超过800ms),触发降级路径——不再重试,直接进入下一步。这和数据库的超时机制是一个思路。
第二,使用"快速通道"。在进入Agent循环之前,先做一次轻量判断:这个问题是"简单问题"还是"复杂问题"?简单问题(单点事实、关键词明确)直接走传统RAG快路径;复杂问题才进Agent循环。这个判断本身也是一次LLM调用,但用的是小模型、短prompt,成本很低。我上线这个快速通道之后,平均延迟从3.8秒降到了2.1秒,因为约60%的问题都被分流到了快路径。
第三,缓存命中的问题。把用户问题和Agent执行计划做缓存。同一问题再次出现时,直接复用上次的执行计划,跳过大半Agent循环。这里要注意缓存键的设计——不是缓存最终答案,而是缓存"问题→执行计划",因为相同问题在不同时间可能需要不同答案(比如价格、政策会变)。执行计划复用后,检索是新的,但规划成本全省了。
4.3 与并行检索的混合策略
Agentic RAG处理多跳问题是串行的——一个一个子问题来。但实际场景里,很多子问题之间并没有依赖关系。比如"对比A和B的售后政策",两个子问题完全独立,串行白白浪费了时间。
我现在的做法是给每个子问题打一个"依赖标签":规划器在输出子问题时,同时标注每个子问题是否依赖前序子问题的结果。独立的子问题放到一个批次里并行检索,有依赖的才串行等待。LangGraph的fan-out/fan-in机制支持这个,实现起来大概多花半天时间,但延迟能再降30%左右。
不过并行也有代价:多个检索请求同时打向量库,数据库QPS会翻倍;返回结果合并时上下文顺序需要重新编排。我建议先跑串行版本验证效果,再在问题量上来之后优化成并行,别一上来就并行,增加排查复杂度。
5. 常见问题与排查实录
最后这部分,整理我在生产环境里被问得最多的问题,以及对应的排查思路。
5.1 高频问题速查表
| 现象 | 根本原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| Agent陷入无限循环 | 验证器持续判"不足" | 看每轮验证输出,确认判断逻辑 | 加重试上限,改进验证prompt |
| 答案前后矛盾 | 各子问题证据冲突 | 检查各子问题检索到的文档领域 | 生成时增加"证据一致性核对"指令 |
| 规划拆解过于碎片 | 规划prompt缺少硬性限制 | 查看规划输出,统计子问题数量 | 限制最多3个子问题,强约束JSON |
| 检索结果偏题 | 工具路由错误 | 查看工具路由节点日志 | 改写工具描述,增加否定式边界 |
| 延迟突然飙升 | 并行检索打爆向量库 | 监控检索节点P99耗时 | 增加向量库连接池,限制并发数 |
| 格式化错误 | Few-shot示例不足 | 查看原始模型输出 | 增加2-3个反例,降温度 |
5.2 三个独家避坑技巧
避坑一:不要让Agent直接操作真实生产数据库。
我在最初版本里让Agent可以直接执行SQL查询,结果它拿到一个含糊的问题后生成了一个没带WHERE条件的查询,把整张表拉出来算了一遍,接口直接超时。后来我改成"参数化查询模板"——Agent只能填参数,不能写完整SQL。这个约束把风险降了一个数量级。凡是Agent的工具,都要考虑"异常输入时的破坏力",能限制就限制。
避坑二:一定要给验证器加"记忆偏差"防护。
这是我从一次线上事故里学到的。用户连续问了好几个关于同一产品的问题,Agent的长期记忆里积累了关于这个产品的信息。到第五个问题时,检索器其实没召回相关内容,但验证器因为看到记忆里有相关事实,错误地判断为"答案可回答",最终生成了大量基于过时记忆的错误信息。修复方式:验证器判断"能否回答"时,只允许看当前轮检索到的新证据,不能看长期记忆。长期记忆只能用于最终生成,不能用于有效性判断。
避坑三:把Agent的思考过程写进日志,而不是只记最终答案。
传统RAG的日志一行就够:问题、检索文档、回答。Agentic RAG必须记录完整的轨迹:每个子问题、每轮检索的查询词、验证结论、重试原因、内存摘要、每步耗时。这不仅是排查问题的依据,更是复盘"这个Agent为什么会这么想"的唯一线索。我建议用JSON Lines格式按步骤追加,每步一行,方便用jq之类的工具分析。
有一回线上用户反馈"系统回答得莫名其妙",我翻开日志一看,发现Agent在第三轮检索时把子问题的查询词改了个离谱的方向,检索结果完全跑偏,后面的验证器居然还通过了。如果只有最终日志,这种问题根本查不出来。
写在最后的个人体会
Agentic RAG这半年给我的最大感受是:它不是一个"装上就能变强"的模块,而是一套需要持续调校的运行时。比写Agent节点更难的是让这个系统稳定、可控、可预测。我个人在实际操作中最常在验证器上花时间——它就像是Agent的方向盘,调好了系统指哪打哪,调不好就是花钱跑偏。
如果你是从基础RAG迁移过来,我强烈建议先不要动现有架构,而是把Agentic RAG作为一个独立增强通路接到旁边:简单问题走老路,复杂问题才分流到Agent通路。跑两周对数、对比准确率后再决定是否全量切。最后再分享一个扩展方向——我现在正在把多模态检索也塞进Agent的工具箱里,让它能同时查文本、表格和图片。这个系列如果有下一篇,我想专门聊聊这个话题。