主题
Agentic RAG:从「一次检索」到「自主检索」——多跳、改写与检索循环
提出问题
用 Naive RAG 给客服系统做知识问答,上线后遇到一个常见场景:用户问「A 产品过期了能退吗?B 产品呢?」,系统分别检索了 A 和 B 的退货政策,回答得挺好。但换一个问题:「我的订单 Z12345 用了优惠券,退货后钱怎么退?」——检索器只返回了通用退货政策,完全没提到优惠券抵扣的退款规则,因为这两个信息不在同一个文档里。
这就是一次检索的天花板:查一次,拿到什么就是什么,不会追问、不会迭代、不会发现信息不够了再补查一次。
Naive RAG 是单发(one-shot),用户问什么就查什么,碰到跨文档、多条件、需要推理链条的问题,召回的信息要么不完整,要么不相关。Agentic RAG 把检索从「被动执行」变成「主动决策」:LLM 自主判断信息够不够,不够就改 query 再查,查到够为止。听起来像 Agent 的思路——没错,RAG 发展到第三阶段,就和 Agent 合并了。
从 Naive 到 Agentic:RAG 的三代进化
第一代:Naive RAG
用户输入 → 向量检索 → 拼提示词 → 生成。Transformer 那个出名的经典架构,不展开了。问题就一个:一次检索,信息不足就是不足,不补救。
第二代:Modular RAG
Naive 的每个环节都可以替换和增强:Query 改写(HyDE、多查询)、Rerank 重排、检索前/后处理。这比一次检索强,但本质上还是管线式的——输入走完一套固定流程输出,LLM 不在循环里,只是最后一步的生成器。
第三代:Agentic RAG
LLM 变成循环的决策者。它决定:
- 当前信息够不够回答?
- 不够的话,是改 query 再查,还是换个数据源查?
- 需要拆成多个子问题分步查吗?
- 查到的东西矛盾了怎么办?
用户输入
│
▼
┌─────────────────────────────────────┐
│ Agent 分析当前信息 │
│ 判断:信息是否足够回答? │
│ - 如果足够 → 直接输出 │
│ - 如果不足 → 进入检索循环 │
└──────────┬──────────────────────────┘
│ 不足
▼
┌─────────────────────────────────────┐
│ 1. 改写 Query(HyDE/子问题拆分) │
│ 2. 检索 top-k │
│ 3. 重排 + 信息充分度评分 │
│ 4. 结果回填到上下文 │
└──────────┬──────────────────────────┘
│ 循环
▼
回到「信息足够?」判断
│
│ 足够
▼
输出最终回答这个循环就是 Agentic RAG 和前面两代的核心区别:Agent 在循环里,每一步都在决定下一步做什么。
核心机制拆解
查询规划:把复杂问题拆成子问题
复杂问题很少是一次检索能解决的。比如「对比 MySQL InnoDB 和 PostgreSQL 的 MVCC 实现差异」——你不能指望一次检索同时拿到两个数据库的 MVCC 资料。Agentic RAG 的做法是:把大问题拆成多个子问题,分别检索,再合并。
原始问题:"对比 MySQL InnoDB 和 PostgreSQL 的 MVCC 实现差异"
↓
Agent 拆解为:
- 子问题 1: "MySQL InnoDB MVCC 实现原理"
- 子问题 2: "PostgreSQL MVCC 实现原理"
- 子问题 3: "InnoDB vs PostgreSQL MVCC 对比"
↓
分别检索 → 结果合并 → 生成对比回答拆解方式不需要复杂算法,LLM 自己就能做。给一条 prompt 让它「把复杂问题拆成 2-3 个独立可检索的子问题」,效果就够用。拆出来的子问题可以并行检索(减少延迟),也可以串行检索(后一个子问题依赖前一个结果)。
迭代检索与信息充分度判断
这是 Agentic RAG 最核心的工程问题:什么时候该停?
一个简单的方案是设一个「信息充分度」评分器。每次检索拿到结果后,LLM 对结果打分(0-10 分),低于阈值就继续检索。但现实情况是,LLM 打分不稳定,容易觉得「信息够了」然后编造答案。
更可靠的方案是显式终止条件:
- 最大轮次硬限制:最多检索 3 次,3 次后不管够不够都输出。这是兜底,防止死循环。
- 信息增益阈值:这一轮检索到的内容,和上一轮相比,有没有新的关键信息?如果连续两轮没有新增信息,说明检索已经饱和,停。
- 直接回答约束:如果模型判断「当前信息足以回答用户问题」,就停止检索。这个判断可以加一个「force」——要求模型必须引用至少 2 个检索段落来支撑回答,减少幻觉。
多跳推理:跨文档实体串联
多跳(Multi-hop)是 Agentic RAG 的另一个关键能力。用户问的问题需要串联多个文档的信息才能回答,比如:
- "《流浪地球》的导演还拍过哪些电影?"——先查导演是谁,再查该导演的作品列表
- "A 产品的竞争对手 B 公司最近有什么融资消息?"——先查 B 公司是谁,再查融资新闻
多跳推理天然需要 Agent 循环:第一跳结果作为第二跳的输入参数。这也是为什么多跳 QA 是测试 Agentic RAG 的基础 benchmark(HotpotQA 数据集就是干这个的)。
检索作为 Agent 的工具
把检索当成一个普通的 tool,和计算器、SQL 查询、API 调用并列——这是 Agentic RAG 的另一种实现方式。LLM 看到一个问题,不是直接触发检索,而是先思考:需要用哪个工具?
python
class RetrievalTool:
"""把检索包装成一个 Agent 可调用的工具"""
def __init__(self, embedding_model, vector_store, top_k=5):
self.embedding_model = embedding_model
self.vector_store = vector_store
self.top_k = top_k
def call(self, query: str) -> list[dict]:
"""根据 query 检索知识库,返回 top_k 段落"""
query_embedding = self.embedding_model.embed(query)
results = self.vector_store.search(query_embedding, top_k=self.top_k)
return [{"content": r["text"], "score": r["score"]} for r in results]
class AgenticRAG:
"""Agent 循环:检索 + 判断 + 改写 + 再检索"""
def __init__(self, llm, retriever, max_rounds=3):
self.llm = llm
self.retriever = retriever
self.max_rounds = max_rounds
self.conversation_history = []
def _is_sufficient(self, question: str, context: list[str]) -> bool:
"""判断当前上下文是否足够回答问题"""
prompt = f"""Question: {question}
Retrieved Context:
{chr(10).join(context)}
Based on the retrieved context, can you fully answer the question?
- If yes, answer "YES" followed by your answer.
- If no, answer "NO" followed by what information is still missing."""
response = self.llm.generate(prompt)
return response.startswith("YES"), response
def _rewrite_query(self, question: str, context: list[str], missing_info: str) -> str:
"""根据缺失信息改写 query"""
prompt = f"""Original question: {question}
Previous context already retrieved:
{chr(10).join(context)}
Still missing: {missing_info}
Generate a new search query that specifically targets the missing information.
Return only the query text, no explanation."""
return self.llm.generate(prompt).strip()
def answer(self, question: str) -> str:
context = []
for round_num in range(1, self.max_rounds + 1):
# 1. 检索
current_query = question if round_num == 1 else self._rewrite_query(
question, context, missing_info
)
results = self.retriever.call(current_query)
new_context = [r["content"] for r in results]
context.extend(new_context)
# 2. 判断信息是否足够
sufficient, response = self._is_sufficient(question, context)
if sufficient:
return response[4:].strip() # 去掉 "YES" 前缀
# 3. 解析缺失信息
missing_info = response[3:].strip() # 去掉 "NO" 前缀
# 最大轮次:用所有上下文强答
final_prompt = f"""Question: {question}
All retrieved context:
{chr(10).join(context)}
Answer the question using only the context above. If the context is insufficient, state what is missing."""
return self.llm.generate(final_prompt)这段代码可以跑通,但生产环境需要加几个东西:上下文去重(多轮检索可能返回重复段落)、工具调用超时(检索超时了直接降级不用等)、多轮 query 的 token 预算控制(检索结果的上下文不能无限膨胀)。
适用边界:不是所有场景都需要 Agentic RAG
Agentic RAG 的代价很明确:延迟和 token 消耗成倍增加。一次检索变三次,延迟从 1s 涨到 3-5s,token 翻 3-5 倍。不是所有场景都值得。
- 事实性问答("公司午餐补贴多少")→ Naive RAG 就够了,多查两次纯属浪费
- 多条件查询("我的订单 A 用了优惠券,退货如何退款")→ Agentic RAG 有用,需要跨文档串联
- 复杂调研("新能源汽车行业 2026 年主要玩家技术路线对比")→ Agentic RAG 是刚需,需要多次检索、多跳推理
- 客服工单分诊("用户说无法登录,可能是什么原因")→ 先查常见原因列表,再查每个原因的排查步骤,Agentic RAG 是天然适合的场景
选型判断很简单:如果一个人工客服需要查 2 次以上资料才能回答的问题,就用 Agentic RAG;如果一次就能回答,别用。
参考
- Shao et al., "Agentic RAG: A Survey", 2025
- Lewis et al., "Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks", NeurIPS 2020
- Trivedi et al., "Interleaving Retrieval with Chain-of-Thought for Multi-Hop Reasoning", 2022
- Khattab et al., "Demonstrate-Search-Predict: Composing Retrieval and Language Models for Knowledge-Intensive NLP", 2023