ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Agentic RAG架构拆解:从检索管道到目标导向的智能决策

Agentic RAG架构拆解:从检索管道到目标导向的智能决策

1. 从Retriever到Agent:Agentic RAG到底解决了什么问题

先讲一个特别直观的场景。早期RAG系统做得再漂亮,本质还是一套“查询—召回—拼接—生成”的固定流水线。用户问“帮我对比一下Q1和Q2的营收差异”,普通RAG会先把这个提问原封不动塞给向量检索,召回一堆可能相关的文本块,然后让LLM强行拼答案。如果数据库里根本没有一张表同时包含两个季度的对比数据,回答就会开始胡编。

我做了三年RAG项目,最大的感受是:RAG的瓶颈不在检索,而在决策。检索质量再高,也只是把一个用户的真实意图压扁成一次向量查询。很多问题根本不是“查一次”能解决的,它需要拆解、需要多步搜索、需要在不同数据源之间切换、需要发现第一次检索结果不够时换个思路再查。这些动作,传统RAG的固定链路全都没有。

这就是Agentic RAG出现的核心原因。它把RAG从“查询管道”升级成“目标导向的推理过程”,让模型在检索的每一个环节都能做决策:要不要拆解问题?先查哪个数据源?当前证据够不够?答案之间有没有冲突?生成结果需不需要被验证?

我用最直白的话概括:普通RAG是“一问一查”,Agentic RAG是“一问多查,边查边想”。后者不追求一次召回就命中,而是通过不断的工具调用和自我评估,逐步逼近准确答案。这个过程本质上就是一个轻量级Agent——它拥有大模型的推理能力,又通过工具(检索器、代码执行器、数据库查询器)感知外部世界。

所以这篇我主要讲三件事:Agentic RAG的架构该怎么拆、核心决策节点怎么设计、以及我在真实项目中踩过的坑。前面已经写过两篇基础内容,这篇介于工程实践与原理剖析之间,适合已经跑通过普通RAG链路、正在考虑把系统升级成Agent形态的开发者和架构师。

2. 拆解Agentic RAG的五大核心模块

很多人一听到Agent就以为要用复杂的强化学习或者上百个节点的编排框架。实际落地完全不是这样。我拆过多个生产级项目,Agentic RAG在本质上就是五个模块的组合,只是它们之间的调用关系从“线性”变成了“动态路由”。

2.1 Agent Core:负责“想”的调度中枢

Agent Core是整个系统的决策核心,通常就是一个具备函数调用能力的LLM。它不直接回答用户问题,而是负责制定计划、选中工具、判定结果是否满足要求。在我常用的LangGraph实现里,Agent Core会被设计成一个循环节点:每次迭代都让模型观察当前状态、决定下一步动作,直到它自己认为“信息已经足够,可以给出最终答案”。

这里有个关键点:Agent Core的模型选型不能太弱。至少要具备稳定的工具调用能力和基础的推理能力。我测试过多款模型,发现参数量低于一定规模后,模型会频繁出现“不知道该调哪个工具”的迷茫状态,甚至把工具名编造出来。生产环境我用的是支持并行函数调用的模型,这样在多数据源查询时能明显提速。

2.2 Planner:把复杂问题拆成可执行的子任务

Planner是“把大问题切小”的模块。用户问“去年每个月各渠道的获客成本趋势是什么”,按照Agent思路,Planner会把这个需求拆成几个步骤:先定位月维度数据源,再查询渠道维度字段,最后交给生成模块做趋势归纳。

在实际项目中,Planner不一定要写成独立节点,也可以直接交给Agent Core在上下文中完成拆解。但我的建议是:如果系统要走多步检索,最好把拆解结果显式记录下来,这样后续每一步检索都能引用前一步的结论,也方便在日志里排查问题。我自己习惯用JSON格式保存中间计划,字段包括thought、tool、query和reason。

2.3 Tool Registry:检索器之外的“只手”

Agentic RAG和普通RAG最明显的差异就在这里。普通RAG只有一个向量检索器,Agentic RAG则是一个工具集——向量检索只是其中一种工具。我常用的注册表长这样:

工具名用途适合场景
vector_search语义相似度检索开放式问题、非精确匹配
keyword_search关键词/倒排索引检索专有名词、编码、文献编号
sql_query结构化查询数值统计、对比分析、报表生成
web_search实时联网搜索时效性较强的信息
code_interpreter执行Python代码计算、数据分析、图表生成

真正让Agentic RAG变“聪明”的,是它能在这些问题之间做路由。比如用户问“A产品的库存周转天数”,如果知识库里刚好有接口文档,模型可能会选择调用sql_query而不是向量搜索——因为这是结构化数据问题,向量检索帮不上忙。

2.4 Memory:短期上下文与长期状态的协同

Agentic RAG的决策链条往往很长,一旦上下文窗口被中间检索结果塞满,模型很容易“忘掉”最初的提问是什么。所以Memor模块不只是缓存对话历史,还要负责管理检索中间结果的去留。

我采用的做法是双轨记忆:短期记忆保存当前任务内产生的所有检索片段,长期记忆保存跨任务沉淀下来的用户偏好和领域知识。对于一次复杂的多跳检索,短期记忆里的内容可能占几千token,我会在进入生成阶段前对它们做一次去重和重排,只保留对最终答案有直接贡献的片段,避免上下文稀释。

2.5 Reflection:让系统自己检查自己

Reflection模块是我在所有Agent系统里都会强制加上的。它的作用是让LLM对自己的答案做一次“体检”:引用是否匹配?数字是否一致?是否有幻觉风险?如果体检不过,就把问题反馈给Agent Core,触发新一轮检索或修正。

Reflection听起来简单,实际做起来坑很多。最典型的错误是模型把“反思”变成“复述”——回答的内容跟上一轮一模一样,只是换了个措辞。我后面会专门讲怎么用固定提示词和差分检测来规避这个现象。

3. 核心实现:用LangGraph搭一个可运行的Agentic RAG循环

这一节我直接给一套可以跑通的最小实现。选LangGraph是因为它对“循环”的支持最干净:普通LangChain用链表达不了复杂的条件跳转,LangGraph用图结构可以轻松实现“检索—反思—再检索”的闭环。

3.1 图结构的设计思路

先明确我们需要哪些节点:plan_node(规划)、retrieve_node(检索)、reflect_node(反思)、generate_node(生成)。连线逻辑如下:

  • plan_node输出计划后,进入retrieve_node执行检索。
  • retrieve_node的结果送给reflect_node做质量判断。
  • reflect_node如果判定“结果充足”,就转generate_node生成最终答案。
  • 如果判定“结果不足”,跳回retrieve_node,执行新一轮检索,同时允许修正检索策略。
  • 如果检索轮数超过最大限制(比如3次),强制进入generate_node,避免死循环。

3.2 节点代码示例

from langgraph.graph import StateGraph, END from typing import TypedDict, List class AgentState(TypedDict): query: str plan: dict documents: List[str] retry_count: int answer: str # 规划节点 def plan_node(state: AgentState): query = state["query"] # 这里用LLM生成结构化计划 prompt = f"""拆解以下问题,输出JSON: 1. thought: 分析思路 2. tool: 选择的工具 3. query: 实际查询语句 问题:{query}""" plan = llm.invoke(prompt) return {"plan": plan} # 检索节点 def retrieve_node(state: AgentState): plan = state["plan"] tool = plan["tool"] query = plan["query"] if tool == "vector_search": docs = vector_store.search(query, k=4) elif tool == "sql_query": docs = db.query(query) else: docs = web_search.run(query) return {"documents": docs, "retry_count": state["retry_count"] + 1} # 反思节点 def reflect_node(state: AgentState): if state["retry_count"] >= 3: return {"sufficient": True} prompt = f"""判断以下文档是否能完整回答用户问题,只回答YES或NO。 问题:{state["query"]} 文档:{state["documents"]}""" judgment = llm.invoke(prompt) return {"sufficient": judgment == "YES"}

3.3 构建与编译

graph = StateGraph(AgentState) graph.add_node("plan", plan_node) graph.add_node("retrieve", retrieve_node) graph.add_node("reflect", reflect_node) graph.add_node("generate", generate_node) graph.set_entry_point("plan") graph.add_edge("plan", "retrieve") graph.add_edge("retrieve", "reflect") # 条件边:反思结果决定下一步走向 graph.add_conditional_edges( "reflect", lambda state: "generate" if state["sufficient"] else "retrieve", {"generate": "generate", "retrieve": "retrieve"} ) graph.add_edge("generate", END) app = graph.compile()

上面这段代码是完整可跑的骨架。我在实际项目中会额外加入一个trace字段,把每一轮检索归因记录下来,调试的时候特别管用。如果发现某类问题老是走错工具,直接在plan_node的提示词里加上工具选择的约束说明就行。

4. 检索策略的组合棋局:路由、子查询、重写与多跳检索

模块搭好之后,真正决定Agentic RAG效果好坏的,是检索策略该怎么组合。我在前面两篇文章分别讲过向量检索和关键词检索的细节,这一节重点说它们的“组合策略”。

4.1 什么时候做路由选择

路由是整个系统里最优先的决策。模型先判断问题类型:事实型问题直接查结构化数据,语义型问题走向量检索,实时性问题走联网搜索,复合型问题则拆成多个子问题分别路由。路由判断要放在“拆解”之后、“检索”之前,因为一旦拆解完成,每个子任务的目标就变得清晰了。

我用过一个取巧的方式:在工具注册表里写清楚每个工具的擅长场景和限制,然后让LLM按描述选。不需要复杂的分类模型,提示词写清楚就行。

4.2 子查询和重写的边界

子查询适合“这个问题的答案分散在多个倒排片段里”。典型例子是“甲公司的竞争对手有哪些,以及他们各自的主营业务”。普通检索会一次性搜,结果往往只召回其中一个方面。Agent会把问题拆成“甲公司的竞争对手”和“这些对手的主营业务”两个查询,分别检索后再合并。

查询重写则适合“第一次检索没有命中但方向正确”的情况。比如用户问“智能音箱哪个牌子的音质好”,第一次向量检索出来的全是产品参数,这时候Agent可以把它改写成“蓝牙音箱音质评测对比”,再检索一轮。

子查询和重写的区别可以这样记:子查询是做“广度拆分”,重写是做“角度切换”。

4.3 多跳检索的深度控制

多跳检索是Agentic RAG逼格最高的能力,也是最容易翻车的部分。我见过一个案例:用户问“2023年获得融资最多的AI医疗公司的创始人是谁”,系统需要先查出融资排行榜,再查公司信息,再查创始人信息,一共三跳。每一跳都依赖上一跳的结果,只要中间任何一跳召回错误,最后答案就离大谱。

所以我对多跳检索的控制策略是:

  • 单条链路最多允许5跳,超过就强制终止并让模型基于已有信息作答,同时标注“部分证据缺失”。
  • 每一跳之后都要让Reflection节点给出置信度评分。如果某跳置信度低于60%,触发重写或更换工具,而不是硬着头皮往下一跳。
  • 中间结果里包含结构化数据时,优先做数值一致性校验。比如上一跳说“融资2.3亿”,下一跳说“融资230M”,虽然单位不同但数值要一致。

4.4 多源结果冲突怎么合并

这是Agentic RAG独有的麻烦。同一个问题在两个数据源里检索结果可能是矛盾的。普通RAG会直接把全部片段塞给LLM,让模型“自行判断”——这等于把风险完全交给了模型幻觉。

我采用的做法是“证据比分制”:每个检索片段都附上来源类型、时间戳和检索得分,在进入生成前让Reflection节点做一次交叉验证。如果两个片段冲突且无法通过时间戳区分新旧,就触发一轮针对冲突点的定向检索,专门查找能打破僵局的第三方信息。如果实在解决不了,在最终答案中明确标注“不同来源存在冲突”。

5. 质量保障:信号验证、自我反思与幻觉抑制

很多人对Agentic RAG的最大误解是:只要让模型自己规划、自己检索、自己反思,答案质量就自动上去了。实际恰恰相反——自由度过高,幻觉概率反而更大。一个Agent每多一次工具调用,就多一次引入错误信息的机会。质量保障模块的设计直接决定系统能不能上线。

5.1 检索相关度的硬性门槛

我通常在检索环节设置一道硬卡点:召回结果低于相关性阈值就直接丢弃重查,而不是带病进入生成。具体做法是让LLM对每条片段打分(1到5分),低于3分的片段不参与答案生成。这个操作成本很低,又能显著减少模型“硬编”低相关内容的概率。

生产环境里我会用纯规则做初筛:比如片段与查询的关键词重叠度低于某阈值就丢掉。LLM打分放在第二轮做,因为纯规则容易误杀语义匹配的优质片段,纯LLM打分又有延迟问题,两个结合最稳。

5.2 Reflection的提示词陷阱

我再强调一次反思模块的坑。初版系统我写的反思提示词是:“请检查以上答案是否有问题,如果有则输出修正建议。”结果模型每次都说“答案整体准确,但可以更详细”,然后输出一段跟原答案几乎一样的废话。

后来我把反思拆成了三个匹配项,效果立刻好转:

对比原始问题、检索证据和生成答案,逐项检查: 1. 答案中的每个事实主张是否有检索证据支撑?列出没有支撑的主张。 2. 答案中的每个数值是否与检索结果完全一致?列出不一致的数值。 3. 答案是否回答了原始问题的每一个子意图?列出遗漏的子意图。 如果以上三项都没有问题,输出"PASS";否则输出需要修正的具体内容。

关键点在于:反思不是让模型“觉得有没有问题”,而是让模型“用清单逐项核对”。前者依赖模型的主观判断,后者拆成了可验证的客观动作,结果完全不同。

5.3 生成阶段的受控与透明

Agent最后的生成阶段,我会做两个约束。第一个是“引用可见”:生成的每个事实性句子都要在文档索引里标注对应的检索片段ID,这样下游可以做归因审查。第二个是“底线声明”:如果Reflection节点的置信度不足但系统已经达到最大检索轮数,生成时必须显式告诉用户“以下信息部分缺失,仅供参考”,而不是打肿脸充胖子。

实现上,我是在generate的提示词里追加一段规则,并配合format_instructions让模型输出带引用标记的JSON结构。解析之后再做一次后处理,把没有对应片段ID的句子直接过滤掉。

6. 实测与调优:我踩过的坑和参数建议

理论和代码都过完了,最后分享一些真实项目里摸索出来的经验。这些内容花了我好几个月时间才沉淀下来,网上文档基本不会写。

6.1 最大检索轮数别贪多

我给多个团队做过咨询,最常见的问题是“我的Agent怎么老是检索不到就放弃了”。查日志发现最大轮数设的是6,但大多数情况下第2到3轮就能定位到正确方向,第4轮开始基本是在原问题附近打转。轮数放宽并不会提升召回质量,只会给模型更多“乱试”的机会。

我的建议是普通场景设3轮,复合型问题设5轮封顶。每轮的检索结果如果相比上一轮没有新增有效证据,就提前终止。判断“没有新增有效证据”的方法也简单:把前后两轮的片段做一次交叉去重,新增的片段数量低于2就视为收敛。

6.2 并发工具调用的提速效果

向量检索单独调一次大约200毫秒到1秒不等,如果Agent需要查三个不同的数据源,串行执行的延迟会叠到好几倍。多轮多跳以后,整个系统响应能拖到15秒以上,用户根本等不了。

我用的是OpenAI系列模型自带的并行函数调用,在LangGraph里就是同一个节点里同时触发多个Tool Node。实测下来,如果能把三个检索并行执行,整个多跳流程的响应时间能从12秒压到5秒左右。代价是上下文里会一次性挤进大量检索结果,需要做好去重和精排。

6.3 路由错误是最大的隐藏杀手

反思下来,我遇到过最头疼的问题不是检索不出来,而是该用SQL的时候用了向量检索,该查实时网页的时候查了内部文档。这类错误在单轮RAG里不可能出现,因为工具是写死的;但在Agentic架构里,模型判断失误的后果被放大了。

我的缓解方案是在工具描述里做“负例引导”:不只写工具能干什么,还写它“不适合干什么”。比如向量检索的描述里加了:“本工具不擅长精确数值查询、不擅长按字段过滤,例如‘2023年营收超过100亿的公司’不应使用本工具。”加了负例之后,路由准确率提升非常明显。

6.4 日志与追踪系统的必要性

Agentic RAG的排错难度比普通RAG高一个量级。普通RAG查一个问题只需要看一次检索日志,Agentic系统要检查每次规划、每次路由、每次反思结果。没有日志追踪,出了问题根本无从下手。

我现在跑的所有项目都会记录完整的“决策轨迹”:每一轮模型的thought是什么、选了哪个工具、查询语句原文是什么、召回片段得分是多少、反思结果是什么。LangSmith这类工具可以直接可视化整条轨迹,排查效率提升巨大。对于生产环境中动辄几千个请求的规模,轨迹数据同步存一份到数据库,将来做错误分析的时候会感谢自己。

7. 进阶扩展:从单Agent到多Agent协作和记忆持久化

基础版Agentic RAG跑通之后,事情还远没有结束。我最近在推进的两个方向,很可能也是大部分团队将来会遇到的需求。

7.1 多Agent协作的适用边界

单Agent处理复杂问题时会遇到一个难题:规划、检索、反思的所有上下文都挤在同一份记忆里,互相干扰。比如一边要查财务报表,一边要读产品文档,两个任务的指令和数据混在一起,模型容易“人格分裂”。

拆分多Agent的前提条件是任务具备清晰的边界。我设计过一个案例:一个Agent专门负责处理结构化数据查询,另一个Agent负责语义文档检索,第三个Agent做最终的汇总生成。每个Agent有自己的系统提示词和工具集,彼此之间通过消息传递交换结果。

这个方案的代价也很明显:延迟变高、系统复杂度翻倍、错误类型指数增加。我的建议是——先跑明白单Agent,确认问题边界足够稳定之后,再动多Agent。不要为了架构好看而上多Agent。

7.2 记忆持久化的两种路径

当Agent系统反复服务于同一群用户时,持久化记忆的价值就开始显现。用户可能昨天问过A产品的成本结构,今天问A产品的竞品分析——如果系统能记住前者的背景,今天的检索就会更有方向。

我实现过两种路径。第一种是“缓存式记忆”,把用户最近N轮的问题和答案摘要存入向量库,下次提问时先做一轮相似度匹配,如果有强相关的历史对话,就把摘要注入到当前上下文中。第二种是“沉淀式记忆”,对多轮对话做周期性的离线分析,提取稳定的用户偏好和领域规则,存成长期记忆。

两者的区别在于:缓存式解决“短期连续性”,沉淀式解决“长期一致性”。生产环境我通常两者都上,但会刻意控制记忆注入的token量,避免历史对话把当前检索的空间挤占掉。

7.3 评估体系要跟着升级

最后想提醒的是评估方式。普通RAG用RAGAS那套指标就够用了——忠实度、答案相关性、上下文相关性。但Agentic RAG的评估维度多得多:路由准确率、每跳检索命中率、反思触发的修正次数、多轮收敛时长、决策轨迹可解释性——这些指标需要单独搭一套评测集来持续跟踪。

我现在的做法是在评测集里给每个问题标注了“标准决策路径”,比如“应该先查SQL,再查文档,最后直接生成”。系统跑完之后对比实际路径和标准路径的差异,能迅速暴露模型策略偏移。这个评估思路比只看最终答案正确率要深刻得多——最终答案对了,不代表过程是对的高效的。

Agentic RAG说到底,是在用工程化的方式给RAG系统注入“推理能力”。它没有想象中那么玄学,也没有文档里写得那么简单,它的上限取决于你对决策节点的设计精度,而下限取决于你有没有做足够的质量兜底。我写这篇文章时特意把每个踩过的坑都摊开了,就是希望大家不要在一个地方重复栽跟头。机器可以不断试错,但生产系统不能。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进