客户端估算的 Token 数量与供应商结算账单的差异
你的应用程序使用与你认为的提供商所使用的相匹配的分词器(tokenizer)库在本地计算 token 数量。SDK 在每次调用前报告“预计 4,200 个 token”。你的预算逻辑通过了该请求。然而,提供商的账单显示相同的负载为 6,800 个 token。将这 60% 的差距乘以每月数百万次的调用,财务团队无法根据你的日志核对的这一项开支,看起来就不再是四舍五入的误差,而是一个架构错误。
错误并不在于本地分词器是错的。错误在于将本地分词器视为一种契约,而不是一种猜测。Token 化(Tokenization)是提供商在其服务栈内部完成的事情——你的库只是该过程的一个模型,而非过程本身,而且两者会产生偏差:这种偏差在每次调用中虽然微小,但在你实际进行的总体调用量中却具有结构性。
这是几个月后才会显现的故障模式。第一周,你的单次对话成本符合预估。第一个月,差异看起来像是噪声。等到财务人员询问为什么 AI 支出超预算 30% 时,你已经在一个无法独立验证的数字之上构建了缓存、速率限制、单客配额和 SLO 警报。你信任的数字是提供商选择告 诉你的数字,而他们选择向你收费的数字则是另一个。
你的 SDK 中的分词器并非他们数据中心里的分词器
每个主要的提供商都会提供或曾经提供过分词器库:OpenAI 的 tiktoken,Anthropic 的 count-tokens 端点,以及 Gemini 类似的本地辅助工具。这个初衷是合理的——在调用前进行计数,以便你可以进行预算、截断或拒绝请求。现实情况是,你导入的库是一个参考实现,它在三个可预见的方面落后于服务栈。
首先,你的 SDK 所针对的编码模型可能已经不再是服务器目前使用的编码模型。分词器会更新。Anthropic 最近的版本引入了一个分词器,对于相同的文本,它的计数可能比之前的版本多得多——公开指南建议某些内容可能会增加多达 35%。如果你的 SDK 锁定在六个月前,而你的账户被静默迁移到了新的模型系列,那么你是在根据去年的分词器进行估算,而根据今年的分词器进行付费。
其次,服务栈会添加你的库看不见的 token。聊天模型会将你的消息包装在聊天模板中——角色头、分隔符、系统前言,偶尔还有提供商在他们那边注入的时间戳。该模板的确切字节数是提供商的细节,并且会不断演变。这些库的 token 计数问题列表中充满了错误报告,其中本地计数与实际偏差从一个到几百个 token 不等,具体取决于消息中包含哪些工具调用(tool calls)或系统提示词(system prompts),因为本地库无法完全复制服务器应用的模板。
第三,多模态和结构化输入的计数规则,本地分词器根本没有实现。图像不是文本。音频不是文本。函数调用(function-calling)工具模式(schema)在传输时是文本,但在计数之前会在服务器上进行规范化和重写。本地辅助工具可以为你提供提示词中散文部分的数字;而图像成本、音频成本和工具调用的开销则由网络另一端的不同代码路径计算。
孤立来看,其中每一项都很小。但结合在一起,它们产生的差距并非随机的——它在你最关心的输入上存在向上偏移。
差距在你关心的输入上最为显著
平均 1–3% 的漂移令人恼火。但如果平均 1–3% 的漂移在短英文提示词上实际上是 0% 的漂移,而在多语言代码粘贴提示词上是 40% 的漂移,那就是另一个问题了。你实际服务的输入群体并非均匀分布,这种差异会集中在特定的地方。
非拉丁脚本是最一致的膨胀来源。对于人类读者来说,一段英文和中文的长度大致相同,但在 token 化时却大不相同。主要针对英文文本训练的分词器会将一个汉字编码为多个 token,倍数取决于脚本和词汇表版本。如果你的产品推出了日语版,而你的预测是基于英语使用情况构建的,那么每次对话的成本就不是你的电子表格上所写的数字了。
粘贴的代码具有相同的特性。源代码包含大量的标识符、标点符号和空格,压缩效果很差。粘贴到聊天中的 500 行文件,其 token 化速率可能是 500 行散文的两倍。如果你运营一个编程代理(coding agent),每次应用的 diff、每次检查的堆栈跟踪、每次读取的测试输出都处于分词器行为中这种容易膨胀的区域。
表情符号(Emoji)和罕见的 Unicode 字符处于曲线最糟糕的部分。一个表情符号占用的 token 可能比一个短单词还多。列表符号、智能引号、数学符号以及人们从富文本编辑器粘贴的装饰性空格都会增加成本,这些成本在字数统计中是不可见的,但在账单中却清晰可见。
**工具模式(Tool schemas)**值得单独拿出来说。函数调用代理在每个请求中都会发送一个 JSON 模式,描述可用的工具——名称、描述、参数类型、枚举值。该模式存在于输入窗口中,并在每次调用时计费。提供商端的规范化对这一部分的膨胀或压缩可能与你本地的预检查计数预期不同,主要 SDK 分词器的 GitHub issue 队列中包含了明确的承认:一旦涉及工具调用, token 计数就会出现明显分歧。
结论是,将你的分词器与 API 进行平均情况下的基准测试会告诉你差距很小。而导致你账单爆炸的是那些最坏情况,并且它们被系统性地低估了。
你未发送但被计费的隐藏 Token
第二类差异更令人不安。提供商可能会在你的输入中添加你的客户端从未见过、也从未有机会计算的 Token。
其中一些是良性且已公开的。Anthropic 的文档指出系统优化可能会增加 Token,但明确声明计费仅反映你的内容。OpenAI 的聊天模板(chat-template)开 销已有详尽文档,经验丰富的从业者通常会加上一个修正因子(fudge factor)。这些是会计细节,而非意外。
其他情况则更具争议。推理模型会对你在响应中从未见过的隐藏思维链(chain-of-thought)Token 收费。一篇 2025 年关于“LLM API 中隐藏 Token 的预测性审计”的研究论文指出,提供商在对中间推理收费的同时又将其隐藏,这造成了客户难以核实的信息不对称——他们只能检查账单是否合理,而无法确认底层计数是否正确。无论这种不对称是否被利用,它在结构上是存在的,因为客户没有独立的方法来审计在提供商服务端架构内部生成的 Token 计数。
这种防御姿态并非偏执——而是假设输入侧是近似的,输出侧是部分不透明的,并将提供商返回的 usage 元数据视为实际计费的真实来源,而不是你的预检估算。
“弥合差距”的实际做法
正确的设计应将你的本地 Token 计数视为预算提示,而非预算权威。以下几种模式可以让这一机制在生产环境中发挥作用。
返回的 usage 是预算账本,而非本地估算。 现代 LLM API 的每一个响应都带有一个 usage 字段,记录了该次调用实际计费的输入和输出 Token。将其接入你保存本地预检估算的地方,并将差异反向传播到每个租户、每个功能、每个提示词模板的成本模型中。如果你只记录估算值,你追踪的数字就会在无人负责的情况下逐渐偏离你的发票。
以带有余量的标准准入请求,而非原始估算。 如果你的本地计数显示 为 4,200 个 Token,而上限是 4,500 个,那么这个上限就是错误的。应根据你即将发送的输入类型的“估算值 / 实际值”的经验分布来选择余量——且该比例应按语言、内容类型和工具模式(tool-schema)版本进行衡量,而不是将其作为单一的全局常量。编码智能体和英文聊天机器人的余量是不通用的。
定期进行差异审计。 每周一次,抽取数千次最近的调用样本,计算返回的 usage 与本地估算的比例,按提示词模板和内容语言进行切片分析,并在任何切片的偏移超过阈值时发出警报。这可以同时捕获两件事:提供商端悄无声息的分词器(tokenizer)更改,以及你这一端的内容组合变化,导致你进入了分词器表现更差的领域。由此产生的数据报告也是财务团队在要求你对账时所需要的。
将 SDK 分词器版本视为依赖合约的一部分。 锁定版本,在每次调用中记录它,并将升级视为值得进行评估(eval)的变更——这并不是因为分词器逻辑本身破坏了任何语义,而是因为下游的每一个成本预测和预算守卫都会因此发生变动。你对模型版本锁定所采用的严谨态度,同样适用于分词器。
如果提供商提供了 count_tokens 端点,将其作为你的预检权威;只有在不存在该端点时才使用本地库。 Anthropic 和 AWS Bedrock 都提供预检计数端点,其运行的分词器与服务端路径完全一致。它们比本地库慢,并且会增加一次网络往返,但当估算值由与发票相同的代码计算时,估算与发票之间的差距会显著缩小。在关键路径上使用它们——例如大额请求的准入控制、昂贵工具的预算门槛——而只在网络往返会主导延迟的廉价、高并发场景中接受本地近似值。
更难的问题:两个分词器,且都是官方的
这个问题最深层的版本是即使使用官方库也无法解决的。“官方”分词器出现在两个地方——一个是捆绑在 SDK 中的,另一个是运行在服务端技术栈中的——它们的版本可能会不一致。SDK 的构建节奏受开源仓库和包发布计划的控制。而服务端技术栈的构建节奏则受提供商集群滚动部署的控制。没有任何合约规定它们在任何给定的一天必须保持一致。
一个真实的例子:提供商推出了一个分词器更改,以解决生成管道中的质量问题。服务端集群在部署窗口内应用了这一更改。而 PyPI 或 npm 上发布的库则在几天或几周后的下一次发布中才包含该更改。任何在该窗口期间使用发布的库运行预检计数的人,实际上是在针对一个服务器上已不再存在的分词器进行计数。这种偏移很小,但具有持久性,因为如果你在该窗口期间缓存了成本估算或设置了预算上限,那么在重新构建之前,这些缓存和上限都是错误的。
结论并不是你应该避免使用本地分词器——它们仍然是在发送请求前限制请求范围的最廉价方式。结论是,你永远不应该让你的系统仅根据本地估算就做出不可逆的决定(准入、拒绝、收费、分配)。提供商返回的 usage 完成了闭环。如果你的架构中没有地方将返回的 usage 反馈到预算逻辑中,那么你构建的系统在结构上就无法与发票对账。
这所要求的工程纪律
Token 计数表面上是一个测量问题。实际上,它是一个合同问题。你发送的 Token 和被计费的 Token 是由网络边界另一端不受你控制的代码决定的,而你导入的库只是该代码的一个近似实现,且允许存在偏差。
处理得好的团队有三个共同的习惯。他们在每次调用时,都会将供应商返回的使用量与他们自己的预估值一起记录。他们针对两者的比例(而非其中任何一个的绝对值)设置告警。他们将 tokenizer 版本固定、提示词模板更改以及供应商模型升级视为影响成本的事件,并像对待数据库迁移一样给予同等级别的审核纪律——因为其中的每一项都可能导致某项成本支出产生百分比级的变动,并在整个请求量中产生复利效应。
成本差异是可以修复的。但一旦你在未经验证的本地估算基础上构建了六个月,无法修复的是机构对于 AI 账单与 AI 日志一致性的信心。趁着业务量还小、差距尚可商榷时,尽早构建对账循环。一旦某项支出大到财务部门开始询问时,他们想要的答案不是“我们认为供应商的 tokenizer 变了”——而是一份你可以毫不犹豫交出的、按租户和按月排列的预估值对比实际值的图表。
- https://github.com/openai/tiktoken/issues/474
- https://community.openai.com/t/discrepancy-in-token-counts-between-tiktoken-and-api-usage-for-o4-mini-gpt-4o-mini/1271170
- https://platform.claude.com/docs/en/build-with-claude/token-counting
- https://docs.anthropic.com/en/api/messages-count-tokens
- https://www.propelcode.ai/blog/token-counting-tiktoken-anthropic-gemini-guide-2025
- https://galileo.ai/blog/tiktoken-guide-production-ai
- https://portkey.ai/blog/tracking-llm-token-usage-across-providers-teams-and-workloads/
- https://flexprice.io/blog/how-to-meter-llm-tokens-usage-for-billing
- https://arxiv.org/html/2508.00912v1
- https://docs.aws.amazon.com/bedrock/latest/userguide/count-tokens.html
