上下文压缩
前面几节讨论了如何往上下文里放内容——提示工程决定写什么,Skills 决定按需加载什么,Agent 状态栏决定注入什么元信息。但随着多轮交互的深入,上下文会不断膨胀。本节讨论相反的方向:如何从上下文中减少内容——什么时候压缩、怎么压缩、为什么即使上下文没满也应该压缩。
为什么需要压缩:不只是长度问题
压缩上下文有三个截然不同的动机,理解这一点对设计压缩策略至关重要。
第一,解决长度约束和成本约束。 这是最直观的原因:上下文窗口有限,工具调用结果动辄数万字符,几轮交互就可能撑满窗口,任务被迫中断。同时 token 越多,API 成本越高,推理延迟也会急剧上升。
第二,提升思考质量——总结后的知识比原始形式更利于模型使用。 这个动机更深层,也更容易被忽视。即使上下文窗口足够大,把所有原始信息堆在上下文里也不是最优选择。考虑一个具体的例子:agent 在执行一个复杂任务的过程中,通过 10 次网页搜索积累了关于某个主题的信息。这些搜索结果以原始形式散落在上下文的各个位置。当 agent 需要基于所有这些信息做最终决策时,它必须在数万 token 中反复“检索”相关片段,注意力被分散,关键信息容易被遗漏。而如果在第 10 次搜索之后,先用一次 LLM 调用将已有信息做一次结构化总结,模型在后续思考时就可以直接使用这个精炼的知识表示。
这个现象的根源在于注意力机制的本质:上下文学习的内部机制更像检索而非推理。模型擅长从已有内容里“查找”,却不擅长在一次前向传播里主动“归纳统计”。因此压缩的第二个价值,是把需要思考才能得到的结论,变成可以直接检索的知识。更深层的问题在于,长上下文会导致检索精度的下降:明明上下文窗口还远没有满,但 agent 突然找不到关键信息,或者反复纠结于一个早已解决的问题——这种现象被称为上下文腐化(Context Rot)。它与上下文溢出(窗口用完)是不同的问题:溢出是“装不下了”,腐化是“装得下但找不到了”——后者更隐蔽,因为 agent 表面上还在正常工作,只是决策质量悄然下降。
第三,缓解上下文焦虑(Context Anxiety)。 当模型认为上下文窗口即将耗尽时,可能在任务尚未完成前提前收尾。因此,即使距离硬性上限还有余量,提前压缩也可能改善后续决策。
压缩与 KV Cache:看似矛盾,实则互补
前面反复强调 KV Cache 要求上下文前缀保持不变,但压缩不就是要修改上下文中间的内容吗?关键在于理解压缩发生的时机和位置。压缩不是在单次 API 调用的过程中修改上下文,而是在两次 API 调用之间,由 agent 框架对消息列表进行预处理:
- System Prompt 和 Tool Definitions 永远不动——这是上下文最前面的“静态前缀”,KV Cache 持续缓存。
- 压缩的对象是对话历史中的 tool results——当框架用压缩后的摘要替换原始的工具输出时,替换位置之后的缓存会失效,但之前的缓存仍然有效。
- 这是一个有意识的权衡:不压缩,上下文膨胀到超出窗口限制,任务直接失败;压缩后,虽然损失了部分缓存,但上下文长度可控且信息密度更高。因此压缩的频次需要权衡——最好在上下文接近阈值时批量压缩,而不是每轮都压。
生产级的分层压缩机制
在生产环境中,成熟的 agent 系统通常不会只采用单一策略,而是将多种策略组合为分层的压缩机制——不同类型的信息有不同的保质期,压缩策略应当与信息的预期生命周期匹配。以 Claude Code 的做法为参照,一个成熟的上下文管理系统通常包含五个层次:
- 工具结果预算控制:大体积的工具输出存到磁盘,模型只看摘要预览。替换决策一旦做出就被冻结,以保证缓存的一致性。
- 噪声直接删除:低价值的内容直接移除,不做摘要——对噪声做摘要只是在浪费 token。
- API 层微压缩:通过 API 层的上下文编辑能力,指示服务端从前缀中移除指定的工具结果,本地消息保持不变。适合在上下文即将溢出、反正要付出这次重建代价时使用。
- 归档式摘要:逐轮做结构化摘要(像 git log 那样保留每轮的独立记录,而非像 git squash 那样合并成一条),保留对话的逻辑脉络。
- 全量压缩:由 LLM 驱动的完整压缩,作为最后手段,并配备连续失败的熔断器。
注意这五层的排列顺序:前三层实现成本最低、对缓存的扰动可控,应当优先使用;后两层成本较高但压缩效果更强,作为兜底手段。
压缩策略的设计原则
- 信息价值的非均匀分布:关键的决策点(如人员名单)的价值高于支撑性的证据,更高于冗余的噪声。
- 语义完整性:“Sutskever 于 2024 年 5 月离开 OpenAI”不能压缩成“Sutskever 离开”——时间和公司名是不可丢失的关键信息。
- 任务相关性:同样的内容在“查找创始人名单”和“了解个人背景”两个不同的任务下,应该产生不同的压缩结果。
- 压缩即理解:有效的压缩需要深层的语义理解能力,而且显式压缩的结果是可审查的、可跨会话复用的。
由此得到一份保留优先级:架构决策和关键约束不得摘要;已修改的文件列表、验证状态(pass/fail)、未解决的 TODO 与回滚笔记必须保留;工具输出可以删除,仅保留 pass/fail 结论。此外,UUID、hash、IP、端口、URL、文件名等标识符必须原样保留——一旦把 commit hash 改错一位,后续的工具调用就会直接失效。
隔离优于压缩:子 agent 上下文隔离
压缩是在信息已经进入上下文之后做减法,而一个更釜底抽薪的思路是:让大体积的中间信息根本不进入主上下文。这就是子 agent 上下文隔离——主 agent 把“读取大量文件”“在代码库中大范围搜索”这类会产生海量中间内容的任务,委派给一个独立的子 agent;子 agent 在自己的上下文中完成探索,只把几百 token 的结论性摘要回传给主 agent。这本质上是用隔离代替压缩:压缩是有损的、需要额外 LLM 调用的事后补救;隔离则让噪声从一开始就与主上下文绝缘,主 agent 的 KV Cache 前缀也完全不受影响。代价是任务描述必须自包含、目标明确。完整设计见子代理。
相关阅读
- 子代理——隔离优于压缩的实现。
- KV Cache 设计——压缩为何采用阈值触发而非即时触发:让前缀在一 turn 各步之间稳定、使缓存持续见效。
来源
- Effective Context Engineering for AI Agents — Anthropic, 2025
- Compaction — Anthropic, Claude API 文档