跳到主要内容

LLM Agent 中的并行工具调用:你可能尚未意识到的耦合测试

· 阅读需 11 分钟
Tian Pan
Software Engineer

大多数工程师之所以选择并行工具调用,是因为他们希望自己的 Agent 运行得更快。工具执行占 Agent 总延迟的 35–60%,具体取决于工作负载——编码任务处于高端,深度研究任务则处于中端。同时运行独立的调用是显而易见的优化方案。但接下来的情况却让大多数团队感到意外。

!["https://opengraph-image.blockeden.xyz/api/og-tianpan-co?title=LLM%20Agent%20%E4%B8%AD%E7%9A%84%E5%B9%B6%E8%A1%8C%E5%B7%A5%E5%85%B7%E8%B0%83%E7%94%A8%EF%BC%9A%E4%BD%A0%E5%8F%AF%E8%83%BD%E5%B0%9A%E6%9C%AA%E6%84%8F%E8%AF%86%E5%88%B0%E7%9A%84%E8%80%A6%E5%90%88%E6%B5%8B%E8%AF%95"]

一旦你启用了并行执行,工具设计中隐藏的每一个假设都会变得显而易见。在顺序执行时可靠工作的工具,在并发运行时可能会悄无声息地失效。原本稳定的行为变得不可预测,而且失败往往不会产生错误——只是在充满自信地返回一个错误的答案。

并行工具调用主要不是一项性能特性。它是一次非自愿的架构审计。

并行工具执行的实际工作原理

在深入探讨失败模式之前,有必要精确了解其机制。并行工具执行是模型做出的决定,而不是你的编排层做出的决定。当模型在单次响应中发出多个 tool_use 块时,你的运行器(runner)应当调用所有这些工具,并在下一步推理之前统一返回它们的结果。模型看不到中间结果——它会一次性看到所有内容。

大多数框架都会提供一个标志来抑制这种行为。OpenAI 的 API 包含 parallel_tool_calls,默认值为 true。Anthropic 没有提供直接的标志——相反,Claude 会根据请求的工具是否看起来独立来做出决定。这种行为在不同模型系列中也不一致:OpenAI 的推理模型(o3、o4-mini)要么忽略,要么完全拒绝 parallel_tool_calls 参数,如果你尝试显式设置它,会返回 400 错误。

这种不一致性在生产环境中至关重要。如果你在多个提供商之间进行路由,或者在模型版本之间进行升级,你不能假设并行行为是稳定的。无论你是否请求过,你的编排层都需要处理多工具响应。

执行模式本身非常直接:当模型在单轮中发出 N 个工具调用时,你的运行器会同时分派所有 N 个调用,等待所有调用完成,并在继续推理之前返回整批结果。延迟的降低完全来自于墙上时钟时间(wall-clock time)的重叠——一批三个独立的 200 ms 工具调用只需 200 ms,而不是 600 ms。

耦合隐藏时的三种失败模式

顺序执行是宽容的。如果工具 A 对工具 B 有隐性依赖,顺序执行会自动强制执行该依赖。你通常甚至不知道这种依赖关系的存在,因为代码一直以相同的顺序运行。并行执行则取消了这种宽容。

上下文依赖(Context dependency):工具 A 悄悄地从一个共享上下文变量中读取数据,而该变量本应由工具 B 填充。在顺序执行中,B 总是运行在 A 之前。在并行执行中,A 在 B 填充上下文之前运行,读取了陈旧或空的数据,并返回了一个看起来有效但基于错误输入计算的结果。没有抛出异常。Agent 带着错误答案继续运行。

共享状态变更(Shared state mutation):两个工具根据在写入完成前读取的状态,向同一个资源(文件、数据库行、缓存值)写入数据。这是经典的“读取-修改-写入”竞态条件。工具 A 读取当前值 (100),计算增量 (+10),并写回 110。工具 B 读取相同的初始值 (100),计算不同的增量 (+20),并写回 120。最终值是 120,但预期的值应该是 130。两个工具都没有报错。这种不一致性是无声的。

执行时序依赖(Execution timing dependency):这是最微妙的失败。一个工具中的逻辑隐式地假设另一个工具已经运行——不是因为它读取了输出,而是因为第一个工具的副作用是第二个工具的前提条件。例如,一个工具创建数据库记录,另一个工具写入相关记录;一个工具初始化会话,另一个工具向该会话添加数据;或者一个工具获取资源锁,另一个工具在锁定的资源上操作。在顺序执行中,前提条件总是能得到满足。在并行执行中则不然。

这些失败都有一个共同点:它们不是崩溃。Agent 循环继续运行,模型处理结果,下一步在损坏的状态下进行。当错误的输出显现时,执行轨迹已经向前推进了好几步,与原始并行调用的因果联系已变得不可见。

幂等性测试:在并行化之前对工具进行分类

最简单且最可靠的分类方法是在决定是否并发运行每个工具之前,问三个问题。

它是原子的吗(Is it atomic)? 工具是否只做一件事,没有其他并发工具可以观察到的中间状态?搜索查询是原子的——它读取并返回,没有其他事情发生。文件重命名在大多数文件系统中不是原子的——存在一个旧名称已消失而新名称尚未显示的窗口。

它是幂等的吗(Is it idempotent)? 如果这个工具对相同的输入运行两次,最终世界的状态是否相同?GET 请求是幂等的。除非你显式处理去重,否则创建记录的 POST 请求不是幂等的。每次调用都发送通知邮件的工具不是幂等的。

加载中…
References:Let's stay in touch and Follow me for more thoughts and updates