06.05概念更新于 2026年9月17日

并行调用与调用链

多工具并发、依赖顺序,以及结果过大时怎么处理。

06.05 · 课时讲解

并行调用与调用链

独立的并行跑,有依赖的按顺序,有副作用的谨慎串行。

1 / 12

一句话理解:互不依赖的调用并行跑,有依赖的调用按顺序跑,有副作用的调用谨慎串行;结果太大时,不要硬塞进上下文,而是分页、摘要或存成文件再按需读取。

一次订单售后诊断通常要查四五个系统。如果每次只查一个、等结果回来再决定下一个,用户要等很久,Token 也翻倍消耗。反过来,如果把所有调用一股脑并行发出,又可能出现“还没拿到客户 ID 就去查历史工单”的依赖错误,或者两个写操作互相覆盖。

编排工具调用,既是性能问题,也是正确性问题。本课讲清楚三件事:什么能并行、什么必须串行、结果太大怎么办。

并行调用:一轮里发出多个请求

Claude 和 OpenAI 的模型都可以在同一轮回复里请求多个工具。以 Claude 为例,一条助手消息里可能同时出现多个 tool_use 块:

{
  "stop_reason": "tool_use",
  "content": [
    { "type": "tool_use", "id": "toolu_01", "name": "get_payment", "input": { "order_id": "ORD2026090142" } },
    { "type": "tool_use", "id": "toolu_02", "name": "get_shipment", "input": { "order_id": "ORD2026090142" } },
    { "type": "tool_use", "id": "toolu_03", "name": "search_tickets", "input": { "customer_id": "C-10233" } }
  ]
}

程序这时要做三件事:

  1. 决定执行方式。 API 不规定执行顺序,你可以用 Promise.all 并发执行,也可以按顺序执行。Claude 的文档建议:互相独立的只读操作通常可以并行,有副作用、共享状态或有顺序要求的操作最好串行。
  2. 一一对应回填结果。 Claude 要求每个 tool_use 都有一个 tool_result,全部放在紧接着的 同一条 用户消息里,用 tool_use_id 对应,并且排在任何文字之前。OpenAI 则用 call_id 对应每个 function_call_output。
  3. 没执行的也要回话。 如果串行执行时前一个调用失败、后面的没有跑,Claude 的文档建议仍然给它返回一个 is_error: true 的结果,并说明“未执行:前一个调用失败”,否则对话结构不完整。

以上面三个并行调用为例,执行与回填的对应关系如下:

并行调用的回填:三个 tool_use 并发执行,结果用 tool_use_id 一一对应,放进同一条用户消息 一条助手消息 程序:并发执行 紧接着的同一条用户消息 get_payment id: toolu_01 成功 320 ms tool_result tool_use_id: toolu_01 get_shipment id: toolu_02 成功 180 ms tool_result tool_use_id: toolu_02 search_tickets id: toolu_03 超时 5 秒未返回 is_error: true toolu_03:超时,可重试 用 tool_use_id 一一对应;所有结果放在同一条消息里,排在文字之前
图 1 · 一轮多个调用:并发执行,逐个回填,失败的也要回话

需要关闭并行时,Claude 在 tool_choice 里设置 disable_parallel_tool_use: true,OpenAI 设置 parallel_tool_calls: false。典型场景是写操作之间有顺序依赖,或者下游接口扛不住并发。

调用链:依赖决定顺序

订单售后诊断的依赖关系可以画成一张图:

订单退款诊断的工具依赖图:先查订单,再并行查发货、支付和工单,最后综合判断,写操作需确认 用户:ORD2026090142 为什么一直没退款? ① get_order(order_id) 得到客户 ID、订单状态、金额 必须先做:后续参数来自它 互不依赖:同一轮并行请求 ② get_shipment 参数:order_id ③ get_payment 参数:order_id ④ search_tickets 参数:customer_id(来自 ①) ⑤ 模型综合判断 退货已入库,但退款流水失败 ⑥ create_refund 写操作,可选 判断完成、经人确认后单独执行
图 2 · 依赖决定顺序:① 串行在前,②③④ 并行,⑥ 写操作单独执行

规律很简单:

  • ① 必须先做,因为后面的参数来自它的结果。
  • ②③④ 互不依赖,可以在同一轮并行请求。
  • ⑥ 是写操作,要在判断完成、必要时经人确认之后单独执行。

模型通常能自己推断出这种依赖,但你可以用两种方式帮它:在工具描述里写明“customer_id 来自 get_order 的返回”;或者干脆在工具层合并,比如让 search_tickets 也接受订单号,由程序内部先查出客户 ID。后者更可靠,这也是 工具设计原则 里“合并总是连续调用的工具”的体现。

固定链路交给程序,判断交给模型

不是所有调用链都该由模型来编排。如果某个链路 每次都一样(比如诊断前总要先查订单、再查支付),把它写成确定性代码更快、更便宜、更可预测。模型只在需要判断的分叉点介入。

场景 更适合
步骤固定、顺序固定 程序直接编排(Workflow)
下一步查什么,取决于上一步的结果 模型编排(Agent Loop)
需要循环处理几百条数据 程序批量处理,或让模型写代码批量调用

最后一类值得单独说:部分平台已经支持让模型写一段代码,在代码里批量调用工具,只把汇总结果带回上下文,例如 OpenAI 的 Programmatic Tool Calling。对“查 200 个订单、统计售后原因分布”这类任务,比让模型一轮轮发 200 次调用高效得多。两种方式怎么混用,见 混合架构。

结果太大怎么办

一次查询返回 5 万 Token 的订单明细,会带来三个问题:成本飙升、挤占上下文窗口、关键信息被淹没。Claude Code 就对 MCP 工具输出设了上限:超过 1 万 Token 时给出警告,默认最多 2.5 万 Token。常见的处理手段:

手段 做法 适用
分页 默认返回前 20 条,附带 next_cursor 与总数 列表类查询
过滤 提供 status、date_from 等参数,让模型只取需要的 大表查询
服务端摘要 工具内部先聚合,返回“共 356 条状态变更,异常 3 条:……” 统计与诊断
详略开关 response_format 取 concise 或 detailed 同一工具的不同用途
存文件、回引用 完整结果写入文件或对象存储,只返回路径和摘要,需要时再读 日志、报表、代码搜索
截断并提示 超长时截断,并说明“已截断,可用 offset 继续读取” 兜底

截断时一定要 告诉模型结果被截断了。否则它会以为自己看到的就是全部,得出“只有 20 条售后”这样的错误结论。

长任务里,旧的工具结果也会不断堆积。定期清理或摘要历史工具结果,属于上下文管理的范畴,见 长任务的上下文管理。

超时与部分失败

并行调用时,常见情况是三个工具成功、一个超时。处理原则:

  • 每个工具都设置超时,不让一个慢接口拖住整轮。
  • 成功的结果照常返回,失败的返回可行动的错误信息,让模型判断“缺了这一项还能不能下结论”。
  • 读操作可以由程序自动重试一两次;写操作重试前必须确认上一次是否已经生效(配合幂等键)。
  • 在 Trace 里记录每个调用的耗时与状态,方便定位瓶颈,见 Tracing。

常见误区

  • “能并行就全部并行。” 写操作并行可能产生竞态,比如同时建两张工单、同时改一个状态。
  • “结果多给点,模型自己会挑。” 超长结果不只贵,还会让模型漏看关键信息。
  • “调用链让模型自己想就行。” 固定链路交给模型,每次都要付推理成本,还可能偶尔走错。能写死的就写死。

设计要点

  • 画出主要任务的工具依赖图:哪些可以并行,哪些必须串行?
  • 有副作用的工具是否关闭了并行,或在程序层保证了顺序与幂等?
  • 每个可能返回大量数据的工具,有没有分页、过滤、摘要或截断提示?
  • 每个工具有没有超时?部分失败时,结果怎么回填?
  • 有没有每次都相同的调用链,可以改成程序编排?

练习

以「订单售后诊断 Agent」为对象(45 分钟):

  1. 选 3 类典型问题(“为什么还没发货”“为什么还没退款”“这周售后最多的商品是哪个”),分别画出工具依赖图,标注并行与串行。
  2. 找出返回量可能最大的工具,设计它的分页参数和截断提示文案。
  3. 第三个问题需要处理大量订单,判断它更适合模型逐个调用、程序批量处理,还是让模型写代码批量调用,并说明理由。

延伸阅读

下一步

到目前为止,工具都是我们自己写的。下一课「通用内置工具」介绍平台直接提供的搜索、代码执行、文件、浏览器与计算机操作。

试试:上下文、MCP、沙箱、Prompt 注入、外链