Skip to content

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) * diversity

2.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 窗口大小对质量的影响

上下文大小可容纳块数覆盖范围忠实度生成质量
4K2-3 块上下文不足可能误解
8K4-6 块中等较好的平衡点
16K8-15 块典型生产配置
32K16-30 块很宽极高复杂场景适用
100K+50+ 块极宽极高但注意"大海捞针"效应

五、面试视角

问题答案要点
上下文窗口有限时,如何选择保留哪些文档块?优先保留:1) 相关性分数高的;2) MMR 多样性排序防止内容重复;3) 更重要是来源权威的。裁剪低分和重复块
MMR 排序比单纯 Top-K 好在哪里?Top-K 可能导致多个块内容高度重叠(都来自同一段),浪费窗口。MMR 在相关性和多样性之间平衡,覆盖更多不同角度的信息
上下文压缩的 trade-off?优点:在有限窗口内装入更多有效信息。缺点:额外 LLM 调用增加延迟和成本,压缩过程可能丢失细节
MapReduce RAG 适用什么场景?当问题需要综合多处文档才能回答时(如"公司所有福利政策汇总")。单文档问答不需要,因为增加了不必要的延迟
长上下文模型是否彻底解决了窗口问题?没有。1) 长窗口不等于模型能有效利用所有信息("大海捞针"问题);2) 更长的输入 = 更高的成本和延迟;3) 冗余信息仍会稀释注意力

📚 相关链接

  • **生成模型选择与Prompt设计** — Prompt 设计与窗口管理配合
  • **检索与重排序** — 检索结果的质量影响窗口管理
  • **RAPTOR与分层检索** — 分层摘要解决窗口限制的方案
  • <- 返回 **生成融合索引**

Knowledge4J — Java 知识库