跳到正文

TECH NOTE

5764 字 29 分钟 系列 4 / 22

04 Subagents 子智能体:主管派活,专家干活

ON THIS PAGE / 目录18 节

一句话讲透:主管 Agent(supervisor)手里有一份”外包名单”,每个专家(子智能体)被包装成一个工具。主管决定派谁、给什么输入、怎么合并结果;专家干完把结果交回主管,由主管对用户说话。这是最通用、最好理解的一种多 Agent 打法。


它到底是什么#

原文定义:

在 subagents 架构里,一个中心的主 Agent(常被称为 supervisor 主管)通过把子智能体当工具调用来协调它们。主 Agent 决定调哪个子智能体、给什么输入、怎么合并结果。默认情况下子智能体是无状态的——它们不记得过去的交互,所有对话记忆都由主 Agent 维护。这带来了上下文隔离:每次子智能体调用都跑在一个干净的上下文窗口里,避免主对话的上下文膨胀。

两句人话补充:

  • 无状态不是缺陷,是设计:子智能体每次调用都是”新人报到”,只拿到本次任务描述。这样它不会被主对话里的杂事污染——这正是 03 篇里 Subagents token 最省的原因。
  • 但也可以”带着上下文”去:如果子智能体需要接着主 Agent 已做的工作干(比如接着改一个没修完的 bug),你可以把主 Agent 的历史消息传进去,见下文「子智能体输入」。

📎 想要内置实现:Deep Agents 自带 subagents 支持,见 Deep Agents / Subagents。

graph LR
A[用户] --> B[主管 Agent]
B --> C[子智能体 A]
B --> D[子智能体 B]
B --> E[子智能体 C]
C --> B
D --> B
E --> B
B --> F[回复用户]

这张图在说什么:① 用户的每一句话都只进主管;② 主管按需要把一个或多个任务分发给子智能体(A/B/C 互不来往);③ 每个子智能体的结果必须回到主管(C→B、D→B、E→B 这三条回边是这个模式的灵魂);④ 最后由主管统一回复用户。


五个关键特征#

特征原文说人话
集中式控制Centralized control:所有路由都经过主 Agent主管是唯一入口和唯一出口,好管控、好审计
不与用户直接交互No direct user interaction:子智能体把结果返回主 Agent,而不是给用户用户感知不到”背后有几个专家”;但可以在子智能体内部用 interrupt 让用户参与
子智能体即工具Subagents via tools:子智能体是通过工具被调用的这句话是全部实现的核心:包装成 @tool 就是一个子智能体
可并行执行Parallel execution:主 Agent 可以在同一个回合里调多个子智能体一次问三个领域不需要串行等三次
无状态(默认)(原文正文)由主 Agent 维护全部对话记忆隔离强、成本稳定,代价是每次都重新推导

📌 主管 ≠ 路由(原文的 Note): 主管 Agent 是一个完整的 Agent——它维护对话上下文,并在多个回合里动态决定该调哪些子智能体。 Router 路由通常只是一次分类步骤,把请求分发出去,不维护持续的对话状态。 一句话:主管会聊天、会记事儿、会连续决策;路由只负责”看一眼、指个路”(详见 07 篇)。


什么时候用它#

原文给的判断标准:

  • 你手上有多个互不相同的领域(比如日历、邮件、CRM、数据库);
  • 子智能体不需要直接跟用户对话;
  • 你想要集中式的工作流控制。

以及一句很重要的”反向提醒”:

如果只是少量工具的简单场景,用一个单 Agent 就够了。

💡 提示(原文):需要在子智能体里跟用户交互怎么办? 子智能体通常把结果返回主管而不是直接跟用户对话,但你可以在子智能体内部用 interrupt 暂停执行、收集用户输入。适用于子智能体需要澄清问题或要审批才能继续的场景——主管仍是指挥者,但子智能体可以在任务中途向用户要信息。


最小可用实现:三行核心思想#

原文的”核心机制”就一段代码——把子智能体包成工具:

from langchain.tools import tool
from langchain.agents import create_agent
# 1) 先建一个子智能体(它自己也是一个完整的 Agent)
subagent = create_agent(model="google_genai:gemini-3.6-flash", tools=[...])
# 2) 把它包成一个工具:工具名 + 描述就是给主管看的"外包说明"
@tool("research", description="Research a topic and return findings")
def call_research_agent(query: str):
result = subagent.invoke({"messages": [{"role": "user", "content": query}]})
return result["messages"][-1].content # 只把最终结论交回主管
# 3) 主管 Agent:工具清单里挂着这个子智能体
main_agent = create_agent(model="google_genai:gemini-3.6-flash", tools=[call_research_agent])

这段代码在说什么:① 子智能体本身就是 create_agent 造出来的普通 Agent;② @tool 装饰器把它变成主管可调用的工具,工具名和描述是主管决策的唯一依据(所以别起 helper、do_stuff 这种名字);③ 只把 messages[-1].content(最终回答)返回给主管——中间的工具调用、推理过程都被”关在”子智能体的上下文里。

📎 官方实战教程:Build a personal assistant with subagents(本笔记的精讲版见 09)


五个要拿主意的设计决策#

原文把实现 Subagents 时的选择整理成一张表,下面逐项展开:

决策点可选方案一句话建议
同步 vs 异步同步(阻塞)vs 异步(后台任务)主管需要结果才能继续 → 同步;用户不该干等 → 异步
工具模式一智能体一工具 vs 单一派发工具要精细控制 → 一智能体一工具;要”约定优于配置” → 单一 task 工具
子智能体规格系统提示枚举 vs Enum 约束 vs 工具化发现少于 10 个用前两种;多/动态用第三种
子智能体输入只给任务描述 vs 给完整上下文默认隔离;要”接着干”就给历史
子智能体输出只回结果 vs 回完整会话历史默认回精简结果;需要额外状态用 Command

决策一:同步还是异步#

方式主管的行为最适合代价
同步 Sync等子智能体做完才继续主管需要结果才能往下走简单,但会把对话卡住
异步 Async子智能体在后台跑,主管继续聊独立任务,用户不该干等响应快,但实现更复杂

💡 提示(原文):别和 Python 的 async/await 搞混。这里的”异步”指主管启动一个后台作业(通常跑在另一个进程或服务里)然后不阻塞地继续。

同步(默认行为)#

默认情况下子智能体调用是同步的:主管等每个子智能体做完才继续。当下一个动作依赖子智能体结果时,用同步。

sequenceDiagram
participant U as 用户
participant M as 主管 Agent
participant R as 调研子智能体
U->>M: "东京天气怎么样?"
M->>R: research("东京天气")
Note over M: 阻塞等待结果…
R-->>M: "当前 72°F,晴"
M-->>U: "东京现在 72°F,晴天"

这张图在说什么:① 主管把任务交给调研子智能体后什么都不做,就等;② 子智能体返回结论;③ 主管再基于结论回答用户。用户在这段时间里看不到任何东西。

什么时候用同步(原文):

  • 主管需要子智能体的结果才能组织回答;
  • 任务之间有顺序依赖(例如:取数据 → 分析 → 回答);
  • 子智能体失败时应该阻塞主管的回答(比如涉及合规、金额)。

代价(原文):实现简单(调了就等);但所有子智能体做完之前用户看不到任何响应;长任务会把对话冻住。

异步(后台作业)#

当子智能体的工作与主对话流程相互独立时用异步:主管甩出一个后台任务,自己继续陪用户聊。

sequenceDiagram
participant U as 用户
participant M as 主管 Agent
participant J as 作业系统
participant C as 合同审查子智能体
U->>M: "帮我审一下这份并购合同"
M->>J: run_agent("legal_reviewer", task)
J->>C: 启动 Agent
J-->>M: job_id: "job_123"
M-->>U: "已开始审查(job_123)"
Note over C: 正在审 150+ 页…
U->>M: "进度怎么样?"
M->>J: check_status(job_id)
J-->>M: "running"
M-->>U: "还在审…"
U->>M: "好了吗?"
M->>J: check_status(job_id)
J-->>M: "completed"
M->>J: get_result(job_id)
J-->>M: 合同分析结果
M-->>U: "审查完成:[结论]"

这张图在说什么:① 主管不等待,立刻给用户一个作业号;② 用户随时可以问进度;③ 作业完成后主管取结果再汇报。用户全程不被阻塞。

什么时候用异步(原文):

  • 子智能体的工作与主对话流程相互独立;
  • 用户应该能在后台干活时继续聊天;
  • 你想并行跑多个独立任务。

“三工具”模式(原文):

  1. Start job(启动):踢出后台任务,返回一个 job ID;
  2. Check status(查状态):返回当前状态(pending / running / completed / failed);
  3. Get result(取结果):取回已完成的结果。

作业完成后怎么通知用户(原文):一种做法是弹一条通知,用户点击后向 Agent 发一条 HumanMessage,例如”检查 job_123 并总结结果”。这样”推”就变成了”拉”,不需要 Agent 主动打断用户。


决策二:工具模式——一智能体一工具,还是一个大 task 工具#

模式最适合代价
一智能体一工具需要对每个子智能体的输入输出做精细控制配置更多,但更可定制
单一派发工具智能体很多、团队分布式开发、“约定优于配置”组合更简单,但每个子智能体可定制的空间更小

一智能体一工具#

graph LR
A[用户] --> B[主管 Agent]
B --> C[子智能体 A]
B --> D[子智能体 B]
B --> E[子智能体 C]
C --> B
D --> B
E --> B
B --> F[回复用户]

这张图在说什么:主管的工具清单里平铺着三个工具,每个工具对应一个子智能体,各自可以有自己独立的入参和返回值设计。

from langchain.tools import tool
from langchain.agents import create_agent
# 每个子智能体 = 一个独立工具(入参/返回值都能单独设计)
subagent = create_agent(model="...", tools=[...])
@tool("subagent_name", description="subagent_description")
def call_subagent(query: str):
result = subagent.invoke({"messages": [{"role": "user", "content": query}]})
return result["messages"][-1].content
main_agent = create_agent(model="...", tools=[call_subagent])

主管在判断”这个任务符合某个子智能体的描述”时调用它,拿到结果后继续编排。想要细粒度控制上下文,见下文「上下文工程」。

单一派发工具(task 模式)#

另一种做法:只暴露一个参数化工具,按名字去调起”临时子智能体”(ephemeral sub-agent)来干独立任务。

原文对机制的描述很精确:

  • 任务描述(description)会作为一条 human message 传给子智能体;
  • 子智能体的最后一条消息作为工具结果返回;
  • 和”一智能体一工具”不同,这是基于约定的:一个 task 工具搞定所有子智能体。

什么时候用它(原文):

  • 想把 Agent 的开发分散到多个团队;
  • 需要把复杂任务隔离到独立上下文窗口;
  • 想在不改协调者代码的前提下可扩展地加新 Agent;
  • 偏好”约定优于配置”。

原文也点明了代价:它用”上下文工程上的灵活性”换了”组合的简单性和强上下文隔离”。

graph LR
A[用户] --> B[主管 Agent]
B --> C{"task 工具<br/>agent_name, description"}
C -->|research| D[调研 Agent]
C -->|writer| E[写作 Agent]
C -->|reviewer| F[审查 Agent]
D --> C
E --> C
F --> C
C --> B
B --> G[回复用户]

这张图在说什么:① 主管只认识一个 task 工具;② 靠 agent_name 参数做按名分发(相当于运行时查表);③ 结果先回到 task 工具,再回到主管——多了一层”派发器”。

关键特征(原文):

  • 单一 task 工具:一个参数化工具,可按名字调起任何已注册的子智能体;
  • 约定式调用:按名字选 Agent,任务作为 human message 传入,最后一条消息作为工具结果返回;
  • 团队分布式:不同团队可以独立开发和部署各自的 Agent;
  • Agent 发现:可以通过系统提示列出可用 Agent,也可以通过渐进式披露(用工具按需加载 Agent 信息)来发现。

💡 提示(原文,很有洞察):这种模式有个有趣的特性——子智能体的能力可能和主 Agent 完全一样。这时候调子智能体的首要理由其实是”上下文隔离”:让复杂的多步任务跑在独立的上下文窗口里,不去污染主对话历史;子智能体自主干完,只交回一份精简总结,主线程始终保持聚焦和轻量。

附:带任务派发器的 Agent 注册表(原文完整示例)#

from langchain.tools import tool
from langchain.agents import create_agent
# 不同团队开发的子智能体
research_agent = create_agent(model="gpt-5.5", prompt="You are a research specialist...")
writer_agent = create_agent(model="gpt-5.5", prompt="You are a writing specialist...")
# 可用子智能体的注册表("按名派活"的核心)
SUBAGENTS = {
"research": research_agent,
"writer": writer_agent,
}
@tool
def task(agent_name: str, description: str) -> str:
"""Launch an ephemeral subagent for a task.
Available agents:
- research: Research and fact-finding
- writer: Content creation and editing
"""
agent = SUBAGENTS[agent_name]
result = agent.invoke({"messages": [{"role": "user", "content": description}]})
return result["messages"][-1].content
# 主协调 Agent
main_agent = create_agent(
model="gpt-5.5",
tools=[task],
system_prompt=(
"You coordinate specialized sub-agents. "
"Available: research (fact-finding), writer (content creation). "
"Use the task tool to delegate work."
),
)

⚠️ 原文如此:task 的 docstring 里硬编码了可用 Agent 列表,而系统提示里又重复了一遍——原文用这种方式演示”系统提示枚举”。真实项目里两处重复容易不同步,建议只留一处权威来源(或改用下面的 Enum / 工具化发现)。


决策三:上下文工程——三处”阀门”#

原文把上下文工程分三类,每一类影响的对象不同:

类别目的影响谁
子智能体规格(specs)确保”该调用的时候”真能调起来主管的路由决策
子智能体输入(inputs)确保子智能体拿到”刚好够用”的上下文子智能体的表现
子智能体输出(outputs)确保主管能基于结果继续行动主管的表现

(更完整的上下文工程方法见官方的上下文工程指南。)

子智能体规格:名字与描述就是提示词杠杆#

原文说得很直白:子智能体的”名字”和”描述”是主 Agent 判断该调谁的主要依据——它们就是提示词杠杆,得仔细起。

  • 名字(Name):主 Agent 怎么称呼这个子智能体。要清晰、动作导向(例如 research_agent、code_reviewer)。
  • 描述(Description):主 Agent 对这个子智能体能力的认知。要具体说明它处理什么任务、什么时候该用它。

如果你用的是单一派发工具,还得额外告诉主 Agent”有哪些子智能体可用”。按数量和注册表是静态还是动态,有三种给法:

方式最适合代价
系统提示枚举小而静态的清单(< 10 个)简单,但 Agent 变更时要改提示词
Enum 约束小而静态的清单(< 10 个)类型安全、显式,但变更时要改代码
工具化发现大或动态的注册表灵活可扩展,但增加复杂度

系统提示枚举#

把可用的 Agent 直接列在主 Agent 的系统提示里——主 Agent 把它们当作指令的一部分读到。

什么时候用(原文):Agent 少且固定(< 10 个);注册表很少变;想要最简单的实现。

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."
),
)

在派发工具上加 Enum 约束#

给 task 工具的 agent_name 参数加一个 Enum 约束:带来类型安全,并让可用 Agent 在工具 schema 里显式可见。

什么时候用(原文):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."""
# ...

工具化发现(tool-based discovery)#

额外提供一个专门的工具(比如 list_agents 或 search_agents),让主 Agent 按需发现有哪些 Agent。这实现了渐进式披露,也支持动态注册表。

什么时候用(原文):Agent 很多(> 10)或注册表在持续增长;注册表经常变或是动态的;想减小提示词体积、省 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 的 state 里取出来,传进子智能体:

from langchain.agents import AgentState
from langchain.tools import tool, ToolRuntime
class CustomState(AgentState):
example_state_key: str
@tool("subagent1_name", description="subagent1_description")
def call_subagent1(query: str, runtime: ToolRuntime[None, CustomState]):
# 按需把消息"加工"成适合子智能体的输入
subagent_input = some_logic(query, runtime.state["messages"])
result = subagent1.invoke({
"messages": subagent_input,
# 也可以顺便传其它 state 字段;
# 但这些字段必须在"主 Agent 和子智能体"两边的 state schema 里都定义。
"example_state_key": runtime.state["example_state_key"],
})
return result["messages"][-1].content

关键在 some_logic(原文):

  • 只传任务描述(query)→ 子智能体保持隔离;
  • 传父级消息历史(runtime.state["messages"])→ 给子智能体铺垫已有工作,适合”接手父级已经开工的事”,比如继续一次 PR 审查、接着修一个 bug。

原文还提到 Deep Agents 把这两种做法命名为 mode: "isolated"(默认)和 mode: "fork",并帮你实现了 fork:被 fork 的子智能体会收到父级的完整消息历史(包括工具调用)和系统提示。

模式子智能体收到什么最适合代价
Isolated 隔离(默认)只有任务描述需要很少前置上下文的聚焦工作上下文隔离好,但父级做过的事它要重新推导一遍
Forked 分叉父级的对话历史(在 Deep Agents 里还包括系统提示)接着父级已经开工的任务继续做有前置上下文,代价是提示词更大、隔离性更弱

子智能体输出:让它交回什么#

原文给了两种策略,目标是让主管”能拿着结果继续做决策”:

  1. 提示词约束子智能体:明确要求它返回什么。常见故障是——子智能体做了一堆工具调用和推理,但最终消息里没把结果写进去。记得提醒它:主管只能看到你的最终输出。
  2. 用代码加工:在返回前调整或补充响应。例如除了最终文本,还用 Command 把特定 state 字段一起带回。
from typing import Annotated
from langchain.tools import InjectedToolCallId
from langgraph.types import Command
@tool("subagent1_name", description="subagent1_description")
def call_subagent1(
query: str,
tool_call_id: Annotated[str, InjectedToolCallId],
) -> Command:
result = subagent1.invoke({"messages": [{"role": "user", "content": query}]})
return Command(update={
# 把子智能体的额外状态带回来
"example_state_key": result["example_state_key"],
"messages": [
ToolMessage(
content=result["messages"][-1].content,
tool_call_id=tool_call_id,
)
],
})

🧭 补充解读(非原文):这三处阀门可以用一句话记住——specs 决定”派不派得出去”,inputs 决定”专家手上有没有料”,outputs 决定”主管能不能接得上”。多 Agent 系统调不动,八成是这三处之一没设计好。


检查点与状态查看(Checkpointing and state inspection)#

底层机制(原文):

  • 默认情况下子智能体使用 inherited checkpointer(继承检查点)模式:每次调用都从全新状态开始,支持 interrupt,并且可以安全地并行运行。
  • 如果你需要子智能体跨多次调用保留自己的持久对话历史,就用 checkpointer=True 编译它(continuations 模式)。各模式的完整对比见子图持久化。

一个很实用的坑(原文):

因为子智能体是在工具函数内部被调用的,LangGraph 无法静态发现它们。这意味着用 get_state + subgraphs 拿不到子智能体的状态。如果你需要读取嵌套图的状态(比如在 interrupt 期间),应该改成在自定义图的一个节点函数里调用子智能体。

🧭 补充解读(非原文):这条对做生产级可观测性的人非常关键——如果你打算把子智能体的执行放进 LangGraph 的 checkpoint / 人工审批链路里,“包成工具”这条路会让它隐身。要在图里可见、可暂停、可恢复,就得把子智能体作为子图节点接进主图(也就是 08 篇 Custom workflow 的思路)。

📎 从 langgraph-supervisor 迁移:langgraph-supervisor 包已不再积极维护。官方提供了从 create_supervisor 迁移到本文 subagents 模式的指南(含 interrupt/resume 与外部 API 回调的流程):Migrate from langgraph-supervisor。


小结#

  1. 一句话实现:子智能体 = create_agent(...) 包一层 @tool;主管 = 工具清单里挂着这些工具的那个 Agent。
  2. 五个特征:集中式控制、不与用户直连(可用 interrupt 变通)、子智能体即工具、可并行、默认无状态。
  3. 同步 vs 异步:主管需要结果 → 同步(简单但卡对话);用户不该等 → 异步 + 三工具模式(start / status / get result),用”通知 → 用户点一下 → 再发消息”完成闭环。
  4. 两种工具模式:一智能体一工具(精细控制)vs 单一 task 派发工具(团队分布、约定优于配置、上下文隔离是主要收益)。
  5. 三处上下文阀门:specs(名字/描述,三种枚举方式)、inputs(isolated vs forked)、outputs(提示词约束 + Command 带回状态)。
  6. 状态可见性有坑:包成工具的子智能体 LangGraph 静态发现不了,get_state(subgraphs=...) 读不到;要读嵌套状态就放进自定义图的节点里;langgraph-supervisor 已弃用,请迁移。

一句话选型#

  • 选它:多个独立领域(日历/邮件/CRM/数据库)、不需要子智能体直接面对用户、想要集中式管控和强上下文隔离。
  • 别选它:只有少量工具的简单场景(用单 Agent);用户需要直接跟专家来回对话(用 05 Handoffs);只是想做一次分类分发(用 07 Router)。

上一篇:03 性能对比 | 下一篇:05 Handoffs 交接 | 实战:09 用 Subagents 搭个人助理

04 Subagents 子智能体:主管派活,专家干活
https://aicooleo.cn/blog/posts/multiagent-04-subagents/
作者
aicooleo
发布于
2026-08-27
许可协议
CC BY-NC-SA 4.0
多 Agent 精讲 · 第 4 / 22 篇 返回合集