别再用「死板 RAG」!手把手实现会思考、会纠错、会联网的 Agentic RAG(LangGraph 实战)

别再用「死板 RAG」!手把手实现会思考、会纠错、会联网的 Agentic RAG(LangGraph 实战)

一句话导读:上一篇文章我们把《天龙八部》灌进了向量库,跑通了一个 Naive RAG。但它什么问题都硬走检索、没有判断、不会多步推理、也不会联网补知识。这篇我们把 RAG 升级成「会思考的 Agent」:问题路由、多跳检索、上下文评估、联网兜底,四招在手,跑出一个真正的 Agentic RAG 闭环。

为什么 Naive RAG 不够用了?

先把朴素 RAG 的短板摊开看。它的流水线固定是 检索 → 生成所有问题一视同仁,于是暴露出一堆问题:

需求场景 Naive RAG 的表现 问题本质
用户问 1+1=? 这种常识 也傻乎乎地走一遍向量检索 简单问题白白浪费 token 和时间
检索回的片段对不对、够不够 不校验就直接喂给模型 没有评估/纠错机制
「四大恶人第二的是谁?他儿子的生父公开身份是什么?」 一次性检索,拆不出因果链 处理不了需要多步检索的复杂问题
问「高血糖」这种专有术语 纯语义检索可能捞回「低血糖」 实体/术语更适合关键词精准匹配
本地知识库没有的东西 硬编或干脆不答 不会主动联网补充

这些单点问题的本质是同一个:流水线太死板了,缺一个「大脑」来做判断。

Agentic RAG(智能体化 RAG) 要做的,就是把这条方程式的控制权交给 LLM + 图编排:让它自主决定要不要检索、用什么检索、信息够不够、要不要重新检索。LangGraph 就是承载这套「思考流程」的最佳载体------节点是动作,边是流向,State 是共享的海绵板。

LLM 的 withStructuredOutput(结构化输出)会是本篇反复用到的魔术,后面每招都会遇到。

第一招:装一个「问题路由器」------简单 / 复杂分道扬镳

第一个优化:别让简单问题也硬走检索。

先用 LLM 判断问题是 simple 还是 complex,然后走不同的分支:

  • simple(常识问答、简短定义)→ 直接让模型作答,不进检索;
  • complex(需要具体情节、事实、原文证据)→ 才进入检索链路。
js 复制代码
import "dotenv/config";
import { ChatOpenAI, OpenAIEmbeddings } from "@langchain/openai";
import { Annotation, END, START, StateGraph } from '@langchain/langgraph';
import { Milvus } from '@langchain/community/vectorstores/milvus';
import { z } from 'zod';

const GraphState = Annotation.Root({
  question: Annotation,
  k: Annotation,
  strategy: Annotation,   // simple | complex
  routeReason: Annotation,
  documents: Annotation,
  generation: Annotation
});

const model = new ChatOpenAI({
  model: process.env.MODEL_NAME,
  temperature: 0,
  configuration: { baseURL: process.env.OPENAI_BASE_URL },
  apiKey: process.env.OPENAI_API_KEY
});
const embeddings = new OpenAIEmbeddings({
  model: "text-embedding-v3",
  dimensions: 1024
});

let vectorStore;

路由节点:让 LLM 决定走哪条路

js 复制代码
// 用 zod 声明一个结构化的输出契约(枚举 + 理由)
const RouteSchema = z.object({
  strategy: z.enum(["simple", "complex"]),
  reason: z.string()
});

const routeQuestionNode = async (state) => {
  console.log('___ROUTE-QUESTION___');
  // withStructuredOutput:逼着模型严格按 schema 输出 JSON
  const router = model.withStructuredOutput(RouteSchema);
  const route = await router.invoke(`
你是问答路由器,请判断用户问题是否需要外部检索。
规则:
- simple: 常识问答、简短定义、无需特定小说细节即可回答。
- complex: 需要《天龙八部》具体情节、人物关系、章节事实、原文细节或证据支持。

用户问题:${state.question}
`);
  console.log(`路由策略:${route.strategy} (${route.reason})`);
  return {
    question: state.question,
    k: state.k,
    strategy: route.strategy,
    routeReason: route.reason
  };
};

withStructuredOutput 的意义在于:路由结果必须是 simple / complex 这种可被代码枚举判断的字符串,而不是模型胡诌的一段散文。这样下游才能用字符串相等来做分支。

直接回答 + 检索 + 生成节点

js 复制代码
const directAnswerNode = async (state) => {
  console.log('----DIRECT_ANSWER----');
  let generation = "";
  const stream = await model.stream(
    `你是一个中文回答助手,请直接简洁回答问题。
问题:${state.question}`
  );
  for await (const chunk of stream) {
    const text = typeof chunk.content === 'string' ? chunk.content : "";
    if (!text) continue;
    generation += text;
    process.stdout.write(text);
  }
  return { question: state.question, k: state.k, strategy: state.strategy,
           routeReason: state.routeReason, documents: [], generation };
};

async function retrieveRelevantContent(question, k = 5) {
  try {
    const docsWithScores = await vectorStore.similaritySearchWithScore(question, k);
    return docsWithScores.map(([doc, score]) => ({
      score,
      content: doc.pageContent,
      id: doc.metadata?.id ?? "unknown",
      book_id: doc.metadata?.book_id ?? "未知",
      chapter_num: doc.metadata?.chapter_num ?? "未知",
      index: doc.metadata?.index ?? "未知"
    }));
  } catch (err) { console.error("检索出错:", err.message); return []; }
}

const retrieveNode = async (state) => {
  const documents = await retrieveRelevantContent(state.question, state.k);
  return { question: state.question, k: state.k, documents };
};

const generateNode = async (state) => {
  const context = state.documents
    .map((item, i) =>
      `[片段 ${i+1}]
章节: 第 ${item.chapter_num}章
内容:${item.content}`)
    .join("\n\n----------\n\n");
  let generation = "";
  const stream = await model.stream(`
你是一个专业的《天龙八部》小说助手,基于小说片段回答问题。
${context}
用户问题:${state.question}
回答要求:有信息就详细准确回答;可综合多片段;没有就如实告知;可引用原文。
AI 助手的回答:`);
  for await (const chunk of stream) {
    const text = typeof chunk.content === "string" ? chunk.content : "";
    if (!text) continue;
    generation += text; process.stdout.write(text);
  }
  return { question: state.question, k: state.k, documents: state.documents, generation };
};

用条件边把分支接起来

关键来了:LangGraph 的 条件边(conditional edges) 能根据 State 内容动态决定下一步。

js 复制代码
const decideNext = (state) =>
  state.strategy === 'simple' ? "direct_answer" : "retrieve";

const graph = new StateGraph(GraphState)
  .addNode("route_question", routeQuestionNode)
  .addNode("direct_answer", directAnswerNode)
  .addNode("retrieve", retrieveNode)
  .addNode("rag_generate", generateNode)
  .addEdge(START, "route_question")
  .addConditionalEdges("route_question", decideNext, {
    direct_answer: "direct_answer",  // simple -> 直接答
    retrieve: "retrieve"              // complex -> 进检索
  })
  .addEdge("retrieve", "rag_generate")
  .addEdge("direct_answer", END)
  .addEdge("rag_generate", END)
  .compile();

现在问 1+1=? 会走直答分支,只有复杂问题才去检索。每一块钱的 token 都花在刀刃上。

第二招:多跳检索(Multi-Hop)------把复杂问题拆成子问题逐个攻破

路由解决了「要不要检索」,但没解决「检索一次够不够」。

有些问题天生需要多步推理,例如:

《天龙八部》中「四大恶人」排行第二的是谁?此人之子在身世揭晓前,其生父在武林中的公开身份是什么?

要答这道题,必须 查出「四大恶人第二 = 叶二娘」,查「叶二娘的儿子(虚竹)的生父,其武林中的公开身份(玄慈方丈)」。一次性向量化整个长问题,匹配必然发散(这就是为什么这个 repo 注释里感叹「直接把 query 向量化匹配不够准确」)。

解决思路:子问题拆解(Decompose) 。让 LLM 把大问题拆成一串有序、可独立检索 的子问题,然后循环执行 检索一个子问题 → 汇总去重 → 判断够不够 → 继续下一个

要在上面那套 GraphState 上做多跳,得先给状态补充几个新字段:子问题列表、当前进度、累计检索次数与预算、以及「规划器」的裁决结果。

js 复制代码
const GraphState = Annotation.Root({
  question: Annotation,
  k: Annotation,
  strategy: Annotation,
  routeReason: Annotation,
  subQuestions: Annotation,  // 拆解出的有序子问题列表
  nextSubIdx: Annotation,    // 下一个要检索的子问题下标(做循环推进)
  currentQuery: Annotation,  // 当前这一轮正在检索的子问题
  retrievalCount: Annotation,// 已检索轮数
  maxRetrievals: Annotation, // 最大检索轮数(预算/护栏)
  plannedNext: Annotation,   // 规划器裁决:下一步 retrieve | generate
  documents: Annotation,
  generation: Annotation
});

问题拆解器

js 复制代码
const DecomposeSchema = z.object({
  sub_questions: z.array(z.string()).min(1).max(8),
  reason: z.string()
});

const decomposeQuestionNode = async (state) => {
  console.log("---DECOMPOSE_QUESTION---");
  const decomposer = model.withStructuredOutput(DecomposeSchema);
  const out = await decomposer.invoke(`
你是《天龙八部》多跳问答的【子问题拆解器】。
用户原始问题:
${state.question}
任务:将问题拆成**有序**子问题列表,用于**依次向量检索**。要求:
1. 链式推理、多层关系、先后因果的问题必须拆成多条;单跳即可答的也可只输出 1 条。
2. 每条子问题必须是**可独立检索**的完整中文问句,禁止使用「他/她/此人/上文」等指代,要写全人物名与事件名。
3. 顺序必须符合推理链:先查前置实体/事实,再查后续结论。
4. 不要把整句原题原样复制成唯一一条(除非确实无法拆分);也不要拆成过碎的关键词列表。
5. 输出 1~8 条即可。
`);
  const subQuestions = out.sub_questions.map(s => s.trim()).filter(Boolean);
  if (subQuestions.length === 0) throw new Error("拆解结果为空");
  console.log(`拆解出 ${subQuestions.length} 条子问题(${out.reason})`);
  subQuestions.forEach((q, i) => console.log(`[${i+1}] ${q}`));
  return { subQuestions, nextSubIdx: 0, currentQuery: subQuestions[0] };
};

循环检索 + 去重

多轮检索容易重复捞回同一片段,既浪费又可能让模型产生「重复 = 强调」的错觉。用 Map 按文档 id 去重,留相似度更高版本。

js 复制代码
const mergeUnique = (existing, fresh) => {
  const map = new Map(); // ES6 的 HashMap,key: value
  for (const d of [...existing, ...fresh]) {
    const key = String(d.id);
    const prev = map.get(key);
    if (!prev || Number(d.score) > Number(prev.score)) map.set(key, d);
  }
  return Array.from(map.values()).sort((a, b) => Number(b.score) - Number(a.score));
};

const retrieveNode = async (state) => {
  const subs = state.subQuestions ?? [];
  const idx = state.nextSubIdx ?? 0;
  const q = subs[idx]?.trim();
  if (!q) throw new Error(`retrieve: 子问题下标 ${idx} 无有效文本`);

  const round = state.retrievalCount + 1;
  console.log(`----第 ${round} 轮,子问题 ${idx+1}/${subs.length}:${q}----`);
  const newDocs = await retrieveRelevantContent(q, state.k);
  const merged = mergeUnique(state.documents ?? [], newDocs);
  console.log(`本轮命中 ${newDocs.length} 条,累计去重后 ${merged.length} 条`);
  return {
    documents: merged,
    retrievalCount: round,
    nextSubIdx: idx + 1,
    currentQuery: q
  };
};

规划器:判断「够了没」还是「继续搜」

每检索完一个子问题,让 LLM 看当前已召回的证据,决定下一步 retrieve(继续)还是 generate(收尾作答)。同时用 硬性规则护栏 :子问题搜完、或轮数到达上限,必须强制 generate,防止死循环。

js 复制代码
const NextStepSchema = z.object({
  nextAction: z.enum(["retrieve", "generate"]),
  reason: z.string()
});

const planNextStepNode = async (state) => {
  console.log("---PLAN_NEXT_STEP---");
  const subs = state.subQuestions ?? [];
  const nextIdx = state.nextSubIdx ?? 0;
  const remaining = subs.length - nextIdx;

  const subList = subs.map((s, i) =>
    `${i+1}.${s} ${i < nextIdx ? "已检索" : i === nextIdx ? "(下一轮将检索)" : "未检索"}`).join("\n");
  const docStr = state.documents.length === 0
    ? "(尚无检索结果)"
    : state.documents.slice(0, 6)
        .map((d, i) => `[${i+1}] score=${Number(d.score).toFixed(4)} 第${d.chapter_num}章:${d.content.slice(0, 200)}`)
        .join("\n\n");

  const prompt = `
你是多跳 RAG 规划器。检索查询已由前置步骤拆解为**有序子问题**,若需继续检索,下一轮将自动使用【下一条子问题】检索,你不要自拟新检索句。
用户原始问题:${state.question}
子问题序列:
${subList || "无"}
已检索轮次:${state.retrievalCount};剩余未检索子问题:${remaining};最大轮数:${state.maxRetrievals}
已召回文档摘要:
${docStr}
请判断下一步:
1)已有足够依据回答原问题 -> nextAction=generate
2)仍缺关键事实、且仍有未检索子问题、且未超过上限 -> nextAction=retrieve
硬性规则:剩余子问题为 0 必须 generate;已达/超最大轮数必须 generate。`;

  const planModel = model.withStructuredOutput(NextStepSchema);
  const { nextAction, reason } = await planModel.invoke(prompt);

  // 硬性护栏覆盖模型建议,防止死循环
  let finalNext = nextAction;
  if (state.retrievalCount >= state.maxRetrievals) finalNext = "generate";
  if (remaining <= 0) finalNext = "generate";
  console.log(`[决策] plannedNext=${finalNext}(模型建议=${nextAction})${reason}`);
  return { plannedNext: finalNext };
};

串成「拆解 → 循环检索 → 规划 → 生成」的图

注意这里出现了一个回环retrieve -> plan_next_step -> retrieve(条件边)。这就是 Agentic 的标志------图允许「走一步、看一眼、决定要不要再走一步」。

js 复制代码
const afterRoute = (state) =>
  state.strategy === 'simple' ? "direct_answer" : "decompose_question";

// 条件边依据注入的 plannedNext 裁决,而不是字符串 strategy
const afterPlan = (state) =>
  state.plannedNext === "retrieve" ? "retrieve" : "generate";

const graph = new StateGraph(GraphState)
  .addNode("route_question", routeQuestionNode)
  .addNode("direct_answer", directAnswerNode)
  .addNode("decompose_question", decomposeQuestionNode)
  .addNode("retrieve", retrieveNode)
  .addNode("plan_next_step", planNextStepNode)
  .addNode("generate", generateNode)
  .addEdge(START, "route_question")
  .addConditionalEdges("route_question", afterRoute, {
    direct_answer: "direct_answer",
    decompose_question: "decompose_question"
  })
  .addEdge("decompose_question", "retrieve")
  .addEdge("retrieve", "plan_next_step")
  .addConditionalEdges("plan_next_step", afterPlan, {
    retrieve: "retrieve",   // 回环:继续检索下一条子问题
    generate: "generate"
  })
  .addEdge("direct_answer", END)
  .addEdge("generate", END)
  .compile();

埋个知识点:条件边的判定函数必须严格依赖 State 中可判定的字段,千万别拿语义不稳定的字段去猜分支------否则在分支判定处会出现永远走不到的分支。这也是多跳图里最常见的隐性 bug。

第三招 & 第四招:评估纠错 + 联网兜底,把闭环焊死

前两招升级了「检索的智能」,但还有一个致命场景没处理:本地知识库里根本没有的东西,模型只能编。

比如用户要「可核对的来源链接」,或者问本地向量库之外的时效信息。正确的姿势是:检索后先让模型评估信息够不够 ,不够就去联网搜索补充,再加一道「二次评估」把「联网补充的上下文」也纳入判断,最后才生成。

状态与评估器

js 复制代码
const GraphState = Annotation.Root({
  question: Annotation,
  k: Annotation,
  strategy: Annotation,
  routeReason: Annotation,
  retrievedDocs: Annotation, // 召回的本地片段
  localContext: Annotation,  // RAG 上下文
  webContext: Annotation,    // 联网搜索补充
  evaluation: Annotation,    // { enough, missing, reason, web_query? }
  generation: Annotation
});

const EvaluateSchema = z.object({
  enough: z.boolean(),                              // 上下文是否足够回答
  missing: z.array(z.string()).max(6),              // 缺失的信息点
  reason: z.string(),
  web_query: z.string().optional()                  // 不足时,给一条联网搜索句
});

沿用前面定义的聊天模型(这里记为 llm),并补一个「本地检索」节点,把召回片段拼成 localContext

js 复制代码
const llm = model; // 复用第一招定义的 ChatOpenAI 实例

const retrieveLocalNode = async (state) => {
  console.log("---LOCAL_RETRIEVE---");
  const retrievedDocs = await retrieveRelevantContent(state.question, state.k);
  console.log(`本地检索命中 ${retrievedDocs.length} 条`);
  const localContext = (retrievedDocs ?? []).map((d) => d.content).join("\n\n");
  return { retrievedDocs, localContext };
};

评估节点:LLM 当「质检员」

js 复制代码
const evaluateNode = async (state) => {
  const hasWeb = Boolean(state.webContext && String(state.webContext).trim());
  console.log(hasWeb ? "---EVALUATE_CONTEXT_WITH_WEB---" : "---EVALUATE_LOCAL_CONTEXT---");
  const evaluator = llm.withStructuredOutput(EvaluateSchema);
  const out = await evaluator.invoke(`
你是信息充分性评估器,判断当前上下文是否足以回答用户问题。
用户问题:${state.question}
已检索上下文(来自本地知识库):
${state.localContext || "(空)"}
${hasWeb ? `联网搜索结果:\n${state.webContext || "(空)"}` : ""}

输出字段:
- enough: 是否足够回答(true/false)
- missing: 若不够,列出缺失信息点(最多 6 条)
- reason: 简短原因
${hasWeb ? "" : "- web_query: 若不够,给出一个适合互联网搜索的中文查询句(完整句,可为空)"}
`);
  console.log(`${hasWeb ? "二次评估" : "评估"}:enough=${out.enough}(${out.reason})`);
  if (!out.enough && out.missing?.length) out.missing.forEach((m, i) => console.log(`缺失 ${i+1}: ${m}`));
  return { evaluation: JSON.stringify(out) };
};

注意 hasWeb 这个开关 :第一次评估(只有本地上下文)时,LLM 可以输出 web_query 告诉系统「去网上搜什么」;但经过联网后进入第二次评估时,强制模型不要再发起联网,而是基于「本地 + 联网」一起判断。这个开关是后面防死循环的一半关键。

联网搜索兜底节点

js 复制代码
async function bochaWebSearch(query, count) {
  const apiKey = process.env.BOCHA_API_KEY;
  if (!apiKey) throw new Error("未配置环境变量 BOCHA_API_KEY。");
  const url = "https://api.bochaai.com/v1/web-search";
  const body = { query, freshness: "noLimit", summary: true, count: count ?? 10 };

  const response = await fetch(url, {
    method: "POST",
    headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
    body: JSON.stringify(body)
  });
  if (!response.ok) {
    const errorText = await response.text().catch(() => "");
    throw new Error(`搜索 API 失败,状态码 ${response.status}:${errorText}`);
  }
  const json = await response.json();
  const webpages = json.data.webPages?.value ?? [];
  if (!webpages.length) return "未找到相关结果。";   // 注意:没结果才返回这句
  return webpages
    .map((p, i) => `引用 ${i+1}
标题:${p.name}
URL: ${p.url}
摘要:${p.summary}
发布时间:${p.dateLastCrawled}`)
    .join("\n\n");
}

const webSearchNode = async (state) => {
  console.log("---WEB_SEARCH---");
  const parsed = (() => { try { return JSON.parse(state.evaluation || "{}"); } catch { return {}; } })();
  const query = (parsed.web_query ?? "").trim() || state.question;
  console.log(`联网查询:${query}`);
  const webContext = await bochaWebSearch(query, 8);
  console.log(`联网结果长度:${webContext.length}`);
  return { webContext };
};

包一层 bochaWebSearch 是很有价值的工程习惯------把搜索厂商隔离成一个函数,将来要换 Brave、Serper、周泊查等任何服务商,只改这一个函数即可,流程图的 State 完全不用动。

条件边的防死循环护栏

这是整篇最容易踩坑的地方。看这条条件边:

js 复制代码
const afterEvaluateLocal = (state) => {
  // 死循环护栏:一旦有联网上下文就放行到 generate,绝不再评估(避免 本地⇄联网 无限循环)
  if (state.webContext && String(state.webContext).trim()) {
    return "generate";
  }
  const parsed = (() => { try { return JSON.parse(state.evaluation || "{}"); } catch { return {}; } })();
  return parsed.enough === true ? "generate" : "web_search";
};

const graph = new StateGraph(GraphState)
  .addNode("route_question", routeQuestionNode)
  .addNode("direct_answer", directAnswerNode)
  .addNode("local_retrieve", retrieveLocalNode)
  .addNode("evaluate_local", evaluateNode)
  .addNode("generate", generateNode)
  .addNode("web_search", webSearchNode)
  .addEdge(START, "route_question")
  .addConditionalEdges("route_question", afterRoute, {
    direct_answer: "direct_answer",
    local_retrieve: "local_retrieve"
  })
  .addEdge("local_retrieve", "evaluate_local")
  .addConditionalEdges("evaluate_local", afterEvaluateLocal, {
    generate: "generate",
    web_search: "web_search"
  })
  .addEdge("web_search", "evaluate_local")   // 联网后再回评估做二次判断
  .addEdge("direct_answer", END)
  .addEdge("generate", END)
  .compile();

推演一遍完整流程:

  1. route_question 判定 simple → 直接答结束;
  2. complex → local_retrieve 在本地向量库里召回片段,拼成 localContext
  3. evaluate_local 第一次评估:本地上下文够不够?
    • enough=truegenerate 结束;
    • enough=false → 输出 web_queryweb_search
  4. web_search 联网,把结果写进 webContext
  5. web_search -> evaluate_local但此时 hasWeb=true,第二次评估不再允许触发联网
  6. 条件边 afterEvaluateLocal 看到 webContext 非空 → 直接 generate不管够不够都收尾

这条护栏的存在保证了:联网最多发生一次,永不陷入「本地→网络→本地→网络」的死循环。这就是 Agentic RAG 里「可以自由,但不能失控」的经典设计。

生成节点:本地 + 联网统一融合

js 复制代码
const generateNode = async (state) => {
  console.log("---GENERATE---");
  // 增强 prompt:本地知识库 + 可选联网补充,一起喂给模型
  const context = [state.localContext, state.webContext].filter(Boolean).join("\n\n==联网补充==\n\n");
  let generation = "";
  const stream = await llm.stream(`
你是一个严谨的中文问答助手,优先依据上下文回答,不要编造。
上下文(本地知识库 + 可选联网补充):
${context || "(空)"}
用户问题:${state.question}
回答要求:
1. 如果上下文足够,给出清晰、可核对的回答,需要时引用来源 / 链接。
2. 如果上下文仍不足以确认关键事实,明确说明"不确定/无法从上下文确认",并说明缺失点。
3. 不要输出表情符号。
回答:`);
  for await (const chunk of stream) {
    const text = typeof chunk.content === "string" ? chunk.content : "";
    if (!text) continue;
    generation += text; process.stdout.write(text);
  }
  return { generation };
};

到这里,一个完整的 Agentic RAG 闭环成型了:

arduino 复制代码
问题 →
  ↗ simple → 直接回答
路由判断 →
  ↘ complex → 本地检索 → 评估
                              ├ enough → 生成
                              └ 不足 → 联网搜索 → 二次评估(不再联网) → 生成

用一张图看清「从 Naive 到 Agentic」的四次进化

css 复制代码
    A[普通 RAG 固定管线] -->|加问题路由| B[简单直接答,复杂才检索]
    B -->|加子问题拆解| C[多跳 RAG 循环检索]
    C -->|加上下文评估| D[信息不足自动联网补]
    D -->|加二次评估护栏| E[闭环 Agentic RAG 收尾生成]

复盘:Agentic RAG 的四个核心设计原则

  1. 路由分流 :用 withStructuredOutput 让 LLM 输出可枚举的 strategy,简单与复杂问题各走各的,省 token。
  2. 拆解 + 回环 :复杂问题拆成有序、去指代的可独立检索子问题;用条件边造 retrieve → plan → retrieve 回环,每轮回溯去重。
  3. 评估纠错 :检索后不盲目生成,先让 LLM 当质检员判断 enough / missing / web_query
  4. 兜底 + 护栏:本地缺失时联网补;用「联网后不再触发联网」的条件边焊死「防死循环」的最后一道闸。

结尾:Agentic RAG 不是终点,是生产化的起点

必须诚实地说:Agentic RAG 的具体设计没有标准答案,完全取决于你的业务场景。 内部客服机器人可能只需要「路由 + 评估」,多源事实核对可能要「子问题拆解 + 混合检索」,而带时效主张的问题则必须「联网兜底」。理解这套 「路由 → 拆解 → 评估 → 兜底」的闭环思路,再根据业务场景裁剪组合,才是正确的打开方式。

相关推荐
sarasuki1 小时前
如何让 Agent 获取更多的能力?插件 / 技能系统
人工智能·设计模式·agent
weixin_435208161 小时前
【大模型面试宝典】开源啦!!!
人工智能·职场和发展·agent·deepseek
Ticnix1 小时前
答案一个字一个字往外蹦,聊天记录却一条都没存下来
python·agent
Ticnix2 小时前
"多数新闻站都禁止内嵌"——这句话我信了很久,直到测了 16 个站点
python·agent
海边捡石子2 小时前
用 Vue 3 + FastAPI 跑通 LangChain DeepAgent:任务规划、双轨 Skills,以及可复现的本地 Agent 工作台
langchain·aigc·agent
亦暖筑序2 小时前
AgentScope Java 实战:Agent 的状态存在哪、怎么恢复、怎么隔离?
人工智能·后端·agent
梅梅绵绵冰2 小时前
RAG知识库
langchain·llm·longchain4j
MicrosoftReactor2 小时前
技术速递|从 AI 基础设施到基于 kars 的安全 AI Agent 基础设施
ai·agent·基础设施·kars