← 返回 V-API 是什么?功能、接入步骤与使用注意事项 首页

OpenAI API token 怎么计算?第三方指南与实操整理

更新于 2026-10-18 · gpt.boats

简答:OpenAI API token 是模型分词后统计的输入和输出单位,不能用字符数固定换算。请求完成后读取 usage;费用计算时,缓存 token 是输入 token 的子集,推理 token 是输出 token 的子集,不能重复相加。

定义:Token 是模型处理输入、生成输出以及部分消息结构时使用的计量单位,API 通常在 usage 中分别返回输入、输出、缓存和推理等统计字段。

OpenAI API token 到底按什么计算

OpenAI API token 不是“一个汉字等于一个 token”,也不是字符数除以固定数字。模型会对文字、空格、标点、消息角色、工具定义、图片和文件等内容进行分词或结构化处理,最终数量取决于具体模型和请求格式。

实际使用时可以分成两个问题:第一,请求已经完成后有多少 token,应读取响应里的 usage;第二,请求发送前预计有多少输入 token,可以使用官方 input token counting 接口。OpenAI 的 Token Counting 指南说明,该接口会按照模型实际接收的请求结构统计输入,并覆盖消息结构、工具、图片和文件等本地 tokenizer 不容易准确处理的内容。OpenAI Token Counting 官方指南

使用 Chat Completions 时,常见顶层字段是 prompt_tokens、completion_tokens 和 total_tokens;使用 Responses API 时,常见字段是 input_tokens、output_tokens 和 total_tokens。不同接口和模型的详细字段可能不同,应以实际响应及对应 API Reference 为准。OpenAI Chat Completions 创建接口OpenAI Responses API Reference

输入、输出、缓存和推理 token 的关系

字段或概念含义计算关系
prompt_tokens 或 input_tokens请求发送给模型的输入 token,可能包含消息、历史记录、工具定义、图片或文件等缓存 token 和缓存写入 token属于输入统计的细分部分,不应在此基础上重复相加
cached_tokens输入中从提示缓存复用的 token 数是输入 token 的子集,不是额外增加的一组 token
cache_write_tokens本次请求写入提示缓存的输入 token 数,是否返回取决于接口和模型如果价格页单独列出缓存写入价格,应从普通输入部分中分离计算
completion_tokens 或 output_tokens模型生成的输出 token 总数推理 token、工具调用参数及部分不可见结构可能包含在其中
reasoning_tokens推理模型用于内部推理的输出 token 数是输出 token 的子集,不能在输出 token之外再次加一遍
total_tokens请求的总 token 统计通常是输入 token 加输出 token,而不是再把缓存或推理字段另算一次

Chat Completions 的详细统计路径通常是 usage.prompt_tokens_details.cached_tokens、usage.prompt_tokens_details.cache_write_tokens 和 usage.completion_tokens_details.reasoning_tokens。官方接口说明将 cached_tokens 定义为提示中的缓存 token,将 reasoning_tokens 定义为模型生成的推理 token。OpenAI Chat Completion usage 字段说明

因此,如果用户看到的回答很短,但 completion_tokens 或 output_tokens 较高,可能是输出统计包含了不可见的格式或结构 token,也可能包含推理模型使用的 reasoning_tokens。推理 token 仍然属于输出统计,不能因为用户看不到就从实际用量中删除。OpenAI Token Counting 官方指南

缓存 token 和推理 token 为什么不能重复计算

这是计算费用时最容易出错的地方。假设一次 Chat Completions 响应返回:

这里不是把 1200、800、300、100 全部相加。正确理解是:1200 是全部输入 token,其中 800 个属于缓存输入;300 是全部输出 token,其中 100 个属于推理 token。缓存 token和推理 token分别是输入、输出的子集。

如果没有缓存写入费用,通用费用公式可以写成:

总费用 =(输入 token − 缓存 token)÷ 1,000,000 × 普通输入单价 + 缓存 token ÷ 1,000,000 × 缓存输入单价 + 输出 token ÷ 1,000,000 × 输出单价

如果接口返回了 cache_write_tokens,并且当前模型价格页单独列出缓存写入价格,则应进一步拆分:

普通输入 token = 输入 token − 缓存 token − 缓存写入 token

总费用 = 普通输入 token × 普通输入单价 + 缓存 token × 缓存输入单价 + 缓存写入 token × 缓存写入单价 + 输出 token × 输出单价

上述各项在公式中都要再除以 1,000,000。OpenAI 的提示缓存文档明确说明,缓存写入不是在普通输入费用上额外叠加,而是根据输入 token 属于普通输入、缓存输入或缓存写入类别分别计价;具体单价应以当前模型价格页为准。OpenAI Prompt Caching 官方指南OpenAI API 官方价格页

最小可运行示例:读取输入、缓存、输出和推理 token

下面示例使用 Python 3 标准库调用 Chat Completions,并打印常见 usage 字段。它不写死接口域名或模型名:BASE_URL 应设置为已经包含版本路径的基础地址,代码不会再次拼接 /v1;KEY 只从环境变量读取;MODEL_ID 应填写所用平台公开模型列表中的实际 ID。

依赖工具:Python 3,无需安装第三方 SDK。

export BASE_URL="https://api.openai.com/v1"
export KEY="your-api-key"
export MODEL_ID="以所用平台官方模型列表为准"

如果项目使用 .env 文件,可以写成:

BASE_URL=https://api.openai.com/v1
KEY=your-api-key
MODEL_ID=以所用平台官方模型列表为准

不要把密钥提交到代码仓库,在 .gitignore 中加入:

.env

保存为 read_usage.py 后运行:

import json
import os
import urllib.request

base_url = os.environ["BASE_URL"].rstrip("/")
api_key = os.environ["KEY"]
model_id = os.environ["MODEL_ID"]

payload = {
    "model": model_id,
    "messages": [
        {"role": "user", "content": "用一句话解释 token。"}
    ],
    "max_completion_tokens": 80,
}

request = urllib.request.Request(
    f"{base_url}/chat/completions",
    data=json.dumps(payload).encode("utf-8"),
    headers={
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
    },
    method="POST",
)

with urllib.request.urlopen(request, timeout=60) as response:
    result = json.load(response)

usage = result.get("usage", {})
prompt_details = usage.get("prompt_tokens_details") or {}
completion_details = usage.get("completion_tokens_details") or {}

print("prompt_tokens:", usage.get("prompt_tokens"))
print("cached_tokens:", prompt_details.get("cached_tokens"))
print("cache_write_tokens:", prompt_details.get("cache_write_tokens"))
print("completion_tokens:", usage.get("completion_tokens"))
print("reasoning_tokens:", completion_details.get("reasoning_tokens"))
print("total_tokens:", usage.get("total_tokens"))
print("answer:", result["choices"][0]["message"]["content"])

成功响应通常应包含 choices 和 usage。某些兼容接口不返回缓存或推理细分字段,程序会打印 None,这不代表一定没有对应统计,只表示当前响应没有提供该字段。若接口或旧模型不接受 max_completion_tokens,可能返回参数错误;这时应查看该接口文档,确认是否需要改用 max_tokens,不要同时发送两个参数。OpenAI 官方 Chat Completions 示例展示了 prompt_tokens_details 和 completion_tokens_details 的返回结构。OpenAI Chat Completions 创建接口

发送前怎样预估输入 token

如果只是比较几段纯文本的长度,可以使用本地 tokenizer,例如 tiktoken;但它不一定能完整模拟消息包装、工具 schema、图片、文件和模型特定行为。OpenAI 官方建议在需要准确预估时,使用与正式请求相同的输入调用 input token counting 接口。OpenAI Token Counting 官方指南

官方接口是 POST /v1/responses/input_tokens,返回对象中的 input_tokens 是模型将接收的输入 token 数。OpenAI Get input token counts API Reference

下面是一个不依赖第三方 SDK 的最小 Python 示例:

import json
import os
import urllib.request

base_url = os.environ["BASE_URL"].rstrip("/")
api_key = os.environ["KEY"]
model_id = os.environ["MODEL_ID"]

payload = {
    "model": model_id,
    "input": [
        {
            "role": "user",
            "content": [
                {"type": "input_text", "text": "用一句话解释 token。"}
            ],
        }
    ],
}

request = urllib.request.Request(
    f"{base_url}/responses/input_tokens",
    data=json.dumps(payload).encode("utf-8"),
    headers={
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
    },
    method="POST",
)

with urllib.request.urlopen(request, timeout=60) as response:
    result = json.load(response)

print("object:", result.get("object"))
print("input_tokens:", result["input_tokens"])

预估流程可以按四步执行:

  1. 确定正式请求使用的模型 ID。
  2. 把正式请求中的系统指令、历史消息、工具定义、图片或文件输入完整准备好。
  3. 调用 /responses/input_tokens,记录返回的 input_tokens。
  4. 正式请求完成后,再把响应里的实际 usage 写入日志,用实际结果校正预算。

如果使用 OpenAI 兼容接口,应先确认该接口是否实现了 Responses API 的 input token counting 路径。没有实现时,以该接口公开文档和实际返回字段为准,不要默认所有兼容接口都支持同一组端点。

Token、上下文长度和限速有什么区别

Token 用量描述一次请求实际处理了多少输入和输出;上下文长度描述一次请求能够容纳的输入、输出及模型相关内容范围;限速描述一段时间内允许发送多少请求或 token。三者相关,但不是同一个指标。

OpenAI 的限速文档列出了 RPM、RPD、TPM、TPD 等指标。一个请求可能因为请求数达到 RPM 而触发限制,也可能因为 token 数达到 TPM 而触发限制。因此,不能把一次请求的 token 数直接当成每分钟额度,也不能把价格和限速混为一谈。OpenAI Rate Limits 官方指南

在工程日志中,建议至少记录模型 ID、输入 token、输出 token、缓存 token、推理 token、请求 ID、响应时间和错误状态。这样可以区分“单次请求变长”“缓存命中变化”“推理消耗增加”和“每分钟额度不足”等不同问题。

对于多轮对话,历史消息通常会继续成为后续请求的输入;对于工具调用,工具名称、描述和参数 schema 也可能占用输入上下文。发送前若要判断是否接近上下文限制,应使用最终请求结构进行计数,而不是只统计用户可见文字。

降低 token 消耗的实用方法

  • 减少重复历史:只保留当前任务需要的对话内容,过长历史可以先摘要。
  • 缩短系统指令:合并重复规则,删除不会影响结果的背景说明。
  • 控制输出上限:根据任务设置合理的 max_output_tokens 或 max_completion_tokens,但要确认所用接口支持哪个参数。
  • 按需发送工具:只提供当前任务需要的工具和 schema,避免把无关函数定义全部放进请求。
  • 保持可缓存前缀稳定:将不会频繁变化的指令、工具定义和公共上下文放在前面,再放用户动态内容。
  • 观察真实字段:同时记录普通输入、缓存输入、缓存写入、输出和推理字段,不要只保存 total_tokens。

提示缓存可以降低重复前缀的输入成本,但缓存 token 仍然会计入相关 token 限速;是否命中缓存也不能只凭请求文本相似来判断,应以响应中的 usage 统计为准。OpenAI Prompt Caching 官方指南

常见问题

中文一个字等于一个 token 吗?

不一定。Token 是模型分词或结构化处理后的片段,中文、英文、数字、空格和标点可能采用不同切分方式。准确数量应读取响应中的 usage,或使用对应模型的 input token counting 接口。

cached_tokens 是不是要在输入 token之外再加一次?

不是。cached_tokens 是输入 token 的子集。计算缓存输入费用时,应把输入 token拆成普通输入、缓存输入以及必要时的缓存写入部分,不能把全部输入和缓存部分重复相加。

reasoning_tokens 是不是额外的输出费用?

通常不是单独加在 output_tokens 或 completion_tokens之外的另一组输出。reasoning_tokens 是输出统计中的细分字段,具体计费类别应以当前模型价格页为准。

为什么可见回答很短,但 completion_tokens 很多?

输出统计可能包含用户不可见的结构或格式 token;对于推理模型,还可能包含 reasoning_tokens。最终应以响应中的 completion_tokens_details 或 output_tokens_details 以及顶层输出字段为准。

max_completion_tokens 是实际消耗的 token 数吗?

不是。它是生成上限,不是实际用量。实际消耗应读取响应中的 completion_tokens 或 output_tokens;同时要确认所用模型和兼容接口是否支持该参数。

OpenAI 兼容接口的 token 规则完全一样吗?

不能默认完全一样。应分别核对 Base URL、模型 ID、usage 字段、Responses 或 Chat Completions 支持情况,以及公开计费说明。没有明确说明的字段和价格,以对应接口官方文档为准。

参考资料

  1. OpenAI Token Counting 官方指南
  2. OpenAI Chat Completions 创建接口
  3. OpenAI Chat Completion usage 字段说明
  4. OpenAI Get input token counts API Reference
  5. OpenAI Prompt Caching 官方指南
  6. OpenAI API 官方价格页
  7. OpenAI Rate Limits 官方指南