Naive RAG / Advanced RAG / Modular RAG / Agentic RAG 的演进路径?
核心概念
检索增强生成(Retrieval-Augmented Generation, RAG)是一种将大型语言模型(LLM)的生成能力与外部知识库的检索能力相结合的架构。其核心思想是,在生成回答之前,首先从一个大规模的文档集合(如维基百科、公司内部文档)中检索出与用户问题相关的上下文信息,然后将这些信息与原始问题一起提供给 LLM,引导其生成更准确、更具事实性的回答。
RAG 的演进路径反映了业界为解决其固有局限性而进行的持续探索,大致可分为四个阶段:
- Naive RAG (朴素 RAG): 最基础的 "检索-增强-生成" 线性流程,是 RAG 的原型实现。
- Advanced RAG (高级 RAG): 在朴素 RAG 的核心流程上,针对索引、检索、后处理等环节进行深度优化的技术集合。
- Modular RAG (模块化 RAG): 将 RAG 视为一个可插拔、可组合的模块化系统,通过引入功能模块(如查询重写、路由、融合)来构建更灵活、更强大的工作流。
Agentic RAG(智能体 RAG): 将 LLM 从一个被动的生成器转变为一个主动的决策者(Agent)。LLM 主动决定何时检索、检索什么、如何使用检索结果,并能进行反思和多步推理,实现更复杂的任务。
原理与推导
1. Naive RAG (朴素 RAG)
Naive RAG 是一个简单的三步线性流水线。
原理: 给定一个用户问题 ,RAG 的目标是生成一个答案 。朴素 RAG 将这个过程分解为:
- 检索 (Retrieve): 使用一个编码器 将文档库 中的所有文档块(chunks) 编码成向量。同时,使用一个查询编码器 将问题 编码成查询向量 。通过计算 与所有 向量的相似度(通常是余弦相似度),找出 Top-K 个最相关的文档块 。
- 增强 (Augment): 将检索到的文档块 与原始问题 合并成一个提示(Prompt)。格式通常为:
"根据以下信息:{d_1, d_2, ..., d_K},回答问题:{x}"。 - 生成 (Generate): 将增强后的提示输入到一个黑盒的 LLM 中,生成最终答案 。
数学表示: 从概率角度看,标准语言模型的生成概率是 。RAG 将其建模为以检索到的文档 为条件的生成概率。其核心公式可以看作是边缘化检索到的文档: 其中:
- 是检索模型的概率,表示给定问题 时,文档 相关的概率。在实践中,这通常由检索器(如向量相似度)的得分来近似。
- 是生成模型的概率,表示在给定问题 和上下文 的情况下,生成答案 的概率。
算法流程:
- 离线处理 (Indexing):
- 文档加载 (Load)
- 文档分块 (
Chunking/ Splitting) - 文档嵌入 (
Embedding) - 建立向量索引 (Indexing)
- 在线处理 (Inference):
- 用户问题嵌入 (Query
Embedding) - 向量检索 (Vector Search) -> 获取 Top-K 文档块
- 构建 Prompt (Augment)
- LLM 生成 (Generate)
- 用户问题嵌入 (Query
复杂度:
- 索引: 时间复杂度 ,其中 是文档块总数, 是嵌入维度。空间复杂度 。
- 查询: 对于基于 FAISS 等近似最近邻(ANN)索引的检索,时间复杂度通常为 到 ,远快于暴力搜索的 。
2. Advanced RAG (高级 RAG)
高级 RAG 并非一个全新的框架,而是对朴素 RAG 各个环节的优化技术总称,旨在解决其“检索质量不高”和“上下文利用不充分”等问题。
原理与技术:
-
预处理/索引优化 (Pre-retrieval):
- 滑动窗口/句子窗口 (Sentence Window): 索引的基本单元是句子,但检索回来后,将该句子及其上下文(前后 N 个句子组成的窗口)一起提供给 LLM。这解决了小块(句子)语义精确但上下文信息不足,与大块上下文完整但语义噪声大的矛盾。
- 元数据与多索引 (Metadata & Multi-indexing): 为每个文档块附加元数据(如日期、章节、来源),并可以根据元数据进行过滤。或者为同一份文档建立多种索引,例如,一个基于摘要的稀疏索引(如
BM25)和一个基于全文的密集索引(向量)。
-
检索优化 (Retrieval):
- 混合搜索 (Hybrid Search): 结合稀疏检索(如
BM25,擅长关键词匹配)和密集检索(向量搜索,擅长语义匹配)的结果。常用 Reciprocal Rank Fusion (RRF) 算法融合排序结果。 其中 是文档 在检索结果集 中的排名, 是一个常数(通常为 60),用于降低高排名文档的极端影响。 - 查询转换 (Query Transformations): 如果原始问题复杂或模糊,可以先让 LLM 对其进行重写、分解成子问题或扩展。例如,将 "RAG in 2024" 扩展为 "RAG advancements 2024", "new RAG techniques 2024" 等多个查询,分别检索后合并结果。
- 混合搜索 (Hybrid Search): 结合稀疏检索(如
-
后处理/生成优化 (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。
- LLM 开始生成答案。
- 当遇到低置信度的词(如不确定的实体、事件)时,暂停生成。
- 将这些低置信度的词构造成一个新的查询。
- 调用搜索模块进行检索。
- 将检索到的信息整合后,继续生成,直到完成。 这个过程形成了一个 "生成-检索-生成" 的循环,实现了按需检索。
4. Agentic RAG (智能体 RAG)
这是 RAG 的最高级形态,LLM 自身成为一个具备规划、工具使用和反思能力的智能体(Agent)。RAG 的检索器被视为 Agent 可以使用的一个工具(Tool)。
原理 (ReAct - Reason and Act):
ReAct 框架是 Agentic RAG 的典型代表。LLM 在一个 "思考-行动-观察" (Thought-Action-Observation) 的循环中运作。
- 思考 (Thought): 面对用户问题,LLM 首先进行内部思考,分析问题,并制定一个行动计划。例如:"用户想比较 A 和 B。我需要先分别查找 A 和 B 的定义,然后再进行比较。"
- 行动 (Action): LLM 决定调用一个工具。这个工具可以是
search[query],其中query是 LLM 自己生成的。例如,Action: search["definition of RAG"]。 - 观察 (Observation): LLM 接收到工具执行的结果(即检索到的文档内容)。
- 循环/反思: LLM 观察结果,并进行新一轮的思考。 "我已经找到了 RAG 的定义。现在我需要查找 Advanced RAG 的定义。" 于是它再次生成
Action: search["definition of Advanced RAG"]。这个循环会持续进行,直到 LLM 认为收集到了足够的信息来回答最初的问题。 - 最终回答: LLM 综合所有步骤中收集到的信息,生成最终的、结构化的答案。
与 Modular RAG 的区别:
- 控制者: 在 Modular RAG 中,控制流通常是预定义的(即使有分支),由规则或一个小型 "路由" 模型控制。而在
Agentic RAG中,核心 LLM 本身就是总控制器,它动态地决定整个工作流。 - 灵活性:
Agentic RAG具有极高的灵活性和适应性,能够处理需要多步推理和动态信息收集的复杂任务,甚至能从失败的工具调用中学习和调整策略(例如,如果一个搜索查询没有结果,它会思考并尝试一个不同的查询)。
代码实现
以下代码将演示一个 Advanced RAG 中的关键技术:句子窗口检索 (Sentence Window Retrieval) + Cross-Encoder 重排。这清晰地展示了如何从 Naive RAG 升级。
1import torch2import numpy as np3from sentence_transformers import SentenceTransformer, CrossEncoder, util45# 确保有 torch 和 sentence_transformers 库6# pip install torch sentence-transformers numpy78# --- 1. 数据准备与预处理 (句子窗口) ---9document = """10检索增强生成(RAG)是一种结合了大型语言模型和外部知识库的技术。11其基本思想是在生成回答前,先从文档中检索相关信息。12朴素RAG流程简单,但存在一些问题,例如上下文窗口与语义单元不匹配。13为了解决这个问题,高级RAG技术应运而生。14句子窗口检索是其中一种有效方法。它将句子作为检索的基本单元,保证了语义的精确性。15在检索到最相关的句子后,系统会扩展其上下文窗口,例如包含该句子的前后句。16这样既保证了检索的精确度,又为语言模型提供了充足的生成上下文。17最后,重排模型(Re-ranker)可以进一步优化检索结果的顺序。18"""1920# 将文档分割成句子21sentences = document.strip().split('\n')22print(f"文档被分割成 {len(sentences)} 个句子。")2324# 定义句子窗口的上下文范围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)3132 # 组合成窗口文本33 window = " ".join(sentences[start_index:end_index])34 sentence_windows.append(window)35 # print(f"句子 '{sentence}' -> 窗口: '{window}'") # 可取消注释查看细节3637# --- 2. 索引与检索 (第一阶段:对称语义搜索) ---38# 为什么用 Bi-Encoder? -> 用于高效地进行初步检索(召回)。它独立编码查询和文档,速度快,适合大规模数据。39bi_encoder = SentenceTransformer('moka-ai/m3e-base')4041# 为什么只嵌入句子? -> 为了更精确的语义匹配。句子是更小的语义单元,匹配精度更高。42# 如果嵌入整个窗口,噪声会更大。43sentence_embeddings = bi_encoder.encode(sentences, convert_to_tensor=True, show_progress_bar=False)4445# 用户查询46query = "如何解决朴素RAG的上下文问题?"47query_embedding = bi_encoder.encode(query, convert_to_tensor=True)4849# 使用余弦相似度进行检索50# 为什么用余弦相似度? -> 在高维空间中,余弦相似度能有效衡量方向上的一致性,即语义的相似性。51cos_scores = util.cos_sim(query_embedding, sentence_embeddings)[0]52top_k = 3 # 初步召回3个最相关的句子5354# 找出top_k个最相关的句子索引55top_results = torch.topk(cos_scores, k=top_k)5657print("\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]}")6162# --- 3. 上下文扩展 ---63# 为什么需要扩展上下文? -> 单个句子信息量太少,LLM需要更丰富的上下文来生成流畅、完整的回答。64# 我们根据第一阶段检索到的句子索引,获取对应的完整窗口。65expanded_contexts = [sentence_windows[idx] for idx in retrieved_sentence_indices]6667print("\n--- 上下文扩展结果 ---")68for i, context in enumerate(expanded_contexts):69 print(f"{i+1}. 扩展后的上下文: {context}")7071# --- 4. 重排 (第二阶段:Cross-Encoder) ---72# 为什么需要重排? -> Bi-Encoder 速度快但精度有限。Cross-Encoder 将问题和文档一起输入模型,73# 能捕捉更细微的交互关系,排序更准,但速度慢,适合对少量候选集进行精排。74cross_encoder = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')7576# 准备 cross_encoder 的输入:(查询, 上下文) 对77cross_inp = [[query, context] for context in expanded_contexts]7879# 预测分数80cross_scores = cross_encoder.predict(cross_inp)8182print("\n--- 第二阶段重排 (Cross-Encoder) 结果 ---")83# 将分数与上下文打包并排序84ranked_results = sorted(zip(cross_scores, expanded_contexts), key=lambda x: x[0], reverse=True)8586for i, (score, context) in enumerate(ranked_results):87 print(f"{i+1}. (分数: {score:.4f}) 最终上下文: {context}")8889print(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 中,可以并行执行对不同数据源的查询。
- 延迟: Naive RAG < Advanced RAG < 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_ids和remove_ids)。这是生产系统中的常用方案。 - 实时更新: 对于需要秒级更新的场景,需要更复杂的索引架构和数据流管道。
- 回答要点: 这涉及到索引的更新策略。
- 追问: 如果检索不到任何相关文档,系统应该如何响应?
- §1.2RAG Survey(Tongji 2024、HKU 2024)的分类体系?→
- §1.2From RAG to Context Engineering(2025 末)的范式转变?→
- §1.2Reasoning-Intensive Retrieval(BRIGHT 基准)的挑战?→
- §1.2Pre-retrieval / Retrieval / Post-retrieval / Generation 四阶段优化点?→
- §1.1RAG 的定义与核心组件(Retriever + Reranker + Generator)?→
- §1.1RAG vs 长上下文 vs 微调 vs Prompt Caching 的决策矩阵?→