§1.2.7

Naive RAG / Advanced RAG / Modular RAG / Agentic RAG 的演进路径?

核心概念

检索增强生成(Retrieval-Augmented Generation, RAG)是一种将大型语言模型(LLM)的生成能力与外部知识库的检索能力相结合的架构。其核心思想是,在生成回答之前,首先从一个大规模的文档集合(如维基百科、公司内部文档)中检索出与用户问题相关的上下文信息,然后将这些信息与原始问题一起提供给 LLM,引导其生成更准确、更具事实性的回答。

RAG 的演进路径反映了业界为解决其固有局限性而进行的持续探索,大致可分为四个阶段:

  1. Naive RAG (朴素 RAG): 最基础的 "检索-增强-生成" 线性流程,是 RAG 的原型实现。
  2. Advanced RAG (高级 RAG): 在朴素 RAG 的核心流程上,针对索引、检索、后处理等环节进行深度优化的技术集合。
  3. Modular RAG (模块化 RAG): 将 RAG 视为一个可插拔、可组合的模块化系统,通过引入功能模块(如查询重写、路由、融合)来构建更灵活、更强大的工作流。
  4. Agentic RAG (智能体 RAG): 将 LLM 从一个被动的生成器转变为一个主动的决策者(Agent)。LLM 主动决定何时检索、检索什么、如何使用检索结果,并能进行反思和多步推理,实现更复杂的任务。

原理与推导

1. Naive RAG (朴素 RAG)

Naive RAG 是一个简单的三步线性流水线。

原理: 给定一个用户问题 xx,RAG 的目标是生成一个答案 yy。朴素 RAG 将这个过程分解为:

  1. 检索 (Retrieve): 使用一个编码器 EDE_D 将文档库 DD 中的所有文档块(chunks)did_i 编码成向量。同时,使用一个查询编码器 EQE_Q 将问题 xx 编码成查询向量 qxq_x。通过计算 qxq_x 与所有 did_i 向量的相似度(通常是余弦相似度),找出 Top-K 个最相关的文档块 Z={d1,d2,...,dK}Z = \{d_1, d_2, ..., d_K\}
  2. 增强 (Augment): 将检索到的文档块 ZZ 与原始问题 xx 合并成一个提示(Prompt)。格式通常为:"根据以下信息:{d_1, d_2, ..., d_K},回答问题:{x}"
  3. 生成 (Generate): 将增强后的提示输入到一个黑盒的 LLM GG 中,生成最终答案 y=G(x,Z)y = G(x, Z)

数学表示: 从概率角度看,标准语言模型的生成概率是 P(yx)P(y|x)。RAG 将其建模为以检索到的文档 ZZ 为条件的生成概率。其核心公式可以看作是边缘化检索到的文档: P(yx)zZP(yx,z)P(zx)P(y|x) \approx \sum_{z \in Z} P(y|x, z) P(z|x) 其中:

  • P(zx)P(z|x) 是检索模型的概率,表示给定问题 xx 时,文档 zz 相关的概率。在实践中,这通常由检索器(如向量相似度)的得分来近似。
  • P(yx,z)P(y|x, z) 是生成模型的概率,表示在给定问题 xx 和上下文 zz 的情况下,生成答案 yy 的概率。

算法流程:

  1. 离线处理 (Indexing):
    • 文档加载 (Load)
    • 文档分块 (Chunking / Splitting)
    • 文档嵌入 (Embedding)
    • 建立向量索引 (Indexing)
  2. 在线处理 (Inference):
    • 用户问题嵌入 (Query Embedding)
    • 向量检索 (Vector Search) -> 获取 Top-K 文档块
    • 构建 Prompt (Augment)
    • LLM 生成 (Generate)

复杂度:

  • 索引: 时间复杂度 O(Nd)O(N \cdot d),其中 NN 是文档块总数, dd 是嵌入维度。空间复杂度 O(Nd)O(N \cdot d)
  • 查询: 对于基于 FAISS 等近似最近邻(ANN)索引的检索,时间复杂度通常为 O(logN)O(\log N)O(N)O(\sqrt{N}),远快于暴力搜索的 O(N)O(N)

2. Advanced RAG (高级 RAG)

高级 RAG 并非一个全新的框架,而是对朴素 RAG 各个环节的优化技术总称,旨在解决其“检索质量不高”和“上下文利用不充分”等问题。

原理与技术:

  • 预处理/索引优化 (Pre-retrieval):

    • 滑动窗口/句子窗口 (Sentence Window): 索引的基本单元是句子,但检索回来后,将该句子及其上下文(前后 N 个句子组成的窗口)一起提供给 LLM。这解决了小块(句子)语义精确但上下文信息不足,与大块上下文完整但语义噪声大的矛盾。
    • 元数据与多索引 (Metadata & Multi-indexing): 为每个文档块附加元数据(如日期、章节、来源),并可以根据元数据进行过滤。或者为同一份文档建立多种索引,例如,一个基于摘要的稀疏索引(如 BM25)和一个基于全文的密集索引(向量)。
  • 检索优化 (Retrieval):

    • 混合搜索 (Hybrid Search): 结合稀疏检索(如 BM25,擅长关键词匹配)和密集检索(向量搜索,擅长语义匹配)的结果。常用 Reciprocal Rank Fusion (RRF) 算法融合排序结果。 RRF-Score(d)=rResults1k+rankr(d)\text{RRF-Score}(d) = \sum_{r \in \text{Results}} \frac{1}{k + \text{rank}_r(d)} 其中 rankr(d)\text{rank}_r(d) 是文档 dd 在检索结果集 rr 中的排名,kk 是一个常数(通常为 60),用于降低高排名文档的极端影响。
    • 查询转换 (Query Transformations): 如果原始问题复杂或模糊,可以先让 LLM 对其进行重写、分解成子问题或扩展。例如,将 "RAG in 2024" 扩展为 "RAG advancements 2024", "new RAG techniques 2024" 等多个查询,分别检索后合并结果。
  • 后处理/生成优化 (Post-retrieval):

    • 重排 (Re-ranking): 初步检索(如向量搜索)返回 Top-K (e.g., K=20) 个文档块,然后使用一个更强大但更慢的模型(如 Cross-Encoder)对这 K 个文档块与问题的相关性进行精细打分,并选出最终的 Top-N (e.g., N=5) 个。Cross-Encoder 将问题和文档块拼接后输入模型,能更好地捕捉细粒度的交互信息。
    • 上下文压缩 (Context Compression): 在将检索到的长上下文送入 LLM 前,先用一个小型 LLM 或特定算法提取其中与问题最相关的句子或摘要,减少无关信息对 LLM 的干扰,并节省 token。

3. Modular RAG (模块化 RAG)

模块化 RAG 将 RAG 系统从一个固定的线性管道,重构为一个由多个可互换、可组合的模块构成的图(Graph)或工作流(Workflow)。

原理: 其核心是引入了控制流和功能性模块。不再是单一的 retrieve -> generate,而是可以根据情况动态选择执行路径。

  • 核心模块:
    • 搜索模块 (Search): 不仅限于向量搜索,可以是多种搜索方式的集合(Web 搜索、SQL 查询、图数据库查询等)。
    • 记忆模块 (Memory): 引入短期记忆(对话历史)或长期记忆(用户画像),用于个性化检索和生成。
    • 路由模块 (Router): 这是一个关键的决策模块。它根据用户的查询,决定下一步应该调用哪个模块。例如,一个问题是关于最新新闻,路由器会将其导向 Web 搜索模块;如果问题是关于内部数据,则导向内部向量数据库。
    • 融合模块 (Fusion): 从不同来源(如 Web 和本地文档)检索到的信息需要被智能地融合和去重。
    • 生成模块 (Generate): 依然是 LLM,但可能根据路由结果使用不同的 Prompt 模板。

示例工作流 (FLARE - Forward-Looking Active REtrieval): FLARE 是一种主动检索的模块化 RAG。

  1. LLM 开始生成答案。
  2. 当遇到低置信度的词(如不确定的实体、事件)时,暂停生成。
  3. 将这些低置信度的词构造成一个新的查询。
  4. 调用搜索模块进行检索。
  5. 将检索到的信息整合后,继续生成,直到完成。 这个过程形成了一个 "生成-检索-生成" 的循环,实现了按需检索。

4. Agentic RAG (智能体 RAG)

这是 RAG 的最高级形态,LLM 自身成为一个具备规划、工具使用和反思能力的智能体(Agent)。RAG 的检索器被视为 Agent 可以使用的一个工具(Tool)。

原理 (ReAct - Reason and Act): ReAct 框架是 Agentic RAG 的典型代表。LLM 在一个 "思考-行动-观察" (Thought-Action-Observation) 的循环中运作。

  1. 思考 (Thought): 面对用户问题,LLM 首先进行内部思考,分析问题,并制定一个行动计划。例如:"用户想比较 A 和 B。我需要先分别查找 A 和 B 的定义,然后再进行比较。"
  2. 行动 (Action): LLM 决定调用一个工具。这个工具可以是 search[query],其中 query 是 LLM 自己生成的。例如,Action: search["definition of RAG"]
  3. 观察 (Observation): LLM 接收到工具执行的结果(即检索到的文档内容)。
  4. 循环/反思: LLM 观察结果,并进行新一轮的思考。 "我已经找到了 RAG 的定义。现在我需要查找 Advanced RAG 的定义。" 于是它再次生成 Action: search["definition of Advanced RAG"]。这个循环会持续进行,直到 LLM 认为收集到了足够的信息来回答最初的问题。
  5. 最终回答: LLM 综合所有步骤中收集到的信息,生成最终的、结构化的答案。

与 Modular RAG 的区别:

  • 控制者: 在 Modular RAG 中,控制流通常是预定义的(即使有分支),由规则或一个小型 "路由" 模型控制。而在 Agentic RAG 中,核心 LLM 本身就是总控制器,它动态地决定整个工作流。
  • 灵活性: Agentic RAG 具有极高的灵活性和适应性,能够处理需要多步推理和动态信息收集的复杂任务,甚至能从失败的工具调用中学习和调整策略(例如,如果一个搜索查询没有结果,它会思考并尝试一个不同的查询)。

代码实现

以下代码将演示一个 Advanced RAG 中的关键技术:句子窗口检索 (Sentence Window Retrieval) + Cross-Encoder 重排。这清晰地展示了如何从 Naive RAG 升级。

python
1import torch
2import numpy as np
3from sentence_transformers import SentenceTransformer, CrossEncoder, util
4
5# 确保有 torch 和 sentence_transformers 库
6# pip install torch sentence-transformers numpy
7
8# --- 1. 数据准备与预处理 (句子窗口) ---
9document = """
10检索增强生成(RAG)是一种结合了大型语言模型和外部知识库的技术。
11其基本思想是在生成回答前,先从文档中检索相关信息。
12朴素RAG流程简单,但存在一些问题,例如上下文窗口与语义单元不匹配。
13为了解决这个问题,高级RAG技术应运而生。
14句子窗口检索是其中一种有效方法。它将句子作为检索的基本单元,保证了语义的精确性。
15在检索到最相关的句子后,系统会扩展其上下文窗口,例如包含该句子的前后句。
16这样既保证了检索的精确度,又为语言模型提供了充足的生成上下文。
17最后,重排模型(Re-ranker)可以进一步优化检索结果的顺序。
18"""
19
20# 将文档分割成句子
21sentences = document.strip().split('\n')
22print(f"文档被分割成 {len(sentences)} 个句子。")
23
24# 定义句子窗口的上下文范围
25window_size = 1 # 每个句子前后各取1个句子作为上下文
26sentence_windows = []
27for i, sentence in enumerate(sentences):
28 # 确定窗口的起始和结束索引
29 start_index = max(0, i - window_size)
30 end_index = min(len(sentences), i + window_size + 1)
31
32 # 组合成窗口文本
33 window = " ".join(sentences[start_index:end_index])
34 sentence_windows.append(window)
35 # print(f"句子 '{sentence}' -> 窗口: '{window}'") # 可取消注释查看细节
36
37# --- 2. 索引与检索 (第一阶段:对称语义搜索) ---
38# 为什么用 Bi-Encoder? -> 用于高效地进行初步检索(召回)。它独立编码查询和文档,速度快,适合大规模数据。
39bi_encoder = SentenceTransformer('moka-ai/m3e-base')
40
41# 为什么只嵌入句子? -> 为了更精确的语义匹配。句子是更小的语义单元,匹配精度更高。
42# 如果嵌入整个窗口,噪声会更大。
43sentence_embeddings = bi_encoder.encode(sentences, convert_to_tensor=True, show_progress_bar=False)
44
45# 用户查询
46query = "如何解决朴素RAG的上下文问题?"
47query_embedding = bi_encoder.encode(query, convert_to_tensor=True)
48
49# 使用余弦相似度进行检索
50# 为什么用余弦相似度? -> 在高维空间中,余弦相似度能有效衡量方向上的一致性,即语义的相似性。
51cos_scores = util.cos_sim(query_embedding, sentence_embeddings)[0]
52top_k = 3 # 初步召回3个最相关的句子
53
54# 找出top_k个最相关的句子索引
55top_results = torch.topk(cos_scores, k=top_k)
56
57print("\n--- 第一阶段检索 (Bi-Encoder) 结果 ---")
58retrieved_sentence_indices = top_results.indices.tolist()
59for i, (score, idx) in enumerate(zip(top_results.values, top_results.indices)):
60 print(f"{i+1}. (分数: {score:.4f}) 句子: {sentences[idx]}")
61
62# --- 3. 上下文扩展 ---
63# 为什么需要扩展上下文? -> 单个句子信息量太少,LLM需要更丰富的上下文来生成流畅、完整的回答。
64# 我们根据第一阶段检索到的句子索引,获取对应的完整窗口。
65expanded_contexts = [sentence_windows[idx] for idx in retrieved_sentence_indices]
66
67print("\n--- 上下文扩展结果 ---")
68for i, context in enumerate(expanded_contexts):
69 print(f"{i+1}. 扩展后的上下文: {context}")
70
71# --- 4. 重排 (第二阶段:Cross-Encoder) ---
72# 为什么需要重排? -> Bi-Encoder 速度快但精度有限。Cross-Encoder 将问题和文档一起输入模型,
73# 能捕捉更细微的交互关系,排序更准,但速度慢,适合对少量候选集进行精排。
74cross_encoder = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
75
76# 准备 cross_encoder 的输入:(查询, 上下文) 对
77cross_inp = [[query, context] for context in expanded_contexts]
78
79# 预测分数
80cross_scores = cross_encoder.predict(cross_inp)
81
82print("\n--- 第二阶段重排 (Cross-Encoder) 结果 ---")
83# 将分数与上下文打包并排序
84ranked_results = sorted(zip(cross_scores, expanded_contexts), key=lambda x: x[0], reverse=True)
85
86for i, (score, context) in enumerate(ranked_results):
87 print(f"{i+1}. (分数: {score:.4f}) 最终上下文: {context}")
88
89print(f"\n最终提供给LLM的Top-1上下文是:\n'{ranked_results[0][1]}'")

工程实践

  • 使用场景:

    • Naive RAG: 适用于快速原型验证、内部知识库问答(文档结构良好、主题明确)、对延迟要求极高的简单场景。
    • Advanced RAG: 绝大多数生产级 RAG 系统的选择。当问答准确性至关重要时,如面向客户的智能客服、金融/法律领域的专业问答系统,必须使用重排、混合搜索等高级技术。
    • Modular RAG: 适用于需要整合多种数据源(如同时查询 PDF、数据库和网页)的复杂问答系统,或需要动态决策流程的应用。例如,一个旅行规划助手可能需要先查询航班信息(结构化数据),再查询目的地介绍(非结构化数据)。
    • Agentic RAG: 适用于需要执行复杂任务、进行多步推理和自主探索的场景。例如,自动化研究助理("帮我调研一下 RAG 技术的最新进展,并总结成报告")、能与外部 API 交互的智能助手。
  • 超参数选择:

    • Chunk Size: 没有万能值。通常在 256-512 token 之间。需要根据文档特性和下游任务进行实验。对于代码等结构化文本,可能需要更大的块。
    • Chunk Overlap: 通常设为 Chunk Size 的 10%-20%,以防止语义在块边界被切断。
    • Retrieval Top-K: 初步检索的 K 值要适中,一般在 10-20 之间。太小可能漏掉真正相关的,太大则会给重排阶段带来过大压力和延迟。
    • Re-ranking Top-N: 最终提供给 LLM 的 N 值,通常在 3-5 之间。太多无关上下文会干扰 LLM,太少则信息不足。
  • 性能 / 显存 / 吞吐 的权衡:

    • 延迟: Naive RAG < Advanced RAG < Modular RAG < Agentic RAG。每增加一个环节(如重排、查询重写、多步调用),延迟都会显著增加。
    • 成本: Agentic RAG 调用 LLM 的次数最多,因此 API 费用或计算成本最高。
    • 优化:
      • 缓存: 对查询、检索结果、甚至生成结果进行缓存是降低延迟和成本的关键。
      • 模型量化: 对 Embedding 模型或 Cross-Encoder 进行量化可以降低显存占用和推理延迟。
      • 异步执行: 在 Modular RAG 中,可以并行执行对不同数据源的查询。
  • 常见坑和调试技巧:

    • "Lost in the Middle": LLM 对长上下文中处于中间部分的信息关注度较低。调试时,可以尝试改变相关文档块在 Prompt 中的顺序,或使用上下文压缩技术。
    • 评估困难: RAG 的评估是端到端的,很难定位是检索错了还是生成错了。需要建立分步评估体系:单独评估检索的召回率/精确率(Retrieval-level metrics),再评估最终答案的忠实度、相关性(Generation-level metrics)。
    • 调试工具: 使用 LangSmith、Arize AI 等可观测性平台,可以可视化 RAG 的每一步(查询、检索到的文档、最终 Prompt),极大地方便了调试。

常见误区与边界情况

  • 误区1: RAG 能完全消除幻觉。

    • 纠正: RAG 极大缓解了幻觉,但不能完全消除。LLM 仍可能忽略提供的上下文、错误地综合信息,或者在上下文信息不足或矛盾时产生幻觉。正确的说法是 RAG 为生成提供了事实依据,降低了模型“凭空捏造”的概率。
  • 误区2: 向量相似度分数高就代表一定相关。

    • 纠正: 不一定。语义相似不等于在特定问题下的“相关”。例如,查询“苹果公司的最新财报”,一个关于“苹果种植技巧”的文档可能在向量空间中距离不远,但对问题毫无用处。这就是为什么需要混合搜索(补充关键词匹配)和重排(更深层次的相关性判断)。
  • 误区3: 只要把所有文档都喂给 RAG 就行了。

    • 纠正: “垃圾进,垃圾出”。文档的质量、清洗和预处理(如去重、修正 OCR 错误、优化分块策略)对 RAG 系统的上限有决定性影响。高质量的知识库是高性能 RAG 的前提。
  • 边界情况与面试追问:

    • 追问: 如果检索不到任何相关文档,系统应该如何响应?
      • 回答要点: 最好的策略是返回一个诚实的回答,如“对不起,我在知识库中没有找到与您问题相关的信息”。这远比基于通用知识胡乱生成一个答案要好。可以在 RAG 流程中设置一个阈值,如果所有检索文档的相似度得分都低于该阈值,则触发此“不知”逻辑。
    • 追问: 如果检索到的多个文档内容相互矛盾,怎么办?
      • 回答要点: 这是一个高级挑战。
        • 简单方法: 将所有矛盾信息都呈现给 LLM,并明确指示它识别并报告这些矛盾。例如,在 Prompt 中加入:“...请注意,以下信息可能存在矛盾,请在回答时指出。”
        • 高级方法 (Agentic): Agentic RAG 可以设计一个反思步骤。当 Agent 发现信息矛盾时,它可以主动进行新一轮的、更具针对性的检索(例如,查找每个信息来源的日期或权威性),然后基于元数据(如更新时间、来源权重)做出判断。
    • 追问: 如何更新 RAG 系统中的知识?
      • 回答要点: 这涉及到索引的更新策略。
        • 全量更新: 定期(如每天/每周)重新对整个文档库进行索引。简单粗暴,但成本高。
        • 增量更新: 只对新增或修改的文档进行嵌入和索引更新。需要维护一个能高效增删改的向量索引(如 FAISS 支持 add_with_idsremove_ids)。这是生产系统中的常用方案。
        • 实时更新: 对于需要秒级更新的场景,需要更复杂的索引架构和数据流管道。
相关题目