在 子代理 架构中,一个中央主 代理(通常称为 监督者)通过将子代理作为 工具 调用来协调它们。主代理决定调用哪个子代理、提供什么输入以及如何组合结果。子代理是无状态的——它们不记得过去的交互,所有对话记忆都由主代理维护。这提供了 上下文 隔离:每次子代理调用都在一个干净的上下文窗口中工作,防止主对话中的上下文膨胀。 有关内置子代理支持,请参阅 深度代理。

核心特征

  • 集中控制:所有路由都通过主代理。
  • 不直接与用户交互:子代理将结果返回给主代理,而不是用户(不过,你可以在子代理内使用 中断 来允许用户交互)。
  • 通过工具调用子代理:子代理通过工具被调用。
  • 并行执行:主代理可以在一个轮次中调用多个子代理。
监督者 vs. 路由器:监督者代理(此模式)与 路由器 不同。监督者是一个完整的代理,维护对话上下文,并在多个轮次中动态决定调用哪些子代理。路由器通常是一个单独的分类步骤,用于将任务分发给代理,而无需维护持续的对话状态。

何时使用

当你拥有多个不同的领域(例如日历、电子邮件、CRM、数据库),子代理不需要直接与用户对话,或者你需要集中式工作流控制时,请使用子代理模式。对于只有几个 工具 的简单场景,请使用 单一代理。
需要在子代理内与用户交互? 虽然子代理通常将结果返回给主代理,而不是直接与用户对话,但你可以在子代理内使用 中断 来暂停执行并收集用户输入。当子代理在继续之前需要澄清或批准时,这很有用。主代理仍然是协调者,但子代理可以在任务执行过程中从用户那里收集信息。

基本实现

核心机制是将子代理包装成一个主代理可以调用的工具:

教程:使用子代理构建个人助理

学习如何使用子代理模式构建个人助理,其中中央主代理(监督者)协调专业的工作者代理。

设计决策

在实现子代理模式时,你需要做出几个关键的设计选择。此表总结了这些选项——每个选项都在以下部分详细讨论。

同步 vs. 异步

子代理执行可以是 同步的(阻塞)或 异步的(后台)。你的选择取决于主代理是否需要结果才能继续。
不要与 Python 的 async/await 混淆。这里的“异步”指的是主代理启动一个后台作业(通常在单独的进程或服务中)并继续执行而不阻塞。

同步(默认)

默认情况下,子代理调用是 同步的:主代理在继续之前等待每个子代理完成。当主代理的下一个操作取决于子代理的结果时,请使用同步模式。 何时使用同步:
  • 主代理需要子代理的结果来构建其响应。
  • 任务具有顺序依赖性(例如获取数据 → 分析 → 响应)。
  • 子代理失败应该阻止主代理的响应。
权衡:
  • 实现简单——只需调用并等待。
  • 在所有子代理完成之前,用户看不到响应。
  • 长时间运行的任务会冻结对话。

异步

当子代理的工作是独立的——主代理不需要结果来继续与用户对话时,使用 异步执行。主代理启动一个后台作业并保持响应。 何时使用异步:
  • 子代理的工作独立于主对话流程。
  • 用户应该能够在工作进行时继续聊天。
  • 你想并行运行多个独立任务。
三工具模式:
  1. 启动作业:启动后台任务,返回作业 ID。
  2. 检查状态:返回当前状态(pending、running、completed、failed)。
  3. 获取结果:检索已完成的结果。
处理作业完成: 当作业完成时,你的应用程序需要通知用户。一种方法是:弹出一个通知,当点击时,发送一条 HumanMessage,内容如“检查 job_123 并总结结果”。

工具模式

主要有两种将子代理公开为工具的方式:

每个代理一个工具

关键思想是将子代理包装成主代理可以调用的工具:
主代理在判断任务符合子代理描述时调用该子代理工具,接收结果,并继续协调。有关细粒度控制,请参阅 上下文工程。

单一调度工具

另一种方法使用单个参数化工具来为独立任务调用临时子代理。与 每个代理一个工具 方法(每个子代理被包装成一个单独的工具)不同,此方法使用基于约定的单一 task 工具:任务描述作为人类消息传递给子代理,子代理的最终消息作为工具结果返回。 当你希望将代理开发分散到多个团队,需要将复杂任务隔离到单独的上下文窗口中,需要一种可扩展的方式来添加新代理而无需修改协调器,或者更喜欢约定而非自定义时,请使用此方法。这种方法以在上下文工程上的灵活性换取了代理组合的简单性和强大的上下文隔离性。 核心特征:
  • 单一任务工具:一个参数化工具,可以通过名称调用任何已注册的子代理。
  • 基于约定的调用:通过名称选择代理,任务作为人类消息传递,最终消息作为工具结果返回。
  • 团队分布式:不同的团队可以独立开发和部署代理。
  • 代理发现:子代理可以通过系统提示(列出可用代理)或通过 渐进式披露(通过工具按需加载代理信息)来发现。
这种方法的一个有趣之处在于,子代理可能与主代理具有完全相同的能力。在这种情况下,调用子代理真正是为了上下文隔离——允许复杂的、多步骤的任务在隔离的上下文窗口中运行,而不会膨胀主代理的对话历史。子代理自主完成其工作,并只返回一个简明的摘要,从而保持主线程的专注和高效。

上下文工程

控制上下文在主代理及其子代理之间如何流动: 另请参阅我们关于代理 上下文工程 的综合指南。

子代理规范

与子代理关联的 名称 和 描述 是主代理了解应调用哪些子代理的主要方式。这些都是提示控制杠杆——请谨慎选择它们。
  • 名称:主代理引用子代理的方式。保持清晰和面向操作(例如 research_agent、code_reviewer)。
  • 描述:主代理对子代理能力的了解。具体说明它处理什么任务以及何时使用它。
对于 单一调度工具 设计,你必须额外向主代理提供有关它可以调用的子代理的信息。 你可以根据代理数量以及你的注册表是静态还是动态,以不同方式提供此信息:

系统提示枚举

直接在主代理的系统提示中列出可用代理。主代理将代理列表及其描述作为其指令的一部分。 何时使用:
  • 你有一个小型、固定的代理集合(< 10 个)。
  • 代理注册表很少更改。
  • 你想要最简单的实现。
示例:

调度工具上的枚举约束

在调度工具的 agent_name 参数中添加枚举约束。这提供了类型安全,并使可用代理在工具架构中显式化。 何时使用:
  • 你有一个小型、固定的代理集合(< 10 个)。
  • 你想要类型安全和显式的代理名称。
  • 你更喜欢基于架构的验证而不是基于提示的指导。
示例:

基于工具的发现

提供一个单独的工具(例如 list_agents 或 search_agents),主代理可以调用它来按需发现可用代理。这实现了渐进式披露并支持动态注册表。 何时使用:
  • 你有很多代理(> 10)或注册表正在增长。
  • 代理注册表经常更改或具有动态性。
  • 你希望减少提示大小和令牌使用量。
  • 不同的团队独立管理不同的代理。
示例:

子代理输入

自定义子代理执行任务时接收的上下文。通过从代理的状态中提取,添加在静态提示中不易捕获的输入——完整的消息历史、先前的结果或任务元数据。
子代理输入示例 可展开

子代理输出

自定义主代理收到的内容,以便它能做出良好的决策。两种策略:
  1. 提示子代理:指定应该返回什么。一个常见的失败模式是子代理执行工具调用或推理,但未在其最终消息中包含结果——提醒它监督者只看到最终输出。
  2. 在代码中格式化:在返回之前调整或丰富响应。例如,除了最终文本之外,使用 Command 传回特定的状态键。
子代理输出示例 可展开

检查点与状态检查

默认情况下,子代理使用 继承的检查点器 模式——每次调用都从全新状态开始,支持 中断,并且可以安全地并行运行。如果你需要子代理在调用之间维护自己的持久对话历史,请使用 checkpointer=True(持续模式)编译它。有关模式的完整比较,请参阅 子图持久性。 由于子代理是在工具函数内部调用的,LangGraph 无法 静态发现 它们。这意味着使用 subgraphs 的 get_state 不会返回子代理状态。如果你需要读取嵌套图状态(例如在 中断 期间),请改为在自定义图中从 节点函数 调用子代理。有关每种模式如何影响状态可见性的详细信息,请参阅 子图持久性。

从 langgraph-supervisor 迁移

langgraph-supervisor 包已不再积极维护。学习如何从 create_supervisor 迁移到子代理模式,包括使用外部 API 回调的中断和恢复流程。