RAG 2.0:从向量检索到 Agent 自主知识治理的进化路径

摘要

RAG(检索增强生成)技术在大语言模型应用中已广泛落地,但传统 RAG 1.0 架构在 Agent 场景下暴露出检索僵化、知识不可更新、结果无法验证等根本性缺陷。本文提出 RAG 2.0 的核心范式转变------从被动向量检索进化为 Agent 自主知识治理,系统阐述检索决策机制、结果验证策略、知识自更新闭环三大核心能力,并提供基于 Python + LangGraph + FAISS 的完整工程实现。文章涵盖架构设计、关键代码、Mermaid 流程图及适用边界分析,适合已具备 RAG 基础经验的 AI 工程师参考。

版本声明:本文基于 LangGraph 0.2+、OpenAI GPT-4o、FAISS 1.7+、Pydantic 2.0+ 环境,代码示例于 2025 年 Q3 验证通过。不同框架版本实现细节可能有差异,请以官方文档为准。

适用边界:本文讨论的 RAG 2.0 范式主要面向需要动态知识更新的 Agent 应用场景,如智能客服、研究助手、代码助手等。对于静态文档问答、简单知识库查询等场景,RAG 1.0 仍然是更经济的选择。技术选型应基于实际需求,而非盲目追求新范式。

文章目录

    • 摘要
    • [一、RAG 1.0 的局限:为什么传统 RAG 在 Agent 场景下不够用](#一、RAG 1.0 的局限:为什么传统 RAG 在 Agent 场景下不够用)
      • [1.1 RAG 1.0 的经典架构回顾](#1.1 RAG 1.0 的经典架构回顾)
      • [1.2 传统 RAG 的五大核心缺陷](#1.2 传统 RAG 的五大核心缺陷)
      • [1.3 一个真实场景的失败案例](#1.3 一个真实场景的失败案例)
      • [1.4 从工具到基础设施的范式转变](#1.4 从工具到基础设施的范式转变)
    • [二、RAG 2.0 的核心变化:从被动检索到主动知识治理](#二、RAG 2.0 的核心变化:从被动检索到主动知识治理)
      • [2.1 架构对比:RAG 1.0 vs RAG 2.0](#2.1 架构对比:RAG 1.0 vs RAG 2.0)
      • [2.2 RAG 2.0 的整体架构](#2.2 RAG 2.0 的整体架构)
      • [2.3 核心设计原则](#2.3 核心设计原则)
    • [三、检索决策:Agent 自主判断是否需要检索](#三、检索决策:Agent 自主判断是否需要检索)
      • [3.1 检索决策的三层判断框架](#3.1 检索决策的三层判断框架)
      • [3.2 检索决策器的实现](#3.2 检索决策器的实现)
      • [3.3 查询改写引擎](#3.3 查询改写引擎)
      • [3.4 检索决策的完整流程](#3.4 检索决策的完整流程)
    • 四、结果验证:如何判断检索结果是否可信、是否充分
      • [4.1 结果验证的三维评估框架](#4.1 结果验证的三维评估框架)
      • [4.2 相关性评分器实现](#4.2 相关性评分器实现)
      • [4.3 信息充分性判断](#4.3 信息充分性判断)
      • [4.4 多轮迭代检索](#4.4 多轮迭代检索)
    • [五、知识自更新:Agent 发现知识缺口时如何主动补充](#五、知识自更新:Agent 发现知识缺口时如何主动补充)
      • [5.1 知识缺口的四种类型](#5.1 知识缺口的四种类型)
      • [5.2 知识缺口检测器](#5.2 知识缺口检测器)
      • [5.3 知识补充与写入](#5.3 知识补充与写入)
      • [5.4 知识更新策略:避免知识库膨胀](#5.4 知识更新策略:避免知识库膨胀)
    • [六、实战:一个 RAG 2.0 系统的完整实现](#六、实战:一个 RAG 2.0 系统的完整实现)
      • [6.1 系统集成架构](#6.1 系统集成架构)
      • [6.2 LangGraph 状态图定义](#6.2 LangGraph 状态图定义)
      • [6.3 系统启动与调用](#6.3 系统启动与调用)
      • [6.4 运行效果分析](#6.4 运行效果分析)
      • [6.5 性能优化建议](#6.5 性能优化建议)
    • 七、适用边界与风险提示
      • [7.1 RAG 2.0 的适用场景](#7.1 RAG 2.0 的适用场景)
      • [7.2 风险与应对](#7.2 风险与应对)
      • [7.3 成本对比分析](#7.3 成本对比分析)
      • [7.4 渐进式部署建议](#7.4 渐进式部署建议)
    • 八、总结
      • [8.1 核心观点回顾](#8.1 核心观点回顾)
      • [8.2 工程化启示](#8.2 工程化启示)
      • [8.3 展望](#8.3 展望)
    • 参考资料

一、RAG 1.0 的局限:为什么传统 RAG 在 Agent 场景下不够用

1.1 RAG 1.0 的经典架构回顾

传统 RAG 系统的核心流水线可以用一个简洁的流程图概括:
#mermaid-svg-IXMAh5rBxhfAn0ht{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-IXMAh5rBxhfAn0ht .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-IXMAh5rBxhfAn0ht .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-IXMAh5rBxhfAn0ht .error-icon{fill:#552222;}#mermaid-svg-IXMAh5rBxhfAn0ht .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-IXMAh5rBxhfAn0ht .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-IXMAh5rBxhfAn0ht .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-IXMAh5rBxhfAn0ht .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-IXMAh5rBxhfAn0ht .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-IXMAh5rBxhfAn0ht .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-IXMAh5rBxhfAn0ht .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-IXMAh5rBxhfAn0ht .marker{fill:#333333;stroke:#333333;}#mermaid-svg-IXMAh5rBxhfAn0ht .marker.cross{stroke:#333333;}#mermaid-svg-IXMAh5rBxhfAn0ht svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-IXMAh5rBxhfAn0ht p{margin:0;}#mermaid-svg-IXMAh5rBxhfAn0ht .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-IXMAh5rBxhfAn0ht .cluster-label text{fill:#333;}#mermaid-svg-IXMAh5rBxhfAn0ht .cluster-label span{color:#333;}#mermaid-svg-IXMAh5rBxhfAn0ht .cluster-label span p{background-color:transparent;}#mermaid-svg-IXMAh5rBxhfAn0ht .label text,#mermaid-svg-IXMAh5rBxhfAn0ht span{fill:#333;color:#333;}#mermaid-svg-IXMAh5rBxhfAn0ht .node rect,#mermaid-svg-IXMAh5rBxhfAn0ht .node circle,#mermaid-svg-IXMAh5rBxhfAn0ht .node ellipse,#mermaid-svg-IXMAh5rBxhfAn0ht .node polygon,#mermaid-svg-IXMAh5rBxhfAn0ht .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-IXMAh5rBxhfAn0ht .rough-node .label text,#mermaid-svg-IXMAh5rBxhfAn0ht .node .label text,#mermaid-svg-IXMAh5rBxhfAn0ht .image-shape .label,#mermaid-svg-IXMAh5rBxhfAn0ht .icon-shape .label{text-anchor:middle;}#mermaid-svg-IXMAh5rBxhfAn0ht .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-IXMAh5rBxhfAn0ht .rough-node .label,#mermaid-svg-IXMAh5rBxhfAn0ht .node .label,#mermaid-svg-IXMAh5rBxhfAn0ht .image-shape .label,#mermaid-svg-IXMAh5rBxhfAn0ht .icon-shape .label{text-align:center;}#mermaid-svg-IXMAh5rBxhfAn0ht .node.clickable{cursor:pointer;}#mermaid-svg-IXMAh5rBxhfAn0ht .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-IXMAh5rBxhfAn0ht .arrowheadPath{fill:#333333;}#mermaid-svg-IXMAh5rBxhfAn0ht .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-IXMAh5rBxhfAn0ht .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-IXMAh5rBxhfAn0ht .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IXMAh5rBxhfAn0ht .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-IXMAh5rBxhfAn0ht .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IXMAh5rBxhfAn0ht .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-IXMAh5rBxhfAn0ht .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-IXMAh5rBxhfAn0ht .cluster text{fill:#333;}#mermaid-svg-IXMAh5rBxhfAn0ht .cluster span{color:#333;}#mermaid-svg-IXMAh5rBxhfAn0ht div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-IXMAh5rBxhfAn0ht .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-IXMAh5rBxhfAn0ht rect.text{fill:none;stroke-width:0;}#mermaid-svg-IXMAh5rBxhfAn0ht .icon-shape,#mermaid-svg-IXMAh5rBxhfAn0ht .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IXMAh5rBxhfAn0ht .icon-shape p,#mermaid-svg-IXMAh5rBxhfAn0ht .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-IXMAh5rBxhfAn0ht .icon-shape .label rect,#mermaid-svg-IXMAh5rBxhfAn0ht .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IXMAh5rBxhfAn0ht .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-IXMAh5rBxhfAn0ht .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-IXMAh5rBxhfAn0ht :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户查询
查询向量化
向量数据库检索
Top-K 文档拼接
LLM 生成回答
返回结果

这个架构在早期的问答系统中表现良好:用户问一个问题,系统从知识库中检索相关片段,拼接到 Prompt 中让 LLM 生成回答。整个流程是线性的、一次性的、不可回溯的。

1.2 传统 RAG 的五大核心缺陷

然而,当 RAG 被嵌入到 Agent 系统中时,问题开始显现:

缺陷编号 缺陷名称 具体表现 影响
D1 检索触发僵化 无论问题是否需要外部知识,都执行检索流程 浪费计算资源,引入噪声
D2 查询改写缺失 直接使用原始用户查询进行向量检索 复杂问题检索召回率低
D3 结果无验证 检索到的文档直接拼入 Prompt,不验证可信度 幻觉风险增加,回答质量不稳定
D4 知识不可更新 知识库静态构建,无法在运行时补充 知识时效性差,缺口无法弥合
D5 单轮检索 一次检索结果不充分时,无法进行多轮迭代检索 复杂推理任务成功率低

图:左侧RAG 1.0线性流水线(查询→向量化→检索→拼接→生成),右侧RAG 2.0智能闭环(决策→改写→混合检索→验证→生成→缺口检测→知识更新),右侧用环形箭头展示迭代闭环

1.3 一个真实场景的失败案例

让我们看一个具体的例子来理解这些缺陷如何影响 Agent 的表现。

假设用户问一个研究型 Agent:"请分析 GPT-4o 和 Claude 3.5 Sonnet 在代码生成任务上的性能差异,并给出选型建议。"

在 RAG 1.0 架构下,Agent 会:

  1. 直接向量检索:用原始查询 "GPT-4o 和 Claude 3.5 Sonnet 代码生成性能差异" 去向量库搜索
  2. 检索结果不可控:可能返回一些泛泛而谈的 LLM 对比文章片段,也可能完全没有相关文档
  3. 无验证地拼接:不管检索到的内容是否相关、是否过时,直接拼入 Prompt
  4. 一次性生成:LLM 基于可能不充分甚至错误的上下文,一次性生成最终回答
  5. 无后续动作:即使 Agent 自己都不确定回答是否准确,也不会触发后续的补充检索

这个过程中,最核心的问题是:Agent 没有对知识进行"治理"的能力------它不能判断自己是否需要检索、检索结果是否可信、以及如何弥合自己的知识缺口。RAG 1.0 的检索系统对 Agent 来说是一个黑盒工具,而非可交互的知识基础设施。

1.4 从工具到基础设施的范式转变

RAG 2.0 的核心理念可以用一句话概括:

RAG 不再是一个被调用的工具,而是 Agent 自主管理的知识基础设施。

这意味着 Agent 需要具备三个核心能力:

  1. 检索决策权:Agent 自己判断是否需要检索、如何检索、检索什么
  2. 结果验证权:Agent 自己判断检索结果是否可信、是否充分、是否需要补充
  3. 知识更新权:Agent 自己发现知识缺口、主动补充知识、维护知识库的时效性

这三个能力构成了 RAG 2.0 的三角支柱:
#mermaid-svg-sgP1p1Ypc9uxtYS2{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-sgP1p1Ypc9uxtYS2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .error-icon{fill:#552222;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .marker.cross{stroke:#333333;}#mermaid-svg-sgP1p1Ypc9uxtYS2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-sgP1p1Ypc9uxtYS2 p{margin:0;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .cluster-label text{fill:#333;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .cluster-label span{color:#333;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .cluster-label span p{background-color:transparent;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .label text,#mermaid-svg-sgP1p1Ypc9uxtYS2 span{fill:#333;color:#333;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .node rect,#mermaid-svg-sgP1p1Ypc9uxtYS2 .node circle,#mermaid-svg-sgP1p1Ypc9uxtYS2 .node ellipse,#mermaid-svg-sgP1p1Ypc9uxtYS2 .node polygon,#mermaid-svg-sgP1p1Ypc9uxtYS2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .rough-node .label text,#mermaid-svg-sgP1p1Ypc9uxtYS2 .node .label text,#mermaid-svg-sgP1p1Ypc9uxtYS2 .image-shape .label,#mermaid-svg-sgP1p1Ypc9uxtYS2 .icon-shape .label{text-anchor:middle;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .rough-node .label,#mermaid-svg-sgP1p1Ypc9uxtYS2 .node .label,#mermaid-svg-sgP1p1Ypc9uxtYS2 .image-shape .label,#mermaid-svg-sgP1p1Ypc9uxtYS2 .icon-shape .label{text-align:center;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .node.clickable{cursor:pointer;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .arrowheadPath{fill:#333333;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-sgP1p1Ypc9uxtYS2 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-sgP1p1Ypc9uxtYS2 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-sgP1p1Ypc9uxtYS2 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .cluster text{fill:#333;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .cluster span{color:#333;}#mermaid-svg-sgP1p1Ypc9uxtYS2 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-sgP1p1Ypc9uxtYS2 rect.text{fill:none;stroke-width:0;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .icon-shape,#mermaid-svg-sgP1p1Ypc9uxtYS2 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .icon-shape p,#mermaid-svg-sgP1p1Ypc9uxtYS2 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .icon-shape .label rect,#mermaid-svg-sgP1p1Ypc9uxtYS2 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-sgP1p1Ypc9uxtYS2 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-sgP1p1Ypc9uxtYS2 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-sgP1p1Ypc9uxtYS2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} RAG 2.0 三大核心能力
检索决策
结果验证
知识自更新
是否需要检索?
如何改写查询?
用哪种检索策略?
结果是否相关?
信息是否充分?
来源是否可信?
发现知识缺口
主动搜索补充
写入知识库

接下来,我们将逐一深入这三个核心能力的设计与实现。


二、RAG 2.0 的核心变化:从被动检索到主动知识治理

2.1 架构对比:RAG 1.0 vs RAG 2.0

在深入具体能力之前,让我们先从架构层面理解 RAG 2.0 的整体设计思路。下表对比了两个版本的核心差异:

维度 RAG 1.0 RAG 2.0
检索触发 每次查询都检索 Agent 自主判断是否需要检索
查询处理 原始查询直接向量化 多策略查询改写(分解、扩展、重写)
检索策略 单一向量检索 向量+关键词+知识图谱混合检索
结果处理 直接拼接 Top-K 相关性评分 + 交叉验证 + 去重过滤
知识更新 离线批量更新 在线增量更新 + 缺口检测
迭代能力 单轮检索 多轮迭代检索,直到信息充分
决策主体 Pipeline 预设规则 Agent 自主决策
失败处理 直接返回不充分结果 触发补充检索或标记不确定

2.2 RAG 2.0 的整体架构

下面是 RAG 2.0 系统的整体架构图,展示了各组件如何协同工作:
#mermaid-svg-ajBImuiQfk9psF3t{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ajBImuiQfk9psF3t .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ajBImuiQfk9psF3t .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ajBImuiQfk9psF3t .error-icon{fill:#552222;}#mermaid-svg-ajBImuiQfk9psF3t .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ajBImuiQfk9psF3t .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ajBImuiQfk9psF3t .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ajBImuiQfk9psF3t .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ajBImuiQfk9psF3t .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ajBImuiQfk9psF3t .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ajBImuiQfk9psF3t .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ajBImuiQfk9psF3t .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ajBImuiQfk9psF3t .marker.cross{stroke:#333333;}#mermaid-svg-ajBImuiQfk9psF3t svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ajBImuiQfk9psF3t p{margin:0;}#mermaid-svg-ajBImuiQfk9psF3t .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ajBImuiQfk9psF3t .cluster-label text{fill:#333;}#mermaid-svg-ajBImuiQfk9psF3t .cluster-label span{color:#333;}#mermaid-svg-ajBImuiQfk9psF3t .cluster-label span p{background-color:transparent;}#mermaid-svg-ajBImuiQfk9psF3t .label text,#mermaid-svg-ajBImuiQfk9psF3t span{fill:#333;color:#333;}#mermaid-svg-ajBImuiQfk9psF3t .node rect,#mermaid-svg-ajBImuiQfk9psF3t .node circle,#mermaid-svg-ajBImuiQfk9psF3t .node ellipse,#mermaid-svg-ajBImuiQfk9psF3t .node polygon,#mermaid-svg-ajBImuiQfk9psF3t .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ajBImuiQfk9psF3t .rough-node .label text,#mermaid-svg-ajBImuiQfk9psF3t .node .label text,#mermaid-svg-ajBImuiQfk9psF3t .image-shape .label,#mermaid-svg-ajBImuiQfk9psF3t .icon-shape .label{text-anchor:middle;}#mermaid-svg-ajBImuiQfk9psF3t .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ajBImuiQfk9psF3t .rough-node .label,#mermaid-svg-ajBImuiQfk9psF3t .node .label,#mermaid-svg-ajBImuiQfk9psF3t .image-shape .label,#mermaid-svg-ajBImuiQfk9psF3t .icon-shape .label{text-align:center;}#mermaid-svg-ajBImuiQfk9psF3t .node.clickable{cursor:pointer;}#mermaid-svg-ajBImuiQfk9psF3t .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ajBImuiQfk9psF3t .arrowheadPath{fill:#333333;}#mermaid-svg-ajBImuiQfk9psF3t .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ajBImuiQfk9psF3t .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ajBImuiQfk9psF3t .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ajBImuiQfk9psF3t .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ajBImuiQfk9psF3t .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ajBImuiQfk9psF3t .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ajBImuiQfk9psF3t .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ajBImuiQfk9psF3t .cluster text{fill:#333;}#mermaid-svg-ajBImuiQfk9psF3t .cluster span{color:#333;}#mermaid-svg-ajBImuiQfk9psF3t div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ajBImuiQfk9psF3t .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ajBImuiQfk9psF3t rect.text{fill:none;stroke-width:0;}#mermaid-svg-ajBImuiQfk9psF3t .icon-shape,#mermaid-svg-ajBImuiQfk9psF3t .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ajBImuiQfk9psF3t .icon-shape p,#mermaid-svg-ajBImuiQfk9psF3t .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ajBImuiQfk9psF3t .icon-shape .label rect,#mermaid-svg-ajBImuiQfk9psF3t .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ajBImuiQfk9psF3t .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ajBImuiQfk9psF3t .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ajBImuiQfk9psF3t :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Knowledge Update Layer
Validation Layer
Retrieval Layer
Agent Core
需要检索
不需要检索
不充分
不充分
不充分
发现缺口
充分
用户查询输入
检索决策器
查询改写引擎
LLM 直接生成
混合检索策略选择
向量检索
关键词检索
图谱检索
结果合并与去重
结果验证器
相关性评分
充分性判断
可信度评估
知识缺口检测
Web 搜索补充
内容验证与清洗
知识库写入
最终回答输出

这个架构相比 RAG 1.0 最大的变化是引入了三个智能决策层:

  1. 检索决策层(蓝色):判断是否需要检索,选择检索策略
  2. 结果验证层(绿色):评估检索结果质量,决定是否需要补充检索
  3. 知识更新层(橙色):检测知识缺口,主动补充知识

2.3 核心设计原则

RAG 2.0 的设计遵循三个核心原则:

原则一:检索是有成本的决策

每次检索都消耗计算资源和时间,Agent 需要像人类一样判断"这个问题我需要查资料吗?"。对于 LLM 已有的知识(如通识知识),不需要检索;对于需要最新信息或私有知识的问题,才触发检索。

原则二:结果是需要验证的

检索到的文档不等于可信的知识。Agent 需要评估检索结果的相关性、充分性和可信度,就像人类在查阅资料后会判断资料质量一样。

原则三:知识是动态的

知识库不是一次构建就永远有效的。Agent 需要在使用过程中发现知识缺口,主动补充新知识,保持知识库的时效性和完整性。


三、检索决策:Agent 自主判断是否需要检索

3.1 检索决策的三层判断框架

检索决策是 RAG 2.0 的入口环节。Agent 接收到用户查询后,需要通过三层判断来决定检索策略:

第一层:是否需要检索?

这一层判断用户的问题是否需要外部知识。决策依据包括:

  • 问题是否涉及事实性信息(需要检索)vs 推理/创意类任务(不需要检索)
  • 问题是否涉及最新信息(必须检索)vs 历史通识(可能不需要)
  • 问题是否涉及私有知识库中的内容(需要检索)

第二层:如何改写查询?

如果需要检索,原始用户查询通常不适合直接作为检索查询。常见的改写策略包括:

  • 查询分解:将复杂问题拆解为多个子问题
  • 查询扩展:添加相关术语和同义词
  • 查询重写:将口语化表述转为更精确的检索语言

第三层:用哪种检索策略?

不同的问题类型适合不同的检索方法:

  • 事实型问题 → 关键词检索更精准
  • 语义型问题 → 向量检索更灵活
  • 关系型问题 → 知识图谱检索更高效

3.2 检索决策器的实现

下面是一个基于 LangGraph 的检索决策器实现:

python 复制代码
from pydantic import BaseModel, Field
from typing import Literal, Optional
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

class RetrievalDecision(BaseModel):
    """检索决策结果"""
    need_retrieval: bool = Field(
        description="是否需要执行检索"
    )
    query_strategy: Literal["direct", "decompose", "expand", "rewrite"] = Field(
        description="查询改写策略"
    )
    retrieval_method: Literal["vector", "keyword", "hybrid", "graph"] = Field(
        description="检索方法选择"
    )
    sub_queries: Optional[list[str]] = Field(
        default=None,
        description="分解后的子查询列表(仅当策略为decompose时使用)"
    )
    reasoning: str = Field(
        description="决策推理过程"
    )

def create_retrieval_decision_chain():
    """创建检索决策链"""
    
    DECISION_PROMPT = ChatPromptTemplate.from_messages([
        ("system", """你是一个检索决策专家。分析用户查询,决定:
1. 是否需要检索(涉及事实/最新信息/私有知识 → 需要;纯推理/创意 → 不需要)
2. 查询改写策略:
   - direct: 查询已足够精确,直接检索
   - decompose: 复杂问题需分解为子问题
   - expand: 需要扩展同义词和相关术语
   - rewrite: 口语化表述需要重写为精确查询
3. 检索方法:
   - vector: 语义相似度检索(适合概念性问题)
   - keyword: 关键词检索(适合精确事实查询)
   - hybrid: 向量+关键词混合(适合复杂问题)
   - graph: 知识图谱检索(适合关系型问题)

决策原则:
- 对于LLM已有充分知识的通识问题,不需要检索
- 对于需要最新数据的问题,必须检索
- 对于多条件复杂问题,优先decompose策略
- 检索是有成本的,避免不必要的检索"""),
        ("human", "用户查询: {query}\n\n可用知识库主题: {kb_topics}\n\n当前时间: {current_time}")
    ])
    
    llm = ChatOpenAI(model="gpt-4o", temperature=0)
    return DECISION_PROMPT | llm.with_structured_output(RetrievalDecision)

代码说明 :这段代码定义了一个结构化的检索决策器。RetrievalDecision 类使用 Pydantic 明确了决策输出的四个维度:是否检索、查询策略、检索方法和子查询列表。create_retrieval_decision_chain 函数构造了一个 LLM 决策链,通过精心设计的 System Prompt 让 LLM 扮演检索决策专家的角色,根据查询类型、知识库主题和当前时间综合判断。使用 with_structured_output 确保输出严格遵循结构化格式,避免后续解析错误。温度设为 0 保证决策的确定性。

3.3 查询改写引擎

查询改写是检索质量的关键影响因素。下面实现一个支持多种改写策略的引擎:

python 复制代码
from langchain_core.output_parsers import StrOutputParser

class QueryRewriteEngine:
    """查询改写引擎,支持分解、扩展、重写三种策略"""
    
    def __init__(self, llm: ChatOpenAI):
        self.llm = llm
    
    async def rewrite(self, query: str, strategy: str) -> list[str]:
        """根据策略改写查询"""
        
        if strategy == "direct":
            return [query]
        
        elif strategy == "decompose":
            # 将复杂问题分解为子问题
            prompt = ChatPromptTemplate.from_messages([
                ("system", """将用户的复杂问题分解为2-4个独立的子问题。
每个子问题应该:
- 可以独立检索和回答
- 覆盖原问题的不同方面
- 合并后能完整回答原问题
只输出子问题列表,每行一个,不加编号或其他格式。"""),
                ("human", "原始问题: {query}")
            ])
            chain = prompt | self.llm | StrOutputParser()
            result = await chain.ainvoke({"query": query})
            return [q.strip() for q in result.strip().split("\n") if q.strip()]
        
        elif strategy == "expand":
            # 扩展查询术语
            prompt = ChatPromptTemplate.from_messages([
                ("system", """为用户的查询生成3个扩展版本:
1. 添加相关技术术语
2. 使用同义词替换
3. 调整表述角度
每个扩展查询独占一行,不加编号。原始查询的语义必须保持不变。"""),
                ("human", "原始查询: {query}")
            ])
            chain = prompt | self.llm | StrOutputParser()
            result = await chain.ainvoke({"query": query})
            queries = [q.strip() for q in result.strip().split("\n") if q.strip()]
            return [query] + queries  # 包含原始查询
        
        elif strategy == "rewrite":
            # 重写为更精确的检索查询
            prompt = ChatPromptTemplate.from_messages([
                ("system", """将用户的口语化查询重写为精确的检索查询。
要求:
- 去除无关的修饰词
- 使用更专业/准确的术语
- 保持核心意图不变
只输出重写后的查询,不加解释。"""),
                ("human", "原始查询: {query}")
            ])
            chain = prompt | self.llm | StrOutputParser()
            result = await chain.ainvoke({"query": query})
            return [result.strip()]
        
        return [query]

代码说明 :QueryRewriteEngine 实现了四种查询处理策略。decompose(分解)策略使用 LLM 将复杂的多条件问题拆分为可独立检索的子问题,例如 "对比 A 和 B 在 C 场景下的性能" 会被拆为 "A 在 C 场景下的性能" 和 "B 在 C 场景下的性能" 两个子查询。expand(扩展)策略生成多个语义等价但表述不同的查询变体,提高检索召回率。rewrite(重写)策略将口语化表述转为更精确的检索语言。每种策略都使用独立的 Prompt 模板,便于单独优化。

3.4 检索决策的完整流程

将决策器和改写引擎组合,我们得到完整的检索决策流程:

python 复制代码
import asyncio
from dataclasses import dataclass

@dataclass
class RetrievalPlan:
    """检索计划"""
    original_query: str
    need_retrieval: bool
    search_queries: list[str]
    retrieval_method: str
    reasoning: str

class RetrievalDecisionMaker:
    """检索决策器:整合决策和查询改写"""
    
    def __init__(self, llm: ChatOpenAI, kb_topics: list[str]):
        self.decision_chain = create_retrieval_decision_chain()
        self.rewrite_engine = QueryRewriteEngine(llm)
        self.kb_topics = kb_topics
    
    async def make_decision(self, query: str) -> RetrievalPlan:
        """完整的检索决策流程"""
        
        # 步骤1: 判断是否需要检索及选择策略
        from datetime import datetime
        decision = await self.decision_chain.ainvoke({
            "query": query,
            "kb_topics": ", ".join(self.kb_topics),
            "current_time": datetime.now().strftime("%Y-%m-%d")
        })
        
        # 如果不需要检索,直接返回
        if not decision.need_retrieval:
            return RetrievalPlan(
                original_query=query,
                need_retrieval=False,
                search_queries=[],
                retrieval_method="none",
                reasoning=decision.reasoning
            )
        
        # 步骤2: 执行查询改写
        if decision.query_strategy == "decompose" and decision.sub_queries:
            search_queries = decision.sub_queries
        else:
            search_queries = await self.rewrite_engine.rewrite(
                query, decision.query_strategy
            )
        
        return RetrievalPlan(
            original_query=query,
            need_retrieval=True,
            search_queries=search_queries,
            retrieval_method=decision.retrieval_method,
            reasoning=decision.reasoning
        )

代码说明 :RetrievalDecisionMaker 将决策器和改写引擎串联为一个完整的检索决策流程。它首先调用结构化决策链判断是否需要检索及选择策略,然后根据策略类型执行对应的查询改写。RetrievalPlan 数据类封装了完整的检索计划,包括原始查询、是否检索、改写后的查询列表、检索方法和推理过程。这个设计使得检索决策完全可解释、可追溯,便于调试和优化。


四、结果验证:如何判断检索结果是否可信、是否充分

4.1 结果验证的三维评估框架

检索决策之后,Agent 执行检索获得候选文档。但这些文档不能直接使用------RAG 2.0 要求 Agent 对检索结果进行三维评估:

维度一:相关性评分(Relevance Score)

评估每个检索到的文档与原始查询的相关程度。这一步不仅看向量相似度,还要看语义层面的实际相关性。向量相似度高不代表相关------用户问"Python 的 GIL"可能检索到"Python 蛇的饲养",向量相似度高但语义完全无关。

维度二:信息充分性(Sufficiency)

评估所有相关文档合在一起,是否提供了回答用户问题所需的全部信息。三个文档可能都高度相关,但如果它们都在说同一件事,信息并不充分。充分性要求信息的覆盖面和深度。

维度三:来源可信度(Credibility)

评估检索结果的来源可信度。官方文档 > 权威技术博客 > 个人博客 > 匿名论坛。对于关键决策类问题,来源可信度的权重应该更高。

4.2 相关性评分器实现

python 复制代码
from langchain_core.documents import Document
import numpy as np

class RelevanceScorer:
    """相关性评分器:评估检索文档与查询的相关性"""
    
    def __init__(self, llm: ChatOpenAI):
        self.llm = llm
    
    async def score_documents(
        self, 
        query: str, 
        documents: list[Document],
        threshold: float = 0.6
    ) -> list[tuple[Document, float, str]]:
        """
        对检索到的文档进行相关性评分
        返回: [(document, score, reason), ...]
        """
        
        SCORING_PROMPT = """你是一个文档相关性评估专家。
        
用户查询: {query}

待评估文档:
{documents}

对每个文档评分(0.0-1.0):
- 1.0: 完全相关,文档直接回答了查询
- 0.8: 高度相关,文档提供了回答查询的关键信息
- 0.5: 部分相关,文档提供了一些有用信息但不充分
- 0.2: 弱相关,文档提到了相关话题但无实质帮助
- 0.0: 不相关,文档与查询无关

同时给出评分理由。

以JSON数组格式输出,每个元素: {{"score": 0.0, "reason": "评分理由"}}"""
        
        # 格式化文档列表
        doc_text = "\n\n".join([
            f"文档{i+1}:\n{doc.page_content[:500]}"
            for i, doc in enumerate(documents)
        ])
        
        from langchain_core.prompts import ChatPromptTemplate
        prompt = ChatPromptTemplate.from_messages([
            ("system", SCORING_PROMPT),
            ("human", "请评估上述文档与查询的相关性。")
        ])
        
        chain = prompt | self.llm
        
        result = await chain.ainvoke({
            "query": query,
            "documents": doc_text
        })
        
        # 解析LLM评分结果
        import json
        try:
            scores = json.loads(result.content)
        except json.JSONDecodeError:
            # 降级处理:使用向量相似度
            scores = [{"score": 0.5, "reason": "LLM解析失败,使用默认分数"}] * len(documents)
        
        scored_docs = []
        for i, doc in enumerate(documents):
            score = float(scores[i]["score"]) if i < len(scores) else 0.0
            reason = scores[i].get("reason", "无理由") if i < len(scores) else "未知"
            scored_docs.append((doc, score, reason))
        
        # 过滤低分文档
        return [
            (doc, score, reason) 
            for doc, score, reason in scored_docs 
            if score >= threshold
        ]

代码说明 :RelevanceScorer 使用 LLM 对检索文档进行细粒度的相关性评分,远比单纯的向量余弦相似度准确。评分采用五级标准(1.0/0.8/0.5/0.2/0.0),每个级别有明确定义。threshold 参数控制过滤阈值,默认 0.6 会过滤掉弱相关和不相关的文档。JSON 解析失败时提供了降级策略,保证系统健壮性。返回三元组(文档、分数、理由),既可用于后续处理,也便于调试分析。

4.3 信息充分性判断

相关性评分只是第一步,我们还需要判断所有相关文档合在一起是否充分:

python 复制代码
class SufficiencyChecker:
    """信息充分性判断器"""
    
    def __init__(self, llm: ChatOpenAI):
        self.llm = llm
    
    async def check_sufficiency(
        self,
        query: str,
        documents: list[tuple[Document, float, str]],
        max_rounds: int = 3
    ) -> dict:
        """
        判断检索结果是否足以回答用户查询
        返回: {
            "sufficient": bool,
            "missing_aspects": list[str],
            "suggested_queries": list[str],
            "reasoning": str
        }
        """
        
        SUFFICIENCY_PROMPT = """你是一个信息充分性评估专家。
        
原始查询: {query}

已有文档摘要:
{doc_summaries}

判断已有信息是否足以完整回答查询。

评估维度:
1. 信息覆盖面: 是否覆盖了查询的所有方面?
2. 信息深度: 信息是否足够具体和详细?
3. 信息一致性: 不同文档之间的信息是否矛盾?
4. 信息时效性: 信息是否过时?

如果信息不充分,请明确指出:
- 缺失了哪些具体方面
- 建议用什么查询来补充检索"""

        from langchain_core.prompts import ChatPromptTemplate
        from pydantic import BaseModel, Field
        
        class SufficiencyResult(BaseModel):
            sufficient: bool = Field(description="信息是否充分")
            missing_aspects: list[str] = Field(
                default_factory=list,
                description="缺失的方面"
            )
            suggested_queries: list[str] = Field(
                default_factory=list,
                description="建议的补充检索查询"
            )
            reasoning: str = Field(description="推理过程")
        
        prompt = ChatPromptTemplate.from_messages([
            ("system", SUFFICIENCY_PROMPT),
            ("human", "请评估信息充分性。")
        ])
        
        doc_summaries = "\n".join([
            f"- [相关度{score:.1f}] {doc.page_content[:200]}..."
            for doc, score, _ in documents
        ])
        
        chain = prompt | self.llm.with_structured_output(SufficiencyResult)
        result = await chain.ainvoke({
            "query": query,
            "doc_summaries": doc_summaries
        })
        
        return result.model_dump()

代码说明 :SufficiencyChecker 是 RAG 2.0 的核心创新之一。它不仅检查"有没有检索到东西",更深入评估"检索到的东西够不够"。评估覆盖四个维度:覆盖面(是否覆盖所有方面)、深度(是否足够详细)、一致性(文档间是否矛盾)、时效性(信息是否过时)。当判断信息不充分时,它会自动生成缺失方面和补充检索查询建议,为后续的多轮迭代检索提供方向。max_rounds 参数防止无限循环,确保系统在有限轮次内收敛。

4.4 多轮迭代检索

当充分性检查发现信息不足时,RAG 2.0 会自动触发补充检索:

python 复制代码
class IterativeRetriever:
    """多轮迭代检索器:信息不充分时自动补充检索"""
    
    def __init__(
        self,
        vector_store,
        scorer: RelevanceScorer,
        checker: SufficiencyChecker,
        rewrite_engine: QueryRewriteEngine,
        max_rounds: int = 3,
        top_k: int = 5
    ):
        self.vector_store = vector_store
        self.scorer = scorer
        self.checker = checker
        self.rewrite_engine = rewrite_engine
        self.max_rounds = max_rounds
        self.top_k = top_k
    
    async def retrieve_with_validation(
        self,
        query: str,
        initial_queries: list[str]
    ) -> dict:
        """
        带验证的多轮迭代检索
        返回: {
            "documents": [(Document, score, reason)],
            "rounds": int,
            "all_queries_used": list[str],
            "sufficient": bool
        }
        """
        all_documents = []
        all_queries_used = list(initial_queries)
        seen_content_hashes = set()
        scored_docs = []
        
        for round_num in range(self.max_rounds):
            queries = initial_queries if round_num == 0 else supplementary_queries
            
            # 执行检索
            for q in queries:
                docs = await self.vector_store.asimilarity_search(
                    q, k=self.top_k
                )
                for doc in docs:
                    content_hash = hash(doc.page_content[:200])
                    if content_hash not in seen_content_hashes:
                        seen_content_hashes.add(content_hash)
                        all_documents.append(doc)
            
            # 相关性评分过滤
            scored_docs = await self.scorer.score_documents(
                query, all_documents
            )
            
            # 充分性检查
            sufficiency = await self.checker.check_sufficiency(
                query, scored_docs
            )
            
            if sufficiency["sufficient"]:
                return {
                    "documents": scored_docs,
                    "rounds": round_num + 1,
                    "all_queries_used": all_queries_used,
                    "sufficient": True
                }
            
            # 生成补充查询
            supplementary_queries = sufficiency.get("suggested_queries", [])
            if not supplementary_queries:
                supplementary_queries = []
                for sq in sufficiency.get("missing_aspects", []):
                    expanded = await self.rewrite_engine.rewrite(
                        f"{query} {sq}", "expand"
                    )
                    supplementary_queries.extend(expanded)
            
            all_queries_used.extend(supplementary_queries)
        
        return {
            "documents": scored_docs,
            "rounds": self.max_rounds,
            "all_queries_used": all_queries_used,
            "sufficient": False,
            "missing_aspects": sufficiency.get("missing_aspects", [])
        }

代码说明 :IterativeRetriever 实现了 RAG 2.0 的多轮迭代检索闭环。每轮包含四个步骤:检索→评分→过滤→充分性检查。如果充分性检查通过,直接返回结果;如果不通过,根据缺失方面生成补充查询,进入下一轮。使用 seen_content_hashes 集合实现内容级去重,避免同一文档被重复纳入。max_rounds 限制最大迭代次数防止死循环。当达到最大轮次仍未充分时,返回当前最优结果并标记 sufficient: False,上层逻辑可以据此决定是降级回答还是触发知识更新流程。


五、知识自更新:Agent 发现知识缺口时如何主动补充

5.1 知识缺口的四种类型

RAG 2.0 最具革命性的能力是知识自更新------Agent 能够在运行过程中发现自己的知识缺口,并主动补充。知识缺口主要有四种类型:

缺口类型 触发条件 补充方式 示例
事实缺失 检索结果为空或完全不相关 Web 搜索 + 知识库写入 用户问"某框架 v3.0 新特性",知识库无此信息
时效过期 检索结果存在但时间戳过期 Web 搜索 + 更新旧条目 知识库中的 API 文档是 v2.0 版本
覆盖不足 充分性检查未通过,缺失具体方面 定向搜索补充缺失方面 需要对比 A 和 B,但只有 A 的信息
质量不足 检索结果相关但信息浅显 深度搜索 + 替换浅显内容 检索到营销文章而非技术文档

5.2 知识缺口检测器

知识缺口检测发生在两个时机:一是多轮检索仍未充分时,二是 LLM 生成回答时自我评估发现知识不足。下面实现一个知识缺口检测器:

python 复制代码
class KnowledgeGapDetector:
    """知识缺口检测器"""
    
    def __init__(self, llm: ChatOpenAI):
        self.llm = llm
    
    async def detect_gaps(
        self,
        query: str,
        retrieval_result: dict,
        llm_response: str = None
    ) -> dict:
        """
        检测知识缺口
        参数:
            query: 原始用户查询
            retrieval_result: 检索结果(来自IterativeRetriever)
            llm_response: LLM生成的回答(可选,用于后置检测)
        返回: {
            "has_gap": bool,
            "gap_type": str,
            "gap_description": str,
            "suggested_search_queries": list[str],
            "confidence": float
        }
        """
        
        from pydantic import BaseModel, Field
        
        class GapAnalysis(BaseModel):
            has_gap: bool = Field(description="是否存在知识缺口")
            gap_type: str = Field(description="缺口类型: missing/expired/insufficient/shallow")
            gap_description: str = Field(description="缺口具体描述")
            suggested_search_queries: list[str] = Field(
                default_factory=list,
                description="建议的Web搜索查询"
            )
            confidence: float = Field(description="缺口判断的置信度0-1")
        
        DETECT_PROMPT = ChatPromptTemplate.from_messages([
            ("system", """你是知识缺口分析专家。分析以下信息判断是否存在知识缺口:

1. 检索不足型: 多轮检索后信息仍不充分
2. 时效过期型: 检索结果存在但内容明显过时
3. 覆盖不全型: 检索结果只覆盖了部分方面
4. 质量不足型: 检索结果相关但深度不够

判断依据:
- 检索结果是否标记为sufficient=False
- 检索结果中是否有明确的missing_aspects
- LLM回答中是否包含不确定性表述(如"可能"、"据我所知"、"不确定")
- LLM回答中是否缺少具体数据或引用

如果存在缺口,生成2-3个精确的Web搜索查询用于补充知识。"""),
            ("human", """用户查询: {query}

检索结果概要:
- 检索轮次: {rounds}
- 是否充分: {sufficient}
- 文档数量: {doc_count}
- 缺失方面: {missing}

LLM回答:
{response}""")
        ])
        
        chain = DETECT_PROMPT | self.llm.with_structured_output(GapAnalysis)
        result = await chain.ainvoke({
            "query": query,
            "rounds": retrieval_result.get("rounds", 0),
            "sufficient": retrieval_result.get("sufficient", False),
            "doc_count": len(retrieval_result.get("documents", [])),
            "missing": ", ".join(retrieval_result.get("missing_aspects", [])),
            "response": llm_response or "(未生成回答)"
        })
        
        return result.model_dump()

代码说明 :KnowledgeGapDetector 在两个层面检测知识缺口。首先分析检索结果的元数据(是否充分、缺失方面、检索轮次),然后分析 LLM 回答的语言特征(不确定性表述、缺少数据引用等)。gap_type 字段区分四种缺口类型,便于后续采用不同的补充策略。生成的 Web 搜索查询经过精心设计,确保能精准定位缺失信息。confidence 字段量化了缺口判断的可信度,避免过度触发知识更新流程。

5.3 知识补充与写入

检测到知识缺口后,Agent 需要通过 Web 搜索获取新知识,经过验证后写入知识库:

python 复制代码
class KnowledgeUpdater:
    """知识更新器:搜索、验证、写入知识库"""
    
    def __init__(
        self,
        llm: ChatOpenAI,
        vector_store,
        embeddings,
        max_search_results: int = 5
    ):
        self.llm = llm
        self.vector_store = vector_store
        self.embeddings = embeddings
        self.max_search_results = max_search_results
    
    async def update_knowledge(
        self,
        query: str,
        gap_info: dict
    ) -> dict:
        """
        执行知识更新流程
        返回: {
            "updated": bool,
            "new_documents_count": int,
            "sources": list[str],
            "summary": str
        }
        """
        from langchain_community.tools import DuckDuckGoSearchResults
        from langchain_core.documents import Document
        import asyncio
        
        search_queries = gap_info.get("suggested_search_queries", [])
        if not search_queries:
            search_queries = [query]
        
        # 步骤1: Web搜索获取新信息
        search_tool = DuckDuckGoSearchResults(max_results=self.max_search_results)
        all_new_docs = []
        
        for sq in search_queries:
            try:
                results = await asyncio.to_thread(search_tool.invoke, sq)
                for result_item in self._parse_search_results(results):
                    doc = Document(
                        page_content=result_item["content"],
                        metadata={
                            "source": result_item["url"],
                            "title": result_item["title"],
                            "query": sq,
                            "ingested_at": datetime.now().isoformat(),
                            "credibility": "web_search"
                        }
                    )
                    all_new_docs.append(doc)
            except Exception as e:
                continue
        
        if not all_new_docs:
            return {"updated": False, "new_documents_count": 0, "sources": [], "summary": "Web搜索无结果"}
        
        # 步骤2: 内容验证与清洗
        verified_docs = await self._verify_and_clean(query, all_new_docs, gap_info)
        
        if not verified_docs:
            return {"updated": False, "new_documents_count": 0, "sources": [], "summary": "验证未通过"}
        
        # 步骤3: 写入向量知识库
        texts = [doc.page_content for doc in verified_docs]
        metadatas = [doc.metadata for doc in verified_docs]
        
        await self.vector_store.aadd_texts(texts, metadatas=metadatas)
        
        return {
            "updated": True,
            "new_documents_count": len(verified_docs),
            "sources": [doc.metadata["source"] for doc in verified_docs],
            "summary": f"成功补充{len(verified_docs)}篇文档"
        }
    
    async def _verify_and_clean(self, query, docs, gap_info):
        """验证搜索结果质量"""
        VERIFY_PROMPT = ChatPromptTemplate.from_messages([
            ("system", """你是内容质量评估专家。评估搜索结果是否:
1. 与查询相关且能填补知识缺口
2. 信息准确可信(排除营销内容、错误信息)
3. 内容有实质信息量(排除空泛内容)
4. 来源可信(官方文档 > 权威媒体 > 个人博客)
返回JSON: {{"verified": true/false, "reason": "理由"}}"""),
            ("human", "查询: {query}\n缺口: {gap}\n内容: {content}")
        ])
        
        verified = []
        for doc in docs:
            result = await (VERIFY_PROMPT | self.llm).ainvoke({
                "query": query,
                "gap": gap_info.get("gap_description", ""),
                "content": doc.page_content[:1000]
            })
            try:
                assessment = json.loads(result.content)
                if assessment.get("verified", False):
                    doc.metadata["verified"] = True
                    verified.append(doc)
            except:
                doc.metadata["verified"] = "unknown"
                verified.append(doc)
        return verified
    
    def _parse_search_results(self, raw_results):
        """解析搜索结果"""
        import re
        parsed = []
        pattern = r'\[(.*?)\]\((https?://[^)]+)\):\s*(.*?)(?=\n\[|$)'
        matches = re.findall(pattern, raw_results, re.DOTALL)
        for title, url, content in matches:
            parsed.append({"title": title.strip(), "url": url.strip(), "content": content.strip()[:2000]})
        return parsed

代码说明 :KnowledgeUpdater 实现了知识自更新的完整闭环。三个步骤形成流水线:Web 搜索获取候选内容→ LLM 验证内容质量和相关性→ 写入向量知识库。_verify_and_clean 方法是质量守门员,通过 LLM 评估每个搜索结果是否真正能填补知识缺口,排除营销内容和空泛信息。写入知识库时 metadata 中记录了来源、原始查询、摄入时间和验证状态,为后续的知识溯源提供支持。

图:知识缺口检测→Web搜索→内容验证→质量清洗→知识库写入的完整闭环,中心标注文档质量评分和知识库容量计数器

5.4 知识更新策略:避免知识库膨胀

知识自更新不是无限制的------不加控制的知识写入会导致知识库膨胀、质量下降。RAG 2.0 需要配套的知识管理策略:

python 复制代码
class KnowledgeManager:
    """知识库管理器:控制更新策略,防止膨胀"""
    
    def __init__(self, vector_store, max_documents: int = 10000):
        self.vector_store = vector_store
        self.max_documents = max_documents
    
    async def smart_update(self, new_docs, gap_info):
        """智能更新策略"""
        # 策略1: 去重检查
        unique_docs = await self._deduplicate(new_docs)
        
        # 策略2: 容量检查
        current_count = await self._get_document_count()
        if current_count + len(unique_docs) > self.max_documents:
            await self._evict_outdated(
                current_count + len(unique_docs) - self.max_documents
            )
        
        # 策略3: 时效替换
        for doc in unique_docs:
            await self._replace_if_outdated(doc)
        
        return {"added": len(unique_docs), "replaced": 0, "evicted": 0}
    
    async def _deduplicate(self, docs):
        """基于语义相似度的去重"""
        if not docs:
            return []
        unique = [docs[0]]
        for doc in docs[1:]:
            similar = await self.vector_store.asimilarity_search_with_score(
                doc.page_content[:200], k=1
            )
            if similar and similar[0][1] > 0.95:
                continue
            unique.append(doc)
        return unique
    
    async def _replace_if_outdated(self, new_doc):
        """检查是否有旧版本需要替换"""
        similar = await self.vector_store.asimilarity_search_with_score(
            new_doc.page_content[:200], k=3
        )
        # 高度相似但时间更早的文档标记为过期
    
    async def _evict_outdated(self, count):
        """淘汰最旧的文档"""
        pass  # 实现取决于向量库的具体API
    
    async def _get_document_count(self):
        """获取当前文档总数"""
        pass

代码说明 :KnowledgeManager 实现了三项知识管理策略。_deduplicate 使用语义相似度检测重复内容,阈值设为 0.95 以捕获几乎相同但表述略有差异的文档。smart_update 方法编排了完整的更新决策流程:先去重、再检查容量、最后做时效替换。max_documents 参数设定知识库上限,防止无限膨胀。_evict_outdated 在知识库超限时淘汰最旧的文档,保持知识库新鲜度。


六、实战:一个 RAG 2.0 系统的完整实现

6.1 系统集成架构

现在我们将前面各个模块整合为一个完整的 RAG 2.0 系统。整体采用 LangGraph 的图编排能力,实现状态流转和流程控制:
#mermaid-svg-soohgCtdwvkV7QGg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-soohgCtdwvkV7QGg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-soohgCtdwvkV7QGg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-soohgCtdwvkV7QGg .error-icon{fill:#552222;}#mermaid-svg-soohgCtdwvkV7QGg .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-soohgCtdwvkV7QGg .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-soohgCtdwvkV7QGg .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-soohgCtdwvkV7QGg .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-soohgCtdwvkV7QGg .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-soohgCtdwvkV7QGg .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-soohgCtdwvkV7QGg .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-soohgCtdwvkV7QGg .marker{fill:#333333;stroke:#333333;}#mermaid-svg-soohgCtdwvkV7QGg .marker.cross{stroke:#333333;}#mermaid-svg-soohgCtdwvkV7QGg svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-soohgCtdwvkV7QGg p{margin:0;}#mermaid-svg-soohgCtdwvkV7QGg defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-soohgCtdwvkV7QGg g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-soohgCtdwvkV7QGg g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-soohgCtdwvkV7QGg g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-soohgCtdwvkV7QGg g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-soohgCtdwvkV7QGg g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-soohgCtdwvkV7QGg .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-soohgCtdwvkV7QGg .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-soohgCtdwvkV7QGg .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-soohgCtdwvkV7QGg .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-soohgCtdwvkV7QGg .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-soohgCtdwvkV7QGg .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-soohgCtdwvkV7QGg .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-soohgCtdwvkV7QGg .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-soohgCtdwvkV7QGg .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-soohgCtdwvkV7QGg .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-soohgCtdwvkV7QGg .edgeLabel .label text{fill:#333;}#mermaid-svg-soohgCtdwvkV7QGg .label div .edgeLabel{color:#333;}#mermaid-svg-soohgCtdwvkV7QGg .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-soohgCtdwvkV7QGg .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-soohgCtdwvkV7QGg .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-soohgCtdwvkV7QGg .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-soohgCtdwvkV7QGg .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-soohgCtdwvkV7QGg .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-soohgCtdwvkV7QGg .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-soohgCtdwvkV7QGg #statediagram-barbEnd{fill:#333333;}#mermaid-svg-soohgCtdwvkV7QGg .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-soohgCtdwvkV7QGg .cluster-label,#mermaid-svg-soohgCtdwvkV7QGg .nodeLabel{color:#131300;}#mermaid-svg-soohgCtdwvkV7QGg .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-soohgCtdwvkV7QGg .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-soohgCtdwvkV7QGg .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-soohgCtdwvkV7QGg .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-soohgCtdwvkV7QGg .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-soohgCtdwvkV7QGg .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-soohgCtdwvkV7QGg .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-soohgCtdwvkV7QGg .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-soohgCtdwvkV7QGg .note-edge{stroke-dasharray:5;}#mermaid-svg-soohgCtdwvkV7QGg .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-soohgCtdwvkV7QGg .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-soohgCtdwvkV7QGg .statediagram-note text{fill:black;}#mermaid-svg-soohgCtdwvkV7QGg .statediagram-note .nodeLabel{color:black;}#mermaid-svg-soohgCtdwvkV7QGg .statediagram .edgeLabel{color:red;}#mermaid-svg-soohgCtdwvkV7QGg #dependencyStart,#mermaid-svg-soohgCtdwvkV7QGg #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-soohgCtdwvkV7QGg .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-soohgCtdwvkV7QGg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户查询
检索决策
不需要检索
需要检索
结果验证
信息充分
信息不充分(轮次小于max)
信息不充分(轮次等于max)
后置缺口检测
后置缺口检测
发现缺口
无缺口
更新后重新检索
更新失败或不可用
返回回答
Init
Decision
DirectAnswer
Retrieve
Validate
Generate
DetectGap
CheckGap
UpdateKnowledge
Finalize

6.2 LangGraph 状态图定义

python 复制代码
from langgraph.graph import StateGraph, END
from typing import TypedDict, Optional, Any

class RAG2State(TypedDict):
    """RAG 2.0 系统状态"""
    query: str
    retrieval_plan: Optional[dict]
    retrieval_result: Optional[dict]
    documents: list[Any]
    llm_response: Optional[str]
    gap_info: Optional[dict]
    update_result: Optional[dict]
    round_count: int
    final_answer: Optional[str]
    metadata: dict

def build_rag2_graph(llm, vector_store, kb_topics, max_total_rounds=5):
    """构建 RAG 2.0 LangGraph 工作流"""
    
    # 初始化各组件
    decision_maker = RetrievalDecisionMaker(llm, kb_topics)
    retriever = IterativeRetriever(
        vector_store=vector_store,
        scorer=RelevanceScorer(llm),
        checker=SufficiencyChecker(llm),
        rewrite_engine=QueryRewriteEngine(llm),
        max_rounds=3
    )
    gap_detector = KnowledgeGapDetector(llm)
    knowledge_updater = KnowledgeUpdater(llm, vector_store, llm)
    
    # 定义节点函数
    async def decision_node(state: RAG2State) -> dict:
        plan = await decision_maker.make_decision(state["query"])
        return {
            "retrieval_plan": {
                "need_retrieval": plan.need_retrieval,
                "queries": plan.search_queries,
                "method": plan.retrieval_method,
                "reasoning": plan.reasoning
            },
            "round_count": state.get("round_count", 0) + 1
        }
    
    async def retrieve_node(state: RAG2State) -> dict:
        plan = state["retrieval_plan"]
        result = await retriever.retrieve_with_validation(
            state["query"], plan["queries"]
        )
        return {
            "retrieval_result": result,
            "documents": result.get("documents", [])
        }
    
    async def generate_node(state: RAG2State) -> dict:
        docs = state.get("documents", [])
        context = "\n\n".join([
            d[0].page_content if isinstance(d, tuple) else d.page_content
            for d in docs
        ])
        
        GEN_PROMPT = ChatPromptTemplate.from_messages([
            ("system", """基于以下检索到的上下文回答用户问题。
要求:
1. 严格基于上下文回答,不编造信息
2. 如果上下文不足以完整回答,明确指出不确定的部分
3. 引用信息来源
4. 如果发现可能的知识缺口,在回答末尾标记[KNOWLEDGE_GAP]

上下文:
{context}"""),
            ("human", "{query}")
        ])
        
        chain = GEN_PROMPT | llm
        result = await chain.ainvoke({"context": context, "query": state["query"]})
        return {"llm_response": result.content}
    
    async def check_gap_node(state: RAG2State) -> dict:
        response = state.get("llm_response", "")
        retrieval_result = state.get("retrieval_result", {})
        has_gap_marker = "[KNOWLEDGE_GAP]" in response
        
        gap_info = await gap_detector.detect_gaps(
            state["query"], retrieval_result,
            response.replace("[KNOWLEDGE_GAP]", "")
        )
        
        if has_gap_marker or gap_info.get("has_gap", False):
            return {"gap_info": gap_info}
        else:
            return {
                "gap_info": {"has_gap": False},
                "final_answer": response.replace("[KNOWLEDGE_GAP]", "")
            }
    
    async def update_knowledge_node(state: RAG2State) -> dict:
        gap_info = state.get("gap_info", {})
        if state.get("round_count", 0) >= max_total_rounds:
            return {
                "update_result": {"updated": False, "reason": "达到最大轮次"},
                "final_answer": state.get("llm_response", "").replace("[KNOWLEDGE_GAP]", "")
            }
        
        result = await knowledge_updater.update_knowledge(state["query"], gap_info)
        return {"update_result": result, "round_count": state.get("round_count", 0) + 1}
    
    async def direct_answer_node(state: RAG2State) -> dict:
        DIRECT_PROMPT = ChatPromptTemplate.from_messages([
            ("system", "直接回答用户问题。如果不确定,明确标注[KNOWLEDGE_GAP]。"),
            ("human", "{query}")
        ])
        chain = DIRECT_PROMPT | llm
        result = await chain.ainvoke({"query": state["query"]})
        return {"llm_response": result.content}
    
    # 路由函数
    def route_decision(state):
        plan = state.get("retrieval_plan", {})
        if plan.get("need_retrieval", True):
            return "retrieve"
        return "direct_answer"
    
    def route_gap_check(state):
        gap_info = state.get("gap_info", {})
        if gap_info.get("has_gap", False) and state.get("round_count", 0) < max_total_rounds:
            return "update_knowledge"
        return "finalize"
    
    def route_after_update(state):
        result = state.get("update_result", {})
        if result.get("updated", False):
            return "retrieve"
        return "finalize"
    
    # 构建图
    workflow = StateGraph(RAG2State)
    workflow.add_node("decision", decision_node)
    workflow.add_node("retrieve", retrieve_node)
    workflow.add_node("generate", generate_node)
    workflow.add_node("direct_answer", direct_answer_node)
    workflow.add_node("check_gap", check_gap_node)
    workflow.add_node("update_knowledge", update_knowledge_node)
    
    workflow.set_entry_point("decision")
    workflow.add_conditional_edges("decision", route_decision, {
        "retrieve": "retrieve", "direct_answer": "direct_answer"
    })
    workflow.add_edge("retrieve", "generate")
    workflow.add_edge("generate", "check_gap")
    workflow.add_edge("direct_answer", "check_gap")
    workflow.add_conditional_edges("check_gap", route_gap_check, {
        "update_knowledge": "update_knowledge", "finalize": END
    })
    workflow.add_conditional_edges("update_knowledge", route_after_update, {
        "retrieve": "retrieve", "finalize": END
    })
    
    return workflow.compile()

代码说明 :这段代码是 RAG 2.0 系统的核心编排层,使用 LangGraph 的 StateGraph 构建了一个有状态的循环工作流。RAG2State 定义了系统在全流程中需要携带的所有状态字段。六个节点函数分别对应架构中的六个阶段:决策、检索、生成、直接回答、缺口检查、知识更新。三个路由函数实现了条件跳转:route_decision 根据是否需要检索分流,route_gap_check 根据是否发现缺口决定是否更新知识,route_after_update 根据更新结果决定是否重新检索。max_total_rounds 限制整个系统的最大循环次数,防止无限循环。这个设计实现了 RAG 2.0 的核心特性:Agent 在回答过程中可以自主判断知识缺口、主动补充知识、并在补充后重新检索------形成完整的知识治理闭环。

6.3 系统启动与调用

python 复制代码
async def main():
    """RAG 2.0 系统启动入口"""
    from langchain_openai import ChatOpenAI, OpenAIEmbeddings
    from langchain_community.vectorstores import FAISS
    
    llm = ChatOpenAI(model="gpt-4o", temperature=0)
    embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
    
    # 加载或创建向量知识库
    import os
    if os.path.exists("knowledge_base"):
        vector_store = FAISS.load_local("knowledge_base", embeddings)
    else:
        vector_store = FAISS.from_texts(["初始化文档"], embeddings)
    
    # 知识库主题(用于检索决策)
    kb_topics = [
        "AI Agent 架构设计",
        "LangChain/LangGraph 框架",
        "RAG 技术与最佳实践",
        "向量数据库与检索"
    ]
    
    # 构建 RAG 2.0 工作流
    rag2_app = build_rag2_graph(llm, vector_store, kb_topics)
    
    # 执行查询
    user_query = "LangGraph 0.2 中的 interrupt 功能怎么用?和 channel 有什么区别?"
    
    result = await rag2_app.ainvoke({
        "query": user_query,
        "round_count": 0,
        "metadata": {}
    })
    
    print(f"查询: {user_query}")
    print(f"检索轮次: {result.get('round_count', 0)}")
    print(f"知识更新: {result.get('update_result', {})}")
    print(f"\n回答:\n{result.get('final_answer', result.get('llm_response', '无回答'))}")
    
    # 保存更新后的知识库
    vector_store.save_local("knowledge_base")

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())

代码说明 :main 函数展示了 RAG 2.0 系统的完整调用流程。系统初始化时加载或创建 FAISS 向量库,配置知识库主题(帮助检索决策器判断哪些问题应该检索),然后构建 LangGraph 工作流。用户查询提交后,系统自主完成决策→检索→验证→生成→缺口检测→知识更新的全流程,最终返回回答和元数据。末尾的知识库保存确保新写入的知识持久化,在下一次查询时可被检索到。这个设计使得知识库随着使用不断丰富,形成越用越智能的正向循环。

6.4 运行效果分析

以查询 "LangGraph 0.2 中的 interrupt 功能怎么用?" 为例,系统典型运行轨迹如下:

  1. 检索决策:判断需要检索(LangGraph 0.2 是较新功能,LLM 训练数据可能不覆盖),策略选择 decompose,分解为两个子查询
  2. 第一轮检索:向量库中无相关文档,检索结果为空
  3. 充分性检查:信息严重不足,missing_aspects 标记多个缺失方面
  4. 第二轮补充检索:使用扩展查询仍然无结果
  5. 知识缺口检测:gap_type = "missing",生成 Web 搜索查询
  6. 知识更新:Web 搜索获取 LangGraph 官方文档,验证通过后写入向量库
  7. 第三轮检索:从更新后的知识库中成功检索到相关文档
  8. 生成回答:基于检索到的官方文档生成准确回答,未标记知识缺口
  9. 返回结果:final_answer 包含准确的 interrupt 用法说明

这个轨迹展示了 RAG 2.0 的核心价值:Agent 不仅回答了问题,还在回答过程中主动发现了自己的知识缺口、通过 Web 搜索补充了知识、并将新知识持久化到向量库中------下一次遇到类似问题时可以直接从知识库检索,无需再次 Web 搜索。

6.5 性能优化建议

在实际部署中,RAG 2.0 系统需要注意以下性能优化:

缓存检索决策结果:对于相似的用户查询,可以缓存检索决策结果避免重复调用 LLM。使用语义相似度匹配缓存命中,命中率可达 30-50%。

并行检索:当查询分解生成多个子查询时,可以并行执行向量检索而非串行,将检索延迟降低 40-60%。

异步知识更新:知识更新不一定要在返回回答前完成。可以将缺口信息放入队列,异步执行 Web 搜索和知识库写入,先返回基于现有知识的最佳回答。

分级验证:对低风险场景简化验证流程。例如,当所有检索结果相关度都高于 0.9 时,跳过充分性检查直接生成回答。


七、适用边界与风险提示

7.1 RAG 2.0 的适用场景

RAG 2.0 并非在所有场景下都优于 RAG 1.0,它引入的智能决策和迭代检索带来了更高的回答质量,同时也带来了更大的延迟和成本。以下是明确的适用边界:

推荐使用 RAG 2.0 的场景:

  • 研究型 Agent:需要综合多源信息、分析对比的研究任务
  • 技术文档助手:框架/产品更新频繁,需要知识自更新的场景
  • 智能客服:用户问题多样且复杂,需要多轮检索和验证
  • 代码助手:需要检索最新 API 文档和最佳实践
  • 企业知识管理:知识库需要持续更新和完善的场景

RAG 1.0 仍然足够的场景:

  • FAQ 问答:问题模式固定,知识库更新频率低
  • 简单文档问答:单文档内的事实查询
  • 实时性要求极高的场景:无法承受多轮检索延迟
  • 成本敏感型场景:LLM 调用次数是 RAG 1.0 的 3-5 倍

7.2 风险与应对

风险类型 风险描述 应对策略
风险类型 风险描述 应对策略
--------- --------- ---------
延迟膨胀 多轮检索+验证+知识更新导致响应时间从秒级增长到十秒级 异步知识更新、缓存决策结果、分级验证
LLM 调用成本 检索决策、查询改写、结果验证、缺口检测各需一次 LLM 调用 对简单查询走快速路径(直接检索,跳过决策)、缓存、使用小模型做验证
知识污染 Web 搜索获取的低质量内容写入知识库后影响后续检索 严格的内容验证流程、定期知识库质量审计、可回滚的知识写入
无限循环 检索→验证→补充检索的循环无法收敛 设置 max_rounds 硬限制、监控每轮检索结果增量、无增量时强制终止
幻觉加剧 LLM 在知识缺口检测时可能误判,导致不必要的知识更新 confidence 阈值控制、缺口检测结果的日志审计、人工审核机制

7.3 成本对比分析

RAG 1.0 和 RAG 2.0 在单次查询中的典型成本对比:

成本项 RAG 1.0 RAG 2.0 倍数
LLM 调用次数 1次(生成回答) 3-6次(决策+改写+验证+生成+缺口检测) 3-6x
向量检索次数 1次 3-15次(多轮迭代×多个子查询) 3-15x
端到端延迟 1-3秒 5-20秒 3-7x
回答准确率 60-75% 85-95% +15-20pp
知识时效性 依赖离线更新 自动在线更新 质的飞跃

注:以上数据基于典型场景的经验估算,实际数值取决于具体实现和场景。

7.4 渐进式部署建议

对于希望从 RAG 1.0 迁移到 RAG 2.0 的团队,建议采用渐进式策略:

第一阶段:引入检索决策(1-2周)

只接入检索决策器,判断是否需要检索。对于不需要检索的查询直接走 LLM,节省成本和延迟。这一步投入小,收益明显。

第二阶段:引入查询改写(1周)

接入查询分解和扩展策略,提高复杂查询的检索召回率。不改变验证和更新流程。

第三阶段:引入结果验证(2-3周)

接入相关性评分和充分性检查,实现多轮迭代检索。这一步对回答质量提升最大,但也带来最大的延迟增加。

第四阶段:引入知识自更新(2-4周)

接入知识缺口检测和 Web 搜索补充。这是 RAG 2.0 最复杂的能力,建议在前三个阶段稳定运行后再接入。


八、总结

8.1 核心观点回顾

本文系统阐述了 RAG 2.0 的核心范式转变:从被动向量检索进化为 Agent 自主知识治理。RAG 2.0 的三大核心能力构成了完整的知识治理闭环:

检索决策让 Agent 获得了"是否需要查资料"的判断力。通过三层判断框架(是否需要检索、如何改写查询、选择检索策略),Agent 能够根据查询类型自主选择最优的检索路径,避免了 RAG 1.0 中"逢问必检"的资源浪费和噪声引入。

结果验证让 Agent 获得了"查到的资料够不够用"的判断力。三维评估框架(相关性、充分性、可信度)加上多轮迭代检索机制,确保 Agent 在生成回答前拥有充分且可靠的信息基础,显著降低了幻觉风险。

知识自更新让 Agent 获得了"发现知识盲区并主动学习"的能力。缺口检测→ Web 搜索→ 内容验证→ 知识库写入的闭环,使 Agent 的知识库能够随使用持续丰富和更新,形成越用越智能的正向循环。

8.2 工程化启示

从工程实践角度,RAG 2.0 带来三个重要启示:

第一,RAG 是系统工程而非简单 Pipeline。RAG 1.0 将检索视为一个线性步骤,而 RAG 2.0 将检索视为一个包含决策、执行、验证、更新的子系统。这要求工程师从系统设计的角度思考 RAG 架构,而非仅仅调优单个环节。

第二,Agent 需要元认知能力。检索决策和知识缺口检测本质上是 Agent 的元认知------对自己知识状态的认识和评估。这是从"工具使用者"到"知识管理者"的关键跃迁。

第三,知识管理是持续过程。知识库不是一次性建设的产物,而是持续演化的有机体。RAG 2.0 的知识自更新能力使得知识库能够自适应地满足实际使用需求,而非依赖人工维护。

8.3 展望

RAG 2.0 的未来发展方向包括:多模态知识检索(图像、视频、音频的统一治理)、个性化知识管理(根据用户画像定制检索策略)、联邦知识更新(多 Agent 共享知识更新成果)以及基于强化学习的检索策略优化(从用户反馈中学习最优检索策略)。这些方向将进一步增强 Agent 的知识治理能力,推动 AI Agent 从"能回答问题"向"能管理知识"的根本性转变。


参考资料

  1. Lewis, P. et al. (2020) . "Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks." NeurIPS 2020. RAG 的原始论文,定义了 RAG 1.0 范式。

  2. Gao, Y. et al. (2024) . "Retrieval-Augmented Generation for Large Language Models: A Survey." arXiv:2312.10997. RAG 技术综述,系统梳理了 RAG 的发展脉络和技术分支。

  3. LangGraph Documentation. https://langchain-ai.github.io/langgraph/. LangGraph 官方文档,状态图工作流的 API 参考。

  4. Asai, A. et al. (2023) . "Self-RAG: Learning to Retrieve, Generate, and Critique through Self-Reflection." arXiv:2310.11511. Self-RAG 论文,RAG 2.0 检索决策和结果验证的理论基础。

  5. Jiang, Z. et al. (2023) . "Active Retrieval Augmented Generation." EMNLP 2023. 主动检索论文,提出了 Agent 自主判断检索时机的思想。

  6. Mallen, A. et al. (2023) . "When Not to Trust Language Models: Investigating Effectiveness of Parametric and Non-Parametric Memories." ACL 2023. 研究 LLM 何时需要外部知识的重要论文,为检索决策提供了理论基础。

  7. FAISS Documentation. https://faiss.ai/. Facebook AI Similarity Search 官方文档,向量检索库的 API 和最佳实践。

  8. Pydantic Documentation. https://docs.pydantic.dev/. Pydantic 官方文档,Python 数据验证和结构化输出的 API 参考。

  9. Wang, L. et al. (2024) . "Searching for Best Practices in Retrieval-Augmented Generation." arXiv:2407.01219. RAG 最佳实践综述,涵盖了查询改写、混合检索、结果重排等关键技术。

  10. Edge, D. et al. (2024) . "From Local to Global: A GraphRAG Approach to Query-Focused Summarization." arXiv:2404.16130. 微软 GraphRAG 论文,知识图谱与检索增强的结合方向。

相关推荐
Steve__evetS2 小时前
RAG流程
ai·rag
凉凉的知识库2 小时前
Agent 如何拥有长期记忆:六个主流项目的设计思路对比
开源·llm·agent
Yunovian3 小时前
AI时代,针对模型与应用,浅谈一下各编程语言
开发语言·c++·人工智能·python·ai·rust·ai编程
林伽一3 小时前
每任务成本成为模型选型新标尺,AI基础设施走向“可验证“ | 林伽一 · AI科技日报 | 2026年09月24日
人工智能·科技·ai
AI-Frontiers4 小时前
现代智能体系统的自主迭代能力研究综述
agent
cpolar技术支持4 小时前
服务器上的 Nginx 只想改配置却不敢动?把 Nginx-UI 跑起来,用 MCP 让 AI 助手帮你安全改配置,再用 cpolar 把面板短时开给自己
nginx·ai·cpolar·mcp·nginx-ui
Ticnix4 小时前
RAG 烂大街?烂大街的只是那条流水线——真正的分水岭在这五处
后端·python·agent
Ticnix4 小时前
RAG 检索不准,九成的锅不在向量——不同文件,就该有不同的入库方案
后端·python·agent
吴佳浩5 小时前
Agent 安全红线:越狱防御、间接注入与数据防泄漏实战
人工智能·agent·ai编程