大语言模型的知识来自训练数据,一旦训练完成就固化了。它不知道最新的新闻、无法访问企业内部文档、也不记得上个月的政策变化。检索增强生成(RAG) 和 长上下文(Long Context) 是解决这一问题的两大技术路线。RAG 通过外部检索为模型注入实时知识,Long Context 则让模型一次性能阅读更多内容。两者各有优劣,实际系统中往往结合使用。
本文要点
- RAG 的基础架构与核心论文
- Naive RAG → Advanced RAG → Modular RAG 的演进
- Long Context 的技术方案与工程挑战
- RAG vs Long Context 的选择框架
- 向量数据库与 Embedding 模型选型
- 混合方案的最佳实践
一、RAG 基础
1.1 问题起源:LLM 的知识局限
LLM 面临三类知识问题:
| 问题类型 | 描述 | 示例 |
|---|---|---|
| 知识过时 | 训练数据有时效性 | 不知道 2024 年奥运会结果 |
| 知识缺失 | 私有数据不在训练集中 | 无法回答企业内部流程问题 |
| 知识幻觉 | 不确定时编造合理但不正确的答案 | 编造不存在的论文引用 |
RAG 的核心思路是:与其让模型记住所有知识,不如在需要时从外部知识库检索。
1.2 RAG 论文起源
RAG 的概念最早由 Facebook AI Research(现 Meta AI)的 Lewis 等人在 2020 年的论文《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》中系统提出。
原始 RAG 的关键公式:
其中 是检索概率, 是给定文档的生成概率。RAG 实际上是对所有 top-k 文档的生成结果做边际化。
1.3 两种 RAG 变体
Lewis 等人提出了两种 RAG 变体:
RAG-Sequence:每个文档独立生成完整回答,然后加权合并
# RAG-Sequencedef rag_sequence(question, retriever, generator, top_k=5): docs = retriever.retrieve(question, top_k=top_k)
answers = [] for doc in docs: # 每个文档独立生成完整答案 answer = generator.generate(question, context=doc) score = retriever.score(question, doc) * generator.score(answer) answers.append((answer, score))
# 边际化:选择得分最高的答案 best_answer = max(answers, key=lambda x: x[1]) return best_answer[0]RAG-Token:每个 token 生成时都考虑所有文档
# RAG-Token(更常用)def rag_token(question, retriever, generator, top_k=5): docs = retriever.retrieve(question, top_k=top_k)
answer_tokens = [] for step in range(max_length): token_probs = [] for doc in docs: # 每个文档对下一个 token 的概率 prob = generator.next_token_prob(question, doc, answer_tokens) doc_weight = retriever.score(question, doc) token_probs.append(prob * doc_weight)
# 边际化后选择 token next_token = merge_and_select(token_probs) answer_tokens.append(next_token)
return tokens_to_text(answer_tokens)RAG-Token 在实践中更受欢迎,因为它允许每个生成步骤从不同的文档中获取信息。
二、RAG 发展阶段
2.1 Naive RAG(2020-2022)
第一代 RAG 流程简单直接:检索 → 拼接 → 生成。
Naive RAG 的问题:
| 问题 | 描述 | 影响 |
|---|---|---|
| 检索不准确 | 语义相似但不相关的文档被检索 | 回答偏题 |
| 信息冗余 | 多个文档包含重复信息 | 浪费上下文窗口 |
| 上下文窗口溢出 | 检索的文档超过模型上下文限制 | 信息截断 |
| 缺乏来源追踪 | 无法知道回答来自哪个文档 | 可信度低 |
2.2 Advanced RAG(2023)
Advanced RAG 在检索前后增加了多个优化步骤:
查询重写(Query Rewriting):用户的原始查询往往不够精确,需要改写为更适合检索的形式。
# 查询重写方法def rewrite_query(original_query, llm): # 方法1:LLM 改写 prompt = f"请将以下问题改写为更适合搜索引擎的查询:{original_query}" rewritten = llm.generate(prompt)
# 方法2:HyDE(Hypothetical Document Embedding) # 先让模型生成假设性答案,用答案去检索 hypothetical_answer = llm.generate(f"请回答:{original_query}") hyde_query = embed(hypothetical_answer)
# 方法3:多查询扩展 sub_queries = llm.generate(f"将以下问题分解为3个子问题:{original_query}")
return rewritten, hyde_query, sub_queriesHyDE 是 Gao 等人 2022 年提出的创新方法。其核心洞察是:用户问题的 embedding 可能和真正答案文档的 embedding 距离较远,但假设性答案的 embedding 和真实答案更接近。
混合检索(Hybrid Retrieval):结合稠密检索和稀疏检索的优势。
# 混合检索def hybrid_retrieve(query, dense_index, sparse_index, alpha=0.7): # 稠密检索:语义相似度 dense_results = dense_index.search(embed(query), top_k=20)
# 稀疏检索:关键词匹配(BM25) sparse_results = sparse_index.search(query, top_k=20)
# Reciprocal Rank Fusion (RRF) 融合 fused = reciprocal_rank_fusion( dense_results, sparse_results, k=60 # RRF 参数 )
return fused[:10]
def reciprocal_rank_fusion(*rankings, k=60): """RRF 融合多个排序列表""" scores = {} for ranking in rankings: for rank, doc in enumerate(ranking): scores[doc] = scores.get(doc, 0) + 1.0 / (k + rank + 1) return sorted(scores.keys(), key=lambda x: scores[x], reverse=True)重排序(Reranking):用交叉编码器对初检结果进行精排。
# 重排序def rerank(query, documents, cross_encoder): """使用交叉编码器重排序""" pairs = [(query, doc.text) for doc in documents] scores = cross_encoder.predict(pairs)
# 按交叉编码器分数重新排序 ranked = sorted(zip(documents, scores), key=lambda x: x[1], reverse=True) return [doc for doc, score in ranked]稠密检索用的是双编码器(Bi-Encoder),查询和文档分别编码后计算相似度,速度快但精度有限。重排序用的是交叉编码器(Cross-Encoder),查询和文档一起编码,精度高但速度慢。因此先用双编码器做粗筛,再用交叉编码器做精排,是效果和速度的最佳平衡。
2.3 Modular RAG(2024+)
第三代 RAG 将系统拆分为可组合的模块,根据任务需求灵活编排:
| 模块 | 功能 | 可选实现 |
|---|---|---|
| 索引模块 | 文档处理与向量化 | 语义分块、递归分块、父子文档 |
| 检索模块 | 相关文档获取 | BM25、DPR、混合检索、多跳检索 |
| 路由模块 | 查询分发 | 意图分类、查询改写 |
| 融合模块 | 多源结果合并 | RRF、互信息融合 |
| 重排模块 | 精细化排序 | 交叉编码器、LLM 重排 |
| 压缩模块 | 上下文精简 | 摘要压缩、token 裁剪 |
| 生成模块 | 答案生成 | 引用生成、事实核查 |
**智能分块(Chunking)**是 Modular RAG 中常被忽视但极其关键的环节:
# 分块策略对比chunking_strategies = { "固定长度": { "方法": "每 512 token 切一块", "优点": "简单", "缺点": "可能切断语义完整性", }, "递归字符分割": { "方法": "按段落 → 句子 → 字符递归分割", "优点": "保持段落完整性", "缺点": "块大小不均匀", }, "语义分块": { "方法": "用 embedding 相似度确定边界", "优点": "语义完整", "缺点": "计算开销大", }, "父-子文档": { "方法": "小块检索,大块生成", "优点": "检索精准 + 上下文丰富", "缺点": "索引复杂", },}父-子文档策略是 2024 年的热门方案:检索时用小块(如 128 token)提高精度,生成时用父块(如 512 token)提供完整上下文。
2.4 GraphRAG:知识图谱增强
2024 年微软发布的 GraphRAG 将知识图谱引入 RAG 流程,解决全局性问题的回答:
GraphRAG 的创新在于:传统 RAG 只能做局部检索(找到相关文档片段),无法回答”这篇报告的总体观点是什么”这类全局性问题。GraphRAG 通过构建知识图谱并对社区进行摘要,使得全局性问题也能被有效回答。
三、Long Context
3.1 上下文长度演进
LLM 的上下文窗口在短短几年内增长了数百倍:
| 时间 | 模型 | 最大上下文 | 注意力复杂度 |
|---|---|---|---|
| 2020 | GPT-3 | 4K | O(n²) = 16M |
| 2022 | PaLM | 8K | O(n²) = 64M |
| 2023 | Claude 1 | 100K | O(n²) = 10B |
| 2023 | GPT-4 Turbo | 128K | O(n²) = 16.4B |
| 2024 | Gemini 1.5 Pro | 1M | O(n²) = 1T |
| 2024 | LLaMA 3 | 128K | O(n²) = 16.4B |
标准 Transformer 注意力的 O(n²) 复杂度使得长上下文的计算成本急剧上升。1M 上下文的自注意力计算量是 4K 的约 62500 倍。
3.2 长上下文的技术方案
扩展上下文窗口主要面临三个挑战:位置编码外推、注意力计算效率、和中间层信息丢失。
方案1:RoPE 外推
RoPE 旋转位置编码在短上下文训练后,需要扩展到长上下文使用:
# NTK-aware 位置缩放def ntk_aware_rope_extension(base_theta, scale_factor, dim): """通过调整 RoPE 频率实现外推""" # 原始频率 freqs = 1.0 / (base_theta ** (torch.arange(0, dim, 2) / dim))
# NTK 缩放:低频分量缩放更多,高频分量保持不变 # 这保证了局部位置关系不被破坏 scale = scale_factor ** (dim / (dim - 2)) freqs_scaled = freqs / scale
return freqs_scaled
# YaRN (Yet another RoPE extensioN) 方法def yarn_rope(base_theta, scale_factor, dim, gamma=0.1, beta=0.5): """YaRN:结合 NTK 和注意力温度调整""" freqs = 1.0 / (base_theta ** (torch.arange(0, dim, 2) / dim))
# 三段式频率缩放 # 高频段:保持不变 # 中频段:插值 # 低频段:完全缩放 critical_freq = gamma / scale_factor
freqs_scaled = torch.where( freqs < critical_freq, freqs / scale_factor, # 低频:缩放 freqs # 高频:不变 )
# 注意力温度调整 attention_scale = 1.0 + 0.1 * math.log(scale_factor)
return freqs_scaled, attention_scaleMeta 在 LLaMA 3 中使用 RoPE theta 从 10000 提升到 500000 的策略,本质上就是一种频率缩放,让模型在更长的位置范围内仍保持有效的注意力分布。
方案2:稀疏注意力
# Sliding Window Attention (Mistral 使用)def sliding_window_attention(Q, K, V, window_size=4096): """每个 token 只关注局部窗口""" seq_len = Q.size(1) output = torch.zeros_like(Q)
for i in range(seq_len): start = max(0, i - window_size + 1) # 只计算窗口内的注意力 local_Q = Q[:, i:i+1, :] local_K = K[:, start:i+1, :] local_V = V[:, start:i+1, :]
attn = torch.matmul(local_Q, local_K.transpose(-2, -1)) attn = F.softmax(attn / math.sqrt(Q.size(-1)), dim=-1) output[:, i:i+1, :] = torch.matmul(attn, local_V)
return output
# 通过多层堆叠,窗口外的信息可以逐层传递# 窗口 4096 × 层数 32 = 理论感受野 131K方案3:Mixture of Depths
2024 年的一种新思路:不是所有 token 都需要全量注意力计算。
# Mixture of Depths (MoD)def mixture_of_depths(layer, x, capacity_ratio=0.5): """只让部分 token 参与完整注意力计算""" # 路由器判断哪些 token 需要"深度思考" importance_scores = router(x) # [batch, seq_len, 1]
# 选择前 50% 重要的 token topk_mask = importance_scores.topk( int(x.size(1) * capacity_ratio), dim=1 )
# 重要 token 做完整注意力 important_tokens = x[topk_mask.indices] processed = layer.attention(important_tokens)
# 不重要的 token 用残差连接跳过 output = x.clone() output[topk_mask.indices] = processed
return output3.3 Lost in the Middle 问题
2023 年斯坦福的研究发现:LLM 在长上下文中对中间位置的信息有明显的”遗忘”倾向。
| 文档位置 | 检索准确率 |
|---|---|
| 开头 | ~95% |
| 中间 | ~55% |
| 末尾 | ~90% |
这个发现对 RAG 设计有重要影响:检索到的文档应该放在上下文的开头或末尾,而不是中间。
3.4 Needle in a Haystack 测试
为了评估长上下文模型的真实能力,业界开发了”大海捞针”(Needle in a Haystack)测试:
# Needle in a Haystack 测试def needle_in_haystack_test(model, context_length, needle_position): """在长文本中的特定位置插入关键信息,测试模型能否检索到"""
# 1. 构建长上下文(填充无关文本) haystack = generate_filler_text(context_length - 200)
# 2. 在指定位置插入"针"(关键信息) needle = "独角兽的喜欢的食物是草莓味的棉花糖"
# 3. 将针插入到指定位置 full_context = insert_at_position(haystack, needle, needle_position)
# 4. 提问 question = "独角兽喜欢什么食物?"
# 5. 测试模型能否回答 answer = model.generate(f"{full_context}\n\n{question}")
return needle in answer # 是否正确检索到针不同模型在不同上下文长度和位置的表现差异很大。GPT-4 Turbo 和 Claude 3 在大部分位置都能正确检索,但早期模型在中间位置的表现明显下降。
四、对比:RAG vs Long Context
4.1 核心差异
| 维度 | RAG | Long Context |
|---|---|---|
| 知识来源 | 外部检索,实时获取 | 一次性加载到上下文窗口 |
| 更新成本 | 更新索引即可,无需重新训练 | 需要重新输入全部内容 |
| 适用数据量 | 可检索 TB 级知识库 | 受限于上下文窗口(128K-1M) |
| 检索精度 | 依赖检索质量,可能有噪声 | 全文可见,无信息丢失 |
| 推理成本 | 检索 + 生成,成本可控 | 超长上下文推理成本很高 |
| 延迟 | 检索增加额外延迟 | 首 token 延迟高(prefill 慢) |
| 可解释性 | 可追溯来源(引用) | 难以定位信息来源 |
4.2 选择框架
4.3 成本对比
以处理 100 篇文档(约 50 万 tokens)为例:
| 方案 | 输入 tokens | 输出 tokens | 成本(GPT-4 定价) |
|---|---|---|---|
| Long Context | 500K | 500 | ~$15.00 |
| RAG(检索 Top-5) | 25K | 500 | ~$0.80 |
| RAG + 摘要 | 5K + 20K | 1K | ~$0.75 |
RAG 的成本优势在大量文档场景下非常明显。但随着上下文窗口的增大和推理成本的下降,Long Context 方案的经济性也在改善。
4.4 混合方案
实际生产系统往往结合 RAG 和 Long Context:
# 混合方案:RAG 为主 + Long Context 辅助def hybrid_rag_long_context(query, knowledge_base, model): # 1. RAG:从大规模知识库中检索相关文档 retrieved_docs = retrieve(query, knowledge_base, top_k=10)
# 2. 上下文评估:检索结果是否能放进上下文窗口 total_tokens = count_tokens(retrieved_docs)
if total_tokens <= model.context_limit * 0.7: # 3a. Long Context 路径:直接放入全部检索结果 context = format_docs(retrieved_docs) answer = model.generate(query, context=context) else: # 3b. RAG 优化路径:压缩后放入 # 先用小模型做摘要压缩 summaries = [compress(doc, ratio=0.3) for doc in retrieved_docs] context = format_docs(summaries) answer = model.generate(query, context=context)
# 4. 引用验证 answer_with_citations = add_citations(answer, retrieved_docs)
return answer_with_citations五、向量数据库与 Embedding
5.1 向量数据库选型
向量数据库是 RAG 系统的:
| 数据库 | 类型 | 特点 | 适用场景 |
|---|---|---|---|
| ChromaDB | 嵌入式 | 轻量、Python 原生 | 原型开发、小项目 |
| Milvus | 分布式 | 大规模、高性能 | 企业级、亿级向量 |
| Pinecone | 云托管 | 免运维、自动扩缩 | 快速上线、免运维 |
| Weaviate | 混合 | 向量+关键词混合搜索 | 需要混合检索 |
| Qdrant | Rust | 高性能、过滤能力强 | 复杂过滤条件 |
| pgvector | 扩展 | PostgreSQL 扩展 | 已有 PG 基础设施 |
# 向量数据库性能参考(百万级向量)performance = { "Milvus": {"QPS": "~10,000", "延迟": "<10ms", "召回率": ">95%"}, "Qdrant": {"QPS": "~8,000", "延迟": "<5ms", "召回率": ">95%"}, "Weaviate": {"QPS": "~5,000", "延迟": "<15ms", "召回率": ">93%"}, "ChromaDB": {"QPS": "~2,000", "延迟": "<20ms", "召回率": ">90%"},}5.2 Embedding 模型
Embedding 模型决定了检索质量的上限:
| 模型 | 维度 | MTEB 得分 | 特点 |
|---|---|---|---|
| text-embedding-3-large | 3072 | 73.2% | OpenAI 最高质量 |
| text-embedding-3-small | 1536 | 70.4% | 性价比好 |
| bge-large-en-v1.5 | 1024 | 64.2% | 开源最佳(英文) |
| bge-m3 | 1024 | - | 多语言支持 |
| m3e-large | 1024 | - | 中文优化 |
| gte-Qwen2-7B-instruct | 3584 | 75.3% | 开源最强 |
# Embedding 模型选择建议def choose_embedding_model(scenario): if scenario == "生产环境、质量优先": return "text-embedding-3-large" # OpenAI,成本较高 elif scenario == "中文为主、开源部署": return "bge-m3" # BAAI,多语言 elif scenario == "英文、开源、高质量": return "gte-Qwen2-7B-instruct" # 阿里,开源最强 elif scenario == "低成本原型": return "text-embedding-3-small" # OpenAI,便宜5.3 索引优化
对于大规模知识库,索引构建的质量直接影响检索效果:
# 索引构建最佳实践def build_quality_index(documents): index_entries = []
for doc in documents: # 1. 语义分块 chunks = semantic_chunk(doc, max_tokens=512, overlap=50)
for chunk in chunks: # 2. 丰富元数据 entry = { "text": chunk.text, "embedding": embed(chunk.text), "metadata": { "source": doc.source, "title": doc.title, "page": chunk.page_number, "section": chunk.section_header, "timestamp": doc.last_updated, } } index_entries.append(entry)
# 3. 构建多级索引 # 一级:文档级摘要索引(用于粗筛) doc_summaries = [summarize(doc) for doc in documents] summary_index = build_index(doc_summaries)
# 二级:段落级详细索引(用于精检) chunk_index = build_index(index_entries)
return {"summary_index": summary_index, "chunk_index": chunk_index}六、工程实践
6.1 RAG 评估
RAG 系统需要从检索和生成两个维度评估:
| 评估维度 | 指标 | 说明 |
|---|---|---|
| 检索质量 | Recall@K | 前K个结果中相关文档比例 |
| 检索质量 | MRR | 第一个相关文档的排名倒数 |
| 检索质量 | NDCG@K | 考虑排序的检索质量 |
| 生成质量 | Faithfulness | 回答是否忠于检索文档 |
| 生成质量 | Relevancy | 回答是否切题 |
| 生成质量 | Answer Correctness | 回答是否事实正确 |
Ragas 和 TruLens 是两个常用的 RAG 评估框架。
6.2 生产环境 RAG 架构
6.3 常见失败模式
| 失败模式 | 原因 | 解决方案 |
|---|---|---|
| 检索不到相关文档 | Embedding 质量差/索引不全 | 混合检索、HyDE |
| 回答编造信息 | 检索文档不够、模型幻觉 | 提高检索量、加事实核查 |
| 回答不完整 | 上下文窗口不足 | 压缩上下文、多轮检索 |
| 延迟过高 | 检索 + 重排 + 生成串行 | 缓存、并行、流式 |
| 来源不可追溯 | 未做引用生成 | 训练引用能力、后处理 |
七、总结
RAG 和 Long Context 是 LLM 知识增强的两条互补路线:
| 方案 | 优势 | 劣势 | 最佳场景 |
|---|---|---|---|
| Naive RAG | 简单、成本低 | 检索噪声大 | 快速原型 |
| Advanced RAG | 质量高、可解释 | 组件多、调试复杂 | 生产环境 |
| Modular RAG | 灵活可组合 | 架构复杂 | 企业级应用 |
| Long Context | 全局理解、无信息丢失 | 成本高、有中间遗忘 | 长文档分析、代码理解 |
| 混合方案 | 兼顾效率与质量 | 需要调优 | 大规模知识库 |
2024-2025 年的趋势是两条路线走向融合:RAG 系统利用更长的上下文窗口容纳更多检索结果,Long Context 模型也引入检索机制减少无效信息。未来的知识增强系统很可能是”智能检索 + 长上下文理解”的深度结合。
参考资料
- Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks — RAG 原始论文(Lewis et al., 2020)
- Precise Zero-Shot Dense Retrieval without Relevance Labels — HyDE 论文(Gao et al., 2022)
- Longformer: Long-Document Transformer — Longformer(Beltagy et al., 2020)
- Lost in the Middle: How Language Models Use Long Contexts — 中间位置遗忘(Liu et al., 2023)
- Extending Context Window of Large Language Models via Positional Interpolation — 位置插值(Chen et al., 2023)
- YaRN: Efficient Context Window Extension of Large Language Models — YaRN(Peng et al., 2023)
- GraphRAG: Unlocking LLM Discovery on Narrative Private Data — GraphRAG(Edge et al., 2024)
- CoQA: A Conversational Question Answering Challenge — CoQA 数据集(Reddy et al., 2018)
- REALM: Retrieval-Augmented Language Model Pre-Training — REALM(Guu et al., 2020)
- DPR: Dense Passage Retrieval for Open-Domain Question Answering — DPR(Karpukhin et al., 2020)
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时






