跳到主要内容

LangGraph.js Supervisor-Worker 架构解析:把它拆成四件事,并实测两个成本旋钮

· 阅读需 22 分钟

Supervisor-Worker 是中心化调度最典型的形态:一个调度者负责「派活」,若干专家负责「干活」。听起来像个新概念,但把它拆开只有四件事——而且我用大约 40 行手写代码复现了官方库完全相同的执行轨迹,这就是「它内部到底做了什么」最直接的证据。

更值得一看的是后半篇:这套架构有两个成本旋钮,实测能把共享上下文从 16 条消息压到 4 条(字符数 -71%),而执行轨迹一字不变。

但请先看下面那段更正:这套架构依然有效,官方的推荐实现却换了——从 createSupervisor(worker 是图节点)换成了 subagents pattern(子 agent 包成 tool)。我在 §五 把新写法实现并实测了,结论对新写法有利。

环境:Node v24.14.1 / @langchain/langgraph@1.4.16 / @langchain/langgraph-supervisor@1.1.2。为了让执行轨迹完全确定、可复现,我用脚本化模型(按调用次序返回预置响应的自定义 BaseChatModel)替代真实 LLM——无需 API Key,全部输出都是本机真实运行结果。脚本留档在 source/langgraph-supervisor/。

⚠️ 更正说明(2026-09-20 追加)​

本文初版把 createSupervisor 当作 Supervisor-Worker 的标准做法来推荐,这一点需要更正:官方已经改了推荐路径。

  • LangChain 官方文档已把新代码导向 subagents pattern——把子 agent 包成一个 tool,交给一个普通的 createAgent 调度;Python 侧另有专门的迁移文档 Migrate from langgraph-supervisor,给出了逐项映射(output_mode → 在工具包装层里自己格式化输出、create_handoff_tool → 自己写一个调用 subagent.invoke() 的 tool)。
  • 同时,createReactAgent → createAgent 也是 JS v1 的既定迁移方向(见 JavaScript v1 迁移指南)。本文初版的示例两个都用上了,等于踩在两条旧路径上。

一个精确措辞上的澄清(核实依据分两层):官方文档的原话是「no longer actively maintained」(不再积极维护)——注意它说的是「不积极维护」,不是「已废弃」。工程上的旁证是我自己核对的 npm 元数据:该包没有 deprecated 标记,最近一次发版是 2026-09-12 的 1.1.2(本文写作前 8 天),仍由 LangChain 官方维护者发布。所以:

  • 存量代码不必惊慌,它不是"弃用炸弹";
  • 但新代码确实不该再用——这不是"选哪个都行"的偏好问题,官方已明确表态推荐变了。

我把 subagents pattern 也实现并实测了,结论对它有利:同一个任务、路由用的 LLM 调用次数完全相同(都是 3 次),但共享上下文只有 6 条消息,不到库版本默认值(12 条)的一半,而且不需要调任何参数。完整实现与对比见下文「五、官方已改推荐:subagents pattern」。

本文仍然值得读的部分:§三 把 supervisor「拆成四件事」的机制分析依然成立——理解机制才能看懂两种实现的关系;§四 的两个成本旋钮实测也依然有效,但请注意它的定位变了:在新范式里,这件事由「你的工具包装层返回什么」直接决定,比调库参数更自由。读的时候请把 §二/§三 当作"存量代码的长什么样",把 §五 当作"新代码该怎么写"。

一、Supervisor-Worker 解决什么问题​

单 Agent(一个 ReAct 循环 + 一堆工具)在工具变多时会退化:

  • 工具太多,选错率上升——30 个工具塞给一个模型,它经常挑错;
  • 上下文被工具说明挤满——每个工具的 description 都进提示词;
  • 能力无法分工——「查资料」和「写代码」需要不同的提示词与工具集,硬塞在一起互相干扰。

Supervisor-Worker 的切法是按角色拆:

┌─────────────┐
用户 ─────► │ Supervisor │◄──── 每轮都回到这里决策
└──────┬──────┘
派活 │ │ 派活
▼ ▼
┌──────────┐ ┌──────────┐
│researcher│ │ coder │
└──────────┘ └──────────┘
自己的提示词 自己的提示词
自己的工具集 自己的工具集

关键点:worker 之间不直接通信,只跟 supervisor 对话;所有信息通过共享的消息状态流动。

二、最小可运行示例与真实轨迹​

用官方包起一个 supervisor,核心代码就这几行:

import { createReactAgent } from '@langchain/langgraph/prebuilt';
import { createSupervisor } from '@langchain/langgraph-supervisor';

// 两个 worker:各自是独立的 ReAct agent,有自己的工具集
const researcher = createReactAgent({ llm: researcherLlm, tools: [searchTool], name: 'researcher' });
const coder = createReactAgent({ llm: coderLlm, tools: [codeTool], name: 'coder' });

// supervisor:只负责路由
const graph = createSupervisor({
agents: [researcher, coder],
llm: supervisorLlm,
prompt: '你是调度者:把任务分给合适的专家,完成后自己收尾。',
}).compile({ name: 'supervisor_demo' });

跑一次「调研 + 出示例」的任务,真实执行轨迹:

[LLM:supervisor] 第 1 次调用 → 调用工具 transfer_to_researcher
→ 节点 supervisor 新增消息 | ToolMessage "Successfully transferred to researcher"
[LLM:researcher] 第 1 次调用 → 调用工具 search
[LLM:researcher] 第 2 次调用 → 直接回答 "研究结论:Supervisor 负责路由,Worker 负责"
→ 节点 researcher 新增消息 | ToolMessage "Successfully transferred back to supervisor"
[LLM:supervisor] 第 2 次调用 → 调用工具 transfer_to_coder
→ 节点 supervisor 新增消息 | ToolMessage "Successfully transferred to coder"
[LLM:coder] 第 1 次调用 → 调用工具 write_code
[LLM:coder] 第 2 次调用 → 直接回答 "代码骨架已给出:graph = createSupervisor({...})"
→ 节点 coder 新增消息 | ToolMessage "Successfully transferred back to supervisor"
[LLM:supervisor] 第 3 次调用 → 直接回答 "已完成:先调研架构,再给出示例。"

执行轨迹: supervisor → researcher → supervisor → coder → supervisor

三个可以直接读出来的架构事实:

  1. transfer_to_<agentName> 是自动生成的工具——supervisor 的「派活」不是靠某种特殊语法,而是让模型调用一个工具。所以 supervisor 的 LLM 必须支持 tool calling。
  2. 每派一次活,都要经过一次 supervisor——supervisor → researcher → supervisor → coder → supervisor。supervisor 被调用了 3 次(两次派活 + 一次收尾)。
  3. 交接在消息流里是「可审计的」——ToolMessage "Successfully transferred to X" 与 "...back to supervisor" 成对出现,一眼能看出任务流转。

循环怎么结束? 看第 3 次调用:supervisor 没有再请求转交(返回一条普通消息),图就走到 END。「不再派活」就是终止条件——这也是死循环风险的来源(详见 §六)。

三、拆解:它内部只有四件事​

我把这套架构手写了一遍(约 40 行),执行轨迹与官方库完全一致。四件事分别是:

① 共享状态:一个 messages 通道(共享黑板)​

const State = Annotation.Root({
messages: Annotation({ reducer: (a, b) => a.concat(b), default: () => [] }),
});

reducer: (a, b) => a.concat(b) 就是「追加」语义——所有 agent 往同一块黑板上贴消息。这也是为什么 worker 之间不需要直接通信:它们读同一块黑板。

② 交接工具:把「请求转交」表达成「调用工具」​

const handoffTools = Object.keys(AGENTS).map((name) =>
tool(async ({ task }) => `Successfully transferred to ${name}, task: ${task}`, {
name: `transfer_to_${name}`,
description: `把任务转交给 ${name}`,
schema: z.object({ task: z.string() }),
}),
);

这是整套架构最巧妙的一点:不给模型发明新语法,而是复用 tool calling。模型「调用 transfer_to_」= 「我要把活交给它」。

③ 路由节点:读模型输出,决定下一步去哪​

async function supervisorNode(state) {
const res = await supervisorLlm.invoke(state.messages);
const call = res.tool_calls?.[0];

if (!call) {
// 没有请求转交 = 任务结束
return new Command({ goto: END, update: { messages: [res] } });
}

const target = call.name.replace(/^transfer_to_/, '');
return new Command({
goto: target,
update: {
messages: [
res,
new ToolMessage({ content: `Successfully transferred to ${target}`, tool_call_id: call.id, name: call.name }),
],
},
});
}

Command({ goto, update }) 是 LangGraph 里「既改状态又决定去哪」的原语——这就是「动态路由」的实现方式,不需要预先画好边。

④ worker 节点:跑自己的 agent,然后交回控制权​

function makeWorkerNode(name) {
return async (state) => {
const result = await AGENTS[name].invoke({ messages: state.messages });
const produced = result.messages.slice(state.messages.length); // 只取这次新增的
return new Command({
goto: 'supervisor',
update: { messages: produced },
});
};
}

注意 goto: 'supervisor'——worker 干完一定要交回控制权,这就是 §二 里那条 transfer_back_to_supervisor 的由来。

⚠️ 手写时踩到的坑:返回 Command 必须声明 ends​

第一次编译直接报错:

UnreachableNodeError: Node `researcher` is not reachable.

If you are returning Command objects from your node,
make sure you are passing names of potential destination nodes as an "ends" array
into ".addNode(..., { ends: ["node1", "node2"] })".

原因:只要节点返回 Command,LangGraph 就无法静态推断它可能去哪,图的连通性校验会认为目标节点不可达。修法是显式声明:

const builder = new StateGraph(State).addNode('supervisor', supervisorNode, {
ends: [...Object.keys(AGENTS), END], // ★ 声明所有可能的去向
});
for (const name of Object.keys(AGENTS)) {
builder.addNode(name, makeWorkerNode(name), { ends: ['supervisor'] });
}

这个坑我也写进了留档脚本的注释里。用官方 createSupervisor 时它替你做了这件事——这正是「用库」相对「手写」省下的第一份认知成本。

手写版的真实输出(与库版本轨迹一致):

执行轨迹节点: supervisor → researcher → supervisor → coder → supervisor(与库版本一致)
消息总数: 12
消息序列:
0. human 帮我调研 LangGraph supervisor 并给出示例
1. ai tool_calls=transfer_to_researcher
2. tool name=transfer_to_researcher Successfully transferred to resear
3. ai name=researcher tool_calls=search
4. tool name=search 搜索「LangGraph supervisor」的结果:LangGr
5. ai name=researcher 研究结论:Supervisor 负责路由,Worker 负责执行。
6. ai tool_calls=transfer_to_coder
...
11. ai 已完成:先调研架构,再给出示例。

能复现同样的轨迹,就说明这四件事就是它的全部机制。

四、★ 实测:两个成本旋钮,上下文 -71%,轨迹不变​

多 Agent 架构最贵的不是模型单价,而是共享消息历史的膨胀——每次派活,worker 都带着完整历史起跑。createSupervisor 给了两个旋钮来控制它。

旋钮一:outputMode(默认 last_message)​

  • "full_history":把 worker 的整个消息历史并入共享状态;
  • "last_message"(默认):只把 worker 的最后一条消息带回来。

旋钮二:addHandoffMessages(默认 true)​

是否把 supervisor→worker 的交接记账消息(那条 transfer_to_X 的 AIMessage + 对应 ToolMessage)放进历史。默认保留;关掉可以给「严格校验 tool-call 序列的 provider」用。

三种配置的真实对比​

配置消息数上下文字符数消息构成
full_history16408human 1 / ai 9 / tool 6
last_message(默认)12338human 1 / ai 7 / tool 4
last_message + addHandoffMessages: false4119human 1 / ai 3

执行轨迹在三种配置下完全相同:supervisor → researcher → supervisor → coder → supervisor。

这组数字的含义要说清楚:这两个旋钮是纯成本优化,不影响行为。 调到最省,共享上下文只剩四条:[用户问题, researcher 结论, coder 产出, supervisor 总结]——408 → 119 字符,-71%。

它们各自丢掉了什么(看消息序列)​

full_history 里,每个 worker 的内部轨迹都进了共享历史:

3. ai name=researcher tool_calls=search ← worker 内部工具调用
4. tool name=search 搜索「...」的结果 ← worker 内部工具结果
5. ai name=researcher 研究结论:... ← worker 的最终产出
6. ai name=researcher tool_calls=transfer_back_to_supervisor
7. tool name=transfer_back_to_supervisor Successfully transferred back

切到 last_message 后,第 3、4 条(worker 的内部工具调用对)被丢掉了,只留结论(第 5 条)——但第 6、7 条的交接记账还在,所以总量只降了 4 条。

再关掉 addHandoffMessages,交接记账也去掉,于是每个 worker 在共享历史上只留一条结论——总数直接掉到 4 条。

这就是「省钱」与「可审计」的取舍:

  • 留着 handoff 消息:历史里能看出谁把活交给了谁,便于调试和审计;代价是每次派活多 2 条消息;
  • 关掉之后:上下文最省,但共享状态不再是完整的审计日志——想知道流转过程,得靠 tracing(LangSmith / Langfuse)而不是消息历史。

我的建议:开发期用默认(addHandoffMessages: true),靠消息历史排查问题最方便;上线压成本时再考虑关掉,并把审计责任移交给 tracing。顺带一提,观测那条链路本身有个坑我单独写过:不 flush 就丢 trace。

五、官方已改推荐:subagents pattern(含实测对比)​

官方为什么改​

原因只有一个词:上下文控制权。回看 §三 的机制——worker 是图节点,它跑完会把自己的消息并进共享状态,于是「带多少历史回去」这件事由框架决定,你只能通过 outputMode / addHandoffMessages 这类参数间接调节(§四 实测的那两个旋钮)。

subagents pattern 把这件事翻转了:worker 不再是节点,而是一个 tool。tool 返回什么、返回多少,完全由你写的那几行决定。官方迁移文档给的第一条映射就是它:

langgraph-supervisor推荐替代
createSupervisor(worker 是图节点)createAgent + 子 agent 包成 tool
outputMode(控制历史)在工具包装层里自己格式化输出
createHandoffTool(自定义路由)自己写一个调用 subagent.invoke() 的 tool
嵌套 supervisor一个 subagent tool 内部再调其他 subagent

完整实现(可运行)​

import { tool } from '@langchain/core/tools';
import { HumanMessage } from '@langchain/core/messages';
import { z } from 'zod';
import { createAgent } from 'langchain'; // ★ 不是 createReactAgent

// 1. 子 agent:用 createAgent 建,跟普通 agent 没有区别
const researcher = createAgent({ model: researcherLlm, tools: [searchTool], name: 'researcher' });
const coder = createAgent({ model: coderLlm, tools: [codeTool], name: 'coder' });

// 2. ★ 把它包成 tool —— 这就是 subagents pattern 的全部
function asSubagentTool({ name, description, agent }) {
return tool(
async ({ task }) => {
const result = await agent.invoke({ messages: [new HumanMessage(task)] });
const last = result.messages.at(-1);
const content = typeof last.content === 'string' ? last.content : JSON.stringify(last.content);
// ★ 官方说的「outputMode 由你负责」,就是这一行:返回什么,外层就看到什么
return `${name} 的结论:${content}`;
},
{ name: `ask_${name}`, description, schema: z.object({ task: z.string() }) },
);
}

const subagentTools = [
asSubagentTool({ name: 'researcher', description: '调研类问题交给它', agent: researcher }),
asSubagentTool({ name: 'coder', description: '写代码/给示例交给它', agent: coder }),
];

// 3. 调度者:一个普通 createAgent,没有任何"supervisor 特殊机制"
const coordinator = createAgent({
model: coordinatorLlm,
tools: subagentTools,
name: 'coordinator',
prompt: '你是调度者:把任务交给合适的专家,完成后自己收尾。',
});

注意第 3 步——外层就是一个普通的 ReAct agent。所谓「Supervisor 架构」在这里退化成了一个最普通的 agent 循环,因为它本来就不需要特殊机制:调度无非是「模型在一个循环里选工具」。

真实执行轨迹​

[LLM:coordinator] 第 1 次调用 → 调用 ask_researcher
→ 节点 model_request
[LLM:researcher] 第 1 次调用 → 调用 search
[LLM:researcher] 第 2 次调用 → 收尾
→ 节点 tools
[LLM:coordinator] 第 2 次调用 → 调用 ask_coder
→ 节点 model_request
[LLM:coder] 第 1 次调用 → 调用 write_code
[LLM:coder] 第 2 次调用 → 收尾
→ 节点 tools
[LLM:coordinator] 第 3 次调用 → 收尾
→ 节点 model_request

外层图只有两个节点:model_request 和 tools(就是 ReAct 循环本身),而不是 supervisor 版里的「supervisor + researcher + coder」。

消息序列(值得逐行对比)​

0. human 帮我调研 LangGraph supervisor 并给出示例
1. ai name=coordinator tool_calls=ask_researcher
2. tool name=ask_researcher researcher 的结论:研究结论:Supervisor 负责路由...
3. ai name=coordinator tool_calls=ask_coder
4. tool name=ask_coder coder 的结论:代码骨架已给出:graph = createSupervisor({..
5. ai name=coordinator 已完成:先调研架构,再给出示例。

只有 6 条。 对比 §四 那张表:supervisor 默认 12 条、full_history 16 条、极限压缩后才 4 条。subagents 版本不调任何参数就是 6 条,而且——子 agent 的内部轨迹(search / write_code 那些工具调用)根本不进外层状态,因为它们在另一个图里跑,边界就是那个 tool 的返回值。

同任务实测对比​

Supervisor 库(默认 last_message)手写 SupervisorSubagents pattern
外层图节点supervisor + 2 个 worker同左model_request / tools
路由用的 LLM 调用3 次3 次3 次(完全相同)
共享状态消息数12126
子 agent 内部轨迹是否进外层否(靠 outputMode 裁掉)是否(天然隔离)
交接记账消息有(transfer + transfer_back 各一对)有(仅 transfer)无(工具调用本身就是交接)
需要调参吗要(outputMode/addHandoffMessages)—不用

两个结论:

  1. 路由成本完全一样——都是 3 次 LLM 调用。所以「subagents 更省」不是省在模型调用上,而是省在上下文(12 → 6)和省在配置复杂度(不用调参);
  2. 它的上下文更短,不是靠裁剪,而是靠天然隔离——子 agent 是另一个图,它的中间过程从来没有进过外层状态,所以不存在「先并进来再裁掉」这回事。这正是官方说「more control over context engineering」的实质。

从官方文档里要记住的三条规则​

官方文档给了一节 「Requirements for interrupt propagation」,三条规则(以下为原文要点,不是我的总结):

  1. ★ 只给最外层图挂 checkpointer(Compile only the outermost graph with a checkpointer);
  2. 子 agent 一律不要写 checkpointer=...——官方给的机制是:让它们使用 per-invocation 持久化,并在运行时继承父图的 checkpointer(inherit the parent's checkpointer at runtime)。这个继承链正是「子 agent 里的 interrupt() 能暂停整个图」的前提;
  3. thread_id 必须传——最外层 invoke() / stream_events() 的 configurable 里要带 thread_id,图才有得 checkpoint 与 resume。

而且官方明确说:这三条对任意层级的嵌套都成立(These rules apply to arbitrarily nested setups),给了一个「自定义 StateGraph 外层 + 中间 create_agent 监督者 + 内层 create_agent 子 agent」的三层例子。

⚠️ 早期版本把第 2 条的后果写成「会让中断静默失效」——官方文档并没有这么说,官方给的是上面这个「继承 + per-invocation 持久化」的机制解释;第 3 条(thread_id)也是本次核对时补上的。

另外一条容易忽略的官方限定(重要):subagents 工具范式不是万能的。官方在嵌套那一节末尾写明——如果你需要 静态子图发现(static subgraph discovery)、按层级的 checkpoint 命名空间、或层级间共享 state key,那就该改用「自定义 StateGraph + subgraph 节点」,而不是把子 agent 包成 tool。

除了上面三条,还有一条来自 JS 文档的实践建议:子 agent 的 name / description 就是路由的提示词——「模型什么时候把活交给它」由这两个字段决定,它们是一等公民的调参对象,不是随便起的名字。

补充一个 JS 侧的写法差异:官方 JS 文档里的示例倾向于只暴露一个参数化的 task tool(schema 里带 agentName + description),而不是上面每个子 agent 一个 tool。二者等价,单一 task tool 的好处是工具列表不随子 agent 数量膨胀——子 agent 多了会明显更干净。

六、它和其他多 Agent 写法怎么选​

写法控制流适合不适合
单 Agent + 多工具模型自己在一个循环里选工具工具数 < 10、任务单一工具多、需要不同提示词/模型
★ Subagents(当前推荐)中心化调度,子 agent 是一个 tool新代码默认选它:角色分明 + 上下文天然隔离 + 不用调参需要「子 agent 之间互相对话」(那要靠更复杂的编排)
Supervisor 库(createSupervisor)中心化调度,worker 是图节点仅存量代码维护新代码(官方已改推荐)
条件边路由预先写死的分支边流程固定(如「先查后算」)需要模型临场决定下一步
Handoff(去中心)agent 之间直接交接平等协作、链式转接需要集中审计与节流

判断依据:你的流程需要「一个地方统一决策」吗? 需要就走中心化调度——而中心化调度的当前实现就是 subagents pattern;不需要,条件边或 handoff 更省(每次调度都要跑一遍 LLM,这是固定成本,三种中心化写法都一样)。

注意「Supervisor-Worker」是一种架构,而 createSupervisor 只是它的一种(已过时的)实现。两者别混:架构继续有效,实现换了。

七、生产上要注意的五件事​

  1. ★ 防死循环:终止条件是「supervisor 不再派活」,但模型可能反复派活。LangGraph 有 recursionLimit 兜底——别关掉它,并给 supervisor 的 prompt 写清「什么时候算完成」。
  2. ★ 历史会膨胀:多轮任务里共享历史线性增长。官方给了 preModelHook——在 supervisor 调 LLM 前插一个节点做裁剪或摘要。这是长任务必须加的一层。
  3. 接 checkpointer 才有记忆:默认跑完即忘。要跨请求续聊,得挂 checkpointer(thread_id),做法见 LangGraph 入门那篇。
  4. includeAgentName 有 provider 限制:默认(undefined)依赖 provider 支持 AI 消息上的 name 属性——目前只有 OpenAI 支持。其他 provider 要设成 "inline",把 agent 名以 XML 标签塞进 content。
  5. worker 的模型可以不同:这是这套架构的隐藏红利——researcher 用便宜模型、coder 用强模型,各取所需(模型分层的做法见 模型路由器)。

八、局限​

  • 全部输出为脚本化模型的真实运行结果:执行轨迹、消息序列、参数效果都是真实的,但不反映真实 LLM 的决策质量(比如 supervisor 会不会挑错 worker);
  • demo 只用了 2 个 worker、各 2 次 LLM 调用,所以上下文差值只有 4 条消息——真实场景里 worker 工具调用越多,last_message 省下的越多,本文的绝对值不可外推;
  • 未测 checkpointer / 持久化 / 中断恢复,因而也未验证§五 那三条官方规则(尤其「给子 agent 单独挂 checkpointer 会让中断静默失效」——这条我只做了文档核对,没有实测复现);
  • 未测真实 provider 下 addHandoffMessages: false 是否真能规避 tool-call 序列校验问题(这是官方注释给出的动机);
  • §五 的 subagents 实现是最简单的一种形态(每个子 agent 一个 tool):未测官方 JS 文档里那个「单一参数化 task tool」写法,也未测多轮/嵌套 subagent;
  • 迁移文档我只核对了 Python 版(/oss/python/migrate/langgraph-supervisor);JS 侧对应的是 Subagents 指南 与 v1 迁移指南,没有找到 JS 专属的 "migrate from supervisor" 页面——所以「JS 与该映射逐条对应」这一层是我基于两侧文档的推断;
  • 版本相关:结论以文首版本为准;langgraph-supervisor 仍在小版本迭代(本文写前一天发过 1.1.2)。

关联​

自测​

  1. Supervisor-Worker 里的「派活」是怎么实现的?为什么要求 supervisor 的模型必须支持 tool calling?
  2. 为什么完整轨迹总是 supervisor → worker → supervisor?worker 之间能直接对话吗?
  3. 这套架构的终止条件是什么?死循环风险从哪来、怎么兜?
  4. 手写时返回 Command 为什么要声明 ends?用官方库为什么不用管这个?
  5. outputMode 的 full_history 与 last_message 差在哪?实测差了几条消息?
  6. 关掉 addHandoffMessages 省下了什么?代价是什么?你会怎么取舍?
  7. 什么情况下不该用中心化调度,而该用条件边或 handoff?
  8. subagents pattern 里「子 agent 内部轨迹不进外层状态」是靠裁剪实现的,还是靠隔离实现的?这两者的区别为什么重要?
  9. 为什么官方要求「只有最外层图能挂 checkpointer」?给子 agent 单独挂会怎样?
  10. 实测里 subagents 与 supervisor 库的路由 LLM 调用次数相同,那 subagents 的优势体现在哪两个地方?

参考​

  • 实测脚本与真实输出(随仓库留档):source/langgraph-supervisor/——supervisor.mjs(官方库 + 三组参数对比)、handrolled.mjs(手写版,约 40 行复现同样轨迹)、subagents.mjs(subagents pattern,§五 的实现与实测)
  • 环境:Node v24.14.1 / @langchain/langgraph@1.4.16 / @langchain/langgraph-supervisor@1.1.2 / @langchain/core@1.2.12 / langchain@1.5.11
  • 官方推荐路径:Subagents(JS)、Migrate from langgraph-supervisor(Python)、JavaScript v1 迁移指南(含 createReactAgent → createAgent)
  • 存量实现的 API:langgraph-supervisor 包、LangGraph JS · Multi-agent
  • 说明:全程使用脚本化模型(自定义 BaseChatModel 按次序返回预置响应),无需 API Key;本机未运行该项目的单元测试