做过多Agent项目的人应该都遇到过这个场景:任务一复杂,单Agent就开始"思维发散",一会跑偏主题,一会重复劳动,上下文越滚越长,最后输出质量断崖式下跌。我在PIG项目里也踩过这个坑,所以第二篇设计模式笔记,专门聊聊我们最后趟出来的路子——主从模式,以及背后一个关键的思想转变:把subagent当成一种特殊的tool来调用。
这个思路听起来简单,但真正落地的时候牵扯到接口设计、状态同步、错误传播、资源控制一堆问题。这篇就把我们在PIG里的完整实践拆开讲清楚,包括踩过的坑和最后的解决方案,给准备做多Agent编排的朋友一个可以直接抄作业的参考。
1. 项目背景:从单Agent到主从模式的必然选择
1.1 单Agent结构的天花板
PIG项目早期就是典型的单Agent结构:一个大模型实例,塞进系统提示词、工具列表、历史对话,让它在一次会话里完成所有事情。简单场景下这个方案够用,但一旦任务复杂到需要多步推理、外部工具协作、阶段性验证,问题就全冒出来了。
最直观的问题是上下文窗口。你让Agent先调研、再写方案、再执行、再复盘,这些中间产物全堆在主上下文里,很快就把Token吃光。更恶心的是,模型在处理超长上下文的时候会"遗忘"早期信息,尤其在中间步骤出错需要回退重试的时候,状态一乱,整个任务就废了。
另一个问题是职责混乱。单Agent里,工具调用、结果判断、下一步规划都是同一个模型在干,逻辑耦合严重。我举个例子你就明白了:Agent调用一个搜索工具,返回结果不满意,它到底是该换个关键词再搜一次,还是直接放弃搜索转去写文档?这个决策在单Agent里完全靠模型"临场发挥",缺乏结构性约束,结果就是行为不可控。
PIG项目真正下定决心改架构,是因为一次压力测试:我们给单Agent塞了一个需要串联5个工具、跨3个阶段的任务,结果它在一个死胡同里反复重试了7次,浪费了几万Token,最后输出还是错的。那次之后我们就明确了,必须做分层编排。
1.2 从单体编排到分层编排的转变
多Agent的设计模式里,主从模式(也叫Supervisor模式、Master-Slave模式)是最基础也最实用的一种。核心思想很简单:一个主管Agent负责拆解任务、分配任务、汇总结果,多个子Agent各自负责一个细分领域,互不干扰,最终由主管整合输出。
你可能会问,这跟传统的函数调用有什么本质区别?区别在于:每个Agent都有自己的模型上下文、自己的工具集、自己的任务边界,它们是"有状态的工作单元",而不是无状态的函数。主管Agent不关心子Agent内部怎么实现,只关心输入输出是否符合约定,这就形成了一个天然的解耦层。
实际落地中,最开始我们也走了弯路。第一版实现里,主管Agent直接调用子Agent的"内部方法",比如让子Agent暴露一个do_research()函数、一个write_report()函数,结果子Agent的内部逻辑一变,主管的调用逻辑就得跟着改,耦合得一塌糊涂。
后来又试过"完全独立"的模式:每个子Agent自己启动一个完整会话,跑完把结果写到一个共享文件里,主管再去读。但这个方案更难搞——子Agent之间的状态没法联动,主管对子Agent的执行过程完全不可见,出了问题都不知道该找谁。
这两种弯路走出来,方向就清晰了:用工具的抽象来包装子Agent的调用,让它们像工具一样"即插即用"。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心思路:把subagent当作另类tool调用
2.1 为什么subagent可以当作tool
在主从模式里,主管Agent和子Agent的交互方式,本质上符合工具调用的全部特征:
- 有明确的输入输出协议——输入是任务描述,输出是执行结果
- 有边界隔离——主管不关心子Agent内部执行细节
- 有错误传播机制——子Agent失败需要以某种方式通知主管
- 有资源和权限控制——子Agent能调什么工具、能访问什么数据,是可以被约束的
所以,与其把subagent当成一个"会思考的对话者",不如把它当成一个"拥有更强能力的工具"。这不仅仅是叫法上的差别,而是把问题从一个"多Agent协作"的问题,转化成了一个"工具编排"的问题,后者在工程上已经有了成熟的经验。
我在PIG项目里的实际做法,就是给每个子Agent写一个描述文件,说明它能干什么、需要什么参数、返回什么结果,然后注册到主管Agent的工具列表里。主管Agent在做任务规划时,把"调用某个子Agent"当成一次普通的工具调用,只是这个"工具"的参数更复杂,执行时间更长,返回结果更结构化。
这里还引出一个有意思的推论:既然subagent可以当作tool,那么tool也可以当作一种"极简subagent"。有些复杂工具本身就需要多步处理、状态保持,比如一个需要先初始化再执行再收尾的数据处理工具链,用subagent包装反而更清晰。这两者在PIG里是统一建模的。
2.2 统一调用协议的设计
把subagent当作tool,第一步就是定义统一的调用协议。我在PIG里参考了OpenAI Function Calling的格式,做了一点扩展。
一个普通的工具调用是这样:
json复制{
"tool_name": "web_search",
"arguments": {
"query": "设计模式 主从模式",
"limit": 10
}
}
而一个subagent调用是这样:
json复制{
"tool_name": "research_agent",
"arguments": {
"task": "调研当前主流的多Agent编排方案",
"context": {
"constraints": "重点关注主从模式的实现细节",
"deadline": "2024-06-30"
},
"max_steps": 10
}
}
关键的区别在max_steps和context这两个字段。max_steps限制子Agent内部最多执行多少步,防止它跑飞;context用来传入主管侧的一些约束信息,相当于给子Agent的"任务简报"。
这样就实现了主管Agent与子Agent之间的解耦:子Agent可以随便换模型、换内部提示词、换工具集,只要满足"输入任务描述、输出结构化结果"的约定就行。
2.3 上下文隔离与数据传递
主从模式最容易出问题的地方,就是上下文管理。我见过很多项目,主管Agent调用子Agent的时候,直接把整个对话历史传过去,让子Agent"基于上文继续",结果子Agent被无关信息干扰,输出质量一塌糊涂。
PIG里采用的原则是:上下文最小化。主管Agent只把与任务直接相关的信息传给子Agent,子Agent返回的也只是一份结构化的结果摘要,而不是完整的过程记录。主管Agent需要保留的是"全局视角",子Agent需要保留的是"领域深度",两者不需要全部共享。
数据传递上,我们定义了统一的Result结构体:
json复制{
"status": "success",
"summary": "已完成调研,发现3种主流方案...",
"data": {
"key_findings": ["..."],
"recommendations": ["..."]
},
"artifacts": ["file:///dat
