Agent Team (多智能体协同)

从零推导 Agent Team (多智能体协同)

1. 寻找"第一性原理":最朴素的硬编码协同

team 包解决的最核心、最原初的业务问题是什么?

答案是:如何让多个 LLM Agent 协同工作,解决单体 Agent 无法处理的复杂长链路任务(例如:一个 Agent 负责规划,另一个负责写代码,再一个负责测试)。

如果不使用任何框架,我们用最朴素、最硬编码的方式(大 for 循环和 if/else)实现两个 Agent 的协同对话,代码长这样:

go 复制代码
// 第一性原理:用 for 循环和硬编码实现两个 Agent 的对话
func RunSimpleTeam(userInput string) {
    leaderHistory := []Message{{Role: "user", Content: userInput}}
    coderHistory := []Message{}
    
    for {
        // Leader 思考
        leaderResp := llm.Generate(leaderHistory)
        leaderHistory = append(leaderHistory, leaderResp)
        
        // 解析 Leader 的意图,如果是分配给 Coder
        if strings.Contains(leaderResp.Text, "@Coder") {
            task := extractTask(leaderResp.Text)
            coderHistory = append(coderHistory, Message{Role: "user", Content: task})
            
            // Coder 思考并执行
            coderResp := llm.Generate(coderHistory)
            coderHistory = append(coderHistory, coderResp)
            
            // Coder 把结果返回给 Leader
            leaderHistory = append(leaderHistory, Message{Role: "user", Content: "Coder 回复: " + coderResp.Text})
        } else if strings.Contains(leaderResp.Text, "Task Done") {
            break
        }
    }
}

2. 第一次演进:应对"同步阻塞与状态纠缠"的危机

痛点与危机

上述最基本的结构在应对真实业务场景时,会遇到第一个致命痛点 :高度耦合的同步阻塞。

随着 Agent 数量增多和交互变复杂,硬编码的调用链路(Leader 调 Coder,Coder 回复 Leader)变得极其僵化。如果多个 Agent 要并行工作?如果系统崩溃需要恢复状态?同步的函数调用会导致整个系统阻塞,且状态分散在局部变量中,无法持久化。

引入第一次抽象:基于信箱(Mailbox)的异步事件驱动

我们需要解耦 Agent 之间的直接函数调用,引入 "信箱(Mailbox)""消息队列" 的概念。每个 Agent 都有自己的独立信箱,通过向信箱读写消息来驱动运行。

go 复制代码
// 第一次演进:引入 Mailbox 和事件驱动

type Mailbox struct {
    Messages []Message
}

var mailboxes = map[string]*Mailbox{
    "Leader": &Mailbox{},
    "Coder":  &Mailbox{},
}

// 独立的 Agent 运行循环(可放入独立 Goroutine 中并行运行)
func RunAgentLoop(agentName string, llm Agent) {
    history := []Message{}
    for {
        // 1. 从自己的信箱读取新消息
        newMsgs := mailboxes[agentName].ReadNew()
        if len(newMsgs) == 0 {
            time.Sleep(1 * time.Second)
            continue
        }
        
        history = append(history, newMsgs...)
        
        // 2. 思考并产生输出
        resp := llm.Generate(history)
        history = append(history, resp)
        
        // 3. 投递消息到目标 Agent 信箱
        if target, content, ok := parseRoute(resp.Text); ok {
            mailboxes[target].Write(Message{From: agentName, Content: content})
        }
    }
}

3. 第二次演进:应对"框架集成与动态生命周期"的挑战

新的棘手问题

随着异步通信体系的建立,工程化上又遇到了新的问题:

  1. 生命周期管理:Agent 怎么动态创建和销毁?
  2. 框架透明性:如何在不破坏现有单体 Agent 框架的前提下,把多智能体通信机制无缝塞进去?

引入更高级抽象:基于中间件(Middleware)的工具注入与路由网关(Router)

我们需要将团队协调逻辑封装为中间件,在 Agent 运行时动态注入团队协作工具(如 send_messageagent ),并引入一个统一的路由网关来分发外部输入和内部消息。

这种注入的核心效果是 "工具化伪装"

  1. 实现"框架透明性"(对现有代码零侵入) :在单体 Agent 真正开始思考前,中间件作为拦截器,往其工具箱里额外塞入 send_message 等特殊工具。底层的单体 Agent 完全不知道自己处于 Team 中,它只以为自己在调用一个普通工具,而实际上触发了中间件底层的 Router 逻辑,将消息投递到了另一个 Agent 的信箱。
  2. 实现动态的"生命周期管理"(按需调度) :中间件专门给 Leader 注入了召唤工具(如 agent )。Leader 大模型推理认为需要帮手时,调用该工具并传入角色参数(如 role="coder" )。该工具底层的 Go 代码会立刻为 Coder 分配新信箱,并启动一个新的后台线程(TurnLoop)。小兵的创建和销毁不再由硬编码决定,而是由 Leader 的推理按需调度。
go 复制代码
// 第二次演进:引入 Router 和 Middleware,实现多 Agent 协同体系

// 1. 统一的消息路由器
type SourceRouter struct {
    sources map[string]*MailboxSource
}

func (r *SourceRouter) Route(msg TurnInput) {
    target := msg.TargetAgent
    if target == "" { target = "team-lead" }
    r.sources[target].Push(msg)
}

// 2. Team Middleware(劫持 Agent 的工具列表)
func TeamMiddleware(agentName string, isLeader bool) Middleware {
    return func(ctx Context, next Agent) Agent {
        // 在 Agent 执行前,动态注入团队工具
        tools := []Tool{SendMessageTool(agentName)}
        if isLeader {
            tools = append(tools, TeamCreateTool(), AgentTool()) // Leader 专属
        }
        ctx.InjectTools(tools)
        return next(ctx)
    }
}

// 3. 统一的执行引擎
func NewTeamRunner() {
    router := NewSourceRouter()
    leader := BuildAgent(WithMiddleware(TeamMiddleware("team-lead", true)))
    StartTurnLoop("team-lead", leader, router.GetSource("team-lead"))
}

4. 映射到真实源码 (Mapping to Reality)

回到真实的 team 源码,它极其优雅地落地了上述推导:

  • 统一的消息路由器: sourceRouter

    源码中定义了带有 TargetAgent 的输入结构体 types.go#L69-L76 。在创建 Runner 时, team_runner.go#L100-L107 初始化了 sourceRouter,它充当系统的主干,拦截所有上游输入并分发给对应的 Agent 信箱。

  • 欺上瞒下的切面拦截器: teamMiddleware

    team.go#L94-L121 中, BeforeAgent 钩子动态向 Agent 注入了团队协作工具。如果是 Leader ,则注入 TeamCreateToolTeamDeleteTool 和召唤 Teammate 的 AgentTool 。所有人都会被注入 SendMessageTool ,获得跨 Agent 通信能力。

  • 独立的事件驱动循环: RunnerTurnLoop

    team_runner.go#L132-L142 中,为 Leader 包装了一个 adk.TurnLoop 。当 Leader 通过工具召唤一个新的 Teammate 时,在 team_runner.go#L174-L203 中,会动态为这个 Teammate 创建独立的 TurnLoop ,并分配一个由文件系统支撑的信箱。

  • 工程上的"脏活累活": 任务共享与并发控制

    team_runner.go#L218-L257 中,系统强制注入了 newTeamPlantaskMiddleware 接管底层任务管理,并通过 TaskLockInboxLocks 实现了跨 Goroutine 的文件读写锁,防止并发读写导致信箱数据损坏。

5. 核心架构拆解:Actor 模型消息驱动

为了更清晰地说明底层的运作逻辑,我们对其核心通信算法进行拆解:

  • 过程 (How)
    1. 系统初始化统一的 Router 和多个独立 Agent 的 Mailbox。
    2. Leader 思考后调用 send_message 工具,生成一条 Target 为 Teammate 的消息结构体。
    3. Router 拦截该消息,路由投递至 Teammate 的专属信箱文件。
    4. Teammate 的轮询器检测到文件变化,读取新消息并拼接至自身对话历史中,触发下一轮推理。
  • 原理 (Why)
    解耦多 Agent 之间的直接同步调用。让 Agent 可以并行工作,且生命周期独立(随时销毁重建而不影响主流程),实现高可用与持久化。
  • 复杂度
    • 时间复杂度:单次消息传递开销为 O(M) + O(I/O) (其中 M 为消息体大小,I/O 为文件读写开销),主导项为文件 I/O 与轮询等待时间
    • 空间复杂度: O(N * H) (N 为 Agent 数量,H 为各自的独立历史记录长度)。
  • 具象化
    就像一个公司里的各个部门(Agent),大家不直接打电话(同步调用),而是通过写邮件(发消息至信箱)。Leader 写好需求发到 Coder 的邮箱,Coder 每天定时刷邮箱(轮询),看到需求后开始写代码,写完再回邮件给 Leader。

6. 深度对比:Team vs. Supervisor

在 Eino 的架构中, TeamSupervisor 都在解决 "多智能体协同" 的问题,但它们的底层哲学、通信机制和适用场景有着本质的差异。

如果用一句话总结: Supervisor 是 "接力棒模式(控制权流转)" ,而 Team 是 "信箱模式(异步消息驱动)" 。

6.1 通信哲学的差异:Transfer vs. Messaging

  • Supervisor (基于 Transfer)

    • 核心是 控制权的转移 。在 Supervisor 模式下,整个网络同一时刻只有一个 Agent 在运行(持有 "接力棒" )。
    • 过程 :主管运行 → 决定派活给小兵 A → 控制权 交接 给小兵 A → 小兵 A 运行 → 任务完成后控制权 交接回 主管。
    • 本质 :它是同步的、阻塞的拓扑约束。
  • Team (基于 Messaging)

    • 核心是 信息的交换 。每个 Agent 都是一个独立的 Actor,拥有自己的信箱 (Mailbox) 。
    • 过程 :Leader 运行 → 调用 send_message 工具发邮件给 Teammate → Leader 继续运行或等待 → Teammate 异步从信箱读到邮件 → 开始自己的运行循环。
    • 本质 :它是异步的、并行的分布式系统模型。

6.2 执行模型的差异:单循环 vs. 多循环

  • Supervisor

    • 运行在 同一个 Run 调用链 中。虽然有多个 Agent,但在底层看来,它们是通过 Transfer 机制串联起来的一条长链路。
    • 生命周期 :小兵是被 "唤起" 的,干完活就退出。
  • Team

    • 运行在 多个独立的 TurnLoop 中。Leader 和每个 Teammate 都有自己独立的执行死循环 (Event Loop) 。
    • 生命周期 :Teammate 是被 "启动 (Spawn) " 的后台进程。它们可以长时间驻留,监听信箱,处理多轮对话,甚至在 Leader 没说话时自己在那 "思考" 。

6.3 状态与记忆的差异:全局上下文 vs. 独立记忆

  • Supervisor

    • 由于是控制权流转,通常共享或部分继承对话历史。小兵执行完的结果会作为 ToolResult 直接塞回主管的上下文里。
    • 透明度 :主管能感知到小兵执行的每一步细节(因为那是它的 ToolCall )。
  • Team

    • 每个 Agent 拥有 完全隔离的私有记忆 。它们只知道自己信箱里收到了什么,以及自己发出了什么。
    • 透明度 :Leader 只能看到 Teammate 回复给它的最终文本,看不到 Teammate 内部的思考过程或它调用的其他工具,除非 Teammate 在回复中显式包含这些信息。

6.4 适用场景对比

  • 协作复杂度
    • Supervisor :中低。任务定义清晰,边界明确。
    • Team :高。长链路、需要并行或背景工作的任务。
  • 实时性
    • Supervisor :高(同步响应)。
    • Team :低(异步轮询信箱有延迟)。
  • 可靠性
    • Supervisor :依赖单体控制流,易于追踪。
    • Team :极强。支持断点续传,状态完全持久化到文件。
  • 动作类比
    • Supervisor部门主管派活 。主管叫你去办公室,把活交代给你,你做完立刻回来汇报。
    • Team跨部门邮件协作 。Leader 发邮件派活,你去忙你的,做完回邮件。

6.5 源码层面的印证

  • Supervisor :主要通过 supervisor.go 中的 DeterministicTransferTo 包装器来强制小兵回传控制权。它复用的是 flow.go 的 Transfer 逻辑。
  • Team :通过 team_runner.go 启动多个 TurnLoop ,并使用 mailbox_file.go 实现基于文件系统的异步消息队列。

总结建议

  • 如果你需要一个 确定性强、逻辑紧凑 的分发器,选 Supervisor
  • 如果你需要构建一个 拟人化、可扩展、支持复杂长异步任务 的 Agent 组织,选 Team

7. 批判性总结 (Critical Trade-offs)

优势

  • 完美适配单体抽象 :通过 Middleware 和 Source/Router 的设计,多 Agent 的复杂逻辑被完美封装。底层的 ChatModelAgentTurnLoop 甚至不知道自己处于一个 Team 中,实现了对原有单体框架的零侵入。
  • 强大的容错与可恢复性:基于文件系统( JSON Array )的信箱机制使得团队状态完全持久化。即使进程崩溃,重启后 Agent 依然能从信箱读取历史记录恢复工作。

代价与局限

  • 基于文件信箱的延迟损耗MailboxPollInterval 默认轮询文件,对于追求低延迟(如语音交互)的多智能体场景,基于文件 I/O 的轮询通信会带来显著的延迟叠加。
  • 上下文隔离带来的信息差 :由于每个 Agent 只看自己的信箱历史,Leader 必须通过 send_message 将必要的上下文显式复制给 Teammate。这不仅增加了大模型的推理负担(需判断传什么上下文),也极易导致关键信息在传递中丢失。

更优解的探讨

目前的 Team 设计是经典的 "Actor Model (参与者模式)" ,每个 Agent 相当于一个 Actor,通过异步消息通信。

  • 针对低延迟场景,可以抽象一层基于 Memory/Redis 的 PubSub 机制来替代文件轮询。
  • 针对上下文隔离,业界(如 LangGraph )更推崇 "State Graph (状态图模型)" :维护一个全局的 State 对象,各个 Agent 作为图上的节点对全局 State 进行读写。这消除了繁琐的 send_message 互传,大模型只需关注如何更新全局状态,心智负担更低且不易丢失上下文。
相关推荐
Alvin千里无风4 小时前
在 Ubuntu 上从源码安装 Nanobot:轻量级 AI 助手完整指南
linux·人工智能·ubuntu
环黄金线HHJX.4 小时前
龙虾钳足启发的AI集群语言交互新范式
开发语言·人工智能·算法·编辑器·交互
Omics Pro4 小时前
虚拟细胞:开启HIV/AIDS治疗新纪元的关键?
大数据·数据库·人工智能·深度学习·算法·机器学习·计算机视觉
悦来客栈的老板5 小时前
AI逆向|猿人学逆向反混淆练习平台第七题加密分析
人工智能
KOYUELEC光与电子努力加油5 小时前
JAE日本航空端子推出支持自走式机器人的自主充电功能浮动式连接器“DW15系列“方案与应用
服务器·人工智能·机器人·无人机
萤火阳光5 小时前
13|自定义 Skill 创作:打造专属自动化利器
人工智能
我哪会这个啊5 小时前
SpringAlibaba Ai基础入门
人工智能
lifewange6 小时前
Go语言-开源编程语言
开发语言·后端·golang
白毛大侠6 小时前
深入理解 Go:用户态和内核态
开发语言·后端·golang
tianbaolc6 小时前
Claude Code 源码剖析 模块一 · 第六节:autoDream 自动记忆整合
人工智能·ai·架构·claude code