并行工具调用 vs 顺序工具调用的设计考量
难度:中级 分类:Tool Use
简短回答
并行工具调用允许 LLM 同时请求多个无依赖关系的工具执行,将总延迟从所有调用时间之和降低到最长单次调用时间。顺序调用则适用于工具间存在数据依赖的场景——后一个工具需要前一个的输出作为输入。生产环境通常采用混合模式:先并行执行独立的数据收集,再顺序执行依赖前序结果的处理步骤。设计时需要权衡延迟优化、速率限制、错误处理复杂度和共享状态管理。
详细解析
顺序调用(Sequential)
工具一个接一个执行,每个工具可以使用前一个的输出:
代码块
SQL
# 顺序调用:后续工具依赖前序工具的输出
# 用户:"查询订单 #123 的状态,然后给客户发物流提醒"
# Step 1: 查询订单
order = await get_order_status(order_id="123")
# → {"status": "shipped", "tracking": "SF1234", "customer_email": "user@example.com"}
# Step 2: 基于 Step 1 的结果发送邮件(有依赖)
result = await send_email(
to=order["customer_email"], # 依赖 Step 1 的输出
subject=f"您的订单已发货",
body=f"物流单号: {order['tracking']}"
)
适用场景:
•
工具 B 的输入参数依赖工具 A 的输出
•
需要根据前一步结果决定下一步操作(条件分支)
•
数据写入后需要验证结果
并行调用(Parallel)
多个无依赖关系的工具同时执行:
代码块
Python
# 并行调用:三个独立的数据获取
# 用户:"比较北京、上海、深圳的天气"
import asyncio
# 三个调用互不依赖,并行执行
results = await asyncio.gather(
get_weather(city="Beijing"),
get_weather(city="Shanghai"),
get_weather(city="Shenzhen")
)
# 延迟 = max(三次调用时间) ≈ 300ms
# 顺序执行则需要 ≈ 900ms
性能对比:
代码块
Plain Text
顺序执行(4 个 300ms 的调用):
|── 300ms ──|── 300ms ──|── 300ms ──|── 300ms ──| 总计: 1200ms
并行执行(相同 4 个调用):
|── 300ms ──|
|── 300ms ──|
|── 300ms ──|
|── 300ms ──| 总计: 300ms(最长的那个)
适用场景:
•
多源数据收集("查看 A、B、C 三个产品的价格")
•
比较分析("对比 React 和 Vue 的优缺点")
•
独立子任务("生成三种不同风格的营销文案")
LLM 如何决定并行 vs 顺序?
模型通过分析工具描述和参数依赖关系来决定:
代码块
Python
# 模型返回多个 tool_use block → 表示并行
response.content = [
ToolUse(name="get_weather", input={"city": "Beijing"}),
ToolUse(name="get_weather", input={"city": "Shanghai"}),
] # 两个独立调用,可并行执行
# 模型返回单个 tool_use → 顺序执行,等结果后再决定下一步
response.content = [
ToolUse(name="get_order", input={"order_id": "123"}),
] # 先获取订单,后续操作取决于结果
关键点:模型生成调用请求,你的基础设施决定如何执行。即使模型返回多个工具调用,开发者可以选择并行或顺序执行。
混合模式(Hybrid)—— 生产最佳实践
混合模式:独立的数据收集并行扇出,依赖前序结果的处理顺序扇入,延迟从 N×T 降为 max(T)+串行段。
代码块
Python
# 混合模式示例:"为产品 A、B、C 生成竞品分析报告"
# 阶段 1:并行收集数据(无依赖)
product_data = await asyncio.gather(
research_product("Product A"),
research_product("Product B"),
research_product("Product C"),
)
# 阶段 2:顺序处理(依赖阶段 1 的结果)
comparison = await generate_comparison(product_data)
report = await format_report(comparison)
关键设计考量
依赖分析
代码块
Python
速率限制
并行执行降低延迟,但增加瞬时并发:
代码块
Python
# 并行但受限的执行
semaphore = asyncio.Semaphore(5) # 最多 5 个并发
async def rate_limited_call(tool, params):
async with semaphore:
return await tool.execute(params)
results = await asyncio.gather(
*[rate_limited_call(tool, p) for p in params_list]
)
错误处理差异
代码块
Python
# 顺序模式:一个失败就停止,比较简单
try:
step1 = await tool_a()
step2 = await tool_b(step1) # 如果 tool_a 失败,这里不会执行
共享状态
并行工具不应修改共享状态:
代码块
Plain Text
# 危险:并行工具修改同一个数据库记录
# 安全:并行工具只读取数据,写入操作顺序执行
编排框架中的实现
代码块
Python
# LangGraph:用 fan-out/fan-in 实现并行
from langgraph.graph import StateGraph
graph = StateGraph(State)
# 并行分支
graph.add_node("research_a", research_product_a)
graph.add_node("research_b", research_product_b)
# fan-out: 一个节点分发到多个并行节点
graph.add_edge("start", "research_a")
graph.add_edge("start", "research_b")
# fan-in: 多个并行节点汇聚到一个节点
graph.add_edge("research_a", "aggregate")
graph.add_edge("research_b", "aggregate")
决策框架
常见误区 / 面试追问
误区:"并行总是更好" — 并行增加瞬时并发,可能触发 API 速率限制。并且并行的错误处理更复杂——需要决定部分失败时是全部回滚还是保留成功结果。
误区:"LLM 会自动优化并行/顺序" — LLM 只负责生成工具调用请求。实际的执行策略(并行或顺序)由应用层的编排代码决定。开发者需要显式实现并行执行逻辑。
追问:"如何处理并行调用中的部分失败?" — 两种策略:(1) 原子性——一个失败全部回滚(适合事务操作);(2) 尽力执行——保留成功结果,将失败信息返回给 LLM 让它决定下一步(适合数据收集)。
追问:"Map-Reduce 模式和并行工具调用有什么关系?" — Map-Reduce 是并行工具调用的一种特定模式:Map 阶段将任务分发到多个并行工具,Reduce 阶段汇聚结果。LangGraph 等框架原生支持这种 fan-out/fan-in 模式。