核验清单

  • ✓把这一次请求的 tools 和 usage.input_tokens 对着看。目录很长、模型却没有 function_call,输入仍然可以很大。那是定义在计,不是接口把「没调用」记错了。
  • ✓若用了 defer_loading,分开看名字、说明和参数结构。工具搜索页写的是:单个被推迟的函数,一开始仍看得到名字和说明,推迟的主要是参数结构。
  • ✓找到下一次请求里的 function_call_output。字符串、图片或文件有多长,就按多长进这一次的 input。不要用上一笔很短的函数参数去估。
  • ✓输出列分开看可见的函数调用、可能存在的格式 token,以及 reasoning_tokens。计数页写明,有的模型在推理明细为 0 时,报告的输出仍可以多于可见正文。
  • ✓要用计数接口,把将要发送的 tools 和 input 一起送进去。文档写明本地分词器很难数清工具和结构,按字数除一个常数会漏。

1. 定义先写进系统消息

函数调用在 Token Usage 一节把机制写在「模型会不会真的去调」之前:函数定义在底层被注入系统消息,用的是模型训练过的那套语法。因此可调用的函数定义既计入上下文上限,也按输入 token 计费。文档接着给的办法是少挂一些、把描述写短,或者用工具搜索,让不常用的工具只在需要时加载。它没有写「这一轮没点到,就把定义的 token 退回」。

所以账单上的输入,可以对上整份你挂上去的目录,而对不上聊天框里那句很短的函数名。模型可以回一个 function_call,也可以一个都不回。定义已经在这次请求的 tools 里,输入列就已经包含它们。把「它没调用任何工具」读成「这次不该为工具付钱」,是把有没有发生调用,和定义占不占输入,当成了同一件事。

计数 token把同一件事说成可事先核对的数字。计数接口接受和 Responses 相同的输入格式,文本、消息、图片、文件、工具或对话都可以送进去,返回的是模型将收到的数量。工具定义那一节写明:函数结构、MCP 服务器等会给上下文增加 token,要和输入一起计。本地分词器能数纯文本,数不清工具和结构。按字符除以一个常数,会把目录当成几乎不占地方。

2. 推迟的是参数,不是名字

工具搜索的第一段说,模型可以按需要把工具搜出来、装进上下文,从而避免一开始加载全部定义,并可能降低总体 token 用量和成本。同一页把边界写在后面。请求开始时,模型仍然看得到可搜索对象的名字和说明。命名空间或 MCP 服务器,一开始只有名字和说明,里面各个函数的细节要等工具搜索把它们加载出来才出现。单个被标记推迟的函数,名字和说明仍然从这一轮一开始就在,实际推迟的主要是参数结构。

新工具被发现之后,注入到上下文窗口的末尾。文档说这样设计是为了在成本和延迟上保住模型的缓存。末尾注入解释的是缓存前缀还在不在,不是把已经在上下文里的名字和说明从输入里删掉。命名空间上的 defer_loading 作用于命名空间里面的函数,不作用于命名空间对象本身。没推迟的工具仍然立刻可调用。

文档还写明,Responses API 里只有 gpt-5.4 及之后的模型支持 tool_search。更早的模型没有这条推迟路径,目录仍要整份挂在这次请求上。支持到哪一档,以工具搜索页的当前说明为准,不要把某一款型号写成永久名单。推迟省下来的,是还没加载的参数结构和还没展开的函数细节。它不把「这个工具在目录里」变成免费。

3. 回传是下一次的 input

函数调用把流程写成五步。先带上工具请求模型,再收到工具调用,再在应用侧执行,然后发起第二次请求把工具输出送回去,最后才拿到文本,或者更多的工具调用。文档用天气例子把第二次要送回的东西列全:工具定义、原始提示、模型的工具调用,以及工具调用的输出。输出可以是一段 JSON,也可以是图片或文件内容。

格式化结果那一节写明,放进 function_call_output 的结果通常是字符串,格式由你决定,JSON、错误码或纯文本都可以。图片和文件可以改成对象数组。函数没有返回值时,文档让你回一句表示成功或失败的字符串,例如 success,而不是什么都不放。附到 input 之后,再把它们送回模型。

那句很短的 function_call 是模型这一笔的输出条目,里面是名字和参数。你贴回去的结果是下一次请求 input 的一部分。结果是一页检索、一张表,还是一句 success,长度差在你放进去的内容上,不在模型有没有「只说了一个函数名」。计数接口要的是你将要发送的那份载荷。工具和这段回传要一起计,才是模型将收到的数量。

4. 短调用、格式、缓存不要并成一列

接上 previous_response_id 之后,链上已有输入仍按输入计,那是另一篇。那一列对的是对话状态里已经存在的输入,不是函数目录占了多少。两件事可以出现在同一次调用里:链在计已有输入,tools 在计这次挂上的定义。不要用「已经接上了」去解释目录为什么还在输入里。

提示缓存是改价,不是删除。工具搜索把新工具放在窗口末尾,写的是为了保住缓存。命中只改匹配前缀的单价。写入可以比普通输入更贵,那是缓存写入那篇。定义还在上下文里,缓存没有把它免掉。推理 token 记在输出上,屏幕上的短答案可以比输出用量短,对法在推理那篇。

计数页还单独写了一类格式 token。部分模型,文档点名包括 GPT-5 系列,会生成用来分隔响应通道、工具调用和其他消息结构的 token。它们不出现在消息正文或 logprobs 里,也不一定在 usage 里单独列项。因此即使报告的 reasoning_tokens 是 0,输出或完成 token 仍可以多于可见正文。那是输出列的结构开销,不是把函数定义从输入列挪走,也不等于推理还在计。文件搜索那种按天走的存储,是另一口时钟,和「这一轮的定义与回传」不是同一个开关。

5. 充值缩不短这份目录

对账按这一次请求的四列走。第一列是 tools 里实际可调用的定义,包括没有被点到的那些,它们按输入计。第二列是推迟之后还留在开头的名字和说明。参数结构是加载之后才进来的,不要把 defer_loading 读成整份工具免费。第三列是 function_call_output 里你放进去的字符串、图片或文件,它属于下一次的 input。第四列在输出侧:可见的函数调用,加上可能多出来的格式 token,和推理明细分开看。四列都落在 usage 上,不能收成「它只是想调一个函数,所以应该便宜」。

给 OpenAI 账户充值,发生在这些列扣款之前。链上把稳定币换成能充进账户的资产,不会把函数定义从系统消息里拿掉,不会让推迟加载变成退款,也不会把你贴回去的结果缩短。需要这一步时,可以用无需注册、非托管的 AllSwap 完成链上兑换。兑换成功只说明资产换了手。目录还挂在这次请求上,输入列就还会按定义和你送回去的那段回传计。