Multi-Agent:让多个 AI 分工协作完成复杂任务

系列「企业级 AI Agent 实现拆解」E15 篇。上一篇 E14 讲了 ChatTemplate------怎么给 AI 发消息。但有些任务一个 Agent 搞不定:需要调研的同时做计算,需要写代码的同时做代码审查,需要先规划再一步步执行。这篇拆 Multi-Agent:多个 AI 怎么分工、怎么通信、怎么在失败时重新规划。

读完这篇你会知道

  • 为什么要用多个 Agent 而不是加更多工具
  • Host-Worker:Host 派活,Specialist 干活,Summarizer 汇总
  • Plan-Execute-Replan:先规划,逐步执行,失败就重新规划
  • Supervisor:为什么 eino 官方说"不推荐"
  • Agent 之间靠什么通信:ToolCall 数据结构
  • 任务分配的三种策略

一、为什么要多个 Agent

单个 Agent 加工具够用吗?理论上够。但实践中有两个问题:

问题 1:专业度。让一个 Agent 既懂检索、又懂计算、又懂代码审查,prompt 会越写越长,越长越混乱,效果越来越差。把职责拆开,每个 Agent 只做一件事,效果好得多。

问题 2:并行。单 Agent 串行执行,5 个任务得一个个来。多 Agent 可以并发跑,总时间缩短。

eino 提供了两种官方推荐的 Multi-Agent 模式,加一种不推荐但值得了解的模式。


二、Host-Worker:派活模式

源码:eino/flow/agent/multiagent/host/

整体结构

csharp 复制代码
用户问题
    ↓
[Host Agent]  (持有所有 Specialist 的"工具描述")
    ↓  通过 ToolCall 选择一个或多个 Specialist
[Specialist A]  [Specialist B]   (并发执行)
    ↓
[Summarizer]  (如果选了多个 Specialist,汇总结果)
    ↓
最终回复

Host 不是普通 ChatModel,它是 ToolCallingChatModel------能输出结构化的 ToolCall,告诉系统"去找哪个 Specialist、传什么参数"。每个 Specialist 的名字和描述被包装成一个"工具"注册到 Host 上,Host 看完用户问题后决定调哪个。

核心配置

go 复制代码
// 源码:eino/flow/agent/multiagent/host/types.go

// Host:决策者
type Host struct {
    ToolCallingModel model.ToolCallingChatModel
    SystemPrompt     string  // "你是一个任务分发助手,根据用户需求选择合适的专家"
}

// Specialist:执行者
type Specialist struct {
    AgentMeta             // Name + IntendedUse(这两个字段决定 Host 怎么描述它)
    ChatModel  model.BaseChatModel
    SystemPrompt string   // "你是一个数学计算专家,只做数学题"
    Invokable  compose.Invoke[[]*schema.Message, *schema.Message, agent.AgentOption]
}

// Summarizer:汇总者(可选)
type Summarizer struct {
    ChatModel    model.BaseChatModel
    SystemPrompt string  // "综合多位专家的回答,给出统一答复"
}

用起来

go 复制代码
// 参考:eino-examples/adk/multiagent/integration-project-manager
hostCfg := &host.Config{
    Host: host.Host{
        ToolCallingModel: myLLM,
        SystemPrompt:     "你是项目经理,根据任务类型分配给合适的专家。",
    },
    Specialists: []host.Specialist{
        {
            AgentMeta:    host.AgentMeta{Name: "researcher", IntendedUse: "负责资料检索和调研"},
            ChatModel:    researchLLM,
            SystemPrompt: "你是研究员,擅长搜索和分析信息。",
            Invokable:    researchAgent.Invoke,
        },
        {
            AgentMeta:    host.AgentMeta{Name: "coder", IntendedUse: "负责编写和调试代码"},
            ChatModel:    codingLLM,
            SystemPrompt: "你是工程师,只负责写代码,不做其他事。",
            Invokable:    coderAgent.Invoke,
        },
    },
    Summarizer: &host.Summarizer{
        ChatModel:    summaryLLM,
        SystemPrompt: "综合研究员和工程师的输出,给出完整报告。",
    },
}

multiAgent, _ := host.NewMultiAgent(ctx, hostCfg)
result, _ := multiAgent.Generate(ctx, userMessages)

单个 vs 多个 Specialist

Host 选了一个 Specialist → 直接返回该 Specialist 的结果(不过 Summarizer)。

Host 选了多个 Specialist → Specialist 并发执行,结果送进 Summarizer 汇总。

HandOff 事件监控

Host 每次把任务转给 Specialist,会触发一个 HandOff 事件,可以用 Callback 监听:

go 复制代码
// 源码:eino/flow/agent/multiagent/host/callback.go
type HandOffInfo struct {
    ToAgentName string  // 转给哪个 Specialist
    Argument    string  // 传了什么参数
}

handler := host.NewMultiAgentCallbackHandler(func(ctx context.Context, info *host.HandOffInfo) context.Context {
    log.Printf("[HandOff] 转给 %s,参数:%s", info.ToAgentName, info.Argument)
    return ctx
})

三、Plan-Execute-Replan:规划循环模式

源码:eino/adk/prebuilt/planexecute/plan_execute.go

这个模式适合步骤不确定的复杂任务:不知道要几步,每步成功了才知道下一步怎么走,中途可能需要调整计划。

三个角色

csharp 复制代码
[Planner]   →  制定计划(步骤列表)
[Executor]  →  执行第一步(用工具)
[Replanner] →  看结果,决定:继续执行 or 宣布完成
               如果需要调整,输出新计划;如果完成,输出最终答案

三者形成循环:Planner → (Executor → Replanner) × N,直到 Replanner 宣布完成。

状态通过 Session 传递

各阶段靠 Session 键值对共享状态,没有显式消息总线:

go 复制代码
// 源码:eino/adk/prebuilt/planexecute/plan_execute.go
const (
    UserInputSessionKey     = "UserInput"       // 用户原始问题
    PlanSessionKey          = "Plan"            // 当前计划(步骤列表)
    ExecutedStepSessionKey  = "ExecutedStep"    // 本次执行结果
    ExecutedStepsSessionKey = "ExecutedSteps"   // 所有历史执行结果
)

// Plan 接口
type Plan interface {
    FirstStep() string  // 取出当前要执行的第一步
    json.Marshaler
    json.Unmarshaler
}

// 默认实现:有序步骤列表
type defaultPlan struct {
    Steps []string `json:"steps"`
}

Planner 写 Plan,Executor 读 Plan.FirstStep() 执行并写 ExecutedStep,Replanner 读所有 ExecutedSteps 判断是否完成。

Planner:用 ToolChoiceForced 强制输出结构化计划

go 复制代码
// Planner 强制 LLM 调 plan() 工具输出结构化步骤,不允许输出普通文字
var PlanToolInfo = schema.ToolInfo{
    Name: "plan",
    Desc: "制定按序执行的步骤列表",
    ParamsOneOf: NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
        "steps": {
            Type:     schema.Array,
            ElemInfo: &schema.ParameterInfo{Type: schema.String},
            Desc:     "按顺序排列的执行步骤",
            Required: true,
        },
    }),
}

Replanner:只能做两个选择

go 复制代码
// 选择 1:任务未完,输出新计划继续
var PlanTool = ...  // 同 Planner 的 PlanToolInfo

// 选择 2:目标达成,输出最终回复退出循环
var RespondToolInfo = schema.ToolInfo{
    Name: "respond",
    Desc: "当目标已达成时,给出最终回复",
    ParamsOneOf: NewParamsOneOfByParams(map[string]*schema.ParameterInfo{
        "response": {
            Type:     schema.String,
            Desc:     "给用户的完整答案",
            Required: true,
        },
    }),
}

完整示例:旅行规划 Agent

go 复制代码
// 参考:eino-examples/adk/multiagent/plan-execute-replan/
cfg := &planexecute.Config{
    Planner: planexecute.NewPlannerAgent(ctx, &planexecute.PlannerConfig{
        ChatModelWithFormattedOutput: llm,
    }),
    Executor: planexecute.NewExecutorAgent(ctx, &planexecute.ExecutorConfig{
        Model: llm,
        ToolsConfig: adk.ToolsConfig{
            ToolsNodeConfig: compose.ToolsNodeConfig{
                Tools: []tool.BaseTool{searchTool, weatherTool, bookingTool},
            },
        },
        MaxIterations: 20,
    }),
    Replanner: planexecute.NewReplannerAgent(ctx, &planexecute.ReplannerConfig{
        ChatModel: llm,
    }),
    MaxIterations: 10,  // 最多重规划 10 次
}

agent, _ := planexecute.New(ctx, cfg)

执行过程:

vbnet 复制代码
Planner:   ["查询热门景点", "查询天气", "规划路线", "预订酒店"]
Executor:  调 searchTool → "故宫、长城、天坛..." → 写 ExecutedStep
Replanner: 步骤未完 → 更新计划(移除已完成步骤)
Executor:  调 weatherTool → "明天晴,适合户外" → 写 ExecutedStep
Replanner: 步骤未完 → 继续...
...
Replanner: 所有步骤完成 → 调 respond() → 输出行程 → 退出循环

四、Supervisor:为什么官方不推荐

源码:eino/adk/prebuilt/supervisor/supervisor.go

Supervisor 把整段对话上下文传给 Sub-Agent,Sub-Agent 执行后再把全部上下文还给 Supervisor:

css 复制代码
[Supervisor] → 全部对话 + 任务 → [Sub-Agent A]
[Sub-Agent A] → 全部对话 + 结果 → [Supervisor]
[Supervisor] → 全部对话 + 任务 → [Sub-Agent B]
...

问题:全上下文传递经过实验没有带来更好效果,context 越来越长,每次调用越来越贵。eino 代码注释里明确标注 ⚠️ Not Recommended

推荐替代方案:

go 复制代码
// AgentTool:把 Sub-Agent 包装成工具,按需调用,不共享全上下文
agentTool := adk.NewAgentTool(ctx, subAgent)

// Parent Agent 通过 ToolCall 调用 Sub-Agent,和调普通工具一样
parentCfg := &adk.ChatModelAgentConfig{
    Model: llm,
    ToolsConfig: adk.ToolsConfig{
        ToolsNodeConfig: compose.ToolsNodeConfig{
            Tools: []tool.BaseTool{agentTool, otherTool},
        },
    },
}

五、Agent 之间靠什么通信

无论哪种模式,Agent 之间传的都是 schema.Message 列表。Host-Worker 靠 ToolCall 路由,Plan-Execute-Replan 靠 Session 键值对。

go 复制代码
// 源码:eino/schema/message.go
type Message struct {
    Role    RoleType  // assistant / user / system / tool
    Content string

    // Host → Specialist:派活指令
    ToolCalls []ToolCall  // 模型输出:调哪个 Specialist,传什么参数

    // Specialist → Host:执行结果
    ToolCallID string  // 对应哪个 ToolCall
    ToolName   string  // 哪个工具/Specialist 产生的结果
}

type ToolCall struct {
    ID       string
    Function struct {
        Name      string  // Specialist 的名字(即 AgentMeta.Name)
        Arguments string  // JSON 格式的任务描述
    }
}

框架用 ToolCall.Function.Name 把结果路由到对应 Specialist,开发者不需要手动写路由逻辑。


六、任务分配三种策略

策略 原理 适用场景
LLM 路由(Host-Worker) Host LLM 通过 ToolCall 决定派给谁 任务类型多样,边界模糊
结构化规划(Plan-Execute-Replan) Planner 先拆步骤,Executor 按步执行 步骤未知,需要迭代调整
AgentTool 调用(推荐替代) Parent 按需 ToolCall 调 Sub-Agent 子任务边界清晰,无需共享上下文

小结

sql 复制代码
Host-Worker(推荐)
  适合:多专家并行,任务类型多样
  关键:Host 通过 ToolCall 路由,Specialist 并发执行

Plan-Execute-Replan(推荐)
  适合:步骤不确定的复杂任务
  关键:Session 传状态,Replanner 决定继续还是结束

Supervisor(不推荐)
  问题:全上下文膨胀,实验效果不及预期
  替代:AgentTool(简单)/ DeepAgent(复杂)

怎么选:

场景 模式
问题可能需要多个专家 Host-Worker
任务复杂、步骤未知、可能失败重试 Plan-Execute-Replan
子任务边界清晰、轻量 AgentTool
多层嵌套、复杂协调 DeepAgent

Multi-Agent 不是越复杂越好。能用一个 Agent 加工具解决的,不要上 Multi-Agent。上了,优先选 Host-Worker 或 Plan-Execute-Replan,不要默认选 Supervisor。

相关推荐
长栎1 小时前
你的策略模式是 Map<String, Strategy>?那不过是最廉价的 if-else 替代品
后端
混沌福王1 小时前
Electron三端统一架构:运行时Adapter、IPC能力边界与分层设计
人工智能·agent·ai编程
长栎2 小时前
你写的 abstract class 里全是钩子方法——模板模式不是让你填空,是让你别越界
后端
AINative软件工程2 小时前
LLM 应用的 Bad Case 反馈闭环工程:别再把用户差评丢进客服表了
llm·openai·agent
ping某2 小时前
语法树,到底是一棵什么形状的树?
后端
_柳青杨2 小时前
一文吃透 Node.js 事件循环:从原理到 Node 20+ 重大变更
javascript·后端
HjhIron2 小时前
🤖 一文搞懂 AI Agent 核心概念:从 LLM 到 Tools,手写一个“股票查询 Agent”
agent
贵慜_Derek2 小时前
《从零实现 Agent 系统》连载 32|闭集 IE 与小模型:分类、意图与字段抽取
人工智能·架构·agent
Alson_Code2 小时前
人机协作项目文档--HITL-AgentScope
后端·aigc·ai编程