缓存点设计
agent 成本 = token 数 × 每 token 单价——压缩管前者、缓存管后者,本篇只谈单价这根杠杆。缓存命中率是它上面最大的一个数 (命中 = 未缓存读的 10%)。讲清四个断点位置、怎么保持前缀字节稳定、压缩对缓存的后果;并说明这套断点只有 Anthropic 需要。
目标直说
一个长运行 agent 的成本可以拆成两个乘数:token 数 × 每 token 单价。这是同一坐标轴上的两根杠杆——压缩 缩减 token 数,缓存把已发送的前缀降到读取价。本篇只谈单价这根杠杆。
单价这根杠杆上最大的一个数,是 cache 命中率。按 Anthropic 定价,命中 = 未缓存读的 10%,写入 = 125%(5 分钟 TTL)或 200%(1 小时 TTL)。每个输入 token 恰好落进三个桶之一,期望单价就是三者按占比加权:
C = 0.10 × p(hit) + 1.25 × p(write, 5m) + 1.00 × p(uncached)
其中 C 是每输入 token 均价(以未缓存价为 1.0× 基准),三者占比之和 p(hit) + p(write) + p(uncached) = 1;1h TTL 把写入项系数从 1.25 换成 2.00。
输出单独计、不缓存。以 Sonnet 4.6 为例($/M token):输入 $3.00、缓存读 $0.30(0.1×)、缓存写 $3.75(5m,1.25×)/$6.00(1h,2×)、输出 $15.00。一个 30 步任务、稳定前缀每步重发、首步写其余 29 步读:每 M 前缀 token 从不缓存的 30 × $3.00 = $90,降到 $3.75 + 29 × $0.30 = $12.45——约 1/7。所以工程目标只有一个:把 p(hit) 推到尽可能接近 1,并在对话增长、压缩触发时守住。
只对 Anthropic:手动放缓存断点是 Anthropic 特有的要求。OpenAI、Gemini、Qwen 等多数 provider 在服务端自动缓存前缀,不接受也不需要
cache_control;对它们下面这套断点机制是 no-op(createCachedInstructions/markPrefixCacheBoundary对非 Anthropic 模型原样返回)。
缓存怎么命中
一个断点(cache_control)在它所在的位置,把从请求起点到这一块为止的整段前缀哈希一次,拿这个哈希去查:
- 之前写过一模一样的前缀 → 直接读缓存,这段只花 0.1×;
- 没查到 → 冷写一条新条目(花 1.0×–1.25×),供后续请求命中。
所以缓存匹配的是整段前缀、不是单个 block。唯一不变式由此而来:任何更靠前的字节一变,它之后每个断点的前缀哈希都变、全部作废。而“查到”要两件事同时成立:
- 字节一致:从请求起点到断点的每个字节,与那条老条目逐字节相同。
- 距离够近:老条目当初的写入位置,落在当前断点往前 20 个 block 以内——回溯只走这 20 格,更早的写入看不见。
任一不成立就是一次冷写。请求按 tools → system → messages 三层排列;改动落在哪一层,那一层连同它后面的全部作废、前面的照旧存活——所以越靠前的改动废得越狠:
| 改动落在 | 作废(括号内为存活部分) |
|---|---|
| 工具定义(任意字节 / 增删 / 换序) | tools + system + messages(全废) |
| 系统提示词(任意字节) | system + messages(tools 存活) |
tool_choice / thinking 开关 / 图片增删 | 仅 messages(tools + system 存活) |
还有一条硬门槛:断点处前缀低于模型下限时,写入被静默跳过(Opus / Haiku 4.5 需 4,096 token,Sonnet 4.6 需 2,048,更早的 Sonnet / Opus 1,024)。
断点放在哪
断点该放哪不靠 playbook,两条规则就定死了——它们直接来自上一节的两个命中条件:
- 只放在“下一轮字节不变”的位置。 断点的价值是让它的前缀下一轮被命中;前缀里只要有一个字节每轮都变,它就永远是冷写、白放。这条决定了哪些位置有资格放。
- 相邻断点间距 ≤ 20 个 block。 回溯只往前走 20 格,所以要把断点排成一条从尾到头、每档 ≤ 20 block 的梯子,回溯每一步都能踩到一档已缓存的。只在头部放一个不行——尾部一旦离它超过 20 block 就够不到,每轮都为整段头部冷写。这条决定了为什么要好几个。
按这两条规则扫一遍请求,字节稳定的位置从前到后正好这几处——这就是全部的槽点:
| 位置 | 是什么 | 为什么稳 | 覆盖 |
|---|---|---|---|
| ① 系统提示词(+工具) | 头部,最后一个 system 块上的断点 | 整个 session 不变 | tools + system(每请求 30–80% token) |
| ② 压缩点 | Task Context 摘要块 | 一个压缩 epoch 内不变 | 到摘要为止的整段前缀 |
| ③ 尾块 | 最后一块(reminder 之前) | 本轮内不变(新 step 追加在其后) | 到本轮为止的整段前缀 |
三档各司其职:① 最稳、最大,是主力降本;② 把回溯从尾部接力回头部(没有它,位置 ① 迟早够不到);③ 是“这轮写、下轮命中”的前瞻档。(代码还在 ② 与 ③ 之间按需补一档已完成轮边界,单步 > 20 block 时多垫一级台阶兜底;正好用满 Anthropic 的 4 断点上限,无摘要时干净退化回 ①③。)
reminder 不是槽点。 它每步都在变、违反规则 1,所以刻意追加在 ③ 之后、排除在缓存前缀外。由此得到一条硬性顺序:先打 ③ 的断点、再追加 reminder——反了,变动的 reminder 就混进前缀,下一轮必然 miss。(打法按 role:system 走 message 级、其余走 block 级,以挺过 adapter 的同 role 合并。)
保持前缀字节稳定
四个位置放对了,剩下唯一会打坏缓存的就是前缀里混进每次都变的字节——两个“语义相同、字节不同”的 block 对缓存就是两个 block。守则:
- 头部冻结:别把时间戳、当前日期、user / session ID、随机 nonce 插进 system 或工具描述;动态内容往后放到 messages 里。
- 确定性序列化:JSON 固定键顺序(
Map/Set迭代顺序、Intl本地化、未排序的json.dumps都是隐形杀手)。 - 工具列表每 session 定死一次:顺序一变(哪怕字母序 vs 定义序)四个断点一次废光。
反过来,有些负载开缓存反而更贵、直接别开:一次性 / 每请求独一无二的提示、头部混了高方差内容(先修方差再开)、低于最小前缀、以及无复用的扇出批处理。
压缩对缓存的后果
压缩是唯一会改写 block 流本身的操作(记忆、JIT 加载、子 agent 隔离都只往后追加、不动既有字节)。所以压缩器怎么改,直接决定缓存活不活:
- 头部不可变:压缩绝不能碰位置 1 之前的 block。改了系统头,下一轮四个断点同时失效,没有局部恢复。
- 整块替换,不原地编辑:被压的整轮换成一个 summary block(它就成了位置 2 的锚);tool_result 按固定长度整块截断,绝不改 block 内部字段——子块一改,其后每个前缀 hash 全变。
- 节拍决定摊销:每次压缩 = 一个新 cache epoch(一次贵写 + 多次便宜读)。按任务边界压、别每几轮压一次,让读写比稳在 ~10 以上。
差距是二元的:同样一个 30 步任务,压缩器“追加式 + 整块 + 位置 2 就位”命中率 ≈ 0.9,而“中途重写系统头或原地改 tool_result”命中率 ≈ 0.2——总成本约差 5 倍。
度量
命中率是头牌指标:
hit_ratio = cache_read_input_tokens
/ (cache_read_input_tokens + cache_creation_input_tokens + input_tokens)
良好长任务第 3 轮后应 > 0.85、持续会话 > 0.90;低于 0.5 是 bug 不是折衷。最快的自检是冷启动断言:第 1 轮 cache_creation_input_tokens > 0 且 cache_read_input_tokens == 0,第 2 轮 cache_read_input_tokens > 0。第 2 轮读为零 = 前缀跨轮变了,回上一节逐条查非确定性。
markPrefixCacheBoundary 在 Anthropic 路径 emit cache.breakpoints_placed(字段 messagesCount / stepIndex / placedAt / lastRole),作为命中率 dashboard 的主数据源:placedAt 长度长期为 1(只有尾块)就说明中途锚一直没放、回溯够不到头部。
延伸阅读
- ← 上下文压缩 —— 那个稳定的压缩点就是位置 2 的来源。
- Context Management —— 注意力预算;命中率是它的成本维度。
- Operations / Dashboards ——
cache.breakpoints_placed的落点。
Sources
- Prompt caching —— Anthropic, Claude API docs(block 模型、20-block 回溯、失效规则、定价的一手参考)
- Effective Context Engineering for AI Agents —— Anthropic, 2026(缓存感知的压缩指引)