Speculative Programmatic Tool Calling
Speculative Programmatic Tool Calling
发表时间: 2026-08 · Blog post by Alex Zhang (alphaxiv.org)
原文: https://www.alphaxiv.org/abs/2608.spec-ptc
作者:Alex Zhang(指导老师:Omar Khattab,计算资源支持:Laude)
速读
一句话结论
提出了一种名为 sPTC(Speculative Programmatic Tool Calling)的推测式编程式工具调用方法,通过在主模型流式生成代码的过程中提前解析并异步执行子工具调用,在 OOLONG 数据集上将基于代码的智能体运行速度提升了 1 到 1.2 倍。
要解决什么问题
在现代智能体架构(如 RLM 或类似 CodeAct 的代码模式)中,系统的主要动作空间是在 REPL(交互式解释器)中编写和执行代码,而所有的外部工具都被封装为代码中的函数。原有的标准做法是严格串行的:系统必须等待大语言模型完成整段代码的流式生成,然后才能将代码交由 REPL 执行。这种机制存在两个严重的延迟卡点。首先,主上下文的逐词生成过程本身非常缓慢,尤其是对于需要长时间“思考”的模型,这会完全阻塞中间工具的调用。其次,代码中调用的工具(如子智能体、子大语言模型调用或搜索 API)通常是高延迟的。当生成的代码包含多个独立的工具调用时,如果模型没有显式地编写异步代码,标准的 REPL 就会同步且阻塞地逐个执行它们。这导致推理引擎的算力利用率极低,整体的墙钟时间被“串行生成时间”与“串行执行时间”的简单相加所严重拖累。
怎么做的
受 CPU 推测执行和大语言模型推测解码的启发,sPTC 的核心思路是将工具的执行时间与主上下文的生成时间重叠,充当一个极其朴素的即时编译器。关键设计由真实 REPL 和“影子 REPL”(Shadow REPL)两个环境构成。影子 REPL 是主代码执行环境的深度拷贝分支,用于在后台实时解析和试运行不完整的代码。在前端契约中,目标工具(如子模型查询 llm_query)会被注入钩子函数。当大语言模型还在流式输出 token 时,影子 REPL 会不断解析当前代码,一旦发现可推测的工具调用,就会异步启动该工具,并将返回结果作为期约(Promise)存入全局缓存中。其核心逻辑可由以下伪代码定义:
open 等读取动态文件或修改状态的函数被标记为不安全,任何依赖不安全函数或处于未决条件分支中的工具调用都会被阻断推测,留给真实 REPL 串行处理。对于非确定性的多次相同调用(如多数投票),系统会追踪唯一实例以防止单个推测结果被错误地路由给所有副本。
效果如何
实验在 OOLONG(132k 数据量)和 OOLONG-Pairs(32k 数据量)数据集上搭建,硬件使用单节点 8xH100 80B 运行 vLLM 服务,测试模型为 Qwen3-30B-A3B-Instruct-0527。对比基线是 Base RLM(基础递归语言模型),代表等待代码完全生成后再串行执行工具的传统路线。为了控制轨迹方差,实验分别在 temperature 为 0.0 和 0.7 的设置下进行,并测试了同一服务引擎下 4 个和 8 个并发任务的场景(每组实验运行 5 次)。量化结果显示,在所有设置下,sPTC 相比基线普遍实现了 1 到 1.2 倍的端到端提速。这种增益纯粹来源于将计算与主上下文生成重叠,以及将缓慢的 REPL 调用并行化。作者也承认了该方法的局限与代价:在内存上虽然深度拷贝 REPL 状态开销极小,但在最坏场景下,过于激进的推测或并发会导致大量额外的推测请求堵塞工具服务引擎;此外,当前实现高度依赖特定的语言和架构组合(如 Python 配合 RLM),面对更复杂的控制流,未来还需要开发更完善的伪编译器来进行优化。
主要贡献
当前在以代码执行作为动作空间(Action Space)的代理系统中,LLM 工具(如子代理或搜索 API)通常具有高延迟,是系统运行的瓶颈。此外,主上下文(Main Context)的实际生成也往往是一个显著的延迟瓶颈,它会阻塞中间调用的发生。
为解决这一问题,本文提出了一种名为推测性编程式工具调用(Speculative Programmatic Tool Calling, sPTC)的框架设计技巧。该方法受 CPU 中的推测执行(Speculative Execution)和 LLM 中的推测解码(Speculative Decoding)启发。其核心创新点在于:在系统仍在生成 token 的流式输出阶段,直接从部分生成的 REPL(交互式解释器)调用中推测并预先启动工具调用,而不是等待整个代码生成完毕。当完全生成的 REPL 实际执行到这些工具调用时,它们可以直接返回推测调用的缓存输出。这不仅能在 token 流式生成期间重叠已生成的工具调用,还能作为一种简单的即时编译器(JIT Compiler),将代码中未写为异步但实际上独立的阻塞工具调用并行化。
背景知识与设计原则
基于代码的工具调用理念:作者基于此前在递归语言模型(Recursive Language Models, RLMs)【索引1】等工作中的经验,确立了两个核心信念:(1) REPL 中的代码是系统唯一需要的“工具”;(2) 所有其他工具都应作为该代码工具中的函数。当代码成为系统的主要动作空间时,必须考虑将这些专门的工具调用与正在生成和执行的代码进行重叠。
推测调用的适用场景与时间节省来源:sPTC 主要在目标工具是子 LLM 或子代理调用(如 RLM 中)时最为有用。该方法带来的时间节省主要体现在两个明显的方面:
* 在 token 流式生成期间重叠已生成的工具调用:大多数框架设计会等待整个模型生成完毕后再执行工具。这种设计可能是 JSON 风格工具调用的遗留产物(在 JSON 模式下这并非真正的瓶颈)。由于每轮主上下文的生成通常很慢,这代表了很大一部分可以削减的时间,特别是对于需要长时间“思考”的模型。
* 作为 REPL 调用的即时(JIT)编译器:即使未启用流式生成,一个明显的优化是许多 REPL 程序包含实际上不需要阻塞的阻塞式工具调用。例如,代码中未写为异步的两个独立子代理调用,仍然可以并行运行。sPTC 充当了一个非常简单的 JIT 编译器来防止这种串行阻塞,并且未来可能在跨语言和 REPL 设计上进一步改进。
算力与内存瓶颈的转移:在本地运行少量聊天实例的 LLM 时,推理引擎通常在解码主上下文时受到高度的内存限制(Memory-bound),而推测可以帮助增加算术强度。对于高吞吐量的服务系统(例如使用前沿实验室或模型路由 API),批处理请求被抽象到各种可能不相交的服务引擎中,因此收益纯粹来自于将计算与主上下文重叠,或者将执行时间与缓慢的 REPL 调用重叠。
方法细节
设计推测性 PTC 方法
高层设计与前端契约:高层设计旨在让 REPL 中导入的工具调用拥有一个钩子(hook),使其能够被提前调用,并在需要时替换为缓存的输出。我们需要一个库和前端契约来定义:(1) 哪些工具应该被推测,哪些不应该(例如,子 LLM 调用可以推测,但子 RLM 调用成本太高);(2) 一种针对输入依赖于先前在内存中计算的变量(而不仅仅是字面量)的工具调用进行推测的机制。
钩子函数与 Promise 机制:上述契约体现为在希望推测的函数(例如 RLM 中的子调用 llm_query())周围设置的钩子。通过这种方式,可以在解析它时异步运行,将其保存到工具输出的全局存储中,并在 REPL 中实际调用它时将其视为从该存储中提取结果的 Promise。同时,系统还需要区分相同工具调用的多次调用,以应对它们是非确定性的情况(如子 LLM 调用)。
@spec.tool(speculatable=True, pure=True)
def tool(...) -> OutputType:
...
# 推测版本 (Speculated version)
def tool_spec(...):
promise = launch(tool(...))
register_speculation(promise)
# 实际 REPL 中运行的钩子版本 (Hooked version run in real REPL)
def tool_real(...):
if exists(promise, ID(...)):
return promise
else:
return tool(...)
影子命名空间与工作流:上述契约允许定义一个“影子(shadowed)”命名空间。在解析 LLM 输出时,该命名空间会调用这些修改后的工具,并将它们作为 Future 保存在存储中。随后真实工具可以调用这些 Future,其核心逻辑如下:
real_ns = {**locals, real_tools}
shadow_ns = replace_tools(real_ns)
# 在 LLM 流式输出时进行推测
while not LLM.done:
code += LLM.next_tokens()
parse_and_peek(code, shadow_ns) # 在不执行代码的情况下排队
parse_and_speculate(code, shadow_ns) # 重新运行影子 REPL
# 真正的工具现在路由到承诺的 (promised) 工具
exec(code, real_ns)
应对复杂逻辑的影子 REPL 执行:最简单的推测情况是,可以直接从 token 中解析工具调用并推断输入(即输入是字面量)。更棘手的情况发生在工具调用嵌入在条件或循环逻辑中,或者输入依赖于在 REPL 中较早计算的内存变量时。对于前者,系统可能无法预先知道条件是否满足,或者循环运行多长时间。对于后者,系统不确定计算变量输入是否是纯粹的(pure)并且是否修改了外部状态(这意味无法预先计算)。为了保持简单,作者选择维护主要代码 REPL 的深拷贝分支(deepcopy fork),称之为影子 REPL(shadow REPL),它会动态执行部分 REPL。
外部函数的安全性阻断:为了防止影子 REPL 产生不必要的副作用,大多数外部库和函数(如 open)被标记为“不安全(unsafe)”。任何依赖这些不安全函数作为输入的推测工具都不会被推测执行。
真实 REPL 执行器的隔离:作者有意选择不使用部分“推测器(speculator)”执行器作为真实的 REPL 执行器。因为模型生成的整个 REPL 可能会产生容易出错的代码或不完整的工具调用。在这些情况下,推测器不应实际修改真实的 REPL 状态,整个 REPL 单元必须被视为一个完整的计算单元进行处理。
提前推测与运行的条件判断
工具调用的唯一索引与依赖追踪:关于究竟能推测什么、推测的激进程度以及运行部分代码的整体安全性,主要考量在于:(1) 是否有足够的信息来提前运行工具调用;(2) 能否确定该工具调用是否会实际执行(特别是在条件语句周围)。在 PTC 中,可以通过工具调用的输入(以及发生次数,如果是非确定性的)来唯一索引它。在流式生成期间,如果每个工具调用的输入在不执行代码的情况下是已知的(即所有输入都是字面量),就可以立即异步调用该工具。
非确定性调用的副本控制:更常见的情况是输入依赖于其他变量,其中一些可以安全计算,而另一些则不能。对于相同的工具调用(例如对几个子代理进行多数投票),系统不希望单个推测的工具调用路由到每个副本,因此需要跟踪相同工具调用的唯一实例,除非已知该调用是确定性的。以下是当前推测机制的四种具体情况:
情况 1:字面量(Literals):字符串、整数或其他字面量可以立即被解析并转换为工具调用,甚至不需要影子执行每一行代码。
title = llm_query("Give a title for: The Odyssey") # 可解析
blurb = llm_query("One-line blurb for: The Odyssey") # 可解析
print(title, blurb)
情况 2:输入依赖(Input dependencies):当涉及输入依赖时,只要所有输入都是安全的(即纯函数,无副作用),它们就可以被推测。此外,依赖于其他被推测项的工具将等待前置依赖项计算完成,然后即使 LLM 仍在流式生成,也会被立即执行。
a = llm_query("Triage: " + doc) # 解析后执行
c = llm_query("Summarize: " + str(a)) # 解析,等待 a,然后执行
print(c)
if len(doc) > 10_000: # 如果安全,将评估并推测
extra = llm_query("Also outline it: " + doc)
情况 3:可窥探与不可窥探的依赖(Peekable and non-peekable dependencies):在流式生成过程的任何步骤中,系统都拥有一个影子 REPL 的工作命名空间。当一个完整的工具被解析时,即使依赖项是内存中的变量而不是字面量,在某些情况下也可以推测输入。
def gist(t):
return llm_query("One-line gist: " + t) # 不可窥探 (non-peekable)
parts = [gist(c) for c in chunks]
side = llm_query("Give me a random title for:", chunks[0]) # 可窥探 (peekable)
print(side, parts)
情况 4:被阻止的推测调用(Blocked speculation calls):在尝试推测时,系统维护了一个允许列表(allowlist),包含可用于计算输入依赖项的关键字和函数调用。用户也可以指定新的非纯函数工具。任何具有被阻止依赖项的可推测工具都不会被推测执行。
a = llm_query("Triage: " + doc) # 被推测
notes = open("/tmp/scratch.txt").read() # 被阻止
b = llm_query("Annotate with notes: " + notes) # 被阻止
c = llm_query("Summarize: " + a) # 在 a 之后被推测
实验环境
- 数据集:使用 OOLONG (trec-coarse, 132k) 【索引2】和 OOLONG-Pairs (32k) 数据集。
- 模型架构:Qwen3-30B-A3B-Instruct-0527。
- 硬件配置:1 个节点,配备 8 张 H100 80GB GPU。
- 软件配置:运行 vLLM 服务器。设置了两组温度参数(temperature=0.7 和 temperature=0.0)以控制 RLM 运行的方差。
实验结果
- 实验内容:在 4 个并发任务和 8 个并发任务的设置下,对比 Speculative PTC 与 Base RLM 在两个数据集上的墙钟时间(Wall-clock time)、每任务子调用数(Sub-calls per task)和每任务轮次数(Turns per task)。每个实验重复运行 5 次以取平均值。
- 实验结果:RLM 的加速比通常在 1 到 1.2 倍的数量级。
- 分析结论:准确估计加速比非常困难,因为它高度依赖于工具的延迟、生成的 token 数量、服务引擎的负载以及 harness 的实际选择。尽管在特定的 LM 程序套件中能观察到更显著的理论或实际运行时间加速,但因过于具体未在文中详述。总体而言,该方法能稳定带来推理时间的节省。
补充细节
PTC 的推测开销分析
运行时与内存开销:推测性 PTC 的额外开销在一定程度上取决于具体实现和推理引擎的设置。在当前实现中,运行时的开销几乎可以忽略不计,因为推测器只需廉价地解析并检查是否可以对部分生成的 REPL 进行推测。在内存方面,系统创建了 harness 代码 REPL 的深拷贝,这相对于实际 REPL 变量分配的内存来说通常是廉价的,因为代码环境中通常很少有大型可变对象。
最坏情况控制:最坏的情况发生在工具的服务引擎被许多并发且可能是额外推测的请求堵塞时。这种风险可以通过调整推测的激进程度以及控制这些工具的排队方式来进行约束。
相关工作对比
解码层的推测算法局限:在 LLM 解码(即流式输出)级别的推测算法在较旧的工具调用设计中被少量探索过。但对于标准工具调用,这些技术可能不太有用,或者其带来的开销不值得换取边际延迟的改善。
现有推测调用的方案对比:Conveyor (Xu 等人, 2024)【索引3】允许用户定义部分执行机会(如一行代码),在解码期间进行解析。在 Speculative Interaction Agents (Hooper 等人, 2026)【索引4】中,他们将 Conveyor 提出的系统正式定义为推测性工具调用,主要通过将现代模型较长的思考链与调用的工具重叠来减少首 token 时间(TTFT)。AsyncFC (Feng 等人, 2026)【索引5】则认为工具调用通常以阻塞方式实现,并为围绕函数调用的基于 future 的异步包装器定义了契约;然而,这种方法存在无法与原始 harness 轨迹 1:1 对应的风险。
sPTC 在复杂程序中的优势:在 sPTC 的情况下,在更复杂的程序中向工具添加推测之所以显著更有用,是因为程序本身的运行时间是未知的。对于标准工具调用,当 LLM 完成生成足够 token 以完全指定工具调用时,通常已经没有太多剩余 token 需要生成了。而在 PTC 情况下,代码执行使得实际的工具调用模式明显更加复杂,从而为重叠留下了更多空间。
结论
推测性编程式工具调用(sPTC)是从编程式工具调用中自然产生的一种技巧,相比传统工具调用,它具有更大的执行重叠潜力。除了与 harness 中根 LLM 的流式 token 生成重叠之外,该方法长远的真正价值将来自于更聪明的 JIT 编译技巧,以在 PTC 设置中将工具调用与实际的 REPL 执行本身重叠(随着 harness 生成更复杂的程序,REPL 执行可能会变得更加昂贵)。
未来有许多方法可以实现更快、更激进且开销更小的推测性 PTC,理想情况下希望它是语言和 harness 无关的(当前主要支持 Python, bash, Bun 结合 Coding harness, RLM, game agent)。目前的实现对于本地运行的模型和代理 harness 已经非常有用,并且可以很容易地作为插件添加到自定义的代码代理中。
参考文献汇总
-
【索引1】 Recursive Language Models (RLMs) + 2025 + arXiv + https://arxiv.org/abs/2512.24601
- 引用段落:背景知识与设计原则。
- 原文描述:作者指出基于其在 Recursive Language Models (RLMs) 上的工作,确信 REPL 中的代码是系统唯一需要的工具。
-
【索引2】 OOLONG: Formulating Language Interactions as Code + 2025 + arXiv + https://arxiv.org/abs/2511.02817
- 引用段落:实验环境。
- 原文描述:实验使用了 OOLONG (trec-coarse, 132k) 数据集。
-
【索引3】 Conveyor + 2024 + arXiv + https://arxiv.org/abs/2406.00059
- 引用段落:补充细节 - 相关工作对比。
- 原文描述:Conveyor (Xu 等人, 2024) 允许用户定义部分执行机会(例如一行代码),这些机会在解码期间被解析。
-
【索引4】 Speculative Interaction Agents + 2026 + arXiv + https://arxiv.org/abs/2605.13360
- 引用段落:补充细节 - 相关工作对比。
- 原文描述:在 Speculative Interaction Agents (Hooper 等人, 2026) 中,他们将 Conveyor 提出的系统正式定义为推测性工具调用,主要通过重叠长思考链与工具调用来减少 TTFT。
-
【索引5】 AsyncFC + 2026 + arXiv + https://arxiv.org/abs/2605.15077
- 引用段落:补充细节 - 相关工作对比。
- 原文描述:AsyncFC (Feng 等人, 2026) 认为工具调用通常以阻塞方式实现,并为围绕函数调用的基于 future 的异步包装器定义了契约。
💬 评论讨论
欢迎在这里分享您的想法和见解!