并行调用与调用链
多工具并发、依赖顺序,以及结果过大时怎么处理。
一句话理解:互不依赖的调用并行跑,有依赖的调用按顺序跑,有副作用的调用谨慎串行;结果太大时,不要硬塞进上下文,而是分页、摘要或存成文件再按需读取。
一次订单售后诊断通常要查四五个系统。如果每次只查一个、等结果回来再决定下一个,用户要等很久,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" } }
]
}
程序这时要做三件事:
- 决定执行方式。 API 不规定执行顺序,你可以用
Promise.all并发执行,也可以按顺序执行。Claude 的文档建议:互相独立的只读操作通常可以并行,有副作用、共享状态或有顺序要求的操作最好串行。 - 一一对应回填结果。 Claude 要求每个
tool_use都有一个tool_result,全部放在紧接着的 同一条 用户消息里,用tool_use_id对应,并且排在任何文字之前。OpenAI 则用call_id对应每个function_call_output。 - 没执行的也要回话。 如果串行执行时前一个调用失败、后面的没有跑,Claude 的文档建议仍然给它返回一个
is_error: true的结果,并说明“未执行:前一个调用失败”,否则对话结构不完整。
以上面三个并行调用为例,执行与回填的对应关系如下:
需要关闭并行时,Claude 在 tool_choice 里设置 disable_parallel_tool_use: true,OpenAI 设置 parallel_tool_calls: false。典型场景是写操作之间有顺序依赖,或者下游接口扛不住并发。
调用链:依赖决定顺序
订单售后诊断的依赖关系可以画成一张图:
规律很简单:
- ① 必须先做,因为后面的参数来自它的结果。
- ②③④ 互不依赖,可以在同一轮并行请求。
- ⑥ 是写操作,要在判断完成、必要时经人确认之后单独执行。
模型通常能自己推断出这种依赖,但你可以用两种方式帮它:在工具描述里写明“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 分钟):
- 选 3 类典型问题(“为什么还没发货”“为什么还没退款”“这周售后最多的商品是哪个”),分别画出工具依赖图,标注并行与串行。
- 找出返回量可能最大的工具,设计它的分页参数和截断提示文案。
- 第三个问题需要处理大量订单,判断它更适合模型逐个调用、程序批量处理,还是让模型写代码批量调用,并说明理由。
延伸阅读
- Parallel tool use(Claude):并行调用的执行语义、结果格式与关闭方式。
- Function calling(OpenAI):
parallel_tool_calls与多函数调用的处理。 - Claude Code MCP 文档:包括 MCP 工具输出的 Token 上限与调整方式。
下一步
到目前为止,工具都是我们自己写的。下一课「通用内置工具」介绍平台直接提供的搜索、代码执行、文件、浏览器与计算机操作。