一文讲清 RAG 演进阶段 101
从 Native 检索到自主 Agent,理解 RAG 的完整进化路径。
为什么需要 RAG ?
LLM 很强,但它有三个根本性缺陷,仅靠模型自身无法解决:
缺陷一:知识截止
LLM 的知识来源于训练数据,训练完成的那一刻就是它认知的终点。
缺陷二:幻觉
这是最危险的缺陷。LLM 本质上是在做"概率最合理的下一个词预测",而不是在"回忆事实"。当它不知道答案时,并不会老老实实说"我不知道",而是会生成一段看起来合理但完全错误的内容——这就是幻觉(Hallucination)。
缺陷三:私有知识缺失
企业内部文档、未公开的研究报告、个人笔记——这些 LLM 的训练数据中根本不存在。你不可能让一个通用模型回答"我高考多少分?“种问题。
其实这里还有一些其他问题:
- 上下文窗口限制,LLM 有上下文长度上限(比如 8k/32k),一次性塞不下整本手册、海量合同、全年日志。
- 模型微调成本,微调 Fine-tune 需要大量标注数据,训练算力成本高,每次新增知识都要重新训练,而且容易灾难性遗忘旧知识。
- Token 成本,每次都把一个大文档喂给 LLM,将产生大量的 Token 浪费。
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-large、BAAI/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年" "碳中和是指……"
完整语义单元 前实现碳达峰。 完整语义单元
实现方式:
- 基于句子边界分块(NLTK/spaCy 断句)
- 基于嵌入相似度:相邻句子相似度骤降处即为语义边界
- 基于文档结构:利用标题、段落等自然边界
分层索引 (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 走向智能的成长之地。