Token Counting

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

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

在统计结构化输出与函数调用 Prompt 的 Token 时,只看用户输入通常会严重低估实际用量。模型收到的完整请求还可能包括 System Prompt、JSON Schema、工具描述、历史消息、Assistant 发起的工具调用、工具返回结果,以及为最终输出预留的空间。

最实用的原则是:统计模型实际收到的完整序列化请求,而不是只统计用户输入框里的文字。

本文将说明结构化输出与函数调用的 Token 统计应包含哪些部分、本地估算为什么可能和 API 返回值不同,以及如何用可复现的请求进行验证。

简短答案

一个包含结构化输出或函数调用的工作流,至少要为以下部分建立 Token 预算:

  1. 基础消息:System、Developer、User 消息及必要的对话历史。
  2. 工具定义:函数名称、描述、参数名、JSON Schema 关键字、枚举和嵌套对象。
  3. 结构化输出 Schema:约束最终回答格式的字段与规则。
  4. Assistant 工具调用:模型生成的函数名与参数 JSON。
  5. 工具结果:数据库记录、搜索结果、API 返回、错误信息等重新加入上下文的内容。
  6. 最终回答:结构化 JSON 或自然语言输出。
  7. 安全余量:应对比测试样本更长的真实生产输入。

不要使用“每个函数固定增加 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
        }
      }
    }
  }'

记录返回值后,再运行几组单变量对照:

每次只改变一个变量,才能知道 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 更大。搜索片段、数据库记录、日志和错误堆栈都应该只保留模型真正需要的字段。

可以采用以下控制措施:

为什么本地 tokenizer 估算可能不同

本地 tokenizer 很适合快速比较,但在结构化请求中,它不一定是最终计费依据。

差异可能来自:

  1. Chat Message 本身有格式开销。角色和消息边界不只由可见文字组成。
  2. 工具定义可能被内部转换。提供商可能把 Schema 序列化为模型专用指令格式。
  3. 不同模型使用不同 tokenizer。一个模型的计数不能直接当作另一个模型的精确值。
  4. 会话状态会增加上下文。续接请求可能包含本地字符串中没有的历史项。
  5. 图片和文件有单独的统计规则。纯文本 tokenizer 无法完整复现多模态用量。
  6. API 会持续更新。旧版 Cookbook 或 SDK 示例中的常数可能失效。

本地统计适合做快速 Diff 和 CI 阈值检查;生产环境应以提供商的精确统计端点或真实 API 返回的 usage 为参考。

用 TokenTest 验证端点返回的 Token 用量

完成本地预算后,可以用 TokenTest 测试实际准备接入的端点与模型路由。

一个实用的验证流程是:

  1. 输入 OpenAI 兼容或 Anthropic 兼容端点。
  2. 使用测试专用 API Key,并选择真实模型 ID。
  3. 运行 Token Usage Audit 与 Tool Channel 检查。
  4. 确认输入和输出用量字段存在,且总数关系合理。
  5. 比较短输入和长输入,验证输入 Token 是否单调增加。
  6. 检查工具调用、停止信号、流式 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 统计:

  1. 捕获完整请求。包含指令、消息、工具、Schema 和续接状态。
  2. 测量基线。如果提供商支持,优先使用精确统计端点。
  3. 测量循环中的最大请求。加入有代表性的调用参数和真实长度的工具结果。
  4. 先预留输出,再压缩输入。确保最终 JSON 或回答有足够空间。
  5. 每次只调整一个组件。缩短说明、减少枚举或裁剪工具结果。
  6. 重新统计并记录差值。只保留不损害行为的 Token 优化。
  7. 运行行为测试。确认精简后的 Schema 仍能选择正确工具并生成有效参数。
  8. 验证真实 Usage。对比本地结果、统计端点结果和响应中的用量字段。
  9. 测试每种生产语言。英文和中文 Prompt 应建立独立预算。

如果需要更完整的上线流程,可以阅读如何建立一套 Token 感知的 Prompt 审查流程。对于长工具循环,还可以参考生产级 LLM 应用的上下文窗口规划开发者指南

Token 统计检查清单

上线前确认已经统计:

总结

结构化输出与函数调用的 Token 统计,本质上是请求工程,而不是单词计数。你需要测量完整 Payload、找到工具循环中的最大请求、保护最终输出空间,并验证端点自身的 Usage 报告。

先使用提供商支持的精确请求统计能力,再用 TokenTest 检查生产路由是否返回一致的 Token 用量和工具通道行为,之后再用这些数据规划成本与上下文窗口。

参考资料