拓冰建站拓冰建站
首页 / 资讯中心 / 正文

多Agent编排中的行为型设计模式:Subagent即Tool的工程实践

这篇“PIG 设计模式二”我打算直接切入一个我这段时间最想聊的认知转变现在主流的多Agent设计基本都是主从模式而主从模式在工程实现上本质上就是把Subagent当成另一种Tool来调用。这个观念一旦建立命令模式、策略模式、观察者模式、模板方法模式、状态模式这些经典设计模式就全部找到了它们在Agent编排系统里的准确位置。先交代一下背景。PIG是我自己持续在推的一个多Agent编排框架核心目标很简单让多个大模型Agent在一条可控的流水线里协同工作而不是各聊各的。上一篇我写了创建型和结构型模式在这套系统里的应用这篇集中讲行为型模式也就是Agent之间怎么派活、怎么通信、怎么切换状态、怎么处理异常。如果你正在做多Agent方向的东西或者正在准备设计模式相关的课程项目和期末作业这篇里的落地方案可以直接参考。1. 从上一篇说起PIG到底在解决什么问题1.1 PIG是什么一个为多Agent协作而生的编排内核PIG这个项目说白了就是把大模型从“单点对话工具”变成“可编排的任务执行单元”。它做了两件事第一把每个模型交互封装成标准化的Agent实例第二把多个Agent之间的调用关系编排成一条可控的流水线。市面上类似的东西不少但PIG的设计原则是轻量核心调度逻辑全部手写没有引入重型的RPC框架这反而给设计模式留了大量实用场景。上篇已经聊过两件事。在实例构建层我们用工厂模式统一创建各类Agent避免到处new导致配置散落用单例模式持有模型网关和配置中心保证全局只有一份共享状态。在结构层用外观模式屏蔽不同模型厂商的接口差异用装饰器给Agent动态附加日志、限流、审计这些横切能力用组合模式把多个流程固定的子Agent拼装成复合Agent。那“设计模式二”讲什么讲行为。上篇搭的是骨架这篇给骨架注入“协作行为”。到这个层面问题不再是“对象怎么创建”或者“类怎么组合”而是“一个Agent收到任务后它下一步该怎么办”“多个Agent之间的消息怎么传递”“某个Agent挂了整个流程要不要回滚”。这些问题几乎都能在GoF的经典行为型模式里找到对应的成熟答案。1.2 为什么多Agent系统的复杂度集中在“行为”层多Agent系统的静态结构其实很简单一个主控几个工作节点画出来不超过七八个方框。但一旦跑起来行为层面的复杂度会迅速爆炸主Agent可能要同时跟踪多个子任务的进度要处理子任务之间的依赖关系要根据中间结果决定是否追加任务还要在失败时决定是重试、降级还是终止。这些都不是类结构能解决的是典型的运行时行为问题。如果这些协作逻辑全靠if-else和裸函数调用堆出来初期跑通没问题迭代几轮之后基本就没人敢动了。设计模式解决的核心问题恰恰是“把变化的部分和不变的部分分开”。状态怎么流转经常变但“流转必须合法”这一点不变所以用状态模式任务路由规则经常变但“任务必须路由给某个执行者”这一点不变所以用策略模式。举一个PIG里的实际例子。早期处理用户请求解析时主Agent里塞了一大段关键词匹配和规则判断后来发现不同渠道来的请求格式差异特别大这个逻辑怎么改都补不齐。最后重构时我把整个解析流程改成了“模板方法策略”的组合主流程固定为“解析-校验-执行-回执”四步具体每一步怎么做由各个渠道的Agent各自实现。改完之后新增一个接入渠道从原来的改半天变成十几分钟而且每个渠道的逻辑互相隔离互不影响。这就是行为型模式在真实系统里的价值不是概念好看是真的能降低维护成本。2. 核心思想主从模式下Subagent就是另一种Tool2.1 为什么主从模式成了多Agent设计的主流现在聊多Agent最常听到的架构就是主从模式。一个主Agent负责理解用户意图、拆解任务、调度执行底下挂一群各有专长的Subagent比如代码生成、SQL查询、文档总结、敏感信息检测。为什么不是让所有Agent平级通信互相自由对话因为大模型的单次上下文窗口再大也有限让一个Agent同时做拆解、执行、汇总三件事提示词会互相干扰结果就是什么都干不精。主从模式的核心思路是把“决策”和“执行”拆开。主Agent拥有全局视角手里攥着完整的用户意图和任务清单Subagent只负责自己那一亩三分地不需要知道整个任务的全貌。这样做的好处是任务边界清晰谁出错就找谁而且全局编排逻辑集中在一个节点上人工排查问题时能顺着一条线追下去。相比之下对等模式所有Agent平级、通过消息互相协作虽然看起来更灵活但工程治理非常痛苦。消息拓扑一旦复杂起来很难回答“当前到底是谁在主导这条任务链”这类问题定位故障和做审计都难。所以我个人的倾向很明确如果业务场景是“主从分工明确、结果可验收”的类型优先用主从模式如果是“需要开放式讨论、共同决策”的场景主从模式不适用得另想方案但那个不在本文讨论范围。2.2 Subagent-as-Tool一个值得反复咀嚼的认知转变主从模式听起来不复杂真正难的是代码层怎么设计。我做了两轮重构之后最大的体会是必须把Subagent调用和Tool调用统一成同一种东西。先看传统意义上的Tool是什么。在Agent系统里Tool就是外部能力搜索引擎、计算器、数据库查询接口都算。它们通常有统一的描述名字、功能说明、参数结构、返回值结构。模型需要调用工具时根据工具描述生成一份JSON参数框架层负责执行并返回结构化结果。Subagent本质上也是这么个东西。它有一个身份描述说明自己擅长什么有输入输出契约规定喂给它什么、返回什么。它内部怎么调用模型、怎么思考调用方根本不关心。所以在PIG里Subagent在调度层的注册方式和普通Tool完全一样只是这个Tool的执行逻辑比较特殊内部会再走一次“请求大模型-解析输出”的流程。打个比方传统开发里你给外部系统写一个SDK调用方不关心SDK内部是同步实现还是异步实现。Subagent就是这样一个由大模型驱动、内部逻辑更复杂的“SDK方法”。主Agent不需要区分“我在调一个函数”还是“我在和一个Agent对话”反正都是同一套请求-响应模型。2.3 统一调用接口带来的三个直接好处第一调度逻辑大幅简化。主Agent不用为“Tool调用”和“Subagent调用”各维护一套代码超时、重试、熔断这些机制可以复用同一套框架。第二可观测性统一。以前调Subagent时两边消息格式经常不一致日志拼不到一条链路上统一成Tool调用后从主Agent到Subagent再到模型层每一跳都能记录通用字段问题排查时可以按traceId一路拉到底。第三成本和资源控制更细粒度。每次Subagent调用都变成一个可计量的“函数执行”可以精确统计调用次数、token消耗和执行耗时做预算控制、限流和按量计费都容易。这里有一个必须提醒的边界Subagent-as-Tool不是万能的。它适合“主从明确、任务边界清晰、结果可验收”的场景。如果需求是让多个Agent自由讨论、互相说服、共同形成决策那它们不是Tool关系而是Peer关系。PIG里遇到这种场景会走另一套基于消息队列的协作机制而不是这套调度框架。我的经验是强上主从模式反而会把开放式讨论压制成“汇报式流程”丢失掉讨论本身的价值。注意判断一个Subagent能不能被当作Tool最简单的方法是看“调用方是否需要知道它的内部推理过程”。如果只需要最终结果放心封装成Tool如果必须参与它的思考过程那就是另一个故事了。3. 行为型设计模式逐个拆解PIG里的落地位置3.1 命令模式把“派活”变成可记录、可重放的对象PIG里最基础的行为单元是“命令”。主Agent对Subagent的每一次请求都被封装成一个命令对象。命令对象里包含任务ID、目标Subagent的标识、输入参数、期望返回格式还会附带一部分执行上下文快照。为什么一定要封装成对象而不是直接调方法因为直接调用没法实现统一重试、审计追溯和任务重放。封装成命令对象后每个任务都是一条结构化数据可以推进队列可以持久化到数据库系统崩溃后还能重新加载未完成任务继续执行。PIG里所有命令都实现同一个接口核心方法就两个execute(context)和undo(context)。对于Agent任务来说真正的undo很难实现因为大模型执行产生的结果很难回滚但我们强制要求每个命令至少支持幂等也就是同一个命令执行两次业务结果不会翻倍。这是一个很实用的约束。命令对象还带来一个附带好处测试变得好写了。以前测试一个Agent任务要手动构造各种上下文现在直接构造命令对象、扔给Runner执行、断言返回结果。PIG里的ReActCommand、TranslateCommand、RiskReviewCommand都是这样组织的每个命令类都很轻只负责定义任务参数和执行入口。3.2 策略模式路由策略从“写死”变成“可插拔”主Agent拆完子任务后要决定交给哪个Subagent。这个“交给谁”的决策看似简单实际包含多种考量维度按能力匹配、按成本最低、按响应速度最快、按当前负载均衡。如果把这些策略写死在主Agent的代码里每加一种策略都要改主逻辑而且主Agent的提示词会被策略细节淹没。策略模式把“路由决策”抽象成一个独立接口PIG里叫TaskRouter核心方法就一个choose(taskInfo, candidateAgents)。主Agent只依赖这个接口具体实现可以随时替换。目前内置了三种策略能力优先按Subagent描述和任务要求做向量匹配、成本优先选外部API单价最低的Subagent、自适应根据每个Subagent当前队列长度动态分配。这个模式带来的直接好处是A/B测试策略变得非常容易。比如今天想试试“成本优先”会不会影响整体效果只需要在配置中心把路由策略从capability切换到cost几秒钟生效完全不用动主Agent代码。这套设计对线上灰度特别有价值。3.3 观察者模式状态变了通知所有关心的人多Agent系统里状态同步是个大问题。主Agent需要知道每个Subagent当前是忙还是闲监控面板需要实时展示每个Agent的运行状态日志系统需要记录每一个状态变更事件的完整上下文。如果让主Agent轮询每个Subagent的状态不仅浪费资源而且轮询拿到的只是某个时间点的快照丢失了事件发生时的上下文细节。PIG里用一个轻量级事件总线来解决这个问题本质就是观察者模式的实现。所有Agent的状态变化、命令执行成功、命令执行失败都会作为事件发布到总线上。订阅方各自注册感兴趣的事件类型监控面板订阅状态变更、日志系统订阅全量事件、主Agent的调度模块订阅Subagent“空闲”事件以便安排下一个任务。事件总线本身不感知业务逻辑只管发布和订阅这样各个模块之间完全解耦。这套机制还有一个意外的好处在做Subagent-as-Tool统一之后Tool调用的生命周期事件和Agent状态事件可以合并成一条事件流。线上排查问题时从一条时间线能同时看到“主Agent调用了哪个Tool”“这个Tool内部对应的Subagent处于什么状态”“最后返回了什么结果”可观测性比之前分开记录好了不止一个量级。3.4 模板方法模式把“任务处理骨架”固化在父类里不同Subagent的业务逻辑千差万别但任务处理的大流程几乎一样接收输入、校验参数、调用底层模型、解析输出、格式化返回。如果每个Agent都自己写一遍这套流程不仅代码重复更麻烦的是异常处理规则会越来越不一致。比如有的Agent失败后重试三次有的什么都不做出了线上问题特别难统一治理。模板方法模式把这个流程固定在抽象父类TaskWorker里子类只实现几个钩子方法parseInput负责把原始请求解析成内部参数doExecute负责真正的业务逻辑formatOutput负责把模型输出转换成统一的结果结构。框架层的错误处理、重试策略、日志记录全部在父类里收口。用了这个模式之后PIG里新增一个Subagent的成本明显降低。开发者只需要关心自己的业务逻辑不需要理解调度框架的细节。而且因为流程骨架是统一的后续给所有Agent加“输出安全检查”或者“敏感词过滤”时只需要在父类里加一步所有Agent同时生效不会出现漏改的情况。3.5 状态模式Agent生命周期告别switch-case地狱每个Subagent都有自己的生命周期状态空闲、忙碌、异常、已完成。早期PIG用枚举加switch分支管理状态状态一多就开始出问题。最典型的一个bug是某个Subagent正在执行任务时因为重复调度又收到了一个新任务代码里没有拦截这种非法状态迁移导致同一个Agent同时跑两个任务上下文全乱了。状态模式把每一种状态封装成独立对象状态转移规则由状态机统一管理。PIG里用一张状态转移表约束合法迁移比如只有Idle状态才能进入BusyBusy状态下接收新任务会直接拒绝并返回错误原因只有执行结束后才能从Busy进入Done或Error。所有代码不能再直接对状态字段赋值必须调用统一的状态迁移入口。这个约束在编译期看不出效果但运行期会把大量非法操作拦截在源头。状态对象本身还承担了入口和出口动作。比如进入Busy状态时自动触发计时和事件发布离开Busy状态时自动释放资源。这样状态切换的副作用都集中管理不会散落在业务代码里。对我们后续在监控面板上画状态流图也很有帮助因为状态转移都是合法、可枚举的图就是状态转移表的可视化。下表整理一下这几个模式在PIG里的汇总定位设计模式解决的核心问题PIG中的落地位置命令模式任务调用的可记录、可重试、可追溯CommandRunner组件与AgentCommand接口策略模式任务路由规则经常变化需要可插拔TaskRouter接口及多种路由实现观察者模式状态变化需要通知多个订阅方避免轮询EventBus事件总线模板方法模式多个Agent流程重复治理规则难以统一抽象父类TaskWorker状态模式Agent生命周期管理混乱非法转移频发状态对象集与状态转移表这些模式之间不是孤立的。命令是“动作的封装”模板方法确定“流程的骨架”策略解决“流程中某一环的多种选择”观察者负责“动作引发的通知”状态模式约束“通知和动作之间的合法性”。理解它们之间的互补关系比单个模式本身更重要。4. PIG中的实现细节与代码结构4.1 核心接口长什么样示例代码我用Java写因为PIG的运行时就是用Java写的。如果你是C或者其他语言背景建议关注的是接口划分的思路而不是具体语法。这套设计的核心就一句话让Subagent和Tool收敛到同一套调用契约上。先看能力描述。无论是Tool还是Subagent在注册时都实现同一个ToolSpec接口public interface ToolSpec { String name(); // 唯一标识如 subagent.writer String description(); // 给模型看的自然语言描述 JsonSchema inputSchema(); // 入参JSON Schema模型据此生成参数 }然后是命令接口。每次调用都会被包装成一个命令对象public interface AgentCommand { String commandId(); // 全局唯一用于幂等和追溯 String targetAgentId(); // 目标Subagent或Tool的名称 AgentResult execute(CommandContext ctx) throws Exception; boolean isIdempotent(); // 是否允许重试 }最后是统一的结果结构。不管底层是普通Tool还是Subagent返回对象都必须收敛到AgentResultpublic class AgentResult { boolean success; String errorCode; JsonNode data; // 结构化结果 MapString, Object meta; // 耗时、token数、重试次数等 }这三个接口非常简洁但它是整个调度体系的地基。因为所有能力都遵循同一套契约调度层就可以用一种通用的方式去执行命令、解析结果、处理异常不需要关心背后是函数还是Agent。4.2 一个真实案例主Agent同时调度三个Subagent用一个实际场景演示一下完整流程。用户给PIG发指令“帮我写一段新品发布文案翻译成英文再检查一遍有没有风险词。”这个任务天然可以拆成三个子任务文案生成writer、翻译translator、风险审查reviewer。在PIG里这三个能力被注册成三个Tools名字分别是subagent.writer、subagent.translator、subagent.risk_reviewer。主Agent收到用户指令后不会在提示词里写“你去调用另一个Agent”而是把这当成三个Tool调用。整个执行流程是这样的主Agent根据Tool描述生成第一次Tool调用subagent.writer参数是主题、语气风格、字数要求。调度层执行该命令writer内部调用大模型生成文案返回AgentResult。文案结果回填到主Agent的上下文主Agent继续生成第二次Tool调用subagent.translator。翻译结果返回后主Agent再发起第三次调用subagent.risk_reviewer。最终主Agent汇总所有结果生成一段最终回复给用户。代码层面调度层只需要一个简单的循环反复执行“模型产生Tool调用-框架执行-结果回填”这个过程直到模型不再生成新的调用ListToolCall toolCalls parseToolCalls(modelReply); while (!toolCalls.isEmpty()) { for (ToolCall call : toolCalls) { AgentCommand command registry.buildCommand(call); AgentResult result commandRunner.execute(command); context.appendToolResult(call.id(), result); // 结果回填上下文 } modelReply model.chat(context.toMessages()); toolCalls parseToolCalls(modelReply); }这段代码看起来朴素但它能通用的原因正是因为所有能力都被统一成了Tool调用。不管是调搜索引擎还是调一个完整的Subagent这个循环都完全适用。PIG后续要接入新能力不需要改调度循环只需要注册新的ToolSpec。4.3 关键参数设计与容错细节Subagent调用和普通Tool调用统一之后容错参数也统一了。这里分享几个实际用得最多的参数配置。timeoutMs是单次Subagent调用最大耗时。普通Tool一般10秒内返回但Subagent内部还会再走一次完整的大模型生成所以默认值要放宽到90秒。没有合理超时的话一个卡住的Subagent会一直占着线程池拖垮整个主Agent的调度能力。maxRetries是失败后的最大重试次数默认设置1次。但要特别注意只有isIdempotent返回true的命令才允许自动重试否则重复执行的副作用没法消除。fallbackAgent用来做降级当某个领域专家Agent不可用时可以把任务转发给一个通用Agent保证主流程不中断。这些参数统一放在ToolExecutionPolicy里既支持全局默认值也支持按命令覆盖。为什么一定要统一因为编排层的职责是“让调用以一种可预期的方式发生”不管被调方是简单函数还是复杂Agent。参数只有统一了监控告警、限流治理、成本统计才能在同一个标准下工作不然每个Agent一套规则系统复杂度会成倍上升。5. 实际踩坑记录与排查思路5.1 问题一Subagent之间互相等待系统卡死PIG上线后遇到最严重的一个问题是死锁。某个复杂任务跑了十几分钟还没结束日志里看到writer Agent一直在等reviewer反馈reviewer Agent在等writer输出两个Subagent互相等对方整个任务链卡死了。排查过程是通过trace日志拉时间线发现根因是产品需求里允许Subagent之间互相调用而两个子任务在依赖关系上形成了环。这跟设计模式没什么关系纯粹是任务拓扑设计问题。解决思路分两层。短期直接禁止Subagent之间互相调用所有任务依赖必须由主Agent编排任何中间结果都先回传主Agent再由主Agent决定下一步。长期如果确实要支持Subagent间协作那必须在任务图构建时做环检测任何会产生循环依赖的命令都不允许提交。这个坑给我一个非常重要的教训主从模式的安全性很大程度上是靠“不让下属私聊”这种强制约束来保证的。给Subagent太多自由虽然在个别场景下灵活但整个系统的可治理性会快速滑坡。5.2 问题二上下文在层层调用中丢失第二个高频问题是上下文丢失。主Agent第一次调subagent.writer生成文案后紧接着调subagent.translator翻译时经常“忘记”之前生成的文案翻译结果驴唇不对马嘴。刚开始以为是大模型上下文窗口不够后来排查发现根因不在窗口而是PIG每次Tool调用后只把返回结果追加到消息列表没有把“用户最初意图”和“当前执行到哪一步”的结构化摘要一起传下去。一旦上下文被截断最早的意图就被挤掉了后续调用就成了无源之水。解决方法是给CommandContext增加一个“会话记忆摘要”字段主Agent在每次调度前先把当前阶段目标注入进去。同时不再用纯文本拼接保存历史改用结构化消息数组每条消息带上role和元信息标签。这个改动之后Subagent能准确区分哪些是历史背景、哪些是当前任务翻译结果的质量明显稳定了。5.3 问题三超时重试导致同一任务被执行两次有一次用户反馈翻译结果被提交了两次后台一查token消耗比预期翻了一倍。定位后发现是网络抖动Subagent其实已经成功翻译并返回结果但回包超时了主Agent等了90秒后判定失败并发起重试结果同一个翻译任务被执行了两次。这个问题的本质是“执行成功”和“通知成功”被混为一谈。网络请求超时只能说明“你没有收到结果”不能说明“对方没有执行”。解决办法是引入幂等机制。每次命令生成时带上全局唯一的commandIdSubagent执行前先用commandId查一下结果缓存如果之前已经执行过就直接返回上次结果不再重复执行。对于翻译、生成这类纯计算任务重复执行的损失主要是token成本但对于会触发外部副作用的命令比如发消息、扣费、写数据库幂等就不仅是成本问题了是正确性底线。所以我在PIG里定了一条规则任何重试动作都必须以幂等为前提没有幂等键的命令宁可失败也不能盲目重试。5.4 问题四监控面板上Agent状态乱跳监控面板刚上线时有一个很影响体验的bug同一个Agent一会儿显示“执行中”一会儿显示“空闲”刷新一下又变回“执行中”告警时不时误报。刚开始以为是前端渲染问题查了一圈发现根因在后端。状态回写逻辑写在了事件回调里多个回调并发执行时旧事件的回写覆盖了新事件导致状态“回退”。这个问题的本质是状态机的设计只约束了合法迁移没有约束并发场景下的顺序。解决办法是在状态变更接口里增加版本号字段每次回写时校验版本号只有版本号不小于当前值的更新才能生效。同时把状态迁移入口收紧成全局唯一方法禁止业务代码里直接修改state字段所有状态变更必须经过状态机校验和事件发布。整理了这么多次排查之后我习惯把常见问题记成一张速查表方便团队其他人快速定位现象可能根因排查方向任务长时间不结束Subagent间循环依赖拉trace时间线检查任务依赖图是否有环后续任务“忘了”前文上下文快照未携带意图摘要检查CommandContext是否包含会话记忆摘要重复执行、重复扣费超时重试未做幂等确认命令是否有commandId并校验结果缓存面板状态乱跳并发回写未校验版本检查状态迁移入口和版本号逻辑6. 最后说点大实话在PIG上投入了这么多时间之后我越来越觉得设计模式从来不是考试里的名词而是系统复杂度膨胀到某个阈值之后你被迫回归的那些“成熟答案”。如果你现在正在准备设计模式课程作业或者在做多Agent方向的期末项目我的建议是别急着堆大模型API先想清楚哪些调用是命令、哪些路由是策略、哪些状态迁移是合法再动手写代码。框架搭对了后面所有需求迭代都是往格子里填东西框架搭错了每加一个功能都是在补窟窿。如果只能从这篇文章带走一个认知我希望是“Subagent即Tool”这个视角。它看起来像是偷懒把一个活生生的Agent降级成一个工具接口但它会直接简化你的调度层、观测层和治理层让你把有限的精力花在真正的业务问题上而不是疲于应付Agent之间混乱的通信。这个系列后面我还会继续写PIG里更偏工程的细节比如事件总线的实现、状态机的配置化、命令队列的持久化方案。先把这篇里的模式用起来比看十篇理论文章都有用。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门