子智能体 (Subagents)
在子智能体 (subagents) 架构中,一个中央主 Agent(通常被称为主管 (supervisor))通过将子智能体作为工具调用来协调它们。主 Agent 决定调用哪个子智能体、提供什么输入以及如何组合结果。默认情况下,子智能体是无状态的——它们不保留过去的交互记忆,所有对话记忆均由主 Agent 维护。这提供了上下文隔离:每次子智能体调用都在干净的上下文窗口中运行,从而防止主对话中的上下文膨胀。
当子智能体需要这些上下文时,你也可以将主 Agent 的对话历史传递给子智能体。请参见子智能体输入。
有关内置的子智能体支持,请参见 Deep Agents。
核心特性
- 集中式控制:所有路由均通过主 Agent 进行
- 无直接用户交互:子智能体将结果返回给主 Agent,而不是用户(尽管你可以在子智能体中使用中断 (interrupts) 来允许用户交互)
- 通过工具调用子智能体:子智能体通过工具进行调用
- 并行执行:主 Agent 可以在单轮交互中调用多个子智能体
NOTE
主管 vs. 路由器 (Supervisor vs. Router):主管 Agent(即本模式)与路由器 (router) 不同。主管是一个完整的 Agent,它维护对话上下文,并在多轮交互中动态决定调用哪些子智能体。而路由器通常只是单一的分类步骤,仅负责分发给各个 Agent,而不维护持续的对话状态。
何时使用
当你有多个不同的领域(例如日历、电子邮件、CRM、数据库)、子智能体无需直接与用户对话,或者你希望对工作流进行集中控制时,请使用子智能体模式。对于只有少数几个工具的更简单场景,请使用单一 Agent。
TIP
需要在子智能体内进行用户交互? 尽管子智能体通常将结果返回给主 Agent 而不是直接与用户对话,但你可以在子智能体内使用中断 (interrupts) 来暂停执行并收集用户输入。这在子智能体在继续执行之前需要澄清或批准时非常有用。主 Agent 仍然是协调器,但子智能体可以在任务中途向用户收集信息。
基础实现
其核心机制是将子智能体包装为主 Agent 可以调用的工具:
import { createAgent, tool } from "langchain";
import { z } from "zod";
// 创建子智能体
const subagent = createAgent({ model: "google:gemini-3.6-flash", tools: [...] });
// 将其包装为工具
const callResearchAgent = tool(
async ({ query }) => {
const result = await subagent.invoke({
messages: [{ role: "user", content: query }]
});
return result.messages.at(-1)?.content;
},
{
name: "research",
description: "Research a topic and return findings",
schema: z.object({ query: z.string() })
}
);
// 将子智能体作为工具的主 Agent
const mainAgent = createAgent({ model: "google:gemini-3.6-flash", tools: [callResearchAgent] });了解如何使用子智能体模式构建个人助理,其中由一个中央主 Agent(主管)协调专门的 Worker Agent。
设计决策
在实现子智能体模式时,你需要做出几个关键的设计抉择。下表总结了这些选项——下文各节将对每一项进行详细介绍。
| 决策 | 选项 |
|---|---|
| 同步 vs. 异步 | 同步(阻塞) vs. 异步(后台) |
| 工具模式 | 单 Agent 单工具 vs. 单一分发工具 |
| 子智能体规范 | 系统提示词 vs. 枚举约束 vs. 基于工具的发现(仅限单一分发工具) |
| 子智能体输入 | 仅查询 vs. 完整上下文 |
| 子智能体输出 | 子智能体结果 vs. 完整对话历史 |
同步 vs. 异步
子智能体的执行可以是同步(阻塞)或异步(后台)。你的选择取决于主 Agent 是否需要该结果才能继续。
| 模式 | 主 Agent 行为 | 最适合 | 权衡 |
|---|---|---|---|
| 同步 (Sync) | 等待子智能体完成 | 主 Agent 需要结果才能继续 | 简单,但会阻塞对话 |
| 异步 (Async) | 在子智能体于后台运行时继续响应 | 独立任务,用户无需等待 | 响应迅速,但复杂度更高 |
TIP
请勿将其与 Python 的 async/await 混淆。在这里,“异步”意味着主 Agent 启动一个后台任务(通常在单独的进程或服务中)并在不阻塞的情况下继续运行。
同步(默认)
默认情况下,子智能体调用是同步的:主 Agent 会等待每个子智能体执行完成,然后再继续。当主 Agent 的下一步行动依赖于子智能体的结果时,请使用同步模式。
何时使用同步:
- 主 Agent 需要子智能体的结果来组织其回复
- 任务具有顺序依赖性(例如:获取数据 → 分析 → 回复)
- 子智能体的失败应当阻断主 Agent 的回复
权衡:
- 实现简单——只需调用并等待即可
- 在所有子智能体完成之前,用户看不到任何响应
- 运行时间较长的任务会冻结对话
异步
当子智能体的工作是独立的——主 Agent 不需要其结果即可继续与用户对话时,请使用异步执行。主 Agent 会启动一个后台任务,并保持对用户的即时响应。
何时使用异步:
- 子智能体的工作独立于主对话流程
- 用户在工作进行期间应该能够继续聊天
- 你希望并行运行多个独立任务
三工具模式 (Three-tool pattern):
- 启动任务 (Start job):启动后台任务,返回任务 ID
- 检查状态 (Check status):返回当前状态(pending、running、completed、failed)
- 获取结果 (Get result):获取已完成的结果
处理任务完成: 当任务完成时,你的应用程序需要通知用户。一种方法是:显示一条通知,点击后发送一条类似“检查 job_123 并总结结果”的 HumanMessage。
工具模式
将子智能体作为工具公开主要有两种方式:
| 模式 | 最适合 | 权衡 |
|---|---|---|
| 单 Agent 单工具 (Tool per agent) | 对每个子智能体的输入/输出进行细粒度控制 | 更多设置工作,但可定制性更强 |
| 单一分发工具 (Single dispatch tool) | 大量子智能体、分布式团队、约定优于配置 | 组合更简单,但每个 Agent 的定制较少 |
单 Agent 单工具
其核心思想是将子智能体包装为主 Agent 可以调用的工具:
import { createAgent, tool } from "langchain";
import * as z from "zod";
// 创建子智能体
const subagent = createAgent({...});
// 将其包装为工具
const callSubagent = tool(
async ({ query }) => {
const result = await subagent.invoke({
messages: [{ role: "user", content: query }]
});
return result.messages.at(-1)?.text;
},
{
name: "subagent_name",
description: "subagent_description",
schema: z.object({
query: z.string().describe("The query to send to subagent")
})
}
);
// 将子智能体作为工具的主 Agent
const mainAgent = createAgent({ model, tools: [callSubagent] }); 当主 Agent 判定任务与子智能体的描述相匹配时,便会调用该子智能体工具,接收其结果并继续编排。有关细粒度控制,请参见上下文工程。
单一分发工具
另一种方法是使用单个参数化工具来为独立任务调用临时子智能体。与将每个子智能体包装为单独工具的单 Agent 单工具方法不同,这种方法采用基于约定的方式,使用单一的 task 工具:将任务描述作为人类消息传递给子智能体,并将子智能体的最终消息作为工具结果返回。
当你想将 Agent 开发分发给多个团队、需要将复杂任务隔离到独立的上下文窗口中、需要一种无需修改协调器即可添加新 Agent 的可扩展方式,或者更青睐约定而非定制时,可以使用这种方法。该方法牺牲了上下文工程的灵活性,换取了 Agent 组合的简单性以及强大的上下文隔离。
核心特性:
- 单一 task 工具:一个参数化工具,可以通过名称调用任何已注册的子智能体
- 基于约定的调用:通过名称选择 Agent,任务作为人类消息传递,最终消息作为工具结果返回
- 团队分发:不同的团队可以独立开发和部署 Agent
- Agent 发现机制:可以通过系统提示词(列出可用 Agent)或通过渐进式披露 (progressive disclosure)(通过工具按需加载 Agent 信息)来发现子智能体
TIP
这种方法的一个有趣之处在于,子智能体可能具有与主 Agent 完全相同的功能。在这种情况下,调用子智能体真正的核心原因在于上下文隔离——允许复杂的多步骤任务在隔离的上下文窗口中运行,而不会导致主 Agent 的对话历史膨胀。子智能体自主完成工作并仅返回简要摘要,从而保持主线程的专注与高效。
带有任务分发器的 Agent 注册表
typescriptimport { tool, createAgent } from "langchain"; import * as z from "zod"; // 由不同团队开发的子智能体 const researchAgent = createAgent({ model: "gpt-5.5", prompt: "You are a research specialist...", }); const writerAgent = createAgent({ model: "gpt-5.5", prompt: "You are a writing specialist...", }); // 可用子智能体注册表 const SUBAGENTS = { research: researchAgent, writer: writerAgent, }; const task = tool( async ({ agentName, description }) => { const agent = SUBAGENTS[agentName]; const result = await agent.invoke({ messages: [ { role: "user", content: description } ], }); return result.messages.at(-1)?.content; }, { name: "task", description: `Launch an ephemeral subagent. Available agents: - research: Research and fact-finding - writer: Content creation and editing`, schema: z.object({ agentName: z .string() .describe("Name of agent to invoke"), description: z .string() .describe("Task description"), }), } ); // 主协调 Agent const mainAgent = createAgent({ model: "gpt-5.5", tools: [task], prompt: ( "You coordinate specialized sub-agents. " + "Available: research (fact-finding), " + "writer (content creation). " + "Use the task tool to delegate work." ), });
上下文工程
控制主 Agent 与其子智能体之间的上下文流动:
| 类别 | 目的 | 影响 |
|---|---|---|
| 子智能体规范 | 确保子智能体在应被调用时得到调用 | 主 Agent 的路由决策 |
| 子智能体输入 | 确保子智能体能在优化后的上下文中良好执行 | 子智能体的性能表现 |
| 子智能体输出 | 确保主管能够根据子智能体的结果采取行动 | 主 Agent 的性能表现 |
另请参阅我们针对 Agent 的上下文工程全面指南。
子智能体规范
与子智能体关联的名称和描述是主 Agent 了解调用哪些子智能体的主要途径。这些是提示词杠杆——请审慎选择。
- 名称:主 Agent 引用该子智能体的方式。保持清晰且面向行动(例如
research_agent、code_reviewer)。 - 描述:主 Agent 对该子智能体能力的了解。明确说明它处理哪些任务以及何时使用它。
对于单一分发工具设计,你还必须为主 Agent 提供有关其可调用的子智能体的信息。 你可以根据 Agent 的数量以及注册表是静态还是动态,通过不同的方式提供此信息:
| 方法 | 最适合 | 权衡 |
|---|---|---|
| 系统提示词枚举 | 小型、静态的 Agent 列表(< 10 个 Agent) | 简单,但当 Agent 发生变化时需要更新提示词 |
| 枚举约束 | 小型、静态的 Agent 列表(< 10 个 Agent) | 类型安全且显式,但当 Agent 发生变化时需要修改代码 |
| 基于工具的发现机制 | 大型或动态的 Agent 注册表 | 灵活且可扩展,但增加了复杂度 |
系统提示词枚举
直接在主 Agent 的系统提示词中列出可用 Agent。主 Agent 将 Agent 列表及其描述作为其指令的一部分来查看。
何时使用:
- 拥有少量的固定 Agent 集合(< 10 个)
- Agent 注册表极少发生变化
- 你希望使用最简单的实现
示例:
main_agent = create_agent(
model="...",
tools=[task],
system_prompt=(
"You coordinate specialized sub-agents. "
"Available agents:\n"
"- research: Research and fact-finding\n"
"- writer: Content creation and editing\n"
"- reviewer: Code and document review\n"
"Use the task tool to delegate work."
),
)分发工具上的枚举约束
在分发工具的 agent_name 参数中添加枚举约束。这提供了类型安全性,并在工具 schema 中明确列出了可用的 Agent。
何时使用:
- 拥有少量的固定 Agent 集合(< 10 个)
- 需要类型安全和显式的 Agent 名称
- 相比于基于提示词的引导,更倾向于基于 schema 的验证
示例:
from enum import Enum
class AgentName(str, Enum):
RESEARCH = "research"
WRITER = "writer"
REVIEWER = "reviewer"
@tool
def task(
agent_name: AgentName, # 枚举约束
description: str
) -> str:
"""Launch an ephemeral subagent for a task."""
# ...基于工具的发现机制
提供一个单独的工具(例如 list_agents 或 search_agents),主 Agent 可以调用该工具来按需发现可用 Agent。这支持了渐进式披露并适配动态注册表。
何时使用:
- 拥有大量 Agent(> 10 个)或不断增长的注册表
- Agent 注册表变动频繁或是动态的
- 希望缩减提示词大小和 Token 消耗
- 不同的团队独立管理不同的 Agent
示例:
@tool
def list_agents(query: str = "") -> str:
"""List available subagents, optionally filtered by query."""
agents = search_agent_registry(query)
return format_agent_list(agents)
@tool
def task(agent_name: str, description: str) -> str:
"""Launch an ephemeral subagent for a task."""
# ...
main_agent = create_agent(
model="...",
tools=[task, list_agents],
system_prompt="Use list_agents to discover available subagents, then use task to invoke them."
)子智能体输入
自定义子智能体为执行其任务所接收的上下文。通过从 Agent 的状态中提取数据,添加在静态提示词中难以捕获的输入——完整的消息历史记录、先前的结果或任务元数据。
import { createAgent, tool, AgentState, ToolMessage } from "langchain";
import { Command } from "@langchain/langgraph";
import * as z from "zod";
// 通过 state 将完整对话历史传递给子智能体的示例。
const callSubagent1 = tool(
async ({query}) => {
const state = getCurrentTaskInput<AgentState>();
// 应用所需的任何逻辑将消息转换为合适的输入
const subAgentInput = someLogic(query, state.messages);
const result = await subagent1.invoke({
messages: subAgentInput,
// 你也可以根据需要在此传递其他 state 键。
// 请确保在主 Agent 和子智能体的
// state schema 中都定义了这些键。
exampleStateKey: state.exampleStateKey
});
return result.messages.at(-1)?.content;
},
{
name: "subagent1_name",
description: "subagent1_description",
}
);some_logic 控制子智能体接收多少上下文。仅传递任务描述 (query) 可以保持子智能体的隔离性。而传递父级的消息历史记录 (runtime.state["messages"]) 则可以为子智能体提供先前工作的基础,当它需要继续执行父级已经启动的任务时(例如继续 PR 审查或接手 Bug 修复),这非常有帮助。
Deep Agents 将这些模式命名为 mode: "isolated"(默认)和 mode: "fork",并为你实现了分支 (forking):分支子智能体会接收父级的完整消息历史记录(包括工具调用)和系统提示词。
| 模式 | 子智能体接收的内容 | 最适合 | 权衡 |
|---|---|---|---|
| 隔离模式 (Isolated)(默认) | 仅任务描述 | 需要较少先前上下文的专注工作 | 上下文隔离,但需要重新推导父级已经完成的所有工作 |
| 分支模式 (Forked) | 父级的对话历史记录(在 Deep Agents 中还包括系统提示词) | 继续执行父级已经启动的任务 | 注入了先前的上下文,但代价是更大的提示词和更弱的隔离性 |
子智能体输出
自定义主 Agent 接收回的内容,以便其能够做出明智的决策。两种策略:
- 通过提示词引导子智能体:精确指定应该返回的内容。一个常见的失败模式是子智能体执行了工具调用或推理,但并未在其最终消息中包含结果——提醒它主管只能看到最终输出。
- 在代码中格式化:在返回响应之前对其进行调整或丰富。例如,使用
Command在返回最终文本之外传回特定的状态键。
import { tool, ToolMessage } from "langchain";
import { Command } from "@langchain/langgraph";
import * as z from "zod";
const callSubagent1 = tool(
async ({ query }, config) => {
const result = await subagent1.invoke({
messages: [{ role: "user", content: query }]
});
// 返回 Command 以更新多个 state 键
return new Command({
update: {
// 从子智能体传回额外的 state
exampleStateKey: result.exampleStateKey,
messages: [
new ToolMessage({
content: result.messages.at(-1)?.text,
tool_call_id: config.toolCall?.id!
})
]
}
});
},
{
name: "subagent1_name",
description: "subagent1_description",
schema: z.object({
query: z.string().describe("The query to send to subagent1")
})
}
);检查点与状态检查
默认情况下,子智能体使用继承检查点 (inherited checkpointer) 模式——每次调用都以全新的状态开始,支持中断 (interrupts),并且能够安全地并行运行。如果你需要子智能体在跨调用之间保持其自身的持久对话历史记录,请在编译时设置 checkpointer=True(延续模式,continuations mode)。有关模式的完整比较,请参见子图持久化 (subgraph persistence)。
由于子智能体是在工具函数内部被调用的,LangGraph 无法对其进行静态发现 (statically discover)。这意味着带 subgraphs 的 get_state 将不会返回子智能体的状态。如果你需要读取嵌套图的状态(例如在中断期间),请改为在自定义图的节点函数 (node function) 中调用子智能体。有关每种模式如何影响状态可见性的详细信息,请参见子图持久化。
