想象一下,你面前摆着一大箱乐高积木。单看每一块——红的、蓝的、小方块、小齿轮——它们各自都简单得不起眼。但一个乐高高手不会满足于只拼出单独的一块砖,他会研究说明书,把齿轮嵌进轴套,把梁柱咬合在一起,最终搭出一辆能跑的赛车,甚至一座会开合的吊桥。AI Agent 里的工具编排与组合,玩的就是这个把戏。单个工具就像乐高颗粒:查天气、算价格、查地图,功能都很单一。但当你掌握了“怎么拼”的技艺——谁接谁、谁并谁、数据怎么从这块砖流到那块砖——你就能用一堆简单零件,搭出能独立完成复杂任务的超级 Agent。本文没有一行抽象的高深理论,只有一套从“拼装思维”到“工程落地”的完整玩法,就像看着乐高说明书一步步搭建,让你从只会用单个工具的小白,成长为能指挥一群工具协同作战的搭建大师。
对 AI 从业者而言,工具编排是把 Agent 从“玩具”推向“生产力工具”的核心技能。掌握组合模式与并行调度,你就能用低成本原子工具构建高价值复杂系统,在 AI 应用开发中解锁远超单工具的能力上限。
可视化演示
展示四种工具组合模式、数据流转格式转换和并行调用聚合策略。
本节配有交互动效(进入视口后自动加载,约 3KB)。
四种工具组合模式 SEQ顺序、PAR并行、COND条件、PIPE管道 每3秒自动切换模式演示 Composite = f(Tool_A, Tool_B, ...) 原子工具组合成复合能力
组合模式设计
拼乐高时你会发现,高手从来不是随手乱拼。他们嘴里念叨的“先搭底盘、再装传动、最后上外观”,其实就是一套组合模式。工具编排的第一课,就是让你从工具箱里挑出积木后,先想清楚“按什么结构往上搭”。最常见的三种搭法,比乐高说明书还要简单直观。
第一种叫管道流水线,像工厂传送带:A 工具的输出,直接变成 B 工具的输入。比如“查天气 -> 查穿衣建议 -> 生成出行清单”,每个工具只干一件事,干完往传送带上放。第二种叫扇出分发,像一条转接头分出多根电线:拿到一个问题,同时派给多个工具去各查各的。第三种叫汇聚合并,像多条滑梯最后落进同一个沙坑:把几个工具的结果收拢起来,合成一份总报告。
这三种模式的数学本质其实是一种函数复合——把前一个函数的输出作为后一个函数的输入,层层嵌套:
回看那箱乐高:单块积木只是静物,但用管道把它们串成传动轴,用扇出让一个指令同时驱动多个齿轮,用汇聚把零散零件拼成车身——组合模式就是你手里的“说明书”。
- 最内层调用先把原始输入 x 交给第一个工具 f₁ 处理
- 第一层传递把 f₁ 的输出结果整体作为参数,传给第二个工具 f₂
- 层层嵌套每一步都重复“取上层结果 + 传入下一工具”的动作,直到最外层 fₙ
- 整体等价最终 C(x) 的值就是所有工具接力完成后的最终输出,这就是“复合能力”
抽象层次划分
搭过大型乐高套装的人都知道,最顶层的说明书写的是“搭城堡塔尖”,中间的写“搭城门框架”,最底层的才写“把这块 2×4 的砖嵌到那块 2×2 上”。如果所有步骤都只描写到单块积木,说明书会比字典还厚,你也根本找不到塔尖在哪。工具编排同样需要抽象层次。
最底层叫原子工具,对应单块积木——查一次天气、算一个数、搜一段资料,功能单一不可再分。往上一层叫场景服务,相当于“城门框架”——把“查天气、查穿衣、查路线”打包成一个“出行准备”服务,调用方不用关心里面用了几个工具。最顶层是能力总线,像套装里的总装图——所有场景服务统一挂到一条总线上,Agent 只需要向总线发出需求,总线自动路由到合适的服务。
分层让每个层级只关心自己的事:顶层只管“要什么”,中间层只管“拼哪些”,底层只管“怎么拼”。这种分层结构可以用一个简洁的层级公式描述:
乐高说明书之所以能一页只画三步,正因为它做了分层抽象。你的 Agent 也一样——把原子工具封装成场景服务,再挂到能力总线上,整个系统的复杂度才不会被一眼看穿,改造起来才不至于牵一发而动全身。
- 定义层级L 表示我们划分的抽象层数量(原子工具层、场景服务层、能力总线层)
- 第 i 层能力Sᵢ 代表第 i 层能对外提供的全部功能
- 成员关系f(i,j) 表示属于第 i 层的第 j 个工具函数
- 分层意义下层函数被上层封装隐藏,调用者只能看到 Sᵢ 层的“服务接口”
数据流转设计
乐高积木之间靠凸点和凹槽咬合。如果两块砖的凸点长得不一样,它们根本拼不到一起。工具编排里的数据流转,做的就是“凸点对齐”这件事——规定前一个工具吐出来的数据长什么样,后一个工具才接得住。我们把这个约定叫做输入输出契约。
假设天气工具输出的是 {"temperature": 26, "condition": "晴"},而后面的穿衣工具期待的是 {"temp": 26, "weather": "晴"}——字段名对不上,就像凸点尺寸不合,硬拼只会散架。解决的办法是让所有工具共享一份上下文(Context):一个流动的公共数据包。每个工具从中读自己需要的字段,再往里写入自己的输出,像流水线上的零件盒,每个工位往盒里放一个零件,也从中取走需要的零件。
在工程上,这个共享上下文本质上是一个可传递的数据对象,前一个工具的输出经过适配,就能变成后一个工具可消费的输入:
这就是为什么编排高手总在强调“先定契约、再写工具”。就像乐高套装里所有积木必须遵循统一的凸点标准,你的工具只要都遵循同一个 Context 数据契约,任意两块“积木”都能严丝合缝地咬合在一起,不必担心谁配合不上谁。
- 取出上一步输出O_prev 是前一个工具处理完后写入上下文的输出数据
- 适配动作Adapt 函数负责把 O_prev 的字段名、格式转换成下一个工具能理解的样子
- 上下文辅助适配过程中可能还要从 Ctx 里读取额外信息(如用户 ID、会话状态)
- 产出新输入转换后的 I_next 就是下一个工具在调用时的标准入参
格式转换适配
如果说数据流转解决了“积木怎么咬合”,那格式转换适配解决的是“两块积木的凸点对不上时怎么办”。玩乐高时你偶尔会遇到两块来自不同系列的砖,接口形状不一样。这时你不会把砖扔掉,而是翻出一块适配器零件——一头是新式凸点,一头是旧式凹槽,往中间一卡,两块砖就顺利连上了。工具编排中,我们把这个中间零件叫做适配器(Adapter)。
具体怎么做?第一步是统一数据规范:团队约定好一种“标准格式”,比如所有工具的时间都用 ISO 格式、经纬度都用小数。第二步是字段映射:写一份“翻译表”,把工具 A 的 temp 字段翻译成标准格式的 temperature,再把标准格式翻译成工具 B 需要的 temp。第三步是适配器封装:把这些翻译逻辑集中到一个包裹层里,工具本身不用改一行代码,只要给它套上适配器,它就能接入整个编排系统。
这种“不改工具、只加中间层”的思想,在数学上可以看作两套数据结构之间的映射函数。给定输入格式的集合 F_A 和输出需要的格式集合 F_B,适配器就是两个集合之间的双射转换:
有了适配器,你的工具库就像乐高混搭玩法一样——不同系列的积木也能强行合体。今天接入一个第三方天气 API,明天换掉一个老旧的数据库查询工具,只要适配器写得好,整个组合体不会因为换了一块砖而散架。
- 源格式集合F_A 表示工具 A 实际输出的所有可能格式
- 映射函数μ 是手写的字段映射规则,如把 “temp“ 改成 ”temperature”
- 逐一转换F_A 中的每个 f_a 都经过 μ 转换一次,得到新格式
- 目标格式集合转换结果的全体构成 F_B,即工具 B 可接受的输入格式集合
并行调用策略
搭乐高时,如果城堡的四面墙互不影响,你不会傻等一面墙完工再去搭下一面。你会同时动手,四面墙一起往上长,最后再合拢。Agent 编排也如此:当多个工具之间没有依赖关系时,串行执行是浪费时间,并行调用才是高手的节奏。这就是乐高玩法的第三条心法。
并行调用的核心操作叫扇出(Fan-out):拿到用户请求后,编排层把它同时广播给多个工具。比如用户问“帮我安排这周末的短途游”,你可以同时调用天气查询工具、火车票查询工具、景点信息工具——三个工具谁也不依赖谁,谁先出结果都不影响别人。但并行不是无限开的,还得配上超时与降级:某个工具卡住了,超过 2 秒没返回,就先跳过它,不让一颗老鼠屎坏了一锅汤。同时要设置并发上限,就像家里电闸不能同时开所有大功率电器,防止下游 API 被瞬间请求冲垮。
并行带来的性能提升不是线性的,因为每多一个并行任务,通讯和调度都会产生额外开销。Amdahl 定律精确描述了这种加速比上限:
这就像一个乐高工厂:四条流水线同时开工,比一条流水线串行快得多,但每条流水线之间的调度、传送带衔接始终有固定耗时。所以聪明的搭建者会先评估“哪些墙能同时砌”,把并行路数控制到恰到好处——既充分利用了时间,又不至于让调度开销吃掉所有收益。
- 串行部分占比(1-p) 表示任务中必须按顺序执行的比例,这部分永远快不了
- 并行部分均摊p/n 表示可并行的那部分被 n 路分摊后所花的时间
- 总耗时二者之和是并行优化后的总耗时
- 加速比用原始耗时 1 除以新耗时,得到加速比 S(n)
结果聚合策略
四面墙分别搭好后,最激动人心的时刻到了——把墙体合拢,城堡封顶。工具编排中的结果聚合,就是把这些从不同工具返回的零散结果,拼成一个对用户有用的完整答案。
聚合有三大策略。全量汇合最稳妥:等所有并行工具全部返回,再按模板统一整理。比如旅行规划,等天气、车票、景点三个结果都到齐,一起生成一份完整行程单。竞速取优是急性子首选:多个工具做同样的事(比如两个翻译引擎),谁先返回一个高质量结果,就用谁的结果,其余直接丢弃——像比赛跑步,只认冠军。增量流式则是“边拼边展示”:工具返回一部分数据,就立刻把这一部分展示给用户。比如搜索推荐,先展示第一批结果,后续结果慢慢补充,用户不用干等。
把聚合策略写成公式,就是一个把 k 个并行结果合并成最终响应的归并函数:
就像拼完的乐高城堡,你不会把一堆散落的塔身、墙壁、城门直接递给朋友,而会先统一组装、检查咬合,再交付一个完整成品。结果聚合就是那个“最后一步合拢”的动作——它决定了你的 Agent 给用户的是“零件堆”还是“完整城堡”。
- 收集结果集r₁ 到 rₖ 是 k 个并行工具各自返回的结果
- 归并算子⊕ 代表具体的合并规则,不同策略对应不同算子
- 两两归并Aggregate 函数内部反复执行归并操作,把所有结果合成一个
- 最终输出归并后的 R 作为单一完整答案返回给用户
测试调试方法
乐高拼完不是终点,还要摇一摇、推一推,看城堡会不会散架。工具编排同样如此:多个工具组合之后,问题往往不在单个工具内部,而在工具之间的衔接处。于是,前三节讲的组合模式、数据流转、格式转换,全部需要一套专门的测试与调试方法来守门。
第一个武器叫组合测试矩阵:把每两个可能相邻的工具作为一对,逐一测试它们的连接是否顺畅。就像把每两块乐高都试着拼一下,用力拉扯看会不会脱开。第二个武器叫链路日志:给每一次编排执行分配一个唯一的 trace_id,从用户请求进来,到每个工具执行完毕,每一步的输入输出都记录在案。出了问题,顺着日志链路走一遍,一眼就能看出是哪块积木在哪个环节“咔哒”一声脱了钩。第三个武器是可观测性监控:不只测一次,而是持续观察线上运行的组合体,统计每个组合结点的耗时、失败率、数据流量,形成一张实时仪表盘。
组合测试矩阵里,每个“连接对”的通过与否可以形式化为一个二值矩阵 M。行代表上游工具 i,列代表下游工具 j,M(i,j)=1 表示这个组合通过测试:
拼乐高时,我们最担心的不是某块砖坏了,而是两块砖接缝处咬不紧。工具编排的测试调试,本质上就是在逐条检查这些“接缝”。当你把组合测试矩阵填满 1、链路日志随时可查、仪表盘一目了然,你就拥有了一个像搭积木一样稳当又灵活的 Agent 系统——搭得快,散不了架。
- 矩阵定义M 是一个 k×k 的矩阵,k 是工具总数
- 组合测试对每一对工具 (T_i, T_j) 执行一次端到端调用测试
- 测试判定测试通过记 1,失败记 0
- 查漏补缺矩阵中所有为 0 的位置,都代表一个“咬合不良”的接口,需要修适配器或调数据映射
AI 应用场景
旅行规划智能助手
阶段:工具编排与组合 — 多工具并行调度与结果聚合
解决的问题:传统旅行规划需要用户分别打开天气 App、12306、地图软件,手动拼凑信息。Agent 通过编排,把多个原子工具组合成“一键行程规划”服务,无需切换应用。
具体案例:用户对 Agent 说“帮我规划周六从上海去杭州的一日游”。编排层先在能力总线上定位“出行规划”场景服务,该服务内部做扇出——并行调用天气查询(杭州周六天气)、火车票查询(沪杭高铁班次)、景点信息(西湖周边景点与开放时间)。三个工具互不依赖,并行执行。等待全部返回后,聚合层执行全量汇合,生成“9:00 高铁 + 带伞(多云)+ 西湖北线步行路线”的完整行程单,一次性交付给用户。
企业客服工单自动分类
阶段:工具编排与组合 — 管道流水线与适配器
解决的问题:传统客服系统收到用户反馈后,靠人工阅读并分配到对应部门,效率低且易出错。Agent 将“分类识别、情感分析、部门映射”三个工具串成管道流水线,自动完成工单流转。
具体案例:用户提交工单“我的订单三天没发货,很生气”。Agent 调用情感分析工具识别出“愤怒”情绪,再调用分类工具识别出“物流延迟”主题,最后通过字段映射适配器,把分类结果翻译成公司内部部门的工单格式,自动派发到物流处理组并附上紧急标记。全程无需人工介入,工单处理时效从 2 小时缩短到 1 分钟。
Python 演示
import timefrom concurrent.futures import ThreadPoolExecutor, as_completedfrom dataclasses import dataclass, fieldfrom typing import Callable, Dict, List, Any @dataclassclass ToolResult: """原子工具执行后写入上下文的结果包裹""" data: Dict[str, Any] latency_ms: float = 0.0 def tool(name: str): """把普通函数注册为一个原子工具,附带工具元信息""" def decorator(fn: Callable) -> Callable: fn.tool_name = name # type: ignore return fn return decorator class ToolPipeline: """管道流水线:按顺序执行一串工具,前一个输出喂给下一个""" def __init__(self, steps: List[Callable]): self.steps_list = steps def run(self, ctx: Dict[str, Any]) -> Dict[str, Any]: for step_fn in self.steps_list: result = step_fn(ctx) ctx.update(result.data) return ctx class FanOutGate: """扇出分发:把同一份上下文并行广播给多个独立工具""" def __init__(self, branches: List[Callable], timeout: float = 3.0): self.branch_list = branches self.timeout = timeout def run(self, ctx: Dict[str, Any]) -> List[Dict[str, Any]]: results = [] with ThreadPoolExecutor(max_workers=len(self.branch_list)) as pool: futures = [pool.submit(fn, ctx) for fn in self.branch_list] for future in as_completed(futures, timeout=self.timeout): results.append(future.result().data) return results class Aggregator: """汇聚合并:把多个并行结果按自定义规则合并成一份输出""" def __init__(self, merge_fn: Callable[[List[Dict[str, Any]]], Dict[str, Any]]): self.merge_fn = merge_fn def run(self, results: List[Dict[str, Any]]) -> Dict[str, Any]: return self.merge_fn(results) @tool("查天气")def fetch_weather(ctx: Dict[str, Any]) -> ToolResult: time.sleep(0.2) return ToolResult(data={"weather": "多云", "temp": 24}) @tool("查高铁")def fetch_train(ctx: Dict[str, Any]) -> ToolResult: time.sleep(0.3) return ToolResult(data={"train": "G7301", "depart": "08:30"}) @tool("查景点")def fetch_spot(ctx: Dict[str, Any]) -> ToolResult: time.sleep(0.1) return ToolResult(data={"spot": "西湖", "open": "06:30"}) def merge_plan(parts: List[Dict[str, Any]]) -> Dict[str, Any]: """把三个并行结果合并成一份完整行程单""" plan = {} for part in parts: plan.update(part) plan["summary"] = "08:30 高铁出发,西湖游览,注意带伞" return plan def main(): # 1. 管道流水线:先查天气,再根据天气生成穿衣建议 def dress_advice(ctx: Dict[str, Any]) -> ToolResult: if "晴" in ctx.get("weather", ""): advice = "短袖" else: advice = "外套" return ToolResult(data={"advice": advice}) pipeline = ToolPipeline([fetch_weather, dress_advice]) ctx_out = pipeline.run({}) print("管道输出:", ctx_out) # 2. 扇出 + 聚合:并行三个查询,再合拢成出行计划 gate = FanOutGate([fetch_weather, fetch_train, fetch_spot]) parallel_data = gate.run({}) print("并行结果:", parallel_data) agg = Aggregator(merge_plan) final_plan = agg.run(parallel_data) print("聚合计划:", final_plan) if __name__ == "__main__": main()
ToolPipeline 把工具串成顺序流水线,FanOutGate 用线程池并行调用无依赖工具,Aggregator 将分散结果合并成最终答案。def tool(name: str)一个装饰器,给普通函数打上“工具”标签,相当于给乐高积木贴上型号标签,方便编排层识别。self.steps_list = steps流水线的步骤清单,记录先拼哪块、后拼哪块,对应乐高说明书的步骤顺序。ctx.update(result.data)把每个工具的输出去写入共享上下文,后面的工具就能读到前面工具的结果,如同料盒在流水线上传递。ThreadPoolExecutor(max_workers=len(self.branch_list))创建与分支数等量的线程池,实现扇出并行,相当于多条流水线同时开工。as_completed(futures, timeout=self.timeout)等待所有并行任务完成,超时则丢弃未完成结果,对应超时降级策略。plan.update(part)将多个并行返回的字典合并成一个大的字典,实现全量汇合聚合。merge_plan用户自定义的聚合规则函数,把“拼好的零件”组装成完整城堡(出行计划)。pipeline.run({})管道模式的实际调用入口,注意传入的是一个空字典——所有中间数据都在工具之间自动流转。Aggregator(merge_plan)把聚合规则封装成对象,方便复用不同策略(全量、竞速、流式)。parallel_data扇出后收集的所有结果列表,它是聚合器的输入,也是“组装台”上的零件。
常见误区
误区“我只要给 Agent 注册几十个工具,它就能自动变得很厉害。工具就是越多越好,编排只是在浪费性能。”
正解工具数量不等于能力。就像你把一千块乐高堆在一起,不会自动变成城堡——没有合理的组合模式和抽象层次,工具之间各自为政,甚至互相冲突。真正决定上限的是编排质量:三个设计良好的工具组合,胜过三十个随手注册的工具散装堆积。编排层还要考虑工具间的数据契约是否对齐、调用顺序是否合理、出错时能否降级,这些才是“乐高说明书”的价值所在。
为何常见:大多数人第一次接触 Agent 工具时,看到“注册工具”就能让模型调用外部 API,误以为工具是能力的直接来源,忽略了工具之间的组织方式才是复杂能力的放大器。
误区“既然并行能同时执行多个工具,那我把所有工具都改成并行调用,响应一定变快。”
正解Amdahl 定律揭示了残酷的真相:如果任务中存在串行依赖(比如必须先查天气才能决定穿衣),这部分时间不会因为并行而减少。而且并行本身有额外开销(线程调度、结果等待、上下文切换),开太多路反而拖慢整体。正确的做法是先画出任务依赖图,识别哪些工具真正互相独立,只有无依赖的子任务才配得上并行,同时要给每条并行路径设置超时,避免最慢的一个工具拖住全局。
为何常见:直觉上“同时做多件事一定更快”很容易被接受,但人们容易忽略编排中的串行依赖和调度成本,把“并行”本身当成了目的而非手段。
误区“接不上线的工具都是坏工具,直接改源码让它输出正确的格式就完事了。”
正解改工具源码是高风险动作——那个工具可能是第三方 API,或者被其他系统共用。更专业的做法是引入适配器层:不改工具本身,只写一层格式转换代码,把 A 的输出翻译成 B 能吃的输入。这样既保护了工具的可复用性,也让变换集中在适配器中,后续排查问题也更简单。这就像给不同系列的乐高积木增加转换插头,而不是去锯掉积木的凸点。
为何常见:初学编排时,面对“数据对接不上”的第一反应是修改数据来源方的代码,因为那看起来最直接。但缺乏“接口隔离”思维,导致工具库越改越乱,复用性越来越差。
误区“单个工具都测过了,组合起来应该没问题。跑通一次就算测试完毕,可以上线了。”
正解组合的 Bug 恰恰出现在单个工具测不到的地方:A 的输出到 B 的输入是否匹配、两个工具的并发是否死锁、一个工具超时后其他工具是否一致降级。所以专有“组合测试矩阵”要覆盖所有工具两两衔接的场景,线上还要靠链路日志定位问题。就像乐高城堡拼好后要反复摇晃测试,而不是拼完拍张照就收工。
为何常见:人们习惯于用“组件都正常 → 系统正常”的思维,忽略了组合后的涌现效应——衔接处往往才藏着真正的风险。
误区“把多个工具的结果拼成一个长文本返回给用户,就是结果聚合了,何必想什么策略。”
正解无脑拼接只是低级的字符串连接,真正的聚合要处理冲突(两个工具对同一问题的回答矛盾)、去重(多个工具返回了重复信息)、排序(哪个结果在前)、择优(竞速模式下取质量最高的结果)。不同的业务场景需要选择全量、竞速、流式三种策略之一,聚合器内部还需要定义冲突解决规则。这就像组装乐高城堡要先看说明书判断哪块砖在哪个位置,而不是把所有零件倒进一个桶里就算完。
为何常见:聚合看起来简单,容易让人觉得“合并成一坨输出”就是聚合,忽略了它需要设计归并规则这一核心工程问题。
面试题
Q1. 工具编排中,以下哪种模式最符合“把 A 工具的输出直接作为 B 工具输入”的场景?
- A.管道流水线← 正确答案
- B.扇出分发
- C.汇聚合并
- D.竞速取优
答案:A。管道流水线正是“前一个工具结果喂给后一个工具”的串行模式,像工厂传送带一样逐级传递。扇出分发是同时分发到多个工具,汇聚合并是把多个结果合并,竞速取优则在多个同质结果中选最快者,均与“直接传递”不符。
Q2. Amdahl 定律中,假设任务可并行比例为 p=0.8,并行路数 n=4,则加速比约为多少?
- A.1.33
- B.2.50← 正确答案
- C.3.33
- D.4.00
答案:B。代入公式 S(n)=1/((1-p)+p/n),p=0.8、n=4 时 S=1/(0.2+0.2)=2.5。由于存在 20% 的串行部分,即使并行路数很大,加速比也会被锁死在上限 5。所以并行不是万能的,识别串行依赖比盲目开大并行路数更重要。
Q3. 当两个工具的数据字段名不一致(如一边叫 temp、一边叫 temperature),最合理的处理方式是?
- A.修改其中一个工具的源码
- B.写一个适配器做字段映射← 正确答案
- C.丢弃不匹配的数据
- D.让两个工具各自返回两份字段
答案:B。适配器模式通过中间转换层解决格式不一致问题,不改动工具本身、保持复用性。修改源码风险高且影响其他调用方,丢弃数据会造成信息缺失,返回两份字段则破坏数据契约。适配器如同乐高转换件,让“凸点不同”的积木也能咬合。
Q4. 编排 Agent 中,“共享上下文(Context)”的主要作用是什么?
- A.保存每个工具的可执行代码
- B.在工具之间传递数据和中间状态← 正确答案
- C.存储用户的历史聊天记录
- D.加速大模型的推理速度
答案:B。共享上下文是一个流动的数据包,每个工具从中读取自己需要的输入、写入自己的输出,实现了工具间解耦。它保存的是当次执行的中间数据而非工具代码;聊天记录只是其中一种可能的字段;加速模型推理与上下文无关。
Q5. 以下哪些属于工具编排中的组合模式?
- A.管道流水线← 正确答案
- B.扇出分发← 正确答案
- C.汇聚合并← 正确答案
- D.模型微调
答案:ABC。管道(串行传递)、扇出(并行分发)、汇聚(结果合并)是三大核心编排模式。模型微调是训练层面的技术,与工具调用组合无关,属于干扰项。三种模式可以嵌套使用,如先扇出再汇聚、再进入下一段管道。
Q6. 结果聚合策略中“竞速取优”最适合以下哪个场景?
- A.需要所有工具结果才能生成完整报告
- B.多个同质工具返回同一类信息,取最快结果← 正确答案
- C.需要把结果按顺序拼成长文本
- D.工具返回结果太慢需要等待重试
答案:B。竞速取优用于多个工具做同样事情的场景,谁快用谁,其余丢弃——如同时调用两个翻译引擎,取先返回的高质量结果。需要完整报告应选全量汇合,顺序拼接是增量流式,等待重试则是超时处理而非聚合策略。
Q7. 组合测试矩阵中,M(i,j)=0 通常表示什么?
- A.工具 i 运行正常
- B.工具 j 运行正常
- C.工具 i 接工具 j 的组合测试失败← 正确答案
- D.工具 i 和 j 不相关
答案:C。矩阵第 i 行第 j 列表示上游工具 i 与下游工具 j 的衔接测试结果,0 代表这条连接失败。注意它测的是“组合连接”而非单个工具本身:两个工具各自正常,接起来却可能因字段不匹配而失败,这正是组合测试的意义。
Q8. 关于工具编排的抽象层次,下列说法正确的有哪些?
- A.原子工具是最底层、不可再分的能力单位← 正确答案
- B.场景服务是对多个原子工具的封装← 正确答案
- C.能力总线统一路由所有场景服务← 正确答案
- D.层次越多系统响应一定越快
答案:ABC。原子工具(如单查天气)、场景服务(如打包成出行准备)、能力总线(统一入口路由)是标准的三层抽象。层次多带来的灵活度提升需要与复杂度平衡,且每层都有额外开销,所以 “层次越多越快” 是错误的。
Q9. 在 Python 的 FanOutGate 实现中,使用 ThreadPoolExecutor 而不是直接写循环串行调用,主要目的是什么?
- A.减少代码行数
- B.并发执行无依赖的工具以缩短总耗时← 正确答案
- C.避免 import 第三方库
- D.让代码兼容 Python 2
答案:B。线程池让多个无依赖工具同时执行,总耗时约等于最慢的一个工具,而非所有工具耗时之和。减少代码行数只是副产品,import 是标准库不需要避免,Python 2 早已停止维护。这正是扇出模式下并发的工程落地方式。
Q10. 以下哪些做法能有效提升工具编排系统的可调试性?
- A.为每次编排执行分配唯一 trace_id← 正确答案
- B.记录每个工具输入输出的完整日志← 正确答案
- C.只记录最终返回结果,忽略中间步骤
- D.对工具两两衔接做组合测试← 正确答案
答案:ABD。链路日志(trace_id + 每步输入输出)让问题定位到具体工具和接口,组合测试矩阵把衔接问题拦截在测试期,三者共同构成可观测性基础。只记最终结果会让中间环节变成“黑盒”,排障无异于大海捞针,故不选。