跳到主要内容

官方为什么把 supervisor 库换掉了:官方只说了一句,剩下的靠实测

· 阅读需 15 分钟

上一篇记录了官方的推荐变更:从 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-supervisor package 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
19 条 / 154 字符7 条 / 141 字符4 条 / 44 字符
313 条 / 180 字符7 条 / 141 字符4 条 / 44 字符
619 条 / 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_supervisor exposes output_mode to 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 for last_message behavior, or return a formatted summary of the full conversation for full_history behavior.

注意加粗那句:官方说的正是「这件事改由你的工具包装层来做」。它没有解释「为什么这样更好」,但指认了责任转移——我的实测是在量化这个转移的收益。

三、为什么会必然如此:node 语义 vs tool 语义​

这不是「库写得不好」,而是抽象语义不匹配——两类东西被塞进了同一层。

旧模型:worker 是图节点​

图节点有两条铁律:

  1. 节点的输出按定义要并进图状态——否则图无法继续(这就是 full_history 会长的原因,也是为什么必须再发明 outputMode 把它裁掉);
  2. 「下一步去哪」必须编码进状态——因为图是靠状态驱动的,于是「转交给谁」这件事必须留下痕迹,这才有了 transfer / transfer_back 消息。

换句话说:那 3 条记账消息不是实现细节,而是「用状态表达路由」这个决定的必然产物。 你想让图上知道「模型请求转交」,就得把它写进消息流;写了就得在每次调用时重新发给模型。

新模型:worker 是 tool​

tool 的两条性质正好相反:

  1. tool 的返回值是一个「值」,不是状态——ask_worker 返回一个字符串,它进上下文的方式和调用 search 完全一样;
  2. 「下一步去哪」由 ReAct 循环天然处理——模型调了工具就执行、不调就结束,根本不需要额外的路由编码。

所以 subagents 版的委派只需要 2 条消息,而且没有任何机制性开销——它用的就是最普通的工具调用。

一句话:旧模型把「委派」实现成了状态机转移,新模型把「委派」实现成了一次函数调用。前者必然更重。

四、症状:一个库用五个参数收拾自己产生的东西​

这是判断「抽象是否划错了边界」的一个实用信号。看看 createSupervisor 后来长出了多少参数:

参数在解决什么
outputMode把 worker 的内部轨迹裁出去(因为节点输出会进状态)
addHandoffMessages把交接记账裁出去
addHandoffBackMessages把回程记账也裁出去
includeAgentNameprovider 不认 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 的入参和返回值),而函数边界是你写的——框架没有立场替你决定。

六、代价:这不是纯收益,是复杂度搬家​

只讲好处是不诚实的。新范式把复杂度从框架配置搬到了你的代码:

  1. 交接记账要自己写(如果你需要的话);
  2. 共享状态不再是完整审计日志——实测证据:subagents 版的最终状态里完全没有 transfer 类痕迹,只有「工具调用 + 工具结果」;而库版本至少留下了 transfer_to_X / transfer_back 的流转记录;
  3. responseFormat 之类的顺带能力,在工具包装层要自己实现。

第 2 条最需要留意:旧范式那句「交接在消息流里是可审计的」,在新范式里不再成立。想在 subagents 架构里追溯「谁把活交给了谁」,只能靠 tracing(LangSmith / Langfuse),不能靠消息历史。

这正好接上我写过的另一篇:Langfuse 与 LangSmith——注意那里的坑(不 flush 就静默丢 trace)。审计责任转移到 tracing 之后,tracing 的可靠性就从「锦上添花」变成了「合规底线」。

一句话概括这笔交易:控制权给了你,责任也给了你。 框架不再替你决定哪些消息该进上下文——代价是你得自己决定,而且猜错的账单(上下文膨胀、成本失控)归你。

必须说清楚:「框架猜不准」是我的判断,不是官方的说法(官方只说了「不再积极维护」,没解释为什么)。

七、怎么判断自己该不该「自己控制上下文」​

三条判据,命中任意一条就值得自己写:

  1. worker 的内部工作量大——实测里 full_history 的斜率是 2 条/工具调用。如果 worker 常做十几二十次工具调用,旧模型的共享状态会被撑得很难看;
  2. 你要的是「一个结果」,不是「一段过程」——如果调度者只关心 worker 的结论,那 worker 的中间轨迹对它就是纯噪音;
  3. 你有明确的上下文预算——把边界做成函数边界之后,预算在哪一行代码上花掉是可见的;靠框架参数调,你只能事后观察。

官方还给了这一侧的反例,值得一并记住:迁移文档在嵌套那一节末尾写明——如果你需要 静态子图发现(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 都在教你用旧写法。

只有读文档的人知道推荐变了。 这就是这类「软弃用」最危险的地方:你不可能靠工具发现它,只能靠人。

可操作的应对:

  1. 把「框架推荐是否变化」纳入依赖升级检查——不要只看 semver 和 changelog,定期看一眼官方文档里的 multi-agent / agents 章节;
  2. 对「框架替你生成机制消息」的设计保持警惕——如果你发现某个组件为了自己工作而往你的上下文/状态里塞东西,多问一句「这些东西我到底要不要」;
  3. 把「配置项数量」当抽象的体温计——一个组件持续长出「用来抵消自己默认行为」的参数,通常说明边界划错了。

九、结论​

回到「官方为什么改」这个问题。诚实的答案是分层的:

  • 官方自己只说了:不再积极维护,请改用 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 条是推断;
  • 版本相关:该生态迭代很快,结论以文首版本为准。

关联​

自测​

  1. full_history 下共享状态的增长斜率是多少?为什么是这个数?
  2. 库默认多出来的 3 条消息是什么?它们携带了什么信息?
  3. 为什么说「worker 是节点」这个设计必然产生记账消息?换成 tool 为什么就不需要了?
  4. 文中的「配置项数量是抽象的体温计」怎么用?举一个本文里的例子。
  5. createReactAgent → createAgent 与本次变更有什么关系?
  6. 新范式下「交接可审计」这个能力去哪了?由此带来的新风险是什么?
  7. 为什么说这次弃用在代码世界里「几乎不留痕迹」?你打算怎么改进自己的依赖检查流程?

参考​