流式调用 Claude 时,token 计量为什么和账单对不上
做过一段时间接入的人大概都遇到过:客户端统计的输出 token 是 0,账单上却有费用;或者同一段系统提示重复调用,费用一次比一次低,自己却说不出原因。问题多半出在 usage 字段的读取位置和缓存字段的理解上。
一、usage 在不同返回模式下的位置不一样
非流式响应里,usage 在顶层一次给全:input_tokens、output_tokens,开了缓存还有 cache_creation_input_tokens 和 cache_read_input_tokens。流式响应则拆成两段:message_start 事件的 message 里带 input_tokens,output_tokens 通常只是很小的初始值;真正完整的输出计数在 message_delta 事件的 usage 里,message_stop 之后不会再补。
只读 message_start 的客户端,输出计数会一直偏小甚至为 0。正确做法是在 message_delta 时覆盖写入,message_stop 时落一次最终值。如果中途连接断开,这一轮的 output_tokens 就拿不到准确数字,需要在网关层用请求日志兜底。
二、缓存字段决定成本结构,不读懂就会算错
prompt caching 的机制是:命中的部分按缓存读取计价,未命中的部分按缓存写入计价,两者倍率不同。请求里要显式加 cache_control 断点,短的提示不会触发缓存。验证方式很直接:拿一段较长的 system prompt 连续调两次,第二次的 cache_read_input_tokens 应该明显大于 0,而 cache_creation_input_tokens 接近 0。如果两次数字完全一样,说明缓存没生效,或者入口没透传这两个字段。
Token5u 的返回体里这两个字段是分开透传的,对着 usage 就能判断缓存有没有真正命中,做成本核算时不用靠估算。
三、截断和重试会制造 “看不见” 的重复计量
max_tokens 设小了,模型会在中途停下,stop_reason 变成 max_tokens,但已经生成的部分照常计费,业务侧却当成失败重跑一次,等于同一段内容算了两遍。自动重试同理:SDK 对 5xx 默认会重发,如果不区分 “已产生输出” 和 “未产生输出”,费用会悄悄翻倍。
建议在网关层做三件事:记录 request_id 与每轮的 usage;按 request_id 去重后再汇总;对 stop_reason 为 max_tokens 的请求单独打标,便于回看是不是参数给得太保守。日志字段齐全的入口,对账时能直接对着时间戳和 usage 逐条比,Token5u 的请求记录属于这类,省去了自己拼日志的功夫。
总结
流式下 usage 分两处给,缓存字段要单独看是否命中,截断和重试则会带来重复计量。把这三处埋点做对,账单和客户端统计才能对得上。
- 点赞
- 收藏
- 关注作者
评论(0)