Kmoon Blog

一文讲清 RAG 演进阶段 101

约 7.1k 字 15 min

从 Native 检索到自主 Agent,理解 RAG 的完整进化路径。

为什么需要 RAG ?

LLM 很强,但它有三个根本性缺陷,仅靠模型自身无法解决:

缺陷一:知识截止

LLM 的知识来源于训练数据,训练完成的那一刻就是它认知的终点。

缺陷二:幻觉

这是最危险的缺陷。LLM 本质上是在做"概率最合理的下一个词预测",而不是在"回忆事实"。当它不知道答案时,并不会老老实实说"我不知道",而是会生成一段看起来合理但完全错误的内容——这就是幻觉(Hallucination)。

缺陷三:私有知识缺失

企业内部文档、未公开的研究报告、个人笔记——这些 LLM 的训练数据中根本不存在。你不可能让一个通用模型回答"我高考多少分?“种问题。

其实这里还有一些其他问题:

RAG 的解法

RAG(Retrieval-Augmented Generation,检索增强生成)的核心思想极其朴素:

让 LLM 带着参考书开卷考试。

与其寄希望于 LLM “记住"所有知识,不如在它回答之前,先从外部知识库中检索出相关资料,然后把资料连同问题一起喂给 LLM。这样:


RAG 的本质

不管 RAG 怎么演进,核心始终围绕三个阶段:

所有演进,本质上都是对这 3 个阶段的持续优化和重组。理解了这一点,后面的内容就是看每个阶段怎么在这 3 个环节上加码加加加加到厌倦~)。


Stage 0:Naive RAG

RAG 的最小可行产品,理解原理的最佳起点

架构图

各环节详解

1. 索引 (Indexing)

原始文档: "碳达峰是指二氧化碳排放量在特定时间点达到历史最高值,
          之后逐步下降。中国承诺在2030年前实现碳达峰……"

         ↓ 固定大小分块 (chunk_size=500, overlap=50)

Chunk 1: "碳达峰是指二氧化碳排放量在特定时间点达到历史最高值,之后逐步下降。
          中国承诺在2030年前实现碳达峰。碳中和是指通过植树造林、节能减排等形式,
          抵消自身产生的二氧化碳排放,实现正负抵消达到相对'零排放'……"

         ↓ 嵌入 (Embedding)

Vector 1: [0.023, -0.041, 0.078, ..., 0.012]  ← 1024维向量

         ↓ 存储

向量数据库: { id: "chunk_0", vector: [...], text: "...", metadata: {...} }

2. 检索 (Retrieval)

用户问题: "碳达峰的目标年份是什么?"

         ↓ 嵌入

Query Vector: [0.019, -0.038, 0.082, ..., 0.009]

         ↓ 余弦相似度搜索

cos(query, chunk_0)  = 0.92  ← 最相关
cos(query, chunk_15) = 0.71
cos(query, chunk_7)  = 0.68
cos(query, chunk_42) = 0.55
cos(query, chunk_99) = 0.51

         ↓ Top-K (K=5)

返回: [chunk_0, chunk_15, chunk_7, chunk_42, chunk_99]

3. 生成 (Generation)

Prompt 构造:
┌─────────────────────────────────────────────────┐
│ System: 你是一个专业的文档问答助手。               │
│         请根据参考资料回答用户的问题。              │
│                                                 │
│ 参考资料:                                       │
│ [1] 碳达峰是指二氧化碳排放量……                    │
│ [2] 中国承诺2030年前碳达峰……                     │
│                                                 │
│ 用户问题:碳达峰的目标年份是什么?                  │
└─────────────────────────────────────────────────┘

         ↓ LLM 生成

"根据参考资料,碳达峰的目标年份是2030年前。"

Native RAG 的问题

Native RAG 能跑通,但效果往往不尽如人意。问题出在三个环节:

环节 问题 举例
索引 固定分块切断语义 “……中国承诺在2030年前实现碳达【截断】峰。碳中和是指……”
检索 向量相似度≠语义相关性 问"如何降低碳排放”,检索到"碳排放交易价格走势”(字面相似但答非所问)
生成 噪声上下文干扰回答 检索到5个chunk,3个无关,LLM被无关信息带偏

其中检索质量是最大的瓶颈——后续所有演进几乎都在围绕这个问题展开。


Stage 1:Advanced RAG

针对 Native RAG 的每个痛点,逐个击破

Advanced RAG 的核心思路:在检索前、检索后各加一道"工序"

           检索前优化               检索后优化
              ↓                       ↓
问题 → [Pre-Retrieval] → 检索 → [Post-Retrieval] → 生成

检索前优化 (Pre-Retrieval)

检索前优化的核心矛盾:用户的问题,不一定是好的检索查询

查询改写 (Query Rewriting)

用户的问题往往太短、太模糊、或口语化等表述方式不利于检索。

用户问题: "那个政策啥时候开始干的?"
                    ↓ LLM 改写
检索查询: "中国碳达峰碳中和战略目标行动方案 发布时间 实施日期"

改写让查询更具体、更利于语义匹配。

常见改写策略:

策略 做法 适用场景
扩展式改写 补充上下文关键词 问题太短/太模糊
抽取式改写 提取核心语义 问题带口语/冗余
分解式改写 拆成多个子问题 复合问题

查询扩展 (Query Expansion)

单一查询可能召回不全。生成多个不同角度的查询,合并检索结果。

用户问题: "新能源汽车发展趋势"

                    ↓ 查询扩展
查询1: "新能源汽车 产业趋势 市场前景"
查询2: "电动汽车 技术发展方向"
查询3: "新能源车 销量增长 政策支持"

                    ↓ 分别检索,合并去重
结果: [chunk_A, chunk_B, chunk_C, chunk_D, chunk_E, ...]

HyDE (Hypothetical Document Embedding)

一个反直觉但很有效的技巧:先让 LLM 生成一个假设性回答,用这个回答去做检索

用户问题: "碳达峰的目标年份是什么?"
                    ↓ LLM 生成假设回答
假设回答: "中国承诺在2030年前实现碳达峰,
          努力争取2060年前实现碳中和。
          这一目标在2020年联合国大会上提出。"
                    ↓ 用假设回答做嵌入检索

为什么有效? 因为"回答"和"文档"在语义空间中更接近,而"问题"和"文档"之间存在语义鸿沟(问题和答案的表述方式往往很不同)。

检索后优化 (Post-Retrieval)

检索后优化的核心矛盾:检索到的 ≠ 相关的。向量相似度只是近似,总会混入噪声。

重排序 (Reranking)

用更精确(但更慢)的模型对检索结果重新排序。

向量检索结果 (快但粗糙):
  chunk_0  cos=0.45  ← 语义相关
  chunk_7  cos=0.43  ← 语义相关
  chunk_15 cos=0.42  ← 不相关(噪声)
  chunk_3  cos=0.41  ← 语义相关
  chunk_22 cos=0.40  ← 不相关(噪声)

         ↓ Cross-Encoder Reranking (慢但精确)

重排序结果:
  chunk_0  score=0.92  ← 真正相关
  chunk_3  score=0.88  ← 真正相关
  chunk_7  score=0.85  ← 真正相关
  chunk_22 score=0.31  ← 确实不相关
  chunk_15 score=0.27  ← 确实不相关

Bi-Encoder(双塔编码器) vs Cross-Encoder(交叉编码器)

Bi-Encoder (向量检索阶段用):
  问题 → [Encoder] → 向量A ──→ cos(A, B) ──→ 相似度
  文档 → [Encoder] → 向量B ──┘
  ✅ 快:预计算文档向量,检索时只需算问题向量
  ❌ 粗糙:问题和文档独立编码,无法捕捉交互特征

Cross-Encoder (Reranking 阶段用):
  [问题, 文档] → [Encoder] → 相似度
  ✅ 精确:问题和文档联合编码,捕捉交互特征
  ❌ 慢:每个(问题,文档)对都要过一遍模型,无法预计算

常用 Reranking 模型:BAAI/bge-reranker-largeBAAI/bge-reranker-v2-m3

上下文压缩 (Context Compression)

检索到的 chunk 可能很长,只有部分内容相关。压缩去掉无关部分。

原始 chunk (200字):
  "碳达峰是指二氧化碳排放量在特定时间点达到历史最高值,之后逐步下降。
   中国承诺在2030年前实现碳达峰。碳中和是指通过植树造林、节能减排
   等形式,抵消自身产生的二氧化碳排放,实现正负抵消达到相对'零排放'。
   2020年9月,习近平主席在第七十五届联合国大会上宣布了这一目标。"

         ↓ LLM 压缩

压缩后 (60字):
  "中国承诺2030年前碳达峰,2060年前碳中和。2020年联合国大会宣布。"

过滤 (Filtering)

基于元数据或相关性阈值过滤掉明显不相关的结果。

检索结果:
  chunk_0  distance=0.25  → ✅ 保留 (distance < 0.5)
  chunk_7  distance=0.38  → ✅ 保留
  chunk_15 distance=0.67  → ❌ 过滤 (distance > 0.5)
  chunk_3  distance=0.41  → ✅ 保留
  chunk_22 distance=0.71  → ❌ 过滤

索引优化

不只是检索前后加工序,索引本身也可以做得更好。

语义分块

替代固定大小分块,按语义边界切割。

固定分块 (Native  RAG):
  |──── 500字符 ────|──── 500字符 ────|
  "碳达峰是指……中国承│诺在2030年前实现碳达│峰。碳中和是指……"
                       ↑ 语义被切断!

语义分块:
  |── 语义段落1 ──|── 语义段落2 ──|── 语义段落3 ──|
  "碳达峰是指……"   "中国承诺2030年"   "碳中和是指……"
  完整语义单元       前实现碳达峰。     完整语义单元

实现方式:

分层索引 (Hierarchical Index)

长文档的"摘要-细节"双层索引。

文档层:
  Doc_1 摘要: "本文分析中国能源转型路径,包括清洁能源发展、
               电力系统改革、碳市场建设三个方面"
  Doc_2 摘要: "本报告评估证券市场投资风险与机会……"

检索流程:
  Step 1: 问题 → 检索文档摘要 → 定位到 Doc_1
  Step 2: 问题 → 仅在 Doc_1 的 chunk 中检索 → 精确结果

好处:先粗后细,避免跨文档的噪声干扰。


Stage 2:Modular RAG

Advanced RAG 的各种优化策略,自由组合、即插即用

Advanced RAG 是"检索前加这个、检索后加那个",本质上还是固定的线性流水线。模块化 RAG 打破了这个限制——把 RAG 拆成独立模块,按需组合

模块架构

核心模块详解

查询变换模块 (Query Transform)

可互换的查询预处理策略:

# 策略路由:根据问题类型选择不同的变换方式
if question_is_vague(query):
    return QueryRewriter().transform(query)    # 改写
elif question_is_complex(query):
    return QueryDecomposer().transform(query)  # 分解
else:
    return HyDE().transform(query)             # 假设文档

检索模块 (Retrieve Module)

多种检索策略,按场景选择:

检索方式 原理 优势 劣势
向量检索 语义嵌入 + 余弦相似度 语义理解,同义词也能匹配 精确关键词匹配弱
BM25 检索 TF-IDF 变体,词频统计 精确关键词匹配强 无语义理解
混合检索 向量 + BM25 加权融合 两者优势互补 需要调参
知识图谱 实体关系三元组 结构化推理,可解释 构建成本高

混合检索(最常用)的实现:

问题: "碳达峰目标年份"

向量检索结果:                    BM25 检索结果:
  chunk_A  score=0.91             chunk_B  score=8.5
  chunk_C  score=0.85             chunk_A  score=7.2
  chunk_D  score=0.78             chunk_E  score=6.1
  chunk_E  score=0.72             chunk_C  score=5.8

         ↓ Reciprocal Rank Fusion (RRF) 融合

融合结果:
  chunk_A  rrf=0.082  ← 向量高 + BM25 高 = 最优
  chunk_C  rrf=0.074
  chunk_B  rrf=0.068
  chunk_E  rrf=0.065
  chunk_D  rrf=0.048

路由模块 (Router)

根据问题特征,动态选择处理路径:

问题: "什么是碳达峰?"
  → 简单事实型 → 走快速路径:直接向量检索 + 生成

问题: "对比分析三份报告中关于能源转型的异同"
  → 复杂分析型 → 走深度路径:查询分解 + 多路检索 + Rerank + 生成

问题: "2026年最新数据"
  → 时效型 → 走搜索路径:调用搜索引擎 API + 生成

上下文处理模块 (Context Process)

检索结果的后处理流水线:

原始检索结果 (7个chunk)
  → 去重: 移除内容高度重复的chunk (剩余5个)
  → 过滤: 移除相关性低于阈值的chunk (剩余4个)
  → 压缩: LLM压缩每个chunk保留核心信息 (剩余4个)
  → 排序: 按与问题的相关性重新排列
  → 截断: 控制总长度不超过模型上下文窗口

与 Advanced RAG 的区别

维度 Advanced RAG 模块化 RAG
流程 固定线性流水线 可组合的模块网络
策略 写死在代码里 运行时动态选择
扩展 加新功能需改流水线 加新模块即插即用
适配 一套策略应对所有问题 不同问题走不同路径

Stage 3:Agentic RAG

从"流水线"到"智能体" —— RAG 有了自主决策能力

核心范式转变

前三个阶段有一个共同特征:流程是预先写死的。不管检索结果好不好,都按固定流程走完。

Agentic RAG 打破了这个限制 —— 引入 Agent(智能体),让 RAG 有了自主决策能力。

传统 RAG:  输入 → [固定流水线] → 输出
Agentic:   输入 → [Agent 思考→行动→观察→再思考...] → 输出
                ↑ 这是一个循环,不是直线

ReAct 框架

Agentic RAG 的核心推理框架是 ReAct (Reason + Act)

┌─────────────────────────────────────────────────┐
│                   Agent Loop                     │
│                                                 │
│  Thought: 我需要查找碳达峰的具体目标年份          │
│     ↓                                           │
│  Action: search("中国碳达峰目标年份")             │
│     ↓                                           │
│  Observation: 找到了"2030年前实现碳达峰"          │
│     ↓                                           │
│  Thought: 我已经找到了答案,可以回复了            │
│     ↓                                           │
│  Action: respond("碳达峰的目标年份是2030年前")    │
│                                                 │
└─────────────────────────────────────────────────┘

关键是:Agent 可以在观察到结果后,决定下一步做什么。如果检索结果不够好,它会换一种方式重新检索;如果问题需要多个步骤,它会逐步完成。

对比示例

问题:“2024年中国新能源汽车出口量比2023年增长了多少?”

❌ 传统 RAG:
  检索 "2024年中国新能源汽车出口量比2023年增长了多少"
  → 找不到直接对比的数据
  → 答非所问或编造数据

✅ Agentic RAG:
  Thought: 这个问题需要两个数据点,我分别查找

  Action: search("2024年中国新能源汽车出口量")
  Observation: 120.3万辆

  Action: search("2023年中国新能源汽车出口量")
  Observation: 77.4万辆

  Thought: 我有了两个数据点,需要计算增长率
  Action: calculate("(120.3 - 77.4) / 77.4 * 100")
  Observation: 55.4%

  Action: respond("2024年中国新能源汽车出口量为120.3万辆,
          较2023年的77.4万辆增长了55.4%")

Agentic RAG 的核心能力

能力 说明 传统 RAG 有没有
动态规划 根据问题复杂度自主决定检索策略
自我评估 判断检索结果是否足够回答问题
自我修正 检索结果不好时,改写查询重新检索
工具调用 搜索引擎、计算器、数据库、API……
多步推理 将复杂问题拆解为子问题逐步解决
自我反思 生成答案后自我审查,发现矛盾则修正

架构图

自我修正循环

这是 Agentic RAG 最关键的能力之一:

Round 1:
  Query: "碳交易市场的发展情况"
  → 检索到5个chunk
  → 评估: 这些都是碳交易价格的碎片信息,没有整体发展脉络
  → 判断: 检索不够,需要换策略

Round 2:
  Query (改写): "中国碳排放权交易市场 建设 发展历程 政策"
  → 检索到5个新chunk
  → 评估: 找到了市场启动时间、覆盖范围、政策文件
  → 判断: 足够回答问题了

  → 生成答案

Stage 4:Graph RAG

用知识图谱弥补向量检索的"碎片化"缺陷

向量检索的终极痛点

前面所有阶段的检索都基于向量相似度,这种方式有一个根本缺陷:只能找到局部相似的文本片段,无法理解文档的全局结构

问题: "这篇报告的核心论证逻辑是什么?"

向量检索: 返回几个与"论证逻辑"字面相似的片段
实际需要: 理解整篇报告的结构、论点之间的支撑关系

问题: "碳达峰和碳中和的关系是什么?"

向量检索: 分别找到提到"碳达峰"和"碳中和"的片段
实际需要: 理解两者在时间线和逻辑上的因果关系

知识图谱如何解决

Graph RAG 用知识图谱替代纯向量检索,把文档中的实体关系结构化:

Graph RAG 的构建流程

Graph RAG 的检索

Graph RAG 支持两种互补的检索模式

局部查询 (Local Search):
  "碳达峰的目标年份是什么?"
  → 向量检索相关chunk + 图谱扩展相关实体
  → 精确的局部信息

全局查询 (Global Search):
  "这份报告的核心观点是什么?"
  → 检索社区摘要 → 聚合多个社区的全局信息
  → 全局性理解

混合查询:
  "碳达峰对碳中和有什么影响,具体目标是什么?"
  → 局部检索: 找到具体目标数据
  → 全局检索: 找到两者关系
  → 合并生成

与向量 RAG 的对比

向量 RAG:
  文档 → 碎片化的chunk → 向量 → 相似度匹配
  ✅ 擅长: 局部细节问题 ("碳达峰是哪一年?")
  ❌ 弱项: 全局结构问题 ("报告的论证逻辑是什么?")
  ❌ 弱项: 关系推理问题 ("A和B有什么因果关系?")

Graph RAG:
  文档 → 结构化的知识图谱 → 图遍历 + 社区摘要
  ✅ 擅长: 全局结构问题
  ✅ 擅长: 关系推理问题
  ✅ 擅长: 多跳推理 ("A影响B,B影响C,那么A如何影响C?")
  ❌ 弱项: 构建成本高,实体抽取可能遗漏
  ❌ 弱项: 简单事实查询可能不如向量检索直接

全阶段横评

能力对比

能力              Naive   Advanced   Modular   Agentic   Graph
──────────────────────────────────────────────────────────────────
检索策略          单一向量  多策略优化  可插拔     动态选择   向量+图谱
检索质量          ⭐⭐      ⭐⭐⭐⭐     ⭐⭐⭐⭐    ⭐⭐⭐⭐    ⭐⭐⭐⭐⭐
自我修正          ✗        ✗          ✗         ✓         ✓
多步推理          ✗        ✗          部分       ✓         ✓
全局理解          ✗        ✗          ✗         部分       ✓
关系推理          ✗        ✗          ✗         部分       ✓
实现复杂度        ⭐        ⭐⭐        ⭐⭐⭐      ⭐⭐⭐⭐     ⭐⭐⭐⭐⭐
运维成本          低        中         中高       高         很高
适合场景          原型验证  生产可用    企业级     复杂任务   知识密集型

如何选择

你的场景是什么?
├─ 快速验证 RAG 可行性
│  └─→ Naive RAG
├─ 检索效果不够好,需要优化
│  └─→ Advanced RAG (先加 Reranking,性价比最高)
├─ 不同类型的问题需要不同策略
│  └─→ Modular RAG
├─ 需要多步推理、工具调用、自我修正
│  └─→ Agentic RAG
└─ 需要全局理解、关系推理
   └─→ Graph RAG (或 Agentic + Graph 混合)

演进不是替代

重要:每个阶段都是前一阶段的超集,不是替代关系。

Graph RAG ⊃ Agentic RAG ⊃ Modular RAG ⊃ Advanced RAG ⊃ Naive RAG

即使是最先进的 Graph RAG,它的底层仍然包含向量检索和 Reranking。 选择哪个阶段,取决于你的问题复杂度和工程资源。


Nidus 的演进路线图

Github 项目仓库:Nidus

v0.1  Naive RAG
 │     ├ PDF 加载 (pypdf)
 │     ├ 固定分块 (500字符, 50重叠)
 │     ├ 向量嵌入 (SiliconFlow BGE)
 │     ├ ChromaDB 存储
 │     ├ 余弦相似度检索
 │     ├ LLM 生成 (SiliconFlow DeepSeek)
 │     ├ CLI (index / ask / interactive)
 │     └ FastAPI REST API
 ├── v0.2  Advanced RAG
 │     ├ Reranking (bge-reranker)
 │     ├ 查询改写
 │     ├ 语义分块
 │     ├ 混合检索 (向量 + BM25)
 │     └ 上下文压缩
 ├── v0.3  Modular RAG
 │     ├ 可插拔模块架构
 │     ├ 检索路由 (问题分类 → 策略选择)
 │     ├ 多路召回 + RRF 融合
 │     └ 模块配置化
 └── v1.0  Agentic RAG
       ├ ReAct 推理循环
       ├ 多工具编排 (检索/计算/搜索/API)
       ├ 自我评估与纠错
       ├ 子问题分解
       └ 自我反思

每一步都是增量演进,保持模块化和可扩展——这也是 Nidus 名字的含义:

Nidus (拉丁语):巢、摇篮,一个让 RAG 从 Native 走向智能的成长之地。

#RAG #101