15. RAG 中的 Context Window 管理策略:上下文太长或太短怎么办?

整理 RAG 中的 Context Window 管理策略:上下文太长或太短怎么办? 的核心概念、工程要点与面试回答。

简单回答

上下文太长会导致 token 成本高、延迟大、关键信息被淹没(Lost in the Middle);上下文太短会导致信息不足、模型回答不完整。管理策略包括:控制召回数量和 Rerank 阈值来调节上下文长度、对长 Chunk 做摘要压缩、合理排列文档顺序、使用 Map-Reduce 或 Refine 策略分批处理,以及根据 query 复杂度动态调整上下文量。

详细解释

上下文太长的问题

当检索返回了很多 Chunk 全部塞进 Prompt 时,会产生几个问题。

Token 消耗和成本。LLM API 按 token 收费,Prompt 中塞 10 个 Chunk 和塞 3 个 Chunk,成本可能差 3~5 倍。对于高并发系统,这个差异在成本上非常显著。

延迟增加。输入 token 数越多,Prefill 阶段的耗时越长。对延迟敏感的场景需要控制 Prompt 总长度。

信息被稀释和淹没。研究表明("Lost in the Middle",Liu et al., 2023),LLM 在处理长上下文时,对开头和结尾位置的信息利用度最高,中间部分的信息容易被忽略。塞了 10 个 Chunk 进去,排在第 5、6 位的 Chunk 即使包含了正确答案,模型也可能没有关注到。

上下文噪声增加。塞进去的 Chunk 不可能每一条都高度相关,其中混杂的低相关内容(噪声)会干扰模型判断。有时候少给几个高质量的 Chunk 效果反而比多给一堆混杂的好。

上下文太短的问题

上下文太短的典型场景是只检索了 1~2 个 Chunk,其中包含的信息不足以完整回答用户的问题。模型在信息不足时,要么说"信息不够无法回答"(如果 Prompt 有明确指令),要么基于自身参数知识补充(可能引入幻觉)。

另一种情况是 Chunk 本身太小,单个 Chunk 缺乏上下文,模型无法理解其含义。比如一个 Chunk 只有一句"该指标上升了 15%"——上升了什么指标?和什么比?在什么时间段?全部缺失。

管理策略

控制召回数量和 Rerank 阈值。 最直接的方式是不要盲目取 Top-K,而是设一个 Rerank 分数的阈值。分数低于阈值的 Chunk 即使在 Top-K 里也不放进上下文。这能保证送给模型的内容都有一定的相关性。K 的值也可以根据 query 复杂度动态调整——简单事实性问题取 Top-3 可能就够了,复杂问题取 Top-8。

文档摘要和压缩。 如果 Chunk 很长或者需要放入多个 Chunk 导致上下文过长,可以先用 LLM 对每个 Chunk 做摘要压缩,然后把压缩后的内容放进上下文。这样既保留了关键信息,又减少了 token 消耗。LangChain 提供了 ContextualCompressionRetriever,就是做这件事的。代价是多了一次 LLM 调用。

文档排序策略。 根据"Lost in the Middle"现象,最相关的文档应该放在上下文的开头或结尾,而不是中间。一种常见的做法是按 Rerank 分数降序排列(最相关的在最前面),或者采用"夹心"排序——最相关的放开头,次相关的放结尾,不太相关的放中间。

Map-Reduce 策略。 当需要综合大量文档时(比如"总结这 20 份报告的共同结论"),不可能把 20 份报告全部塞进上下文。Map-Reduce 的做法是先对每份报告分别做摘要(Map),再把所有摘要合并起来让模型做最终总结(Reduce)。这样每一步的上下文都不会太长。

Refine 策略。 和 Map-Reduce 类似但是顺序处理的。先用第一个 Chunk 生成初步答案,然后带着初步答案和第二个 Chunk 让模型"修正和补充",依次处理所有 Chunk。适合需要逐步积累信息的场景。

Parent-Child 分层。 前面提到过,用小 Chunk 做检索(精度高),但返回大 Chunk 给模型(上下文充分)。这能在不增加太多上下文长度的前提下保证信息的完整性。

动态上下文管理

更高级的做法是根据 query 的特征动态决定上下文策略。比如用一个分类器或规则判断 query 的类型和复杂度:简单事实性问题用少量精确 Chunk、复杂推理问题用更多 Chunk 加多跳检索、总结性问题用 Map-Reduce。这需要更多的工程投入,但对用户体验和成本控制都有明显好处。

面试时可以这样答

上下文管理是 RAG 工程中容易被忽视但影响很大的环节。太长了成本高、延迟大、关键信息被淹没,太短了信息不足导致回答不完整或幻觉。

我一般的做法是先用 Rerank 分数做阈值过滤,分数低的 Chunk 不放进上下文,保证信噪比。然后根据 query 复杂度动态调整取多少条——简单问题 3 条左右,复杂问题可以到 8 条。排序上遵循"Lost in the Middle"的结论,最相关的放开头。

如果上下文实在太长,可以做文档摘要压缩——用 LLM 对每个 Chunk 先做一轮摘要,再把摘要放进上下文。需要综合大量文档时用 Map-Reduce 策略。如果上下文信息不足,可以用 Parent-Child 分层策略,检索时用小 Chunk 保精度,返回时给大 Chunk 保上下文。

这些策略不是互斥的,实际项目中经常组合使用。核心思路就是让模型拿到的上下文"精而不杂、够用但不过量"。

常见追问

  1. "Lost in the Middle" 的实验结论具体是什么?在实际项目中验证过吗?
  2. Map-Reduce 和 Refine 策略各自适合什么场景?
  3. 动态上下文管理在工程上怎么实现?分类器怎么训练?