RAG Survey(Tongji 2024、HKU 2024)的分类体系?
好的,我们来深入剖析 RAG 领域的最新研究进展,特别是两篇关键综述论文(通常指同济大学发布的《A Survey on Retrieval-Augmented Text Generation》和香港大学等机构发布的《Retrieval-Augmented Generation for Large Language Models: A Survey》)中提炼出的分类体系。
核心概念
检索增强生成(Retrieval-Augmented Generation, RAG)是一种将大型语言模型(LLM)与外部知识库相结合的范式。其核心思想是,在生成回答之前,首先从一个大规模的语料库(如维基百科、公司内部文档)中检索出与用户问题相关的文档片段,然后将这些片段作为上下文(Context)一并提供给 LLM,引导其生成更准确、更具事实性、更符合特定知识领域的回答。这有效缓解了 LLM 固有的知识陈旧、事实幻觉等问题,如同让模型进行“开卷考试”。
原理与推导
RAG 的核心可以被形式化为一个两阶段的概率过程。给定一个输入查询 ,我们想生成一个输出序列 。传统的 Seq2Seq 模型直接建模 ,而 RAG 则引入了一个隐变量 ,代表检索到的文档。
生成过程的概率可以表示为:
这个公式可以分为两个核心部分:
- Retriever (检索器): 。这一部分负责根据输入 从知识库 中找到最相关的文档 。在实践中,这通常不是一个严格的概率分布,而是通过一个评分函数 来实现的,例如向量相似度。检索器会返回得分最高的 Top-K 个文档。
- 几何解释: 检索过程可以看作是在高维向量空间中寻找与查询向量 最邻近的文档向量 。常用的相似度度量是余弦相似度:
- Generator (生成器): 。这是一个标准的条件生成模型(即 LLM),它接收原始查询 和检索到的文档 作为联合输入,然后生成最终的答案 。
根据 Tongji 2024 和 HKU 2024 等综述,RAG 的发展可以划分为三个主要范式:
1. Naïve RAG (朴素 RAG)
这是最基础和经典的 RAG 流程,遵循“检索-阅读”的线性管道。
- 原理:
- Indexing: 离线过程。将文档库切分成块(Chunks),使用预训练的编码器(如 Sentence-BERT)将每个块编码为向量,并存入向量数据库(如 FAISS, Milvus)。
- Retrieval: 在线过程。将用户查询 用同一个编码器编码成向量,然后在向量数据库中执行相似性搜索(如 MIPS,Maximum Inner Product Search),获取 Top-K 个最相似的文档块。
- Generation: 将原始查询 和检索到的 K 个文档块拼接成一个长长的提示(Prompt),输入给 LLM 生成答案。
- 算法复杂度:
- Indexing: 设文档库有 个块,编码器输出维度为 。建立索引的时间复杂度取决于所用的向量数据库,例如 FAISS 的
IndexFlatL2是 ,但更高效的索引结构(如 HNSW)可以做到近似对数时间。 - Retrieval: 对于暴力搜索是 ,对于 HNSW 等近似最近邻(ANN)算法,查询时间复杂度约为 或 。
- Generation: 时间复杂度与输入序列长度和生成长度成正比,通常是
Transformer架构的 ,其中 是输入 Prompt 的长度。
- Indexing: 设文档库有 个块,编码器输出维度为 。建立索引的时间复杂度取决于所用的向量数据库,例如 FAISS 的
2. Advanced RAG (高级 RAG)
朴素 RAG 在索引、检索和生成等环节存在诸多挑战。高级 RAG 旨在通过优化特定模块来提升性能。
- 原理: 围绕核心 RAG 流程的**前(Pre-)、中(Retrieval)、后(Post-)**三个阶段进行增强。
- Pre-Retrieval (检索前优化): 优化索引质量。
Chunking优化: 从固定大小切分,发展到基于句子边界(Sentence-aware)、递归切分(RecursiveChunking)或使用模型(如 Unstructured.io)进行内容感知的切分。- Data Cleaning & Metadata: 清理无关信息,并为每个 Chunk 添加标题、章节、日期等元数据,用于后续的过滤或混合检索。
- Retrieval (检索中优化): 提升检索精度。
- Query Transformation: 用户的原始查询可能不适合直接用于向量检索。
HyDE(Hypothetical Document Embeddings): 让 LLM 先为查询生成一个“假设性”的答案文档,再用这个文档的向量去检索,因为假设的文档在形式上更接近于知识库中的文档。- Multi-Query: 让 LLM 将一个复杂查询分解成多个子查询,分别检索后合并结果。
- Fine-tuning Embedder: 使用领域内的数据对检索用的编码器模型进行微调,使其更能理解特定领域的语义相似性。通常使用对比学习损失(Contrastive Loss),如 Triplet Loss: 其中 是查询, 是正样本(相关文档), 是负样本(不相关文档), 是间隔。
- Query Transformation: 用户的原始查询可能不适合直接用于向量检索。
- Post-Retrieval (检索后优化): 处理检索到的信息。
- Reranking: 初步检索(e.g., Top-20)的结果可能包含噪声。使用一个更强大但更慢的模型(如 Cross-Encoder)对这些初步结果进行重新排序,选出最终的 Top-K (e.g., Top-3)。
- Context Compression: 检索到的上下文可能很长,包含冗余或无关信息,且可能超出 LLM 的上下文窗口。使用小模型或规则来压缩上下文,提取最关键的信息,以应对“迷失在中间(Lost in the Middle)”的问题。
- Pre-Retrieval (检索前优化): 优化索引质量。
3. Modular RAG (模块化 RAG)
这是 RAG 的最新范式,将 RAG 视为一个灵活、可插拔的框架,而非固定的线性管道。它引入了更复杂的控制流和功能模块。
- 原理:
- 新模块:
- Search Module: 不再局限于向量检索,可以融合关键词搜索(
BM25)、知识图谱查询、SQL 查询等多种检索方式。 - Memory Module: 将近期对话的检索结果或生成的答案缓存起来,用于后续的交互,实现上下文感知。
- Fusion Module: 智能地融合来自不同检索源的信息,或对检索到的信息进行加权、排序。
- Search Module: 不再局限于向量检索,可以融合关键词搜索(
- 新模式:
- Iterative / Recursive RAG: 一次检索不满足时,系统可以基于初步结果生成新的查询,进行多轮检索,直到收集到足够的信息。这类似于人类的研究过程。
- LLM-as-X: LLM 的角色不再仅仅是最后的生成器。
- LLM-as-Planner: 对于复杂问题,LLM 首先制定一个执行计划,决定何时检索、检索什么、如何组合信息。
- LLM-as-Rewriter: LLM 负责重写用户的原始查询,使其更适合检索。
- LLM-as-Judge: LLM 评估检索到的文档的相关性,充当
Reranker或决定是否需要再次检索。
- 新模块:
代码实现
下面是一个使用 transformers, sentence-transformers, 和 faiss-cpu 实现的 Naïve RAG 示例。
1import torch2import faiss3import numpy as np4from sentence_transformers import SentenceTransformer5from transformers import pipeline, AutoTokenizer, AutoModelForCausalLM67# 1. 准备环境和模型8# 确保已安装: pip install torch sentence-transformers faiss-cpu transformers9device = "cuda" if torch.cuda.is_available() else "cpu"1011# 用于编码文档和查询的句向量模型12print("加载句向量模型...")13encoder = SentenceTransformer('moka-ai/m3e-base', device=device)1415# 用于最终生成答案的语言模型16print("加载语言模型...")17# 注意:这里使用一个较小的模型作为示例。实际应用中通常使用更大的模型。18# 如果显存不足,可以换成更小的模型如 'gpt2'19model_name = "Qwen/Qwen1.5-0.5B-Chat"20tokenizer = AutoTokenizer.from_pretrained(model_name)21model = AutoModelForCausalLM.from_pretrained(model_name).to(device)22generator = pipeline('text-generation', model=model, tokenizer=tokenizer, device=device)2324# 2. 索引 (Indexing)25print("\n--- 索引阶段 ---")26# 我们的知识库27documents = [28 "人工智能(AI)是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学。",29 "大型语言模型(LLM)是人工智能领域的一个重要分支,它能够理解和生成人类语言。",30 "检索增强生成(RAG)通过结合外部知识库来减少大型语言模型的幻觉问题。",31 "FAISS 是 Facebook AI 开发的一个用于高效相似性搜索和密集向量聚类的库。"32]3334# 为什么这样做:将文本编码为向量是RAG的核心,它将语义信息压缩到数学表示中。35print("正在将文档编码为向量...")36doc_embeddings = encoder.encode(documents)37doc_embeddings = np.array(doc_embeddings).astype('float32')3839# 为什么这样做:向量数据库(这里用FAISS模拟)能够极快地找到与查询向量最相似的文档向量。40# d 是向量维度41d = doc_embeddings.shape[1]42# IndexFlatL2 使用L2距离进行暴力搜索,对于小数据集来说足够了。43index = faiss.IndexFlatL2(d)44index.add(doc_embeddings)45print(f"索引建立完毕,包含 {index.ntotal} 个文档,向量维度为 {d}。")4647# 3. 检索 (Retrieval)48print("\n--- 检索阶段 ---")49query = "什么是RAG?"50k = 2 # 我们希望检索最相关的2个文档5152# 为什么这样做:查询也必须用同一个模型编码,以确保查询向量和文档向量在同一个语义空间中。53print(f"查询: {query}")54query_embedding = encoder.encode([query])55query_embedding = np.array(query_embedding).astype('float32')5657# 为什么这样做:index.search 会返回距离和索引。D是距离,I是文档在原始列表中的索引。58distances, indices = index.search(query_embedding, k)5960print(f"检索到的 Top-{k} 文档索引: {indices[0]}")61retrieved_docs = [documents[i] for i in indices[0]]6263# 4. 生成 (Generation)64print("\n--- 生成阶段 ---")65# 为什么这样做:这是RAG的关键步骤,将检索到的知识(上下文)和原始问题一起打包成一个清晰的指令,66# 引导LLM基于给定的信息来回答问题,而不是依赖其内部可能过时或错误的知识。67context = "\n".join(retrieved_docs)68prompt = f"""69你是一个AI助手。请根据下面提供的上下文信息来回答问题。如果上下文中没有相关信息,请说你不知道。7071上下文:72{context}7374问题: {query}7576回答:77"""7879print("构建的Prompt:")80print(prompt)8182# 为什么这样做:调用LLM进行文本生成,max_length和temperature是常用参数,83# 控制生成长度和创造性。84messages = [85 {"role": "system", "content": "你是一个AI助手。"},86 {"role": "user", "content": prompt}87]88text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)89model_inputs = tokenizer([text], return_tensors="pt").to(device)9091generated_ids = model.generate(92 model_inputs.input_ids,93 max_new_tokens=12894)9596generated_ids = [97 output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)98]99100response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]101102print("\nLLM 生成的回答:")103print(response)
工程实践
- 使用场景:
- 企业级知识库问答: 客服机器人、内部文档查询系统、投研报告分析。
- 内容创作辅助: 辅助撰写需要事实依据的文章、报告,自动引用来源。
- 个性化教育: 根据学生的提问,从教材库中检索相关知识点并生成解释。
- 超参数选择:
chunk_size和chunk_overlap: 这是最关键的超参数。chunk_size太小会丢失上下文,太大则可能引入噪声。通常在 256-1024 token 之间。chunk_overlap(e.g., 10-20% of chunk_size) 用于保证句子不被切断。top_k: 检索文档的数量。通常在 3-5 之间。太小可能信息不足,太大则增加噪声和 LLM 处理成本,并可能触发“迷失在中间”问题。EmbeddingModel: 选择在 MTEB (Massive TextEmbeddingBenchmark) 等榜单上表现好的模型,并考虑其是否针对你的应用领域(如代码、金融)。
- 性能 / 显存 / 吞吐 的权衡:
- Latency: Naïve RAG 延迟最低。增加
Reranker、Query Transformation 等高级 RAG 模块会显著增加端到端延迟。 - Cost: Modular RAG 中多次调用 LLM(如用于规划、重写)会大幅增加 API 调用成本。
- Offline vs. Online: 索引构建是昂贵的离线任务。实时更新索引(例如,对于新闻数据)是一个工程挑战,通常采用增量索引或混合索引策略。
- Latency: Naïve RAG 延迟最低。增加
- 常见坑和调试技巧:
- 检索质量差:
- 调试: 单独评估检索模块的召回率和准确率。检查
Chunking策略是否合理。可视化查询和文档向量的分布(用 t-SNE 或 UMAP)。 - 解决: 尝试不同的
Chunking策略,或微调Embedding模型。
- 调试: 单独评估检索模块的召回率和准确率。检查
- LLM 忽略上下文:
- 调试: 检查最终构建的 Prompt 格式是否清晰。是否将最重要的信息放在了 Prompt 的开头或结尾(缓解“迷失在中间”)。
- 解决: 优化 Prompt 模板,使用更强的指令,或使用专为遵循指令而微调的模型。
- 评估困难: RAG 系统的评估是复杂的,需要综合评估检索和生成两部分。使用 RAGAS、ARES 等框架,从 Faithfulness(忠实度)、Answer Relevance(答案相关性)、Context Relevance(上下文相关性)等多个维度进行评估。
- 检索质量差:
常见误区与边界情况
- 误区一:RAG 就是“给 Prompt 加点东西”
- 这是对 RAG 的极大简化。一个生产级的 RAG 系统是一个复杂的工程,涉及数据预处理、索引、多模态检索、Reranking、Prompt 压缩、结果生成和端到端评估等一系列技术栈。
- 误区二:有了 RAG 就不需要微调(Fine-tuning)了
- RAG 和微调是互补的。RAG 负责提供“外部知识”,解决事实性、时效性问题;微调负责教授模型特定的“技能”或“风格”,如模仿特定口吻、遵循复杂指令、掌握领域术语。最佳实践常常是“RAG + Fine-tuning”。
- 误区三:检索到的文档越多(
top_k越大)越好- 错误。过多的文档会引入噪声,干扰 LLM 的判断,并可能超出其上下文窗口限制。实验表明,
top_k超过一定数量后,性能甚至会下降。
- 错误。过多的文档会引入噪声,干扰 LLM 的判断,并可能超出其上下文窗口限制。实验表明,
- 边界情况与失败模式:
- 知识库中无答案: 这是 RAG 的关键测试点。系统不应幻化答案,而应明确表示“根据我有的资料,无法回答这个问题”。这需要通过精心设计的 Prompt 来引导。
- 查询与文档语义鸿沟: 用户的提问方式和文档的表述方式可能差异巨大。高级 RAG 中的 Query Transformation 就是为了解决这个问题。
- 信息冲突: 检索到的多个文档包含相互矛盾的信息。LLM 如何处理这种情况是一个开放性问题。目前的做法可能包括:信任排名最高的文档、让 LLM 自行判断、或向用户呈现所有信息。
- 常见面试追问:
- "如果你的 RAG 系统上线后,用户反馈答案不准确,你会从哪些方面去排查和优化?"
- 回答要点: 分模块排查。首先,检查检索模块,看是否召回了正确的文档(离线评估)。其次,检查生成模块,看 Prompt 是否合理,LLM 是否正确利用了上下文。最后,考虑端到端评估,引入 RAGAS 等工具进行量化分析,并根据分析结果决定是优化
Chunking、微调Embedding模型,还是引入Reranker。
- 回答要点: 分模块排查。首先,检查检索模块,看是否召回了正确的文档(离线评估)。其次,检查生成模块,看 Prompt 是否合理,LLM 是否正确利用了上下文。最后,考虑端到端评估,引入 RAGAS 等工具进行量化分析,并根据分析结果决定是优化
- "如何评估一个 RAG 系统的好坏?"
- 回答要点: 提到 RAGAS 等框架,并解释其核心评估维度:Faithfulness(生成内容是否基于上下文)、Answer Relevance(答案是否切题)、Context Precision/Recall(上下文是否精准且全面)。同时,也要结合业务指标(如用户满意度、采纳率)进行综合评估。
- "RAG 如何处理实时性要求高的数据,比如新闻流?"
- 回答要点: 讨论索引更新策略。可以有定期(如每小时)的全量或增量更新。对于极高实时性,可以设计一个双路系统:一路查询静态的、大规模的向量索引,另一路查询一个小的、包含最新数据的实时索引(如内存中的小 FAISS 索引或直接用
BM25搜索),最后融合两路结果。
- 回答要点: 讨论索引更新策略。可以有定期(如每小时)的全量或增量更新。对于极高实时性,可以设计一个双路系统:一路查询静态的、大规模的向量索引,另一路查询一个小的、包含最新数据的实时索引(如内存中的小 FAISS 索引或直接用
- "如果你的 RAG 系统上线后,用户反馈答案不准确,你会从哪些方面去排查和优化?"
- §1.2Naive RAG / Advanced RAG / Modular RAG / Agentic RAG 的演进路径?→
- §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 的决策矩阵?→