API 计费不对怎么查:token 计数口径、重复扣费与逐条核对方法

博客 / 开发工具

API 计费不对怎么查:token 计数口径、重复扣费与逐条核对方法

觉得 API 计费不对,常见的情况不是平台算错了,而是你估算用的口径和平台计费的口径不一样:输入、输出、缓存、推理 token 单价不同,不同模型的 tokenizer 切分方式不同,客户端还可能在你不知情的情况下重试。要核对账单,最可靠的办法是把每次请求响应里的 usage 字段记下来,按同一套单价重算一遍,再和平台的明细对比。

为什么我自己估的 token 和账单对不上?

最常见的原因是把所有 token 当成一个价。实际上一次请求至少有四类 token,各自计价:

类别是什么常见误区
输入 tokensystem prompt、历史消息、工具定义、你本次发的内容只算了用户这句话,忘了历史对话和工具 schema 每轮都会重发
输出 token模型生成的可见内容用字数粗估,中英文、代码的 token 密度差很多
推理 token推理类模型在给出答案前的内部思考响应里看不到正文,但按输出计费
缓存 token命中提示缓存的输入部分,或写入缓存的部分读缓存通常比普通输入便宜,写缓存可能更贵,具体倍率以官方价格页为准

输出比输入贵得多,这一点会把误差放大。按 cdnping 在 2026-10-17 从 OpenRouter 公开模型目录抓取的 16 个主流模型价格,输出单价普遍是输入单价的 3 到 5 倍,其中一个模型约为 39 倍。所以同样“多算了 1 万 token”,落在输入还是输出上,金额可能差一个数量级。这些价格会变,想跟踪变化可以看 /models 的调价记录和 /changes 对官方价格页的逐行监控,做法在大模型 API 价格变了怎么第一时间知道里有详细说明。

Agent 类工具为什么特别费 token?

Claude Code、Codex 这类工具每一轮都会带上完整的上下文:系统指令、工具定义、已读过的文件、之前每一步的工具调用结果。假设一次会话跑了 30 轮,每轮上下文平均 4 万 token,仅输入就是 120 万 token,而你看到的对话可能只有几屏。这部分如果没有命中缓存,就全部按普通输入计费。

不同 tokenizer 会让同一段文字算出不同的 token 数吗?

会。每家模型用自己的分词器,同一段中文在不同模型上的 token 数可能相差明显,代码和 JSON 也一样。用某一家的 tokenizer 去估另一家的账单,本身就会有偏差。

更稳妥的做法:

  • OpenAI 系模型可以用 tiktoken 本地计数,但要选对编码,并且记住消息格式本身也会占少量 token。
  • Anthropic 提供 /v1/messages/count_tokens 接口,按实际请求体返回输入 token 数,比本地估算准。
  • 其他模型以响应里的 usage 为准,不要拿通用分词器去推算。

如果你用的是中转站或聚合网关,还要确认它转发到的是不是你以为的那个模型。模型换了,tokenizer 和单价都会跟着变,怎么确认 API 给你的是哪个模型里有可以动手做的一致性检查。

流式输出和重试会不会重复扣费?

账单“凭空多出来”的部分,常见的有三种来源:

  1. 客户端自动重试:很多 SDK 默认对超时、429、5xx 自动重试若干次。如果第一次请求其实已经在服务端生成了一部分甚至全部内容,只是响应没回到你这里,这次调用可能已经计费,重试又算一次。
  2. 流式中途断开:流式请求在中途断开时,已经生成的 token 通常仍会计费。客户端随后重新发起整轮请求,输入 token 也会再算一遍。
  3. 上层工具的重放:Agent 工具在出错后经常把整轮上下文重发,单次失败的代价是“整段上下文 + 已生成的输出”。

失败和重试往往集中出现在服务不稳定的时段。以 cdnping 镜像的 AGI Plan 公开请求数据为例(AGI Plan 是 cdnping 的商业合作方;窗口为北京时间 2026-10-15 19:05 至 2026-10-17 18:05,逐小时计数),2026-10-16 14:05 和 15:05 两个小时分别有 722 次和 877 次失败,都超过了同小时的成功次数(664 和 455),其余大部分小时的失败数在两位数以内。对账时如果某几个小时的消耗明显偏高,先去看那段时间服务端有没有故障,再看客户端日志里同一个任务是不是被发了多次。可用率怎么从这种计数里算出来,可以参考自己测 AI API 的可用率。

怎么用 usage 字段逐条核对?

第一步:确认每次请求都拿到了 usage

非流式请求的响应里直接带 usage。流式请求要注意:

  • OpenAI 兼容接口需要在请求里加 "stream_options": {"include_usage": true},最后一个数据块才会带上 usage,否则流式请求可能拿不到任何用量信息。
  • Anthropic 接口的 message_start 事件里有输入用量,message_delta 事件里有累计的输出 token,要以最后一个 message_delta 为准,不要把每个事件里的数字相加。

第二步:认清字段

含义OpenAI 兼容格式Anthropic 格式
输入 tokenusage.prompt_tokens(含缓存命中部分)usage.input_tokens(不含缓存部分)
缓存读取usage.prompt_tokens_details.cached_tokensusage.cache_read_input_tokens
缓存写入无单独字段usage.cache_creation_input_tokens
输出 tokenusage.completion_tokens(含推理)usage.output_tokens
推理 tokenusage.completion_tokens_details.reasoning_tokens计入 output_tokens

注意两种格式在“输入是否包含缓存”上口径相反。把 OpenAI 的 prompt_tokens 再加上 cached_tokens,或者把 Anthropic 的 input_tokens 当成全部输入,都会算错。

第三步:在自己这边记账

在调用层加一层日志,每次请求记一行。下面是一个最小例子,把 OpenAI 兼容接口的用量按单价重算:

import json, time

# 单价按你实际使用的渠道填写,单位:每百万 token
PRICE = {"input": 0.0, "cached": 0.0, "output": 0.0}

def log_usage(resp_json, request_id, path="usage.jsonl"):
    u = resp_json.get("usage") or {}
    cached = (u.get("prompt_tokens_details") or {}).get("cached_tokens", 0)
    prompt = u.get("prompt_tokens", 0)
    output = u.get("completion_tokens", 0)
    cost = ((prompt - cached) * PRICE["input"]
            + cached * PRICE["cached"]
            + output * PRICE["output"]) / 1_000_000
    row = {"ts": time.time(), "id": request_id, "model": resp_json.get("model"),
           "prompt": prompt, "cached": cached, "output": output, "cost": cost}
    with open(path, "a") as f:
        f.write(json.dumps(row) + "\n")

request_id 用响应里的 id 字段,或你自己生成后放进请求头的 ID。重试时要复用同一个业务 ID、另记一个尝试次数,这样才能在日志里看出哪些是重复请求。

第四步:和平台明细对账

拿到平台导出的明细后,按下面的顺序比:

  1. 总请求数:平台条数比你日志多,说明有你没记到的调用:SDK 自动重试、另一个进程在用同一个密钥,或密钥泄漏。
  2. 按小时聚合的 token 量:差额集中在少数几个小时,回头看这几个小时有没有故障或断流。
  3. 单条比对:挑差额最大的几条,用请求 ID 对上,比较输入、缓存、输出三项。输入对得上、输出差很多,多半是推理 token;输入差很多,多半是缓存口径或上下文膨胀。
  4. 单价:数量都对上了金额还对不上,检查渠道单价是否在这段时间调整过。

中转站和聚合网关的扣费要额外注意什么?

用中转站或聚合网关时,你看到的账单经过了一层换算,需要额外确认:

  • 计量单位:是直接按 token、按次,还是按自定义单位。
  • 换算规则是否公开:输入、输出、缓存是否分别换算,不同模型的系数是多少。
  • 失败请求怎么算:5xx、超时、客户端主动断开分别是否扣费。
  • 能否导出逐条明细:只有总额、没有逐条记录的话,基本无法自己核对。

这几项在AI API 中转站怎么选的 8 项检查里都有展开。

以 CU 为例:统一计量单位怎么换算?

前面提到的 AGI Plan(是 cdnping 的商业合作方)用的是 CU 这种统一计量单位:1 CU 等于基准模型 1,000 个输出 token;按量付费时 1 元 = 500 CU。按这个定义,在基准模型上生成 10 万个输出 token 消耗 100 CU,折合按量价 0.2 元。输入 token、缓存以及其他能力组的折算系数,以 agiplan.dev/pricing 公布的为准,这里不替它推算。

统一单位的好处是一份额度可以在多个能力组之间切换,代价是你要多做一步换算才能和官方 token 价格对比。核对时还是同样的方法:每条请求记下 usage,按公布的系数折成 CU,再和后台扣减对比。如果你在 Claude Code、Codex 里改了接口地址,记得确认请求确实走到了你以为的那个地址,配置方法见Claude Code 自定义 API 与 Codex、OpenCode 接口地址配置。AGI Plan 各能力组的公开可用率可以在 /status/agiplan 查看,对账时遇到失败集中的时段可以对照。

一份排查清单

账单对不上时,按这个顺序过一遍:

  • 流式请求是否开启了用量回传(include_usage 或读取 message_delta)
  • 输入 token 是否混淆了“含缓存”和“不含缓存”两种口径
  • 推理 token 是否被算进了输出
  • SDK 的自动重试次数是多少,日志里能否区分重试
  • 差额是否集中在某几个小时,那段时间有无服务故障
  • 平台记录的请求数是否多于自己的日志,密钥是否只在预期的地方使用
  • 统计期间单价或换算系数是否调整过
  • 中转站或网关是否提供逐条明细,失败请求是否计费

如果查下来发现主要成本来自上下文膨胀,而不是计费错误,那就该从用法上省了:把补全、格式化这类简单任务分到便宜的模型上,思路见多模型路由怎么做。

相关文章

继续阅读