更新于

LLM Token 计算完整指南:tiktoken / Anthropic / 中文


写 LLM 应用早晚要算 token:估算成本、控制上下文、走 caching 边界。但每家 tokenizer 不同,中文又特别诡异。本文给完整解析与实战代码。

一、什么是 token

简单说:模型不直接看字符,看 token——子词单元。一个 token 可能是:

  • 一个字符(英文标点)
  • 一段词根(“running”=“run”+“ning”)
  • 一个常见词(“hello”)
  • 一个汉字(中文)

LLM 的”上下文长度 200k tokens”= 200k 个这种单元。

二、英文 vs 中文 token 比例

文本字符token 数(OpenAI)
Hello world112
你好世界44–6
1000 字英文段落1000~200
1000 字中文段落1000~600

中文每个字大约 1–1.5 个 token,英文一个 token 平均 3–4 个字符。中文输入成本约是英文的 3 倍

三、OpenAI 用 tiktoken

# pip install tiktoken
import tiktoken

enc = tiktoken.encoding_for_model("gpt-5")
tokens = enc.encode("你好世界 Hello world")
print(len(tokens))   # 7
print(tokens)        # [...]

GPT-4 / GPT-5 都用 o200k_base 编码。早期模型用 cl100k_base

估算 message 数

不是简单拼字符串。Chat Completion 有 overhead:

def count_tokens(messages, model="gpt-5"):
    enc = tiktoken.encoding_for_model(model)
    total = 0
    for m in messages:
        # 每个 message 约 +3-4 token overhead
        total += 4
        for k, v in m.items():
            total += len(enc.encode(str(v)))
            if k == "name":
                total -= 1
    total += 2   # priming reply
    return total

实际值看 response.usage 准。客户端 tiktoken 是估算,可能差 5–10%。

四、Anthropic 用专属 tokenizer

Claude 没有公开本地 tokenizer。两种方法:

方法 A:用 API 算

import anthropic

client = anthropic.Anthropic(base_url="https://yotradeapi.com", api_key="sk-yo-...")

resp = client.messages.count_tokens(
    model="claude-sonnet-4-6",
    messages=[{"role": "user", "content": "你好世界"}],
)
print(resp.input_tokens)

需要中转支持 /v1/messages/count_tokens 端点。

方法 B:用 tiktoken 做粗估

# 不精确但能用,差 10-20%
enc = tiktoken.get_encoding("cl100k_base")
estimated = len(enc.encode(text)) * 1.1   # Claude 略多

适合预估,精确数字看 response.usage

五、Gemini

# google-generativeai
import google.generativeai as genai

model = genai.GenerativeModel("gemini-2.5-pro")
print(model.count_tokens("你好世界"))

通过 OpenAI 兼容路径调用时,看 response.usage。

六、Tokens vs 字符 vs 字数

写工具函数前注意:

概念含义
token模型单元(计费)
characterUnicode 字符
字 / 字符(中文)一个汉字 = 一个 char
词(中文)“你好” = 1 词 = 2 字

不同语言换算不一样:

语言1k tokens ≈
英文750 字
中文600–700 字
代码500 行
JSON300 行

七、估算成本

def estimate_cost(input_tokens, output_tokens, model="claude-sonnet-4-6"):
    PRICING = {
        "claude-opus-4-7": {"input": 15.0, "output": 75.0},  # per 1M tokens
        "claude-sonnet-4-6": {"input": 3.0, "output": 15.0},
        "claude-haiku-4-5": {"input": 0.8, "output": 4.0},
        "gpt-5": {"input": 10.0, "output": 50.0},
        "gemini-2.5-pro": {"input": 1.25, "output": 5.0},
        "gemini-2.5-flash": {"input": 0.075, "output": 0.30},
    }
    p = PRICING[model]
    return (input_tokens / 1_000_000) * p["input"] + (output_tokens / 1_000_000) * p["output"]

# 用法
print(estimate_cost(5000, 800, "claude-sonnet-4-6"))   # → $0.027

UI 里实时展示当前对话累计花费,用户体验好。

八、token-aware 上下文裁剪

def trim_messages_to_budget(messages, model, budget=180_000):
    """从最早消息删,保留 system + 最近 N 条"""
    enc = tiktoken.encoding_for_model(model)
    
    def cost(msgs):
        return sum(len(enc.encode(str(m["content"]))) for m in msgs)
    
    system = [m for m in messages if m["role"] == "system"]
    history = [m for m in messages if m["role"] != "system"]
    
    while history and cost(system + history) > budget:
        history.pop(0)   # 删最早
    
    return system + history

200k 上下文模型留 20k 安全冗余给输出。

九、Caching 边界(Anthropic)

Anthropic 缓存有最小长度:

  • Sonnet / Opus: 1024 tokens
  • Haiku: 2048 tokens

低于这个长度 cache_control 无效。校验:

if count_tokens(system_prompt) < 1024:
    # 这段太短,不要加 cache_control

十、Tool schema 也算 token

很多人忘了:

tools=[{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "...",
        "parameters": {...}
    }
}]

整个 tools 数组算进 input tokens。10 个工具能占 5–10k token。只注册必要的 tool

十一、Vision token

图片也算 token:

模型1024×1024 图片
GPT-5~1100 token
Claude Sonnet~1500 token
Gemini Pro~250 token

详见 LLM Vision API 对比

十二、实战:token budget tracker

class TokenBudget:
    def __init__(self, daily_limit=1_000_000):
        self.daily_limit = daily_limit
        self.used_today = 0

    def check(self, estimated):
        if self.used_today + estimated > self.daily_limit:
            raise BudgetExceeded()

    def record(self, actual):
        self.used_today += actual

# 用法
budget = TokenBudget(daily_limit=1_000_000)
budget.check(estimate_tokens(prompt) + 1000)
resp = client.chat.completions.create(...)
budget.record(resp.usage.total_tokens)

加 redis 持久化 + cron 每天清零,能给个人 / 团队加保护。

十三、相关阅读

需要查询每条请求精确 token 数的中转?YoTradeApi 后台展示每条请求的 input / output / cached / total tokens,方便复盘。