1. 计费系统不能只测正常请求
输入 1,000 Token、输出 500 Token,然后断言金额正确,只覆盖了最容易的一条路径。重复结算、并发预占、价格切换、Redis 故障和流式中断,才是容易造成真实损失的地方。
上线门槛也不能只是“接口返回 200”。系统要证明金额可复算、事务不会产生半成品、旧请求不能再次扣费,并且新旧计费结果的差异已经被解释。
2. 纯计算使用表格测试和属性测试
费用计算引擎适合使用表格测试,把普通输入、缓存、推理、长上下文、倍率和按次计费组合成清楚的案例。每个案例同时断言费用明细与总额。
属性测试不固定某个数字,而是验证始终成立的规律:Token 增加时基础费用不能下降;倍率为 1 时售价等于基础费用;所有 Token 为零时 Token 模式费用为零;费用桶之和等于基础费用;相同输入重复计算结果完全相同。
01it('never charges less when output tokens increase', () => {02fc.assert(fc.property(03fc.nat(),04fc.nat(),05(small, delta) => {06const a = calculate({ outputTokens: small })07const b = calculate({ outputTokens: small + delta })08expect(b.chargedMicroUsd).toBeGreaterThanOrEqual(a.chargedMicroUsd)09},10))11})
金额断言使用整数或十进制字符串,不使用 toBeCloseTo 掩盖精度问题。
3. 事务与并发怎样测试
集成测试使用真实 PostgreSQL,不能只 mock repository。启动一百个并发预占,账户可用余额必须始终大于等于零,成功预占金额之和不能超过初始余额。
同一个结算命令并发提交多次,只允许一个返回 applied: true,最终只有一条消费账本。相同 request ID 携带不同指纹时,所有后续请求都应得到冲突错误,余额保持第一次结算后的结果。
还要在事务每一步注入失败:幂等声明后失败、用量写入后失败、账本写入后失败、账户更新后失败。事务回滚后,幂等键、账本、余额和 hold 都不能留下半成品。
4. 流式与依赖故障测试
准备可控的假上游,模拟完整 SSE、事件被拆成任意 chunk、客户端中途断开、上游返回半个事件、最终 usage 缺失、响应结束后连接重置和同一事件重复发送。
Redis 测试覆盖超时、读失败、写失败、Lua 执行失败和窗口切换。余额缓存故障应回源或 fail-closed,RPM 故障则按照配置验证 fail-open。Queue 测试要重复投递、乱序投递、延迟投递和进入死信。
熔断器需要使用可控时钟测试 closed、open、half-open 状态,避免测试等待真实 30 秒。恢复探测只允许配置数量的请求通过,探测失败重新打开,成功后清空失败计数。
5. 先做影子计费
旧系统已经有流量时,不要直接切换扣费。新引擎先运行在 shadow 模式:读取同一份 usage 和价格,只计算并记录,不修改余额。后台按请求比较旧金额和新金额。
差异要分类,而不是只看平均值。缓存字段口径不同、舍入规则变化、模型映射不一致、价格版本不同和旧系统漏记 usage,都会产生差异。只有每种差异都有解释并达到阈值,才进入小流量真实结算。
灰度可以按内部账号、测试用户、特定 API Key 和低风险模型逐步扩大。每个阶段都保留一键停止新请求的计费熔断开关,但已经完成的请求仍然要结算,不能在回滚时丢失在途账单。
6. 上线验收清单
上线前至少确认以下项目:
- 所有常用模型都能在转发前解析价格,未知模型默认拒绝。
- 用量适配器使用当前厂商字段,并保存适配器版本。
- 余额预占、结算和释放均可幂等恢复。
- 账本、余额、用量和配额在同一事务中更新。
- Redis、Queue 和通知失败不会破坏核心账务。
- 每个请求能够关联价格版本、上游账号和厂商响应 ID。
- 对账任务、差异告警和补偿审批已经运行。
- 管理员调账不允许直接覆盖余额,并且具有审计日志。
- 日志、导出和 usage 证据没有泄露 API Key 或 Prompt。
- 影子计费差异率、漏 usage 比例和结算延迟达到发布门槛。
这些检查项应进入持续集成和发布流程,不能只保存在文档里。价格表或适配器变更同样要触发计费回归测试。
7. 面试时怎样说明这套设计
面试中介绍 Token 计费,不要只说“根据 input 和 output 乘单价”。可以从请求开始讲:网关鉴权后冻结价格版本和余额,代理层从上游提取真实 usage,适配器统一厂商口径,纯计算引擎生成费用明细,数据库事务通过幂等键同时更新账本、余额和配额,最后由异步任务刷新缓存、聚合统计并定期对账。
继续追问时,再解释三个取舍:为什么账本不能用缓存代替,为什么流式断开需要待对账,以及为什么路由失败的上游成本和用户应付金额要分开。能够把这些边界讲清楚,比堆砌“高并发”“分布式”更能说明真正处理过生产问题。
8. 总结
一套可靠的 Token 计费系统,需要把计量、定价、预占、结算、幂等、缓存、对账和运营连接起来。每个模块都不算神秘,难点在于故障和并发发生时,资金事实仍然只有一份,并且能够解释。
到这里,大章已经从一笔 Token 费用走到了完整中转站。后续把方案接入 AI 伴侣时,可以先实现价格快照、统一 usage、账本和余额预占,再逐步加入 Redis、支付和自动对账,不需要一开始就部署所有基础设施。