运营指南

API 中转站 token 消耗统计怎么落地:流式响应、缓存命中与重试的计费点拆解

一次 API 调用要穿过网关、路由和上游接口,token 统计的难点从来不在计算而在口径。本文拆解非流式与流式响应的用量获取、分词估算兜底、缓存与推理 token 拆列、预扣结算配合、重试去重与多模型倍率快照,并给出日结对账的排查清单,帮助中转服务把账单误差压到可解释的范围内。

做 API 中转的人迟早会碰到同一个问题:用户说自己这个月只跑了三百万 token,上游账单却显示四百万,差额既找不到请求也找不到模型。token 是这门生意里唯一的计费单位,可它偏偏是最难对齐的一项数据。一次调用要穿过网关、路由、上游接口和回调日志四道关口,任何一处口径不一致,账就会错位。

先把统计的落点想清楚。一次完整调用至少有三个可以记录 token 的位置:请求进入网关时的估算值、上游响应里返回的 usage 字段、以及异步写进日志表的最终值。三者用途并不相同,入口估算用于预扣额度,上游 usage 用于最终结算,日志表用于对账与查询。大量统计事故的根源,就是把入口估算当成了结算依据。

在 OpenAI 兼容协议下,非流式请求的用量很好拿,响应体里的 prompt_tokens、completion_tokens 和 total_tokens 直接可用,照着写库即可。真正麻烦的是流式响应:默认情况下最后一个数据块只带结束标记,不带用量。要拿到准确数字,需要在请求里开启用量回传选项,或者在网关侧自己累积增量内容再做估算。

上游不返回用量时,本地分词估算是唯一的兜底手段。做法通常是引入与目标模型同源的分词库,把提示词和生成内容分别编码后统计长度。这里有个常见误区:不同模型家族的分词规则并不一致,用同一套分词器去估算所有模型,偏差可能从个位数到十几个百分点不等。合理做法是按模型分组配置分词器,并定期用上游真实返回值校准系数。

流式场景还有一个绕不开的环节:连接中途断开。用户主动取消、网络抖动、上游超时都可能让一次请求没有正常收尾,此时既不能按全额结算,也不能直接归零。比较稳妥的处理是记录已经推送给用户的增量内容,用估算方式结算这部分输出,并在日志里打上中断标记。这个标记对后续查询解释和额度补偿判定非常有用。

缓存命中和推理过程产生的 token 需要单独拆列。提示词缓存能把重复前缀的成本压下来,但命中缓存的那部分 token 与未命中的部分单价不同,如果合并成一行统计,账单永远算不准。推理类模型返回的思考 token 也类似,是否计费、如何计费取决于上游规则。中转层要做的至少是把它们作为独立字段保留下来,不能直接丢掉。

预扣与结算的配合决定了用户体验。常见做法是按请求内容估算一个预扣额度,从账户余额里先冻结,等上游返回真实用量后再补差或退回。这个过程要保证两点:一是并发请求下的冻结额度不能互相覆盖,二是余额不足时的中断必须发生在请求发出之前,否则会产生既付了上游费用又无法向用户收款的坏账。

重试的归属问题最容易被忽略。上游返回 5xx 或连接超时时,网关通常会换节点重发一次请求。如果第一次请求其实已经生成了部分内容,第二次又完整跑完,上游账单上就会出现两笔消耗。解决办法是在网关层为每次逻辑请求生成唯一标识,重试沿用同一标识,并在结算时按标识去重,只对成功返回的那一次计费。

多模型路由让统计维度进一步变复杂。同一个 Key 可能在一天内调用多个模型,每个模型的输入输出单价都不同,统计表里必须带上模型名称和当时的倍率快照。如果只记录 token 总数而不记录模型,事后想复原账单几乎不可能。倍率也应该在写入日志时固化成快照,而不是查询时再去关联一张可能已经变更的价目表。

对账环节建议做成日结。把用户侧账单按模型、按小时聚合,再和上游账单逐项比对,差异通常集中在几类:流式中断、重试重复计费、缓存 token 归并、分词估算偏差。把这几类做成固定排查项,每次对账按顺序过一遍,绝大部分异常都能定位。剩下的零头差异可以设置容差阈值,超过阈值再人工介入。

从工程落地角度看,统计模块最好和业务逻辑解耦。网关只负责把原始事件写进消息队列,结算服务从队列里消费并完成计价、扣款、写账单,两边通过唯一请求标识串联。这样即使计价规则调整、模型上下线,也只需要改结算侧,不必动到转发链路。快米兔 API 在这一层走的是 OpenAI 兼容协议接入,用量按实际消耗计入账单,新用户注册送 5 元测试金,可以先跑通统计链路再放大调用量,具体计费细节以官方说明为准。

最后提醒一句,token 统计不是一次性开发完就结束的事情。模型版本迭代、上游协议微调、新的计费维度出现,都会让原本准确的统计逐渐漂移。把对账做成常态化的运营动作,比一次性把代码写得多完美更重要。对使用者来说,选一个用量看得清、账单能追溯、异常有标记的中转服务,长期看反而比单纯比较单价更省钱。