03.2 - 上下文窗口管理
定位: 解决 RAG 中的核心矛盾——可检索的文档量 vs LLM 的上下文窗口限制 面试高频度: ⭐⭐⭐⭐ 考查方式: 窗口限制问题、压缩/裁剪策略、分治处理
一、这是什么?为什么需要它?
是什么
上下文窗口管理是指在 LLM 的上下文窗口限制下,如何有效组织检索到的文档块,让模型在有限的输入空间内获得最有效的信息。
问题描述:
检索系统找出了 10 个相关文档块(共 10000 tokens)
但 LLM 的上下文窗口只有 4096 tokens
所以需要:选哪些?压缩哪些?如何编排?为什么需要专门管理?
没有上下文管理 = 截断到窗口大小(简单但危险)
如果只是简单截断到窗口大小:
丢失了排最后的 3 个文档块 -> 但这些块可能包含关键信息
前 7 个块中只有 2 个真正相关 -> 模型注意力被浪费
好的上下文管理:
裁剪掉不相关的块 -> 只保留最有信息的块
压缩长块 -> 提取关键信息
多个块融合 -> 整合相关内容
结果:在有限的窗口内,放入了最多的有效信息二、原理拆解
2.1 四层上下文管理策略
| 策略层级 | 方法 | 复杂度 | 信息保留度 | 适用场景 |
|---|---|---|---|---|
| 1. 裁剪 | Top-K 选择 + 分数阈值 | 低 | 中 | 文档质量均衡 |
| 2. 排序优化 | MMR / 差异度排序 | 中 | 中高 | 文档内容重叠多 |
| 3. 压缩 | LLM 摘要/提取 | 高 | 高 | 文档冗余多 |
| 4. 分治 | MapReduce | 高 | 极高 | 需要大量文档 |
2.2 策略 1:裁剪(最基础)
按相关性分数裁剪:
检索结果(按分数排序):
块A: 0.92 - "年假规定..." -> 保留
块B: 0.88 - "年假申请流程" -> 保留
块C: 0.75 - "请假制度概览" -> 保留
块D: 0.45 - "考勤定义" -> 丢弃(阈值0.5以下)
块E: 0.32 - "公司简介" -> 丢弃
优点:简单直接
缺点:可能丢弃相关信息2.3 策略 2:排序优化(MMR)
最大边际相关性(MMR):在相关性和多样性之间取平衡
传统 Top-K:只按相关性排序
块A: 相关度0.92
块B: 相关度0.88(和块A 90%相似 -> 说了差不多的话)
块C: 相关度0.85(和块A 85%相似)
结果:3个块都覆盖了同一信息 -> 浪费窗口
MMR 排序:
块A: 相关度0.92 + 多样性(基准) -> 选入
块B: 相关度0.88 - 和已选集合80%相似 -> 降权
块C: 相关度0.75 - 和已选集合20%相似 -> 虽然分低但信息新!-> 选入
结果:覆盖更多不同信息python
def mmr_similarity(query_vec, doc_vecs, selected_idxs, lambda_param=0.5):
"""MMR 分数计算"""
relevance = cosine_similarity(query_vec, doc_vec)
diversity = min([
cosine_similarity(doc_vec, selected_vec)
for selected_vec in selected_vecs
])
return lambda_param * relevance - (1 - lambda_param) * diversity2.4 策略 3:压缩
用 LLM 压缩/提取关键信息:
原始块(500 tokens):
"公司员工福利制度第三章第一节明确规定,员工在入职满一年后,
可根据工龄享受每年5到15天不等的带薪年假。其中工龄1-3年
享有5天,工龄3-5年10天,工龄5年以上15天..."
压缩后(100 tokens):
"带薪年假:入职1年起,工龄1-3年5天,3-5年10天,5年+15天"python
# 上下文压缩
from langchain.retrievers.document_compressors import LLMChainExtractor
compressor = LLMChainExtractor.from_llm(llm)
compressed_docs = compressor.compress_documents(
documents=retrieved_chunks,
query="年假政策"
)
# 输出:仅保留与查询相关的关键信息2.5 策略 4:分治(MapReduce)
当需要处理大量文档时:
MapReduce RAG 流程:
Map 阶段(并行):
[块1-3] -> LLM1 -> 局部回答1
[块4-6] -> LLM2 -> 局部回答2
[块7-9] -> LLM3 -> 局部回答3
Reduce 阶段(合并):
[局部回答1, 局部回答2, 局部回答3] -> LLM -> 最终综合回答
优点:可处理任意数量的文档
缺点:多轮 LLM 调用,延迟和成本增加三、图解全景
上下文管理策略选择
你的问题需要多少上下文?
|
+-- 需要 3-5 个文档块(简单问答)?
| -> 裁剪策略:Top-K 按分数选择
|
+-- 需要 5-10 个块,但内容重叠?
| -> MMR 排序:平衡相关性和多样性
|
+-- 需要 10-30 个块,但很多冗余?
| -> 压缩策略:LLM 提取关键信息
|
+-- 需要 30+ 个块,跨多文档?
| -> 分治策略:MapReduce 分步处理
|
+-- LLM 有超长上下文(如 200K)
-> 尝试全量输入 + 分层注意力四、实战验证
4.1 上下文压缩实践
python
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import LLMChainExtractor
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# 创建压缩器
compressor = LLMChainExtractor.from_llm(llm)
# 创建压缩检索器
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=retriever
)
# 压缩后检索
compressed_docs = compression_retriever.invoke("年假政策是什么?")
for doc in compressed_docs:
print(f"压缩后长度: {len(doc.page_content)} 字符")
print(f"原始来源: {doc.metadata.get('source')}")
print(f"内容: {doc.page_content[:200]}")4.2 窗口大小对质量的影响
| 上下文大小 | 可容纳块数 | 覆盖范围 | 忠实度 | 生成质量 |
|---|---|---|---|---|
| 4K | 2-3 块 | 窄 | 中 | 上下文不足可能误解 |
| 8K | 4-6 块 | 中等 | 高 | 较好的平衡点 |
| 16K | 8-15 块 | 宽 | 高 | 典型生产配置 |
| 32K | 16-30 块 | 很宽 | 极高 | 复杂场景适用 |
| 100K+ | 50+ 块 | 极宽 | 极高 | 但注意"大海捞针"效应 |
五、面试视角
| 问题 | 答案要点 |
|---|---|
| 上下文窗口有限时,如何选择保留哪些文档块? | 优先保留:1) 相关性分数高的;2) MMR 多样性排序防止内容重复;3) 更重要是来源权威的。裁剪低分和重复块 |
| MMR 排序比单纯 Top-K 好在哪里? | Top-K 可能导致多个块内容高度重叠(都来自同一段),浪费窗口。MMR 在相关性和多样性之间平衡,覆盖更多不同角度的信息 |
| 上下文压缩的 trade-off? | 优点:在有限窗口内装入更多有效信息。缺点:额外 LLM 调用增加延迟和成本,压缩过程可能丢失细节 |
| MapReduce RAG 适用什么场景? | 当问题需要综合多处文档才能回答时(如"公司所有福利政策汇总")。单文档问答不需要,因为增加了不必要的延迟 |
| 长上下文模型是否彻底解决了窗口问题? | 没有。1) 长窗口不等于模型能有效利用所有信息("大海捞针"问题);2) 更长的输入 = 更高的成本和延迟;3) 冗余信息仍会稀释注意力 |
📚 相关链接
- **生成模型选择与Prompt设计** — Prompt 设计与窗口管理配合
- **检索与重排序** — 检索结果的质量影响窗口管理
- **RAPTOR与分层检索** — 分层摘要解决窗口限制的方案
- <- 返回 **生成融合索引**