05篇讲Agentic RAG时给了基本概念和查询路由的代码。但那篇是"架构概览"级别——你知道了Agentic RAG是什么,但真要落地还有一堆问题:Agent怎么判断检索结果好不好?检索到垃圾内容怎么自动修正?多步推理怎么避免死循环?延迟太高怎么优化?

这篇把Agentic RAG从概念推到工程落地。重点讲两个2024-2025年最有影响力的方案:Corrective RAG(CRAG)和Self-RAG。这两个方案的核心思想一样——让RAG学会自我纠错——但实现路径完全不同。

基础RAG的问题在于它太"老实":检索到什么就用什么,不管检索结果好不好。如果检索到了不相关的内容,LLM要么硬编答案,要么说"我不知道"。Agentic RAG的思路是加一层"裁判":先评估检索质量,不好的话就换方式重新检索。

基础RAG vs Agentic RAG:差在哪

| 维度

|

基础RAG

|

Agentic RAG

检索策略

|

固定:1次检索,取TopK

|

自适应:根据问题复杂度决定检索几次

| |

结果评估

|

无:检索到什么用什么

|

有:评估检索结果质量,低质量触发重检索

| |

错误修正

|

无:答错就答错了

|

有:发现答案有问题自动重试

| |

数据源

|

单一:向量库

|

多源:向量库+数据库+API+搜索引擎

| |

决策

|

无:固定流水线

|

有:Agent自主选择检索策略

| |

延迟

|

200-500ms

|

1-10秒(多了决策和重试)

| |

成本

|

1次LLM调用

|

3-8次LLM调用

| |

准确率

|

基准

|

提升15-30%

|

核心权衡:Agentic RAG用更多的时间和成本换取更高的准确率。适合对准确率要求高、对延迟不敏感的场景(如企业知识库问答、法律咨询)。不适合实时对话场景(延迟要求<2秒)。

Corrective RAG(CRAG):检索结果打分 + 自动纠正

CRAG是2024年提出的方案,核心思路:给检索结果打分,分三档处理。

<span leaf="">检索结果 → 质量评估 → ① Correct(相关):直接用</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; → ② Incorrect(不相关):丢掉,去网络搜索</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; → ③ Ambiguous(不确定):部分用 + 网络搜索补充</span><span leaf=""><br></span>

质量评估:怎么判断检索结果好不好

<span><span leaf="">from</span></span><span leaf="">&nbsp;langchain_core.prompts&nbsp;</span><span><span leaf="">import</span></span><span leaf="">&nbsp;ChatPromptTemplate</span><span leaf=""><br></span><span><span leaf="">from</span></span><span leaf="">&nbsp;pydantic&nbsp;</span><span><span leaf="">import</span></span><span leaf="">&nbsp;BaseModel, Field</span><span leaf=""><br></span><span><span leaf="">from</span></span><span leaf="">&nbsp;enum&nbsp;</span><span><span leaf="">import</span></span><span leaf="">&nbsp;Enum</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""><br></span><span><span><span leaf="">class</span></span><span leaf="">&nbsp;</span><span><span leaf="">RetrievalGrade</span></span><span><span leaf="">(str, Enum)</span></span><span leaf="">:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""检索结果质量等级"""</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; CORRECT =&nbsp;</span><span><span leaf="">"correct"</span></span><span leaf="">&nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf=""># 相关:直接用</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; INCORRECT =&nbsp;</span><span><span leaf="">"incorrect"</span></span><span leaf="">&nbsp;&nbsp;</span><span><span leaf=""># 不相关:丢弃</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; AMBIGUOUS =&nbsp;</span><span><span leaf="">"ambiguous"</span></span><span leaf="">&nbsp;&nbsp;</span><span><span leaf=""># 不确定:部分使用</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""><br></span><span><span><span leaf="">class</span></span><span leaf="">&nbsp;</span><span><span leaf="">GradeResult</span></span><span><span leaf="">(BaseModel)</span></span><span leaf="">:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""检索结果评估"""</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; grade: RetrievalGrade = Field(description=</span><span><span leaf="">"检索结果质量等级"</span></span><span leaf="">)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; reason: str = Field(description=</span><span><span leaf="">"判断理由"</span></span><span leaf="">)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; relevant_parts: str = Field(description=</span><span><span leaf="">"如果ambiguous,哪些部分是相关的"</span></span><span leaf="">, default=</span><span><span leaf="">""</span></span><span leaf="">)</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""><br></span><span><span><span leaf="">def</span></span><span leaf="">&nbsp;</span><span><span leaf="">grade_retrieval</span></span><span><span leaf="">(question, retrieved_docs, llm)</span></span><span leaf="">:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""评估检索结果质量</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; CRAG的核心:不是所有检索结果都值得用。</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; 先让LLM判断检索结果跟问题的相关度,再决定怎么处理。</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; """</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; grade_prompt = ChatPromptTemplate.from_template(</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""你是一个检索质量评估专家。请判断以下检索结果是否能回答用户问题。</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">用户问题:{question}</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">检索结果:</span><span leaf=""><br></span><span leaf="">{documents}</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">判断标准:</span><span leaf=""><br></span><span leaf="">- correct:检索结果直接包含回答问题所需的信息</span><span leaf=""><br></span><span leaf="">- incorrect:检索结果跟问题完全无关</span><span leaf=""><br></span><span leaf="">- ambiguous:检索结果部分相关,但不完整或不确定</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">{format_instructions}"""</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; )</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf=""># 拼接检索结果</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; doc_text =&nbsp;</span><span><span leaf="">"\n---\n"</span></span><span leaf="">.join([</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">f"[文档</span><span><span leaf="">{i+</span><span><span leaf="">1</span></span><span leaf="">}</span></span><span leaf="">]&nbsp;</span><span><span leaf="">{doc.page_content}</span></span><span leaf="">"</span></span><span leaf="">&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">for</span></span><span leaf="">&nbsp;i, doc&nbsp;</span><span><span leaf="">in</span></span><span leaf="">&nbsp;enumerate(retrieved_docs)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; ])</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">from</span></span><span leaf="">&nbsp;langchain_core.output_parsers&nbsp;</span><span><span leaf="">import</span></span><span leaf="">&nbsp;PydanticOutputParser</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; parser = PydanticOutputParser(pydantic_object=GradeResult)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; chain = grade_prompt | llm | parser</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; result = chain.invoke({</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">"question"</span></span><span leaf="">: question,</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">"documents"</span></span><span leaf="">: doc_text,</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">"format_instructions"</span></span><span leaf="">: parser.get_format_instructions(),</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; })</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;result</span><span leaf=""><br></span>

CRAG的纠正策略

<span><span><span leaf="">def</span></span><span leaf="">&nbsp;</span><span><span leaf="">crag_search</span></span><span><span leaf="">(question, retriever, llm, web_search_fn=None)</span></span><span leaf="">:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""Corrective RAG:检索 → 评估 → 纠正</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; 三档处理:</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; - correct → 知识转换(提取关键信息,去噪)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; - incorrect → 丢弃,用网络搜索补充</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; - ambiguous → 提取相关部分 + 网络搜索补充</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; """</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf=""># Step 1: 初始检索</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; retrieved_docs = retriever.invoke(question)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf=""># Step 2: 质量评估</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; grade = grade_retrieval(question, retrieved_docs, llm)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; print(</span><span><span leaf="">f"检索评估:&nbsp;</span><span><span leaf="">{grade.grade}</span></span><span leaf="">&nbsp;-&nbsp;</span><span><span leaf="">{grade.reason}</span></span><span leaf="">"</span></span><span leaf="">)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">if</span></span><span leaf="">&nbsp;grade.grade == RetrievalGrade.CORRECT:</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf=""># 相关:知识转换(提取关键信息,去掉噪声)</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; refined_docs = knowledge_transformation(retrieved_docs, question, llm)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;refined_docs,&nbsp;</span><span><span leaf="">"knowledge_base"</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">elif</span></span><span leaf="">&nbsp;grade.grade == RetrievalGrade.INCORRECT:</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf=""># 不相关:丢弃,去网络搜索</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">if</span></span><span leaf="">&nbsp;web_search_fn:</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; print(</span><span><span leaf="">"检索结果不相关,启用网络搜索..."</span></span><span leaf="">)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; web_results = web_search_fn(question)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;web_results,&nbsp;</span><span><span leaf="">"web_search"</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">else</span></span><span leaf="">:</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;[],&nbsp;</span><span><span leaf="">"no_result"</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">else</span></span><span leaf="">: &nbsp;</span><span><span leaf=""># AMBIGUOUS</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf=""># 部分相关:提取相关部分 + 网络搜索补充</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; relevant_docs = [doc&nbsp;</span><span><span leaf="">for</span></span><span leaf="">&nbsp;doc&nbsp;</span><span><span leaf="">in</span></span><span leaf="">&nbsp;retrieved_docs&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">if</span></span><span leaf="">&nbsp;any(keyword&nbsp;</span><span><span leaf="">in</span></span><span leaf="">&nbsp;doc.page_content&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;</span><span><span leaf="">for</span></span><span leaf="">&nbsp;keyword&nbsp;</span><span><span leaf="">in</span></span><span leaf="">&nbsp;grade.relevant_parts.split())]</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">if</span></span><span leaf="">&nbsp;web_search_fn:</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; web_results = web_search_fn(question)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;relevant_docs + web_results,&nbsp;</span><span><span leaf="">"hybrid"</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">else</span></span><span leaf="">:</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;relevant_docs,&nbsp;</span><span><span leaf="">"partial"</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""><br></span><span><span><span leaf="">def</span></span><span leaf="">&nbsp;</span><span><span leaf="">knowledge_transformation</span></span><span><span leaf="">(docs, question, llm)</span></span><span leaf="">:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""知识转换:提取跟问题最相关的信息,去掉噪声</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; CRAG的一个细节:即使是"correct"的检索结果,</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; 也不是整篇用,而是提取最相关的部分。</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; """</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; transform_prompt = ChatPromptTemplate.from_template(</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""从以下文档中提取与问题最相关的信息,去掉无关内容。</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">问题:{question}</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">文档:</span><span leaf=""><br></span><span leaf="">{documents}</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">提取关键信息(保留原文表述,不要改写):"""</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; )</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; doc_text =&nbsp;</span><span><span leaf="">"\n"</span></span><span leaf="">.join([doc.page_content&nbsp;</span><span><span leaf="">for</span></span><span leaf="">&nbsp;doc&nbsp;</span><span><span leaf="">in</span></span><span leaf="">&nbsp;docs])</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; chain = transform_prompt | llm</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; refined = chain.invoke({</span><span><span leaf="">"question"</span></span><span leaf="">: question,&nbsp;</span><span><span leaf="">"documents"</span></span><span leaf="">: doc_text})</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf=""># 返回精炼后的文档</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">from</span></span><span leaf="">&nbsp;langchain_core.documents&nbsp;</span><span><span leaf="">import</span></span><span leaf="">&nbsp;Document</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;[Document(page_content=refined.content, metadata={</span><span><span leaf="">"source"</span></span><span leaf="">:&nbsp;</span><span><span leaf="">"crag_refined"</span></span><span leaf="">})]</span><span leaf=""><br></span>

Java类比:CRAG就像你写了一个带重试机制的HTTP客户端——先发请求,检查响应状态码,200就用,404就换URL重试,206就提取可用部分。基础RAG是发完请求不管返回什么都直接用。所以CRAG在Java开发者看来非常直觉——这不就是带fallback的REST模板嘛。

Self-RAG:让模型自己决定要不要检索

Self-RAG和CRAG的思路不同。CRAG是"先检索再纠错",Self-RAG是"让模型自己决定要不要检索、检索结果好不好、要不要用"。

Self-RAG训练了一个特殊的LLM,它能输出"反思token"(reflection tokens):

  • [Retrieve]:这个问题需要检索吗?

  • [IsRel]:检索结果相关吗?

  • [IsSup]:生成的答案有检索结果支持吗?

  • [IsUse]:这个答案对用户有用吗?

Self-RAG的推理流程

<span leaf="">用户问题 → [Retrieve]判断 → 需要检索 → 检索 → [IsRel]判断 → 相关 → 生成 → [IsSup]判断 → 有支撑 → [IsUse]判断 → 有用 → 输出</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; → 不需要 &nbsp; → 直接生成 → 输出</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;→ 不相关 &nbsp; → 重新检索或直接生成</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; → 无支撑 &nbsp; → 重新生成</span><span leaf=""><br></span>

工程实现:用LangGraph模拟Self-RAG

真正的Self-RAG需要微调一个能输出反思token的模型。但工程上我们可以用LangGraph模拟这个流程——用普通LLM做判断节点:

<span><span leaf="">from</span></span><span leaf="">&nbsp;langgraph.graph&nbsp;</span><span><span leaf="">import</span></span><span leaf="">&nbsp;StateGraph, END</span><span leaf=""><br></span><span><span leaf="">from</span></span><span leaf="">&nbsp;typing&nbsp;</span><span><span leaf="">import</span></span><span leaf="">&nbsp;TypedDict, Annotated</span><span leaf=""><br></span><span><span leaf="">import</span></span><span leaf="">&nbsp;operator</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""><br></span><span><span><span leaf="">class</span></span><span leaf="">&nbsp;</span><span><span leaf="">SelfRAGState</span></span><span><span leaf="">(TypedDict)</span></span><span leaf="">:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""Self-RAG的状态"""</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; question: str &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;</span><span><span leaf=""># 用户问题</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; need_retrieval: bool &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf=""># 是否需要检索</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; documents: Annotated[list, operator.add] &nbsp;&nbsp;</span><span><span leaf=""># 检索到的文档</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; is_relevant: bool &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;</span><span><span leaf=""># 检索结果是否相关</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; answer: str &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;</span><span><span leaf=""># 生成的答案</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; is_supported: bool &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf=""># 答案是否有文档支撑</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; is_useful: bool &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;</span><span><span leaf=""># 答案是否有用</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; retry_count: int &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf=""># 重试次数</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""><br></span><span><span><span leaf="">def</span></span><span leaf="">&nbsp;</span><span><span leaf="">judge_need_retrieval</span></span><span><span leaf="">(state)</span></span><span leaf="">:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""[Retrieve] 判断是否需要检索</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; 简单问题(闲聊、通用知识)不需要检索,</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; 复杂问题(公司政策、产品细节)需要检索。</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; """</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; question = state[</span><span><span leaf="">"question"</span></span><span leaf="">]</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; judge_prompt =&nbsp;</span><span><span leaf="">f"""判断以下问题是否需要从知识库检索信息。</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">问题:</span><span><span leaf="">{question}</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">判断标准:</span><span leaf=""><br></span><span leaf="">- 需要:涉及具体事实、公司政策、产品细节、技术文档</span><span leaf=""><br></span><span leaf="">- 不需要:闲聊、通用知识、观点表达</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">回答(需要/不需要):"""</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; response = llm.invoke(judge_prompt).content</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; need =&nbsp;</span><span><span leaf="">"需要"</span></span><span leaf="">&nbsp;</span><span><span leaf="">in</span></span><span leaf="">&nbsp;response</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;{</span><span><span leaf="">"need_retrieval"</span></span><span leaf="">: need,&nbsp;</span><span><span leaf="">"retry_count"</span></span><span leaf="">: state.get(</span><span><span leaf="">"retry_count"</span></span><span leaf="">,&nbsp;</span><span><span leaf="">0</span></span><span leaf="">)}</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""><br></span><span><span><span leaf="">def</span></span><span leaf="">&nbsp;</span><span><span leaf="">retrieve_documents</span></span><span><span leaf="">(state)</span></span><span leaf="">:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""执行检索"""</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; docs = retriever.invoke(state[</span><span><span leaf="">"question"</span></span><span leaf="">])</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;{</span><span><span leaf="">"documents"</span></span><span leaf="">: docs}</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""><br></span><span><span><span leaf="">def</span></span><span leaf="">&nbsp;</span><span><span leaf="">judge_relevance</span></span><span><span leaf="">(state)</span></span><span leaf="">:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""[IsRel] 判断检索结果是否相关"""</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; grade = grade_retrieval(state[</span><span><span leaf="">"question"</span></span><span leaf="">], state[</span><span><span leaf="">"documents"</span></span><span leaf="">], llm)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; is_relevant = grade.grade != RetrievalGrade.INCORRECT</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;{</span><span><span leaf="">"is_relevant"</span></span><span leaf="">: is_relevant}</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""><br></span><span><span><span leaf="">def</span></span><span leaf="">&nbsp;</span><span><span leaf="">generate_answer</span></span><span><span leaf="">(state)</span></span><span leaf="">:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""生成答案"""</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; context =&nbsp;</span><span><span leaf="">"\n"</span></span><span leaf="">.join([doc.page_content&nbsp;</span><span><span leaf="">for</span></span><span leaf="">&nbsp;doc&nbsp;</span><span><span leaf="">in</span></span><span leaf="">&nbsp;state[</span><span><span leaf="">"documents"</span></span><span leaf="">]])</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; prompt =&nbsp;</span><span><span leaf="">f"""基于以下上下文回答问题。如果上下文不足以回答,请说明。</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">上下文:</span><span leaf=""><br></span><span><span leaf="">{context}</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">问题:</span><span><span leaf="">{state[</span><span><span leaf="">"question"</span></span><span leaf="">]}</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">回答:"""</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; answer = llm.invoke(prompt).content</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;{</span><span><span leaf="">"answer"</span></span><span leaf="">: answer}</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""><br></span><span><span><span leaf="">def</span></span><span leaf="">&nbsp;</span><span><span leaf="">judge_support</span></span><span><span leaf="">(state)</span></span><span leaf="">:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""[IsSup] 判断答案是否有文档支撑</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; 关键:防止LLM编造答案。</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; 检查答案中的每个论断是否都能在检索到的文档中找到依据。</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; """</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; answer = state[</span><span><span leaf="">"answer"</span></span><span leaf="">]</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; docs = state[</span><span><span leaf="">"documents"</span></span><span leaf="">]</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; doc_text =&nbsp;</span><span><span leaf="">"\n"</span></span><span leaf="">.join([doc.page_content&nbsp;</span><span><span leaf="">for</span></span><span leaf="">&nbsp;doc&nbsp;</span><span><span leaf="">in</span></span><span leaf="">&nbsp;docs])</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; support_prompt =&nbsp;</span><span><span leaf="">f"""判断以下答案是否被文档支撑。</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">文档:</span><span leaf=""><br></span><span><span leaf="">{doc_text}</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">答案:</span><span leaf=""><br></span><span><span leaf="">{answer}</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">判断标准:</span><span leaf=""><br></span><span leaf="">- supported:答案中的信息都能在文档中找到依据</span><span leaf=""><br></span><span leaf="">- unsupported:答案包含文档中没有的信息(可能是编造的)</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">回答(supported/unsupported):"""</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; response = llm.invoke(support_prompt).content</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; is_supported =&nbsp;</span><span><span leaf="">"supported"</span></span><span leaf="">&nbsp;</span><span><span leaf="">in</span></span><span leaf="">&nbsp;response.lower()</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;{</span><span><span leaf="">"is_supported"</span></span><span leaf="">: is_supported}</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""><br></span><span><span><span leaf="">def</span></span><span leaf="">&nbsp;</span><span><span leaf="">judge_useful</span></span><span><span leaf="">(state)</span></span><span leaf="">:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""[IsUse] 判断答案是否有用"""</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; answer = state[</span><span><span leaf="">"answer"</span></span><span leaf="">]</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; question = state[</span><span><span leaf="">"question"</span></span><span leaf="">]</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; useful_prompt =&nbsp;</span><span><span leaf="">f"""判断以下答案对用户问题是否有用。</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">问题:</span><span><span leaf="">{question}</span></span><span leaf=""><br></span><span leaf="">答案:</span><span><span leaf="">{answer}</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">判断标准:</span><span leaf=""><br></span><span leaf="">- useful:直接回答了问题,信息完整</span><span leaf=""><br></span><span leaf="">- not_useful:没有回答问题,或答非所问</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">回答(useful/not_useful):"""</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; response = llm.invoke(useful_prompt).content</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; is_useful =&nbsp;</span><span><span leaf="">"useful"</span></span><span leaf="">&nbsp;</span><span><span leaf="">in</span></span><span leaf="">&nbsp;response.lower()</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;{</span><span><span leaf="">"is_useful"</span></span><span leaf="">: is_useful}</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""><br></span><span><span><span leaf="">def</span></span><span leaf="">&nbsp;</span><span><span leaf="">direct_generate</span></span><span><span leaf="">(state)</span></span><span leaf="">:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""不需要检索,直接生成"""</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; prompt =&nbsp;</span><span><span leaf="">f"回答以下问题:</span><span><span leaf="">{state[</span><span><span leaf="">'question'</span></span><span leaf="">]}</span></span><span leaf="">"</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; answer = llm.invoke(prompt).content</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;{</span><span><span leaf="">"answer"</span></span><span leaf="">: answer,&nbsp;</span><span><span leaf="">"is_supported"</span></span><span leaf="">:&nbsp;</span><span><span leaf="">True</span></span><span leaf="">,&nbsp;</span><span><span leaf="">"is_useful"</span></span><span leaf="">:&nbsp;</span><span><span leaf="">True</span></span><span leaf="">}</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""><br></span><span><span><span leaf="">def</span></span><span leaf="">&nbsp;</span><span><span leaf="">should_retrieve</span></span><span><span leaf="">(state)</span></span><span leaf="">:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""路由:是否检索"""</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">if</span></span><span leaf="">&nbsp;state.get(</span><span><span leaf="">"need_retrieval"</span></span><span leaf="">,&nbsp;</span><span><span leaf="">True</span></span><span leaf="">):</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;</span><span><span leaf="">"retrieve"</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">else</span></span><span leaf="">:</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;</span><span><span leaf="">"direct_generate"</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""><br></span><span><span><span leaf="">def</span></span><span leaf="">&nbsp;</span><span><span leaf="">should_regenerate</span></span><span><span leaf="">(state)</span></span><span leaf="">:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""路由:答案无支撑时是否重新生成"""</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; retry = state.get(</span><span><span leaf="">"retry_count"</span></span><span leaf="">,&nbsp;</span><span><span leaf="">0</span></span><span leaf="">)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">if</span></span><span leaf="">&nbsp;</span><span><span leaf="">not</span></span><span leaf="">&nbsp;state.get(</span><span><span leaf="">"is_supported"</span></span><span leaf="">,&nbsp;</span><span><span leaf="">True</span></span><span leaf="">)&nbsp;</span><span><span leaf="">and</span></span><span leaf="">&nbsp;retry &lt;&nbsp;</span><span><span leaf="">2</span></span><span leaf="">:</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;</span><span><span leaf="">"regenerate"</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;</span><span><span leaf="">"end"</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""><br></span><span><span leaf=""># 构建LangGraph流程图</span></span><span leaf=""><br></span><span><span><span leaf="">def</span></span><span leaf="">&nbsp;</span><span><span leaf="">build_self_rag_graph</span></span><span><span leaf="">()</span></span><span leaf="">:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""构建Self-RAG的LangGraph流程"""</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; workflow = StateGraph(SelfRAGState)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf=""># 添加节点</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; workflow.add_node(</span><span><span leaf="">"judge_retrieval"</span></span><span leaf="">, judge_need_retrieval)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; workflow.add_node(</span><span><span leaf="">"retrieve"</span></span><span leaf="">, retrieve_documents)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; workflow.add_node(</span><span><span leaf="">"judge_relevance"</span></span><span leaf="">, judge_relevance)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; workflow.add_node(</span><span><span leaf="">"generate"</span></span><span leaf="">, generate_answer)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; workflow.add_node(</span><span><span leaf="">"judge_support"</span></span><span leaf="">, judge_support)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; workflow.add_node(</span><span><span leaf="">"judge_useful"</span></span><span leaf="">, judge_useful)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; workflow.add_node(</span><span><span leaf="">"direct_generate"</span></span><span leaf="">, direct_generate)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf=""># 设置入口</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; workflow.set_entry_point(</span><span><span leaf="">"judge_retrieval"</span></span><span leaf="">)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf=""># 条件路由</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; workflow.add_conditional_edges(</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">"judge_retrieval"</span></span><span leaf="">,</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; should_retrieve,</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; {</span><span><span leaf="">"retrieve"</span></span><span leaf="">:&nbsp;</span><span><span leaf="">"retrieve"</span></span><span leaf="">,&nbsp;</span><span><span leaf="">"direct_generate"</span></span><span leaf="">:&nbsp;</span><span><span leaf="">"direct_generate"</span></span><span leaf="">},</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; )</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; workflow.add_edge(</span><span><span leaf="">"retrieve"</span></span><span leaf="">,&nbsp;</span><span><span leaf="">"judge_relevance"</span></span><span leaf="">)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; workflow.add_conditional_edges(</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">"judge_relevance"</span></span><span leaf="">,</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">lambda</span></span><span leaf="">&nbsp;state:&nbsp;</span><span><span leaf="">"generate"</span></span><span leaf="">&nbsp;</span><span><span leaf="">if</span></span><span leaf="">&nbsp;state[</span><span><span leaf="">"is_relevant"</span></span><span leaf="">]&nbsp;</span><span><span leaf="">else</span></span><span leaf="">&nbsp;</span><span><span leaf="">"retrieve"</span></span><span leaf="">,</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; {</span><span><span leaf="">"generate"</span></span><span leaf="">:&nbsp;</span><span><span leaf="">"generate"</span></span><span leaf="">,&nbsp;</span><span><span leaf="">"retrieve"</span></span><span leaf="">:&nbsp;</span><span><span leaf="">"retrieve"</span></span><span leaf="">}, &nbsp;</span><span><span leaf=""># 不相关就重新检索</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; )</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; workflow.add_edge(</span><span><span leaf="">"generate"</span></span><span leaf="">,&nbsp;</span><span><span leaf="">"judge_support"</span></span><span leaf="">)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; workflow.add_conditional_edges(</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">"judge_support"</span></span><span leaf="">,</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; should_regenerate,</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; {</span><span><span leaf="">"regenerate"</span></span><span leaf="">:&nbsp;</span><span><span leaf="">"generate"</span></span><span leaf="">,&nbsp;</span><span><span leaf="">"end"</span></span><span leaf="">:&nbsp;</span><span><span leaf="">"judge_useful"</span></span><span leaf="">},</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; )</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; workflow.add_edge(</span><span><span leaf="">"judge_useful"</span></span><span leaf="">, END)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; workflow.add_edge(</span><span><span leaf="">"direct_generate"</span></span><span leaf="">, END)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;workflow.compile()</span><span leaf=""><br></span>

坑1:重试死循环。Self-RAG的判断节点可能形成循环——检索不相关→重新检索→还是不相关→重新检索……必须在路由函数里加重试次数限制。上面代码的 should_regenerate 里有 retry < 2 限制,最多重试2次。

Agentic RAG的查询路由:多数据源调度

Agentic RAG和基础RAG的另一个区别:多数据源。Agent根据问题类型选择不同的检索源。

<span><span leaf="">from</span></span><span leaf="">&nbsp;enum&nbsp;</span><span><span leaf="">import</span></span><span leaf="">&nbsp;Enum</span><span leaf=""><br></span><span><span leaf="">from</span></span><span leaf="">&nbsp;pydantic&nbsp;</span><span><span leaf="">import</span></span><span leaf="">&nbsp;BaseModel, Field</span><span leaf=""><br></span><span><span leaf="">from</span></span><span leaf="">&nbsp;typing&nbsp;</span><span><span leaf="">import</span></span><span leaf="">&nbsp;Optional</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""><br></span><span><span><span leaf="">class</span></span><span leaf="">&nbsp;</span><span><span leaf="">DataSource</span></span><span><span leaf="">(str, Enum)</span></span><span leaf="">:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""数据源类型"""</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; KNOWLEDGE_BASE =&nbsp;</span><span><span leaf="">"knowledge_base"</span></span><span leaf="">&nbsp;&nbsp;</span><span><span leaf=""># 向量知识库</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; DATABASE =&nbsp;</span><span><span leaf="">"database"</span></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf=""># SQL数据库</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; WEB_SEARCH =&nbsp;</span><span><span leaf="">"web_search"</span></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf=""># 网络搜索</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; CALCULATOR =&nbsp;</span><span><span leaf="">"calculator"</span></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf=""># 计算器</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; DIRECT_ANSWER =&nbsp;</span><span><span leaf="">"direct_answer"</span></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf=""># 直接回答(不需要检索)</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""><br></span><span><span><span leaf="">class</span></span><span leaf="">&nbsp;</span><span><span leaf="">QueryRoute</span></span><span><span leaf="">(BaseModel)</span></span><span leaf="">:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""查询路由结果"""</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; source: DataSource = Field(description=</span><span><span leaf="">"数据源"</span></span><span leaf="">)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; rewritten_query: str = Field(description=</span><span><span leaf="">"针对该数据源优化后的查询"</span></span><span leaf="">)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; reason: str = Field(description=</span><span><span leaf="">"选择该数据源的理由"</span></span><span leaf="">)</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""><br></span><span><span><span leaf="">def</span></span><span leaf="">&nbsp;</span><span><span leaf="">route_query</span></span><span><span leaf="">(question, llm)</span></span><span leaf="">:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""查询路由:判断问题该走哪个数据源</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; 这是Agentic RAG的"大脑"——决定了信息从哪来。</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; """</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; route_prompt = ChatPromptTemplate.from_template(</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""你是一个查询路由专家。请分析用户问题,选择最合适的数据源。</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">可用数据源:</span><span leaf=""><br></span><span leaf="">- knowledge_base:公司内部知识库(产品文档、政策流程、技术文档)</span><span leaf=""><br></span><span leaf="">- database:业务数据库(销售数据、用户数据、财务数据,支持SQL查询)</span><span leaf=""><br></span><span leaf="">- web_search:网络搜索(实时信息、新闻、外部知识)</span><span leaf=""><br></span><span leaf="">- calculator:计算器(数值计算、统计)</span><span leaf=""><br></span><span leaf="">- direct_answer:直接回答(闲聊、通用知识)</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">用户问题:{question}</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">请选择数据源并给出优化后的查询。"""</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; )</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; chain = route_prompt | llm.with_structured_output(QueryRoute)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;chain.invoke({</span><span><span leaf="">"question"</span></span><span leaf="">: question})</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""><br></span><span><span leaf=""># 路由示例</span></span><span leaf=""><br></span><span><span leaf=""># 问题:"公司退货流程是什么" → knowledge_base, "退货流程 退换货政策"</span></span><span leaf=""><br></span><span><span leaf=""># 问题:"上季度华东区销售额" → database, "SELECT SUM(amount) FROM sales WHERE region='华东' AND quarter='Q3'"</span></span><span leaf=""><br></span><span><span leaf=""># 问题:"最新的AI行业趋势" → web_search, "2025年AI行业发展趋势"</span></span><span leaf=""><br></span><span><span leaf=""># 问题:"3.14乘以2.5" → calculator, "3.14 * 2.5"</span></span><span leaf=""><br></span><span><span leaf=""># 问题:"你好" → direct_answer, ""</span></span><span leaf=""><br></span>

多工具Agent

<span><span leaf="">from</span></span><span leaf="">&nbsp;langchain.agents&nbsp;</span><span><span leaf="">import</span></span><span leaf="">&nbsp;create_agent</span><span leaf=""><br></span><span><span leaf="">from</span></span><span leaf="">&nbsp;langchain_core.tools&nbsp;</span><span><span leaf="">import</span></span><span leaf="">&nbsp;tool</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""><br></span><span><span leaf="">@tool</span></span><span leaf=""><br></span><span><span><span leaf="">def</span></span><span leaf="">&nbsp;</span><span><span leaf="">search_knowledge_base</span></span><span><span leaf="">(query: str)</span></span><span leaf="">&nbsp;-&gt; str:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""搜索公司内部知识库,获取产品文档、政策流程、技术规范等信息。</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; 当用户问公司内部信息时使用。"""</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; results = retriever.invoke(query)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;</span><span><span leaf="">"\n"</span></span><span leaf="">.join([r.page_content&nbsp;</span><span><span leaf="">for</span></span><span leaf="">&nbsp;r&nbsp;</span><span><span leaf="">in</span></span><span leaf="">&nbsp;results[:</span><span><span leaf="">5</span></span><span leaf="">]])</span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""><br></span><span><span leaf="">@tool</span></span><span leaf=""><br></span><span><span><span leaf="">def</span></span><span leaf="">&nbsp;</span><span><span leaf="">query_database</span></span><span><span leaf="">(sql: str)</span></span><span leaf="">&nbsp;-&gt; str:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""执行SQL查询获取业务数据。</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; 当用户问销售数据、用户统计等结构化数据时使用。</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; 参数必须是合法的SQL语句。"""</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf=""># 实际项目对接数据库</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">try</span></span><span leaf="">:</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf=""># result = db.execute(sql)</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;</span><span><span leaf="">f"查询结果:[模拟数据]"</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">except</span></span><span leaf="">&nbsp;Exception&nbsp;</span><span><span leaf="">as</span></span><span leaf="">&nbsp;e:</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;</span><span><span leaf="">f"查询失败:</span><span><span leaf="">{e}</span></span><span leaf="">"</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""><br></span><span><span leaf="">@tool</span></span><span leaf=""><br></span><span><span><span leaf="">def</span></span><span leaf="">&nbsp;</span><span><span leaf="">search_web</span></span><span><span leaf="">(query: str)</span></span><span leaf="">&nbsp;-&gt; str:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""搜索互联网获取实时信息、新闻、外部知识。</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; 当用户问最新信息或知识库中没有的外部知识时使用。"""</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf=""># 实际项目对接搜索API</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;</span><span><span leaf="">f"搜索结果:[模拟数据]"</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""><br></span><span><span leaf="">@tool</span></span><span leaf=""><br></span><span><span><span leaf="">def</span></span><span leaf="">&nbsp;</span><span><span leaf="">calculate</span></span><span><span leaf="">(expression: str)</span></span><span leaf="">&nbsp;-&gt; str:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""执行数值计算。当用户需要数学计算时使用。</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; 参数是数学表达式,如 '3.14 * 2.5'。"""</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">try</span></span><span leaf="">:</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; result = eval(expression) &nbsp;</span><span><span leaf=""># 生产环境用ast.literal_eval或专门的解析器</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;</span><span><span leaf="">f"计算结果:</span><span><span leaf="">{result}</span></span><span leaf="">"</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">except</span></span><span leaf="">&nbsp;Exception&nbsp;</span><span><span leaf="">as</span></span><span leaf="">&nbsp;e:</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;</span><span><span leaf="">f"计算失败:</span><span><span leaf="">{e}</span></span><span leaf="">"</span></span><span leaf=""><br></span><span leaf=""><br></span><span leaf=""><br></span><span><span><span leaf="">def</span></span><span leaf="">&nbsp;</span><span><span leaf="">create_agentic_rag</span></span><span><span leaf="">(llm)</span></span><span leaf="">:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""创建Agentic RAG Agent</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; Agent拥有多个工具,自主决定:</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; 1. 用哪个工具获取信息</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; 2. 是否需要多次检索</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; 3. 检索结果够不够</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; 4. 什么时候停止检索开始生成</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; """</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; agent = create_agent(</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; model=llm,</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; tools=[search_knowledge_base, query_database, search_web, calculate],</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; system_prompt=</span><span><span leaf="">"""你是一个智能助手,拥有多种信息获取工具。</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">工作流程:</span><span leaf=""><br></span><span leaf="">1. 分析用户问题的类型和所需信息</span><span leaf=""><br></span><span leaf="">2. 选择最合适的工具获取信息</span><span leaf=""><br></span><span leaf="">3. 如果第一次结果不够,换一个工具或换query再试</span><span leaf=""><br></span><span leaf="">4. 基于获取的信息生成回答</span><span leaf=""><br></span><span leaf="">5. 在回答末尾标注信息来源(知识库/数据库/网络)</span><span leaf=""><br></span><span leaf=""><br></span><span leaf="">重要规则:</span><span leaf=""><br></span><span leaf="">- 不要编造答案,所有信息必须基于工具返回的结果</span><span leaf=""><br></span><span leaf="">- 如果所有工具都找不到相关信息,明确告知用户</span><span leaf=""><br></span><span leaf="">- 最多使用5次工具调用,避免无限循环</span><span leaf=""><br></span><span leaf="">- 数值类问题用calculator,不要让LLM自己算"""</span></span><span leaf="">,</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; )</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;agent</span><span leaf=""><br></span>

坑2:Agent工具选择不稳定。同一个问题"上季度销售额",Agent有时选database(对),有时选knowledge_base(错——知识库里可能有过期数据)。解决:在工具描述里写清楚适用场景,并在system_prompt里给明确的决策规则。上面代码的docstring就是给Agent看的"使用说明"。

延迟优化:Agentic RAG最大的工程挑战

Agentic RAG最大的问题是慢——基础RAG 200ms出结果,Agentic RAG可能要5-10秒。用户体验很差。

优化方案1:并行化

<span><span leaf="">import</span></span><span leaf="">&nbsp;asyncio</span><span leaf=""><br></span><span leaf=""><br></span><span><span leaf="">async</span></span><span leaf="">&nbsp;</span><span><span><span leaf="">def</span></span><span leaf="">&nbsp;</span><span><span leaf="">parallel_retrieve</span></span><span><span leaf="">(question, retrievers)</span></span><span leaf="">:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""并行检索多个数据源,取最快返回的结果</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; 不等Agent决策完再检索,而是同时发起多个检索,</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; Agent决策完直接从已完成的检索结果里取。</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; """</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; tasks = []</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">for</span></span><span leaf="">&nbsp;name, retriever&nbsp;</span><span><span leaf="">in</span></span><span leaf="">&nbsp;retrievers.items():</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; tasks.append(retriever.ainvoke(question))</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf=""># 并行执行,取全部结果</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; results =&nbsp;</span><span><span leaf="">await</span></span><span leaf="">&nbsp;asyncio.gather(*tasks, return_exceptions=</span><span><span leaf="">True</span></span><span leaf="">)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf=""># 过滤掉异常</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; valid_results = []</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">for</span></span><span leaf="">&nbsp;name, result&nbsp;</span><span><span leaf="">in</span></span><span leaf="">&nbsp;zip(retrievers.keys(), results):</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">if</span></span><span leaf="">&nbsp;</span><span><span leaf="">not</span></span><span leaf="">&nbsp;isinstance(result, Exception):</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; valid_results.append((name, result))</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;valid_results</span><span leaf=""><br></span>

优化方案2:缓存+预判

<span><span leaf="">from</span></span><span leaf="">&nbsp;functools&nbsp;</span><span><span leaf="">import</span></span><span leaf="">&nbsp;lru_cache</span><span leaf=""><br></span><span><span leaf="">import</span></span><span leaf="">&nbsp;hashlib</span><span leaf=""><br></span><span leaf=""><br></span><span><span><span leaf="">class</span></span><span leaf="">&nbsp;</span><span><span leaf="">CachedAgenticRAG</span></span><span leaf="">:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""带缓存的Agentic RAG</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; 两层缓存:</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; 1. 答案缓存:相同问题直接返回(命中率高的问题重复问很多)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; 2. 路由缓存:相同类型问题直接用之前的路由决策</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; """</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span><span leaf="">def</span></span><span leaf="">&nbsp;</span><span><span leaf="">__init__</span></span><span><span leaf="">(self, agent)</span></span><span leaf="">:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; self.agent = agent</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; self.answer_cache = {} &nbsp; &nbsp; &nbsp;</span><span><span leaf=""># question_hash → answer</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; self.route_cache = {} &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf=""># question_pattern → route</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span><span leaf="">def</span></span><span leaf="">&nbsp;</span><span><span leaf="">query</span></span><span><span leaf="">(self, question)</span></span><span leaf="">:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf=""># 1. 答案缓存</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; q_hash = hashlib.md5(question.encode()).hexdigest()</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">if</span></span><span leaf="">&nbsp;q_hash&nbsp;</span><span><span leaf="">in</span></span><span leaf="">&nbsp;self.answer_cache:</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; print(</span><span><span leaf="">"命中答案缓存"</span></span><span leaf="">)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;self.answer_cache[q_hash],&nbsp;</span><span><span leaf="">0</span></span><span leaf="">&nbsp;&nbsp;</span><span><span leaf=""># 0ms</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf=""># 2. 路由预判:根据问题模式快速选数据源</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; route = self._quick_route(question)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">if</span></span><span leaf="">&nbsp;route:</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; print(</span><span><span leaf="">f"命中路由缓存:</span><span><span leaf="">{route}</span></span><span leaf="">"</span></span><span leaf="">)</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf=""># 直接走对应数据源,跳过Agent决策</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf=""># 3. 正常Agent流程</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; answer = self.agent.invoke({</span><span><span leaf="">"messages"</span></span><span leaf="">: [{</span><span><span leaf="">"role"</span></span><span leaf="">:&nbsp;</span><span><span leaf="">"user"</span></span><span leaf="">,&nbsp;</span><span><span leaf="">"content"</span></span><span leaf="">: question}]})</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; result = answer[</span><span><span leaf="">"messages"</span></span><span leaf="">][</span><span><span leaf="">-1</span></span><span leaf="">].content</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf=""># 4. 写入缓存</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; self.answer_cache[q_hash] = result</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;result</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span><span leaf="">def</span></span><span leaf="">&nbsp;</span><span><span leaf="">_quick_route</span></span><span><span leaf="">(self, question)</span></span><span leaf="">:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""快速路由:基于规则匹配,不走LLM"""</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; rules = [</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (</span><span><span leaf="">r"销售额|收入|利润|用户数"</span></span><span leaf="">,&nbsp;</span><span><span leaf="">"database"</span></span><span leaf="">),</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (</span><span><span leaf="">r"最新|新闻|今天|昨天"</span></span><span leaf="">,&nbsp;</span><span><span leaf="">"web_search"</span></span><span leaf="">),</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (</span><span><span leaf="">r"流程|政策|文档|规范"</span></span><span leaf="">,&nbsp;</span><span><span leaf="">"knowledge_base"</span></span><span leaf="">),</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; ]</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">import</span></span><span leaf="">&nbsp;re</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">for</span></span><span leaf="">&nbsp;pattern, route&nbsp;</span><span><span leaf="">in</span></span><span leaf="">&nbsp;rules:</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">if</span></span><span leaf="">&nbsp;re.search(pattern, question):</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;route</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">return</span></span><span leaf="">&nbsp;</span><span><span leaf="">None</span></span><span leaf=""><br></span>

优化方案3:流式输出

<span><span leaf="">async</span></span><span leaf="">&nbsp;</span><span><span><span leaf="">def</span></span><span leaf="">&nbsp;</span><span><span leaf="">agentic_rag_stream</span></span><span><span leaf="">(question, agent)</span></span><span leaf="">:</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">"""流式输出:Agent决策时先输出"正在检索...",不让用户干等</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; 用户体验的关键不是绝对延迟,而是感知延迟。</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; 流式输出让用户看到进度,感知延迟降低50%。</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; """</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">yield</span></span><span leaf="">&nbsp;</span><span><span leaf="">"正在分析问题...\n"</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf=""># Agent第一步:路由判断</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">yield</span></span><span leaf="">&nbsp;</span><span><span leaf="">"正在检索相关信息...\n"</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf=""># Agent检索(这里简化,实际是Agent的中间步骤)</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf=""># ...</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf=""># 生成答案时流式输出</span></span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp;&nbsp;</span><span><span leaf="">async</span></span><span leaf="">&nbsp;</span><span><span leaf="">for</span></span><span leaf="">&nbsp;chunk&nbsp;</span><span><span leaf="">in</span></span><span leaf="">&nbsp;agent.astream({</span><span><span leaf="">"messages"</span></span><span leaf="">: [{</span><span><span leaf="">"role"</span></span><span leaf="">:&nbsp;</span><span><span leaf="">"user"</span></span><span leaf="">,&nbsp;</span><span><span leaf="">"content"</span></span><span leaf="">: question}]}):</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">if</span></span><span leaf="">&nbsp;</span><span><span leaf="">"content"</span></span><span leaf="">&nbsp;</span><span><span leaf="">in</span></span><span leaf="">&nbsp;chunk:</span><span leaf=""><br></span><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;</span><span><span leaf="">yield</span></span><span leaf="">&nbsp;chunk[</span><span><span leaf="">"content"</span></span><span leaf="">]</span><span leaf=""><br></span>

CRAG vs Self-RAG:选哪个

| 维度

|

CRAG

|

Self-RAG

核心思路

|

检索后纠错

|

全流程反思

| |

判断次数

|

1次(评估检索质量)

|

3-4次(检索/相关/支撑/有用)

| |

延迟

|

中(1次检索+1次评估)

|

高(多次判断+可能重试)

| |

准确率

|

比基础RAG提升15-20%

|

比基础RAG提升20-30%

| |

工程复杂度

|

中(加一层评估+纠正)

|

高(LangGraph多节点流程)

| |

适合场景

|

知识库质量参差不齐

|

对准确率要求极高

|

我的建议:先上CRAG——在现有RAG基础上加一个检索质量评估节点,成本低效果明显。如果CRAG的准确率还不够(比如法律/医疗场景),再考虑Self-RAG。别一上来就Self-RAG,工程复杂度和延迟你都受不了。

要不要上Agentic RAG

| 你的情况

|

建议

|

原因

Advanced RAG准确率<80%

|

先别上Agentic

|

基础没打好,加Agent更乱

| |

准确率>80%但要更高

|

先上CRAG

|

加一层评估,成本低

| |

法律/医疗等高要求

|

再考虑Self-RAG

|

全流程反思,准确率最高

| |

延迟要求<2秒

|

别上Agentic

|

Agentic比基础慢10-20倍

| |

需要多数据源调度

|

上查询路由

|

投入产出比最高的一步

| |

预算有限

|

别上Self-RAG

|

一次查询3-8次LLM调用

|

本篇要点

| 要点

|

说明

Agentic RAG核心

|

自主决策检索策略 + 结果评估 + 自我纠正

| |

CRAG

|

检索结果分三档:correct直接用/incorrect换网络搜/ambiguous部分用+补充

| |

Self-RAG

|

四个反思节点:[Retrieve][IsRel][IsSup][IsUse],全流程自我检查

| |

查询路由

|

根据问题类型选数据源:知识库/数据库/网络/计算器/直接回答

| |

多工具Agent

|

Agent拥有多个检索工具,自主决定用哪个、用几次

| |

延迟优化

|

并行检索 + 缓存 + 流式输出,把5-10秒降到可接受范围

| |

选择建议

|

先CRAG再Self-RAG,别一上来就搞复杂的

|

踩坑清单

  1. 重试死循环:Self-RAG的判断节点可能形成循环——不相关→重新检索→还是不相关→重新检索。必须在路由函数里加 retry_count 限制,最多重试2次。

  2. Agent工具选择不稳定:同一个问题,Agent有时选对工具有时选错。在工具docstring里写清楚适用场景,system_prompt里给明确决策规则。

  3. 延迟太大用户接受不了:Agentic RAG比基础RAG慢10-20倍。必须加流式输出让用户看到进度,加缓存减少重复计算。如果延迟还是太高,退回CRAG甚至基础RAG。

  4. 成本爆炸:一次查询3-8次LLM调用。1000次查询就是$30-80。用小模型做判断节点(路由、评估),大模型只做最终生成。

  5. Agent调试困难:Agent的决策过程不透明,出了问题不知道哪一步走错了。用LangGraph的trace功能记录每步决策,或在每个节点加日志。

  6. 多数据源结果冲突:知识库说"退货时限7天",网络搜索说"退货时限15天"。Agent不知道信谁。解决:给数据源设优先级(知识库 > 数据库 > 网络),或在system_prompt里指定冲突时的处理规则。

  7. 评估指标难定:Agentic RAG的评估比基础RAG复杂——除了检索准确率,还要评估Agent的决策质量(路由对不对、重试值不值)。用RAGAS + 人工审核路由日志结合评估。

下篇预告

至此RAG系列的10篇正文全部完成。下一篇是系列总结——把10篇文章的知识点串成一张图谱,给你决策树和速查表,帮你快速定位"我的RAG问题出在哪、该怎么调"。


你的RAG项目在考虑上Agentic RAG吗?还是基础RAG够用了?评论区说说你的场景。

觉得有用就点个在看,下一篇是RAG系列总结——一张图串通全流程。