数据截至 (上游 commit b77d61291399)
活区与缓存安全 — 为什么「删历史消息」是错的
30 秒导读: 几乎所有「上下文压缩」项目的第一直觉是"历史太长了,删几条老消息"。Headroom 在自己的代码库里把这个直觉判成了架构级错误:老消息躺在 provider 的 prompt cache 里,读它们只要 0.1 倍价钱;你删一条老消息,后面所有缓存全部作废,要按 1.25 倍重写一遍。本章讲 Headroom 怎么把「能动的字节」缩小成一个叫 活区(live zone) 的窄窗口,以及围绕这个窗口建的一整套安全设施。
1. 先算一笔账:删历史为什么是亏的
先不谈代码,谈钱。Anthropic 的 prompt cache 对同一段前缀有三种计价:
| token 类型 | 相对普通输入 token 的价钱 | 代码里的常量 |
|---|---|---|
| 普通输入(没进缓存) | 1.0 | —— |
缓存写入 cache_creation(5 分钟档) | 1.25 | CACHE_WRITE_MULTIPLIER |
| 缓存写入(1 小时档) | 2.0 | CACHE_WRITE_MULTIPLIER_1H |
缓存读取 cache_read | 0.1 | CACHE_READ_MULTIPLIER |
三个常量都写死在 crates/headroom-core/src/compression_policy.rs:136-144(CACHE_WRITE_MULTIPLIER / CACHE_WRITE_MULTIPLIER_1H / CACHE_READ_MULTIPLIER)。
关键在于 prompt cache 命中要求字节完全相同,而且是「前缀」语义:缓存命中的是从头开始一段连续的字节。你在第 K 条消息上改了一个字节,第 K 条之后的所有缓存全废。
于是"删一条老消息省 2000 token"的真实收支是:
省下: 2000 token 不再被读 → 2000 × 0.1 = 200
赔上: 它后面 50000 token 的缓存作废,
下一轮要重新写一遍 → 50000 × (1.25 − 0.1) = 57500
净亏两百多倍。项目自己在 REALIGNMENT/00-overview.md 的开篇把旧架构的心智模型直接判死:旧的 IntelligentContextManager 给每条消息打分、按分数丢消息,还把 frozen_message_count 硬编码成 0——等于每次压缩都从第 0 条开始删,"为每个触发它的客户炸掉 Anthropic prompt cache"。整改的结论一句话:passthrough is sacred(不动字节是神圣的),只压活区。
提醒:上面这段是作者自述的改造史,可作背景;本章所有结论以当前源码为准。
2. 顶层全景:一次请求里,哪些字节允许被动
这张图从上到下就是 Anthropic /v1/messages 请求体的规范顺序。读法:越靠上越"冷"(在缓存里、绝不能动),越靠下越"热"(模型马上要读它作答)。
┌──────────────────────────────────────────────┐
│ system 提示词 │ ← 缓存热区
├──────────────────────────────────────────────┤ 一个字节都不动
│ tools[] 工具定义 │ (E1/E2 的确定性排序除外)
├──────────────────────────────────────────────┤
│ messages[0 .. frozen_message_count-1] │ ← 地板以下 = 冻结区
│ (客户自己打了 cache_control 标记的前缀) │
├──────────────────────────────────────────────┤
│ 中间的历史轮次 / 最新 assistant 消息 │ ← 仍然不动
├══════════════════════════════════════════════┤
│ 最新的 user 消息 │ ★ 活区(唯一可改)
│ ├ tool_result 块 → 可压 │
│ ├ text 块 → 可压 │
│ └ tool_use/thinking/compaction → 排除 │
└──────────────────────────────────────────────┘
术语先钉死,后面不换词:
- 缓存热区(cache hot zone) —— 已经进了 provider 缓存、字节必须原样的部分。
- 活区(live zone) —— 模型将要"针对它"作答的那些块,改它们不会让已缓存的前缀失效。
- 地板
frozen_message_count—— 活区的下界,消息下标。 - 天花板 —— 最新那条
role == "user"的消息。