第 8 章 / 共 21 章
上下文压缩:先便宜的,后昂贵的
上一章留下的问题:到这里,messages 数组的增长来源已经很多了——工具输出、文件内容、技能正文、子代理的结论、清单更新。在一个真实规模的项目上跑三十来轮,你会撞上那堵墙:API 返回 prompt_too_long,任务直接中断。
而且这堵墙没有商量余地:上下文窗口是硬约束,你唯一能做的是主动决定丢掉什么,否则系统会替你决定——用崩溃的方式。
8.1 四层流水线,按代价排序
关键设计原则一句话:先做便宜的,再做昂贵的。
“便宜”指的是不需要额外调用模型的纯文本操作,“昂贵”指的是要花一次模型调用去做摘要。
| 层 | 做什么 | 模型调用 | 丢失程度 |
|---|---|---|---|
| L3 大结果落盘 | 超过阈值的工具结果写到磁盘,上下文里只留标记和一小段预览 | 0 | 可恢复(模型能再读回来) |
| L1 中段裁剪 | 保留开头几条(任务上下文)和最近几十条(当前工作),中间用占位符替换 | 0 | 不可恢复 |
| L2 旧结果占位 | 把较早的工具结果块换成一行”此处结果已压缩,需要请重新执行” | 0 | 不可恢复,但可重跑 |
| L4 历史摘要 | 让模型把整段对话总结成一段摘要,替换掉全部消息 | 1 | 大量丢失,原文落盘但模型读不回 |
执行顺序有一个必须记住的坑:
messages[:] = tool_result_budget(messages) # L3 先跑
messages[:] = snip_compact(messages) # L1
messages[:] = micro_compact(messages) # L2
if estimate_token_count(messages) > THRESHOLD:
messages[:] = compact_history(messages) # L4 最后
**L3 必须在 L2 之前。**因为 L2 会把大段内容替换成占位符——如果 L3 还没来得及把原文落盘,那些内容就永久消失了。顺序在这里不是风格问题,是数据丢失问题。

8.2 各层在做什么
L1 中段裁剪:对话的两头最有价值——开头有任务定义,结尾是当前进展;中间那一大段最容易冗余。保留前 3 条和后 47 条,中间换成一句”(此处省略若干轮)“。裁剪时必须成对处理 tool_use 和 tool_result:切掉一个 tool_use 却留下它的 tool_result,API 会直接报错。
L2 旧结果占位:保留最近 3 条工具结果的原文(模型很可能马上还要用),更早的换成 "[更早的工具结果已压缩。如需要请重新执行。]"。注意那句”如需要请重新执行”——它在告诉模型”这个信息不是消失了,是可以重新获取的”。
L3 大结果落盘:超过阈值(比如 200KB)的结果写到 .task_outputs/tool-results/ 下,上下文里留一个文件路径和前 2000 字符的预览。模型需要完整内容时,可以用 read_file 把它读回来。这一层是四层里唯一”可逆”的。
L4 历史摘要:前三层跑完还是超,就只能请模型自己出手——把整段对话总结成一段结构化摘要(目标是什么、已经查明了什么、改过哪些文件、有哪些约束),然后用这段摘要替换掉所有历史消息。原始对话会存到 .transcripts/ 下留档,但模型读不回来——它对模型而言就是不存在了。
8.3 走一遍:一次真实的压缩
假设一次大重构跑到第 35 轮,估算 token 数达到 180K:
| 步骤 | 动作 | token 变化 |
|---|---|---|
| 起点 | 35 轮,含 3 次大文件读取(各 60K 字符) | 180K |
| L3 | 3 个大结果落盘,各留 2000 字符预览 | 180K → 132K |
| L1 | 保留前 3 + 后 47,中间无可裁(本例总数不足) | 132K |
| L2 | 早于最近 3 条的工具结果换占位符 | 132K → 71K |
| 判定 | 71K < 阈值(比如 120K) | 不触发 L4 |
三层免费操作省下了 60% 的上下文,一次模型调用都没花。这就是”先便宜后昂贵”的实际收益。只有当免费手段用尽还是超标时,才动用 L4。
8.4 工程现实:兜底与熔断
即使这样,仍可能在调用时收到 prompt_too_long。这时需要一个兜底压缩:更激进地从尾部往前砍,但保住最近几条工具结果,然后重试一次。
关键是只重试一次。因为压过一次之后,第二次压缩几乎不可能让上下文显著变小——继续重试只是在烧钱。同理,连续失败达到一定次数(比如 3 次)应该触发熔断,直接退出并告诉用户,而不是无限循环。
还有一件生产系统会做、教学版没做的事:压缩之后的恢复。摘要替换掉历史后,模型可能会”忘了”自己正在编辑哪个文件、当前计划是什么。成熟实现会在压缩后主动把最近改过的文件和当前任务清单重新贴回上下文。