这个坑值得记一笔。Anthropic 的 prompt caching 用 5 分钟 TTL,业务把长文档放在缓存断点上,某一轮 run 到第 4 分半才续跑,账单里 cache_creation 又记了一次全量。统计脚本按命中请求数除以总请求数算出 78%,实际 cache_read 的 token 只占 41%,把断点前移也没有变化。

刚开始盯着「同一会话里换了 model 参数,缓存键不同直接未命中」,折腾了半个多小时,排掉了。

原因不止一个,得分开看。TTL 从写入时刻开始算,续跑时已经过期只能重建;统计口径把 creation 和 read 混在一起算成命中;并发请求在缓存写入完成前就发出,全部走了 creation。

从响应 usage 里拆出三个字段分别统计

python -c "import json;u=json.load(open('r.json'))['usage'];print(u.get('cache_creation_input_tokens'), u.get('cache_read_input_tokens'), u.get('input_tokens'))"

接着把两次调用的时间差打出来

python -c "import time;print('gap', time.time()-float(open('t1').read()))"

再往下把长任务拆成 4 分钟以内的两段

python -c "print('split at 240s')"

等前面都确认过,再用同一组参数重放一次验证是否命中

curl https://api.anthropic.com/v1/messages -H "x-api-key: $KEY" -H "anthropic-version: 2023-06-01" -d '{"model":"claude-3-7-sonnet-20250219","max_tokens":64,"system":[{"type":"text","text":"...","cache_control":{"type":"ephemeral"}}],"messages":[{"role":"user","content":"hi"}]}'

别只看报错消失,验证要多做一步。续跑时 cache_read_input_tokens 大于 0 且 creation 为 0,按 token 口径算出的命中率与账单一致。

提醒一点。缓存 TTL 是固定窗口不是滑动窗口,长任务要么缩短间隔要么接受重建,统计命中率必须用 token 口径而不是请求次数。线上能稳定复现的问题都不算难,难的是只在大流量下才出现的那种。

作者 9823189