多 Agent 协同模式:Router 路由分发、Supervisor 主从编排与 Handoff 交接

本节目标

为什么需要把一个大 Agent 拆成多个专职子 Agent?

如果你往单个 Agent 里塞入 35 个涵盖前端、数据库、运维、财务的工具,再配上一份 4,000 字的巨型 System Prompt:

  1. 工具选择准确率雪崩:当候选工具超过 15~20 个且存在职责重叠时,模型极易调错工具或编造参数。
  2. 子任务过程污染主上下文:让主 Agent 亲自去翻阅 10 个日志文件排查某行报错,这 10 个文件的原始文本会永远留在主对话历史里,把后续任务的上下文空间挤爆。

多 Agent 分治(Multi-Agent Orchestration)的核心收益在于工具隔离(Tool Scoping)与上下文隔离(Context Isolation):每个专职子 Agent(Sub-agent)只挂载 3~5 个本领域的工具,并在自己独立的临时消息栈里跑完 10 步复杂排查,最后只把 200 字的精炼结论(Summary Artifact)交还给主 Agent!

                       ┌──► [Coder Agent]   (独立上下文 + 文件/AST 工具) ──► 仅返回补丁摘要
[Supervisor 主控 Agent] ┼──► [Tester Agent]  (独立上下文 + 单测沙箱工具) ──► 仅返回测试报告
                       └──► [SecOps Agent]  (独立上下文 + 漏洞扫描工具) ──► 仅返回审计结论

手写支持 Handoff 交接与子上下文隔离的多 Agent 编排器

export class MultiAgentSwarm {
  constructor({ llm, maxHandoffs = 6 }) {
    this.llm = llm
    this.agents = new Map() // agentName -> { name, rolePrompt, registry }
    this.maxHandoffs = maxHandoffs
  }

  registerAgent({ name, description, rolePrompt, registry }) {
    this.agents.set(name, { name, description, rolePrompt, registry })
    return this
  }

  // 为当前激活的 Agent 动态注入「交接给其他专职 Agent」的虚拟 Handoff 工具
  #buildToolsWithHandoffs(currentAgentName) {
    const current = this.agents.get(currentAgentName)
    const baseTools = current.registry ? current.registry.toOpenAiTools() : []

    const peerNames = [...this.agents.keys()].filter((n) => n !== currentAgentName)
    if (peerNames.length === 0) return baseTools

    const peerDesc = peerNames
      .map((n) => `${n}: ${this.agents.get(n).description}`)
      .join(';')

    return [
      ...baseTools,
      {
        type: 'function',
        function: {
          name: 'delegate_to_agent',
          description: `将子任务委派或交接给更专业的同伴 Agent。可选目标:${peerDesc}`,
          parameters: {
            type: 'object',
            properties: {
              targetAgent: { type: 'string', enum: peerNames },
              subtaskBrief: { type: 'string', description: '清晰、自包含的子任务说明与已掌握的关键上下文' }
            },
            required: ['targetAgent', 'subtaskBrief']
          }
        }
      }
    ]
  }

  // 在独立隔离的临时上下文中运行指定子 Agent,仅向上游返回最终摘要结论
  async runIsolatedSubagent(agentName, subtaskBrief, depth = 0) {
    if (depth > this.maxHandoffs) {
      return `委派深度超出上限 (${this.maxHandoffs}),已终止交接。`
    }
    const agent = this.agents.get(agentName)
    if (!agent) throw new Error(`未找到目标 Agent: ${agentName}`)

    const subMessages = [
      { role: 'system', content: agent.rolePrompt },
      { role: 'user', content: subtaskBrief }
    ]

    for (let step = 1; step <= 6; step++) {
      const res = await this.llm.chat({
        messages: subMessages,
        tools: this.#buildToolsWithHandoffs(agentName)
      })
      const msg = res.message
      subMessages.push(msg)

      if (!msg.tool_calls || msg.tool_calls.length === 0) {
        return msg.content || ''
      }

      for (const tc of msg.tool_calls) {
        if (tc.function.name === 'delegate_to_agent') {
          const { targetAgent, subtaskBrief: nextBrief } = JSON.parse(tc.function.arguments || '{}')
          const subResult = await this.runIsolatedSubagent(targetAgent, nextBrief, depth + 1)
          subMessages.push({
            role: 'tool',
            tool_call_id: tc.id,
            name: 'delegate_to_agent',
            content: JSON.stringify({ delegatedTo: targetAgent, summary: subResult })
          })
        } else {
          const toolMsg = await agent.registry.invokeOne(tc)
          subMessages.push(toolMsg)
        }
      }
    }

    return subMessages.at(-1)?.content || '子任务已执行完毕'
  }
}

课后练习

练习 1:在 runIsolatedSubagent 中,为什么每次委派子 Agent 时都要新建一个独立的 subMessages 数组,而不是把主 Agent 的全部历史 messages 原封不动传过去?

答案:这就是多 Agent 架构中最关键的「上下文作用域隔离(Context Scoping)」。子 Agent 在执行子任务时可能调用十几次底层搜索和文件读取工具,产生上万 Token 的中间过程;在独立 subMessages 中运行并只把最终的 summary 返回给主 Agent,能彻底避免中间噪声污染主会话上下文。

练习 2:为什么 delegate_to_agent 工具的参数设计中,要求传入自包含的 subtaskBrief,且必须对递归委派深度 depth > this.maxHandoffs 做限制?

答案:因为子 Agent 看不到主 Agent 的完整对话历史,主 Agent 必须在 subtaskBrief 中把完成该子任务所需的背景参数交待清楚;同时限制 maxHandoffs 可以防止 Agent A 与 Agent B 因为职责边界模糊而互相无限踢皮球(Ping-Pong Loop)。