想象你是一家繁忙餐厅的总厨。客人点了一道“脆皮烤鸭配酸梅酱”,你不会自己一个人从头忙到尾——你会让帮厨去清洗鸭子、让烧烤师傅掌控火候、让酱料师调制蘸酱、让传菜员在最佳时机上桌。每个环节都有专人(工具)负责,而你作为总厨(Agent),心里装着整张菜单和流程,知道谁先谁后、什么条件该叫谁上、哪一步需要确认。如果烤鸭糊了,你会立刻叫烧烤师傅重烤;如果客人多,你会让多个灶台同时开工。这就是“工具编排”——让AI Agent像总厨调度后厨一样,按照预定计划有序地组合多个工具,完成复杂任务。 本文将用“餐厅后厨”这个比喻贯穿始终,带你理解工具编排的核心模式(顺序、条件、循环、并行)、状态如何传递、依赖如何处理、异常如何恢复,以及实战设计原则。学完你会明白:为什么说“工具编排是Agent从'能说话'到'能办事'的关键一跃”。

岗位关联

对于AI从业者:工具编排是构建多工具协作Agent的核心能力,掌握它意味着你能把零散的API调用变成可靠的业务自动化流程,这是成为高级AI应用工程师的关键技能。

目录
  1. 可视化演示
  2. 工具编排基础
  3. 顺序编排
  4. 条件分支
  5. 循环与并行
  6. 状态传递
  7. 依赖与调度
  8. 异常恢复
  9. 设计原则
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

动态演示工具编排的核心机制,通过全局/局部/动态/静态四种模式展示关键概念与交互流程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

工具编排:工具编排 工具编排总览与核心编排模式 顺序执行与条件分支编排实践 核心机制可视化 工具编排整体架构与关键组件

工具编排基础

作为总厨,你手里有一份“后厨协作手册”——它规定了什么菜该经过哪些厨师的手、每道工序负责什么、出现问题找谁。这份手册就是“编排”。如果只有一位厨师,一手包办洗切炒,那就不需要手册;但现实是,一个复杂任务(做一桌宴席)必然涉及多个专长(食材采购、清洗、刀工、火候、摆盘),必须有人统筹规划。AI Agent 也是如此:单个工具能力再强也有边界,工具编排的价值在于把“单点能力”变成“流程能力”。

技术视角来看,工具编排(Tool Orchestration)是指 Agent 依照预设流程或动态规划,依次或并行地调用多个工具(API、函数、服务),并协调它们之间的数据流与控制流。一个最简单的编排就是顺序调用:先调用工具A,把A的结果作为参数传给工具B。但真实场景中,编排要处理条件判断(库存不足怎么办)、循环(批量处理客户名单)、并行(同时查询多个供应商价格)、异常(API超时怎么办)等复杂逻辑。

从工程师的角度,编排层就像后厨的“传菜铃”和“看板”——它不关心每道菜怎么炒(工具内部实现),只关心工序之间的衔接、资源和结果的流动。成熟的 Agent 框架(如 LangChain、AutoGen)都内置了编排引擎,而理解编排的核心模式,是你自己设计复杂 Agent 的基础。

一场宴会如果只有一道菜,你不需要编排;但如果你要做一桌酒席,没有编排就会乱成一锅粥。工具编排就是 Agent 的“后厨手册”,让每个工具在最合适的时机、以正确的方式被调用。

工具编排=顺序执行+条件分支+循环迭代+并行执行⏟控制流模式+状态传递+依赖调度+异常恢复⏟数据流与可靠性\text{工具编排} = \underbrace{\text{顺序执行} + \text{条件分支} + \text{循环迭代} + \text{并行执行}}_{\text{控制流模式}} + \underbrace{\text{状态传递} + \text{依赖调度} + \text{异常恢复}}_{\text{数据流与可靠性}}
这个公式告诉你:工具编排 = 控制流(怎么走)+ 数据流(传什么)+ 可靠性(出错了怎么办)。三者缺一不可。
  1. 顺序执行最简单的编排模式,按步骤先后逐个调用。
    顺序:A→B→C\text{顺序:}A \rightarrow B \rightarrow C
  2. 条件分支根据中间结果决定走哪条支路。
    条件:if x>0 then B else C\text{条件:}\text{if } x > 0 \text{ then } B \text{ else } C
  3. 循环迭代对一批数据反复执行同一工具。
    循环:for item∈list do Tool(item)\text{循环:}\text{for } item \in list \text{ do } Tool(item)
  4. 并行执行无依赖的多个工具同时运行。
    并行:A∥B(互不依赖,同时执行)\text{并行:}A \parallel B \text{(互不依赖,同时执行)}

顺序编排

后厨里做“佛跳墙”绝不能先上桌再炖汤——流程顺序错了,菜品就废了。顺序编排就是“认准步骤、一步一步来”。它的核心特征是:后一个工具的启动依赖前一个工具的输出,前一步失败则整个流程中止。这种模式适合那些客观上存在先后逻辑的任务——比如“查询商品库存”必须在“生成订单”之前,因为你先要知道有没有货,才能决定是否下单。

在技术落地时,顺序编排通常用一个“任务队列”来实现:Agent 维护一个工具调用列表,依序取出执行,并将每一步的返回值存入共享上下文(即“传递挡板”)。比如,工具1返回 {"库存": 5},工具2 就能从上下文中读取 库存 这个变量来决定自己的参数。实现上相当于一个不可跳过的步骤序列,任何一步抛出异常,队列即停止。

LangChain 等框架中,这种编排被封装成 Chain(链),开发者只需声明步骤序列。你还可以为每步配置 max_retries(最大重试次数),但顺序本身的严格性是硬约束——换顺序就可能产生错误结果。这里的关键词是“依赖”:顺序编排的价值就在于,它天然保证了上游结果对下游可见。

“先洗菜、再切菜、最后炒菜”——顺序是刻在流程里的。顺序编排的价值就是这种“天然的先后依赖”,对于必须按部就班的业务流程(如订单处理、审核流、计费流程),它是最稳妥的编排方式。

顺序编排:T1→输出 o1T2→输出 o2T3→输出 o3完成\text{顺序编排:}T_1 \xrightarrow{\text{输出 } o_1} T_2 \xrightarrow{\text{输出 } o_2} T_3 \xrightarrow{\text{输出 } o_3} \text{完成}
上一步的输出 o_i 会成为下一步 T_{i+1} 的输入,这就是“结果传递”的流水线。任何一环中断,整条流水线停摆。
  1. 步骤队列按预定义顺序排列工具,形成执行队列。
    queue=[T1,T2,T3,…,Tn]\text{queue} = [T_1, T_2, T_3, \ldots, T_n]
  2. 结果传递每步输出写入上下文,供后续步骤读取。
    ok=Tk(context)o_k = T_k(\text{context})
  3. 失败中止任一步抛错,队列停止向后推进。
    如果 Tk 抛出异常⇒停止调度后续步骤\text{如果 } T_k \text{ 抛出异常} \Rightarrow \text{停止调度后续步骤}

条件分支

餐厅每天会收到两类订单:普通散客单和宴会大单。散客,服务员直接下单即可;宴会,则必须请经理确认菜单、审核报价。处理方式完全不同。这就是“条件分支”——Agent 根据当前上下文(订单金额、用户类型、库存状态)判断走哪条工具路径。它让编排从“僵化的直线”变成了“灵活的路网”。

技术实现上就是一个 if-else(或 switch)逻辑:Agent 执行一个判断工具(或根据上一步返回值),如果满足条件X,则走工具A路径;否则走工具B路径。这要求编排层能够读取上下文中已有变量的值,并据此改变控制流。多数框架对此原生支持,如 LangGraph 的 conditional edges。

条件分支的威力在于“适应性”:一个订单处理 Agent 面对低价商品自动发货、面对高价值商品触发人工审核、面对库存不足的商品自动推荐替代品。每个分支是一段独立的顺序编排子流程,从而组合出了复杂的决策结构。设计时要注意设置完备的“默认路径”——当所有条件都不满足时,走兜底逻辑,避免流程“死在了岔路口”。

“宴会大单走经理审核,散客直接下单”——同一件事,条件不同路径就不同。条件分支让 Agent 不再是“一条道走到黑”,而是在每个关键节点上都像经验丰富的总厨一样:看一眼食材(数据),决定下一步怎么处理。

条件分支:r=Evaluate(x);{r>θ⇒TpathAr≤θ⇒TpathB\text{条件分支:}r = \text{Evaluate}(x); \quad \begin{cases} r > \theta \Rightarrow T_{\text{pathA}} \\ r \leq \theta \Rightarrow T_{\text{pathB}} \end{cases}
条件分支像岔路口的路牌——先看评估值,再决定往哪走。关键是,一定要写"else"默认分支,相当于给每条岔路都画上箭头。
  1. 条件评估读取上下文中关键变量的值。
    r=Read(context.amount)r = \text{Read}(\text{context}.\text{amount})
  2. 阈值比较将评估值与预置阈值比较,决定走向。
    if r>θ then 路径A else 路径B\text{if } r > \theta \text{ then 路径A else 路径B}
  3. 默认兜底所有条件未覆盖时,走预置的默认流程。
    else⇒Tfallback\text{else} \Rightarrow T_{\text{fallback}}

循环与并行

高峰期,后厨同时来了十桌客人点同一道菜。总厨有两种做法:一是一桌一桌慢慢做(串行);二是让三位厨师同时开灶,一锅给一桌(并行)。或者,面对“给1000个客户发通知”的任务,你显然是循环调用“发送通知”工具1000次。循环与并行是编排中处理“批量和并发”的两大法器。

循环迭代(Loop)用于对列表中的每个元素重复执行同一工具。伪代码逻辑是:从上下文中读取一个列表,遍历每个元素作为参数调用工具,把每次结果收集起来。并行执行(Parallel)则用于多个无依赖的工具调用同时进行——例如,Agent 需要同时查询汇率、库存和物流价格,三个调用互不依赖,并行发出可以极大缩短总耗时。在实现层面,并行可以用 Promise.all 或 ThreadPool 完成,并等待所有结果聚合后继续后续流程。

实际编排中,两者常常组合使用:“循环内每轮任务可并行”或“并行批处理每批数据”。比如批量处理100个订单,每批10个并行执行;又比如一个大循环中,每次迭代内部有多个无依赖查询可以并发。这种“嵌套编排”模式极大地提高了吞吐量,但实现复杂度也相应上升——要注意并行数限制、接口限流、结果顺序与合并。

高峰期总厨让三个灶台同时开火,一桌的菜和其他桌的菜同时在做。循环与并行,就是 Agent 版本的“多灶台策略”——逐批处理数据、并发优化耗时。用好这两招,你的 Agent 才能在真实负载下扛住大量调用。

并行加速比:S=T串行T并行=∑i=1ntimax⁡(ti)(假设无限并行资源)\text{并行加速比:} S = \frac{T_{\text{串行}}}{T_{\text{并行}}} = \frac{\sum_{i=1}^n t_i}{\max(t_i)} \quad (\text{假设无限并行资源})
并行不是“免费的午餐”——它用资源换取时间。系统能同时开几个灶,取决于你的“厨房人手”(线程池/连接池)和“炉灶数量”(API并发上限)。
  1. 循环遍历从上下文读取列表数据。
    for x∈items do exec(T,x)\text{for } x \in \text{items} \text{ do } \text{exec}(T, x)
  2. 并行分派无依赖的工具调用同时下发。
    exec(T1)  ∥  exec(T2)  ∥  exec(T3)\text{exec}(T_1)\; \|\; \text{exec}(T_2) \;\|\; \text{exec}(T_3)
  3. 结果聚合收集所有并行任务的返回结果。
    result=[exec(Ti).return]i=1n\text{result} = [\text{exec}(T_i).\text{return}]_{i=1}^n

状态传递

传菜员端着一盘菜从厨房走到前厅,如果没人告诉他这是哪桌点的,菜就送错了。在编排流程中,“状态”就是传菜员手里的托盘——它装着从第一步到当前步的所有关键信息。状态传递机制,决定了下游工具能否拿到上游的正确结果。

技术层面上,编排引擎维护一个“共享上下文”(Shared Context / State Store),本质是一个结构化的键值容器或 Pydantic 模型。每执行完一个工具,就把该工具的返回值写入上下文某些字段;后续工具的参数不再写死,而是从上下文中动态读取。例如:

1. 工具A返回 {"customer_name": "张三", "order_amount": 999}

2. 编排引擎将这个结果写入上下文

3. 工具B的参数声明为 customer_name={context.customer_name}

这就是“参数转换”与“结果聚合”的雏形。更复杂的场景中,工具A的输出字段名和工具B的输入字段名不一致,就需要一个映射层(如 from context.a_id to input.b_id)。LangChain 的 TransformChain、AutoGen 的对话历史机制都属于状态传递的实现。设计状态模型时要保持字段命名清晰、避免过度嵌套,否则后面调试会非常痛苦。

托盘上的每道菜都写着桌号;上下文中的每个字段都标着来源。状态传递是编排的“记忆系统”——没有它,每个工具都是“失忆的厨师”,做完一道菜就忘了之前的所有事。

上下文更新:Ck+1=Ck∪output(Tk)=Ck∪{ok}\text{上下文更新:} C_{k+1} = C_k \cup \text{output}(T_k) = C_k \cup \{o_k\}
每执行一步,全局状态就“长大”一点。下游工具从当前状态中读取自己需要的部分。状态模型稳定了,编排的骨架就稳定了。
  1. 写入状态工具执行完毕后,返回值写入共享上下文。
    C←C∪output(Tk)C \leftarrow C \cup \text{output}(T_k)
  2. 参数映射把上下文字段映射为下一个工具的参数。
    input(Tk+1)=Map(C)\text{input}(T_{k+1}) = \text{Map}(C)
  3. 状态版本化部分框架保留每一步的快照,便于回溯调试。
    Snapshots=[C0,C1,…,Cn]\text{Snapshots} = [C_0, C_1, \ldots, C_n]

依赖与调度

“先有鸡还是先有蛋?”在后厨里这不是哲学问题——你当然先得买到鸡(采购工具),才能杀鸡(处理工具);先有食材,才能做菜。工具之间天然存在“依赖关系”:B 依赖 A 的输出,C 依赖 A 和 B 的输出。编排引擎必须能识别这些依赖,决定先调谁、再调谁、哪些可以同时调。这就是依赖与调度。

技术上,常用“有向无环图”(DAG)来表示工具间的依赖关系。图中每个节点是一个工具调用,每条边表示“必须先执行完上游才能执行下游”。调度器遍历 DAG,找出“入度为0”的节点先执行,执行完毕后“解锁”其下游节点,循环往复。这个算法就是经典的“拓扑排序”。

举个例子:做“番茄炒蛋”需要三个工具——买番茄、买鸡蛋、炒菜。买番茄和买鸡蛋互不依赖,可以并行;炒菜依赖前两者的输出。DAG 结构为 [买番茄, 买鸡蛋] → 炒菜,调度器会先并行执行两个“买”工具,等结果齐了再执行“炒”。在 Agent 编排中,DAG 往往不是静态预定义,而是 Agent 根据用户 request 动态规划生成的(如 ReAct 模式就是推理一步、执行一步)。

后厨挂着一张“工序图”:哪个菜等哪个食材、哪道菜和哪道菜可以同时做。DAG 就是这张图的工程表达。理解了依赖与调度,你就明白了 Agent 怎样在“万事俱备”时才启动某个工具,又怎样让无依赖的活儿齐头并进。

拓扑排序:∀ 边 (u→v), pos(u)<pos(v)\text{拓扑排序:} \forall\ \text{边 }(u \rightarrow v),\ \text{pos}(u) < \text{pos}(v)
拓扑排序就是“给所有工序排一个合法先后顺序”:任何一条依赖边的起点都必须排在终点前面。有环(循环依赖)则整个流程不可调度。
  1. 构建DAG将工具及依赖关系建模为有向图。
    G=(V,E), V=工具集, E=依赖边G = (V, E),\ V=\text{工具集},\ E=\text{依赖边}
  2. 入度为零当前无前置依赖的节点可立即执行。
    in-degree(v)=0⇒v 可执行\text{in-degree}(v) = 0 \Rightarrow v \text{ 可执行}
  3. 逐层解锁执行完的节点释放其下游节点的依赖锁。
    in-degree(w)−=1, ∀(v,w)∈E\text{in-degree}(w) \mathrel{-}= 1,\ \forall (v, w) \in E

异常恢复

再好的总厨也会遇到“食材不新鲜”“灶台起火”“客人临时改口味”——后厨的应急预案决定了一家餐厅能否在意外中存活。工具编排同样如此:API 会超时、第三方服务会挂、数据格式会变。异常恢复机制,就是编排层的“消防预案”,它决定了 Agent 在生产环境中的可靠性。

常见的恢复手段有三板斧:

1. 重试(Retry):对一次性、临时性故障(网络抖动、超时),在等待一小段时间后重新调用同一工具。通常配合指数退避(backoff),避免重试风暴。

2. 降级(Fallback):当主工具不可用时,切换到备用实现(如同一功能的备用 API、缓存值、默认结果)。比如查询天气的主 API 挂了,切到备用的天气 API。

3. 熔断(Circuit Breaker):当某工具连续失败 N 次,标记其为“熔断开路”,短时间内不再调用它,直接走降级逻辑,让服务有时间恢复。

实现这些策略,关键点在于区分“可重试错误”(超时、5xx)与“不可重试错误”(参数错误、鉴权失败)。前者值得重试,后者重试多少次都没用。同时,日志追踪(Logging)贯穿整个异常处理——每步调用的入参、出参、耗时、错误信息都应记录,否则异常发生后你根本无法定位是哪个环节出了岔子。

应急预案的核心不是“避免所有意外”,而是“意外发生后知道做什么”。异常恢复机制让 Agent 从“一摔就碎的花瓶”变成“推不倒的不倒翁”。在生产环境中,这三个字(重试、降级、熔断)能决定你的 Agent 是“能用”还是“好用”。

重试判定:retry(Tk)={True若 error∈E可重试 且次数≤NFalse否则,转降级或中止\text{重试判定:} \text{retry}(T_k) = \begin{cases}\text{True} & \text{若 } error \in E_{\text{可重试}} \text{ 且次数} \le N \\\text{False} & \text{否则,转降级或中止}\end{cases}
重试不是盲目重复——只有“值得再试一次”的错误才重试,并控制次数。重试耗尽后,就启动降级或优雅失败。
  1. 错误分类判断错误类型是否可重试。
    error_type=classify(thrown)\text{error\_type} = \text{classify}(\text{thrown})
  2. 计数与退避控制重试次数,并逐步增加等待间隔。
    wait=base×2attempts\text{wait} = \text{base} \times 2^{\text{attempts}}
  3. 降级切换到备用重试耗尽后选择备用工具路径。
    Tfallback=select_backup(Tk)T_{\text{fallback}} = \text{select\_backup}(T_k)

设计原则

最后,站在总厨的高度,总结一套“后厨管理哲学”。无论编排多复杂,几个设计原则能帮你保持系统健壮、可扩展、可维护:

1. 高内聚、松耦合:每个工具只做一件事、做好一件事;工具之间不直接依赖,只通过上下文通信。这就像切菜的只管切菜,炒菜的只管炒菜,两者通过“切好的菜盘”传递,而不是切菜的跑过去帮炒菜。

2. 幂等设计:同一工具用相同入参调用多次,结果一致(如 cancelOrder 重复执行不会重复扣款)。幂等是安全重试的前提——如果你不确定重复执行会出什么问题,就别轻易重试。

3. 快速失败(Fail Fast):如果前置条件不满足(如缺失关键上下文字段),第一时间抛出异常,而不是带着脏数据往下走。宁可早崩,不崩错地方。

4. 可观测性(Observability):每个工具调用的入参、出参、耗时、错误码全量打日志 + 链路追踪。分布式系统中,没有日志=没有眼睛。

这些原则不是写在 PPT 上的口号,而是每个做 Agent 编排的工程师每天都会踩的坑:不幂等的重试导致客户被扣两次款;隐式上下文耦合导致改了A工具破坏了B工具;不设快速失败导致脏数据一路污染到最终结果。反过来,遵循原则的系统,出问题时你能迅速定位、恢复,而且新工具接入只需实现一个接口,不需要改动整个流程。

总厨的管理哲学浓缩成一句话:每件事有人负责、每份材料有来源、每次意外有预案、每个环节有记录。工具编排的设计原则也是这样——它不会让你的流程“永远不坏”,但能保证你的流程“坏了也能快速修好,而且知道为什么坏”。

可观测性:log⁡(Ck,paramsk,outputk,latencyk),∀k∈[1..n]\text{可观测性:} \log(C_k, \text{params}_k, \text{output}_k, \text{latency}_k), \quad \forall k \in [1..n]
把每一步都记下来——就像后厨的“菜品追溯卡”,出了问题能查到是哪道工序、哪个厨师、用了什么料。
  1. 幂等校验重复执行不产生额外副作用。
    Idempotent(T):T(T(x))=T(x)\text{Idempotent}(T): T(T(x)) = T(x)
  2. 上下文约束工具只能读取自己声明需要的字段。
    input(T)⊆C, 不读无关字段\text{input}(T) \subseteq C,\ \text{不读无关字段}
  3. 全量日志每一步落日志,含入参出参与耗时。
    trace=⋃k{log⁡k}\text{trace} = \bigcup_k \{\log_k\}

AI 应用场景

供应链订单自动化

阶段:工具编排 → 顺序 + 条件分支

解决的问题:传统电商订单处理需要人工在多个系统中切换——ERP 查库存、财务算价格、物流下单,每单耗时 3-5 分钟。AI Agent 通过工具编排把这些系统 API 串成一条流水线:自动查库存 → 算价格 → 走审批(大单人工、小单自动)→ 下物流单,单均耗时降到 20 秒。

具体案例:某 B2B 电商平台接入编排 Agent 后,采购订单从进系统到发货全自动完成。库存不足时自动触发“找替代供应商”工具;金额超阈值自动转人工审批节点(条件分支)。整体订单处理效率提升了 12 倍,差错率降为原来的 1/5。

智能会议纪要自动执行

阶段:工具编排 → 顺序 + 循环 + 并行

解决的问题:传统会议后需要人工整理纪要、分发任务、设置提醒,耗时 1 小时/场。编排 Agent 自动串联语音转文字 → 摘要生成 → 任务提取(循环遍历每条 action item)→ 更新项目看板(并行调用 Jira、飞书、日历 API)。

具体案例:某互联网公司产品团队每周使用该 Agent 处理 40+ 场会议。Agent 识别出“张三负责跟进 PD 对接”,自动在 Jira 创建任务、给张三发飞书通知、在他日历上添加提醒。人工只需在最后确认一次“任务分配无误”,整个会后流程基本不需要人参与。

异常恢复驱动的数据同步服务

阶段:工具编排 → 异常恢复 + 重试/降级

解决的问题:多系统数据同步过程中,第三方 API 不稳定是常态。传统方案中断后需要人工介入恢复。编排 Agent 在同步链路上内置重试(指数退避)、降级(切备库)、熔断(隔离故障数据源)策略,并全程记录同步日志。

具体案例:某金融科技公司的清算系统每天凌晨从交易所拉取行情数据。某天交易所 API 连续超时,Agent 自动重试 3 次不成功后,切换至备用数据源完成拉取,并发送告警通知值班工程师。整个过程无人干预,账务处理准时完成。事后日志完整还原了故障链路,定位到是交易所网关配置变更所致。

Python 演示

python67 行
class Context:    """共享上下文对象,模拟后厨的'传菜托盘'。"""    def __init__(self):        self.store = {}     def set(self, key, value):        self.store[key] = value     def get(self, key, default=None):        return self.store.get(key, default)  def check_stock(ctx):    """工具: 查库存(模拟请求库存服务)"""    stock = 15    ctx.set("stock", stock)    print(f"[库存] 当前库存: {stock}")    return stock  def calc_amount(ctx):    """工具: 计算订单金额(依赖库存步骤写入的上下文)"""    qty = ctx.get("qty", 3)    price = 50    amount = qty * price    ctx.set("amount", amount)    print(f"[价格] 数量={qty} 单价={price} 金额={amount}")    return amount  def place_order(ctx):    """工具: 提交订单(依赖金额字段)"""    amount = ctx.get("amount")    if amount is None:        raise RuntimeError("缺少金额字段,无法下单")    print(f"[订单] 已提交, 金额={amount}")    return "done"  def request_approval(ctx):    """工具: 大单走人工审批"""    amount = ctx.get("amount")    print(f"[审批] 金额={amount} 超阈值,转人工审批")  def orchestrate(ctx, qty):    """编排主控: 顺序执行 + 条件分支"""    ctx.set("qty", qty)    check_stock(ctx)    stock = ctx.get("stock")    if stock < qty:        print("[编排] 库存不足,终止流程")        return    calc_amount(ctx)    amount = ctx.get("amount")    if amount > 500:        request_approval(ctx)      # 条件分支    else:        place_order(ctx)           # 默认路径  if __name__ == "__main__":    demo_ctx = Context()    orchestrate(demo_ctx, qty=3)    print("--- 第二单(大额)---")    demo_ctx2 = Context()    orchestrate(demo_ctx2, qty=100)
目的:用约 70 行代码实现一个带共享上下文的工具编排引擎演示器,展示顺序执行、条件判断和状态传递三大核心概念。
  1. class Context:共享上下文对象,是所有工具之间传递数据的“传菜托盘”。
  2. ctx.set / ctx.get工具通过这两个方法读写上下文,实现状态传递。
  3. def check_stock(ctx)第一个被调用的工具,把库存量写入上下文。
  4. stock = ctx.get("stock")后续逻辑从上下文读取上一步的结果。
  5. if stock < qty:条件分支:库存不足时终止流程,不执行后续步骤。
  6. if amount > 500:条件分支:按金额阈值走审批或自动下单路径。
  7. def orchestrate(ctx, qty)编排主控函数,决定了工具的执行顺序与分支逻辑。
  8. if __name__ == "__main__":可执行入口,分别演示小单自动下单与大单转审批。

常见误区

误区我以为工具编排就是把几个 API 按顺序一个个调用,写个 for 循环挨个执行就完事了,跟写普通脚本没什么区别。

正解顺序调用只是编排最基础的形态。真实编排包含条件分支(异常时走降级路径)、循环(批量处理)、并行(无依赖的调用同时发)、状态管理(上下文传递)和异常恢复(重试与熔断)。就像后厨不是“挨个上菜”,而是要根据订单情况灵活调度多个灶台、多个厨师,还要处理食材短缺等突发状况。

为何常见:因为入门教程演示的第一个例子往往是最简单的顺序调用,新手很容易把“最简单形态”误当成“全部内涵”,忽略了编排作为一个独立领域的能力边界。

误区既然 Agent 有智能,我告诉它“帮我把订单处理了”,它应该自己知道先查库存、再算价格、最后下单,不需要我写流程。

正解完全自由的工具调用(ReAct 模式)确实存在,但不可控——Agent 可能跳过关键步骤、调错参数、不处理错误。工程上的最佳实践是“预定流程 + 局部自主”:用编排定义好骨架(顺序、分支、异常处理),让 Agent 在骨架内做选择(如选择哪个分支),而不是把整个流程都交给自由发挥。就像总厨会把大框架定好,但允许烧烤师傅在烧烤细节上有自己的经验发挥。

为何常见:大模型表现出足够强的“推理能力”,让人误以为它可以像人一样“随机应变”完成所有事。但实际上模型输出的稳定性不足以支撑关键业务流程的可靠性要求。

误区我写编排时把所有中间结果都塞进一个全局字典里,谁需要谁就去取,反正能跑起来。

正解无节制的全局状态是灾难源头——字段命名冲突、版本混乱、并发写入丢数据、排查困难。正确做法是设计结构化上下文(如 Pydantic 模型),明确每个字段的写入方和读取方;并行任务各自维护局部状态,最后再聚合。就像传菜托盘上每道菜都标好了桌号,而不是所有菜混在一个大锅里随便盛。

为何常见:早期脚本编程养成的“全局变量爷”习惯被带到了 Agent 编排中。加上业务流程一急,先跑通再重构的心态让状态管理变成后期最大的技术债。

误区Agents 调用工具报错了就循环重试,重试 10 次总有成功的吧?实在不行再说。

正解盲目重试至少有三个坑:一是对不可重试错误(如参数格式错)重试多少次都是浪费;二是重试可能放大系统压力,让已经过载的服务雪上加霜;三是不幂等的工具被重复执行会产生脏数据(如重复下单扣款)。正确做法是对错误分类,超时、5xx 可重试,且用指数退避;参数错误直接失败返回;同时确保工具幂等。

为何常见:直觉上“错了就再来一次”是人之常情,就像菜没熟再回锅炒一下。但工程上没有“回锅”这么简单——每次重试都要考虑资源消耗、副作用和服务端压力。

误区既然 DAG 能表达依赖关系,我就把所有编排都建模成 DAG,搞张图全搞定。

正解DAG 适合表达“静态依赖 + 并行调度”,但很多场景并不适合:比如需要循环直到满足条件的流程(DAG 没有循环)、需要人机交互暂停审批的流程(图编译后难以挂起恢复)、动态分叉数不确定的流程。实际工程中,顺序、状态机、DAG、甚至事件驱动都是可选范式,要按流程特征选择。就像后厨的运作既有流水线(顺序)、也有看板(状态机)、还有会议调度(事件),不只有一张图。

为何常见:DAG 听起来“高级”且可视化效果也好,容易让工程师过度设计。但正如“手里拿着锤子,看什么都是钉子”,工具的流行遮蔽了需求本来的多样性。

面试题

Q1. 工具编排最基础、最核心的编排模式是哪种?

单选题 · easy

  • A.条件分支
  • B.顺序执行← 正确答案
  • C.循环迭代
  • D.并行执行

答案:B。顺序执行是所有编排的基础形态,它保证了上游结果对下游可见,是其他所有模式(条件、循环、并行)的骨架。就像做菜必须先切后炒,顺序是流程的底线。条件/循环/并行都是在顺序骨架上添加的控制流变体。

Q2. 在编排流程中,“共享上下文”的主要作用是什么?

单选题 · easy

  • A.存储所有工具的源代码
  • B.在不同工具步骤间传递状态与结果← 正确答案
  • C.控制工具的并发数量上限
  • D.存储用户的历史聊天记录

答案:B。共享上下文相当于后厨的传菜托盘,每完成一步就把结果放上去,下游工具从上面取自己需要的输入。它解决的是“步骤间数据如何流动”的问题(状态传递),与源代码管理、并发控制、聊天记录无关。

Q3. 当工具调用抛出“参数格式错误”异常时,正确的处理策略是?

单选题 · easy

  • A.立即重试 5 次直到成功
  • B.指数退避重试 3 次
  • C.直接失败并返回错误信息← 正确答案
  • D.切换到备用 API 再试一次

答案:C。参数错误属于不可重试错误——入参本身有问题,重试不可能成功,就像食材已经坏了,回锅多少次都是坏的。正确做法是快速失败并暴露清晰的错误信息。只有超时、5xx 这类临时性错误才值得重试或降级。

Q4. 两个工具之间没有数据依赖关系,以下哪种编排方式最合理?

单选题 · mid

  • A.强制按顺序执行
  • B.并行执行两个工具← 正确答案
  • C.仅执行其中一个工具
  • D.将两个工具合并为一个工具

答案:B。无依赖则并行是编排优化的基本策略,就像两个互不干扰的灶台可以同时开火。并行能把总耗时从 t1+t2 降为 max(t1,t2)。强制顺序浪费资源,只执行一个或强行合并则丢失了业务正确性或模块清晰度。

Q5. 下面哪种情况最适合使用“条件分支”编排模式?

单选题 · mid

  • A.批量给 1000 个用户发通知
  • B.大额订单转人工审批、小额订单自动通过← 正确答案
  • C.同时查询汇率、库存和物流价格
  • D.先查询库存再生成订单

答案:B。条件分支的核心是“根据上下文状态选择不同路径”。大额/小额走不同处理路径是典型场景。批量发通知属于循环迭代,同时查询多个价格属于并行执行,查库存再下单属于顺序执行。

Q6. 在工具编排中,依赖管理需要解决哪些问题?

多选题 · mid

  • A.判断哪些工具可以并行执行← 正确答案
  • B.确保上游工具执行完毕再执行下游← 正确答案
  • C.避免循环依赖导致死锁← 正确答案
  • D.提升单个工具的内部执行速度

答案:ABC。依赖管理的核心是“谁先谁后、谁可并行、不能有环”三个问题。DAG + 拓扑排序正是为了回答这三个问题。但单个工具本身的性能优化不在编排层的职责范围内,那是工具内部实现该考虑的。

Q7. 关于“幂等设计”在工具编排中的作用,以下说法正确的有?

多选题 · hard

  • A.幂等是安全重试的前提条件← 正确答案
  • B.幂等工具重复执行不会产生额外副作用← 正确答案
  • C.所有工具都必须设计成幂等的
  • D.幂等设计可以完全替代异常处理机制

答案:AB。只有幂等的工具,才能在失败后放心重试(执行两次等于执行一次),否则重试可能重复扣款/重复下单。但并非所有工具都天然幂等(如“发送验证码”重复发送有额外副作用),需要根据业务设计。且幂等只是可靠性的一环,异常处理还包括重试策略、降级、熔断等。

Q8. DAG 拓扑排序的意义在于?

单选题 · hard

  • A.找出执行时间最长的工具
  • B.为所有节点排出一个满足依赖顺序的执行序列← 正确答案
  • C.计算工具调用的总成本
  • D.决定每个工具的重试次数

答案:B。拓扑排序的结果是一个线性序列,满足“每条依赖边的起点都在终点之前”。调度器按这个序列执行,就能保证所有工具都在依赖就绪后才被调用。它不关心耗时、成本或重试,只关心“先后合法性”。

Q9. 设计 Agent 工具编排系统时,正确的设计原则包括?

多选题 · hard

  • A.工具间高内聚、松耦合,只通过上下文通信← 正确答案
  • B.上下文可被任意工具自由读写,保持灵活性
  • C.关键工具调用全量记录日志与耗时,便于追踪← 正确答案
  • D.前置条件不满足时,尽量用默认值继续执行

答案:AC。松耦合 + 可观测性是工程共识。A 对:工具只通过上下文打交道,避免隐式依赖。B 错:自由读写会导致状态混乱与技术债。C 对:全量日志是排查问题的眼睛。D 错:前置条件不满足应该快速失败,而不是带脏数据硬跑,否则错误会被放大到下游。