Claude Code 走中转后 Prompt Cache 不生效?2.1.237 修复 Custom Base URL 缓存
如果你用中转跑 Claude Code,升级到 2.1.237 之后打开 Status Line 看一眼 cache_read_input_tokens。要是这个数字从 0 跳到了几十万——恭喜,你之前每一轮对话都在为完全相同的内容重复掏钱。
花了多少冤枉钱
先做一道简单的算术。
Claude Code 的工作方式是每轮对话都把完整上下文重新发给 API——系统提示、工具定义、CLAUDE.md、所有历史消息,最后才是你的新消息。模型不”记得”任何东西,全靠重发。一个跑了 30 轮的会话,上下文可能已经积累到 30 万 token。
正常情况下,Prompt Cache 会识别出这次请求和上次请求的前缀是一样的,直接从缓存读取,只处理新增的部分。缓存读取的价格是标准输入的 10%。
但如果缓存没工作,这 30 万 token 每一轮都按标准输入价全额计费。以 Opus 4.6 为例(15/MTok输入),一轮对话的输入成本是4.5。如果缓存正常,其中 29 万 token 走缓存读取(0.15/MTok),只需要0.44。
一轮对话差 10 倍。跑 30 轮差 120 美元。
2.1.237 的 changelog 只有一句话,但它可能是近期影响钱包最大的一个修复:
Fixed prompt caching for sessions using LLM gateways or custom base URLs to prevent stale prompts
Prompt Cache 长什么样
在聊 Bug 之前,得先知道正常的 Prompt Cache 应该是什么表现。
API 响应里有三个关键字段:
cache_read_input_tokens:从缓存读取的 token 数,价格是标准输入的 10%cache_creation_input_tokens:写入缓存的 token 数,价格是标准输入的 125%(5 分钟 TTL)或 200%(1 小时 TTL)input_tokens:缓存断点之后的新内容,标准价
一个正常会话的节奏是这样的:第 1 轮,cache_creation 很大(冷启动,把系统提示和工具定义写入缓存),cache_read 为 0。从第 2 轮开始,cache_read 迅速增长——因为前面所有轮的内容都命中了缓存——而 cache_creation 只有每轮新增的一小段。
异常的标志只有一个:cache_read_input_tokens 持续为 0。 如果你看到它每轮都是 0,而 cache_creation 或 input_tokens 每轮都很大,说明缓存完全没在工作。每一轮都是一次”冷启动”。
为什么中转场景特别脆弱
Prompt Cache 的匹配规则极其苛刻——逐字节前缀匹配。不是”语义相同”就行,是”字节完全一致”才行。请求的前半部分哪怕多了一个空格、字段换了个顺序、Unicode 转义方式不同,整个缓存就作废。
直连 Anthropic 的时候这不是问题。Claude Code 完全控制请求的构造方式,每轮的前缀天然一致。但当你在中间插一层网关——中转服务、LiteLLM、自建代理——事情就变得脆弱了。
网关可能做一些”无害”的操作,每一个都能杀死缓存:
重新序列化 JSON,字段顺序变了——完蛋。注入额外的 header——完蛋。修改或丢弃 cache_control 字段——完蛋。对请求体做格式标准化,多了一个换行——也完蛋。
Claude Code 在请求体里通过 cache_control: {"type": "ephemeral"} 标记缓存断点。这个字段告诉 Anthropic “到这里为止的内容可以缓存”。网关必须把它原封不动地传过去。丢了这个字段,就等于在说”我不需要缓存”。
但 2.1.237 修的不只是网关透传的问题。Claude Code 自身在构造发往自定义 Base URL 的请求时,缓存逻辑就有 Bug。 也就是说,即使你的网关完美透传了所有字段,缓存也可能不生效。这是客户端侧的问题,不是网关的锅——这也是为什么只靠检查网关配置解决不了的。
怎么确认自己受不受影响
先看版本号:
claude --version
低于 2.1.237 就升级:
claude update
然后看缓存数据。最直接的方式是配一个 Status Line 脚本,从 current_usage 对象里取 cache_read_input_tokens 和 cache_creation_input_tokens,显示在状态栏上。每轮对话扫一眼就知道缓存有没有在工作。
没有 Status Line 也行。如果你用 LiteLLM,它的 Admin UI 的 Request Logs 里可以看到每个请求的 cache write / cache read token 数。用 Helicone 或者自建日志也一样——关键就是看到 cache_read_input_tokens 这个字段。
如果你有升级前的历史数据,拉一条升级前后的 cache_read 趋势线。差异应该是悬崖式的——从 0 直接跳到几十万。
升级之后 cache_read 还是 0?
2.1.237 解决的是 Claude Code 客户端侧的 Bug。但中转场景下缓存失效的原因可能不止一个。如果升级后问题仍然存在,按这个顺序排查:
网关是不是把 cache_control 吃了? 在网关侧抓一下发给 Anthropic 的请求体,看 content block 上有没有 cache_control: {"type": "ephemeral"} 字段。有些网关在转发时会自作主张地清理”未知字段”。
网关是不是重写了请求体? 即使 cache_control 没丢,JSON 重新序列化也会破坏前缀匹配。字段排序不同、空格处理不同、Unicode 转义方式不同——对人来说是同一个 JSON,对缓存来说是完全不同的请求。
LiteLLM 用户特别注意一个坑。 如果你开了负载均衡(多个 deployment),缓存是按 deployment 隔离的。第 1 轮请求发到 deployment A 写入了缓存,第 2 轮被路由到 deployment B——缓存 miss。解决办法是配置缓存感知路由:
# LiteLLM config.yaml
router_settings:
optional_pre_call_checks: ["prompt_caching"]
这样 LiteLLM 会记住哪个 deployment 有缓存,后续请求路由到同一个。
你的”中转”真的到了 Anthropic 吗? 听起来像废话,但确实有人的中转背后接的是 OpenAI 兼容接口,请求根本没到 Anthropic。Prompt Cache 是 Anthropic 服务端的能力,只有请求真正到达 Anthropic(或 Bedrock / Vertex 的 Claude 端点)时才存在。
Token 数够不够? 不同模型有最低可缓存门槛。Opus 5 / Fable 5 只要 512 token,但 Opus 4.6 / Haiku 4.5 需要 4,096 token。低于门槛的请求会静默跳过缓存,不报错。不过 Claude Code 的系统提示加上工具定义通常远超这个门槛,这条更多是排查兜底用的。
这不是第一次修了
Claude Code 在 Prompt Cache 这条线上已经修了好几轮。放在一起看,能理解为什么中转用户在过去两个月可能积累了非常可观的冤枉账单:
7 月 15 日的 2.1.211 修了 Bedrock / Vertex / Mantle / Foundry 上的一个计费回归——尾部系统上下文块被当作未缓存输入 token 计费,明明应该走缓存的内容被全价收费。8 月 18 日的 2.1.235 修了语言服务器断连重连时整个缓存被意外清除的问题——IDE 里的 LSP 抽个风,你的缓存就全没了。然后才是 8 月 20 日的 2.1.237,修了 Custom Base URL 场景下的缓存失效。
如果你还在用 2.1.211 之前的版本,可能同时踩在好几个坑上。直接升到最新版一次性解决。
直连和中转的缓存差异
直连 Anthropic(API Key 或订阅)的用户基本不需要操心——缓存是服务端自动处理的,Claude Code 控制请求构造,前缀一致性天然有保障。
Bedrock / Vertex / Foundry 用户在 2.1.211 之后也没问题了,缓存在各云厂商的服务端处理。
中转用户的情况最复杂。缓存发生在 Anthropic 的服务端(假设请求最终到了 Anthropic),但中间经过了一层你自己控制的网关。这层网关既要透传 cache_control 字段,又不能改写请求体,如果还开了负载均衡还得配缓存感知路由。任何一个环节出问题,缓存就废了。
还有一个容易忽略的点:订阅用户默认是 1 小时缓存 TTL,API Key 用户默认只有 5 分钟。你停下来泡杯咖啡回来,缓存可能就过期了。API Key 用户可以通过 ENABLE_PROMPT_CACHING_1H=1 切到 1 小时 TTL——缓存写入成本从 1.25 倍涨到 2 倍,但读取仍然是 0.1 倍。只要你的会话超过几轮,多付的写入成本和省下的读取成本相比完全不值一提。
一件事
升级到 2.1.237,看一眼 cache_read_input_tokens。如果它从 0 变成了一个大数字,你以前多花的钱已经花了,但至少从现在开始不会再多花了。
参考:Claude Code changelog · How Claude Code uses prompt caching · Prompt caching (API 文档) · Connect Claude Code to an LLM gateway · LiteLLM Prompt Cache Routing