第 37 章:分层压缩

"把旧的压掉不就行了"——可你四十步之前读的那个文件,正是你此刻在改的那个。


⏱️ 快速通道(5 分钟掌握核心)

  1. 年龄是「不再相关」的代理指标,不是它本身——而代理有时候就是错的
  2. 按距离末尾的远近分三层:完整、语义摘要、元数据存根
  3. 承载正文的工具要有下限——它们只降到首尾截断,永不降到元数据
  4. 压缩本身要花模型调用;按趟设上限,否则它会变成一条隐藏的调用瀑布
  5. 每次重写都必须保住 tool call 与 tool result 的配对,否则 Provider 直接拒收

10 分钟路径:37.1-37.3 → 37.5 → Kocoro OSS Lab


公开源码快照:实现细节取自 Kocoro 公开仓库 origin/main4ec6772 提交,复核日期为 2026-07-27。章节讲的是不变量;具体常量应以当前源码为准。

37.1 从那个「它还在改的文件」说起

一个 Agent 重构做到第三十轮。很早的时候——第 3 轮——它对 internal/auth/session.go 跑了 file_read,而整个任务就是围绕这个文件的。那条结果现在在二十七条消息之前。

一刀切的按年龄压缩看了一眼,认定这是条旧工具结果,把它剥成元数据:

[file_read: internal/auth/session.go  412 , 18.2 KB]

按规则完全正确,效果是灾难。模型手上没有它正在编辑的那个文件了。于是它再读一遍——18 KB 回到 Prompt,你刚省下的 token 全还回去,外加一次往返。再过几轮,这条读取又老了,于是再来一遍。

规则对年龄的判断没错,它错在年龄意味着什么。年龄是「大概率不再需要」的代理指标,对大多数工具输出这个代理成立。对你正在动的那个文件,它完全不成立。

37.2 三层,按距离末尾的远近

结构是一道以「离对话末尾几条消息」为刻度的阶梯:

Tier 3——最新的 8 条工具结果原封不动。 这是工作记忆,模型正在上面推理,动它省下的还不如动它花掉的。

Tier 2——距离 8 到 19 之间压缩,但保留实质。 超过 2,000 字符的结果,由一次模型调用做语义摘要;不到这个长度、或者没有摘要器可用时,退回到机械的首尾截断。无论哪条路,内容都以缩减形态活下来。

Tier 1——距离 20 以外只留元数据。 工具名、参数、体量。够你知道发生过什么,不够你重建它。

Tier 2 压缩后的输出封顶 300 字符。这很激进,而且是故意的:在那个距离上,这条结果的职责是提醒模型它学到了什么,不是重新教它一遍。

注意这个降级的形状——它不是线性的。近期内容不动,中年内容丢形式但留意义,陈旧内容只留骨架。每一步变的是「保留什么」,不只是「保留多少」。

37.3 修好 37.1 的那条下限

Tier 1 对大多数工具是对的,对一类工具是错的:那些正文本身就是你调用它的理由的工具。

file_readgrepglobdirectory_list,以及所有匹配 browser_* 的,都有一条 Tier 2 下限。它们在距离 20 以外降级为首尾截断,但永不掉进元数据存根。你在第 3 轮读的那个文件,会永远保住它的开头和结尾。

理由是:对这些工具做元数据存根,销毁的恰恰是「让这次调用值得做」的那样东西。[grep: "handleRequest" 47 处匹配] 告诉你发生过一次搜索,但它一个字都没告诉你匹配到了什么——而那是任何人跑 grep 的唯一理由。

浏览器工具属于这一类,理由相同,值得说明白:页面快照本身就是任务载荷。 一个在网页流程里干活的 Agent,需要知道页面上有什么,而不是「观察过一个页面」。

可推广的判据:这条结果的元数据存根,能让模型继续下去,还是逼它重跑工具? 如果只剩重跑一条路,那你没有压缩——你只是把成本推迟了,还搭上一次往返。

37.4 压缩不是免费的

Tier 2 的语义摘要是模型调用。一趟里压十二条结果,就是为了省上下文而多打十二次模型调用——很容易比那份上下文本身还贵。

所以每趟的语义压缩次数封顶为 2。Tier 2 区间里的其余部分退回机械首尾截断,那是零成本的。而且上限是对尝试次数设的,不是成功次数——这样一个坏掉的摘要器就没法悄悄烧光预算、最后还让你什么都没压成。

旁边还跑着第二条豁免:有些工具完全跳过 micro-compaction,逻辑和 Tier 2 下限一样。把一份页面快照摘要成散文,丢掉的正是模型接下来要在上面导航的那份结构。

值得带走的规则:要花模型调用的压缩必须有预算,而且预算要设在尝试上。 否则摘要器状态不好的那天,你的上下文优化会变成成本放大器。

37.5 每一次重写都可能搞砸这一轮

两个 Tier 都在原地重写消息内容,而这件事有两种坏法。

配对。 一个 tool_use 块和它对应的 tool_result 是一个整体。只重写其中一个——或者丢掉一条结果而它的调用还在——Provider 会直接拒收整个请求。这也是为什么压缩开始之前必须先建一张从 tool_use_id 到工具名和参数的映射:它得知道每条结果曾经是什么,才写得出对应的元数据存根,同时还得把这一对留完整。

缓存。 每一次原地重写都会让该条消息之后的 Prompt Cache 失效。这不可避免——重写的全部意义就是改内容——但它必须可归因。每次重写都发出一个带 Tier 标记的 cache-compaction 事件,这样缓存命中率掉下来时,你分得清是 Tier 1、Tier 2,还是完全另一码事干的。

没打点的重写,正是让缓存调试变得不可能的那种。你看见命中率跌了,却没有任何办法从十几种 Prompt 整形机制里找出是哪一个。第 39 章讲的是这套完整纪律。

37.6 快照证据

观察4ec6772 源码位置
最新 8 条工具结果保持完整loop.go L2565
Tier 2 压缩输出封顶 300 字符loop.go L2566
Tier 1 从距离 20 开始loop.go L6104
承载正文工具的 Tier 2 下限loop.go L6095
Tier 2 压缩路径loop.go L6211
语义摘要阈值 2,000 字符microcompact.go L19
每趟语义尝试上限为 2microcompact.go L23
跳过 micro-compaction 的工具microcompact.go L40

以上描述的是一个有日期的实现快照,不是普遍契约。

37.7 什么时候分层是错的模型

按年龄分层假设相关性是单调衰减的。当它不是时,这个模型就垮了。

一个长时间的对比任务,可能在最后需要把第 2 条结果和第 40 条结果并排看。一次调试会话,可能在探索二十步之后回头看最早那段 stack trace。这两种情况里,「越新越相关」的假设干脆就是假的,而且下限也救不了你——下限保的是首尾,不是中间。

如果这就是你的负载,答案不是调一个更好的 Tier 边界,而是外溢:写到磁盘上,让模型有意识地去取,而不是靠「在转录里的位置」去猜它将来会想要什么。

另外:短会话不要分层。低于 Tier 3 窗口就没有可压的东西,照样跑这套机制,只是多加一条可能出错的代码路径。

37.8 常见的坑

承载正文的工具没有下限。 就是 37.1——模型会重读,而你为了省 token 付了 token 一次往返。

语义压缩不设上限。 打十二次摘要调用,去省一份不值十二次摘要调用的上下文。

上限设在成功次数而不是尝试次数。 一个坏掉的摘要器会永远重试,而且永远撞不上预算。

破坏工具调用配对。 Provider 拒收整个请求,而错误信息指向的是某条消息下标,不是你的压缩代码。

重写不打点。 缓存命中率掉了,遥测里没有任何东西告诉你是哪个机制干的。

重复压缩已压缩的内容。 一个 300 字符的 Tier 1 存根不需要再压一趟。记录哪些已经缩减过了。

Kocoro OSS Lab(10 分钟上手)

  1. isTier2FloorTool,注意 browser_ 是用前缀匹配的,而不是逐个列出来。想想新增一个 playwright 工具时,这个选择买到了什么。
  2. 找到 compressOldToolResults 在压缩之前建 tool_use_id 映射的地方,想想没有它的话 Tier 1 存根会长什么样。
  3. 在你自己的 Agent 里列出所有工具,逐个标注:二十步之后它的正文还有决策价值吗,还是元数据就够?这张清单就是你的下限。

划重点

  1. 年龄是「不再相关」的代理,而代理会失效。 你正在改的那个文件,又老又不可或缺。
  2. 降级要换种类,不只是换体量。 完整 → 有意义无形式 → 只剩骨架。
  3. 承载正文的工具需要下限。 一次 grep 的元数据存根,销毁的正是这次 grep 存在的理由。
  4. 给压缩本身设预算,而且设在尝试上。 否则省上下文会变成成本放大器。
  5. 保住配对,给每次重写打点。 前者坏的是请求,后者坏的是你调试缓存的能力。

下一章:第 38 章从结果体量转到 Schema 体量——填满 Prompt 的另一半。

引用本文 / Cite
Zhang, Wayland (2026). 第 37 章:分层压缩. In AI Agent 架构:从单体到企业级多智能体. https://waylandz.com/ai-agent-book/%E7%AC%AC37%E7%AB%A0-%E5%88%86%E5%B1%82%E5%8E%8B%E7%BC%A9/
@incollection{zhang2026aiagent_37_-,
  author = {Zhang, Wayland},
  title = {第 37 章:分层压缩},
  booktitle = {AI Agent 架构:从单体到企业级多智能体},
  year = {2026},
  url = {https://waylandz.com/ai-agent-book/%E7%AC%AC37%E7%AB%A0-%E5%88%86%E5%B1%82%E5%8E%8B%E7%BC%A9/}
}