The Bitter Lesson of Tool Calling
The Bitter Lesson of Tool Calling
发表时间: 2026-08 · arXiv:2608.06370 (PricewaterhouseCoopers)
原文: https://arxiv.org/abs/2608.06370
Ishan Patel, Sahil Sen, Elias Lumer, and Vamse Kumar Subbiah
Commercial Technology and Innovation Office PricewaterhouseCoopers, U.S.A
速读
一句话结论 本文证明了让大语言模型通过编写 Python 脚本来调用工具(Programmatic Tool Calling),能够突破原生 JSON 工具调用在长链路串行和大规模并行场景下的结构性瓶颈,并在最新一代模型上实现了最高 10.6% 的准确率提升。
要解决什么问题 当前大语言模型调用外部工具的主流范式是原生 JSON 工具调用(JSON Tool Calling),即模型在每次调用时输出结构化的 JSON 对象。这种做法在复杂任务中存在三个机制卡点。首先是长链路串行调用延迟高:如果工具 B 的输入依赖工具 A 的输出,JSON 范式必须消耗两次完整的推理轮次(生成调用 A、接收结果、生成调用 B)。其次是大规模并行调用的结构性截断:当需要同时发起大量独立调用(Fan-out)时,模型需要在单次响应中序列化庞大的 JSON 数组,一旦并发数超过模型的特定阈值,模型就会开始完全丢弃调用。最后是上下文腐化:当上下文中混入大量无关的干扰函数声明时,JSON 范式的准确率会受损。
怎么做的 核心思路是将工具调用从“输出 JSON 对象”转变为“编写并执行 Python 脚本”,即 Programmatic Tool Calling(PTC)范式。这种方法之所以能绕开上述卡点,是因为它将工具的串行组装和并行分发交给了图灵完备的代码逻辑,而不是依赖语言模型的 JSON 序列化能力。关键设计由以下几个部件构成:首先是类型化存根(Typed Python stubs),系统提示词中会嵌入一个 Python 模块的源码,里面的存根函数与基准测试中的工具定义一一对应,这些函数被调用时会直接捕获参数并将其打印到标准输出。其次是单轮次脚本生成,模型不再输出 JSON,而是编写一段导入上述存根模块的 Python 脚本。对于串行任务,模型在脚本中直接用变量传递中间结果;对于并行任务,模型使用 asyncio.gather 或循环语句发起调用。最后是子进程执行与解析,智能体循环在一个原生 Shell 子进程中一次性执行这段脚本,通过捕获标准输出(stdout)来提取函数名和参数,随后立即终止循环。在串行场景下,PTC 将原本需要多轮推理的调用压缩为单次推理,其推理轮次复杂度从 JSON 范式的 $\mathcal{O}(N)$ 降低为 PTC 的 $\mathcal{O}(1)$;在并行场景下,代码的循环结构天然打破了 JSON 数组的长度限制。
效果如何 实验在 BFCL v4 基准测试的 309 个代表性样本上进行,覆盖 8 个任务类别。测试了发布于 2024 年 11 月至 2026 年 7 月间的 14 个模型(涵盖 Claude 4.5 至 5 及 GPT-4o 至 GPT-5.6 系列),温度均设为 0。对比基线主要是原生 JSON 工具调用(代表当前工业界标准部署路线),在上下文腐化实验中额外引入了基于文件系统发现的方法作为参考。量化结果显示,在 14 个模型中,有 11 个在 PTC 范式下的准确率持平或超越了 JSON 基线。其中 GPT-5.6 家族实现了 10.6% 的绝对提升。在长链路串行任务中,PTC 的优势随链路长度增加而扩大,在长度 $\ge 12$ 时相较 JSON 基线取得了 18.8% 的绝对领先,并将 13 个模型的端到端延迟降低了约一半。在大规模并行任务中,JSON 基线在并发数达到 70 至 72 时开始完全丢弃调用,而 PTC 在并发数达到 100 时依然保持 100% 的枚举准确率。在 128 个干扰声明的上下文腐化测试中,PTC 的准确率不降反升(提升 5.5%),而 JSON 基线下降 2.3%,文件系统方法暴跌 32%。该方法也有局限:首先是输入 Token 开销,由于系统提示词较长,在并发数小于 26 时 PTC 的成本更高(例如串行任务中 PTC 的输入 Token 是 JSON 的 1.5 倍)。其次,部分旧版模型(如 GPT-4o、GPT-4.1、GPT-5.4-mini)在生成多行脚本时会错误地输出字面量 \n 而非真实换行符,导致严重的语法错误。最后,实验使用的是回显存根(仅返回参数本身),因此测量的纯粹是参数序列化准确率,而非真实 API 调用的端到端成功率。
主要贡献
大型语言模型(LLMs)目前主要作为工具调用代理,通过要求模型在每次函数调用时发出结构化JSON对象的API来调用外部服务。对于已经具备编写可执行代码能力的模型来说,这种JSON格式仅仅是一种设计选择,而非必要条件。JSON函数调用在需要精确的参数序列化、多步链式调用以及跨异构API的并行扇出(fan-out)时面临着巨大的挑战。
为了系统性地评估将工具作为代码执行的有效性,本文在真实的复杂任务条件下,对比了当前和之前多代模型在既定基准上的表现。本文在BFCL v4基准的代表性子集(包含309个条目,跨越8个任务类别)上,对14个语言模型(发布于2024年11月至2026年7月之间)的编程工具调用(Programmatic Tool Calling, PTC)与原生JSON工具调用进行了实证比较。在PTC范式中,工具被暴露为类型化的Python存根(stubs),模型通过编写代码来调用它们,执行和结果处理在单个代理轮次中完成。
本文的核心贡献如下:
* 按模型代数划分的PTC可行性:PTC的可行性随模型的代数(而非模型家族)产生分化。所有5个Anthropic模型和3个最新的GPT代模型在BFCL v4上均达到或超过了JSON工具调用的准确率,而3个较旧的GPT模型则未能做到。GPT-5.6家族相较于JSON基线实现了$10.6\%$的提升。
* 链式任务中的准确率优势:在顺序链式任务中,PTC的准确率优势随链条长度的增加而扩大。在链长$\geq 12$时,PTC相较于JSON工具调用达到了$18.8\%$的绝对差距。这种效应在短链中不存在,主要是由于JSON工具调用在每个链接中都会产生额外的推理轮次。
* 突破原生范式的结构性限制:JSON工具调用在超过特定模型的并行扇出阈值(例如Claude Sonnet 5的$\Delta N = 70-72$)时,会完全丢失工具调用。而PTC在$N = 100$时仍能保持$100\%$的枚举准确率,暴露出原生JSON范式存在硬性的结构限制。
* 对抗上下文负载的鲁棒性:在上下文泛滥(context flooding)条件下,PTC保持稳定(甚至平均绝对提升$5.5\%$),而JSON工具调用平均下降$2.3\%$,文件系统发现方法则大幅下降$32\%$,证明了PTC对对抗性上下文负载具有极强的鲁棒性。
背景知识与设计原则
代码动作代理的理论与实践基础:先前的研究为用可执行代码替代JSON工具调用奠定了理论基础。CodeAct(Wang 等人,2024)表明,在多工具任务中,代码动作可使任务成功率提高多达$20\%$,同时交互轮次减少$30\%$,这些增益主要集中在并行和组合场景中。Recursive Agent Harnesses(Lumer 等人,2026)将相同的代码执行原语扩展到并行子代理生成,表明在子进程中执行代码可以绕过限制原生JSON在高扇出时每轮工具调用上限的问题。工业界框架(Hugging Face,2024;Anthropic,2024;Karten 等人,2026)将这一发现扩展到生产环境中,将代码组合作为默认接口。然而,像Chronos(Sen 等人,2026b)这样的生产记忆系统仍将迭代JSON工具调用循环作为其核心代理机制,证实了JSON工具调用在已部署的代理系统中仍占据主导地位。基础设施提供商也在采用相同的模式,例如Cloudflare的Agents平台将代码执行作为其原生工具使用接口(Cloudflare,2026)。最近的一项调查(Ning 等人,2026)认为,代码是代理推理的理想基质,因为它是可执行、可检查和有状态的。Deterministic Horizon(Guo 等人,2026)提供了理论依据,指出在思维链推理无法产生可验证答案的地方,正是需要工具委托的地方。
工具调用基准测试的局限性:多个基准测试衡量了LLM工具调用的准确性,包括API-Bank(Li 等人,2023)、T-Eval(Chen 等人,2024)、API-BLEND(Basu 等人,2024)、UltraTool(Huang 等人,2024)、CONFETTI(Alkhouli 等人,2025)和ToolHop(Ye 等人,2025)。它们都评估了模型使用正确参数调用正确函数的准确性,但没有一个比较过调用范式。BFCL v4涵盖了八个任务类别并带有确定性评分器。最近的一项审计(Vaghasiya 等人,2026)发现其LLM裁判模式存在$20\%$的评估者-人类不对齐问题。关于代理工具调用训练效率(Liu 等人,2026)和上下文膨胀(Du 等人,2026)的研究指出,格式和提示工程是工具调用基准测试中方差的主要来源。
方法细节
任务定义与评估机制:本文研究编程工具调用(PTC)是否能在不牺牲准确率的情况下替代JSON工具调用。每个基准测试条目指定一个自然语言用户查询$q$,一组带有类型签名的可用函数$\mathcal{F} = \{f_1, \dots, f_k\}$,以及一组真实的函数调用集合$\mathcal{C}^* = \{(f_i, \mathbf{a}_i)\}$,其中$\mathbf{a}_i$是调用$i$的参数映射。当且仅当模型的输出产生一个在基准的归一化字符串比较(去除标点符号并将所有值转为小写)下与$\mathcal{C}^*$匹配的调用集合时,模型才在该条目上被判定为正确。该定义在所有三种范式(JSON、PTC、文件系统)中完全一致,差异仅在于模型的提示方式和输出解析方式。
JSON工具调用范式(基线):模型接收一个系统提示,其中包含格式化为JSON工具定义的函数模式,以及调用工具端点的API调用。模型在每次函数调用时输出一个结构化的JSON对象。这是工具增强型LLM的标准部署模式,作为本研究的参考条件。
编程工具调用(PTC)范式:模型接收一个系统提示,该提示嵌入了一个类型化Python模块的源代码,其函数与基准测试的函数模式一一对应。每个存根(stub)函数捕获其参数并将其作为结构化字典返回。此过程不联系任何外部服务。模型编写一个导入此模块并调用适当函数的Python脚本。代理循环在原生的shell子进程中执行该脚本。子进程捕获stdout,评分器从打印的输出中提取函数名和参数值。在子进程返回后,不会发生额外的推理轮次。一个停止中间件(stop middleware)会拦截下一次模型调用并终止代理循环。这种设计确保了PTC和JSON工具调用在每个条目上消耗相同数量的LLM调用,从而使准确率比较变得直接公平。
在一个要求模型检索半径为7的圆的周长和边长为5的正方形的面积的任务中,JSON范式会发出两个顺序的JSON对象。而在PTC范式中,模型会编写如下脚本:
execute(command="python3 -c '
import json
from stubs import ( circumference, area_square)
print(json.dumps(circumference(radius=7)))
print(json.dumps(area_square(side = 5 )))
'")
shell子进程捕获这两行stdout,评分器将每个打印的调用与真实值进行匹配。PTC在一个子进程中评估的单个Python表达式中解析了这两个调用,而JSON工具调用则需要两个独立的模型输出。
基准测试与评分处理:评估使用了BFCL v4(Vaghasiya 等人,2026)中按比例抽样的309个代表性条目,覆盖八个类别,并应用了每个类别的最小值以确保较小类别中有足够的条目。所有三种范式都使用确定性评分器。准确率被报告为每个必需的函数调用都存在且参数化正确的条目比例。当shell子进程引发语法或运行时错误时,该条目得分为零。没有任何条目被跳过或从报告的总数中排除。
消融实验设计:研究设计了三个消融实验,针对范式选择产生影响的任务结构。
* 链式调用(Chaining):针对顺序的多跳函数调用,其中$f_1$的输出必须被计算并作为参数传递给$f_2$。在JSON工具调用中,这需要两个模型轮次:模型调用$f_1$,接收返回值,然后调用$f_2$。在PTC中,这两个调用出现在同一个脚本中。模型利用参数化知识计算中间值并直接传递它。该子集包含52个条目,链长为2到20次调用,权重偏向于较长的链($n_{chain} \geq 6$: 17个条目; $n_{chain} \geq 13$: 10个条目)。
* 并行性(Parallelism):针对独立的扇出函数调用,其中必须在单步中发出$N$个调用。JSON工具调用将它们作为并行的工具调用对象发出。PTC则编写一个asyncio.gather块或顺序循环。该子集使用32个枚举类型的条目,扇出数量从7到48不等。为了定位Anthropic前沿模型在JSON范式下开始丢弃调用的扇出阈值,研究在$N \in \{60, 70, 72, 75, 100\}$处扩展了探测条目。实验分别报告枚举准确率(模型是否发出了所有$N$个所需调用)和聚合准确率(模型是否产生了正确的聚合答案),因为PTC可以从参数化知识中回答聚合问题而无需执行每次调用。
* 上下文腐败(Context rot):测试范式对无关上下文的鲁棒性。分为过滤条件(仅包含查询使用的函数)和泛滥条件(Flood,注入不相关的诱饵函数模式,使总上下文大小增加到128个模式)。该子集每个条件包含31个条目。
方法细节中的引用汇总
- 引用1:Wang 等人,2024([1] CodeAct: Executable code actions elicit better LLM agents + 2024 + ICML)。在背景知识中被引用,原文描述其证明了代码动作在多工具任务上优于JSON替代方案,增益集中在并行和组合场景中。
- 引用2:Lumer 等人,2026([2] Recursive agent harnesses + 2026 + arXiv)。在背景知识中被引用,原文描述其将代码执行原语扩展到并行子代理生成,表明子进程执行代码绕过了JSON函数调用的每轮上限。
- 引用3:Sen 等人,2026b([3] Chronos: Temporal-aware conversational agents with structured event retrieval for long-term memory + 2026 + arXiv)。在背景知识中被引用,原文描述其作为生产记忆系统,证实JSON工具调用依然是部署系统的主导接口。
- 引用4:Ning 等人,2026([4] Code as agent harness + 2026 + arXiv)。在背景知识中被引用,原文描述其认为代码是代理推理的理想基质。
- 引用5:Guo 等人,2026([5] The deterministic horizon: When extended reasoning fails and tool delegation becomes necessary + 2026 + ICML)。在背景知识中被引用,原文描述其提供了理论基础,指出当思维链推理失败时工具委托是必要的。
- 引用6:Vaghasiya 等人,2026([6] Benchmarking the benchmarks: A validity audit of tool-calling evaluation + 2026 + arXiv)。在方法细节与背景中被多次引用,原文描述其审计发现BFCL v4的LLM裁判模式存在$20\%$的人类不对齐,促使本文使用确定性评分器。
- 引用7:Liu 等人,2026([7] On effectiveness and efficiency of agentic tool-calling and RL training + 2026 + ICML)与 Du 等人,2026([8] HyperTool: Beyond step-wise tool calls for tool-augmented agents + 2026 + arXiv)。在背景知识中被引用,指出格式和提示工程是工具调用基准测试中方差的主要来源。
实验环境
- 数据集:BFCL v4的一个具有代表性的309条目子集,跨越8个任务类别。此外,针对消融实验构建了特定子集:链式调用(52个条目,链长2-20),并行性(32个条目,扇出数7-48及额外探测点),上下文腐败(每个条件31个条目,包含128个模式的泛滥条件)。
- 模型配置:评估了跨越两个家族和20个月发布周期的14个模型,所有模型均在温度$0$下运行。模型包括:Anthropic系列的Claude Haiku 4.5, Claude Sonnet 4.5, Claude Sonnet 4.6, Claude Opus 4.8, Claude Sonnet 5;OpenAI系列的GPT-4o, GPT-4.1, GPT-5-nano, GPT-5, GPT-5.4-mini, GPT-5.4, GPT-5.6-Luna, GPT-5.6-Sol, GPT-5.6-Terra。
- 软件配置:Python子进程执行环境。工具调用通过类型化的Python存根模块实现并捕获标准输出。
实验结果
- BFCL v4 主评估:在14个模型中,有11个在PTC下的准确率达到或超过了JSON基线。GPT-5.6家族在PTC下获得了最大的增益,其中GPT-5.6-Sol和GPT-5.6-Terra相较于其自身的JSON基线均实现了$10.6\%$的绝对提升。所有5个Anthropic模型均达到或超过基线准确率。3个较旧的OpenAI模型(GPT-4o, GPT-4.1, GPT-5.4-mini)在PTC下低于基线$19.7\%$到$26.9\%$,原因是这些模型在多行脚本中生成了字面量的
\n转义字符而不是真实的换行符,导致子进程抛出语法错误。 - 链式调用消融实验:Claude Sonnet 5实现了最大的PTC增益(从$80.8\%$提升至$96.2\%$),其次是Claude Opus 4.8($80.8\%$提升至$94.2\%$)。6个模型保持了接近的平价(绝对差异在$5\%$以内)。GPT-4.1是唯一的异常值,其基线准确率$98.1\%$在PTC下崩溃至$40.4\%$,这完全是由上述的
\n编码失败引起的,因为中间计算需要多行脚本。 - 并行性消融实验:13个模型在PTC下达到或超过了基线准确率。GPT-5的PTC增益最大,从$71.9\%$提升至$96.9\%$。随着扇出数量增加到13以上,GPT-5的JSON基线准确率下降,而PTC在所有扇出级别保持近乎完美的枚举准确率。代币成本在$N \approx 26$时出现交叉。低于此阈值时,PTC因系统提示开销而更昂贵;高于此阈值时,JSON工具调用由于必须枚举所有$N$个工具调用对象而变得更加昂贵(例如在$N=48$时,JSON消耗5097个代币,而PTC仅消耗3535个)。
- 上下文腐败消融实验:JSON和PTC在泛滥条件下都保持稳定。从过滤条件到泛滥条件,JSON工具调用的平均准确率绝对变化为$-2.3\%$,而PTC为$+5.5\%$。PTC在泛滥条件下的提升主要是由于某些模型在过滤条件下难以应对严格的类型约束,但在泛滥条件更丰富的上下文中更容易识别正确的函数。作为参考,文件系统发现方法在泛滥条件下平均绝对下降了$32.0\%$。
补充细节
模型代数预测PTC可行性:实验结果中最清晰的模式是,PTC相对于JSON工具调用的准确性与模型的代数相关,而不是模型家族。所有五个Anthropic模型在所有三项消融研究中都达到或超过了基线,最新的三个GPT-5.6变体也是如此。在PTC下低于基线的模型(GPT-4o、GPT-4.1和GPT-5.4-mini)共享一个特定的故障:它们产生带有\n字符序列而不是真实换行符的Python代码。相同的系统提示和换行指令,较小且较早的GPT-5-nano能生成正确的多行代码,而较大且较晚的GPT-5.4-mini却失败了,这排除了提示配置作为原因,表明这是一个能力差距问题。
PTC在结构上处理扇出,而JSON工具调用则不然:在并行消融中,PTC使GPT-5达到了接近天花板的准确率,而JSON工具调用在扇出数大于13时准确率下降。在JSON中,模型必须在单一响应中发出并行的工具调用块,在高扇出时它开始遗漏调用。在PTC中,扇出被表达为Python中的循环或函数调用序列,这对数量没有结构性限制。对Claude Sonnet 5的探测显示,JSON枚举准确率在$N \leq 70$时为$100\%$,在$N = 72$时降至$75\%$,在$N = 100$时降至$0\%$。而PTC在$N = 72$和$N = 100$时均保持$100\%$的枚举准确率。
PTC降低了链式任务的延迟:对于14个模型中的13个,PTC完成链式条目的挂钟时间(wall-clock time)大约是基线的一半,每条目的延迟比率在基线的0.32到0.96之间。JSON工具调用需要两个推理轮次,而PTC在单个推理轮次后接一个子进程执行中解析了两个调用。GPT-5是个例外,其在PTC下的延迟是基线的$2.8 \times$,因为其扩展的推理输出膨胀了生成时间,抵消了轮次减少带来的好处。
结论
随着语言模型代理越来越依赖工具使用来超越其训练数据采取行动,工具接口的选择(结构化JSON调用与编程工具调用)对链式调用、并行性以及真实世界条件下的鲁棒性具有实际影响。本文的系统评估表明,PTC是原生JSON工具调用的一个可行且可靠的替代方案。PTC在14个模型中的11个达到或超过了原生JSON工具调用,GPT-5.6家族实现了$10.7\%$的提升。在并行扇出和上下文腐败条件下,PTC表现出更强的稳定性和结构优势。剩余的性能差距与模型的代数相关,而不是模型家族。
本文也指出了研究的局限性:BFCL v4使用回显返回存根(仅测量参数序列化准确性);消融实验样本量较小;基准测试的真实标签可能包含噪声;以及PTC在低扇出时具有固定的输入代币开销。
附录
附录A:BFCL v4各类别准确率:详细记录了各类别下的平均准确率表现,指出并行和并行多重类别中的准确率差距主要是由三个OpenAI模型的\n编码失败引起的。
附录B:编程工具调用执行轨迹详解
* 单次调用(simple_python):任务要求计算底为10、高为5的三角形面积。提供calculate_triangle_area存根。代理发出一个execute_python工具调用,包含导入存根、执行计算并使用print(__import__('json').dumps(result))打印结果的Python代码。子进程打印出捕获的字典,停止中间件终止循环,评分器提取并验证结果。
* 链式调用消融:任务要求计算半径为7的圆的周长,并将其用作正方形的边长以求周长。代理编写单个脚本,首先调用geometry_circumference(radius=7),然后在Python中利用数学公式circ = 2 * math.pi * 7计算中间值,接着将该值传递给geometry_square_perimeter(side=circ)。脚本打印两个调用的JSON结果。两个调用在一个子进程和单一LLM轮次中得到解析。
* 并行性消融:任务要求从7个给定国家中找出人口最多的3个。代理发出一个execute_python调用,利用asyncio.gather并发执行所有7个country_info_population查找。随后,脚本在Python内部对返回的人口数据进行排序,提取前3名并打印聚合答案。所有7个存根被并行调用,整个条目在单个LLM轮次中完成。
附录C:系统提示设计
* JSON工具调用系统提示:指示模型使用工具调用API调用提供的工具定义,并为每个所需的函数调用发出一个JSON工具调用对象,不附加任何散文。函数模式通过API请求的tools参数传递。
* 编程工具调用系统提示:嵌入类型化Python存根模块的源代码,指示模型恰好调用一次execute,并附带python3 -c '...'命令。该命令导入模块,使用正确参数调用函数,并将结果打印为JSON。提示明确规定在多行脚本中必须使用真实的换行符(而非\n转义序列)。停止中间件在execute工具返回后拦截第一次模型调用,终止循环。
* 存根模块设计:每个存根函数由基准的函数模式生成。它接受模式的类型化关键字参数,立即将它们作为结构化字典返回,并将结果打印到stdout。不联系任何外部服务。评分器解析stdout以恢复函数名和参数映射。
💬 评论讨论
欢迎在这里分享您的想法和见解!