一句简单的“继续”,看似轻松,背后可能隐藏着几毛钱与几块钱之间的巨大鸿沟。这一量级的账单差异,根源并非模型选错,也不是你多打了几个字,而是每一轮重复发送的上下文是否成功利用了缓存。
我第一次深刻意识到这个问题,是因为 Coding Agent 的账单高得令人咋舌。明明只是让它“继续修改”、“再跑一次测试”、“修复刚才那个错误”,输入框里只有寥寥几个字,但额度却快速下降。问题其实不在于你输入的那几个字,而在于其背后庞大的上下文结构:工具定义、项目规则、历史对话、上一轮的输出结果……真正吃掉 token 的,是这些每一轮都会被重复加载的内容。
如果这些重复内容命中了缓存,后续几轮将显著便宜。缓存读取的价格通常仅为普通输入的十分之一左右,某些模型甚至更低。若未命中,每一轮都会按全新输入重新计费,相当于全程按原价运行。同样是 100k token 的上下文,一轮按缓存读取、一轮按普通输入,账单能相差一个数量级。

这件事很难单靠你自己辨别出来。模型厂商、Agent 框架、中转站、路由位置,每一层都会影响最终的调用成本。只看模型单价,根本无法覆盖这些隐藏变量。真正决定长对话成本的,是另外三个问题:每一轮输入中有多少重复内容,重复内容是否命中了缓存,命中之后是否真正体现在你的账单中。
本文就顺着这笔账继续拆解。先讲缓存到底缓存了什么,再看 Claude、OpenAI、DeepSeek 的规则差异;然后回到 Coding Agent,分析它为什么命中率难以稳定;最后落到最敏感的一层——缓存省下来的钱,到底有没有算给你。
02|缓存到底缓存了什么
先把它和几个容易混淆的概念区分开。Prompt caching 的核心是输入前缀复用,与模型长期记忆、语义检索完全不同。模型会检测当前请求的前半段是否与之前的某次请求完全一致,如果一致,已处理过的部分就可以用更低的价格读取。关键词是“前缀完全一致”。
一个 Agent 请求大致结构如下:
[tools] 工具定义
[system prompt] 系统提示词
[project rules] 项目规则
[conversation history]历史对话
[tool results]上一轮工具结果
[new user message]“继续” ← 真正新增的只有这几个字
普通聊天中很少有人关心这个结构。到了 Agent 场景,前面的固定内容变得非常庞大,真正新增的往往只是最后那条指令。缓存能节省的,就是前面这一批重复输入。tools、system、project rules 如果连续多轮保持一致,后续请求的前缀就有机会命中。模型不必每轮按普通价格重新处理这段内容,首 token 延迟也会降低。
缓存也很容易被动态内容打断。当前时间、随机 request id、顺序重排过的工具列表、动态拼接的 system prompt,只要出现在请求靠前的位置且每轮都在变化,命中率就会显著下降。这类问题在 Agent 框架中特别常见。你没输入多少新内容,但框架却每轮往前缀里塞入动态字段,原本可以复用的上下文被当作新输入处理。

因此,稳妥的排列方式很简单:长期稳定的内容放在最前面,每轮变化的内容放在最后面。越稳定越靠前,缓存越有效;越动态越靠前,缓存越容易失效。
还要记住一点:缓存只影响输入侧。输出每一轮都需要重新生成。缓存节省的是重复上下文的读取成本和预填充延迟,不会让输出免费,也不会让回答质量提升。
所以判断缓存是否生效,不能只看总 token 数,要看输入中有多少被缓存读取、多少新写入、多少完全未命中。各家给这些字段起的名字不同:
- Claude:
cache_creation_input_tokens、cache_read_input_tokens、input_tokens - OpenAI:
cached_tokens - DeepSeek:
prompt_cache_hit_tokens、prompt_cache_miss_tokens
名字不同,但核心问题是同一个:这一轮输入中,重复内容是否按低价复用。如果看不到这个信息,模型单价就只能解释账单的一小部分。
03|Claude 的缓存,控制最强,也最容易用错
Claude 非常适合用来讲解 Agent 成本,因为它的机制最明确,usage 字段也最细致。Claude 缓存基于有序前缀,引用顺序为 tools → system → messages。前面发生变化,会连带影响后面。例如 tool definition 改动后,后面的 system 和 messages 缓存都可能失效。system prompt 改动后,后面的 messages 也会失效。然而在 Agent 里,最靠前的内容往往也最容易因框架动态拼接而改变。
Claude 通过 cache_control 指定缓存断点,意思是告诉 API:到此为止的前缀值得被缓存。
{
"model": "claude-opus-4-8",
"system": [{ "type": "text", "text": "You are a coding assistant. Follow the project rules below..." }],
"messages": [{
"role": "user",
"content": [
{ "type": "text", "text": "Here is the repository context and coding rules...", "cache_control": { "type": "ephemeral" } },
{ "type": "text", "text": "Now fix the failing test." }
]
}]
}
关键是位置。cache_control 应放在稳定内容之后,后续用户问题可以任意变化,但前面那段稳定前缀仍有几率被复用。Claude 对命中条件要求非常严格:断点之前的内容必须逐字一致。语义相近是不够的,文本、结构、顺序稍有变动就可能无法命中。
常见的失效来源包括:tool schema 变化、工具顺序变化、system prompt 混入动态状态、项目规则位置调整、工具结果结构变动。这对 Agent 框架提出了一个明确要求:稳定内容必须真正稳定。
Claude 还支持在对话中间插入 role: system 消息。它的价值正是为了维持前缀稳定。如果中途需要添加新规则、切换权限模式、补充临时约束,无需修改最前面的 system prompt,新规则以后续消息的方式加入,这样前缀保持不变,已建立的缓存就可以继续复用。需要注意的是,system message 权限很高,网页正文、工具返回、用户上传的内容不要放进去。
查看账单主要关注这三个输入字段:
{
"usage": {
"input_tokens": 842,
"cache_creation_input_tokens": 18640,
"cache_read_input_tokens": 73210,
"output_tokens": 1260
}
}
input_tokens 是本轮按普通输入收费的部分。cache_creation_input_tokens 是本轮新写入缓存的部分,写入有一定溢价。cache_read_input_tokens 是本轮从缓存读取的部分,价格通常远低于普通输入。如果只看总 token 很容易误判。一次请求看似输入很多,但大部分是 cache read,实际很便宜。反过来,写入高、读取低,说明你一直在写缓存但很少读取回来。
对于 Claude 来说,最需要关注的是写入和读取的比例,而不是“有没有开启缓存”。写入高读取低,说明前缀不稳定或复用次数不足;读取高普通输入低,才说明稳定前缀确实被复用了。
04|5 分钟还是 1 小时,TTL 按节奏选
Claude 缓存默认 5 分钟 TTL,也可以选择 1 小时。这两个选项不仅时长不同,写入成本也有明显差异:
- 5 分钟 cache write:普通输入价格的 1.25 倍
- 1 小时 cache write:普通输入价格的 2 倍
- cache read:普通输入价格的 0.1 倍
写入本身不免费。1 小时写入更贵,只有后续能多次读取回来才划算。
5 分钟适合高频连续会话。例如让 Coding Agent 连续修复 bug、运行测试、读取文件、修改代码,几分钟内反复使用同一批稳定上下文,5 分钟基本足够。而且 5 分钟缓存命中后会刷新,只要会话不长时间中断,就不容易过期。
1 小时适合中间有明显停顿的场景。比如读完输出后隔二三十分钟再继续、side agent 执行长任务、应用预热一段大上下文后反复使用。这些场景中 5 分钟缓存会过期,1 小时才能保住前缀。
但也不应无脑开启。如果内容只用一次,1 小时写入就是浪费。如果前缀每轮都在变化,1 小时也无法提高命中率。
Coding Agent 用户还需要多注意一层。Claude Code 在 2.1.108 版本中增加了两个环境变量:ENABLE_PROMPT_CACHING_1H 用于启动 1 小时 TTL,FORCE_PROMPT_CACHING_5M 强制使用 5 分钟。后续 changelog 还修复过“1 小时 TTL 被静默降级为 5 分钟”的问题。到 2.1.160 版本,它不再将整个会话固定为 5m 或 1h,而是按策略给不同缓存块分别生成 cache_control。usage 中可以看到 ephemeral_5m_input_tokens 和 ephemeral_1h_input_tokens 这样的拆分。
这里有一个容易忽略的成本边界:用很小的保活请求去维持 5 分钟缓存,本身也要计算费用。粗算一下,一小时 12 次保活,每次读取缓存前缀按 0.1x 计算,是 1.2x;加上第一次 5 分钟写入的 1.25x,合计约 2.45x。反而比直接写 1 小时的 2x 更贵。这只是一个敏感性估算,实际还要看保活请求是否真的读取了完整前缀、是否产出了输出、是否被中转站按原价计费。
Claude 也支持混用 TTL,但有顺序要求:长 TTL 内容放在短 TTL 之前。道理还是前缀,越稳定越靠前。
工具定义 / 系统规则 / 项目规范 → 1h cache
近期对话 / 本轮任务上下文 → 5m cache
当前用户输入 → no cache

TTL 不能只看时长,还要结合写入成本、复用次数和会话节奏来综合判断。
05|OpenAI 与 DeepSeek,一个自动省心,一个价差极端
Claude 是显式控制,另外两家则走了不同的路线。
OpenAI 不需要手动添加 cache_control,对于支持的模型,只要前缀足够长,系统会自动尝试缓存。这对产品化请求非常友好。固定的系统提示、产品说明、长文档开头等稳定长前缀会被系统自动复用,命中信息显示在 usage 的 cached_tokens 中。它强调 exact prefix match,前缀必须完全一致才能命中,缓存从 1024 token 起生效。原则与 Claude 一致,只是无需手动标注断点。
OpenAI 还提供了 prompt_cache_key。它的作用不是强制缓存某段,而是帮助平台将具有相同长前缀的请求路由到更可能命中的位置。同一个文档、同一个客服知识库、同一个应用模板,使用稳定的 cache key 可以提高复用机会。
代价是控制感较弱:不能精确指定断点,也不能为不同片段单独设置 TTL。还有两个边界需要记住:缓存 token 仍然计入 TPM 等速率限制;缓存有保留时间,文档中提到通常 5 到 10 分钟不活跃后失效,最长约 1 小时,部分模型有 extended retention。因此它适合省心使用,但不能当作无限期记忆。
DeepSeek 默认开启,称为上下文硬盘缓存,无需修改代码。但它的命中逻辑与前两家体验差异最大。前缀需要先被持久化成一个完整的 cache prefix unit,后续请求只有完整匹配某个已持久化的 unit 才能命中。
第一次:A + B → 结束后 A + B 成为一个 cache prefix unit
第二次:A + B + C → 命中 A + B
如果第二次请求是 A + C,则不一定命中,因为持久化的是 A + B,A + C 没有完整匹配。不过 DeepSeek 会检测多次请求之间的 common prefix。前两次都包含 A,系统可能会将 A 单独持久化,第三次变成 A + D 时,就有机会命中 A。
这也解释了一个看似玄学的现象:DeepSeek 有时第一、二轮没有命中,第三轮才开始命中。这不是抽风,而是缓存构建需要几秒钟,后台还在整理 prefix unit。
它的 usage 很直观。prompt_cache_hit_tokens 表示命中的部分,prompt_cache_miss_tokens 表示未命中的部分。这两个字段比总 input token 更有用,因为 DeepSeek 的 hit / miss 价差极为悬殊。4 月底一轮降价后,限时折扣价直接转为挂牌价。按当前中文价格页,100 万 input token:
deepseek-v4-flash:cache hit0.02 元,cache miss1 元deepseek-v4-pro:cache hit0.025 元,cache miss3 元
Flash 的 hit / miss 相差 50 倍,Pro 相差 120 倍。

对 DeepSeek 来说,命中与否直接决定这一轮长输入是几分钱,还是几块钱。我们五一期间跑过一批 DeepSeek 调用,总量约 15 亿 token,最终花费不到 50 元,平均约 0.033 元 / 100 万 token,基本是把 cache hit 利用到极致的效果。真正决定总成本的,已经从标价表上的 miss 单价,转向了大量重复上下文是否落到了 hit 档位。
这也是为什么 DeepSeek 最能暴露中转站的问题。网关只返回总 input token、不区分 hit 和 miss,上游大量命中却按普通输入计费,你根本无法察觉。它的缓存只匹配输入前缀,输出仍然每次生成,也不承诺 100% 命中(官方表示 best-effort),缓存不用后几小时到几天自动清理。
06|Coding Agent 为什么缓存难稳
用 Claude Code 作为例子,并不是因为它做得差,而是因为它把这类问题展现得最清楚。换到 OpenCode、OpenClaw、Hermes 等其他工具,细节不同,但压力来源相似。
编程客户端早已不是单个 Agent 在运行。它会调度工具、压缩上下文、切换模式、启动子任务。一个请求前缀中包含很多内容:工具定义、项目规则、CLAUDE.md、权限模式、MCP schema、git 状态、历史消息、工具结果。有些部分应该长期稳定,有些每轮都会变化。系统越复杂,动态上下文越多;动态上下文越多,缓存就越难稳定。
这就是它们比直连 API 更难命中的根本原因。直连 API 的缓存很干净,前缀一致就好解释。Agent 框架则多了一层上下文组织逻辑,每轮到底塞了哪些工具、状态、摘要、历史片段,你通常很难完全看清。
最近一批 Coding Agent 的缓存优化,核心都是同一件事:将稳定内容和动态内容分离开。几个反复出现的问题正好串起这条线。
第一个是动态 system prompt。当前时间、cwd、git 状态、权限模式、token budget,这些对 Agent 有用但每轮都在变化。一旦混入很靠前的 system prompt,原本稳定的前缀就变成了动态前缀。Claude Code 后来增加了 --exclude-dynamic-system-prompt-sections,就是为了将这类内容从稳定前缀中移除,从而改善跨用户缓存。
第二个是 tool schema。MCP server 一多,tool definitions 既长又容易变化。Claude Code 曾提出 MCP tool definitions deferred,即延迟加载工具定义。这不仅能减少 token 传输,更能减少工具 schema 对前缀的扰动。任何接入 MCP 的客户端都难以避免这个问题。
第三个是切换模型。Claude Code 会提醒用户:中途切换模型后,下一轮会重新读取完整历史,且没有缓存。切换模型不仅改变了推理能力,还可能导致缓存链路完全中断,下一轮输入成本突然升高。任何支持多模型切换的 Agent 都要面对这一挑战。
第四个是 auto-compaction 和 subagent。compaction 将历史压缩成摘要,能降低总 token,但会改写 messages 前缀,可能打断原有缓存链。它并不天然省钱,而是在“缩短上下文”和“保持缓存连续”之间做权衡。subagent 和 ultracode 这类自动多 agent 编排,虽然对大型重构、深度 review 有价值,但每个子 agent 都有自己的 prompt、工具、规则和执行结果,不一定共享主会话的缓存,token 消耗更快,缓存也更不稳定。小修小改时强行拆分,反而会把缓存和 token 都打散。
还有一类是统计本身的 bug,同样会误导:cache_creation_input_tokens 显示异常会让你误判写入成本,sub-agent 摘要缺少缓存会在多 agent 场景中放大 cache creation。
合理的方向只有一个:稳定内容固定住,动态内容往后放,用不到的工具不要提前暴露,compaction 和 subagent 要有明确的边界。provider-agnostic 框架还有一个先天难题:Claude 的 breakpoint、OpenAI 的自动前缀和 cache key、DeepSeek 的 hit / miss 账本,各走各的路。框架要兼容多家,就很难把任何一家用到极致。到这一层,缓存已经不是某个客户端的附属优化,而是上下文工程的一部分。
07|中转站,缓存省下的钱到底给了谁
前面讲的是模型和框架。到了中转站这里,问题变得最现实。模型有没有缓存是一回事,你是否拿到了缓存收益是另一回事。三家模型都会在 usage 中返回缓存信息,作用相同:让你知道这轮输入哪些按普通价格算、哪些走了缓存优惠。但这些信息一旦经过中转站,就可能消失。
很多 OpenAI-compatible API 会把各家 usage 统一成一套简单格式。接入虽然方便了,但缓存账本也被压平了,你最后只看到一个总 input token。

由此引出三层问题。
第一层是计费。上游已经按缓存读取结算了优惠价格,中转站转手按普通输入卖给你,中间的差价就被它吃掉了。它不需要修改请求,只要账单不区分普通输入和缓存输入,你就很难发现。DeepSeek 这种价差巨大的模型尤为明显。
第二层是路由。Prompt caching 既依赖前缀一致,也依赖请求落到能复用缓存的位置。中转站如果在多个 key、workspace、region、上游账号之间轮询,同一会话的请求就会被分散。你看到的模型名一直是 claude-opus-4-8、gpt-5.5、deepseek-v4-pro,但背后走哪个渠道你无从知晓。有时中转站自己也不一定清楚完整链路,它上面可能还接了其他聚合层、云入口、备用渠道。模型名一致,不代表缓存位置一致。
第三层是改写。有些中转站会插入自己的 system prompt、修改 tool schema,或者为了兼容格式而重排 messages。你以为发送的是原始请求,到上游时前缀已经变了。更麻烦的是,这类改写通常不会出现在你这侧的日志中。你只能看到发给中转站的请求,却看不到它转给上游的最终请求。
所以判断中转站时,不要只看标价。便宜单价没有意义,关键要看它如何处理缓存账本。至少需要关注三件事:
- usage 字段是否透传,最好能看到供应商原始字段
- 缓存输入如何计费,上游低价读取的部分是否按低价算给你
- 同一会话是否有路由粘性,连续任务最好固定在同一组上游资源
三样都没有,基本就无法对账。这已经是成本解释权的问题了。
08|怎么判断自己有没有被缓存坑到
落到实际使用上,不要一上来就问“它有没有缓存”,这个问题太笼统。更精准的问法分三层:接口是否返回了缓存信息,账单是否保留了这些信息,计费是否按缓存价格执行。
先看接口返回。关键是它是否将普通输入、缓存写入、缓存读取分开。如果只返回一个总 input token,那这条链路就少了一半信息——可能真的没有缓存,也可能缓存了但没有透传,仅看总 token 判断不出来。
再看账单口径。接口里有缓存字段,不代表账单按缓存计算。很多平台 API 返回中保留了 usage,但后台计费却仍然按总输入乘以单价。账单只写“输入多少 token、单价多少”,缓存收益给了谁,你还是看不到。
然后看连续会话中的比例。不要拿完全不同的请求去比较。要在同一项目、同一任务、同一会话中测试:前面大段上下文基本稳定,只在最后追加新问题。正常情况下,后续几轮会逐渐出现更多缓存读取,普通输入占比下降。如果每一轮都是大量普通输入、几乎没有缓存读取,就应该怀疑前缀被中断了。
一次测试不能说明所有问题。各家 TTL、最小 token 门槛、写入时机都不同,DeepSeek 还有构建延迟,中转站也可能有账单刷新延迟。一次没命中不等于有问题,连续多轮都看不到缓存痕迹,才值得深入排查。
使用 Agent 工具的,还要检查动态内容是否打断了前缀:当前时间、git 状态、任务进度、tool schema、MCP 工具列表、测试日志、网页正文等都可能影响。中转站也需要排查。同一个 base url、key、模型名,不代表上游缓存位置稳定。
最后不要只看总价。总价便宜,可能是模型本身便宜;总价贵,可能是输出 token 多。缓存主要影响输入侧的重复长前缀,判断缓存问题,要看输入中的普通部分和缓存部分。更可靠的做法,是把每一轮请求当作一条账本记录:用了哪个模型,走了哪条链路,输入中多少普通、多少缓存,账单按什么口径收费。
个人用户重点留意接口 usage、平台账单和请求间隔。团队用户还要记录 key、项目、用户、模型、base url、workspace、region。一旦这条账本断了,你就只能看平台给出的总价——钱到底花哪了,心里永远没数。
09|选型与判断框架
讲到这里,模型选型就不能只看标价了。长上下文和 Agent 场景中,实际成本由三个因素决定:模型基础价格、缓存命中率、链路是否将缓存优惠传递给你。
举一个粗略的示例。设普通输入价格为 1,缓存读取按 0.1。
一个中转站打 3 折,但实际命中率只有 50%。有效输入成本是 0.5×1 + 0.5×0.1 = 0.55,再乘以 3 折,得到 0.165。
另一个打 6 折,但命中率达到 95%。有效成本是 0.05×1 + 0.95×0.1 = 0.145,再乘以 6 折,得到 0.087。
看上去 6 折更贵,但算完之后实际只有前者的一半。

“几折中转”很容易误导人。折扣只作用于表面单价,缓存命中率会改变计费基数。在长任务中,命中率相差一点,价格排序就可能完全颠倒。
按场景选型:
- 重度 Agent、长会话、代码任务,Claude 仍然合适。优势在于可控制,breakpoint、TTL、usage 都很明确;代价是工程要求高,上下文位置放错、TTL 选错,账单就会很难看。
- 产品化调用选择 OpenAI 更省心。稳定前缀足够长时,系统会自动复用,适合调用规模大、不想手动管理缓存点的团队。
- 价格敏感的长输入复用场景看 DeepSeek。hit / miss 价差大,长文档、多轮问答很受益,前提是链路能透传缓存账本。如果字段不透传,优势就会被中转站抹平。
Agent 框架的选型,不要只看“支持多少工具”。更要看它如何组织上下文:稳定规则是否会每轮重写,tool schema 能否按需加载,MCP 工具会不会一次性全塞进去,compaction 和 subagent 是否有明确边界。工具越多、能力越强,请求也越容易变化,命中率最终取决于前缀是否稳定。
中转站同理。便宜的折扣只是入口,重点仍然是那三件事:usage 透传、缓存输入计费、路由粘性。企业团队还需要多考虑一层治理:至少能按项目、用户、key、模型查看输入、输出和缓存统计,否则账单上涨时,只能看到“某个模型花了很多钱”,却看不到钱花在了普通输入、重复上下文、工具结果还是路由打散上。
一个简单的框架作为收尾:
- 短输入、低频调用,只需看模型单价即可
- 长上下文、多轮会话,必须关注缓存命中率
- Agent 和中转站场景,要看上下文组织和账单透明度
这也是为什么本文把模型、Agent 框架、中转站放在一起讨论。在 Coding 和 Agent 场景中,输入往往远大于输出。模型每轮处理大量已有上下文,最后只产出几行修改或一次命令结果。输入侧占比一旦升高,缓存命中率就直接决定总成本。未来窗口更大、工具更多、Agent 读取的内容也会更多。上下文变长本身并不可怕,可怕的是每一轮都将长上下文按普通输入重新付费。
10|下次看账单,先看这三个数
下次再觉得 Agent 贵得离谱,不要急着换模型。打开 usage,看三个数:普通输入、缓存写入、缓存读取。连续运行几轮同一任务,如果缓存读取一直起不来,就该检查前缀——是动态字段插得太靠前,还是中转站在中间动了手脚。再对一眼账单。如果 usage 中大半是缓存读取,而账单却按普通输入计费,那么省下来的钱就没有到你的手里。
这本账看得懂,你花的是模型能力的钱;看不懂,就是一直在为重复上下文、不透明路由和无效工具调用交冤枉钱。
