流式响应里的 token 计数藏在哪:从 chunk 里还原用量
流式响应中,token 用量通常不在每个 chunk 里逐字累加,而是由后端在流的末尾或特定字段中一次性返回。本文讲清不同 API 在流式场景下暴露 usage 的常见位置,客户端如何正确累加并避免重复计数,以及如何将得到的 token 数与聚合站的 USDC 扣费记录进行对账,确保成本透明。
流式响应(streaming)让我们能尽早看到模型输出,但 token 计数往往不是随每个 chunk 均匀出现的。很多开发者第一次做用量统计时,会误以为每个 chunk 都带一个 usage 字段,累加即可;实际上不同后端把最终用量放在不同位置,处理不当就会少算、多算或重复算。
流式响应里的 usage 通常在哪
在 OpenAI 兼容风格的流式返回中,每个 chunk 通常只包含增量内容(delta),而 usage 字段默认不会出现,或者只出现在最后一个 chunk 里。即便出现,也常常是 null 直到流结束。启用“包含用量”的选项后,末尾会有一个只带 usage、不带内容增量的 chunk。
不同后端的具体位置有差异:
- OpenAI 风格:最后一个 chunk 的
choices可能为空数组,同时顶层带usage。 - Anthropic 风格:流式事件是分类型的,用量信息通常出现在
messagestart(输入 token)和messagedelta(输出 token)等事件里,需要按事件类型分别提取。 - 部分兼容层:可能在流结束前插入一个专门的 usage chunk,或把
usage放在finish_reason同级的字段中。
所以第一步不是写累加逻辑,而是先打印几个真实响应,确认这个后端到底把用量放在哪里、出现几次。
客户端怎么累加才不重复
确认位置后,累加策略取决于用量是“快照”还是“增量”:
- 如果末尾 chunk 给的是本次请求的总用量(prompt tokens + completion tokens),直接取用,不要再去累加前面每个 chunk 里可能出现的零散数字。
- 如果后端在多个事件里分别给出输入和输出,就按字段分别记录,最后再加总;不要对同一个字段重复累加。
- 如果某些 chunk 的
usage是null或缺失,跳过即可,不要当成 0 去覆盖已有值。
一个稳妥的做法是:只信任流结束前最后一次出现的完整 usage 对象;中间出现的零散用量只做日志,不参与最终计数。这样能避免“末尾总用量 + 前面增量”被重复相加。
与 USDC 扣费记录对账
聚合站按官方价 ×1.3 扣费,所以你从流里还原出的 token 数,应该能和扣费记录对上。对账时注意几点:
- 确认计价口径:扣费记录通常区分输入 token 和输出 token,价格不同。你还原的用量也要分项,而不是只留一个总数。
- 保留请求 ID:很多平台的扣费明细带请求 ID,用它把客户端日志和服务端账单一一对应,比按时间估算可靠。
- 注意缓存与计费差异:如果后端报告了缓存命中或特殊计费字段,以账单口径为准,客户端统计只作参考。
- 定期抽样:不必逐条核对,按比例抽样比对,发现系统性偏差再排查解析逻辑。
如果你同时贡献 key 获得返 USDC,返还是按官方价 ×1.1(优质 ×1.2)计算,同样可以用这套对账方法核对自己的贡献记录。
常见坑
- 只解析第一个 chunk 的
usage,结果全是null。 - 把每个 chunk 的
usage相加,但末尾给的是总量,导致翻倍。 - 没有处理流中断:连接提前断开时,末尾 usage chunk 可能收不到,此时应以服务端账单为准。
- 忽略不同后端的事件结构差异,用一套解析逻辑硬套所有模型。
流式响应的 token 计数不在“每个 chunk 里”,而在后端约定的用量位置。先确认位置,再决定取总值还是分项累加,最后用请求 ID 与 USDC 扣费记录对账,就能把成本算清楚。