Token 是怎么计算的:AI 厂商和中转站如何统计用量
用大模型 API 时,经常会看到
prompt_tokens、completion_tokens、total_tokens这些字段。很多人第一反应是:“Token 是不是就是字数?”不是。Token 是模型真正处理文本时使用的基本编号单元,厂商和中转站的用量统计,本质上就是围绕这些编号单元做输入、输出、缓存、工具调用和账单记录。
先给出一句话版本:
| 问题 | 简短答案 |
|---|---|
| Token 是什么 | 模型 tokenizer 把文本切分后得到的编号单元 |
| Token 等于字数吗 | 不等于。中文、英文、标点、空格、代码都会有不同切法 |
| 厂商怎么统计 | 请求进入模型前统计输入 token,生成时统计输出 token,结束后返回 usage |
| 中转站怎么统计 | 优先读取上游厂商返回的 usage,同时自己预估、限额、落库、扣费 |
| 为什么我本地算的不一样 | tokenizer 不同、隐藏模板、工具 schema、RAG 上下文、多模态、重试和缓存都会带来差异 |
一、Token 到底是什么
大模型并不是直接理解“字”或“词”。在进入模型前,文本会先经过一个叫 Tokenizer 的组件,被切成一串 token,然后每个 token 再映射成数字 ID。
比如用户输入:
|
|
在 tokenizer 看来,它可能会被切成类似这样的片段:
|
|
这只是示意,不同模型的 tokenizer 结果可能完全不同。有的模型会把常见词切成一个 token,有的会把生僻字、特殊符号、空格、换行或代码符号切得更碎。
因此:
hello可能是 1 个 token;hello world可能是 2 到 3 个 token;- 一个中文词可能是 1 个 token,也可能被拆成多个 token;
- 代码、JSON、Base64、日志堆栈通常比较费 token;
- 空格、换行、标点也可能被计入 token。
更准确地说,Token 是 “模型词表里的最小可识别片段”。模型每次生成回答,本质上也是一个 token 一个 token 地往外吐。
二、为什么大模型要按 Token 计费
因为大模型的主要成本和 token 数强相关:
- 输入越长,模型要读的上下文越多;
- 输出越长,模型要生成的步骤越多;
- 上下文越长,显存、缓存和注意力计算压力越大;
- 如果启用工具调用、多模态、长上下文、推理增强,成本结构还会更复杂。
所以厂商通常不会简单按“请求次数”计费,而是按 token 计量。
常见账单可以抽象成:
|
|
为什么输出 token 往往更贵?因为输出不是一次性算出来的,而是模型逐步生成。每生成一个新 token,都要基于已有上下文继续推理一次。输入阶段可以有缓存和批处理优化,输出阶段通常更受实时生成过程约束。
三、一次请求里哪些内容会被计入 Token
很多人只会数用户输入的那句话,但真实 API 请求里远不止这些内容。
| 内容 | 是否通常计入 | 说明 |
|---|---|---|
| 用户消息 | 是 | 用户真正输入的问题 |
| 系统提示词 | 是 | system 或平台预置角色设定 |
| 开发者提示词 | 是 | 一些平台会额外注入规则或安全策略 |
| 历史对话 | 是 | 多轮聊天时,前面的上下文也会重新进入模型 |
| RAG 检索片段 | 是 | 从知识库召回后拼进 prompt 的内容 |
| 工具定义 | 是 | Function calling / tool schema 往往很占 token |
| 工具调用结果 | 是 | 工具返回的 JSON、表格、日志也会进入后续上下文 |
| 输出文本 | 是 | 模型生成的回答 |
| 图片、音频、视频 | 视模型而定 | 通常会折算成专门的输入或输出计量单位 |
| 缓存命中内容 | 视厂商而定 | 有的平台会单独返回 cached tokens,并按更低价格计费 |
所以你看到的一次“简单提问”,在服务端可能是这样的:
|
|
这也是为什么同样一句“帮我总结一下”,在不同应用里消耗 token 差异很大。聊天框、知识库问答、Agent、代码助手、联网搜索,它们背后拼进去的上下文不一样。
四、厂商是怎么统计 Token 的
AI 厂商统计 token,通常发生在模型服务的几个关键节点。
大致流程是:
- 请求进入网关:先鉴权,确认 API Key、组织、项目、余额、限速策略;
- 规范化请求:把 messages、tools、response format、多模态内容等转成模型内部格式;
- 输入计数:用该模型对应的 tokenizer 计算输入 token;
- 上下文检查:判断输入 token + 允许输出 token 是否超过模型上下文窗口;
- 模型生成:模型每生成一个 token,服务端就能记录输出 token;
- 结束汇总:生成完成、被 stop 截断、达到最大输出长度或发生错误后,汇总 usage;
- 账单落库:把模型、用户、请求 ID、token 数、价格、状态写入计费系统;
- 返回结果:API 响应里通常会带 usage 字段,供调用方记录。
一个典型 usage 结构可能长这样:
|
|
更复杂的平台还可能拆得更细:
|
|
字段名和含义会随厂商变化,但底层逻辑类似:把模型实际处理过的输入和实际生成过的输出,折算成可计费单位。
五、流式输出怎么统计
流式输出看起来像模型一边想一边发,其实服务端仍然知道自己生成了多少 token。
普通非流式响应:
|
|
流式响应:
|
|
中转站处理流式请求时通常会做两件事:
- 边转发 chunk 给客户端,保证用户体验;
- 同时缓存或累积输出文本,等上游结束后记录用量。
如果上游在最后一个 chunk 里返回 usage,中转站就直接使用它。如果上游不返回 usage,中转站可能会用 tokenizer 对完整输出再算一次,作为估算值。
流式计费最容易出问题的地方是断连:
| 场景 | 通常怎么处理 |
|---|---|
| 用户主动停止生成 | 已经生成的输出 token 仍可能计费 |
| 客户端网络断开 | 上游可能还生成了一小段,取决于中转站是否及时取消请求 |
| 中转站转发失败 | 要看上游是否已经成功处理,以及中转站的计费策略 |
| 请求超时 | 可能只有输入被处理,也可能已有部分输出 |
因此严谨的中转站不会只根据“客户端收到了多少字”扣费,而会尽量以 上游 usage 或服务端实际生成记录 为准。
六、中转站是怎么统计用户用量的
中转站可以理解成站在用户和上游模型厂商之间的 API 网关。它要做的不只是转发请求,还要做鉴权、限流、余额、计费、渠道选择和审计。
中转站一般会有三层统计:
| 层级 | 作用 | 准确性 |
|---|---|---|
| 请求前预估 | 判断是否超上下文、余额是否足够、是否限流 | 估算 |
| 请求后结算 | 根据上游 usage 或本地 tokenizer 计算真实消耗 | 更接近真实 |
| 离线对账 | 和上游账单、渠道日志、用户流水做一致性校验 | 最终核对 |
1. 请求前:预估 Token
请求还没发给上游时,中转站通常已经要做判断:
- 这个请求会不会超过模型上下文窗口?
- 用户余额够不够?
- 是否超过每分钟 token 限额?
- 是否命中某个模型或渠道的风控限制?
这时它会用对应模型的 tokenizer 先估算输入 token:
|
|
这里的 render_messages 很关键。真实模型不是只看用户输入的字符串,而是看消息角色、分隔符、工具定义、输出格式等拼接后的内部 prompt。中转站如果只数 messages[-1].content,一定会低估。
2. 请求后:读取 usage 并扣费
请求完成后,中转站会优先使用上游厂商返回的 usage:
|
|
为什么要优先用上游 usage?因为上游最清楚模型内部到底拼了什么、缓存命中了多少、输出生成了多少、特殊模态如何折算。中转站自己的 tokenizer 只能尽量接近,不能保证完全一致。
3. 落库:为什么要有用量流水
一个成熟中转站不会只在用户表里减余额,还会写不可随意改动的流水记录。否则一旦用户质疑扣费,平台很难解释。
一个简化的用量表可以这样设计:
|
|
关键字段:
| 字段 | 用途 |
|---|---|
request_id |
幂等和排查,避免重试导致重复扣费 |
provider |
区分不同上游厂商 |
model |
不同模型价格和 tokenizer 不同 |
prompt_tokens |
输入 token |
completion_tokens |
输出 token |
cached_tokens |
缓存命中的输入 token |
reasoning_tokens |
推理类模型可能单独统计的内部输出 |
upstream_cost |
中转站实际付给厂商的成本 |
user_cost |
中转站按自己价格表向用户收取的费用 |
status |
成功、失败、取消、超时、部分输出等状态 |
如果平台有余额系统,扣费还要做事务处理:
|
|
这样才能保证“用量流水、余额变化、账单记录”三者对得上。
七、中转站为什么会和官方统计不一致
如果你自己写一个 token 计算器,经常会发现结果和官方 usage 对不上。原因主要有这些:
| 原因 | 为什么会导致差异 |
|---|---|
| tokenizer 版本不同 | 模型升级后词表或特殊 token 规则可能变了 |
| 消息模板不同 | 厂商内部会给 role、message、tool 加包装格式 |
| 系统提示词不可见 | 应用或中转站可能注入了用户看不到的 prompt |
| 历史上下文被带入 | 多轮聊天会把旧消息一起计入输入 |
| 工具 schema 很大 | 一个复杂 JSON Schema 可能比用户问题还长 |
| RAG 召回内容多 | 知识库片段会直接增加输入 token |
| 输出格式约束 | JSON mode、结构化输出、示例格式都会增加 prompt |
| 多模态折算 | 图片、音频、视频通常不是按普通文本 token 简单计算 |
| 缓存命中 | cached tokens 可能计入总量,但按不同价格结算 |
| 请求重试 | 上游失败重试可能产生额外成本,平台要定义是否转嫁 |
所以中转站的合理做法是:
- 请求前用本地 tokenizer 预估,用于限额和上下文判断;
- 请求后以上游 usage 为准结算,尽量减少争议;
- 没有上游 usage 时再本地估算,并在账单里标记来源;
- 定期离线对账,比较中转站记录和上游账单差异。
八、上下文窗口和最大输出 Token
Token 统计不只影响费用,也影响请求能不能成功。
每个模型都有上下文窗口,比如能处理 8K、32K、128K 或更长上下文。这个窗口通常要同时容纳输入和输出:
|
|
假设某模型上下文窗口是 8192:
| 输入 token | 设置最大输出 | 是否安全 |
|---|---|---|
| 2000 | 1000 | 安全 |
| 7000 | 1000 | 接近上限 |
| 7900 | 1000 | 可能被拒绝 |
| 8200 | 500 | 输入本身已经超限 |
很多“上下文超长”错误,不是用户当前问题太长,而是应用把历史对话、系统 prompt、RAG 内容和工具 schema 全塞进去了。
中转站通常会在请求前做保护:
|
|
这也是为什么有些平台会提示“请开启上下文压缩”或“减少知识库引用数量”。本质是在帮你节省输入 token。
九、缓存 Token 是怎么回事
一些厂商会对重复前缀做上下文缓存。比如系统提示词、工具 schema、长文档前半部分每次都一样,那么服务端可以复用已经处理过的缓存,降低延迟和成本。
抽象来看:
|
|
这类 usage 可能拆成:
| 字段 | 含义 |
|---|---|
input_tokens |
输入 token 总数 |
cached_input_tokens |
输入中命中缓存的部分 |
billable_input_tokens |
实际按普通输入价计费的部分,具体取决于厂商规则 |
对中转站来说,缓存会让计费更复杂。它不能只看 total_tokens 乘一个固定单价,而要按厂商返回的细分字段计算。
十、不同任务的 Token 消耗差异
同一个模型,不同应用形态的 token 消耗会差很多。
| 场景 | Token 消耗特点 |
|---|---|
| 普通问答 | 用户输入短,输出长度决定主要成本 |
| 长文总结 | 输入很长,输出中等 |
| 代码生成 | 代码符号、缩进、上下文文件都比较费 token |
| RAG 问答 | 检索片段越多,输入 token 越高 |
| Agent 调工具 | 工具定义和工具结果会反复进入上下文 |
| JSON 结构化输出 | schema、字段名、示例都会增加输入和输出 |
| Embedding | 通常只按输入文本 token 计量,没有 completion token |
| 图片理解 | 图片会被折算成视觉输入 token 或类似单位 |
| 语音转写 / 语音生成 | 可能按音频时长、音频 token 或混合规则计费 |
如果你在开发 AI 应用,最容易低估的是三类开销:
- 历史对话:用户只发一句话,但你把前 30 轮都带上了;
- 工具 schema:函数参数定义复杂,每次请求都要传;
- RAG 内容:Top-K 设得太大,召回片段太长。
优化 token 成本,通常不是把用户问题压短,而是控制这些“看不见的上下文”。
十一、如何自己估算 Token
如果你是应用开发者,至少要在请求前做一次本地估算,用来控制上下文和成本。
最简单但不精确的方法:
|
|
这只能做非常粗的 UI 提示。更靠谱的方法是使用模型对应的 tokenizer:
|
|
但要记住:本地估算要尽量接近真实请求,而不是只数用户输入。
应该计入:
- system prompt;
- developer prompt;
- 历史 messages;
- RAG context;
- tool definitions;
- response format;
- 当前用户问题。
不应该只计入:
|
|
更合理的是:
|
|
本地估算的目标不是做到和厂商 100% 一样,而是提前发现明显超限和明显超预算。
十二、中转站计费系统的工程要点
如果从工程实现角度看,一个可靠的中转站至少要有这些模块:
| 模块 | 作用 |
|---|---|
| 模型路由 | 把用户请求路由到不同厂商、渠道或模型 |
| Token 预估器 | 请求前计算输入 token 和最大可能成本 |
| Usage 归一化 | 把不同厂商返回的 usage 统一成内部格式 |
| 价格表 | 维护模型输入、输出、缓存、多模态等单价 |
| 账单流水 | 记录每次请求的 token、费用、状态和 request_id |
| 余额系统 | 做预扣、实扣、退款或额度扣减 |
| 重试策略 | 定义上游失败、超时、断流时是否继续扣费 |
| 对账任务 | 和上游账单做日级或小时级核对 |
| 风控限流 | 按用户、模型、IP、组织维度限制请求和 token |
一个常见请求链路可以写成:
|
|
这里有两个细节很关键:
- 预扣不等于实扣:请求前按最大可能输出预扣,结束后按真实 usage 结算,多退少补;
- 幂等不等于不重试:同一个
request_id重复上报时不能重复扣费,但上游真实重试产生的成本也要能解释清楚。
十三、怎么降低 Token 成本
| 方法 | 原理 | 风险 |
|---|---|---|
| 精简系统提示词 | 减少每次固定输入 | 规则太少可能影响输出稳定性 |
| 压缩历史对话 | 摘要替代完整历史 | 摘要可能丢细节 |
| 控制 RAG Top-K | 少塞无关片段 | 召回不足会影响答案 |
| 缩短工具 schema | 减少重复传入的结构定义 | 过度简化会降低工具调用准确性 |
| 设置合理最大输出 | 避免模型无限写长 | 可能截断答案 |
| 使用缓存 | 重复前缀命中缓存 | 依赖厂商支持和请求结构稳定 |
| 按任务选模型 | 简单任务用低成本模型 | 模型能力不足会带来返工 |
| 日志里记录 usage | 找到最费 token 的链路 | 注意隐私和数据合规 |
实践中最有效的优化通常是这三个:
- 不要无脑带全量历史;
- 不要把过长的文档片段塞进 prompt;
- 不要给简单任务使用过强、过贵、上下文过大的模型。
总结
Token 计算可以拆成三层理解:
| 层级 | 关注点 |
|---|---|
| 模型层 | tokenizer 如何把文本变成 token ID,模型如何逐 token 生成 |
| API 层 | 输入、输出、工具、多模态、缓存如何汇总成 usage |
| 平台层 | 中转站如何预估、限额、扣费、落库、对账和处理异常 |
如果只记住一句话:
Token 不是字数,而是模型实际处理的上下文单位;厂商按模型真实处理量统计 usage,中转站则围绕 usage 做预估、转发、结算和审计。
对开发者来说,真正要养成的习惯是:不要只看用户输入的长度,而要看完整上下文。系统提示词、历史消息、RAG 片段、工具 schema、结构化输出约束,都会变成模型要处理的 token,也都会最终影响成本、延迟和上下文上限。