24|Supervisor 模式:让协调者管理多个专家 - eino教程
24|Supervisor 模式:让协调者管理多个专家
《Eino 实战:用 Go 构建 AI 应用与智能体》· 第 24 篇
关键词:Supervisor、TransferToAgent、专家分工、上下文共享、控制权转移
前置条件:已完成 22|AgentAsTool 与 23|Workflow Agents。
前两篇讲了两种协作:AgentAsTool(动态委派,子 Agent 隔离)和 Workflow Agents(固定结构)。
还有第三种:有一个明确的“负责人”,它把任务分配给专家,专家完成后回到负责人。这就是 Supervisor 模式。
它的关键机制是控制权转移(Transfer),而不是工具调用——这是与 AgentAsTool 的本质区别。
一、Supervisor 的结构
用户 ↓ Supervisor(协调者) ↙ ↘ writer reviewer (写完交回) (审完交回)三条规则:
- Supervisor 负责分配任务,并持有最终结论。
- 子 Agent 只与 Supervisor 通信,子 Agent 之间不直接交互。
- 子 Agent 完成后交回控制权给 Supervisor。
第 2 条是有意的约束:它避免了子 Agent 之间的复杂网状调用,让流程可观测、可调试。
二、控制权转移 vs 工具调用
这是本篇最需要理解的一点。
| AgentAsTool | Supervisor(Transfer) | |
|---|---|---|
| 机制 | 调用工具 | 转移控制权 |
| 父 Agent 状态 | 保持运行,等结果返回 | 交出控制权 |
| 上下文 | 子 Agent 独立 | 全量共享 |
| 返回方式 | 工具结果回到父上下文 | 子 Agent 把控制权转回 |
用一句话概括:AgentAsTool 是“我叫你去做件事,做完告诉我”;Transfer 是“现在由你负责,做完再叫我”。
后者会造成上下文的完全合并,因此官方注释里对基于 transfer 的模式(包括 Supervisor)明确标注了 “NOT RECOMMENDED”,并建议大多数场景使用AgentAsTool或DeepAgent。
这不代表 Supervisor 不能用。当业务上确实存在“分层指挥”的关系、且需要共享上下文时,它是合适的表达。但要清楚代价:上下文膨胀、调试变难。
三、实现方式
Supervisor 的构建很简单:
sup,err:=supervisor.New(ctx,&supervisor.Config{Supervisor:coordinatorAgent,SubAgents:[]adk.Agent{writer,reviewer},})它内部做了两件事:
- 把每个子 Agent 包装上“确定性转回 Supervisor”的能力。
- 调用
SetSubAgents建立Supervisor → 子 Agent的层级关系。
第二步——SetSubAgents——是关键:它让 Supervisor 获得transfer_to_agent工具。
转移是怎么发起的
Supervisor 通过工具调用来发起转移。框架为它注入了内置工具:
工具名:transfer_to_agent 参数: {"agent_name": "writer"}也就是说,虽然机制上是“控制权转移”,触发方式仍然是模型生成工具调用。这让转移决策完全由模型驱动——也是为什么Description必须写清楚(否则 Supervisor 不知道该转给谁)。
子 Agent 完成后,框架通过确定性机制自动转回 Supervisor,不需要模型决定。
四、完整示例
下面这段程序完整演示“Supervisor 转交 → 子 Agent 执行 → 转回 → Supervisor 汇总”的闭环,不访问外部服务:
packagemainimport("context""fmt""log""github.com/cloudwego/eino/adk""github.com/cloudwego/eino/adk/prebuilt/supervisor""github.com/cloudwego/eino/components/model""github.com/cloudwego/eino/schema")// scriptedModel 按脚本返回响应。typescriptedModelstruct{responses[]*schema.Message idxint}func(m*scriptedModel)Generate(_context.Context,_[]*schema.Message,_...model.Option)(*schema.Message,error){ifm.idx>=len(m.responses){returnschema.AssistantMessage("(脚本结束)",nil),nil}r:=m.responses[m.idx]m.idx++returnr,nil}func(m*scriptedModel)Stream(ctx context.Context,in[]*schema.Message,opts...model.Option)(*schema.StreamReader[*schema.Message],error){msg,err:=m.Generate(ctx,in,opts...)iferr!=nil{returnnil,err}sr,sw:=schema.Pipe[*schema.Message](1)gofunc(){defersw.Close();sw.Send(msg,nil)}()returnsr,nil}func(m*scriptedModel)WithTools([]*schema.ToolInfo)(model.ToolCallingChatModel,error){returnm,nil}funcmain(){iferr:=run();err!=nil{log.Fatal(err)}}funcrun()error{ctx:=context.Background()// 协调者:先转交给 writer,子 Agent 会按框架的确定性机制转回协调者,最后协调者总结。coordinator,err:=adk.NewChatModelAgent(ctx,&adk.ChatModelAgentConfig{Name:"coordinator",Description:"协调专家完成任务",Instruction:"你负责把任务转交给合适的专家,专家返回后给出最终结论。",Model:&scriptedModel{responses:[]*schema.Message{// 第一步:以工具调用的形式发起转交。schema.AssistantMessage("",[]schema.ToolCall{{ID:"t1",Type:"function",Function:schema.FunctionCall{Name:"transfer_to_agent",Arguments:`{"agent_name":"writer"}`,},}}),// 子 Agent 返回后:给出最终结论。schema.AssistantMessage("最终结论:请先检查网线,如仍异常请联系网络支持组。",nil),}},})iferr!=nil{returnerr}writer,err:=adk.NewChatModelAgent(ctx,&adk.ChatModelAgentConfig{Name:"writer",Description:"撰写答复内容",Instruction:"你负责撰写答复。",Model:&scriptedModel{responses:[]*schema.Message{schema.AssistantMessage("答复草稿:请先检查网线连接。",nil),}},})iferr!=nil{returnerr}sup,err:=supervisor.New(ctx,&supervisor.Config{Supervisor:coordinator,SubAgents:[]adk.Agent{writer},})iferr!=nil{returnfmt.Errorf("创建 Supervisor 失败: %w",err)}runner:=adk.NewRunner(ctx,adk.RunnerConfig{Agent:sup})iter:=runner.Query(ctx,"请撰写一则答复")n:=0for{event,ok:=iter.Next()if!ok{break}ifevent.Err!=nil{fmt.Println(" [事件错误]",event.Err)continue}n++ifmv:=messageOutput(event);mv!=nil&&mv.Message!=nil&&mv.Message.Content!=""{fmt.Printf(" agent=%-12s content=%q\n",event.AgentName,mv.Message.Content)}}fmt.Println("事件总数:",n)returnnil}funcmessageOutput(event*adk.AgentEvent)*adk.MessageVariant{ifevent==nil||event.Output==nil{returnnil}returnevent.Output.MessageOutput}实机输出:
agent=coordinator content="successfully transferred to agent [writer]" agent=writer content="答复草稿:请先检查网线连接。" agent=writer content="successfully transferred to agent [coordinator]" agent=coordinator content="最终结论:请先检查网线,如仍异常请联系网络支持组。" 事件总数: 6这四行输出就是 Supervisor 模式的完整生命周期:
- coordinator → writer:协调者发起转交。
- writer 产出内容:子 Agent 在自己的环节工作。
- writer → coordinator:框架自动转回,不需要模型决定。
- coordinator 汇总:拿回控制权,给出最终结论。
注意第 3 步:子 Agent 并不需要显式“归还”控制权,这是 Supervisor 构建时注入的确定性行为。
还有一处细节值得注意:事件里的agent字段清楚地标识了当前是谁在工作。这让多智能体的执行过程在日志里也是可读的。
五、与另外两种方式的对比
| AgentAsTool | Workflow Agents | Supervisor | |
|---|---|---|---|
| 谁决定下一步 | 父 Agent | 代码结构 | Supervisor |
| 上下文 | 隔离 | 上游拼入下游 | 全量共享 |
| 子 Agent 间通信 | 无 | 无直接通信 | 无,只与 Supervisor |
| 调试友好度 | 高 | 高 | 较低 |
| 官方建议 | 推荐 | 有限场景 | 有限场景 |
选择顺序建议:
- 流程固定 → Workflow Agents(或 compose 编排)。
- 需要动态委派且希望隔离 → AgentAsTool。
- 确实存在分层指挥、且需要共享上下文 → Supervisor。
六、代价与注意事项
1. 上下文会膨胀。
全量共享意味着每个子 Agent 都带着完整历史。多轮转移后,上下文会快速变大。第 18 篇的上下文管理手段在这里尤其必要。
2.Description决定路由质量。
Supervisor 靠描述判断该转给谁。描述含糊会导致转错——而且转错之后流程可能反复来回。
3. 防止无限来回。
Supervisor 可能把任务转给 writer,writer 转回后 Supervisor 又转给 writer。这类循环需要靠提示词约束和迭代上限控制。
4. 成本要提前估算。
每次转移都是一次完整的 Agent 运行。Supervisor + 3 个子 Agent 的结构,实际调用次数可能是单 Agent 的数倍。
5. 鉴权仍在最内层。
子 Agent 里的工具同样需要自己做权限校验(第 11 篇)。控制权转移不改变安全边界。
七、常见问题
Supervisor 不转交,直接自己回答Instruction没有说明“应该转交给专家”,或子 Agent 的Description不够清晰。
转交了但子 Agent 没有转回
正常情况下框架会自动转回(借助构建时的确定性转移配置)。若异常,检查是否使用了supervisor.New而不是手动SetSubAgents。
在 coordinator 和 writer 之间反复转账
转移循环。约束提示词(例如“每位专家只调用一次”),并设置迭代上限。
看不到子 Agent 的中间过程EmitInternalEvents默认关闭。Supervisor 场景下因为共享上下文,通常能直接看到关键输出。
上下文太长导致报错
共享上下文在多次转移后膨胀。加入摘要压缩或限制转移次数。
八、小结
- Supervisor 模式:一个协调者分配任务,子 Agent 只与协调者通信,完成后交回控制权。
- 核心机制是控制权转移,与 AgentAsTool 的“工具调用”有本质区别。
- 转移通过内置的
transfer_to_agent工具发起;子 Agent 转回是框架的确定性行为。 - 上下文完全共享,代价是膨胀与调试难度;官方对此类模式持保留意见。
- 与另外两种方式的选择顺序:固定流程 → Workflow Agents;动态隔离 → AgentAsTool;分层共享 → Supervisor。
- 鉴权仍在最内层,转移不改变安全边界。
下一篇《Plan-Execute-Replan:让复杂任务拥有可调整的计划》,看如何应对“一次计划不够用”的情况。
参考资料与版本说明
资料核对日期为2026 年 10 月 9 日:
- Agent 协作(多 Agent 协作模式)
- Eino ADK 概述(预置模式)
- Eino 主仓库
本文示例按Eino v0.9.21编写,使用脚本化模型(不访问外部服务),已实机运行,正文输出为完整转移闭环的真实结果。