游乐游手机版
首页/AI教程/文章详情

Agent多Tool Call返回:并行还是顺序执行?

时间:2026-07-21 14:46
一个 Agent 接入了多个工具后,模型在单次回复中可能返回不止一个 tool_call。例如,当用户要求同时分析两个文件时,模型可能产生如下调用: read_file(a js) read_file(b js) 假设每个文件读取需要 2 秒,顺序执行大约耗时 4 秒,而并行执行则可能仅需 2 秒左

一个 Agent 接入了多个工具后,模型在单次回复中可能返回不止一个 tool_call。例如,当用户要求同时分析两个文件时,模型可能产生如下调用:

read_file(a.js)
read_file(b.js)

假设每个文件读取需要 2 秒,顺序执行大约耗时 4 秒,而并行执行则可能仅需 2 秒左右。乍看之下,将所有工具调用放入 Promise.all() 似乎就能提升性能。

Agent 一次返回多个 Tool Call,应该并行还是顺序执行?

但换个场景:

创建项目
安装依赖
启动开发服务器

如果这三个步骤同时执行,安装依赖时项目可能尚未创建,启动服务时依赖也可能还未完成。因此,多 Tool Call 的关键问题并非“会不会用 Promise.all()”,而是:这些调用之间是否存在依赖关系?

本文将通过对比项目中的两种真实 Agent Loop 来探讨:tool.mjs 使用 Promise.all() 并行执行,mini-cursor.mjs 和多 MCP Demo 则采用 for...of + await 顺序执行。我们将分析它们分别适用于何种任务,以及如何确保工具结果准确对应回原始的 tool_call_id

模型返回的是 Tool Call 数组

当模型决定使用工具时,会在 AIMessage 中返回 tool_calls,即 response.tool_calls。每个调用通常包含如下结构:

{
  id: 'call_xxx',
  name: 'read_file',
  args: { filePath: './src/app.js' }
}

若一次回复需要多个工具,tool_calls 便是一个数组:

[
  { id: 'call_1', name: 'read_file', args: { filePath: './src/app.js' } },
  { id: 'call_2', name: 'read_file', args: { filePath: './src/config.js' } }
]

模型仅生成调用意图与参数,实际调度这些任务的是 Runtime。Runtime 可以选择顺序执行(一个完成后再执行下一个),或并行执行(同时启动,等待全部完成)。

方案一:使用 Promise.all() 并行执行

tool.mjs 中,通过 Promise.all() 处理一轮中的所有 Tool Call:

const toolResults = await Promise.all(
  response.tool_calls.map(async (toolCall) => {
    const tool = tools.find(item => item.name === toolCall.name);
    if (!tool) {
      return `错误:找不到工具 ${toolCall.name}`;
    }
    try {
      return await tool.invoke(toolCall.args);
    } catch (err) {
      return `错误:${err.message}`;
    }
  })
);

map() 将每个 Tool Call 转换为一个 Promise,Promise.all() 同时等待这些异步任务完成。执行过程可描述为:

tool_call 1 ──开始────────完成
tool_call 2 ──开始────完成
tool_call 3 ──开始──────────完成
           ↓ 所有任务完成后继续

该方案最适合彼此独立的 I/O 任务,例如:

  • 同时读取多个互不相关的文件
  • 查询多个城市的天气
  • 请求多个独立数据接口
  • 对多份内容执行互不影响的检查

这些任务无需等待前一个结果,也不会修改同一份共享状态。

Promise.all() 的结果顺序会乱吗?

不会。即便第二个工具最先执行完成,Promise.all() 返回的结果数组仍按传入 Promise 的顺序排列。例如:

输入顺序:call_1、call_2、call_3
完成顺序:call_2、call_1、call_3
结果数组:result_1、result_2、result_3

因此,项目中可通过相同的 index 将结果对应回原始调用:

response.tool_calls.forEach((toolCall, index) => {
  messages.push(new ToolMessage({
    content: toolResults[index],
    tool_call_id: toolCall.id,
  }));
});

这里存在两层对应关系:toolResults[index]response.tool_calls[index],以及 tool_call_idtoolCall.id。数组下标帮助 Runtime 取到正确结果,而 tool_call_id 则帮助模型确认结果属于哪一次调用。并行执行绝不意味着可以省略 tool_call_id

Promise.all() 为什么经常“一错全错”?

标准 Promise.all() 有一个重要特性:只要其中一个 Promise 进入 rejected 状态,外层等待就会立即 reject。

await Promise.all([
  taskA(),
  taskB(), // rejected
  taskC(),
]);

需注意,外层提前失败并不会自动取消其他已启动的异步任务。taskAtaskC 仍可能继续执行,只是当前代码不再等待它们的完整结果。

项目中的处理方式是在每个工具内部捕获错误:

try {
  return await tool.invoke(toolCall.args);
} catch (err) {
  return `错误:${err.message}`;
}

工具失败后返回的是错误字符串,而非继续抛出异常。这样 Promise.all() 仍能获得完整的结果数组:

['文件 A 的内容', '错误:文件不存在', '文件 C 的内容']

模型在下一轮中既能看到成功结果,也能看到失败原因,从而决定是否重试或换一种方式处理。

方案二:使用 for...of 顺序执行

mini-cursor.mjs 中采用了另一种方式:

for (const toolCall of response.tool_calls) {
  const foundTool = tools.find(tool => tool.name === toolCall.name);
  if (foundTool) {
    const toolResult = await foundTool.invoke(toolCall.args);
    messages.push(new ToolMessage({
      content: toolResult,
      tool_call_id: toolCall.id,
    }));
  }
}

await 位于循环内部,因此当前工具完成后,程序才会执行下一个工具。执行过程如下:

tool_call 1:开始 → 完成
tool_call 2:         开始 → 完成
tool_call 3:                  开始 → 完成

顺序执行耗时可能更长,但更适用于会产生副作用或共享状态的任务:

  • 写入同一个文件
  • 创建项目后再安装依赖
  • 安装依赖后再启动服务
  • 打开网页后再获取页面快照
  • 获取快照后再根据页面内容点击元素
  • 点击进入详情页后再截图

这些任务不能仅看“有几个 Tool Call”,还需考虑先后关系。

顺序执行就能解决所有依赖吗?

不能。同一个 AIMessage 中的多个 Tool Call,是模型在看到工具结果之前一次性规划出来的。即使 Runtime 按顺序执行,后一个调用的参数也已经提前确定:

模型同时生成 call_1 和 call_2
→ Runtime 执行 call_1
→ Runtime 执行 call_2

执行 call_1 后,模型没有机会根据真实结果重新生成 call_2 的参数。以浏览器任务为例:

打开搜索页 → 获取页面快照 → 根据快照找到酒店按钮 → 点击按钮

模型必须先看到页面快照,才能知道应点击哪个元素。这种真正依赖上一步结果的任务,更适合拆成多个 Agent Loop:

第 1 轮:模型调用打开网页 → ToolMessage 返回页面状态
第 2 轮:模型根据状态调用页面快照 → ToolMessage 返回快照内容
第 3 轮:模型根据快照决定点击哪个元素

因此需区分两种“顺序”:

  • 同一轮内按顺序执行:只保证副作用发生的先后顺序
  • 跨模型轮次执行:后一步可真正依据前一步结果重新规划

哪些工具不能轻易并行?

1. 修改同一个资源

两个工具同时写入同一个文件,最终结果可能取决于谁最后完成:

write_file(app.js, 内容 A)
write_file(app.js, 内容 B)

这会产生竞态条件,不能简单放入 Promise.all()

2. 浏览器操作

点击、切换标签页、输入和截图都依赖当前页面状态。如果同时点击两个元素或在页面加载前截图,结果将变得不可预测。

3. 命令行任务

以下三步具有明确依赖:

pnpm install
pnpm build
pnpm preview

构建依赖安装结果,预览又依赖构建产物。

4. 共享数据库状态

同时执行扣库存、创建订单和更新支付状态,可能引发数据一致性问题。是否允许并发,应由业务事务和幂等设计决定,而非仅靠模型判断。

哪些工具适合并行?

通常满足以下条件的任务更适合并行:

  • 彼此不依赖返回结果
  • 不修改同一资源
  • 失败不会破坏其他任务
  • 调用顺序不影响最终结果
  • 服务端允许相应并发量

例如:

读取 a.js、b.js、c.js
查询北京、上海、广州天气
检查三份独立配置文件
获取多个互不关联的公开接口数据

并行前仍需考虑接口限流、连接数量和机器资源。若模型一次返回几十个调用,直接全部启动可能触发 API 限流或占满文件句柄。实际项目通常还需增加并发上限,而非无限制地使用 Promise.all()

Runtime 应该怎样决定?

不能将“并行还是顺序”完全交给 LLM 的自然语言推理。更稳妥的做法是让 Runtime 根据工具属性和资源冲突做出判断。

可从以下四个问题入手:

  1. 后一个调用是否需要前一个调用的返回值?
  2. 多个调用是否会修改同一个文件、页面或数据库记录?
  3. 工具失败后是否可以安全重试?
  4. 外部服务是否有并发与频率限制?

简单决策可整理为:

场景建议方式
多个独立只读查询并行执行
修改同一份状态顺序执行
后一步需要前一步结果跨 Agent 轮次执行
浏览器点击、等待、截图顺序并分轮执行
大量独立调用限制并发数量

ToolMessage 的顺序也要保持稳定

无论工具如何调度,最终都需将每个结果放回 messages

AIMessage(tool_calls)
→ Runtime 执行工具
→ ToolMessage(tool_call_id)
→ 再次调用模型

并行执行时,不能按照“哪个工具先完成就先随意塞进去”的方式丢失对应关系。项目利用 Promise.all() 的结果顺序与原始 tool_calls 下标完成映射,并为每条消息保留 tool_call_id,这是并行工具执行能够稳定工作的关键。

总结

Agent 一次返回多个 Tool Call,并不意味着所有工具都应并行。

并行解决的是等待时间
顺序保证的是状态和依赖
跨轮次解决的是根据真实结果重新规划

选择原则可归纳为:

独立、只读、无共享状态 → Promise.all()
存在副作用或资源冲突 → for...of + await
后一步依赖前一步结果 → 返回 ToolMessage,让模型进入下一轮

模型负责提出调用计划,Runtime 才应负责并发控制、错误隔离和执行顺序。这也是 Agent 从“能调用工具”走向“稳定完成复杂任务”时必须补上的工程能力。

来源:https://juejin.cn/post/7663161716598898734
上一篇高级AI工程师面试备战手记:实战经验与深度思考 下一篇人工智能又又造词 Graph是否即将替代Loop引发热议
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
TalkVisions实时视频翻译应用,消除语言障碍
AI教程 · 2026-07-25

TalkVisions实时视频翻译应用,消除语言障碍

TalkVisions是一款实时视频翻译应用,能将视频中的口语实时转录为文本并翻译成用户所选语言,以字幕形式叠加在画面上,支持多语言、低延迟,还可保存录制视频,有效消除跨语言沟通障碍。

AI驱动的日历管理工具Ipso
AI教程 · 2026-07-25

AI驱动的日历管理工具Ipso

IpsoAI是一款专为专业人士及助手打造的AI日历管理工具,能够自动协调多方日程、智能草拟邮件,并通过快速安排会议、提供智能建议及自动化工作流程,显著减少琐碎操作,帮助用户高效管理时间、提升工作效率。

Spectate企业级专业高效监控与事故管理一体化平台
AI教程 · 2026-07-25

Spectate企业级专业高效监控与事故管理一体化平台

Spectate是一款高效监控和事故管理工具,能在30秒内检测故障并推送告警。它支持Slack、PagerDuty等主流集成,提供自定义状态页面和全球性能监控。系统自动更新状态并推送修复建议,帮助团队减少沟通成本,快速解决问题。

阿里云通义千问2.5大模型发布 多项能力赶超GPT-4
AI教程 · 2026-07-25

阿里云通义千问2.5大模型发布 多项能力赶超GPT-4

通义千问2 5大模型发布,多项能力宣称赶超GPT-4,中文语境下文本理解、生成、知识问答等表现优异。相比2 1版本,理解提升9%、逻辑推理提升16%、指令遵循提升19%。开源1100亿参数模型超越Llama-3-70B,获评开源最强。已服务超9万家企业,与小米、微博等达成合作。

万知个人AI工作站:一站式智能阅读创作分享平台
AI教程 · 2026-07-25

万知个人AI工作站:一站式智能阅读创作分享平台

万知是集成多种AI能力的个人工作站,支持自然语言交互、文档快速阅读与摘要生成、PPT自动设计与优化,覆盖学术研究、商务报告、写作辅助及日常问答等场景,全方位提升工作效率。