Token计费怎么算:把一次大模型 API 调用的账单逐项拆开
大模型 API 的 token计费,公式本身很简单:费用 = 输入 token 数 × 输入单价 + 输出 token 数 × 输出单价,有缓存的平台再把命中缓存的那部分输入按缓存价单独计算。难的是“输入 token 数”往往比你以为的大得多,因为它不只是你这一轮打的字,还包括系统提示词、全部对话历史和工具定义。这篇文章从接口返回的 usage 字段讲起,把一次调用的账单拆开,最后给一张可以照着填的估算表。
一次调用到底按什么收费?
几乎所有主流接口都会在响应里带一个 usage 对象,它就是计费的原始依据。字段名各家略有不同,常见的有两套:
| 含义 | OpenAI 兼容格式 | Anthropic 格式 |
|---|---|---|
| 输入 token | prompt_tokens(含缓存命中部分) | input_tokens(不含缓存部分) |
| 缓存读取 | prompt_tokens_details.cached_tokens | cache_read_input_tokens |
| 缓存写入 | 一般不单列 | cache_creation_input_tokens |
| 输出 token | completion_tokens | output_tokens |
| 其中推理 token | completion_tokens_details.reasoning_tokens | 计入 output_tokens |
注意两点。第一,OpenAI 兼容格式里 prompt_tokens 通常已经包含了缓存命中的部分,算钱时要先减掉 cached_tokens,再分别乘单价;Anthropic 格式则是三项分开给。第二,推理模型“想”的那部分 token 不一定显示给你,但按输出价计费。字段的确切含义以各家官方 API 文档为准,接入前先核对一遍。
输入 token 里装了哪些东西?
实际账单里,你自己这一轮输入的内容常常只占很小一部分。一次请求的输入通常由这几块拼成:
- 系统提示词:每次请求都会完整发送。Agent 类工具的系统提示词可能有几千 token。
- 工具定义:开了函数调用或 MCP,每个工具的名称、描述和参数 JSON Schema 都算输入。工具一多,这部分很容易超过系统提示词。
- 对话历史:接口是无状态的,模型“记得”上文,是因为客户端把之前所有轮次重新发了一遍。工具调用的返回结果(比如读进来的整个文件)也在历史里。
- 当前输入:你这一轮打的字、贴的代码、上传的图片(图片也会折算成 token)。
输出部分则是:
- 可见输出:你看到的回答。
- 推理输出:推理模型内部的思考过程,按输出价计费。
一个标注为“示例”的计算
下面用假设的数字算一遍。单价取输入 2 美元 / 百万 token、输出 10 美元 / 百万 token,这个组合落在 cdnping 于 2026 年 10 月 17 日从 OpenRouter 公开 API 取得的价格快照区间内;缓存读取价假设为输入价的十分之一,实际倍率请看各家价格页。
| 组成 | token 数(示例) | 计价方式 | 费用(美元) |
|---|---|---|---|
| 系统提示词 | 1,500 | 输入 | |
| 工具定义 | 3,000 | 输入 | |
| 对话历史 | 12,000 | 输入 | |
| 当前输入 | 500 | 输入 | |
| 输入合计 | 17,000 | 无缓存时 × 2 / 1M | 0.034 |
| 可见输出 | 800 | 输出 | |
| 推理输出 | 1,200 | 输出 | |
| 输出合计 | 2,000 | × 10 / 1M | 0.020 |
| 单次合计 | 0.054 |
假如前 15,000 个输入 token(系统提示词、工具定义和大部分历史)命中缓存:
- 缓存读取:15,000 × 0.2 / 1M = 0.003
- 未命中输入:2,000 × 2 / 1M = 0.004
- 输出不变:0.020
- 合计 0.027,大约是无缓存时的一半。
这个例子里,你这一轮真正“说的话”只有 500 token,不到输入的 3%。
为什么多轮对话越聊越贵?
因为每一轮都要把之前的全部历史重新发一遍并重新计费。假设固定前缀(系统提示词 + 工具定义)是 4,500 token,每轮问答新增 1,000 token 历史:
- 第 1 轮输入约 4,500
- 第 10 轮输入约 13,500
- 第 20 轮输入约 23,500
20 轮的累计输入是 4,500 × 20 + 1,000 × (0 + 1 + … + 19) = 90,000 + 190,000 = 280,000 token。其中历史部分按平方级增长:轮数翻倍,历史带来的累计输入接近翻四倍。编程 Agent 读文件、跑命令、看日志,每次工具返回都塞进历史,增长更快。
能做的事情:
- 一个任务完成就开新会话,别在一个窗口里聊一整天。
- 用客户端的压缩或摘要功能,把旧历史换成短摘要。
- 只启用当前任务需要的工具,减少每次都要发送的工具定义。
- 把不变的内容放在请求前部,让缓存能命中,具体做法见 Claude API 提示缓存怎么用才省钱。
max_tokens 管什么,不管什么?
max_tokens(有的接口叫 max_output_tokens 或 max_completion_tokens)只限制输出的上限,不限制输入。
- 能做到:防止模型一次输出过长,给单次请求的输出费用设一个上限。
- 做不到:减少输入费用。历史有 10 万 token,设多小的
max_tokens输入照样按 10 万算。 - 对推理模型,推理 token 一般也占用这个额度。设得太小,可能推理还没结束就被截断,可见回答是空的,但已经生成的 token 仍然计费。
- 截断也收钱:响应里
finish_reason为length(或stop_reason为max_tokens)时,说明被截断了,这次的输出照常计费。
想控制总成本,要管的是输入那一侧:历史长度、工具数量、缓存命中率。
怎么用 usage 字段自己对账?
平台后台的账单是按天或按月汇总的,出了偏差很难定位到某次请求。最可靠的办法是在自己的代码里把每次的 usage 记下来。
第一步:看一次请求的原始 usage
curl -s https://你的接口地址/v1/chat/completions \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"你的模型","messages":[{"role":"user","content":"你好"}],"max_tokens":50}' \
| jq '.usage'
流式请求(stream: true)默认可能不返回 usage,OpenAI 兼容接口通常要加 "stream_options": {"include_usage": true},usage 会出现在最后一个数据块里。
第二步:每次调用落一行日志
import json, time
PRICE = {"in": 2.0, "cache": 0.2, "out": 10.0} # 示例单价,美元/百万 token,换成你实际的
def log_usage(model, usage, path="usage.jsonl"):
cached = (usage.get("prompt_tokens_details") or {}).get("cached_tokens", 0)
prompt = usage.get("prompt_tokens", 0)
out = usage.get("completion_tokens", 0)
cost = ((prompt - cached) * PRICE["in"] + cached * PRICE["cache"] + out * PRICE["out"]) / 1e6
row = {"ts": int(time.time()), "model": model, "prompt": prompt,
"cached": cached, "out": out, "est_cost": round(cost, 6)}
with open(path, "a") as f:
f.write(json.dumps(row) + "\n")
Anthropic 格式把字段换成 input_tokens、cache_read_input_tokens、cache_creation_input_tokens、output_tokens,缓存写入要按写入价单独算。
第三步:按天汇总,和后台账单比
jq -s 'group_by(.ts / 86400 | floor) | map({day: (.[0].ts/86400|floor), cost: (map(.est_cost)|add), prompt: (map(.prompt)|add), out: (map(.out)|add)})' usage.jsonl
对账时的检查表:
- 自己记录的请求数和后台的请求数是否一致(重试、超时后客户端重发都算请求)
- 流式请求是否都拿到了 usage,有没有漏记
- 缓存命中的 token 有没有按缓存价算,缓存写入有没有单独算
- 推理 token 有没有计入输出
- 单价是否是最新的,平台有没有调价
- 偏差明显时,挑几条请求逐条核对
如果对不上,常见原因和逐条核对方法在 AI API 账单对不上:token 计数口径与核对方法 里有更细的讲法。单价会变,可以在 cdnping 的 官网动态 页面跟踪各家价格页的逐行变化,模型监测 页面会记录 OpenRouter 目录里的调价;完整的追踪思路见 大模型 API 价格变了怎么第一时间知道。
单价差多少,值得在意吗?
以 cdnping 在 2026 年 10 月 17 日从 OpenRouter 公开 API 取得的快照为例(单日快照,不代表长期价格),我们跟踪的 16 个重点模型里,输入单价从 0.1 到 4 美元 / 百万 token,输出单价从 0.47 到 12 美元 / 百万 token,上下文窗口在约 26 万到 105 万 token 之间。同样一次请求,选不同的模型,费用可以差出几十倍。
另外两个规律:
- 输出通常比输入贵。快照里多数模型的输出单价是输入的 3 到 5 倍,个别模型的差距更大。所以推理模型“想得多”,费用涨得快。
- 便宜的模型不一定省钱。如果它要多试几次才能做对,重试的输入和输出都要再付一遍。
按任务难度把请求分到不同价位的模型,是比压提示词更有效的省钱方式,做法见 多模型路由怎么做;比较价格时容易踩的坑见 大模型 API 价格怎么比。
不按 token 计费的方式怎么换算?
有些平台不直接按 token 报价,而是用自己的计量单位。以 AGI Plan 为例(AGI Plan 是 cdnping 的商业合作方):计量单位是 CU,1 CU = 基准模型 1,000 个输出 token;按量付费时 1 元 = 500 CU。其他模型和输入 token 如何折算成 CU,以 https://agiplan.dev/pricing 和 https://agiplan.dev/models 为准。
遇到这类计量方式,换算思路是一样的:先弄清楚单位对应的是输入还是输出、是哪个基准模型,再用自己日志里的 usage 去核对实际扣减。
成本估算表模板
上线一个功能前,把下面这张表填一遍,基本能估出量级。
| 项目 | 填写 | 说明 |
|---|---|---|
| 系统提示词 token | 用接口发一次只含系统提示词的请求,看 usage | |
| 工具定义 token | 带工具和不带工具各发一次,相减 | |
| 平均每轮新增历史 token | 用户输入 + 模型输出 + 工具返回 | |
| 平均每会话轮数 n | ||
| 每会话累计输入 | 前缀 × n + 每轮新增 × n(n−1)/2 | |
| 预计缓存命中比例 | 没开缓存填 0 | |
| 平均每轮输出(含推理) | 看 completion_tokens / output_tokens | |
| 输入 / 缓存 / 输出单价 | 抄官方价格页,记下日期 | |
| 每会话费用 | 按上文公式 | |
| 每日会话数 × 30 | 月度估算 | |
| 重试率 | 月度估算 × (1 + 重试率) |
估完之后,上线第一周用日志里的真实 usage 回填这张表,如果历史和工具定义比预估的大,先从这两项优化。