mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
3102 字
9 分钟
RAG 与 Long Context:LLM 的知识增强
2025-01-01

大语言模型的知识来自训练数据,一旦训练完成就固化了。它不知道最新的新闻、无法访问企业内部文档、也不记得上个月的政策变化。检索增强生成(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》中系统提出。

flowchart TB subgraph 原始 RAG 架构 A["用户问题 q"] --> B["检索器 Retriever<br>DPR (Dense Passage Retriever)"] B --> C["Top-K 相关文档<br>d1, d2, ..., dk"] C --> D["生成器 Generator<br>BART"] D --> E["生成回答<br>p(a|q, d1...dk)"] F["文档库<br>Wikipedia 索引"] --> B end

原始 RAG 的关键公式:

p(aq)=dtop-k(q)p(aq,d)p(dq)p(a|q) = \sum_{d \in \text{top-k}(q)} p(a|q, d) \cdot p(d|q)

其中 p(dq)p(d|q) 是检索概率,p(aq,d)p(a|q,d) 是给定文档的生成概率。RAG 实际上是对所有 top-k 文档的生成结果做边际化。

1.3 两种 RAG 变体#

Lewis 等人提出了两种 RAG 变体:

RAG-Sequence:每个文档独立生成完整回答,然后加权合并

# RAG-Sequence
def 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 流程简单直接:检索 → 拼接 → 生成。

flowchart LR A["用户问题"] --> B["Embedding<br>向量化"] B --> C["向量检索<br>Top-K"] C --> D["拼接上下文<br>问题 + 文档"] D --> E["LLM 生成"] E --> F["最终回答"]

Naive RAG 的问题:

问题描述影响
检索不准确语义相似但不相关的文档被检索回答偏题
信息冗余多个文档包含重复信息浪费上下文窗口
上下文窗口溢出检索的文档超过模型上下文限制信息截断
缺乏来源追踪无法知道回答来自哪个文档可信度低

2.2 Advanced RAG(2023)#

Advanced RAG 在检索前后增加了多个优化步骤:

flowchart TB subgraph Advanced RAG A["用户问题"] --> B["查询重写<br>Query Rewriting"] B --> C["查询扩展<br>Query Expansion"] C --> D["混合检索<br>Dense + Sparse"] D --> E["重排序<br>Reranking"] E --> F["上下文压缩<br>Context Compression"] F --> G["LLM 生成"] G --> H["最终回答"] end

查询重写(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_queries

HyDE 是 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 将系统拆分为可组合的模块,根据任务需求灵活编排:

flowchart TB subgraph Modular RAG 模块 A["索引模块<br>Indexing"] --> A1["文档解析<br>PDF/HTML/表格"] A --> A2["分块策略<br>Chunking"] A --> A3["向量化<br>Embedding"] B["检索模块<br>Retrieval"] --> B1["查询路由<br>Query Routing"] B --> B2["混合检索<br>Dense + Sparse"] B --> B3["多跳检索<br>Multi-hop"] C["后处理<br>Post-processing"] --> C1["重排序<br>Reranking"] C --> C2["上下文压缩<br>Compression"] C --> C3["去重过滤<br>Dedup"] D["生成模块<br>Generation"] --> D1["引用生成<br>Citation"] D --> D2["事实核查<br>Fact Check"] D --> D3["置信度评估<br>Confidence"] end
模块功能可选实现
索引模块文档处理与向量化语义分块、递归分块、父子文档
检索模块相关文档获取BM25、DPR、混合检索、多跳检索
路由模块查询分发意图分类、查询改写
融合模块多源结果合并RRF、互信息融合
重排模块精细化排序交叉编码器、LLM 重排
压缩模块上下文精简摘要压缩、token 裁剪
生成模块答案生成引用生成、事实核查

**智能分块(Chunking)**是 Modular RAG 中常被忽视但极其关键的环节:

# 分块策略对比
chunking_strategies = {
"固定长度": {
"方法": "每 512 token 切一块",
"优点": "简单",
"缺点": "可能切断语义完整性",
},
"递归字符分割": {
"方法": "按段落 → 句子 → 字符递归分割",
"优点": "保持段落完整性",
"缺点": "块大小不均匀",
},
"语义分块": {
"方法": "用 embedding 相似度确定边界",
"优点": "语义完整",
"缺点": "计算开销大",
},
"父-子文档": {
"方法": "小块检索,大块生成",
"优点": "检索精准 + 上下文丰富",
"缺点": "索引复杂",
},
}

父-子文档策略是 2024 年的热门方案:检索时用小块(如 128 token)提高精度,生成时用父块(如 512 token)提供完整上下文。

2.4 GraphRAG:知识图谱增强#

2024 年微软发布的 GraphRAG 将知识图谱引入 RAG 流程,解决全局性问题的回答:

flowchart TB subgraph GraphRAG 流程 A["原始文档"] --> B["实体抽取<br>LLM 提取实体和关系"] B --> C["知识图谱构建"] C --> D["社区检测<br>Leiden 算法"] D --> E["社区摘要<br>多级摘要"] E --> F["查询<br>全局搜索 vs 局部搜索"] end

GraphRAG 的创新在于:传统 RAG 只能做局部检索(找到相关文档片段),无法回答”这篇报告的总体观点是什么”这类全局性问题。GraphRAG 通过构建知识图谱并对社区进行摘要,使得全局性问题也能被有效回答。

三、Long Context#

3.1 上下文长度演进#

LLM 的上下文窗口在短短几年内增长了数百倍:

时间模型最大上下文注意力复杂度
2020GPT-34KO(n²) = 16M
2022PaLM8KO(n²) = 64M
2023Claude 1100KO(n²) = 10B
2023GPT-4 Turbo128KO(n²) = 16.4B
2024Gemini 1.5 Pro1MO(n²) = 1T
2024LLaMA 3128KO(n²) = 16.4B

标准 Transformer 注意力的 O(n²) 复杂度使得长上下文的计算成本急剧上升。1M 上下文的自注意力计算量是 4K 的约 62500 倍。

3.2 长上下文的技术方案#

扩展上下文窗口主要面临三个挑战:位置编码外推、注意力计算效率、和中间层信息丢失。

flowchart TB subgraph 长上下文三大挑战 A["位置编码外推"] --> A1["训练时没见过的位置<br>导致注意力崩塌"] B["计算效率"] --> B1["O(n²) 复杂度<br>内存和延迟暴增"] C["中间层信息丢失"] --> C1["Lost in the Middle<br>中间位置信息被忽略"] end subgraph 解决方案 D["RoPE 外推<br>NTK-aware / YaRN"] --> A E["稀疏注意力<br>Sliding Window / GQA"] --> B F["注意力偏置<br>位置感知训练"] --> C end

方案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_scale

Meta 在 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 output

3.3 Lost in the Middle 问题#

2023 年斯坦福的研究发现:LLM 在长上下文中对中间位置的信息有明显的”遗忘”倾向。

flowchart LR subgraph 信息检索准确率 vs 位置 A["开头位置<br>准确率高"] --> B["中间位置<br>准确率大幅下降"] B --> C["末尾位置<br>准确率恢复"] end
文档位置检索准确率
开头~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 核心差异#

维度RAGLong Context
知识来源外部检索,实时获取一次性加载到上下文窗口
更新成本更新索引即可,无需重新训练需要重新输入全部内容
适用数据量可检索 TB 级知识库受限于上下文窗口(128K-1M)
检索精度依赖检索质量,可能有噪声全文可见,无信息丢失
推理成本检索 + 生成,成本可控超长上下文推理成本很高
延迟检索增加额外延迟首 token 延迟高(prefill 慢)
可解释性可追溯来源(引用)难以定位信息来源

4.2 选择框架#

flowchart TB A{"需要外部知识?"} -->|"否"| B["纯 LLM 即可"] A -->|"是"| C{"知识是否实时更新?"} C -->|"是"| D["RAG"] C -->|"否"| E{"知识总量多大?"} E -->|"< 128K tokens"| F["Long Context"] E -->|"> 128K tokens"| G["RAG 或混合方案"] D --> H{"需要全局理解?"} H -->|"是"| I["RAG + 摘要缓存"] H -->|"否"| J["纯 RAG"]

4.3 成本对比#

以处理 100 篇文档(约 50 万 tokens)为例:

方案输入 tokens输出 tokens成本(GPT-4 定价)
Long Context500K500~$15.00
RAG(检索 Top-5)25K500~$0.80
RAG + 摘要5K + 20K1K~$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混合向量+关键词混合搜索需要混合检索
QdrantRust高性能、过滤能力强复杂过滤条件
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-large307273.2%OpenAI 最高质量
text-embedding-3-small153670.4%性价比好
bge-large-en-v1.5102464.2%开源最佳(英文)
bge-m31024-多语言支持
m3e-large1024-中文优化
gte-Qwen2-7B-instruct358475.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 架构#

flowchart TB subgraph 生产级 RAG 架构 A["用户请求"] --> B["API Gateway"] B --> C["查询预处理<br>意图识别 + 查询改写"] C --> D["检索层<br>混合检索 + 重排序"] D --> E["上下文构建<br>压缩 + 去重 + 排序"] E --> F["LLM 生成<br>引用 + 事实核查"] F --> G["后处理<br>格式化 + 安全过滤"] G --> H["返回用户"] I["知识库管理"] --> J["文档解析管道"] J --> K["分块 + Embedding"] K --> L["向量索引更新"] M["监控与评估"] --> N["检索质量监控"] M --> O["生成质量评估"] M --> P["延迟与成本追踪"] end

6.3 常见失败模式#

失败模式原因解决方案
检索不到相关文档Embedding 质量差/索引不全混合检索、HyDE
回答编造信息检索文档不够、模型幻觉提高检索量、加事实核查
回答不完整上下文窗口不足压缩上下文、多轮检索
延迟过高检索 + 重排 + 生成串行缓存、并行、流式
来源不可追溯未做引用生成训练引用能力、后处理

七、总结#

RAG 和 Long Context 是 LLM 知识增强的两条互补路线:

方案优势劣势最佳场景
Naive RAG简单、成本低检索噪声大快速原型
Advanced RAG质量高、可解释组件多、调试复杂生产环境
Modular RAG灵活可组合架构复杂企业级应用
Long Context全局理解、无信息丢失成本高、有中间遗忘长文档分析、代码理解
混合方案兼顾效率与质量需要调优大规模知识库

2024-2025 年的趋势是两条路线走向融合:RAG 系统利用更长的上下文窗口容纳更多检索结果,Long Context 模型也引入检索机制减少无效信息。未来的知识增强系统很可能是”智能检索 + 长上下文理解”的深度结合。


参考资料#

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

RAG 与 Long Context:LLM 的知识增强
https://blog.souloss.com/posts/machine-learning/llm-paper-history/rag-and-long-context-knowledge-enhancement/
作者
Souloss
发布于
2025-01-01
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时