对比标准RAG与Agentic RAG,阐述如何将检索决策从设计时移到运行时,解决复杂多跳问题,并提供planner、memory、MCP server的具体实现思路。
Standard RAG 的隐含假设
每个 RAG 演示都有一个隐含假设:用户的问题映射到一次向量搜索。一次查询进,一次 embedding,一次 top-k 查找,一个答案。
这个假设在演示中成立,因为演示问的都是演示级别的问题。"我们的育儿假政策是什么?"这对应一份文档。检索到,塞进 prompt,搞定。
然后你上线了,真正的用户输入的是:"我们在 Q2 批准的承运商费率变化,真的降低了东北地区的每票发货成本吗?如果排除波士顿仓库,这个结论还成立吗?"
这个问题需要一份政策文档、一张费率表、一个事务聚合结果,以及一次过滤后的重新计算。你的检索器会把整个句子做 embedding,在向量空间中找出与它最接近的三个 chunk,然后把那些话题相邻但事实无用的文本交给模型。模型作为一颗"好苗子",还是会硬着头皮回答。
问题不在 embedding 模型,也不在 chunk 大小。你在设计时——对于一个你还没看过的问题——硬编码了检索多少次、从哪里检索。
Agentic RAG 把这个决策移到了运行时。Planner、Memory、MCP 服务器、子 Agent:所有这些都只是挂在这个核心变化之上的实现细节。
架构 1:标准 RAG 是一条直线
STANDARD RAG — fixed pipeline, one pass
┌──────┐ 1. prompt+query ┌─────────────┐
│ User │ ───────────────────► │ Chat UI │
└──────┘ └──────┬──────┘
▲ │ 2. query
│ 6. response │
│ ┌──────▼──────┐
│ │ Retriever │
│ └──────┬──────┘
│ │ 3. fetch (top-k, one shot)
│ ▼
│ ┌───────────────────────────┐
│ │ Knowledge Sources │
│ │ docs · PDFs · code · DB │
│ │ APIs · web index │
│ └───────────┬───────────────┘
│ │ 4. chunks
│ ┌──────▼──────┐
└──────────────────────────│ LLM │
└─────────────┘
5. prompt + query + enhanced context
它的本质属性是:模型从不参与检索决策。它接收上下文、生成文本,而检索在它运行之前就已经完成了。
这是一种有真实优势的设计选择。一次 embedding 调用加一次向量查询成本低且可预测,延迟分布范围窄,缓存策略可以非常激进。失败是透明的:答案不好是因为 chunk 不好,你可以去读那些 chunk。你的 eval 框架是固定输入对应固定输出,所以可以工作。
当你的语料库同质化、用户大多只需要查询而非综合时,标准 RAG 是正确的架构。在这类工作负载上,不要让任何人说服你放弃它。
架构 2:agentic RAG 把检索变成一个决策
AGENTIC RAG — planner decides retrieval at runtime
┌──────┐ 1. prompt+query ┌─────────────┐
│ User │ ─────────────────► │ Chat UI │
└──────┘ └──────┬──────┘
▲ │ 2. query
│ 6. response ▼
│ ┌──────────────────────┐ ┌──────────────┐
│ │ Aggregator / │◄────►│ Planning │
│ │ Orchestrator Agent │ 3. │ ReAct · CoT │
│ └───────┬──────────────┘ └──────────────┘
│ │ ▲
│ │ └──────┐ ┌──────────────┐
│ │ 4. fan-out └─►│ Memory │
│ │ │ short · long │
│ ▼ └──────────────┘
│ ┌───────────────┼───────────────┐
│ ┌────▼────┐ ┌────▼────┐ ┌─────▼───┐
│ │ Agent 1 │ │ Agent 2 │ │ Agent 3 │
│ └────┬────┘ └────┬────┘ └────┬────┘
│ │ MCP servers / tool layer │
│ ┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ │ SQL DB │ │ Search │ │ Data │
│ │ │ │ index │ │ Explorer│
│ └─────────┘ └─────────┘ └─────────┘
│ │ 5. prompt + query + enhanced context
│ ┌──────▼──────┐
└─────────────────────│ LLM │
└─────────────┘
◄── loop back to (3) if context insufficient
有四个关键不同点。
1. 查询和检索器之间有一个 planner。 聚合 Agent 在任何内容被 embedding 之前先分解问题。ReAct 和思维链(chain-of-thought)是把一个用户句子转化为检索计划的机制。东北货运问题可以分解为四个步骤:获取 Q2 费率变更记录、按地区和日期聚合货运成本、在排除波士顿仓库的情况下重新聚合、对比。
2. 检索向异构后端发散。 标准 RAG 在 ingestion 时把所有内容归一化到一个向量索引。Agentic RAG 在查询时以系统原生语言查询:针对仓库跑 SQL、针对文档索引做语义搜索、针对事件数据做时序查询。你不需要 embedding 一张费率表,直接查询它就行了。
3. MCP 是集成的接缝。 当你的检索器是 MCP 服务器而非定制函数时,工具表面是声明式、可替换的。你通过注册一个服务器来添加数据源,而不是发一个新版 Agent。所有这些都只是挂在这个核心变化之上的实现细节。
4. Memory 携带检索状态跨轮次流转。 短期记忆保存本会话已获取的内容,这样第三轮就不需要重新检索第一轮已经找到的东西。长期记忆保存关于用户及其先前查询的持久事实。没有它,一个多跳系统每轮都会重新推导同样的上下文,而你要为此付两次钱。
结构上的差异在于循环回 planner。标准 RAG 只有一次检索过程。Agentic RAG 有一个带终止条件的循环,而这个循环就是价值与风险共同存在的地方。
运行时检索的代价
确定性这一行是在实践中代价最高的。标准 RAG 的失败是无聊的、可复现的。Agentic RAG 的失败是有趣的——对于生产系统来说这是一个糟糕的特性:同一个问题问两次可能走不同的路径、产生不同的答案,而两者都没错,只是来源不同。可复现性一直在你的故障响应流程中默默发挥作用,而当你把决策移到运行时时,你就放弃了它。
在增加更多跳之前,先检查你已有的上下文是否已经足够回答,并把上限设置在"不够"的情况上。这个检查对于生产级 agentic RAG 循环的作用,比一个更好的 planner 还大。
MAX_HOPS = 4
TOKEN_BUDGET = 20_000
def agentic_retrieve(query: str, tools: dict) -> RetrievalResult:
plan = planner.decompose(query) # ReAct-style sub-questions
ctx, trace, spent = [], [], 0
for hop in range(MAX_HOPS):
gap = planner.next_gap(query, ctx) # what's still missing?
if gap is None:
break # sufficiency gate: stop early
tool = router.select(gap, tools) # sql | search | timeseries
chunks = tool.invoke(gap)
spent += count_tokens(chunks)
if spent > TOKEN_BUDGET:
trace.append(("halt", "budget_exceeded", hop))
break
ctx.extend(chunks)
trace.append((tool.name, gap, len(chunks)))
return RetrievalResult(
context=dedupe(ctx),
trace=trace, # emit this. always.
exhausted=len(trace) >= MAX_HOPS, # flag for review queue
)
next_gap 返回 None 是充分性门控。它让单跳问题只花一个跳的成本,而不是四个。没有它,你的 agentic 系统会在大多数流量——查找问题——上支付多跳的价格,而这最终会体现在账单上。
MAX_HOPS 和 TOKEN_BUDGET 不是可选项。一个针对在线 SQL 工具的无界检索循环是一种指向你自己仓库的拒绝服务攻击,而一条措辞不当的用户问题就足以触发它。
trace 是让系统可调试的关键。每个请求都 emit 它,把答案和 trace 存在一起,并使其可查询。当 agentic RAG 失败时,答案本身不会告诉你原因。
团队在这个问题上常犯的错误
最常见的错误是为单跳语料库采用 agentic RAG。如果你的知识库是几千篇支持文章,用户问的都是文章形态的问题,那么 planner 只是在增加延迟和成本,最终达到的标准 RAG 本来一次就能找到的那份文档。根据问题形态来路由,而不是根据架构时尚来路由。
跳过 router 也是常见问题。一个被交给 11 个工具且没有路由启发式的 planner 会把 11 个都探索一遍。在 planner 看到问题之前,先按查询类别约束工具集。
然后是把两种架构当作非此即彼的关系。实际上在两者之前放一个分类器通常效果更好:对于查找问题走廉价的单跳路径,对于需要分解的问题走 agentic 路径。如果你的流量大部分是查找,那大部分流量就不应该为 planner 付费。
评估是那个代价昂贵的错误。在标准 RAG 中,答案质量是系统健康状况的良好代理。在 agentic RAG 中不是,因为一个通过六次浪费性跳到达的正确答案,是一个等待你的流量增长后爆发的成本和延迟问题。追踪每查询跳数、工具选择精度、冗余检索率,以及充分性门控触发的频率。
最后,团队忘记了真正能一锤定音的参数不是多跳推理。Embedding 是关系schema的有损表示。如果你的答案需要连接、聚合或过滤,没有任何分块策略能救你,你需要的是一个说 SQL 的工具。
两种架构之间的差异是控制流,所有其他东西都由此而来。标准 RAG 在设计时决定如何检索;agentic RAG 把这个决策推迟到运行时。
标准 RAG 不是遗留模式。在同质语料库上处理单跳问题时,它更便宜、更快、更可复现、更易于评估。
充分性门控是保持成本模型可行的关键,因为它在最常见的问题——构成大部分流量的问题——上提前停止循环。
永远要给循环设上界。MAX_HOPS 和 token 预算给你一个你能说出来的最坏情况。
结构化数据是触发切换的最清晰信号。Embedding 扁平化了 schema,所以连接和聚合需要一个直接查询数据库的工具。
不仅要评估答案,还要评估轨迹。一个通过错误路径得到的正确答案是一个潜伏的生产问题。