官方为什么把 supervisor 库换掉了:官方只说了一句,剩下的靠实测
上一篇记录了官方的推荐变更:从 createSupervisor(worker 是图节点)改成 subagents pattern(子 agent 包成 tool)。这篇回答「为什么」。
⚠️ 更正说明(2026-09-21 追加)
本文初版把「context engineering / context isolation」当成了官方的理由来引用。这是错的:我后来拿到了官方迁移文档的原文(用
curl直取,此前误判为被网络策略挡住),逐字核对后发现——
官方文档里
context engineering出现 0 次、context isolation出现 0 次、more control出现 0 次;官方给出的理由只有一句,而且纯粹是维护状态(原文逐字):
The
langgraph-supervisorpackage is no longer actively maintained. Instead use the subagents pattern: a main agent coordinates specialized workers by calling them as tools.「context engineering / isolation」那些措辞来自第三方社区资料(搜索命中的 skill 文档与技术 PDF),不是 LangChain 官方。
因此本文的定位是:官方没有给技术论证,只说了「不再积极维护」——为什么该换,官方是留白的;下面全部机制解释都是本文自己的实测分析,不是官方立场。
官方没给理由,那理由得自己找。下面用实测数据给出一个工程师视角的解释:
我的结论是:在旧模型里,框架会产生一批「你并不想要」的消息,然后用一堆参数让你把它们再弄出去。新模型从构造上就不产生它们。
注意这句是我的分析,不是官方说法。 但它有官方文档的间接印证——迁移文档里那句关于 output_mode 的原文(见 §二 末),说的正是同一件事。
环境:Node v24.14.1 /
@langchain/langgraph@1.4.16/@langchain/langgraph-supervisor@1.1.2/langchain@1.5.11。用脚本化模型(自定义BaseChatModel按次序返回预置响应)替代真实 LLM——无需 API Key、执行轨迹完全确定。脚本留档在source/langgraph-supervisor/context-growth.mjs。
一、先看数据:上下文随 worker 的工作量怎么变
同一个任务(调度者派活一次给一个 worker),只改一个变量:worker 内部要做多少次工具调用(N)。测三种实现下共享状态的规模:
| N(worker 工具调用数) | 库 full_history | 库 last_message(默认) | subagents |
|---|---|---|---|
| 1 | 9 条 / 154 字符 | 7 条 / 141 字符 | 4 条 / 44 字符 |
| 3 | 13 条 / 180 字符 | 7 条 / 141 字符 | 4 条 / 44 字符 |
| 6 | 19 条 / 219 字符 | 7 条 / 141 字符 | 4 条 / 44 字符 |
三条曲线,三种形状:
full_history线性增长——worker 每多做一次工具调用,共享状态 +2 条(一条AI工具调用 + 一条tool结果)。斜率是 2,意味着worker 越努力干活,调度者要背的上下文越重;last_message恒定 7 条——靠裁剪把 worker 内部轨迹切掉了,所以与 N 无关;- subagents 恒定 4 条——与 N 无关,而且比库的默认值还少 3 条。
N=6 时字符数差 5 倍(219 vs 44)。
二、那 3 条差额是什么?——「交接记账」
差额不是玄学,可以直接定位到具体消息。每次「委派」占用的消息数(扣掉 human 问题和最终回答):
库默认 last_message : 5 条 = transfer 对(2) + worker 结论(1) + transfer_back 对(2)
subagents : 2 条 = 工具调用(1) + 工具结果(1)
库多出来的 3 条,是 transfer_to_worker 与 transfer_back_to_supervisor 这两对「记账消息」——它们不携带任何有用信息,只是为了让图知道「谁下一步该跑」。
在上一篇里我把这三条称作「可审计的流转记录」,那是解读;从上下文成本的角度看,它们是纯开销。 同一件事有两个视角,这里必须给出另一面。
官方文档其实间接印证了这个机制——迁移文档里关于消息历史的那一节,原文逐字如下:
create_supervisorexposesoutput_modeto control how worker messages appear in conversation history:full_history: Include all messages from the worker agent.last_message: Include only the worker's final response. With the subagents pattern, control this in the tool wrapper. Return only the final message forlast_messagebehavior, or return a formatted summary of the full conversation forfull_historybehavior.
注意加粗那句:官方说的正是「这件事改由你的工具包装层来做」。它没有解释「为什么这样更好」,但指认了责任转移——我的实测是在量化这个转移的收益。
三、为什么会必然如此:node 语义 vs tool 语义
这不是「库写得不好」,而是抽象语义不匹配——两类东西被塞进了同一层。
旧模型:worker 是图节点
图节点有两条铁律:
- 节点的输出按定义要并进图状态——否则图无法继续(这就是
full_history会长的原因,也是为什么必须再发明outputMode把它裁掉); - 「下一步去哪」必须编码进状态——因为图是靠状态驱动的,于是「转交给谁」这件事必须留下痕迹,这才有了 transfer / transfer_back 消息。
换句话说:那 3 条记账消息不是实现细节,而是「用状态表达路由」这个决定的必然产物。 你想让图上知道「模型请求转交」,就得把它写进消息流;写了就得在每次调用时重新发给模型。
新模型:worker 是 tool
tool 的两条性质正好相反:
- tool 的返回值是一个「值」,不是状态——
ask_worker返回一个字符串,它进上下文的方式和调用search完全一样; - 「下一步去哪」由 ReAct 循环天然处理——模型调了工具就执行、不调就结束,根本不需要额外的路由编码。
所以 subagents 版的委派只需要 2 条消息,而且没有任何机制性开销——它用的就是最普通的工具调用。
一句话:旧模型把「委派」实现成了状态机转移,新模型把「委派」实现成了一次函数调用。前者必然更重。
四、症状:一个库用五个参数收拾自己产生的东西
这是判断「抽象是否划错了边界」的一个实用信号。看看 createSupervisor 后来长出了多少参数:
| 参数 | 在解决什么 |
|---|---|
outputMode | 把 worker 的内部轨迹裁出去(因为节点输出会进状态) |
addHandoffMessages | 把交接记账裁出去 |
addHandoffBackMessages | 把回程记账也裁出去 |
includeAgentName | provider 不认 AI 消息的 name 字段时,改用 XML 塞进 content |
preModelHook / postModelHook | 在调度前裁剪/摘要历史(控制上下文) |
前三个和后两个的共同点:都在把不该进上下文的东西弄出去,或在给已经进上下文的东西补救。
当边界划对了,你不该需要这么多参数来undo自己的默认行为。这也是为什么新范式里这些参数一个都不需要——不是省略了,而是它们要解决的问题不存在了。
五、更大的背景:框架的抽象单位从「图」换成了「agent」
把这次变更放到 LangChain v1 的整体方向里看,会更清楚它不是孤立事件:
| 旧 | 新 | 方向 |
|---|---|---|
createReactAgent(langgraph/prebuilt) | createAgent(langchain) | agent 成为一等公民 |
preModelHook / postModelHook(库专属钩子) | middleware(通用机制) | 通用机制取代专属钩子 |
| worker 是图节点,supervisor 是图 | 子 agent 是 tool,外层就是普通 agent | 图退化为实现细节 |
实测能看到最后一条的直接证据:subagents 版的外层图只有 model_request 和 tools 两个节点——那就是一个最普通的 ReAct 循环。所谓「Supervisor 架构」在新范式里根本不需要特殊的图结构。
所以这次不是「换个库」,而是框架层面的抽象在换单位:从「你搭一张图,图里有会思考的节点」变成「你组合若干 agent,图怎么跑是内部实现」。
顺手一个观察:同一个方向也解释了为什么
outputMode这类参数会消失。当单位是 agent,上下文边界就是函数边界(tool 的入参和返回值),而函数边界是你写的——框架没有立场替你决定。
六、代价:这不是纯收益,是复杂度搬家
只讲好处是不诚实的。新范式把复杂度从框架配置搬到了你的代码:
- 交接记账要自己写(如果你需要的话);
- 共享状态不再是完整审计日志——实测证据:subagents 版的最终状态里完全没有
transfer类痕迹,只有「工具调用 + 工具结果」;而库版本至少留下了transfer_to_X/transfer_back的流转记录; responseFormat之类的顺带能力,在工具包装层要自己实现。
第 2 条最需要留意:旧范式那句「交接在消息流里是可审计的」,在新范式里不再成立。想在 subagents 架构里追溯「谁把活交给了谁」,只能靠 tracing(LangSmith / Langfuse),不能靠消息历史。
这正好接上我写过的另一篇:Langfuse 与 LangSmith——注意那里的坑(不 flush 就静默丢 trace)。审计责任转移到 tracing 之后,tracing 的可靠性就从「锦上添花」变成了「合规底线」。
一句话概括这笔交易:控制权给了你,责任也给了你。 框架不再替你决定哪些消息该进上下文——代价是你得自己决定,而且猜错的账单(上下文膨胀、成本失控)归你。
必须说清楚:「框架猜不准」是我的判断,不是官方的说法(官方只说了「不再积极维护」,没解释为什么)。
七、怎么判断自己该不该「自己控制上下文」
三条判据,命中任意一条就值得自己写:
- worker 的内部工作量大——实测里
full_history的斜率是 2 条/工具调用。如果 worker 常做十几二十次工具调用,旧模型的共享状态会被撑得很难看; - 你要的是「一个结果」,不是「一段过程」——如果调度者只关心 worker 的结论,那 worker 的中间轨迹对它就是纯噪音;
- 你有明确的上下文预算——把边界做成函数边界之后,预算在哪一行代码上花掉是可见的;靠框架参数调,你只能事后观察。
官方还给了这一侧的反例,值得一并记住:迁移文档在嵌套那一节末尾写明——如果你需要 静态子图发现(static subgraph discovery)、按层级的 checkpoint 命名空间、或层级间共享 state key,那就不该把子 agent 包成 tool,而应该用「自定义 StateGraph + subgraph 节点」。
也就是说:subagents 是大多数情况的默认解,不是唯一解。 判据是「你要的是结果,还是要子图本身可被发现、可被单独 checkpoint、可与父层共享状态」。
反过来说,如果你确实需要「过程留在共享状态里」(比如要回放整个协作轨迹做审计),那旧模型的 full_history 反而是特性而不是负担——但那时你应该用 tracing,而不是把消息历史当日志用。
八、顺带一个观察:软弃用几乎不留代码痕迹
我核对过:@langchain/langgraph-supervisor 的包内 README 至今没有任何弃用或迁移提示,仍然在教你怎么用 createSupervisor,还把「Tool-based agent handoff mechanism」列为特性;npm 上也没有 deprecated 标记,最近一次发版是 1.1.2 / 2026-09-12。
也就是说:这次「弃用」在代码世界里几乎不留痕迹。
- 不会有运行时警告;
- 不会有安装时的 deprecation 提示;
- 不会因为版本号跳变而触发你的依赖审查;
- 连包的官方 README 都在教你用旧写法。
只有读文档的人知道推荐变了。 这就是这类「软弃用」最危险的地方:你不可能靠工具发现它,只能靠人。
可操作的应对:
- 把「框架推荐是否变化」纳入依赖升级检查——不要只看 semver 和 changelog,定期看一眼官方文档里的 multi-agent / agents 章节;
- 对「框架替你生成机制消息」的设计保持警惕——如果你发现某个组件为了自己工作而往你的上下文/状态里塞东西,多问一句「这些东西我到底要不要」;
- 把「配置项数量」当抽象的体温计——一个组件持续长出「用来抵消自己默认行为」的参数,通常说明边界划错了。
九、结论
回到「官方为什么改」这个问题。诚实的答案是分层的:
-
官方自己只说了:不再积极维护,请改用 subagents pattern。没有技术论证;
-
本文给出的机制解释(我自己的分析):把「哪些消息进上下文」这件事,从框架的默认行为 + 一堆补偿参数,变成你写在函数边界上的显式代码。
-
旧模型(worker 是节点):图必须用状态表达路由 → 产生记账消息 → 产生「裁掉它」的参数 → 你调参数;
-
新模型(worker 是工具):路由就是一次工具调用 → 没有记账 → 没有参数 → 你写边界。
代价是审计轨迹从状态里消失,责任转移到 tracing。 这笔交易值不值,取决于你是更需要「框架替你管上下文」还是「自己决定上下文的每一行」——但官方的立场很明确:前者他们猜不准。
十、局限
- **【已更正】**本文初版称「没能打开官方文档、引用来自搜索转述」,并把原因写成「与项目网络策略冲突」;实际是拉取工具的域名校验环节未通过。本文现已用
curl直取官方文档(HTTP 200)并逐字核对; - 文中官方原文(不再积极维护那一句、
output_mode那一节、interrupt 传播三条规则、subgraph 反例)均为逐字核对; - 「context engineering / context isolation」不是官方措辞——官方文档中该词组出现 0 次,它们来自第三方社区资料。本文正文已按此更正;
- 「框架猜不准上下文该怎么管」「复杂度搬家」等判断性结论是我的分析,不是官方立场;
- 实验是脚本化模型的确定性对照:斜率、消息构成、开销归属都是真实的,但绝对字符数不可外推(我的工具描述、结论文案都很短);
- 只测了单 worker、单次委派;多 worker、多轮次、嵌套 subagent 的上下文增长未测;
- 未测
full_history在真实长任务里的实际膨胀(理论上限更极端); - 「复杂度搬家」的代价里,第 2 条(审计轨迹消失)是我实测的(对比了最终状态的消息构成),第 1、3 条是推断;
- 版本相关:该生态迭代很快,结论以文首版本为准。
关联
- 本次变更的事实与实测:LangGraph.js Supervisor-Worker 架构解析(含更正说明与 subagents 实现)
- 审计责任转移到 tracing:Langfuse 与 LangSmith
- 「上下文是主要成本」的另一组证据:多路检索实战、Agent 工程化总览
- 状态与 reducer 的机制(旧模型依赖的正是它):LangGraph reducer 与 Annotation
自测
full_history下共享状态的增长斜率是多少?为什么是这个数?- 库默认多出来的 3 条消息是什么?它们携带了什么信息?
- 为什么说「worker 是节点」这个设计必然产生记账消息?换成 tool 为什么就不需要了?
- 文中的「配置项数量是抽象的体温计」怎么用?举一个本文里的例子。
createReactAgent→createAgent与本次变更有什么关系?- 新范式下「交接可审计」这个能力去哪了?由此带来的新风险是什么?
- 为什么说这次弃用在代码世界里「几乎不留痕迹」?你打算怎么改进自己的依赖检查流程?
参考
- 实测脚本与真实输出(随仓库留档):
source/langgraph-supervisor/context-growth.mjs(参数化对比 N=1/3/6)及*.log - 官方推荐路径(本文未能直接抓取页面,仅经搜索结果转述):Subagents(JS)、Migrate from langgraph-supervisor(Python)、JavaScript v1 迁移指南
- 本次变更的事实核对:LangGraph.js Supervisor-Worker 架构解析
- 环境:Node v24.14.1 /
@langchain/langgraph@1.4.16/@langchain/langgraph-supervisor@1.1.2/langchain@1.5.11
