上下文窗口悬崖:长对话的应用层管理策略
一场持续 90 分钟的客服会话。一个已经浏览文档一小时的研究助手。一个已经处理十几个文件的编程 Agent。所有这些最终都会撞上同一堵墙——但撞上时,它们不会大声报错。它们只会变得迟钝。
模型开始遗忘二十分钟前做出的决定。它自相矛盾。本应显而易见的检索结果莫名消失。用户察觉到有些不对劲,却说不清助手为何变差了。这就是上下文窗口悬崖:不是一个硬性错误,而是一种渐进的质量崩塌——而你的监控系统几乎肯定没有衡量它。
扩大上下文窗口并不能解决这个问题。拥有百万 Token 窗口的模型在处理中间位置内容时仍然会退化;即便不退化,你也在为多出 100 倍的 Token 买单,而模型实际关注的只是其中一小部分。解决方案是应用层的上下文管理——明确策略什么留在窗口里、什么被压缩为摘要、什么完全移到窗口之外。
为什么这个悬崖比看起来更难处理
朴素的心智模型是:上下文像桶一样被填满——装得下就装,装不下就报错。现实更糟。
质量在硬性上限之前就已经开始退化。随着上下文增长,注意力机制需要将焦点分散到更多 Token 上,信噪比随之下降。在生产环境中运行过量化评测的团队普遍反映:模型质量从额定上下文窗口的 60–70% 处就开始下降,而非 100% 处。硬性上限并不是你的有效上限。
"中间位置遗忘"问题使情况雪上加霜。针对多文档问答的研究发现,模型性能遵循一条 U 形曲线,取决于信息出现在上下文中的位置:开头(首因效应)和结尾(近因效应)的内容能可靠地被关注,而中间部分的内容则逐渐被忽略。对于 GPT-3.5-Turbo,将相关文档放在长上下文的中间位置,会导致性能低于无文档基准——这意味着检索反而适得其反。这一弱点在专门为长上下文处理设计的模型上同样存在。
框架掩盖了退化。大多数生产框架会静默截断或直接丢弃旧消息,而不通知应用层。系统提示词消失了。会话早期的工具调用输出被裁掉了。模型继续运行,仿佛拥有完整的上下文,而你却没有任何信号表明有什么改变了。较新的模型会返回显式错误,但这只能捕捉到硬性上限——而不是更早开始的软性退化。
延迟随上下文增长。输入中每增加一个 Token,都会增加处理时间。基准测试显示,在 15,000 词的上下文下,延迟比短提示词增加约 7 倍。对于交互式应用,这意味着长会话中的用户会体验到越来越慢的响应,而这往往被归因于"网络问题"或"模型繁忙"。
摘要阶梯
最常见的解决方案是定期摘要,但朴素版本——"摘要整个对话"——扩展性不好。更好的模式是分层摘要阶梯。
滚动缓冲摘要 维护一个固定长度的近期消息缓冲区(通常是最近 20–30 轮),同时维护一份涵盖之前所有内容的滚动摘要。当缓冲区填满时,最旧的消息被摘要并合并到滚动摘要中。模型始终以完整保真度看到新鲜上下文,以摘要形式看到压缩历史。合适的触发时机是上下文容量的 70–80%——在被迫截断之前进行摘要,能干净地完成压缩,而不是在对话中途打断。
层次化摘要 处理的是长文档而非对话历史。它将文档分块,独立摘要每个块,然后合并摘要并再次摘要。结果是一棵具有不同粒度的摘要树。这对 Agent 读取 200 页文档的研究工作流非常有效——但要注意错误会沿树向上传播。块摘要中的错误会成为包含它的每个更高层摘要中的错误。
实体保留摘要 是最昂贵但最可靠的变体。系统不是对散文进行摘要,而是提取实体(人物、系统、决定、未解决的问题),并将它们的结构化记录与压缩对话文本分开维护。当模型需要对特定实体进行推理时,它拿到的是精确数据而非可能失真的摘要。这对跨会话 Agent 尤为重要,因为摘要错误会跨会话累积。
关键的设计问题是:什么以完整保真度保留,什么进行压缩。好的启发式规则:逐字保留最近 N 轮(模型需要精确的近期上下文),逐字保留任何明确的用户指令或约束,逐字保留工具调用的输出(对结构化数据的摘要会引入错误)。压缩对话填充语、中间推理过程,以及已被后续信息取代的内容。
选择性保留:不是所有内容都值得保留
摘要假设所有对话历史都有一定价值。选择性保留则质疑这一假设。大多数对话包含大量冗余、已被取代或与任务当前状态无关的内容。
基于相关性评分的驱逐 为每个上下文元素根据近期性、与当前查询的语义相似度,以及该元素是否在后续轮次中被显式引用来打分。当上下文填满时,低分元素优先被驱逐。这比简单的滑动窗口方法更精确,但需要在上下文旁边维护元数据。
取代追踪 识别新信息何时明确覆盖了旧信息。如果用户说"算了,忽略那个文件——用这个",旧的文件引用在上下文中就毫无价值。追踪取代事件的系统可以主动驱逐过时信息,而不是将其继续携带。
对话阶段检测 区分探索阶段(早期轮次,许多线索尚未收束)和执行阶段(后期轮次,已选定明确路径)。在执行阶段,大部分探索上下文都是噪音。检测这一转变并压缩探索阶段,是一个高价值的驱逐机会。
需要警惕的失败模式:过于激进地驱逐看似冗余但实际上是承重约束的上下文。用户在十五轮之前陈述的初始需求,正是那种会被静默丢弃的内容。赋予约束和需求比讨论内容高得多的保留评分。
外部化:将上下文移出窗口
扩展性最强的方法是将上下文窗口视为工作内存,并将一切不必留在其中的内容外部化。
检索增强生成 是广为人知的版本:嵌入源材料,存储在向量数据库中,在查询时只检索最匹配的块。生产中关键的细节是:大规模检索精度不只靠原始嵌入相似度。混合搜索(稠密 + 稀疏)、重排序和查询扩展都能提升进入上下文的内容的相关性。三个高度相关的块几乎总是胜过十个中度相关的块,无论是质量还是成本。
Agent 记忆服务器 实现双层模式:短期记忆(当前会话,完整保真度)和长期记忆(跨会话,通过语义相似度检索)。当会话结束时,重要事实被提取并写入长期记忆。当新会话开始时,相关的长期记忆被注入为上下文。这就是 Agent 如何"记住"两周前对话中的细节,而不必在每个后续提示词中携带那段历史。
工具输出外部化 被低估了。当 Agent 调用返回大型文档或数据集的工具时,朴素的做法是将整个输出放入上下文。更好的模式是:将原始输出存储在外部,只将结构化摘要或模型所需的特定字段放入上下文,并在模型需要更多细节时通过后续工具调用提供完整输出。这种按需检索到上下文的模式,在文档密集型工作流中可以将上下文大小减少 60–80%。
语义缓存 解决的是另一个维度的问题:为什么要重复计算之前见过的上下文?语义缓存识别新查询在语义上等价于之前的查询时,直接返回缓存响应,完全绕过上下文组装和推理。在规模上,这显著降低了延迟和成本——还有一个副效应:防止频繁查询用冗余的检索结果使上下文膨胀。
Token 预算框架
所有这些策略都需要一个大多数工程师在起步时没有的心智模型:上下文窗口有一个预算,每个元素都在竞争有限的分配。
将上下文预算视为具有明确类别:
- 系统提示词:你的指令、人设、约束。这些必须完整保留。明确预留空间;绝不让对话历史将其挤出。
- 检索上下文:来自外部来源的块。将其上限设为总预算的某个比例,而非无限扩张。
- 对话历史:近期轮次完整保留,较早轮次压缩为摘要。明确定义边界。
- 工作状态:当前任务描述、未解决的问题、已做出的决定。保持精简且最新。
- 响应空间:模型需要空间来生成。这常常被遗忘,直到输出被截断。
最常见的生产失败是完全没有预算框架——只是不断追加轮次,直到某处崩溃。有了明确的预算,你可以做出有意识的权衡:在研究工作流中,给检索上下文更多预算;在多轮对话中,给对话历史更多预算。
应该添加哪些监控
大多数团队间接且滞后地衡量上下文窗口相关问题。能早期发现问题的信号:
每次请求的上下文利用率百分比。追踪分布,而不只是均值。P95 和 P99 利用率超过 75% 才是真实信号——你经常接近软性质量悬崖。
按上下文利 用率百分位分段的响应质量。使用你已有的评测指标(无论是用户评分、任务成功率还是幻觉率),并按上下文窗口的填充程度分段。如果质量在某个利用率阈值之上显著下降,你就找到了你的软性悬崖。
摘要触发率。如果在你最长的会话中每次请求都触发摘要,说明对话设计已经超出了策略的承载能力。你需要更激进的外部化方案。
系统提示词存活率。明确检查你的系统提示词是否出现在最终组装的上下文中。静默截断将其移除是一种严重的失败模式,标准错误监控不会发现它。
大多数框架做错了什么
最大的错误是将上下文管理视为事后补充——等到用户开始抱怨行为退化时才去添加。到那时,你已经让用户习惯了一种在迁移期间会退步的行为。
从第一天起就为上下文压力进行设计。在发布之前选定你的摘要触发点、外部化策略和预算框架。从第一周起就监控上下文利用率。给上下文窗口带来压力的对话几乎总是最有价值的——高级用户、复杂任务、长会话——而他们也会在质量下降时第一个察觉到。
第二个错误是将公告的上下文窗口视为有效的上下文窗口。真正重要的数字是:质量开始退化的位置(通常是最大值的 60–80%)、延迟随上下文长度如何扩展,以及你使用的具体模型在不同位置的表现。这些因模型而异,你应该在你的实际使用场景上衡量它们,而不是假设基准数字适用于你。
上下文窗口会持续变大。管理它们的工程学科需要同步跟上——不是因为上限不会增长,而是因为失败模式不会随着更多 Token 而消失。它们只是变得更难被察觉。
- https://redis.io/blog/context-window-management-llm-apps-developer-guide/
- https://redis.io/blog/context-window-overflow/
- https://aclanthology.org/2024.tacl-1.9/
- https://arize.com/blog/lost-in-the-middle-how-language-models-use-long-contexts-paper-reading/
- https://agenta.ai/blog/top-6-techniques-to-manage-context-length-in-llms
- https://medium.com/@kuldeep.paul08/context-engineering-optimizing-llm-memory-for-production-ai-agents-6a7c9165a431
