---
title: "Token计费怎么算：把一次大模型 API 调用的账单逐项拆开"
description: "讲清大模型 token计费的完整公式：系统提示词、对话历史、工具定义、推理输出和缓存命中各占多少，多轮对话为什么越聊越贵，以及如何用接口返回的 usage 字段自己对账，附可直接填写的成本估算表。"
url: https://cdnping.org/blog/llm-token-billing-explained
language: zh-CN
published: 2026-10-22
category: "模型观察"
tags: ["token计费","API 成本","大模型","提示缓存","对账"]
source: cdnping.org
license: Free to quote and summarize with a link to the url above.
---
# 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 里装了哪些东西？

实际账单里，你自己这一轮输入的内容常常只占很小一部分。一次请求的输入通常由这几块拼成：

1. 系统提示词：每次请求都会完整发送。Agent 类工具的系统提示词可能有几千 token。
2. 工具定义：开了函数调用或 MCP，每个工具的名称、描述和参数 JSON Schema 都算输入。工具一多，这部分很容易超过系统提示词。
3. 对话历史：接口是无状态的，模型“记得”上文，是因为客户端把之前所有轮次重新发了一遍。工具调用的返回结果（比如读进来的整个文件）也在历史里。
4. 当前输入：你这一轮打的字、贴的代码、上传的图片（图片也会折算成 token）。

输出部分则是：

5. 可见输出：你看到的回答。
6. 推理输出：推理模型内部的思考过程，按输出价计费。

### 一个标注为“示例”的计算

下面用假设的数字算一遍。单价取输入 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 提示缓存怎么用才省钱](/blog/claude-prompt-caching-save-cost)。

## 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

```bash
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 会出现在最后一个数据块里。

### 第二步：每次调用落一行日志

```python
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`，缓存写入要按写入价单独算。

### 第三步：按天汇总，和后台账单比

```bash
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 计数口径与核对方法](/blog/relay-billing-token-mismatch) 里有更细的讲法。单价会变，可以在 cdnping 的 [官网动态](/changes) 页面跟踪各家价格页的逐行变化，[模型监测](/models) 页面会记录 OpenRouter 目录里的调价；完整的追踪思路见 [大模型 API 价格变了怎么第一时间知道](/blog/track-llm-price-and-model-changes)。

## 单价差多少，值得在意吗？

以 cdnping 在 2026 年 10 月 17 日从 OpenRouter 公开 API 取得的快照为例（单日快照，不代表长期价格），我们跟踪的 16 个重点模型里，输入单价从 0.1 到 4 美元 / 百万 token，输出单价从 0.47 到 12 美元 / 百万 token，上下文窗口在约 26 万到 105 万 token 之间。同样一次请求，选不同的模型，费用可以差出几十倍。

另外两个规律：

- 输出通常比输入贵。快照里多数模型的输出单价是输入的 3 到 5 倍，个别模型的差距更大。所以推理模型“想得多”，费用涨得快。
- 便宜的模型不一定省钱。如果它要多试几次才能做对，重试的输入和输出都要再付一遍。

按任务难度把请求分到不同价位的模型，是比压提示词更有效的省钱方式，做法见 [多模型路由怎么做](/blog/multi-model-routing-save-cost)；比较价格时容易踩的坑见 [大模型 API 价格怎么比](/blog/llm-api-pricing-how-to-compare)。

## 不按 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/pricing) 和 [https://agiplan.dev/models](https://agiplan.dev/models) 为准。

遇到这类计量方式，换算思路是一样的：先弄清楚单位对应的是输入还是输出、是哪个基准模型，再用自己日志里的 `usage` 去核对实际扣减。

## 成本估算表模板

上线一个功能前，把下面这张表填一遍，基本能估出量级。

| 项目 | 填写 | 说明 |
|---|---|---|
| 系统提示词 token | | 用接口发一次只含系统提示词的请求，看 `usage` |
| 工具定义 token | | 带工具和不带工具各发一次，相减 |
| 平均每轮新增历史 token | | 用户输入 + 模型输出 + 工具返回 |
| 平均每会话轮数 n | | |
| 每会话累计输入 | | 前缀 × n + 每轮新增 × n(n−1)/2 |
| 预计缓存命中比例 | | 没开缓存填 0 |
| 平均每轮输出（含推理） | | 看 `completion_tokens` / `output_tokens` |
| 输入 / 缓存 / 输出单价 | | 抄官方价格页，记下日期 |
| 每会话费用 | | 按上文公式 |
| 每日会话数 × 30 | | 月度估算 |
| 重试率 | | 月度估算 × (1 + 重试率) |

估完之后，上线第一周用日志里的真实 `usage` 回填这张表，如果历史和工具定义比预估的大，先从这两项优化。
