指南/Token 对账
Token 账单对不上:怎样核对 Usage、费率与扣款
响应里的 usage、中转站的消费明细和账户余额,记录的是三个不同环节。三者对不上时,可以确认账务存在差异,但差异来自字段口径、重试、计价规则还是人为改写,需要继续核对。本文给出一套能保留原始证据的排查顺序。
一笔请求有三本账
对账时先把记录分开。只看余额减少多少,无法判断是哪一个环节产生了差额。
| 记录 | 回答的问题 | 必须保留的内容 |
|---|---|---|
响应 usage | 服务为这次响应报告了多少输入、输出和缓存用量 | 原始 JSON、模型标识、接口类型、请求 ID |
| 中转站消费明细 | 中转站按什么项目给这次调用记账 | 计费时间、倍率、单价、币种或额度单位、明细 ID |
| 账户扣款 | 余额或账期最终减少多少 | 扣款前后余额、账单导出、退款与赠送额度 |
官方直连接口可以提供第四份对照:同一份请求由模型厂商如何计数和响应。它适合排查中转站是否改写请求、模型或 Usage,但不能代替中转站公开的价格规则。渠道若明确收取服务费、使用独立额度单位或采用自己的舍入方式,计费合同才是核对应付金额的依据。
数字有差异,先别猜动机
以下情况都可能让账单看起来对不上:
- 字段口径不同:OpenAI、Claude 与 Gemini 对缓存、思考和总量采用不同的包含关系。把缓存明细重复相加,会在本地报表中制造虚高。完整公式见 OpenAI、Claude、Gemini 的 Token 用量对照。
- 一次操作触发多次调用:SDK 自动重试、Agent 工具续轮、后台摘要或客户端补发,会让一次点击对应多笔 API 请求。此时不能拿一条响应的 Usage 去解释整段时间的扣款。
- 计价规则不同:输入、输出、缓存写入和缓存读取可能使用不同费率,渠道还可能另收倍率、服务费或采用最小扣费单位。没有保存当时的价格页或合同,事后很难还原应付金额。
- 比较条件已经变化:模型别名、接口、system、tools、历史消息和多模态内容都会改变输入计数。聊天框末尾的文字相同,不等于完整请求相同。
差额能证明账面需要解释。要进一步判断计费错误或虚标,至少要排除上面的口径与调用次数问题,并取得服务方无法用公开规则解释的持续差额。运营方是否故意修改数字,还需要配置、日志、合同履行记录或其他独立材料。
先按官方口径还原 Usage
同名字段不能直接套同一条公式。以缓存输入为例:
- Claude Messages 将未缓存输入、缓存写入和缓存读取分列,三项相加才是有效输入总量。
- OpenAI Responses 的
cached_tokens是input_tokens的明细,不能再加一次。 - Gemini GenerateContent 的
cachedContentTokenCount也属于promptTokenCount的一部分。
思考 Token 和工具调用同样存在包含关系差异。归一前应保存供应商、接口名称和模型完整标识;归一后仍要保留原始 usage,避免官方字段调整后无法重算。
计费公式应按渠道公布的项目逐项展开。例如某个 Claude 原生接口的费用可能由未缓存输入、缓存写入、缓存读取和输出分别计价;Anthropic 的价格说明列出了缓存写入、读取以及其他价格修正项。实际核对中转站时,应把公式里的费率换成中转站在请求发生时承诺的费率。
一套可以复查的对账流程
1. 固定核对单位
选择业务空闲时段,用独立密钥发送一份固定请求。保存最终出站 JSON,而非业务代码中的参数对象;模型、协议、system、messages、tools、输出上限和 SDK 版本都要固定。
测试期间暂停同一密钥的其他任务。否则余额差值里会混入并发请求。无法暂停时,使用能按密钥、请求 ID 或明细 ID 筛选的账单导出。
2. 给每次尝试单独编号
记录客户端调用 ID、发送时间、HTTP 状态、服务方请求 ID、原始 Usage 和完整的计费明细。若 SDK 发生重试,每次网络请求都算一次尝试;不要把后一次成功响应覆盖前一次失败记录。
3. 按接口语义计算预期费用
先把 Usage 分成渠道价格表里的计费项,再分别乘以请求发生时的单价。倍率、币种换算、舍入、赠送额度和退款单独列出,不要把它们折进 Token 数。
一个便于审阅的记录可以写成:
预期扣款 = 未缓存输入费用
+ 缓存写入费用
+ 缓存读取费用
+ 输出费用
+ 已公开的服务费或倍率
这是一份核对模板,不是跨供应商通用公式。各字段是否相加,以当前接口文档为准。
4. 把明细映射到账户扣款
核对每条请求对应了几条消费记录,消费记录的合计是否等于余额变化。若 Usage 自洽而扣款偏高,优先查倍率、重试、舍入和其他计费项目;若扣款与明细一致而 Usage 口径异常,问题落在用量报告或请求处理这一层。
账户汇总通常有入账延迟。Anthropic 的 Usage and Cost API也把 Usage 与 Cost 分成两类报告,并说明它们用于成本核对。中转站没有逐请求明细时,只能扩大时间窗口做汇总对账,结论强度会下降。
5. 用官方端点做诊断对照
同一份固定请求分别发送到官方端点和中转站,可以检查两边报告的 Usage 结构与数值。比较时要使用相同协议和固定模型版本;移动别名、不同兼容接口或渠道额外注入的 system 内容都会带来合理差异。
如果输入字段持续不一致,稳妥的记录是:「相同客户端请求在两个端点得到不同的用量报告,差异尚未定位。」拿到中转站的上游请求或转换日志后,才能区分请求改写、模型映射、tokenizer 差异和响应字段重组。
用 reconcile.mjs 快速找出字段差异
仓库提供的 reconcile.mjs 会把同一段短请求分别发送到官方端点和中转站,多次采样后拍平 Usage 对象,列出同名字段差异及单侧字段。先下载并运行自检:
curl -O https://linkymonitor.ai/reconcile.mjs
bun reconcile.mjs --self-check
自检通过后再发起对照:
bun reconcile.mjs \
--protocol anthropic --model claude-sonnet-4-6 --runs 3 \
--official https://api.anthropic.com --official-key "$OFFICIAL_KEY" \
--relay https://your-relay.example --relay-key "$RELAY_KEY"
这个脚本是字段差异探针,不是完整账务工具。它没有读取中转站价格表、消费明细或账户余额,内置 prompt 也很短,不适合验证 Prompt Cache。结果出现差异后,仍要按前面的三本账定位;结果一致,只能说明这一组样本中的 Usage 一致。
结论写到证据允许的位置
| 已观察到的情况 | 可以确认 | 还不能确认 |
|---|---|---|
| 本地重算与中转消费明细不一致 | 计价过程或公开规则存在待解释差额 | 服务方有意虚标 |
| 官方与中转的输入 Usage 持续不同 | 两条路径的请求处理、模型或报告口径不同 | 具体在哪一层改写 |
| 消费明细合计与余额变化不一致 | 账户账务无法由现有明细解释 | 差额对应哪一笔上游调用 |
| 单次对账全部一致 | 这组请求的三本账能够对应 | 后续请求一直采用相同规则 |
对账解决的是「这笔钱怎么算出来」。若担心充值或业务放量后规则、路由发生变化,需要保留同一组探针并定期复跑,方法见中转站准入通过了,后面还可能换模型。
LinkyMonitor 持续记录固定请求的 Usage、响应结构与计费变化,让一次差额可以回到对应时间和样本复核。