6. 多 Agent 编排

上一篇文章实现了 SubAgent:主 Agent 把子任务委派给一个临时工,临时工干完活、返回结果、然后消失。

用了一阵之后会发现一个明显的局限:SubAgent 没有身份,也没有记忆。

你让 SubAgent 去检查 /src/api/ 的安全问题,它干完汇报了。五分钟后你又想让它”再查一遍,看看上次漏了什么”——它一脸茫然。它不是同一个人,是一个全新的临时工,只是恰好接了相似的活。

现实中的团队不这样。你找同事 A 帮忙看代码,十分钟后你再找他,“刚才那个问题你再确认一下”——他知道你在说什么。有身份,有记忆,你们的协作是持续的。

SubAgent 是函数调用,调完就忘。多 Agent 团队需要的是对象——活的,有状态的。

从一次性委派到持久团队

把 SubAgent 变成团队成员,需要补齐三样东西:

1. 持久化。 Agent 跨多轮 chat() 调用存活。它记得上一轮做了什么、结果是什么——不需要每次都从零开始。

2. 身份与生命周期。 Agent 有名字、有角色。它被创建、持续存活、最终解散。生命周期是显式管理的,而不是”函数返回就没了”。

3. 通信通道。 Agent 之间可以互相发消息。点对点,或者广播。不是彼此隔离的孤岛。

这三样东西,用两个类就能装下。

Agent 类

SubAgent 的实现是一个函数——输入 task,输出结果,中间状态全是局部变量,函数返回就释放。

现在把它升级为一个类:

class Agent {
  name: string;
  role: string;
  private messages: ChatMessage[];
  private inbox: Message[];
 
  constructor(name: string, role: string, systemPrompt: string) {
    this.name = name;
    this.role = role;
    this.messages = [{ role: "system", content: systemPrompt }];
    this.inbox = [];
  }
 
  receive(from: string, content: string) {
    this.inbox.push({ from, content, timestamp: Date.now() });
  }
 
  async chat(task?: string): Promise<string> {
    // 干活之前,先把收件箱里的消息同步进对话历史
    while (this.inbox.length > 0) {
      const msg = this.inbox.shift()!;
      this.messages.push({
        role: "user",
        content: `[消息来自 ${msg.from}]: ${msg.content}`,
      });
    }
 
    if (task) {
      this.messages.push({ role: "user", content: task });
    }
 
    const result = await agentLoop(this.messages, {
      tools: TEAM_TOOLS,
      maxIterations: 15,
    });
 
    this.messages = result.messages; // 保留对话历史,下次 chat 继续
    return extractFinalAnswer(result.messages);
  }
}

跟 SubAgent 的关键区别就一个:messages 从局部变量变成了实例属性。

函数版的 SubAgent:每次调用创建新数组,返回时销毁。下一次调用从头开始。

类版的 Agent:messages 挂在 this 上,跟对象同生命周期。第一次 chat() 做的事情,第二次 chat() 还记得。

inbox 同理——其他 Agent 发来的消息先存在这里,chat() 执行前一口气读完。如果在 chat() 中途塞新消息进来,Agent 正在思考的上下文就被污染了。消息只在执行边界消费。

Team 类

Agent 类解决了单个成员的持久化和通信接收端。还需要一个容器来管理一群 Agent——创建、通信路由、销毁。

class Team {
  private members: Map<string, Agent> = new Map();
  private leader: string;
 
  constructor(leader: string) {
    this.leader = leader;
  }
 
  hire(name: string, role: string, systemPrompt: string): Agent {
    const agent = new Agent(name, role, systemPrompt);
    this.members.set(name, agent);
    return agent;
  }
 
  send(from: string, to: string, content: string) {
    const target = this.members.get(to);
    if (!target) throw new Error(`Agent ${to} not found`);
    target.receive(from, content);
  }
 
  broadcast(from: string, content: string) {
    for (const [name, agent] of this.members) {
      if (name !== from) {
        agent.receive(from, content);
      }
    }
  }
 
  get(name: string): Agent | undefined {
    return this.members.get(name);
  }
 
  disband() {
    this.members.clear();
  }
}

Team 不做决策。它不替 LLM 判断谁该干什么、什么时候通信、通信内容是什么。它只提供两个能力:成员管理(hire、disband)和消息路由(send、broadcast)。

决策权在 LLM 手里——它控制每个 Agent 的 chat() 什么时候调、传什么 task、给谁发消息。Team 只是管道。

这是对 SubAgent 那篇”代码提供能力,LLM 控制流程”的延续。代码提供的是类——Agent 和 Team。LLM 控制的是运行时——组什么团队、谁跟谁通信、什么时候解散。

运行流程

一个典型的团队协作流程是这样的:

// 1. 组建团队
const team = new Team("planner");
 
team.hire(
  "planner",
  "规划者",
  "你是项目规划者。分析用户需求,拆解任务,分配给合适的成员。"
);
 
team.hire(
  "implementer",
  "实现者",
  "你是代码实现者。根据规划者的方案写代码。有问题就通过 inbox 向规划者确认。"
);
 
team.hire(
  "reviewer",
  "审查者",
  "你是代码审查者。审查实现者的代码,找出 bug 和设计问题。不要改代码,只指出问题。"
);
 
// 2. Planner 先分析需求,产出任务分配方案
const plan = await team.get("planner")!.chat(
  "用户需要一个用户认证模块,包含登录、注册、密码重置。"
);
 
// Planner 通知实现者开始干活
team.send("planner", "implementer", `按这个方案实现:\n${plan}`);
 
// 3. 实现者收到消息后开始写代码
const code = await team.get("implementer")!.chat();
 
// 实现完成,通知审查者
team.broadcast("implementer", `代码已完成。请审查者检查。\n${code}`);
 
// 4. 审查者收到广播,开始审查
const review = await team.get("reviewer")!.chat();
 
// 审查发现问题,发回给实现者修改
team.send("reviewer", "implementer", `审查意见:\n${review}`);
 
// 5. 实现者根据意见修改
const revised = await team.get("implementer")!.chat();
 
// 修改完成,审查者复查
team.send("implementer", "reviewer", `已修改,请复查。\n${revised}`);
const finalReview = await team.get("reviewer")!.chat();
 
// 6. 解散团队
team.disband();

流程里的每一步——谁先开始、谁给谁发消息、什么时候算完——都是 LLM 在做决策。Team 和 Agent 只提供基础设施:存储状态、路由消息、保证每个 Agent 能独立思考和通信。

回到面向对象

多 Agent 编排,说穿了就是把函数改写成类。

SubAgent 是静态方法——输入 task,输出 result。中间状态全是局部变量,函数返回就释放。没有身份,没有记忆,不能通信。

Agent 是对象——有自己的状态(messages),有身份(namerole),能接收消息(inbox)。

Team 是容器——管一组 Agent 对象的生命周期(hiredisband)和通信路由(sendbroadcast)。

把整个系列的演进串起来看:

  1. Agent Loop — 一个 for 循环,最朴素的函数调用
  2. Memory — 把结果持久化,循环有了”过去”
  3. Plan — 任务拆分,循环套循环
  4. Rules / Skills / MCP — 给循环扩展输入
  5. SubAgent — 循环可以 spawn 子循环,但还是函数调函数
  6. Multi-Agent — 函数变成了对象。循环不再是一次性的执行过程,而是一个活的实体

至于 LLM 怎么用这些对象——组什么团队、谁跟谁通信、什么时候解散——它自己决定。代码只管把类写好。


下一篇7. 上下文压缩