结构化输出与函数调用 Prompt 的 Token 统计方法

结构化输出与函数调用 Prompt 的 Token 统计方法
在统计结构化输出与函数调用 Prompt 的 Token 时,只看用户输入通常会严重低估实际用量。模型收到的完整请求还可能包括 System Prompt、JSON Schema、工具描述、历史消息、Assistant 发起的工具调用、工具返回结果,以及为最终输出预留的空间。
最实用的原则是:统计模型实际收到的完整序列化请求,而不是只统计用户输入框里的文字。
本文将说明结构化输出与函数调用的 Token 统计应包含哪些部分、本地估算为什么可能和 API 返回值不同,以及如何用可复现的请求进行验证。
简短答案
一个包含结构化输出或函数调用的工作流,至少要为以下部分建立 Token 预算:
- 基础消息:System、Developer、User 消息及必要的对话历史。
- 工具定义:函数名称、描述、参数名、JSON Schema 关键字、枚举和嵌套对象。
- 结构化输出 Schema:约束最终回答格式的字段与规则。
- Assistant 工具调用:模型生成的函数名与参数 JSON。
- 工具结果:数据库记录、搜索结果、API 返回、错误信息等重新加入上下文的内容。
- 最终回答:结构化 JSON 或自然语言输出。
- 安全余量:应对比测试样本更长的真实生产输入。
不要使用“每个函数固定增加 N 个 Token”这样的通用公式。真实开销会随模型、API、Schema、消息格式和工具返回内容变化。你需要针对实际生产端点统计完整请求。
为什么 JSON Schema 和工具定义会占用 Token
普通聊天请求中,开发者通常会统计 Prompt 和预期回答。加入函数调用后,模型还必须知道有哪些工具、什么时候调用,以及参数应如何构造。
OpenAI 的函数调用文档明确说明:函数定义会被注入 System Message,并作为输入 Token 占用上下文窗口。因此,即使用户在聊天记录中看不到工具 Schema,其中的字段名、描述、枚举和嵌套结构仍属于实际 Prompt 预算。
下面这个工具定义有明确用途,但它并不是“免费上下文”:
{
"type": "function",
"name": "lookup_order",
"description": "Find an order by its public order ID.",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "Public order ID, for example ORD-10482."
},
"include_events": {
"type": "boolean",
"description": "Whether to include shipment event history."
}
},
"required": ["order_id"],
"additionalProperties": false
},
"strict": true
}
过长的描述、重复表述、大型枚举以及深层嵌套对象都会增加输入长度。优化目标不应该是把 Schema 压缩到最短,而应该是找到仍能稳定完成工具选择和参数生成的最小 Schema。
结构化输出和函数调用不是同一种预算
两者经常一起使用,但解决的问题不同。
| 功能 | 主要用途 | 需要统计的 Token 影响 |
|---|---|---|
| 结构化输出 | 让最终回答符合指定 Schema | 请求中的格式/Schema 开销,以及生成的 JSON 输出 |
| 函数调用 | 让模型选择应用工具并生成参数 | 工具定义、调用参数、工具结果和后续轮次 |
| 两者结合 | 调用工具后返回符合 Schema 的最终 JSON | 上述所有部分 |
结构化输出不会消除输出 Token。模型返回 JSON 时,字段名、字段值、大括号、标点,以及 Schema 允许的说明文字,仍然会计入输出。
一个典型的函数调用流程如下:
System + User + 工具定义
↓
Assistant 生成工具调用及 JSON 参数
↓
工具结果加入对话
↓
Assistant 生成最终回答
如果模型连续调用三个工具,应用可能会在最终回答前加入三组调用参数和三组工具结果。因此,最初很短的 Prompt 也可能在多轮循环中快速膨胀。
一个实用的 Token 预算公式
可以用下面的公式做规划:
计划上下文 = 固定指令
+ 真实分位数下的用户输入
+ 对话历史
+ 工具定义
+ 预计工具调用参数
+ 预计工具结果
+ 结构化输出 Schema
+ 最终输出预留
+ 安全余量
不要只计算第一轮请求。对于 Agent 工作流,应计算整个循环中可能出现的最大请求。最终回答前的那一次请求通常比初始请求更大,因为它已经包含工具调用和工具结果。
结构化输出的精确统计示例
OpenAI 文档提供了 Responses API 的输入 Token 统计端点。该端点接收与真实请求相同的结构,并在不生成回答的情况下返回精确输入 Token 数。对于支持该能力的模型和 API,它比字符数或单词数估算更可靠。
把下面的请求粘贴到 API 客户端,并将模型替换为生产环境实际使用的模型:
curl https://api.openai.com/v1/responses/input_tokens \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "YOUR_MODEL_ID",
"input": [
{
"role": "system",
"content": "Extract the support request into the required schema."
},
{
"role": "user",
"content": "Order ORD-10482 arrived damaged. Refund to the original payment method."
}
],
"text": {
"format": {
"type": "json_schema",
"name": "support_request",
"strict": true,
"schema": {
"type": "object",
"properties": {
"order_id": {"type": "string"},
"issue": {"type": "string"},
"requested_action": {"type": "string"}
},
"required": ["order_id", "issue", "requested_action"],
"additionalProperties": false
}
}
}
}'
记录返回值后,再运行几组单变量对照:
- 删除没有必要的字段说明。
- 把大型枚举替换为更短、稳定的代码列表。
- 将一个覆盖面过大的 Schema 拆成按路由选择的多个 Schema。
- 分别测试英文和中文生产输入。
- 使用最长的合理用户消息,而不是只测试理想样本。
每次只改变一个变量,才能知道 Token 差异来自哪里。
包含函数工具的精确统计示例
接下来测量包含工具定义的请求:
curl https://api.openai.com/v1/responses/input_tokens \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "YOUR_MODEL_ID",
"input": "Where is order ORD-10482?",
"tools": [
{
"type": "function",
"name": "lookup_order",
"description": "Find an order by its public order ID.",
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string"},
"include_events": {"type": "boolean"}
},
"required": ["order_id"],
"additionalProperties": false
},
"strict": true
}
]
}'
然后运行一遍删除 tools 的相同请求。两者差值就是该模型与该请求结构下,这个工具定义带来的请求侧开销。不要把这个差值当成跨模型、跨 API 永久有效的常数。
不要只统计第一次请求,要统计完整工具循环
最常见的预算错误,是只测量初始请求,却忽略后续轮次。
假设模型生成以下工具调用:
{
"name": "lookup_order",
"arguments": {
"order_id": "ORD-10482",
"include_events": true
}
}
随后应用把工具结果加入对话:
{
"order_id": "ORD-10482",
"status": "in_transit",
"carrier": "Example Express",
"estimated_delivery": "2026-07-30",
"events": [
{"time": "2026-07-27T08:10:00Z", "status": "departed_sort_center"}
]
}
下一次模型请求将同时包含早期消息、Assistant 工具调用和工具结果。在生产环境中,工具结果通常比工具 Schema 更大。搜索片段、数据库记录、日志和错误堆栈都应该只保留模型真正需要的字段。
可以采用以下控制措施:
- 返回紧凑对象,而不是把上游 API 完整响应直接塞进上下文。
- 在加入模型上下文前限制结果数量。
- 删除重复标签、无用元数据和
null字段。 - 在安全可行时,总结较早的工具结果。
- 保留稳定 ID,让模型只在需要时请求详细信息。
为什么本地 tokenizer 估算可能不同
本地 tokenizer 很适合快速比较,但在结构化请求中,它不一定是最终计费依据。
差异可能来自:
- Chat Message 本身有格式开销。角色和消息边界不只由可见文字组成。
- 工具定义可能被内部转换。提供商可能把 Schema 序列化为模型专用指令格式。
- 不同模型使用不同 tokenizer。一个模型的计数不能直接当作另一个模型的精确值。
- 会话状态会增加上下文。续接请求可能包含本地字符串中没有的历史项。
- 图片和文件有单独的统计规则。纯文本 tokenizer 无法完整复现多模态用量。
- API 会持续更新。旧版 Cookbook 或 SDK 示例中的常数可能失效。
本地统计适合做快速 Diff 和 CI 阈值检查;生产环境应以提供商的精确统计端点或真实 API 返回的 usage 为参考。
用 TokenTest 验证端点返回的 Token 用量
完成本地预算后,可以用 TokenTest 测试实际准备接入的端点与模型路由。
一个实用的验证流程是:
- 输入 OpenAI 兼容或 Anthropic 兼容端点。
- 使用测试专用 API Key,并选择真实模型 ID。
- 运行 Token Usage Audit 与 Tool Channel 检查。
- 确认输入和输出用量字段存在,且总数关系合理。
- 比较短输入和长输入,验证输入 Token 是否单调增加。
- 检查工具调用、停止信号、流式 Usage、缓存证据或推理 Token 字段是否符合该路由预期。
TokenTest 还提供离线响应分析。你可以把原始 Response JSON 粘贴到“Analyze response JSON”输入框中,不需要提供 API Key。例如:
{
"model": "your-resolved-model-id",
"usage": {
"input_tokens": 842,
"output_tokens": 96,
"total_tokens": 938
}
}
这个示例只用于检查响应结构解析。验证生产行为时,请替换为实际端点返回的响应。
可复用的优化流程
按照以下顺序完成结构化输出与函数调用的 Token 统计:
- 捕获完整请求。包含指令、消息、工具、Schema 和续接状态。
- 测量基线。如果提供商支持,优先使用精确统计端点。
- 测量循环中的最大请求。加入有代表性的调用参数和真实长度的工具结果。
- 先预留输出,再压缩输入。确保最终 JSON 或回答有足够空间。
- 每次只调整一个组件。缩短说明、减少枚举或裁剪工具结果。
- 重新统计并记录差值。只保留不损害行为的 Token 优化。
- 运行行为测试。确认精简后的 Schema 仍能选择正确工具并生成有效参数。
- 验证真实 Usage。对比本地结果、统计端点结果和响应中的用量字段。
- 测试每种生产语言。英文和中文 Prompt 应建立独立预算。
如果需要更完整的上线流程,可以阅读如何建立一套 Token 感知的 Prompt 审查流程。对于长工具循环,还可以参考生产级 LLM 应用的上下文窗口规划开发者指南。
Token 统计检查清单
上线前确认已经统计:
- [ ] System 与 Developer 指令
- [ ] 符合真实长度的用户输入
- [ ] 必要的对话历史
- [ ] 每个启用的工具定义
- [ ] 函数调用参数 JSON
- [ ] 工具结果 Payload
- [ ] 结构化输出 Schema
- [ ] 最终 JSON 或文本输出预留
- [ ] 重试与修复轮次
- [ ] 英文、中文和其他生产语言版本
- [ ] 超长输入的安全余量
- [ ] 上线后的实际端点 Usage
总结
结构化输出与函数调用的 Token 统计,本质上是请求工程,而不是单词计数。你需要测量完整 Payload、找到工具循环中的最大请求、保护最终输出空间,并验证端点自身的 Usage 报告。
先使用提供商支持的精确请求统计能力,再用 TokenTest 检查生产路由是否返回一致的 Token 用量和工具通道行为,之后再用这些数据规划成本与上下文窗口。