'RAG is Dead' 争议背后 Context Engineering 的转向?
核心概念
"RAG is Dead" 这一论断是一种行业内的夸张说法,其本质并非指代 RAG(Retrieval-Augmented Generation,检索增强生成)技术的终结,而是标志着朴素 RAG (Naive RAG) 的局限性日益凸显。它反映了业界从简单的“检索-拼接-生成”模式,向一个更系统、更精细化的上下文工程 (Context Engineering) 范式的转变。上下文工程是一个更广阔的领域,它将 RAG 视为其核心组件之一,但更强调对输入给大语言模型 (LLM) 的上下文进行多阶段、结构化的处理、筛选、排序和合成,以最大化 LLM 的性能和可靠性。
原理与推导
1. 朴素 RAG (Naive RAG) 的原理
朴素 RAG 的流程可以抽象地表示为一个简单的函数组合:
其中:
- : 原始文档集合,被预处理(如分块 chunking)并编码为向量索引。
- : 用户输入的问题。
- : 检索阶段。通常使用向量相似度搜索(如 FAISS, HNSW)从知识库中找出与查询最相关的 个文本块 (chunks)。
- : 上下文拼接阶段。将检索到的文本块和原始问题简单地组合成一个最终的提示词。例如:"根据以下信息:[C1, C2, ... Ck],回答问题:[Q]"。
- : 生成阶段。LLM 基于拼接好的提示词生成最终答案。
朴素 RAG 的局限性(“Dead”的原因):
- 检索质量不高: 单纯的向量相似度无法完全捕捉语义相关性,可能检索到不准确或充满噪声的文本块。
- “大海捞针”问题 (Lost in the Middle): 当上下文窗口很长,LLM 的注意力会分散,位于上下文中间的信息容易被忽略,导致性能下降。简单拼接大量文本块会加剧此问题。
- 上下文冲突与冗余: 检索到的多个文本块可能包含矛盾或重复的信息,干扰 LLM 的判断。
- 缺乏深度推理能力: 朴素 RAG 难以回答需要综合多个文档、进行多步推理(multi-hop)的复杂问题。
2. 上下文工程 (Context Engineering) 的原理
上下文工程将 RAG 流程精细化、模块化,引入了多个预处理和后处理步骤。其流程可以表示为更复杂的函数组合:
核心步骤与原理推导:
-
查询转换 (Query Transformation):
- 动机: 原始用户查询可能模糊或不完整。
- 方法: 使用 LLM 将原始查询重写、扩展为多个子查询,或生成一个假设性文档 (
HyDE) 来进行检索。 - 原理: 。通过生成更适合检索的查询 ,提升检索的召回率和精确率。例如,将 "RAG 的缺点" 扩展为 ["RAG 检索噪声", "RAG 上下文长度限制", "RAG 推理能力不足"]。
-
结构化知识库与分层检索 (Structured KB & Layered Retrieval):
- 动机: 单一的文本块索引粒度太粗。
- 方法: 不仅索引小文本块,还建立文档摘要、实体关系图谱、表格索引等多层次、多模态的知识结构。检索时可以先从摘要层或图谱中定位关键信息,再深入到具体的文本块。
- 信息论解释: 这是一种信息压缩和分层索引的策略。高层索引(摘要、图谱)提供了低熵、高信息量的入口,引导检索过程,避免在原始高熵的文本海洋中盲目搜索。
-
精排 (Re-ranking):
- 动机: 召回 (Retrieve) 阶段追求高召回率(宁可错杀一千,不可放过一个),因此会引入噪声。精排则追求高精确率。
- 方法: 初步检索(如向量搜索)召回一个较大的集合(如 top-50),然后使用更强大但计算量更大的模型(如 Cross-Encoder)对这个小集合进行重新排序,选出最终的 top-N(如 top-5)。
- 数学表示: 设初筛结果为 。精排模型 计算查询和每个候选块的精确相关性得分。最终选择的上下文为 。Cross-Encoder 将 Query 和 Chunk 同时输入模型,能更好地捕捉交互信息,比单纯计算向量距离更精确。
- 复杂度: 初筛检索复杂度通常为 或 (取决于索引类型),而精排复杂度为 ,其中 是初筛候选集大小。这是一个典型的漏斗模型,用计算换精度。
-
上下文合成/压缩 (Context Synthesis/Compression):
- 动机: 避免“大海捞针”问题,并为 LLM 提供更清晰、简洁的上下文。
- 方法: 在将最终上下文送入 LLM 之前,使用一个小的 LLM 对精排后的文本块进行总结、提取关键信息或识别并删除冗余/矛盾部分。
- 原理: 这一步是对信息进行“有损压缩”,目标是保留与回答问题最相关的信号,去除噪声。这可以看作是为最终的生成模型“预处理”输入,降低其认知负荷。
代码实现
以下代码展示了从朴素 RAG到包含精排 (Re-ranking) 的高级 RAG的演进,这是上下文工程中的一个关键步骤。
1import numpy as np2from sentence_transformers import SentenceTransformer, CrossEncoder, util3import torch45# 确保有可用的 GPU,否则 CrossEncoder 会很慢6device = 'cuda' if torch.cuda.is_available() else 'cpu'7print(f"使用设备: {device}")89# --- 1. 知识库准备 ---10# 这是一个简单的文档集合,模拟我们的知识库11documents = [12 "RAG 通过从外部知识库检索信息来增强大型语言模型。",13 "上下文工程是优化输入给 LLM 的上下文以提高其性能的学科。",14 "朴素 RAG 的一个主要缺点是检索到的信息可能包含噪声。",15 "精排 (Re-ranking) 使用更强大的模型对初步检索结果进行重新排序,以提高相关性。",16 "大型语言模型的上下文窗口长度是有限的,过长的上下文可能导致 '大海捞针' 问题。",17 "混合搜索结合了关键词搜索和向量搜索的优点。",18 "FAISS 是一个用于高效相似性搜索和密集向量聚类的库。"19]2021# --- 2. 嵌入模型和精排模型加载 ---22# Bi-Encoder: 用于将句子编码为向量,进行快速的向量检索23# 这是一个轻量级、高质量的中文 embedding 模型24embedding_model = SentenceTransformer('moka-ai/m3e-base', device=device)2526# Cross-Encoder: 用于对 (query, document) 对进行打分,用于精排27# 这是一个基于 BERT 的模型,精度更高但速度慢28rerank_model = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2', device=device)2930# --- 3. 知识库索引 (Embedding) ---31# 为什么这样做: 将所有文档转换为向量,以便进行快速的相似度计算。这是所有 RAG 系统的基础。32print("正在为文档创建向量索引...")33doc_embeddings = embedding_model.encode(documents, convert_to_tensor=True, show_progress_bar=True)3435# --- 4. 查询与检索 ---36query = "RAG 系统如何提升准确率?"37print(f"\n查询: {query}")3839# --- 朴素 RAG (Naive RAG) 的实现 ---40print("\n--- 朴素 RAG (仅向量检索) ---")41# 为什么这样做: 这是最基础的检索步骤。我们计算查询和所有文档向量的余弦相似度,找出最相似的 top-k 个。42query_embedding = embedding_model.encode(query, convert_to_tensor=True)43# 使用 PyTorch 的 util.cos_sim 计算余弦相似度44cos_scores = util.cos_sim(query_embedding, doc_embeddings)[0]45# top_k 设为 346top_k_naive = 347# 使用 torch.topk 找到分数最高的 k 个结果的索引和分数48top_results_naive = torch.topk(cos_scores, k=top_k_naive)4950print(f"Top {top_k_naive} 检索结果:")51for score, idx in zip(top_results_naive[0], top_results_naive[1]):52 print(f" - 得分: {score:.4f}, 内容: {documents[idx]}")5354# --- 上下文工程: 引入精排 (Re-ranking) ---55print("\n--- 高级 RAG (向量检索 + 精排) ---")56# 步骤 A: 扩大召回范围 (Recall)57# 为什么这样做: 精排的输入需要一个更大的候选集,以确保真正相关的文档被包含在内。58# 我们先用向量检索召回一个更大的集合,比如 top-5。59top_k_retrieval = 560cos_scores = util.cos_sim(query_embedding, doc_embeddings)[0]61top_results_retrieval = torch.topk(cos_scores, k=top_k_retrieval)62retrieved_docs = [documents[idx] for idx in top_results_retrieval[1]]6364print(f"初步召回 {top_k_retrieval} 个候选文档进行精排...")6566# 步骤 B: 使用 Cross-Encoder 进行精排67# 为什么这样做: Cross-Encoder 会将查询和每个候选文档拼接起来输入模型,68# 能够更精确地判断两者的相关性,弥补向量相似度有时不准确的缺陷。69cross_inp = [[query, doc] for doc in retrieved_docs]70cross_scores = rerank_model.predict(cross_inp)7172# 将精排得分与文档内容配对73reranked_results = list(zip(cross_scores, retrieved_docs))74# 按得分从高到低排序75reranked_results.sort(key=lambda x: x[0], reverse=True)7677# 步骤 C: 选择最终的 top-k78top_k_final = 379print(f"\n精排后的 Top {top_k_final} 结果:")80for score, doc in reranked_results[:top_k_final]:81 print(f" - 精排得分: {score:.4f}, 内容: {doc}")8283# 观察结果:84# 朴素 RAG 可能会返回一些仅在词汇上相似但不直接回答问题的文档。85# 精排后的结果通常更贴合查询的意图。在这个例子中,"精排" 和 "噪声" 相关的文档被提到了更高的位置。
工程实践
-
使用场景:
- 朴素 RAG: 适用于简单的问答机器人、FAQ 系统,知识库内容相对干净、事实明确。
- 上下文工程: 适用于复杂的、生产级的应用,如法律/金融文档分析、多文档综合报告、需要深度推理的智能客服。当答案分散在多个文档中,或用户查询意图复杂时,必须采用上下文工程。
-
超参数选择:
- Chunk Size: 这是一个关键权衡。小 Chunk(如 128 tokens)能更精确地定位信息,但可能丢失上下文。大 Chunk(如 1024 tokens)保留了更多上下文,但可能引入噪声。经验法则是从 256-512 tokens 开始,并根据文档结构调整。
- Retrieval
top_k: 在朴素 RAG 中,k通常较小(3-5)。在带精排的系统中,初筛的k可以更大(20-50),而精排后的k仍然是 3-5。 - Re-ranker 模型: 选择 Cross-Encoder 时要在性能和延迟之间权衡。
MiniLM系列是速度和效果的良好平衡点。对于最高精度的场景,可以使用更大的模型如ms-marco-electra-large。
-
性能 / 显存 / 吞吐 的权衡:
- 延迟: 上下文工程显著增加了延迟。查询转换、多路检索、精排、上下文合成每一步都需要模型调用或复杂计算。精排是主要的延迟来源。
- 成本: 更多的模型调用(特别是 LLM 调用)意味着更高的 API 费用或更昂贵的自部署基础设施。
- 优化策略:
- 缓存: 对查询转换结果、检索结果进行缓存。
- 模型量化/剪枝: 对精排模型进行优化以加速推理。
- 异步与流式处理: 将 RAG 流程设计为流式 pipeline,可以先返回一个基于简单检索的快速答案,然后在后台进行精排和合成,并更新答案。
-
常见坑和调试技巧:
- "Garbage In, Garbage Out": RAG 系统的上限取决于知识库的质量。预处理阶段的数据清洗、元数据提取至关重要。
- 调试黄金法则: 永远检查检索到的上下文! 当 RAG 输出错误时,90% 的问题出在检索环节。将检索到的
chunks和它们的分数打印出来,是调试的第一步。 - 评估: 建立一个端到端的评估框架(如使用 RAGAs, TruLens, ARES 等工具)来衡量检索(Context Recall/Precision)和生成(Faithfulness/Answer Relevance)的质量,这是迭代优化的基础。
常见误区与边界情况
-
误区一:“RAG 已死,我应该直接用长上下文模型”
- 辨析: 这是最常见的误解。即使有百万级上下文窗口,LLM 仍然面临“大海捞针”问题,性能会随输入长度增加而下降。此外,长上下文的推理成本和延迟极高。RAG 的本质是在无限的外部知识和有限的 LLM 注意力之间搭建一座高效的桥梁。因此,RAG(或其演进形式)仍然是处理海量外部知识不可或缺的工具。正确的思路是:用 RAG 来筛选出最高质量的上下文,再喂给长上下文模型。
-
误区二:“向量搜索就是一切”
- 辨析: 向量搜索擅长语义模糊匹配,但对关键词、产品编号 (SKU)、人名等精确匹配能力较弱。生产级系统通常采用混合搜索 (Hybrid Search),结合了向量搜索(如 FAISS)和传统的关键词搜索(如
BM25),并对两者的得分进行加权融合,以兼顾两种场景。
- 辨析: 向量搜索擅长语义模糊匹配,但对关键词、产品编号 (SKU)、人名等精确匹配能力较弱。生产级系统通常采用混合搜索 (Hybrid Search),结合了向量搜索(如 FAISS)和传统的关键词搜索(如
-
误区三:“只要我用了 Re-ranker,效果就一定好”
- 辨析: Re-ranker 的效果依赖于初筛召回的质量。如果相关的文档在初筛阶段(向量检索)就被漏掉了,Re-ranker 神仙难救。因此,优化 embedding 模型和检索策略与引入 Re-ranker 同等重要。
-
常见面试追问:
- Q: 如果你的 RAG 系统总是回答“我不知道”,你会从哪些方面去排查?
- A: 1. 检索问题: 检查检索模块是否召回了任何文档。可能是查询与文档向量空间不匹配(换 embedding 模型),或相似度阈值设置过高。2. 内容问题: 检查召回的文档内容是否真的包含了答案。可能知识库本身就缺失相关信息。3. 生成问题: 检查 prompt 模板,是否过于保守,导致 LLM 在没有 100% 把握时倾向于不回答。4. 精排问题: 检查精排器是否错误地将相关文档排到了后面。
- Q: 如何处理需要综合多个文档才能回答的复杂问题(Multi-hop Question)?
- A: 这超出了朴素 RAG 的能力。可以采用:1. 迭代式 RAG: 第一轮 RAG 生成初步答案或新的查询,然后用这个新查询进行第二轮检索,逐步深入。2. 图 RAG (Graph RAG): 将知识库构建成知识图谱,检索过程变成在图上游走,寻找连接不同实体和事实的路径。3. 使用 Agent: 让 LLM 充当 Agent,自己决定何时需要检索、检索什么、以及何时停止并生成最终答案。
- Q: 如何评估一个 RAG 系统的好坏?
- A: 分两个层面评估:1. 组件评估: 单独评估检索模块的召回率和精确率(
Context Recall/Precision)。2. 端到端评估: 评估最终答案的质量,常用指标包括:Faithfulness(答案是否忠于原文)、Answer Relevance(答案是否切题)、Answer Correctness(事实正确性)。可以使用 RAGAs 等自动化评估框架,或构建人工评测集。
- A: 分两个层面评估:1. 组件评估: 单独评估检索模块的召回率和精确率(
- Q: 如果你的 RAG 系统总是回答“我不知道”,你会从哪些方面去排查?