Token Counting

为什么你的 Prompt 太长:常见的 token 计数错误

一个 prompt 在编辑器里看起来很短,实际却仍然可能因上下文长度错误而失败。通常原因并不是模型“算错了”。而是你计算的数字并不是模型实际收到的完整请求。

开发者经常只统计可见的用户消息,按词数或字符数估算,然后把这个数字与模型的上下文窗口比较。但实际输入还可能包括 system 或 developer 指令、对话历史、检索到的文档、工具定义、JSON schema、附件,以及特定于模型的格式化内容。你还需要为输出留出空间。

更安全的规则是:

统计精确序列化后的请求,预留输出容量,然后验证线上端点报告的用量。

本指南解释最常见的 token 计数错误,并提供你可以借助 TokenTest 在端点上运行的实用测试。

“prompt 太长”通常是什么意思

在实践中,“prompt 太长”可能描述几种不同的失败:

先把错误当作请求构造问题来处理。在缩短有用指令之前,先确定到底是哪一层在消耗预算。

错误 1:把 token 当作词或字符

Token 是 tokenizer 生成的文本片段。它们并不可靠地等同于词、字符、字节或行。

标点、空格、大小写、较长的标识符、数字、emoji、重音符号文本、中文文本和代码的拆分方式都可能不同。即使看起来相似的字符串,也可能产生不同的 token 数量。

例如,这些输入在视觉长度上相近,但结构差异很大:

Reset the user password after identity verification.
{"action":"reset_password","requires_identity_verification":true}
验证身份后重置用户密码。

一个固定的“每个 token 四个字符”或“每个单词一个 token”规则,可能足以在已知的英文语料上做粗略的早期估算。但它不适合用于强制执行生产环境限制。

更好的做法:在可用时,使用与目标模型对应的 tokenizer。对于结构化 API 请求,如果提供方提供了精确的服务端计数方法,就使用该方法。

错误 2:使用了错误的模型或 tokenizer

token 数量取决于 tokenizer。用一个方便的 tokenizer 来计数,却发送给另一个模型,会造成一种虚假的精确感。

这个问题会在以下情况下出现:

OpenAI 的 token 计数指南明确将模型映射到编码,并建议使用与模型相关的编码查找。其较新的 API 指南还将本地字符串 tokenization 与结构化 API 输入的精确计数区分开来。

更好的做法:在每次测量中同时记录 requested_modelresolved_model。在测试中固定 tokenizer 版本。如果解析后的模型发生变化,就让旧基线失效。

错误 3:只计算可见的用户 prompt

你在 prompt 编辑器中看到的文本,通常只是请求的一层。

一个真实的应用可能会发送:

system 或 developer 指令
+ 对话历史
+ 当前用户消息
+ 检索到的文档
+ 文件或图像输入
+ 工具定义
+ JSON schema
+ 响应格式说明
+ 提供商特定的消息格式化
= 实际呈现给模型的输入

如果你的界面显示的是 2,000 token 的用户消息,但你的框架附加了 8,000 token 的历史记录和检索内容,那么实际输入并不是 2,000 token。

这在聊天机器人中尤其常见。每一轮都可能重放更早的消息。一个十轮前还能正常工作的对话,即使最新的用户消息很短,也可能跨过限制。

更好的做法:建立一个组件级台账:

请求组件 是否计入? 压缩规则
System/developer 指令 去重重复的策略
对话历史 对旧轮次做摘要或窗口化
当前用户消息 保留实际任务
检索上下文 对片段排序、过滤并设上限
工具和 schema 只加载相关定义
附件 使用提供商的计数方法
输出预留 为有效回答预留空间

不要把用户消息的计数标记为总 prompt 计数。

错误 4:忘记 tools、schema 和框架包装层

工具调用会增加令人意外的大量输入。函数名通常开销很小;冗长的描述、重复的示例、嵌套 schema、枚举以及响应格式定义则不是。

框架也可能把一个紧凑的配置转换成更大的线上负载。一个工具可能只在应用代码中定义一次,但会序列化到每一次请求中。

考虑一个拥有 30 个工具的支持代理。如果当前问题只需要订单查询和退款资格,那么发送全部 30 个定义会浪费上下文,并可能让工具选择更困难。

更好的做法:在 API 调用之前,立即捕获最终的请求负载。统计或测量那个版本,而不是更早的 prompt 模板。仅动态加载与当前路由相关的工具,并保持描述精确。

错误 5:把整个上下文窗口都用于输入

模型的上下文容量并不自动等同于输入额度。请求还需要为输出预留空间,而且某些系统可能会在整体预算中将额外生成 token 或推理 token 计入其中。

如果某个应用几乎用尽了可用上下文来放输入,然后又要求输出一段很长的回答,就会出现以下三种情况之一:

  1. 请求在生成前被拒绝。
  2. 答案在达到 token 限制时提前停止。
  3. 系统以某种会破坏任务的方式裁剪输入或输出。

更好的做法:在组装上下文之前先定义预算:

安全输入预算
= 支持的上下文容量
- 必需的输出预留
- 运行安全余量

输出预留应根据任务来定,而不是根据剩余空间来定。分类响应可能只需要很少的空间。代码补丁、结构化报告或多步骤分析可能需要更多。

错误 6:统计草稿,而不是生产请求

小的转换会不断累积:

在这些转换之前进行统计,回答的是错误的问题。

更好的做法:在请求管道中尽可能靠后的时点进行统计。将被统计负载的哈希值与测量结果一起保存。如果在传输前哈希值发生变化,那么这个计数就是过时的。

错误 7:假设所有粘贴内容都有相近的 token 密度

开发者常常因为按字符数或行数比较文件,而修剪错了内容。

高 token 密度内容可能包括:

相较之下,一段更长的英文自然语言说明,可能比一段更短但噪声很大的机器数据更省 token。

更好的做法:分别测量各个组件。先移除低价值、高 token 的内容:重复日志、无关帧、压缩后的资源、生成文件、过时的检索片段,以及无关的工具 schema。

错误 8:把本地计数当作最终用量

本地分词统计很有用,但它并不总是最终的运行时数字。

实时端点可能会计入结构化消息、缓存输入、图片、文件、工具、schema,或特定提供商的格式化方式。一个与 OpenAI 兼容的网关也可能报告不完整或内部不一致的 usage 字段。

请将这些度量分开:

Measurement What it tells you
artifact_tokens_local 粘贴的文档、代码或日志的大小
request_tokens_local 组装后请求的本地估计值
input_tokens_reported 实时端点报告的输入用量
cached_input_tokens 单独报告的复用输入(如支持)
output_tokens_reported 生成输出的用量
total_tokens_reported 提供商报告的总量(如果有)

不要把缺失字段强行设为零。将缺失值存储为 null,因为“未报告”和“报告为零”是不同的事实。

用于调试长 prompt 的实用 TokenTest 工作流

TokenTest 不是模型特定 tokenizer 的替代品。它会在 tokenizer 之外验证实时端点:模型身份、usage 完整性、输入 token 行为、输出上限、缓存证据以及协议一致性。

TokenTest 产品手册 描述了用于检查输入 token 单调性、总 token 一致性、输出合理性、stop/token-limit 关联、流式 usage 以及缓存 token 行为的方法。在组装好真实请求后,请使用这些检查。

测试 1:短 prompt 与扩展 prompt 对比

使用相同的任务和模型发送两个请求:

Version A: current user task only
Version B: system prompt + history + retrieved context + current task

报告的输入用量应当朝着可信的方向上升。如果它保持不变、以可疑的固定量跳变,或者消失了,就要检查端点的 usage 报告。

测试 2:无工具与启用工具对比

同一条用户消息先在不带工具的情况下运行一次,再使用生产环境的工具 schema 运行一次。

记录:

这可以揭示你的工具表面的实际成本。

测试 3:输出预留与停止行为

保持输入不变,改变输出限制。确认端点的停止信号和报告的输出用量表现一致。

一个 prompt 不能仅仅因为请求被接受就算生产安全。它必须留出足够空间来完成所需的回答。

测试 4:为缓存证据重复执行

当你的提供方支持 prompt 缓存时,请对同一个稳定前缀请求发送两次。检查是否出现与缓存相关的用量,以及总计账是否仍然一致。不要因为文本相同,就假设重复输入一定会被缓存。

你可以保留在 CI 中的请求清单

为每个 prompt fixture 使用一个小型清单:

{
  "test_id": "support-agent-refund-01",
  "requested_model": "your-model-id",
  "resolved_model": null,
  "payload_sha256": "replace-after-serialization",
  "user_message_tokens_local": null,
  "request_tokens_local": null,
  "input_tokens_reported": null,
  "cached_input_tokens": null,
  "output_tokens_reported": null,
  "total_tokens_reported": null,
  "output_reserve": 1200,
  "finish_reason": null,
  "task_valid": null
}

在硬限制之下设置一个告警阈值。当 prompt 变更超出已批准的请求预算,或移除了输出预留时,让 CI 失败。然后在更改模型、网关、工具集或缓存行为之前,先运行一次实时的 TokenTest 评估。

若要了解更深入的预算工作流程,请阅读 面向生产环境 LLM 应用的上下文窗口规划开发者指南。若要针对具体工件进行缩减,请参见 用于代码 prompt 的 token 计数:文件、diff、日志和堆栈跟踪

最终检查清单:为什么你的 prompt 太长?

在删除有用上下文之前,请验证以下所有内容:

最重要的修正很简单:你可见的 prompt 并不一定就是完整的 prompt。一旦你统计完整请求并验证端点的用量行为,“prompt 太长”就不再是一个模糊的模型错误,而会变成一个可测量的工程问题。

从实时的 TokenTest 评估控制台 开始,或者浏览 TokenTest 博客 上更多实用指南。

来源