LangGraph supervisor 与 swarm 实测对照
LangGraph 多 Agent 两拓扑实测:supervisor 与 swarm 的 6 处差异(本机零 Key 对照)
TL;DR:LangGraph 官方给了两个「多 Agent」库,名字听起来像是同一个东西的两种叫法,我实际测试之后的结论是:它们决定的是「下一个说话的是谁」这件事的答案来源——supervisor 由中央节点重新决策,swarm 由状态字段 active_agent 直接决定,而这个字段会被 checkpointer 记住。这条差别在单轮对话里几乎看不出来,在第二轮对话里会变成「你的追问根本没有重新分诊」。另外两处更隐蔽:supervisor 默认会把 worker 的中间过程从主历史里删掉;以及两种拓扑都没有真正的并行委派,但失败形态完全不同。本文所有数字来自本机实测(langgraph 1.2.18 + langgraph-supervisor 0.0.31 + langgraph-swarm 0.1.0,Python 3.13,Windows 11),不含任何联盟推广链接。
先把两句话摆正
官方文档里对两种模式的描述各只有一句话,但对错就是从这两句里分出来的。docs.langchain.com/oss/python/langchain/multi-agent 的 Patterns 表原文:
- Subagents(supervisor 语义):「A main agent coordinates subagents as tools. All routing passes through the main agent, which decides when and how to invoke each subagent.」
- Handoffs(swarm 语义):「Agents transfer control to each other via tool calls. Each agent can hand off to others or respond directly to the user.」
官方仓库 langchain-ai/langgraph-swarm 的 README 第一句更直白:「The system remembers which agent was last active, ensuring that on subsequent interactions, the conversation resumes with that agent.」
一句话总结:supervisor 是「所有人都通过前台转接」,swarm 是「电话直接转到上一个人手里」。前者多一轮翻译,后者省一轮但会「粘」在最后一个专家身上。LangChain 官方博客《Benchmarking Multi-Agent Architectures》也把 supervisor 的额外成本归因于这层「translation」:子 Agent 不能直接回用户,supervisor 必须复述一遍,因此成本更高、也更容易失真。
如果你已经在本站读过 LangGraph 持久化那一篇,可以把本文当成它的续篇:checkpointer 记住的是「状态」,而 swarm 记住的是「谁拿着话筒」。
实测方法与可复现环境
我不想为了跑一次对照就在每篇文章背后绑一张账单,所以这里用了一个零成本做法:把「模型」换成一个按脚本吐消息的 Python 对象。LangGraph 的 create_react_agent 支持「动态模型」——只要传入的对象是 callable、签名是 (state, runtime)、返回值是一个 LangChain Runnable 即可(langgraph/prebuilt/chat_agent_executor.py 里的 _resolve_model)。这样图怎么路由、工具怎么执行、状态怎么写全都照常,唯独「模型思考」这一步被换成了写死的剧本。
环境安装不污染主环境,全部落到隔离目录:
pip install --target D:/openclaw-blog/tmp/ma-env langgraph-supervisor langgraph-swarm
实测拿到的版本组合(版本与发布时间取自 GitHub Releases API):
- langgraph 1.2.18(Release 时间 2026-10-26T20:04:45Z)
- langgraph-prebuilt 1.1.1 / langgraph-checkpoint 4.3.1 / langchain-core 1.7.0
- langgraph-supervisor 0.0.31(Release 时间 2025-11-19T18:34:43Z)
- langgraph-swarm 0.1.0(Release 时间 2025-12-04T19:04:25Z)
驱动用的假模型(create_supervisor 内部会对模型调 bind_tools,所以这个对象要同时提供返回自身的 bind_tools):
from langchain_core.messages import AIMessage
from langchain_core.runnables import RunnableLambda
class ScriptedModel:
def __init__(self, script):
self.script = script # 一串 AIMessage,第 n 次调用返回第 n 条
self.n = 0
def bind_tools(self, tools, **kw):
return self
def __call__(self, state, runtime=None):
i = self.n
self.n += 1
msg = self.script[i] if i < len(self.script) else AIMessage(content="(exhausted)")
return RunnableLambda(lambda _: msg)
用它造一个 worker 就是一行:bob = create_react_agent(ScriptedModel([AIMessage(content="Bob worked")]), tools=[], name="bob")。
差异一:第二轮对话谁接(最容易踩的一个)
同一件事「让 bob 干活」,两个拓扑跑完的路径完全不同。用 stream_mode="updates" 抓节点轨迹:
- supervisor:
supervisor(agent) → supervisor → bob(agent) → bob → supervisor(agent) → supervisor,6 步 - swarm:
alice(agent) → alice → bob(agent) → bob,4 步
差别不止是步数。带上 checkpointer 再问第二句「and now?」(同一个 thread_id):
- supervisor 第二个执行的节点仍然是 supervisor——追问会被重新分诊,这是预期行为。
- swarm 第二个执行的节点直接是 bob——
get_state().values["active_agent"]显示"bob",上一轮写进去的值被保留了。
这件事本身是官方设计(README 原话已在上节引过),在官方文档的语境里它是优点:「后续交互可以从同一个专家继续」。但它有一个很容易被忽略的代价:用户的第二句话可能根本不属于 bob 的领域。举例:第一轮「帮我算个账」转去了财务 Agent;第二轮随口问「今天天气怎么样」,swarm 会把这句直接交给财务 Agent,而 supervisor 至少会让中央节点再看一眼。所以用了 swarm 就必须配套做两件事之一:要么在每个 Agent 的系统提示里明确「不属于你的领域要主动交回某某」,要么在应用层每次新任务开始时把 active_agent 重置一次。
差异二:默认配置会悄悄删掉 worker 的中间过程
create_supervisor 有个参数 output_mode,默认是 last_message。它的作用是:worker 跑完之后,把 worker 内部的消息历史以什么粒度写回主图。
我给 bob 加了一个工具,让它内部产生一个工具调用回合(一次 add 工具及其 ToolMessage),然后对比:
output_mode="full_history":主历史 9 条消息,bob 贡献 3 条(含中间的工具结果)output_mode="last_message"(默认):主历史 7 条消息,bob 只贡献 2 条,中间那条 ToolMessage 不见了
源码在 langgraph_supervisor/supervisor.py 的 _make_call_agent,它不是 bug,是一段写死的取舍:
elif output_mode == "last_message":
if isinstance(messages[-1], ToolMessage):
messages = messages[-2:]
else:
messages = messages[-1:]
什么时候这会让人生不如死:你的 supervisor 需要根据 worker「实际调用过什么工具、传了什么参数」来做下一步决策(比如审计、计费、把上下文递给另一个 worker),这些证据在默认配置下被删掉了,而且不报错、不告警。两条应对:显式传 output_mode="full_history";或者干脆在 worker 的最后一条消息里把关键中间结果复述一遍。反过来,如果你只想要答案、想压 context,默认值反而是对的——默认值不是错,是你需要知道它存在。
差异三:并行委派——两种拓扑都不并行,但崩法完全不同
官方文档的「Choosing a pattern」表里有一列 Parallelization,Handoffs(swarm)那一格是 --(明确不支持),Subagents(supervisor)是有星的。我实测的结果比表格更微妙。
先在 supervisor 上试:让 supervisor 在一条 AIMessage 里同时发出两个 handoff 工具调用(transfer_to_alice 与 transfer_to_bob)。langgraph_supervisor/handoff.py 里确实有专门处理并行的分支——一旦检测到 len(last_ai_message.tool_calls) > 1,就用 Send 列表把跳转包进同一个 Command,注释原文是这么写的:
# NOTE: we are using Send here to allow the ToolNode in langgraph.prebuilt
# to handle parallel handoffs by combining all Send commands into a single command
goto=[Send(agent_name, {**state, "messages": handoff_messages})],
顺着这条线索我预期两个 worker 都会跑。实际结果:6 次独立运行,6 次都只有一个 worker 真的执行了(bob 4 次、alice 2 次,谁活下来不确定),另一个 worker 既不报错也不留痕,最终历史里只有 5 条消息。我用 monkeypatch 抓了 ToolNode._combine_tool_outputs 的调用记录,它被执行了 2 次、每次 n_input=1——也就是两个 handoff 工具各自产出了一条只含一个 Send 的父命令,没有落进同一批,源码里那条负责累加合并的分支(replace(parent_command, goto=parent_command.goto + output.goto))压根没被走到。
那么在 swarm 上做同样的事呢?直接抛异常,而且是个看起来跟 handoff 毫不相干的错:
ValueError: Found AIMessages with tool_calls that do not have a corresponding ToolMessage.
Here are the first few of those tool calls:
[{'name': 'transfer_to_charlie', 'args': {}, 'id': 'w2', 'type': 'tool_call'}].
Every tool call (LLM requesting to call a tool) in the message history MUST have a
corresponding ToolMessage (result of a tool invocation to return to the LLM) ...
For troubleshooting, visit: https://docs.langchain.com/oss/python/langgraph/errors/INVALID_CHAT_HISTORY
排到这里可以给一条工程结论:真要并行就别指望这两种拓扑自动给你。需要「同时派几个 worker」的场景,用 Send 手工搭 fan-out / fan-in 子图,或者在 supervisor 侧强制每轮只发一个 handoff。这比依赖一条「注释里说支持、实测只跑一个」的路径可靠得多。
Troubleshooting:7 个真实报错与复现方式
下面 7 条全部在本机复现过,都给了抛出位置与绕过办法。
报错 1:ParentCommand——把带 handoff 工具的 agent 单独 invoke
现象:为了调试单独跑一个 worker,bob.invoke(...) 直接炸,异常类名是 langgraph.errors.ParentCommand,str(e) 打印出来是一整个 Command 对象的千字符 repr(含全部消息),既没有错误说明也没有修复提示。
原因:create_handoff_tool 返回的工具本体是 Command(goto=..., graph=Command.PARENT, ...),而 langgraph/graph/state.py 第 1762 行的处理是 raise ParentCommand(command),没有做友好封装。ParentCommand 继承自 GraphBubbleUp,本来是给「子图向父图传话」用的内部信号——一旦没有父图可传,它就变成了一个没有文案的裸异常。
修复:handoff 工具只能在被 create_swarm / create_supervisor 包起来的图里用。要单测某个 worker,就临时构造一个只含它的 swarm,或者把工具列表换成不含 handoff 的版本。
报错 2:KeyError: 'charlie'——给 active_agent 写了不存在的名字
现象:手动传参、update_state、或被带偏的模型写入了一个不在 agent 列表里的 active_agent,报出来的只有一句 KeyError: 'charlie',定位在 langgraph/graph/_branch.py 第 203 行。
原因:create_swarm 会在内部把 schema 里的 active_agent: str | None 收窄成 Literal['alice','bob'] | None(langgraph_swarm/swarm.py 的 _update_state_schema_agent_names),随后 add_active_agent_router 从 START 挂了一条带 path_map=route_to 的条件边;路由函数返回一个不在 path_map 里的值,就是一声不吭的 KeyError。
修复:校验优先。入口处就断言 active_agent in {a.name for a in agents},别指望图给你友好报错。
报错 3:Error: transfer_to_Bob is not a valid tool——工具名的大小写与空格
现象:模型输出 transfer_to_Bob,日志里出现一条 ToolMessage:Error: transfer_to_Bob is not a valid tool, try one of [transfer_to_bob]。不抛异常,handoff 静默失败,模型通常会重试或者直接开摆。
原因:create_handoff_tool 里有一行归一化 _normalize_agent_name:WHITESPACE_RE.sub("_", agent_name.strip()).lower()。于是:
agent_name="Bob"→ 工具名transfer_to_bob,而Command(goto=...)用的仍是原名"Bob"agent_name="code reviewer"→ 工具名transfer_to_code_reviewer,而跳转目标仍是带空格的"code reviewer"
也就是说:工具名和节点名是两套不同的字符串。修复:agent 名一律用小写加下划线的 snake_case;给 handoff 工具写 description 时把目标节点名写死进去,而不是让模型猜。
报错 4:ValueError: Please specify a name when you create your agent
抛点 langgraph_supervisor/supervisor.py:400。只要传给 create_supervisor 的图 name is None or name == "LangGraph"(即 compile() 时没命名)就炸。修复:create_react_agent(..., name="xxx") 或 graph.compile(name="xxx")。
报错 5:ValueError: Agent with name 'dup' already exists. Agent names must be unique.
抛点 supervisor.py:406,与报错 4 是同一个循环的两条支路。注意判据是名字而不是对象同源:两个用途不同但同名的 worker 也会踩。
报错 6:ValueError: Invalid agent output mode: all_history. Needs to be one of ('full_history', 'last_message')
抛点 supervisor.py:73。合法值只有两个(源码注释原文:full_history 加整个历史 / last_message 只加最后一条),拼写成 all_history、history 都会被直接拒绝——这条反而是最友好的一个。
报错 7:ValueError: Missing required key 'active_agent' in state_schema 与 Default active agent 'charlie' not found in agent names ['alice']
两个都在 langgraph_swarm/swarm.py(257 / 267 行)。第一种最常见的触发姿势是「照着 supervisor 的写法写代码」——把 MessagesState 直接传给 create_swarm;swarm 必须用自己的 SwarmState(或自定义 schema,但一定要带 active_agent)。第二种是 default_active_agent 与 agents 的 name 对不上,大小写敏感。另外 agents=[] 也会被直接拒绝(agents list cannot be empty,swarm.py:261)。
顺带记录一条 supervisor 侧的同类报错方便搜索:一旦开始自定义 handoff 工具,就必须给所有子 agent 配齐,否则 supervisor.py:167 会抛 ValueError: When providing custom handoff tools, you must provide them for all subagents. Missing handoff tools for agents '{'bob'}'.
什么时候用哪个
- 需要中央兜底、审计、计费,或希望追问一定被重新分诊 → supervisor
- Agent 需要直接跟用户聊(客服转接、专家接管),且单条对话基本不跨领域 → swarm,同时补上「领域外主动交回」的提示词
- 两者都不提供自动并行;真要并行就用
Send自己搭 fan-out / fan-in - 一个不太有人提的事实:
langchain-ai/langgraph-supervisor仓库 README 顶部现在挂着一段官方声明——「We now recommend using the supervisor pattern directly via tools rather than this library for most use cases... We're making this library compatible with LangChain 1.0 to help users upgrade their existing code.」也就是说官方推荐的新写法是「把子 agent 包成工具」,create_supervisor现在更像兼容层。存量项目不必急着迁,但新项目可以直接照官方 supervisor 教程写。
FAQ
Q:不用 checkpointer 的话,swarm 还会记住上一个 active agent 吗?
不会。active_agent 是图 state 的一个字段,两轮之间靠 checkpoint 存活;没有 checkpointer,每次 invoke 都从 default_active_agent 出发。上文差异一的结论正是建立在带 checkpointer 的前提上的。
Q:supervisor 每轮都要回中央节点,是不是一定更贵?
官方博客的结论是 supervisor 持续比 swarm 贵,贵在复述(translation)。但如果你本来就需要中央节点做校验或审计,这笔开销买到的是控制力,不是浪费。另外别忘了默认的 output_mode 会砍掉 worker 的中间过程,在压成本时反而是加分项。
Q:能不能两个混着用?
可以,而且官方也鼓励这么想:外层用 handoff 做专家切换,每个专家内部再把别的 agent 当工具调用。本站 LangGraph 流式三口径 那篇讲的是同一套运行时在 streaming 上的表现,人审中断那篇 讲的是把人放进这条链路时的六条硬规则,与本拓扑问题组合着看更容易定位。
Q:我只想知道现在该装哪个库?
短期维护老项目就继续用这两个库(版本号见前文,均为发稿时 GitHub Releases 上的最新版);新项目先看官方 supervisor 教程里「用工具包一层」的写法。两个库不冲突,但要注意它们依赖同一个 langgraph 版本基线。
参考来源(一手)
docs.langchain.com/oss/python/langchain/multi-agent:Patterns 表与「Choosing a pattern」星级表(Handoffs 的 Parallelization 一列为--)docs.langchain.com/oss/python/langchain/supervisor:官方 supervisor 教程,含「If you are migrating from the langgraph-supervisor package...」迁移指引- GitHub
langchain-ai/langgraph-swarmREADME:active_agent跨轮保留的原文表述 - GitHub
langchain-ai/langgraph-supervisorREADME:顶部「不再推荐直接使用本库」的官方声明 - LangChain 官方博客《Benchmarking Multi-Agent Architectures》:多 Agent 架构基准测试与成本归因
- GitHub Releases API:
langgraph==1.2.18(2026-10-26)、langgraph-supervisor==0.0.31(2025-11-19)、langgraph-swarm==0.1.0(2025-12-04) - 本机源码字面量:
langgraph_supervisor/supervisor.py(73 / 167 / 400 / 406 行)、langgraph_supervisor/handoff.py(Send 合并分支)、langgraph_swarm/swarm.py(257 / 261 / 267 行)、langgraph_swarm/handoff.py(_normalize_agent_name)、langgraph/graph/state.py(1762 行)、langgraph/graph/_branch.py(203 行)、langgraph/prebuilt/tool_node.py(_combine_tool_outputs的合并分支)
本文的边界
三条要说在前面。第一,全程没有真实 LLM:模型由脚本驱动,因此结论覆盖的是「拓扑与运行时行为」,不覆盖真实模型会不会发出并行工具调用——后者取决于模型与 provider。官方文档在 supervisor 教程里的说法是「许多 LLM 会在一次响应里发出多个工具调用,runtime 会并行执行」,与我实测到的 langgraph-supervisor 0.0.31 手牌路径的表现并不一致,这是本文最值得你自己复算的一点。第二,只测了 InMemorySaver,没有测 SQLite / Postgres 后端下的并发一致性。第三,全部数据在 Windows 11 + Python 3.13 上取得,未做 Linux 对照;拓扑结论与平台无关,但文中的源码行号是对着 langgraph 1.2.18 的。本文不含任何联盟推广链接。
👉 立即参与小米 MiMo 开放平台:国内领先的 AI 大模型开放平台,高性价比推理服务
👉 立即参与阿里云 AI:汇集爆款 AI 产品,热门模型专属权益优惠券助力企业创新加速
📌 本文由 AI 辅助生成并经人工审核发布 | TechPassive — AI 驱动的内容测试站点,专注于效率工具与 SaaS 真实评测
🔗 精选推荐工具
使用以下链接支持我们持续产出高质量内容(点击可直接前往购买):