@a1zhang: 介绍 Speculative Programmatic Tool Calling (sPTC)!一种用于推测工具调用的一般技术类。
摘要
介绍 Speculative Programmatic Tool Calling (sPTC),一种在代码生成期间推测工具调用的技术,以与令牌生成和执行时间重叠,提高 AI 框架中的效率。
查看缓存全文
缓存时间: 2026/08/24 17:56
介绍推测性编程工具调用(sPTC)!
一种通用技术,用于在代码执行环境中对代码生成过程中的工具调用进行推测,并提前将其加入队列,以便与令牌生成及 REPL 执行时间重叠。博客:https://alexzhang13.github.io/blog/2026/spec-ptc/
推测性编程工具调用
来源:https://alexzhang13.github.io/blog/2026/spec-ptc/ 图 1:推测性编程工具调用(sPTC)示例
模型轮次 - 流式传输
采用推测解码执行预处理子调用 ×6 判断执行基线答案 · 7/7 主张 不采用推测 · 相同程序答案 c1 c2 c3 c4 c5 c6 判断执行崩溃 - 2.4倍,无异步
时间 t
相关代码在此:https://github.com/alexzhang13/spec-ptc
从我之前的文章(https://alexzhang13.github.io/blog/2026/harness/)以及对递归语言模型(RLMs)的研究(https://arxiv.org/abs/2512.24601)来看,我坚信(1)REPL 中的代码是系统所需的唯一“工具”;(2)所有其他工具都应该是该代码工具中的函数。当代码成为系统的主要操作空间时,你就需要开始考虑将这些专用工具调用与正在生成和执行的代码进行重叠。
受 CPU 中推测执行(https://en.wikipedia.org/wiki/Speculative_execution)和大型语言模型(LLM)中推测解码(https://arxiv.org/abs/2211.17192)的启发,我在执行环境设计中提出一个相对简单且有用的技巧——推测性编程工具调用(sPTC)。该技巧在环境仍在生成令牌时,就从部分生成的 REPL 调用中推测并预先启动工具调用,而不是等待整个生成完成。特别是当目标工具是子 LLM 或子智能体调用时(例如在 RLM 中),此方法最为有用。如果最终生成的 REPL 确实调用了这些工具,它们将立即返回从推测调用中缓存的输出。
一个关键观察是,LLM 工具(如子智能体或搜索 API)通常具有高延迟,并且通常是依赖代码执行作为操作的环境中的瓶颈。此外,主上下文的实际生成本身往往也是一个显著的延迟瓶颈,阻碍了中间调用的发生。
在本文的剩余部分,我将主要讨论相对于 RLM 的推理时间节省,但请注意,这通常适用于生成代码的环境(即 CodeAct 类或“代码模式”环境)。有两个明显的领域可以节省时间:
- 在令牌流式传输期间重叠已生成的工具调用。 大多数环境设计会等待整个模型生成完成后再执行工具。这种设计可能是 JSON 风格工具调用的产物,在那种模式下这并非真正的瓶颈。由于主上下文每轮的生成通常很慢,这代表了可以削减的大量时间,尤其是对于思考时间很长的模型。
- 充当 REPL 调用的即时(JIT)编译器。 即使不启用流式传输,一个明显的优化是许多 REPL 程序包含非阻塞的阻塞工具调用——例如,代码中未写为异步的两个独立子智能体调用,仍然可以并行运行。sPTC充当一个非常初级的JIT 编译器来防止此类情况,并且很可能在语言和 REPL 设计方面进一步改进。
并排示例展示了:具有字符串字面量输入的子智能体、具有变量依赖的子智能体,以及对具有变量依赖的子智能体进行循环,如何被推测并与主上下文生成重叠。
对于本地运行的 LLM,每次运行一个或少数几个聊天实例时,您的推理引擎通常因解码主上下文而高度受限于内存,而推测可以帮助提高算术强度。对于高吞吐量的服务系统(例如,如果您使用前沿实验室或模型路由 API),批处理请求被抽象到各种可能不相连的服务引擎中,因此收益纯粹来自于将计算与主上下文重叠,或将执行时间与慢速的 REPL 调用重叠。
关于 RLM 的一些对比数据
我将首先突出一些基本的运行时数据,尽管您可能可以推导出运行时的收益。实际的实现细节在代码库中相当简单易懂,我将在博客的后半部分讨论。
您应该记住,很难准确估算加速比,因为它高度依赖于工具的延迟、生成的令牌数量、服务引擎的负载以及环境实际做出的选择。我们可以隔离某些组件(例如,固定一个程序)来突出不需要基准测试就能理解的明显案例,但最简单的例子就是复制一个已知的设置和环境,并对多次采样的运行时进行基准测试。
我们在原始论文中的 OOLONG(trec-coarse,132k)(https://arxiv.org/abs/2511.02817)和 OOLONG-Pairs(32k)(https://huggingface.co/datasets/mit-oasys/oolong-pairs)上运行,使用一个配备 8xH100 80GB 的节点运行 vLLM 服务器。我们使用 Qwen3-30B-A3B-Instruct-0527 测试了两种情况:一种使用标准温度 0.7,另一种使用温度 0.0 以控制这些 RLM 运行的方差(其速度高度依赖于它所采取的路径)。每个实验运行 5 次。我们报告平均子调用次数和轮次数,以便对每个模型的行为有相对的了解,并报告在服务引擎上并发运行 4 次和 8 次的结果。
推测性 PTC 与基础 RLM 在 OOLONG 和 OOLONG-Pairs 上的对比 RLM 的加速通常在 1-1.2 倍左右。我们还测试了一个更“确定性”的 LM 程序套件,其中我们观察了代码库中假设或现实的运行时加速,但这里省略了,因为它们过于特定。
设计推测性 PTC 方法
代码(https://github.com/alexzhang13/spec-ptc/)非常简短且可读,但我将在下面谈谈设计理念。
高层设计是在我们 REPL 中导入的工具调用上添加一个钩子,以便在需要时可以提前调用并替换为缓存输出。我们需要一个库和前端契约,允许我们定义(1)哪些工具应该被推测,哪些不应该(例如,我们可能希望子 LLM 调用被推测,但子 RLM 调用成本太高);以及(2)一种机制来推测其输入依赖于先前在内存中计算的变量(而不仅仅是字面量)的工具调用。
从高层次上看,我们想要的契约类型是在我们想要推测的函数周围设置一个钩子(例如 RLM 中的子调用 llm_query()),这样我们就可以在解析它时异步运行它,将其保存到某个全局工具输出存储中,并在 REPL 中实际调用时像 Promise 一样从中提取。我们还需要区分相同工具调用的多次调用,尤其是在它们是非确定性的情况下(如子调用)。
@spec.tool(speculatable=True, pure=True)
def tool(...) -> OutputType:
...
# 推测版本
def tool_spec(...):
promise = launch(tool(...))
register_speculation(promise)
# 在真实 REPL 中运行的钩子版本
def tool_real(...):
if exists(promise, ID(...)):
return promise
else:
return tool(...)
上面的契约让我们能够定义一个“影子”命名空间,在解析 LLM 输出时调用这些修改后的工具调用,并将它们作为 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
# 真实工具现在路由到承诺的工具
exec(code, real_ns)
用于推测的影子执行。 推测的简单情况是,您可以解析工具调用并直接从令牌推断输入,即当输入是字面量时。更棘手的情况出现在工具调用嵌入在条件或循环逻辑中,或者输入依赖于 REPL 调用中先前计算的某些内存变量时。在前一种情况下,我们可能无法先验知道条件是否满足,或者循环运行多久。在后一种情况下,我们不确定计算变量输入是否是纯的并且会修改某些外部状态,这意味着我们无法提前计算它。
有许多潜在的方法来解决这个问题,可能构成一整条研究路线,但为简单起见,我选择维护我们主要代码 REPL 的一个深拷贝分支,我们将其标记为影子 REPL,它在运行时执行部分 REPL。为防止不需要的副作用,大多数外部库和函数(如 open)被标记为“不安全”,任何使用这些函数作为输入依赖的可推测工具都不会被推测。
注意: 我故意选择不将部分“推测器”执行器用作真实 REPL 执行器,因为模型生成的整个 REPL 有可能包含容易出错的代码或不完整的工具调用。在这些情况下,我们不希望推测器实际修改 REPL 状态,我们希望将整个 REPL 单元作为计算单元来处理。
您可以提前“运行”什么。
对于具体可以推测什么、您希望推测多激进以及运行部分代码的整体安全性,有一些考量。我们通常关心了解(1)我们是否有足够的信息来提前运行工具调用;(2)我们能否弄清楚该工具调用是否会实际执行,尤其是在条件语句周围。
在 PTC 中,我们可以通过工具调用的输入(以及非确定性时的发生次数)唯一地索引一个工具调用。在流式传输期间,如果每个工具调用的输入无需代码执行即已知(即所有输入都是字面量,如字符串字面量),我们可以立即异步调用该工具调用。更常见的情况是输入依赖于其他变量,其中一些我们可以安全计算,另一些则不能。对于相同的工具调用(例如对几个子智能体进行多数投票),我们不希望单个推测工具调用路由到每个副本,因此我们需要跟踪同一工具调用的唯一实例,除非我们知道该调用是确定性的。
我们可以看下面几个当前被推测和未被推测的案例,以提供一些直觉:
案例 1:字面量。 字符串、整数或其他字面量可以立即被解析并转换为工具调用,甚至无需每行的影子执行。
title = llm_query("为《奥德赛》提供一个标题") # 解析
blurb = llm_query("为《奥德赛》提供一句话简介") # 解析
print(title, blurb)
案例 2:输入依赖。 当涉及输入依赖时,只要所有输入是安全的(即纯函数,无副作用),它们就可以被推测。此外,依赖于已推测依赖项的工具将等待依赖项先计算完毕,然后在 LLM 仍在流式传输时执行。
a = llm_query("分流: " + doc) # 解析后执行
c = llm_query("摘要: " + str(a)) # 解析,等待 a,然后执行
print(c)
if len(doc) > 10_000: # 如果安全,将求值并推测
extra = llm_query("也提纲挈领: " + doc)
案例 3:可窥视和不可窥视的依赖。 在流式传输的任何步骤中,我们都有一个影子 REPL 的工作命名空间。当解析出一个完整工具时,存在我们可以推测输入的情况,即使依赖是内存中的变量而非字面量。
def gist(t):
return llm_query("一句话要点: " + t) # 不可窥视
parts = [gist(c) for c in chunks]
side = llm_query("给我一个随机标题,关于:", chunks[0]) # 可窥视
print(side, parts)
案例 4:被阻止的推测调用。 尝试推测时,有一个关键词和函数调用的允许列表,可用于计算输入的依赖项。我们也可以指定哪些新工具我们不希望是纯函数。任何依赖项被阻止的可推测工具将不会被推测。
a = llm_query("分流: " + doc) # 被推测
notes = open("/tmp/scratch.txt").read() # 被阻止
b = llm_query("用注释标注: " + notes) # 被阻止
c = llm_query("摘要: " + a) # 在 a 之后被推测
还有很多其他案例,理想情况下我们会为这类事情编写一个完整的伪编译器。我猜测不同的环境设计也会出现各种权衡,但到目前为止这还没有被大量优化。
PTC 的推测开销
与这个技巧的其他部分一样,推测性 PTC 的额外开销在某种程度上取决于具体的实现和推理引擎的设置。对于这个实现,运行时开销可以忽略不计,因为推测器可以低成本地解析并检查它是否可以推测部分生成的 REPL。在内存方面,我们创建了环境代码 REPL 的一个深拷贝,由于我们通常只有少量大型可变对象,这相对于实际 REPL 变量分配的内存来说通常是廉价的。
最坏的情况发生在工具的服务引擎被许多并发且可能额外的推测请求堵塞时,这可以通过控制推测的激进程度以及这些工具的入队方式来管理。
在 LLM 解码层面(即流式输出)的推测算法在较旧的工具调用设计中已有一些探索,尽管并不广泛。可能的情况是,对于标准工具调用,这些技术用处不大,或者开销不值得边际延迟改进。
Conveyor(Xu 等人,2024)(https://arxiv.org/abs/2406.00059)允许用户定义部分执行机会,例如一行代码,这些代码在解码期间被解析。在推测交互智能体(Hooper 等人,2026)(https://arxiv.org/abs/2605.13360)中,他们正式将 Conveyor 中提出的系统定义为推测工具调用,主要通过将更现代模型的长思考链与调用的工具调用重叠来减少首令牌时间(TTFT)。AsyncFC(Feng 等人,2026)(https://arxiv.org/abs/2605.15077)则认为工具调用通常以阻塞方式实现,并定义了一个基于 Future 的异步包装器围绕函数调用的契约;然而,这存在无法与原始环境轨迹 1:1 匹配的风险。
在 sPTC 的情况下,我认为在更复杂的程序中向工具添加推测会显著更有用,原因在于程序本身运行时的不确定性。对于标准工具调用,等到 LLM 生成足够令牌以完全指定工具调用时,可能已经没有更多令牌需要生成了。在 PTC 案例中,关于实际推测什么以及如何推测有更多考虑,因为代码执行使实际工具调用模式显著复杂化,为重叠留下了更多空间。
结语
推测性编程工具调用是编程工具调用本身比传统工具调用本身具有更大重叠潜力而自然产生的一个技巧。我想澄清的是,除了与环境中根 LLM 的流式令牌生成重叠之外,重新
相似文章
OoO-Spec:用于快速工具调用的无序语义推测
介绍了 OoO-Spec,一种通过小型辅助模型以无序方式计算语义槽位来加速 LLM 工具调用的方法,相比自回归解码实现了最高 5.34 倍的加速,并在多个目标和基准测试中超越了现有的草稿模型方法。
@dair_ai: // 代理是其自身最佳的推测者 // 代理花费大量实际时间等待工具结果。推测…
加州大学圣巴巴拉分校和 LinkedIn 的新研究提出了一种自推测代理,将代理和推测者角色统一到一个模型中,通过联合代理-推测者强化学习提高了下一个工具调用预测准确率(Hit@1),同时保持了任务成功率。
什么是推测性解码?(在paperswithco.de上热门)[R]
推测性解码是一种推理优化技术,它使用快速草稿模型提出未来 token,并由较大模型并行验证,从而提高 LLM 的生成速度。文章强调了它在 Papers with Code 上的热门状态,以及最近的 SGLang 博客文章,该文章介绍了使用 DFlash 模型实现的最先进延迟。
ConFu:通过未来思考实现更好的推测采样
ConFu引入了一个新颖的推测解码框架,使草稿模型能够通过思考令牌和软提示预期未来的生成方向,在多个LLM模型上相比EAGLE-3实现了8-20%的令牌接受率和生成速度提升。
@SaitoWu: https://x.com/SaitoWu/status/2053101671035851216
The article summarizes a talk by Matt Pocock criticizing 'specs-to-code' approaches, arguing that solid software engineering fundamentals like TDD and modular design are more critical than ever for effectively using AI coding assistants like Claude Code.