本节摘要:Token 计数(token counting)让你在真正发推理请求之前,精确预估一段文本(或一份文件)会消耗多少 Claude token——从而预估成本、判断是否超长、决定要不要切更大上下文模型。本节讲清它的核心要点:用
count_tokens端点(POST /v1/messages/count_tokens)拿精确计数;千万不要用 OpenAI 的tiktoken,它在 Claude 文本上普遍少算 15~20%,代码与非英文内容偏差更大;计数是模型相关的,要传与推理时相同的模型 ID;端点无状态,跨版本对比靠分别计数再相减。读完本节,你能在发请求前算清成本,并理解不同模型 tokenizer 的差异。
内容来源:Anthropic 官方 Claude API 文档
shared/token-counting.md(随 Claude Code 分发,从泄露素材库提取),汉化并套用体系化模板。
阅读完本节,你应当能够:
count_tokens 端点对一段文本或一份文件做精确的 Claude token 计数。tiktoken/gpt-tokenizer 这类 OpenAI tokenizer 估 Claude 的 token。LLM 按 token 计费、按 token 限长。在发推理请求前先预估 token 数,有三类典型用途:
问题在于:Claude 的 tokenizer 不开源、不公开,本地无法精确模拟。唯一的精确办法是调 Anthropic 提供的 count_tokens 端点。
count_tokens 端点的签名与 messages.create 类似——传 model 与 messages(可选 system、tools),返回 input_tokens:
from anthropic import Anthropic client = Anthropic() resp = client.messages.count_tokens( model="claude-opus-4-8", messages=[{"role": "user", "content": open("CLAUDE.md").read()}], ) print(resp.input_tokens)
TypeScript 写法对称:await client.messages.countTokens({model, messages}) 返回 .input_tokens。其它语言见各自 README,方法名一致。
💡 传与推理时完全相同的参数:要让计数准确,就把推理请求会带的
system、tools、messages都传给count_tokens——它们都计入输入 token。只传 messages 会漏算 system 与工具定义的开销。
ant 命令行工具同样支持,配合 --transform 直接取出数字:
ant messages count-tokens --model claude-opus-4-8 \ --message '{role: user, content: "@./CLAUDE.md"}' \ --transform input_tokens -r
@./CLAUDE.md 是 ant 的文件内联语法,把文件内容读入消息。
最常见的错误是用 OpenAI 的 tiktoken(或 gpt-tokenizer)来估 Claude 的 token 数。这是错的。
tiktoken 是 OpenAI 模型的 tokenizer。Claude 用的是 Anthropic 自家的 tokenizer,两者分词规则不同。在典型英文文本上,tiktoken 比 Claude 实际 token 少算约 15~20%;在代码、非英文(尤其是中文、日文)内容上偏差更大,可能少算 30~50%。
后果是直接的:你以为一份文档是 4 万 token、能塞进预算,实际跑起来是 5.5 万 token——成本超预算、长度超限、缓存策略失效。任何来自 tiktoken、gpt-tokenizer 或类似库的估算,对 Claude 都不可信。
⚠️ 唯一可信源:Claude token 的精确计数只能来自
count_tokens端点(或同等 tokenizer)。如果你看到代码里用tiktoken.encoding_for_model("claud...")之类,那是错的——Claude 不在 tiktoken 的支持列表里。
token 数随模型变化,因为不同模型用不同 tokenizer(或同族 tokenizer 的不同版本)。计数时务必传你推理时打算用的同一个模型 ID。
一个重要差异:Sonnet 5 用了新 tokenizer,同样的文本比 Sonnet 4.6 多约 30% token。这意味着从 Sonnet 4.6 迁到 Sonnet 5,即便提示一字不改,token 消耗也会上涨约三成——成本预估、缓存最小命中门槛、上下文窗口占用都要重算。Opus 4.8 与 Opus 4.7/4.6 用同一 tokenizer,token 数大致不变。
同一句中文/代码,不同模型 token 数差异(示意): Sonnet 4.6: 1000 tokens Sonnet 5: ~1300 tokens ← 新 tokenizer,多约30% Opus 4.8: ~1010 tokens ← 与 4.7/4.6 同 tokenizer
💡 工程启示:迁移模型后,第一件事是用
count_tokens重新数一遍典型提示,重算成本与缓存策略。不要假设 token 数不变。
count_tokens 是无状态的——它不记住任何上下文。所以要对比「同一文件的两个版本」(比如 CLAUDE.md 修改前后)各消耗多少 token,只能分别计数再相减:
from anthropic import Anthropic import subprocess client = Anthropic() def count(text: str) -> int: return client.messages.count_tokens( model="claude-opus-4-8", messages=[{"role": "user", "content": text}], ).input_tokens before = subprocess.check_output(["git", "show", "HEAD:CLAUDE.md"], text=True) after = open("CLAUDE.md").read() print(count(after) - count(before)) # 本次修改净增减的 token 数
这种「两次计数相减」的模式在评估「精简 system prompt 省了多少 token」「加一段检索文档多了多少 token」时很有用。注意两次计数要用同一个模型 ID,否则差异里混入了 tokenizer 差异,无法归因。
count_tokens 的返回值直接服务于第 2 章 03 节的缓存决策与成本预估:
count_resp = client.messages.count_tokens( model="claude-opus-4-8", messages=messages, system=system, ) estimated_input_cost = count_resp.input_tokens * 0.000005 # $5/百万 token print(f"Estimated input cost: ${estimated_input_cost:.4f}")
把计数、缓存、成本三者串起来,就是一个完整的「发请求前预算检查」流程:先数 token → 判断是否达到缓存最小门槛 → 估算命中缓存后的实际成本 → 决定是否值得发。
💡 注意输出 token 无法精确预估:
count_tokens只数输入。输出 token 数取决于模型生成长度,只能按max_tokens设上限、按历史经验估实际值。成本预估里,输入用count_tokens精确算,输出按经验比例估。
count_tokens 端点(POST /v1/messages/count_tokens),传 model + messages(及 system/tools),返回 input_tokens。tiktoken/gpt-tokenizer 是 OpenAI tokenizer,对 Claude 普遍少算 15~20%,代码与非英文偏差更大,任何此类估算都不可信。下一节讲模型选择与迁移——Fable/Opus/Sonnet/Haiku 四档怎么选,以及从旧模型迁到新模型时要处理哪些破坏性变更(如
budget_tokens改 adaptive thinking、采样参数被移除)。