第 27 课: 多 Agent 协作

2 阅读6分钟

课程目标

掌握多 Agent 系统的设计模式:supervisor(主管)、swarm(群体)、hierarchical(层级)。精读子 Agent 封装为工具的实现、withAgentName() 的消息标识机制,以及 Runtime 上下文的传递。


27.1 为什么需要多 Agent

单个 Agent 面对复杂任务时会遇到瓶颈:

  • 工具过多:LLM 在 20+ 工具中选择准确率下降
  • 职责混杂:一个 system prompt 难以同时描述多种角色
  • 上下文膨胀:所有子任务共享同一对话历史

多 Agent 系统通过分工协作解决这些问题。


27.2 多 Agent 模式分类

模式核心思想适用场景
Supervisor一个主管 Agent 协调多个专家子 Agent任务可清晰分派的场景
SwarmAgent 之间动态切换控制权多角色对话
Hierarchical多层级 supervisor大规模复杂系统
Debate多个 Agent 辩论后汇总结论需要多角度分析的决策

27.3 Supervisor 模式:子 Agent 封装为工具

LangChain.js 中多 Agent 的主要实现方式是将子 Agent 封装为工具

源码位置: examples/src/multi-agent/subagents-personal-assistant.ts

import { tool, createAgent } from "langchain";
import { z } from "zod";

// 步骤 1:创建专家子 Agent
const calendarAgent = createAgent({
  model: llm,
  tools: [createCalendarEvent, getAvailableTimeSlots],
  systemPrompt: "你是日历调度助手...",
});

const emailAgent = createAgent({
  model: llm,
  tools: [sendEmail],
  systemPrompt: "你是邮件助手...",
});

// 步骤 2:将子 Agent 封装为工具
const scheduleEvent = tool(
  async ({ request }) => {
    const result = await calendarAgent.invoke({
      messages: [{ role: "user", content: request }],
    });
    const lastMessage = result.messages[result.messages.length - 1];
    return lastMessage.text;
  },
  {
    name: "schedule_event",
    description: "使用自然语言调度日历事件",
    schema: z.object({
      request: z.string().describe("自然语言调度请求"),
    }),
  }
);

const manageEmail = tool(
  async ({ request }) => {
    const result = await emailAgent.invoke({
      messages: [{ role: "user", content: request }],
    });
    const lastMessage = result.messages[result.messages.length - 1];
    return lastMessage.text;
  },
  {
    name: "manage_email",
    description: "使用自然语言发送邮件",
    schema: z.object({
      request: z.string().describe("自然语言邮件请求"),
    }),
  }
);

// 步骤 3:创建 supervisor Agent
const supervisorAgent = createAgent({
  model: llm,
  tools: [scheduleEvent, manageEmail],
  systemPrompt: "你是一个个人助手,可以安排日历和发送邮件...",
});

关键设计:supervisor Agent 不直接访问底层工具(如 createCalendarEvent),而是通过封装的工具接口与子 Agent 通信。这保持了职责分离。


27.4 withAgentName — Agent 身份标识

在多 Agent 系统中,不同 Agent 的消息会混在同一个对话历史中。withAgentName() 通过 XML 标签在消息内容中嵌入 Agent 名称。

源码位置: libs/langchain/src/agents/withAgentName.ts

export function withAgentName(
  model: LanguageModelLike,
  agentNameMode: AgentNameMode
): LanguageModelLike {
  let processInputMessage: (message: BaseMessageLike) => BaseMessageLike;
  let processOutputMessage: (message: BaseMessage) => BaseMessage;

  if (agentNameMode === "inline") {
    processInputMessage = _addInlineAgentName;
    processOutputMessage = _removeInlineAgentName;
  }

  return RunnableSequence.from([
    RunnableLambda.from(processInputMessages),
    model,
    RunnableLambda.from(processOutputMessage),
  ]);
}

它构建了一个三步管道:处理输入消息 → 调用模型 → 处理输出消息

名称编码方式

源码位置: libs/langchain/src/agents/utils.ts:97

export function _addInlineAgentName<T extends BaseMessageLike>(message: T): T | AIMessage {
  if (!AIMessage.isInstance(message) || !message.name) return message;

  const { name } = message;
  if (typeof message.content === "string") {
    return new AIMessage({
      ...message.lc_kwargs,
      content: `<name>${name}</name><content>${message.content}</content>`,
      name: undefined,
    });
  }
  // 数组内容的处理...
}

编码前: AIMessage { name: "calendar_agent", content: "会议已安排" } 编码后: AIMessage { content: "<name>calendar_agent</name><content>会议已安排</content>" }

对应的解码函数 _removeInlineAgentName 使用正则提取内容:

const NAME_PATTERN = /<name>(.*?)<\/name>/s;
const CONTENT_PATTERN = /<content>(.*?)<\/content>/s;

在 createAgent 中使用

const agent = createAgent({
  model: llm,
  tools: [...],
  name: "CalendarAgent",          // 设置 Agent 名称
  includeAgentName: "inline",     // 启用 inline 名称模式
});

27.5 Runtime — Agent 运行时上下文

Runtime 类型定义了中间件可访问的运行时信息:

源码位置: libs/langchain/src/agents/runtime.ts:63

export type Runtime<TContext = unknown> = Partial<
  Omit<LangGraphRuntime<TContext>, "context" | "configurable">
> & WithMaybeContext<TContext> & {
  configurable?: {
    thread_id?: string;
    [key: string]: unknown;
  };
};

Runtime 提供:

  • context:用户传入的上下文数据(通过 contextSchema 定义)
  • configurable:可配置参数(如 thread_id
  • signal:取消信号(AbortSignal)
  • writer:流式写入器
  • interrupt:人机协作中断函数

27.6 子 Agent 的上下文传递

在 supervisor 模式中,子 Agent 可以通过 getCurrentTaskInput 访问父 Agent 的状态:

import { getCurrentTaskInput } from "@langchain/langgraph";

const scheduleEvent = tool(
  async ({ request }, config) => {
    // 获取父 Agent 线程中的消息
    const currentMessages = getCurrentTaskInput<BuiltInState>(config).messages;
    const originalUserMessage = currentMessages.find(HumanMessage.isInstance);

    const prompt = `
      用户原始请求:${originalUserMessage?.content}
      子任务:${request}
    `;

    const result = await calendarAgent.invoke({
      messages: [{ role: "user", content: prompt }],
    });
    return result.messages.at(-1)?.text;
  },
  { name: "schedule_event", description: "...", schema: z.object({ ... }) }
);

27.7 多 Agent + Human-in-the-Loop

结合 humanInTheLoopMiddleware,可以在子 Agent 执行敏感操作前暂停等待人工审批:

import { humanInTheLoopMiddleware, createAgent } from "langchain";
import { MemorySaver, Command } from "@langchain/langgraph";

const calendarAgent = createAgent({
  model: llm,
  tools: [createCalendarEvent],
  middleware: [
    humanInTheLoopMiddleware({
      interruptOn: { create_calendar_event: true },
      descriptionPrefix: "日历事件待审批",
    }),
  ],
});

// supervisor 使用 checkpointer 保存中断状态
const supervisor = createAgent({
  model: llm,
  tools: [scheduleEventTool],
  checkpointer: new MemorySaver(),
});

const config = { configurable: { thread_id: "session-1" } };

// 第一次调用 — 会在工具执行前中断
const stream = await supervisor.stream(
  { messages: [{ role: "user", content: "安排明天2点的会议" }] },
  config
);

// 收集中断信息
for await (const step of stream) { /* 处理中断 */ }

// 审批后恢复执行
const resumeStream = await supervisor.stream(
  new Command({ resume: { [interruptId]: { decisions: [{ type: "approve" }] } } }),
  config
);

27.8 设计多 Agent 系统的原则

  1. 最小权限:每个子 Agent 只拥有其职责所需的工具
  2. 清晰的接口:子 Agent 通过工具描述定义能力边界
  3. 独立的上下文:子 Agent 有自己的对话历史,避免上下文污染
  4. 统一的错误处理:supervisor 层面统一处理子 Agent 的失败

27.9 实战练习:构建"研究助手"系统

import { z } from "zod";
import { createAgent, tool } from "langchain";

// 研究员 Agent
const researcherAgent = createAgent({
  model: "openai:gpt-4o",
  tools: [searchTool, readUrlTool],
  systemPrompt: "你是一个研究员,负责搜索和收集信息。",
  name: "researcher",
});

// 撰写员 Agent
const writerAgent = createAgent({
  model: "openai:gpt-4o",
  tools: [],
  systemPrompt: "你是一个技术写手,根据收集的信息撰写报告。",
  name: "writer",
});

// 审核员 Agent
const reviewerAgent = createAgent({
  model: "openai:gpt-4o",
  tools: [],
  systemPrompt: "你是一个审核员,检查报告的准确性和完整性。",
  name: "reviewer",
});

// 封装为工具
const research = tool(
  async ({ topic }) => {
    const result = await researcherAgent.invoke({
      messages: [{ role: "user", content: `研究:${topic}` }],
    });
    return result.messages.at(-1)?.text ?? "";
  },
  {
    name: "research",
    description: "搜索和收集指定主题的信息",
    schema: z.object({ topic: z.string() }),
  }
);

const writeReport = tool(
  async ({ content }) => {
    const result = await writerAgent.invoke({
      messages: [{ role: "user", content: `根据以下信息撰写报告:\n${content}` }],
    });
    return result.messages.at(-1)?.text ?? "";
  },
  {
    name: "write_report",
    description: "根据研究信息撰写报告",
    schema: z.object({ content: z.string() }),
  }
);

const reviewReport = tool(
  async ({ report }) => {
    const result = await reviewerAgent.invoke({
      messages: [{ role: "user", content: `审核以下报告:\n${report}` }],
    });
    return result.messages.at(-1)?.text ?? "";
  },
  {
    name: "review_report",
    description: "审核报告的准确性和完整性",
    schema: z.object({ report: z.string() }),
  }
);

// Supervisor Agent
const supervisor = createAgent({
  model: "openai:gpt-4o",
  tools: [research, writeReport, reviewReport],
  systemPrompt: `你是研究团队的主管。工作流程:
    1. 使用 research 工具收集信息
    2. 使用 write_report 工具撰写报告
    3. 使用 review_report 工具审核报告`,
});

const result = await supervisor.invoke({
  messages: [{ role: "user", content: "写一篇关于 LangChain.js Agent 系统的技术报告" }],
});

27.10 源码精读路线

优先级文件关注点
P0examples/src/multi-agent/subagents-personal-assistant.ts完整的 supervisor 多 Agent 示例
P0examples/src/createAgent/supervisor.tssupervisor + 子 Agent 封装为工具
P1agents/withAgentName.tsAgent 名称的编码/解码
P1agents/utils.ts:97-234_addInlineAgentName / _removeInlineAgentName
P2agents/runtime.tsRuntime 类型定义与上下文传递
P2agents/types.ts:522-881CreateAgentParams — 完整的 Agent 创建参数

本课收获总结

级别你应该掌握的
🟢 基础理解多 Agent 的典型场景:分工、审核、辩论
🔵 中阶学会用 supervisor 模式编排多个 Agent:子 Agent 封装为工具
🟡 高阶掌握 Agent 间的消息传递(withAgentName)与上下文共享(getCurrentTaskInput
🟠 资深分析多 Agent 的错误传播与恢复策略;结合 human-in-the-loop
🔴 架构设计可扩展的多 Agent 架构:最小权限、清晰接口、独立上下文

下一课预告

第 28 课深入生产级 Agent 的错误处理、限流与安全 —— 理解 Agent 错误类型、最大迭代控制、超时机制和工具调用安全。