现场是这样的。Anthropic SDK 0.34 调 claude-3-5-sonnet,system 里放了 6000 字的公司制度,按文档加了 cache_control 的 ephemeral 标记,但返回的 usage 里 cache_read_input_tokens 一直是 0,只有第一轮 cache_creation 有数值,后面几轮也没命中,账单按全价结算。

中间走过弯路,把「请求里 user 消息排在 system 之前,块顺序被打乱」当成主因,改完现象没变。

真正麻烦的地方在于,几个原因的现象几乎一样。每轮在 system 开头拼了带时间戳的标题,前缀整段都变了;cache_control 加在了最后一个内容块上,前缀本身不稳定;缓存有效期只有 5 分钟,两次请求间隔超过窗口。

先把完整请求体打出来核对块顺序

python -c "import json;b=json.load(open('req.json'));print([x['role'] for x in b['messages']],b['system'][0].get('cache_control'))"

去掉时间戳后发一次同样的请求

curl -s https://api.anthropic.com/v1/messages -H "x-api-key: $KEY" -H 'anthropic-version: 2023-06-01' -d @req.json | jq '.usage'

隔 30 秒连发三次观察 cache_read 变化

for i in 1 2 3; do curl -s https://api.anthropic.com/v1/messages -H "x-api-key: $KEY" -H 'anthropic-version: 2023-06-01' -d @req.json | jq -c '.usage'; sleep 30; done

剩下的就是把稳定前缀抽成模板固定下来

python render_prompt.py --template sys.tpl --no-timestamp --out req.json

别只看报错消失,验证要多做一步。第二次请求的 cache_read_input_tokens 接近 6000,这部分按缓存单价计费,连续五次请求都能稳定命中。

容易忽略的是,缓存按前缀逐字节比对,任何动态内容都要放到断点之后;有效期一到就得重建,别把长任务寄望在缓存上。同一份配置在测试环境跑通,不代表线上没事,两边的默认值经常不一样。

作者 9823189