Agentic RAG 深度解析:让 Agent 自己决定要不要检索、检索几次,这才是 RAG 的正确打开方式

Agentic RAG 深度解析:让 Agent 自己决定要不要检索、检索几次,这才是 RAG 的正确打开方式

你有没有遇到过这种情况:搭了一套标准 RAG,上线后发现检索结果驴唇不对马嘴------用户问「2024 和 2025 的年度报告对比一下」,系统只检索到了 2024 的内容,然后大模型用这半桶水给了你一个「信心满满但完全错误」的答案。你反复调 top-K、调 chunk size,就是不稳。根本原因不是参数没调对,而是传统 RAG 的架构本身就没有自我纠错的能力------它就是个固定管道,检一次,生成,完事。

Agentic RAG 的核心思路是:把检索这件事交给 Agent 来决策。要不要检索?检哪个数据源?检完发现不够怎么办?这些都由 Agent 在运行时自主判断,而不是你在代码里硬编码。

这篇从原理到实战,把四种 Agentic RAG 模式拆透。


01 为什么说传统 RAG 是「一次性赌注」

传统 RAG 的执行路径是固定的:用户提问 → Embedding → 向量检索(top-K) → 塞进 Prompt → 生成答案。这个流程在生产环境中有三个死穴:

死穴一:单次检索,没有回头路。 第一次检索结果质量差,没有任何机制能发现这一点并重试。系统会继续往下走,用一堆不相关的 chunk 生成一个「看起来很正经」的错误答案。

死穴二:单一数据源,知识孤岛。 用户的问题往往跨越多个数据域------文档库、数据库、实时 API。传统 RAG 只能连一个向量库,碰到跨域问题直接哑火。

死穴三:无法分解复杂问题。 「对比 Q3 和 Q4 的用户留存率,结合客服反馈分析原因」------这个问题需要至少三次独立检索再汇总。传统管道里,你只能硬塞进一个 query,然后祈祷。

根据多个生产系统的统计,15%-30% 的 RAG 失败都源于检索质量问题------而这些失败,在传统架构里根本无从发现,更无法修复。

Agentic RAG 的解法很直接:在检索和生成之间,加一层「会思考的 Agent」。

能力维度 传统 RAG Agentic RAG
检索策略 固定(单次向量搜索) 动态(Agent 选数据源、改查询)
检索轮次 永远是 1 次 按需决定(1 到 N 次)
质量评估 Agent 给检索结果打分
错误恢复 检测到失败后换策略重试
工具调用 可调用 API、SQL、网页搜索
查询分解 把复杂问题拆成子问题

02 四种模式:Agentic RAG 的设计图谱

Agentic RAG 不是一种固定架构,而是四种可组合的模式。理解这四种模式,是选型的基础。

模式一:路由型 RAG(Routing RAG) 适用场景:知识分散在多个后端------向量库、SQL 数据库、知识图谱、实时 API。核心逻辑是 Agent 先理解问题意图,再决定去哪里取数据。用户问「Q3 营收」走 SQL,问「产品规格」走向量库,问「最新股价」走实时 API。

模式二:多步型 RAG(Multi-step RAG) 适用场景:单个 Query 需要多轮独立检索才能回答。Agent 把复杂问题拆解成子问题,依次检索,最后汇总。「新定价方案上线后流失率怎么变化,客服反馈如何」拆成三个独立子查询,分别检索,最后合并推理。

模式三:纠错型 RAG(Corrective RAG / CRAG) 适用场景:需要对检索结果进行可信度评估。检索 → 评分(相关吗?)→ 相关就生成,不相关就改写 Query 重试,完全没有就降级到网页搜索。这是实际项目里最实用的模式。

模式四:自适应型 RAG(Adaptive RAG) 适用场景:需要在「要不要检索」这一步就做判断。Agent 先判断:这个问题是常识、上下文已覆盖,还是真的需要检索?不需要就直接生成,避免无意义的检索开销。


03 CRAG 实战:给检索加一个「评分官」

CRAG(Corrective RAG)是工程价值最高的模式,用 LangGraph + TypeScript 完整实现它。

先定义 State 和图结构:

typescript 复制代码
import { Annotation, StateGraph, END } from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
import { z } from "zod";
​
// 定义 Graph 状态
const AgenticRAGState = Annotation.Root({
  question: Annotation<string>(),
  documents: Annotation<string[]>({
    reducer: (prev, next) => [...prev, ...next],
    default: () => [],
  }),
  generation: Annotation<string>(),
  retrieval_grade: Annotation<"relevant" | "irrelevant" | "none">(),
  retry_count: Annotation<number>({
    reducer: (prev, next) => next,
    default: () => 0,
  }),
});
​
type RAGState = typeof AgenticRAGState.State;
const llm = new ChatOpenAI({ model: "gpt-4o-mini", temperature: 0 });

检索节点 + 评分节点------从向量库取文档,再让 LLM 判断相关性:

javascript 复制代码
// 检索节点
async function retrieveNode(state: RAGState) {
  const results = await vectorStore.similaritySearch(state.question, 5);
  return { documents: results.map((d) => d.pageContent) };
}
​
// 评分节点
const GradeSchema = z.object({
  score: z.enum(["relevant", "irrelevant", "none"]),
  reason: z.string(),
});
​
async function gradeDocumentsNode(state: RAGState) {
  const structuredLLM = llm.withStructuredOutput(GradeSchema);
  const prompt = `你是一个检索结果评分器。
用户问题:${state.question}
检索到的文档:
${state.documents.join("\n\n---\n\n")}
​
评估这些文档是否能回答用户问题:
- relevant:高度相关,可以据此生成可靠答案
- irrelevant:关联性很弱,但还有一些信息
- none:完全无关或为空
​
给出评分和原因。`;
  const result = await structuredLLM.invoke(prompt);
  return { retrieval_grade: result.score };
}

Query 改写节点 + 生成节点:

javascript 复制代码
// Query 改写节点------检索质量差时优化问题表述
async function rewriteQueryNode(state: RAGState) {
  const prompt = `原始问题:${state.question}
向量检索结果质量不佳。请重写问题,包含更多关键词、拆解更具体、去除歧义。
只输出改写后的问题,不要任何解释。`;
  const response = await llm.invoke(prompt);
  return {
    question: response.content as string,
    documents: [], // 清空旧结果,准备重新检索
    retry_count: state.retry_count + 1,
  };
}
​
// 生成节点
async function generateNode(state: RAGState) {
  const context = state.documents.join("\n\n");
  const prompt = `基于以下文档回答问题。
文档:${context}
问题:${state.question}
给出准确、有据可查的回答。如果文档不足以完整回答,请明确说明。`;
  const response = await llm.invoke(prompt);
  return { generation: response.content as string };
}

条件路由 + 组装 Graph:

arduino 复制代码
// 条件路由------核心决策逻辑
const MAX_RETRY = 2;
function routeAfterGrading(state: RAGState): string {
  if (state.retrieval_grade === "relevant") return "generate";
  if (state.retry_count >= MAX_RETRY) {
    console.log("⚠️ 超过重试上限,降级生成");
    return "generate";
  }
  if (state.retrieval_grade === "none") return "rewrite_query";
  return "generate"; // irrelevant 但有一些信息,接受并生成
}
​
// 组装 Graph
const workflow = new StateGraph(AgenticRAGState)
  .addNode("retrieve", retrieveNode)
  .addNode("grade_documents", gradeDocumentsNode)
  .addNode("rewrite_query", rewriteQueryNode)
  .addNode("generate", generateNode)
  .addEdge("__start__", "retrieve")
  .addEdge("retrieve", "grade_documents")
  .addConditionalEdges("grade_documents", routeAfterGrading, {
    generate: "generate",
    rewrite_query: "rewrite_query",
  })
  .addEdge("rewrite_query", "retrieve") // 改写后重新检索
  .addEdge("generate", END);
​
const app = workflow.compile();
​
// 运行
const result = await app.invoke({ question: "LangGraph 的 Checkpoint 是什么?" });
console.log("最终答案:", result.generation);

04 路由型 RAG:让 Agent 决定去哪个「图书馆」找书

路由型 RAG 的关键在于把每个数据源封装成 Tool,让 LLM 自主选择。

csharp 复制代码
import { tool } from "@langchain/core/tools";
import { z } from "zod";
​
// 工具1:向量库搜索(文档、FAQ)
const searchDocsTool = tool(
  async ({ query }: { query: string }) => {
    const results = await vectorStore.similaritySearch(query, 5);
    return results.map((d) => d.pageContent).join("\n\n");
  },
  {
    name: "search_docs",
    description:
      "搜索产品文档、技术规格、使用指南。适用于功能介绍、操作步骤类问题。",
    schema: z.object({ query: z.string() }),
  }
);
​
// 工具2:SQL 数据库(结构化数据)
const queryDatabaseTool = tool(
  async ({ sql }: { sql: string }) => {
    const result = await db.query(sql);
    return JSON.stringify(result.rows);
  },
  {
    name: "query_database",
    description:
      "查询业务数据库。适用于营收、用户量、留存率等量化指标问题。只支持 SELECT 语句。",
    schema: z.object({ sql: z.string().describe("只读 SQL 查询语句") }),
  }
);
​
// 工具3:网页搜索(实时信息)
const webSearchTool = tool(
  async ({ query }: { query: string }) => {
    const results = await tavilySearch(query);
    return results.map((r: any) => r.content).join("\n\n");
  },
  {
    name: "web_search",
    description:
      "搜索互联网获取最新信息。适用于当前事件、最新版本、外部市场数据。",
    schema: z.object({ query: z.string() }),
  }
);
​
// 绑定工具,让 Agent 自主路由
const agentWithTools = llm.bindTools([
  searchDocsTool,
  queryDatabaseTool,
  webSearchTool,
]);

工具描述是路由准确性的关键------越清晰,LLM 选错的概率越低。每个工具描述都要写清楚:适用于什么问题,而不只是「功能是什么」。反例就在上面:如果你把三个工具描述都写成「搜索相关信息」,LLM 会倾向于总选第一个,路由完全失效。


05 自适应 RAG:连「要不要检索」都让 Agent 决定

自适应 RAG 在所有模式里成本最优,因为它在最上游就做了一次过滤:这个问题需要检索吗?

csharp 复制代码
const RouteSchema = z.object({
  datasource: z.enum(["vectorstore", "web_search", "direct_answer"]),
  reason: z.string(),
});
​
async function routeQuestion(state: RAGState) {
  const structuredLLM = llm.withStructuredOutput(RouteSchema);
  const prompt = `你是一个问题路由器,决定如何最高效地回答用户问题。
问题:${state.question}
​
选择路由策略:
- direct_answer:通用知识或对话上下文已覆盖,不需要检索
- vectorstore:关于特定文档、产品内部知识
- web_search:需要实时信息或外部知识
​
选择最合适的一个,给出原因。`;
  return await structuredLLM.invoke(prompt);
}
​
// Graph 中根据路由结果分叉
function routeNode(state: RAGState & { routing_decision?: string }): string {
  const decision = state.routing_decision;
  if (decision === "direct_answer") return "generate";
  if (decision === "web_search") return "web_search";
  return "retrieve";
}

这个模式特别适合通用助手场景------用户既会问「你好,帮我写段代码」(不需要检索),也会问「我们的 API 文档里 rate limit 是多少」(需要检索内部文档)。统一入口,自适应路由。


06 常见坑:Agentic RAG 踩过才知道

坑1:忘记设置最大重试次数,Graph 无限循环

纠错型 RAG 最容易踩这个坑。如果向量库里压根没有这个知识,CRAG 会一直循环到 token 耗尽。

kotlin 复制代码
// 正确做法:硬性保护,State 里记录重试计数
if (state.retry_count >= MAX_RETRY) {
  return "fallback_generate"; // 降级,绝不继续循环
}

坑2:评分 Prompt 太模糊,LLM 永远返回 "relevant"

如果评分 Prompt 只说「判断文档是否相关」,LLM 倾向于给「相关」------它在训练时被优化为「有帮助」的助手,不喜欢说「这个我不知道」。解法:在 Prompt 里给出具体的 "irrelevant" 判断条件,比如「如果文档只是包含相同关键词但讨论完全不同话题,算 irrelevant」。

坑3:多步 RAG 子问题有依赖却并行执行

把复杂问题拆成子问题后,如果直接并行检索,但子问题 B 的答案依赖子问题 A 的结果,最终合并时会出现逻辑断层。解法:在分解步骤里判断依赖关系,有依赖的串行,独立的才并行。

坑4:工具描述写得太像,LLM 每次都选同一个

路由型 RAG 里,如果三个工具描述都是「搜索相关信息」,LLM 会分布极不均匀。解法:每个工具描述要突出差异化适用场景,用反例说明「什么情况下不要用这个工具」。

坑5:评分节点用大模型,贵还不稳定

用 LLM 评估 LLM 检索结果本身有幻觉风险。评分用小模型(gpt-4o-mini)配 Zod schema 强制结构化输出,不要用大模型做简单分类任务------成本高且评分反而更不稳定。


总结

这篇我们把 Agentic RAG 从头到尾拆完了:

  • 传统 RAG 的三个死穴:单次检索无回头、单一数据源孤岛、无法分解复杂问题------不是调参能解决的,是架构层的缺陷。
  • 四种模式各有适场:路由型解决多数据源、多步型解决复杂分解、CRAG 解决检索质量、自适应型解决无意义检索开销------实际项目经常组合使用。
  • CRAG 是工程价值最高的起点:加一个评分节点 + Query 改写节点 + 重试上限,三步改造让 RAG 具备自我纠错能力。
  • 最大重试次数是生命线:任何带循环的 Agentic RAG,都要在 State 里记录重试计数,超限强制降级,永远不要让 Graph 无限重试。
  • 工具描述决定路由准确率:路由型 RAG 里,工具的 description 比代码逻辑更关键------写清楚适用场景和反例。

下一篇我们进入「知识库的动态更新」------文档变了、新文档进来了,向量库怎么做增量同步,避免全量重导。


相关推荐
weixin_468466851 小时前
SURF 图像特征提取算法新手实战指南
图像处理·人工智能·算法·机器视觉·surf·sift
weiwin1231 小时前
MAF入门(3 下):多轮对话进阶——清除历史、注入 System、截断策略
人工智能·agent
Coder小相1 小时前
LangChain 1.0 第五篇 - Tool与MCP让Agent拥有行动力
人工智能·langchain·ai编程
太华1 小时前
学习AI Agent编程-第五天-LlamaIndex - 将Nodes生成索引并存储
人工智能
太华1 小时前
学习AI Agent编程-第三天-LlamaIndex - 如何将PDF文件正确转成Document
人工智能
jiayong231 小时前
AI架构师面试问题与解答 - 深度学习架构篇
人工智能·深度学习
unclejet1 小时前
颠覆传统开发!AI根治软件工程技术债务顽疾
大数据·人工智能·软件工程
程序员鱼皮2 小时前
我用 GitHub 仓库养 AI 龙虾,自动开发上线项目!保姆级教程
前端·人工智能·ai·程序员·github·编程·ai编程
Master_oid2 小时前
机器学习44:线性回归进阶篇②
人工智能·机器学习·线性回归