削减 90% Claude Code API 费用的 Cache 设计
Cache 在生产环境中断的那一小时,API 账单比前三天加起来还高。同一天,Anthropic 工程师发文解释了根本原因。
昨天有一项紧急的生产工作。进行到一半,prompt cache 突然失效了。那一个小时产生的 API 账单,比前三天加起来还要高。
时机有点戏剧性。就在当天晚上,Anthropic 的 Claude Code 创始工程师 Thariq 和同在 Anthropic 的 Lance Martin 先后发文,专门讲了 prompt caching 的设计逻辑。读完他们的文章,我才意识到:我的 cache 脆弱,是设计层面的问题,不是偶发故障。
下面是我从这两篇文章中提炼出的核心内容,结合刚刚亲历的生产事故过了一遍滤网。
前缀匹配,意味着顺序就是一切
Anthropic API 的 prompt caching 工作方式是从请求开头逐 token 做前缀匹配。只要有一个字符与缓存版本不同,从那个位置往后的所有内容全部变成 cache miss,没有部分匹配,没有跳过机制。
Claude Code 团队把 prompt 的排列顺序当作基础设施来对待。静态系统提示词放最前面,然后是 CLAUDE.md,然后是会话上下文,对话消息放在最后,因为每一轮都会发生变化。这种排列方式确保了开销大、内容稳定的前缀在整个会话的每次请求中都能被缓存复用。
命中缓存的 token 只需要支付正常输入 token 价格的 10%。这个差距解释了为什么 cache 一旦失效,感觉就像价格涨了 10 倍。
我犯的错误是在系统提示词里嵌了一个时间戳。每次请求都会生成新的时间戳,意味着最开头的 token 每次都不一样,后面所有内容都无法命中缓存。一个调试日志放错了位置,每次请求超过 10 万 token 的内容就全部按全价计算。
Claude Code 团队还提到,工具定义的非确定性排序也会导致 cache miss。如果工具在不同请求之间序列化顺序发生变化,即使工具本身没有任何改动,cache 也会从那个位置开始失效。
通过消息推送更新,而不是修改系统提示词
当会话中途上下文发生变化,比如文件被修改、时间更新、模式切换,第一反应往往是去改系统提示词。这个做法要避免。每次修改系统提示词,都会让整个已缓存前缀全部失效。
Claude Code 的处理方式是:第一次请求之后,系统提示词保持不动。变化的上下文通过下一条用户消息传入,用 system-reminder 标签包裹。模型读取的效果完全一样,但 cache 前缀得以保留。
Plan Mode 是一个典型例子。切换到 Plan Mode 理论上可能需要替换工具定义,这会破坏 cache。Claude Code 的做法是把它实现成一个工具调用 EnterPlanMode,让模型自己决定何时调用。工具集始终不变。当模型判断遇到了难题,它可以自行进入 Plan Mode,无需任何系统提示词修改。
同样的逻辑也适用于模型切换。在对话中途换模型,会让 cache 完全失效。Claude Code 的解决方案是让不同的模型以子 Agent 的形式在独立上下文中运行,保持父对话的 cache 完整。
隐藏工具,而不是删除工具
MCP 服务器可以加载几十个工具。每次请求都带上全部工具定义开销很大,但在请求之间删除工具会破坏 cache,因为工具定义属于缓存前缀的一部分。
Claude Code 团队的解决方案是 defer_loading。他们不把完整的工具 schema 包含进去,而是插入只含工具名称和 defer_loading: true 标志的轻量占位符。这些占位符每次保持相同的顺序,cache 前缀因此保持一致。当模型真正需要某个工具的完整 schema 时,它会调用 ToolSearch 工具按需加载。
这个模式在 Anthropic API 中今天就可以使用。你可以在自己的 Agent 中实现同样的占位符加按需搜索方式。
Manus 的 peakji 把 cache 命中率称为生产 Agent 最关键的单一指标。昨天之后,我完全认同。
上下文压缩隐藏着一个 cache 陷阱
当对话填满了上下文窗口,需要进行压缩:汇总历史记录,在精简后的形式中继续。直觉上的做法是用一个摘要提示词调用 API。但如果这次摘要调用使用了不同的系统提示词或不同的工具定义,就无法匹配现有缓存。你需要在开销最高的时刻,对超过 10 万 token 的整段对话做全价处理,没有任何 cache 收益。
Claude Code 的解决方案是在压缩调用中复用父对话完全相同的系统提示词和工具定义。只有最后一条用户消息变成压缩指令。父对话的缓存前缀依然匹配,因此只需要为新消息和摘要输出支付全价。
Anthropic 此后已将这个模式内置到 API 中,作为 compaction 功能提供。他们还发布了自动缓存功能,只需在请求体中设置一次 cache_control,即可自动处理 cache 断点。
Cache 命中率作为运营指标
Claude Code 团队监控 cache 命中率的方式,和运维团队监控系统可用性一样认真。数字下降,就当作故障来处理。
这个视角改变了我对 prompt 设计的思考方式。每次编辑系统提示词、每次调整工具顺序、每次会话中途切换模型,都是潜在的故障事件。命中缓存的 token 是最便宜的 token,昨天我切实体验到了另一种选择有多贵。
订阅通讯
获取关于我最新项目、文章以及 AI 和 Web 开发实验的更新。