☰
Agentic RAG架构与实践:让大模型自主决策的检索增强生成
2026/10/26 5:34:10 网站建设 项目流程

1. 为什么需要Agentic RAG:从“管道”到“代理”的思维转变

先说结论:Agentic RAG说白了,就是让大模型不再被动地“等检索结果喂到嘴边”,而是让它自己决定什么时候查、查什么、查几次、怎么判断结果够不够用。这个“-3”如果是系列第三篇,那前面大概率已经聊过基础RAG怎么搭、向量库怎么选,这一篇我会把重心放在“代理化”这件事本身——它到底改了什么、为什么非改不可、以及落地时要踩哪些坑。

1.1 传统RAG的三个老大难问题

传统RAG的标准流程长这样:用户提问 → 向量检索Top-K → 拼Prompt → LLM生成回答。这套流程在演示场景里跑得挺漂亮,但一上生产环境就露馅。我归纳下来主要是三个死穴。

第一个死穴:检索是“一次性的、不可纠错的”。向量检索的本质是语义相似度匹配,它不理解用户的真实意图是一个复合查询还是多跳问题。比如用户问“A公司去年收购的那家做芯片的公司,现在主打产品是什么”,你要是老老实实做一次检索,大概率只能召回“收购”相关的那几段,后面的“主打产品”根本没人去查。传统RAG没有“先查一步、再决定下一步”的能力。

第二个死穴:查询路由是写死的。传统方案里,你通常要手工配置“什么类型的问题走向量检索、什么问题走SQL查询、什么问题直接让LLM回答”。这套规则表一开始还能用,等知识库一复杂、问题一多样,你会发现自己陷入规则覆盖的死循环——每加一种问法就要加一条规则,规则之间还会打架。

第三个死穴:上下文利用率极低。一次只取top_k条,取多了噪声大,取少了覆盖不全。而且无论来的文档里有没有真正有用的信息,LLM都只能拿这些内容硬答,答错了还得背锅。说白了,传统RAG是个“一次性餐具”模型——用完就扔,没有任何反馈回路。

这其实是过去两年很多人做RAG做到最后都有的共同困惑:RAG的上限,根本不在检索算法本身,而在“检索决策”这个环节被写死了。

1.2 Agentic RAG到底“代理”在哪

Agentic RAG的核心变化只有一个:把检索链路从一个DAG(有向无环图)变成一个有状态的循环。代理(Agent)拿着用户问题,拥有一系列“工具”(向量检索、Web搜索、数据库查询、代码执行器),它自己做规划(Plan)、调用(Act)、观察结果(Observe),不满意就再来一轮。

这里面最关键的设计哲学是**“迭代”**。每次检索到的内容不是直接拼进Prompt完事,而是先经过一个“够不够用”的判断——如果信息不足,代理可以换一个查询词再搜,可以拆成子问题分头查,也可以直接坦白告诉用户“这个问题现有资料答不了”。这一圈下来,传统RAG的三大死穴全部被绕开了。

所以我一直跟团队里人说:Agentic RAG不是某种具体的技术框架,而是一种设计范式。你用什么工具实现都行——LangGraph、AutoGen、甚至自己手写一个while循环——只要满足“多轮决策、需要工具就调用、对结果有评估”这三个特征,它就是Agentic RAG。理解这一点非常关键,否则很容易被各种名词带偏。

2. 整体架构拆解:检索不再是“一次调用”

我把一个典型Agentic RAG的架构拆成四个模块:查询理解与规划器(Planner)、工具执行层(Tools)、观察与记忆层(Observer)、答案合成器(Synthesizer)。看起来名字有点唬人,但每个角色的职责其实很清晰,我一个个说。

2.1 规划器:整条链路的“大脑”

规划器的本质是一个LLM调用,但它的输出不是最终答案,而是一个“行动计划”。它可以是一段ReAct格式的思考链(Thought/Action/Action Input),也可以是一个结构化JSON(比如{"tool": "vector_search", "query": "...", "filter": "..."})。

实战中我建议优先用结构化JSON而不是纯文本思考链,原因很简单:结构化输出好解析、好校验、好兜底。纯文本解析只要模型说一句废话,你的代码就得写一堆正则去捞字段。用JSON模式(OpenAI的response_format、或者各种框架的structured output)可以直接把输出锁死在Schema里。

规划器最常见的决策分支有这么几类:

  • 直接回答:用户问常识问题、或者问题与知识库无关,直接答。
  • 单轮检索:问题明确、只需要查一次就能答。
  • 多路并行检索:问题可以拆成多个独立子问题,并行查更快。
  • 多跳迭代检索:第一轮查完发现信息不够,需要带着第一轮的结果再查下一轮。
  • 调用外部API:查数据库、查天气、查实时行情等。

注意,规划器并不是越复杂越好。我见过有人给Agent配了八个工具,结果模型整天在工具选择上抽风。原则是:在能满足需求的前提下,工具数量越少越好,决策路径越短越好。

2.2 工具执行层与观察记忆层

工具执行层相对简单,就是把规划器选定的工具真正跑起来。但这里有个很容易踩的坑:工具返回的结果需要做“预处理”再交给模型。不要直接把一整篇PDF文本塞进上下文——先做章节抽取、关键词高亮、长度裁剪,只保留最相关的一两段。这一步叫“面向Agent的检索结果压缩”,比起一步到位的RAG,它能省掉你大量反复检索的次数。

观察与记忆层则负责把工具返回的内容“加工”成下一轮决策的依据。我常用的是个简单粗暴的方案:给返回文档打分(re-ranking的分数、关键词命中数都行),低于阈值的直接过滤掉;然后记录“已经查过哪些查询词”和“已经看过哪些文档ID”,防止Agent在循环里原地转圈或者反复查同一批文档。这个防重机制我当时没做,结果Agent经常连续三四轮返回一模一样的结果,纯烧token。

2.3 两种典型工作流模式对比

我自己实际用下来的两种主流模式:

模式A:Plan-then-Execute(先规划再执行)代理先一次性把需要查的所有子问题列出来,然后并行执行检索,最后汇总所有结果统一生成答案。优势是快——并行检索比串行省一半时间;劣势是规划错了后面没法补救,子问题列歪了答案就跑偏。适合问题边界清晰、知识库结构明确的场景。

模式B:ReAct式动态决策(边做边看)代理每走一步都停下来评估当前信息够不够,不够就继续查。优势是容错性好、能处理非常复杂的多跳问题;劣势是慢、token消耗大、还有可能陷入死循环。适合问题复杂、没法一上来就拆清楚的场景。

我做项目的时候一般先用模式A做基线版本,跑通后再针对“失败case”逐个升级成模式B。别一上来就搞全动态决策,否则你连问题都定位不清楚。

3. 实操落地:从0到1构建一个Agentic RAG

光讲原理不练等于白说。这一节我把自己最近一个项目的完整搭建过程梳理一遍。技术栈用的是LangGraph + Qdrant + OpenAI GPT-4o,整个系统的核心逻辑100多行就能说明白。

3.1 工具选型:为什么选LangGraph而不是自己写循环

很多人觉得Agent逻辑不复杂,自己手写个while循环调工具不就行了?我早期也这么干过,但做到后面发现两个硬伤:一是代码复杂度失控——每加一种“判断分支”就要在循环体里塞一个if-else,三个月之后自己都看不懂;二是可观测性太差——出了问题想回放Agent的决策过程,得自己打一堆日志,还不一定全。

LangGraph这种编排框架解决的就是这两件事:它把Agent的每一步决策都建模成图上的节点和边,状态流是显式的,每一步的输入输出都可以随意挂载记录。另外它自带一个很大的好处——checkpoint机制,可以只对某一步做重放和调试。这些能力自研代价不低。

当然,LangGraph不是唯一选择。LlamaIndex的Agent体系也很成熟,微软的AutoGen在多Agent协作上更强。我的建议是:单Agent解决任务的场景用LangGraph最顺手,多Agent协作或者拟人交互场景再考虑AutoGen。工具没有绝对好坏,顺手最重要。

3.2 核心代码实现:LangGraph版Agentic RAG

我直接把核心代码骨架贴出来,注释已经写清楚每一步在干什么。

from typing import TypedDict, List from langgraph.graph import StateGraph, END from langchain_openai import ChatOpenAI from langchain_qdrant import QdrantVectorStore from qdrant_client import QdrantClient import json # 1. 定义Agent运行状态 class AgentState(TypedDict): question: str plan: List[str] # 子问题列表 documents: List[str] # 已检索到的文档内容 tool_results: List[str] # 各子问题的检索结果 answer: str iteration: int # 用于限制最大循环次数 visited_queries: List[str] # 防重:已查过的查询词 # 2. 初始化模型与向量库 llm = ChatOpenAI(model="gpt-4o", temperature=0) client = QdrantClient(host="localhost", port=6333) vector_store = QdrantVectorStore(client=client, collection_name="knowledge_base") # 3. 规划节点:决定是否需要检索、检索哪些子问题 def planner_node(state: AgentState) -> AgentState: if state["iteration"] == 0: prompt = f"""你是检索规划器。判断以下问题是否需要查知识库。 如果不需要,直接返回 {{"plan": []}}。 如果需要,拆解为最多3个子问题,返回 {{"plan": ["子问题1", ...]}}。 问题:{state["question"]} """ response = llm.invoke(prompt, response_format={"type": "json_object"}) plan = json.loads(response.content)["plan"] state["plan"] = plan state["iteration"] += 1 return state # 4. 检索执行节点:按规划并行检索 def retrieve_node(state: AgentState) -> AgentState: new_docs = [] for q in state["plan"]: # 防重检查:跳过已经查过的意图相近的查询 if q in state["visited_queries"]: continue docs = vector_store.similarity_search_with_score(q, k=3) docs = [d for d, s in docs if s > 0.7] # 相关性阈值过滤 new_docs.extend([d.page_content for d in docs]) state["visited_queries"].append(q) state["documents"] = list(set(new_docs)) return state # 5. 评估节点:信息够不够?够就生成答案,不够就返回重新规划 def evaluate_node(state: AgentState) -> AgentState: if not state["documents"] and state["iteration"] < 3: # 没有拿到有用文档,重新规划(换个查询角度) state["iteration"] += 1 state["plan"] = ["尝试更宽泛的关键词搜索"] return state return state # 6. 生成节点:用检索到的文档合成最终答案 def generate_node(state: AgentState) -> AgentState: combined_context = "\n\n---\n\n".join(state["documents"]) prompt = f"""仅基于以下资料回答问题。如果资料不足以回答,直接说“资料不足”。 资料:{combined_context} 问题:{state["question"]} """ state["answer"] = llm.invoke(prompt).content return state # 组装图 graph = StateGraph(AgentState) graph.add_node("planner", planner_node) graph.add_node("retrieve", retrieve_node) graph.add_node("evaluate", evaluate_node) graph.add_node("generate", generate_node) graph.set_entry_point("planner") graph.add_edge("planner", "retrieve") graph.add_edge("retrieve", "evaluate") graph.add_conditional_edges( "evaluate", lambda s: "generate" if (s["documents"] or s["iteration"] >= 3) else "planner" ) graph.add_edge("generate", END) app = graph.compile()

这段代码看起来不复杂,但它已经具备了一个Agentic RAG的完整闭环:规划 → 检索 → 评估 → 决定继续还是结束。生产环境里你要再往上加的东西无非是这几个方向:更复杂的路由条件(比如要不要走Web搜索)、更精细的记忆(比如跨会话记住用户偏好)、以及更丰富的工具(比如SQL查询、代码解释器)。

3.3 提示词设计要点与参数调优

我踩过很多次提示词的坑,这里把我认为最有价值的几条经验写出来。

首先,规划器的提示词里要明确“什么情况不该检索”。不写这条,Agent会把“今天天气怎么样”“1+1等于几”这种问题也拿去查向量库,白白浪费时间。我通常在规划器提示词里加一句:如果问题不依赖特定领域知识或属于常识,返回空计划直接回答。就这么一句话,能省下大约30%的不必要检索。

其次,检索评估的提示词不能太含糊。不要写“请判断以下资料是否充分”,要写得更具操作性:基于这些资料是否能够回答用户问题中的每个子要点?如果有任何一个要点找不到出处,请回答"INSUFFICIENT"并说明缺失哪个要点。让模型输出一个明确的标记词,比你让它自由发挥好操控得多。

参数调优方面,有两个参数我会单独说一下。一个是检索Top-K,Agentic RAG里K不需要太大,我一般设3,因为反正可以多轮检索,单轮质量比数量重要。另一个是相关性阈值,这个一定要根据你知识库的真实分布去调,不要用什么“凭经验设0.8”——跑一批真实查询,看分数分布,把阈值设在“明显不相关”和“勉强相关”的边界上,这才是合理做法。

3.4 一个完整案例的对话过程回放

为了让你看得更直观,我放一个真实跑过的例子。用户问题是:“我们Q3的营收增速为什么放缓?主要受哪个产品线影响?”

Agent内部决策过程如下(这些都能通过LangGraph的追踪面板看到):

  1. 规划器判断这需要检索,拆解出三个子问题:“Q3营收数据”、“各产品线增速对比”、“增速变化的原因解释”。
  2. 三个子问题并行检索,返回一堆财报片段。
  3. 评估器发现“原因解释”相关片段太少,只提到“某产品线增长乏力”,但没有细节数据支撑。
  4. Agent换了个查询词:“Q3 某产品线销量 同比变动”,重新检索,拿到了具体销量数据。
  5. 第二次评估通过,生成器给出了有数据支撑的回答:“Q3营收增速放缓主因某产品线销量同比下降12%,贡献了整体增速放缓的约七成”。

这个流程里最关键的第4步,传统RAG永远做不到——因为传统RAG没有“发现信息不足”的能力。这就是Agentic RAG在复杂问题上的价值所在。

4. 常见问题与排查技巧实录

以下每一个问题都是我在真实项目中踩过的,不是从文档里抄的。按出现频率从高到低排。

4.1 查询路由判错:把简单问题当成复杂问题处理

症状:用户问“退款政策是什么”,Agent不去向量库检索退款相关的单篇文档,反而拆了三个子问题去检索,最后生成的答案冗长且跑偏。

根因:规划器的拆解逻辑过于激进。很多实现里提示词写的是“将问题拆解为多个子问题”,模型就会机械执行,哪怕这个问题一句话就能查完。

解决方案:规划提示词里要强调拆解的条件。我最后改成的版本是:

  • 用户问题涉及多个不同实体或主题 → 允许拆解
  • 用户问题只有一个主题且意图明确 → 不拆解,直接用原问题检索

改完以后,意图明确的问题路由正确率从82%提升到了94%。这个提升幅度是实打实的。

4.2 多跳检索失效:第二跳查询词质量太差

症状:第一轮检索拿到了部分信息,但Agent生成的第二轮查询词质量极差,甚至跑偏到其他主题上去。比如用户问“A公司的竞争对手是谁”,第一轮查到“A公司创始人曾在B公司任职”,第二轮Agent居然去查“B公司创始人”,完全跑偏。

根因:很多实现里,第二轮的查询词生成是用了一个独立LLM调用,prompt里没有把“原始用户问题”作为硬性锚点。模型基于中间结果自由发挥,自然容易跑偏。

解决方案:在生成后续查询词的prompt里,把用户原文问题放在最前,并明确要求“输出必须在语义上服务于原始问题”。加上这个硬约束后,跑偏率显著下降。你可以用这一条模板替换你的第二跳查询生成逻辑,亲测有效。

4.3 LLM幻觉:Agent把检索到的信息“加工”过头了

症状:检索结果里只提到某产品“在华东地区销量领先”,Agent生成答案时却说“该产品全国销量领先”。这种问题其实比完全没检索到还危险,因为它看起来有出处。

根因:生成阶段的prompt约束不够严。很多人的prompt写的是“基于以下资料回答问题”,这句话对模型的约束力非常弱。

解决方案:我在生成节点改用这句话:只允许复述资料中出现过的信息,禁止进行任何推测、泛化或跨区域推广。如果资料与问题不完全匹配,明确指出不匹配之处。另外在知识库文档层面,给段落加metadata(来源、时间、地区范围),生成时把metadata一并传给模型,会让它更“老实”。

4.4 成本与延迟失控:动辄10层循环,烧钱又慢

症状:Agent在复杂问题上循环太多轮,单次对话消耗几万token,响应时间超过30秒。这个我见得最多,也是很多人最后放弃Agentic RAG的原因——不是不聪明,是用不起。

解决方案:上硬性限制。最大循环次数设成3-4轮就够了,多了边际收益极低。另外在评估环节,如果连续两轮检索到的文档相似度超过某个阈值(比如都没超过0.6),直接终止,让生成器用已有信息尽力回答。还有一个立竿见影的办法:把“是否继续检索”的判断模型换成小型便宜模型,比如GPT-4o做规划,让一个快mini模型做信息充分性判断,成本能降一半,效果几乎不降。

5. 最后的实战心得与扩展方向

如果上面这些你都认真读完,我最后再分享几个自己总结的判断标准。

第一,Agentic RAG不是银弹。如果你的知识库问题80%以上都是“单文档单主题”的类型,传统RAG够用了,别引入不必要的复杂度。Agentic RAG适合的是“问题跨多个文档、需要多轮逻辑推理”的场景。我在每个项目开始前都会做一轮问题样本分析,数一下有多少问题需要多跳推理。比例低于两成我就会劝业务方先别做Agent化。

第二,一定要把“不回答”作为Agent的合法选项。Agentic RAG最容易被忽视的价值就是“有勇气说不知道”。我在生产系统里专门加了一个分支:当检索评估连续两次不通过时,Agent直接回答“现有知识库无法完整回答该问题,建议补充以下资料:xxx”,并给出缺失的方向。用户对这样的回答接受度反而很高,因为它诚实且提供了下一步路径。

第三,考虑从“单Agent”走向“多Agent”。这是我正在做的扩展方向:一个查询理解Agent负责拆解问题,一个检索质量监督Agent负责验证检索结果是否对齐用户意图,一个回答生成Agent专注于写作。多Agent的代价是系统复杂度更高,但换来的是每个环节的职责更单一、更好调优。如果你遇到了“单Agent干什么都差一口气”的瓶颈,可以尝试往这个方向走。

还有一个小技巧,最近实践出来的:把Agent的中间决策过程折叠进最终答案的引用里。比如最终回答里可以这样说“根据A产品的财报数据(经与产品线负责人核对)……”,这样用户能感知到Agent是认真做过分析的,而不是一次性回答。当然,这个需要你在元数据设计上下功夫,把每一步检索的来源信息都结构化记录下来。

Agentic RAG这个方向我做了小半年,最大的感受是:它把RAG从“搜索工具”升级成了“研究助手”。搜索工具给你一堆链接,研究助手给你一个结论。这个转化过程中的每一步都可以被设计、被调试、被优化——这也是为什么我愿意花这么多篇幅去写这些细节。希望这篇文章对你有用,也欢迎在评论区交流你的落地经验。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询