核验清单
- ✓看返回里的
usage.prompt_tokens_details.cached_tokens。大于 0 才是命中,等于 0 就是没进。隐式缓存不保证命中,存储也不计费。 - ✓只有请求里写了
"caching": {"type": "enabled"}才产生存储费。存储从创建起算,手动按 ID 删除或expire_at到期才停。不调用不能证明这列是 0。 - ✓
expire_at最长 7 天,等于当前 UTC 时间加 604800 秒。再用、再命中都不会把这个时刻往后推。过期后要重新创建。 - ✓前缀缓存创建时输入少于 256 token,或
stream为 true,会直接报错,不是静默跳过。创建那一笔output_tokens恒为 0,零输出不等于没花钱。 - ✓删掉中间一轮之后,后面几轮在下一次请求时按新输入重算并重新存储。版本切换期间命中失败,存储费仍在产生。
1. 命中折扣和存储时钟是两列
火山方舟把上下文缓存拆成两条路,写在现行的上下文缓存页上。隐式缓存自动开启,不能关闭。它不保证命中,存储不计费。判断只看 cached_tokens:大于 0 按缓存命中单价,等于 0 按正常输入单价,输出仍按正常输出。命中单价还会随输入长度换档,同一只模型在 32K 以内和 128K 以上不是一个价。门槛也按模型变,常见默认是 1024,有的 256,有的 2048,个别到八千以上。把型号清单背成价目表没有意义,页上改了以页为准;cached_tokens 为 0,就是这一笔没进缓存。
显式缓存要你自己创建。文档写明它命中率更高、更可控,代价是多一列存储。计费四项:输入、缓存输入、缓存存储、输出。缓存输入的费率明显低于新输入。存储按「元/千 token/小时」走,和命中折扣不是加减关系。请求一旦使用显式缓存,隐式缓存就不再生效,两条路不能叠在同一次调用上。使用前还要在开通管理页打开该模型的「推理(缓存)定价」,否则不是「自动帮你省」,而是这口能力根本没开。
这四列不要和站里已经写过的三口表混。知识库空闲计的是规格小时、向量和排序,删对话不停,删库才停。专属部署和模型单元计的是容量或时长,运行中或下单成功之后时钟就在走。DeepSeek、通义的上下文缓存是前缀命中折扣,没有火山这口按小时的存储表。Claude 和 OpenAI 的提示缓存是写入加价:命中便宜,把内容写进缓存的那一笔比普通输入更贵,时限过了要重写,但没有「创建之后每个自然小时继续收存储」这一列。方舟显式缓存反过来:写入本身不一定加价,贵的是缓存还活着的每一个小时。
2. 存储从创建起算,再用也不会续期
上下文缓存页把存储写得很硬:费用在缓存创建时就产生,直到手动删除或过期才停。每个自然小时整点出账,不足 1 小时按 1 小时。对话停了、脚本停了、这一小时一次请求都没有,只要缓存还在,这一列不会因为调用次数归零而消失。创建在 13:50,14:00 整点就会把这不到一小时收成一小时。当小时账单还是空的,只能说明按小时的那一列还没出账,不能当成已经停表。
expire_at 同时管 store 和 caching。当前最大可存 7 天,也就是当前 UTC Unix 时间戳加 604800。过了这个时刻,存储过期,而且不会因为你又命中了一次、又调用了一次就把生命周期重置。过期之后必须用 Responses API 重新创建。这和海外提示缓存里「窗口内再命中就刷新时限」不是同一套机制。方舟这里的 7 天是上限,不是「常用就一直续」。一个长期挂着的会话,如果没人去改 expire_at、也没人删除,存储最多走到第 7 天;如果有人把过期时间设得很远却忘了删,中间每个自然小时都在累。
两页官方说明对「这一小时按什么量乘单价」并不完全一样。上下文缓存页写:该小时内每一轮新增的缓存 token 累加,存储费是新增内容乘存储单价。模型服务计费说明则写:缓存存储按小时出账,基于每个自然小时内存储的最大 token 数;缓存命中是准实时出账。旧的 Context API 文档已经下线,上面还有「该小时最大标记数」和 TTL 按秒设置的说法,不要拿那一页的字段去对现在的 Responses API 账单。现行两页都同意的部分只有这些:按自然小时、不足 1 小时按 1 小时、创建即计、删除或过期才停。对账以账单上的「缓存存储」行为准,不要用「这小时没调用所以应该是 0」去否定这一行。命中折扣走准实时,存储走整点,两列的出账时间本来就不是同一分钟。
版本切换是一个更容易被当成故障的窗口。文档写明:切换过程中,Responses API 请求无法命中缓存,但会继续存储并产生缓存存储费用;切换完成后,才能命中历史轮次。命中率掉到 0,存储列可以还在涨。把「没命中」读成「没在计费」,会把这几个小时的存储费看成异常扣款。
3. 删一轮不是退订,改一个字段也会让后面重算
按 ID 删除某一轮,停的是那一轮自己的缓存信息,不是整口存储时钟。文档的例子是第 5 轮之后删掉第 3 轮:下一次请求时,后续轮次的缓存会重新计算并重新存储。第 6 轮里,受影响的第 4、5 轮信息按输入计费,不再按缓存输入,直到新的缓存再次建立。删除之后的第一笔,看起来像「缓存失效所以更贵」,贵的原因是那些 token 重新走了新输入,并且又写进了存储。删一轮再继续聊,可能比留着贵,而不是退了一笔存储费。
删掉首轮还有一条附带后果。tools 只能在首轮设置,后面的轮次默认携带首轮 tools 的缓存输入,不能再传 tools,否则冲突报错。首轮一旦被删,后续轮次不再携带这份 tools 缓存,也不能再配置 tools。一个带函数调用的会话,删了第一轮不只是少了一段历史,是工具定义那一截缓存也没了,下一轮要么不能再挂工具,要么得整段重建。
另外三个字段会让命中和写入一起失败,失败的同时如果缓存还没到期,存储费不会跟着停。thinking 必须和前一轮保持一致:第一轮是 auto,后面也得是 auto;第一轮没设,后面也不能设。本轮如果写了非空的 instructions,这一轮既不能调用已有缓存,也不能把本轮写入缓存。store 默认是 true,写入的前提是存储开着;某一轮没开 "caching": {"type": "enabled"},后面的轮次就写不进去。前面只要有一轮开了 caching,就不支持 json_schema,json_object 还可以。这些都是「这一笔没打折」,不是「存储已退订」。
Prompt Cache Key 更容易被当成开关。它只影响隐式缓存的路由亲和,让同一段前缀尽量打到同一条缓存上。它不是强一致:扩缩容、发布、故障切换、映射过期或保护策略都会重新路由。它不创建显式缓存,也不产生显式存储费,更不能把 cached_tokens 从 0 变成大于 0。给隐式请求加一把 Key,解决不了「显式缓存还在按小时扣」。
4. 前缀缓存不够 256 会报错,零输出也不等于免费
显式缓存有两种。前缀缓存存的是不变的开头:角色、规则、长文档。首轮要 "store": true(缺省就是 true),再加 "caching": {"type": "enabled", "prefix": true},后面用 previous_response_id 引用。创建时输入 token 必须大于等于 256,否则报错;stream 不能为 true。这和隐式缓存不同。隐式不够门槛是静默不命中,cached_tokens 为 0,请求仍然成功。前缀缓存不够 256 是创建失败。用 glm-5-3-flash-260828 创建前缀时,role 还必须是 user,否则也建不起来。
创建成功的那一笔,返回的 usage 里 total_tokens 等于 input_tokens,output_tokens 始终是 0。财务如果只盯输出,会把这笔看成空调用。输入已经按未命中或缓存输入计过,存储也从这一刻开始按小时走。零输出是前缀创建的正常形状,不是没计费。
Session 缓存没有 256 这个下限。它随每一轮更新:本轮写入缓存的是本轮输入,模型回复不直接进缓存;上一轮的回复和思维链会变成下一轮输入的一部分,所以输入 token 会变多。可以用上下文编辑去砍思维链和工具内容,命中的部分是「本轮输入」和「编辑后的缓存」的交集。普通新输入的量用 input_tokens - cached_tokens 算,不要把 input_tokens 整段都当成未命中。存储费只在 "caching": {"type": "enabled"} 时产生。Session 和前缀的差别是门槛和会不会随对话变长,不是「Session 不收存储」。两种显式缓存都在那四列里面。
5. 给账户充值,停不了已经创建的缓存
对账先分四列。第一列是这条请求走的隐式还是显式:没有 caching.type = enabled,就没有存储费,只有可能出现的命中折扣。第二列是 expire_at 还在不在 7 天窗口里,以及有没有人以为「再用会续期」。第三列是缓存 ID 有没有被删;删了一轮之后,后面是否已经按新输入重算并重新存储。第四列是账单上的缓存存储是不是仍在整点增加。命中列准实时、存储列按小时,当小时为空不能当成停表。版本切换、thinking 或 instructions 被改过,都只能解释命中失败,解释不了存储为什么还在。
给火山引擎账户充值是第五本账。链上把稳定币换成能充进云账号的资产,发生在入账之前,不删除缓存 ID,也不改 expire_at。需要这一步时,可以用无需注册、非托管的 AllSwap 完成链上兑换。兑换成功只说明资产换了手。显式缓存该按 ID 删还得删,到期之前每个自然小时的存储费该走还在走。