LangGraph 子图事件为何突然消失
升级到 1.2.22 后,带独立配置的子图可重新进入父图的 `subgraphs=True` 流;未开启该选项的调用仍不会收到子图事件。
正文
先看结论
如果 LangGraph 的父图在流式运行时突然看不到子图的 token、工具事件或节点输出,先检查两件事:调用方是否设置了 subgraphs=True,以及子图或子任务是否使用了自己的 thread_id、checkpoint_ns、checkpoint_id,或通过带独立 configurable 的 runnable 调用。LangGraph 1.2.22 修复了这条组合路径:这类子图重新把 messages 和 tools 事件交给正在接收子图流的父图,事件命名空间也保持子图自己的形状。
这不是“所有流式调用默认都能看到内部事件”的改变。父图没有开启 subgraphs=True 时,子图事件仍然不会被转发;直接调用子图本身,也和调用已经包裹子图的父图是两条不同的观测路径。应用如果只根据父图最终返回值判断运行正常,可能会漏掉这次回归;应用如果依赖 token、工具调用或节点状态实时更新,则会直接表现为流变空、界面停住或工具进度缺失。
具体坏在哪里
LangGraph 的图可以把另一个已编译图作为节点,因此外层图负责调度,内层图负责一段独立的状态转换。流式输出则需要回答另一个问题:调用方想观察哪一层的事件。官方 Python 流式文档把 subgraphs=True 定义为“同时输出父图和子图的结果”;对 messages 模式而言,子图中的模型 token、工具事件和任务事件都可能成为调用方需要展示或记录的内容。
回归发生在一个更窄的组合条件中。子图不是简单地在父节点当前任务里执行,而是因为自己的 thread_id、checkpoint_ns 或 checkpoint_id,或者因为 runnable 带了自己的 configurable,在调用方任务之外运行。这样的子图会继承父图的流处理器。1.2.19 之后,处理器筛选逻辑把这些事件全部丢掉了:没有 subgraphs=True 的调用本来不应看到它们,而明确要求子图事件的调用也被同一条过滤逻辑误伤。
因此,故障现象可能很像“模型没有生成内容”,实际却是事件没有到达正确的观察者。最终状态仍可能写入检查点,invoke() 也可能返回预期结果;只有依赖 stream() 的界面、日志管道、工具进度面板或增量消息聚合器会先暴露问题。排查时要把“图执行是否成功”和“事件是否被转发”分成两个独立问题。
1.2.22 改了什么
LangGraph 1.2.22 的 release note 将修复写成:当子图在自己的线程中运行时,如果调用方使用 subgraphs=True 流式接收,子图的 messages 与 tools 事件会重新出现,行为恢复到 1.2.19 之前。修复没有把事件移动到调用节点的命名空间;子图事件继续使用它原本的 namespace。这个细节很重要,因为消费端往往会把 namespace 当作路由键,改变它会造成另一种兼容性问题。
对应的 GitHub PR 还列出了明确边界。恢复的是带 subgraphs=True 的调用;没有这个选项的父图仍不会接收外部子图事件。JavaScript 路径没有同样的改动,因为它原有的处理器只会在不使用 subgraphs 时过滤这些事件。PR 同时加入了同步与异步回归测试,覆盖“子图拥有自己的线程,但调用方正在流式接收子图”的场景。
版本判断也需要谨慎。不能只看 langgraph 主包版本就推断所有相关包已经同步,因为 1.2.22 的 release 同时列出了 langgraph-prebuilt、langgraph-checkpoint 与 langgraph-checkpoint-postgres 的版本变化。真正受影响的进程应记录 langgraph 实际安装版本、Python 解释器环境,以及是否从锁文件安装;容器里运行的版本和本地开发环境不一致时,单看项目声明文件不够。
可以先运行下面的检查,确认当前进程加载的版本,而不是猜测包管理器解析结果:
python -c "import importlib.metadata as m; print(m.version('langgraph'))"
python -m pip show langgraph
如果环境仍在 1.2.19 附近或更早版本,先在隔离环境中升级并运行现有测试。若已经是 1.2.22,但事件仍然消失,就不要继续盲目升级,直接检查流式参数、配置继承和事件消费代码。
调用方必须显式打开子图流
最小的验证点不是“调用了 stream()”,而是父图调用是否包含 subgraphs=True:
for namespace, chunk in app.stream(
input_data,
config,
stream_mode="messages",
subgraphs=True,
):
handle(namespace, chunk)
实际项目要根据版本和流式模式处理返回结构。文档说明,在 v1 输出格式下,启用子图后可能得到 (namespace, data),如果同时启用多个模式,还会出现包含 mode 的元组;v2 则使用统一的 StreamPart 字典。不要把一个版本中的元组下标解析器直接复制到另一个版本,也不要把 namespace 当成永远固定的一层字符串。
如果使用 stream_mode="messages",消费端应区分模型消息块、元数据和工具相关事件。一个稳妥的排查顺序是:先把原始 chunk 连同 namespace 完整记录,再确认是否有子图 namespace;随后确认消息块来自哪个节点,最后才把它转换成前端需要的文本或工具进度。先做结构化记录,能避免把解析错误误判成 LangGraph 没有发事件。
还要注意直接调用和包裹调用的差异。官方文档特别说明,把一个 compiled agent 作为节点加入父图后,它就成为子图;父图的 messages 流如果没有 subgraphs=True,不会发出内部 agent 的 token,而直接调用 agent 自己的 stream() 可以发出。也就是说,测试“子图单独能流式输出”并不能证明“父图能把子图输出转发出来”。这两个测试必须分别保留。
如何复现与确认修复
回归测试应尽量贴近官方 PR 的触发条件,而不是只测一个没有独立配置的普通嵌套图。可以按四组用例组织:
- 子图直接作为父图节点运行,父图使用
subgraphs=True,确认能看到子图事件。 - 子图拥有独立
thread_id、checkpoint_ns或checkpoint_id,父图仍使用subgraphs=True,确认messages、tools和节点输出不会消失。 - 同一图去掉
subgraphs=True,确认子图事件仍然不会混入父图普通流,避免修复扩大转发范围。 - 分别运行同步和异步 API,确认两条路径的 namespace 与事件数量满足应用协议。
示例断言不应只比较最终字符串。对于 messages 模式,至少保存每个事件的 namespace、事件类型、节点标识和是否包含工具调用;对于工具流,还要记录工具名和参数片段是否在同一调用中按预期到达。应用若在收到第一个 token 后就把工具参数交给执行器,更应该验证事件顺序,而不是只断言最终结果包含某个文本。
升级前后对比时,建议固定同一输入、同一 configurable、同一流模式和同一 subgraphs 选项。不要把升级测试与模型供应商切换、提示词改动或前端解析重构放在同一个变更里。若应用经由 API 中转或自建适配层接入模型,可在 平台目录 中记录实际使用的模型入口,但这不能替 LangGraph 自身的事件回归测试;模型请求成功,也不代表本地图事件已经被正确转发。
三类看似相同的故障
第一类是版本回归。父图确实设置了 subgraphs=True,子图也使用独立配置,升级前有事件、升级后消失;这与 1.2.22 PR 描述的路径相符,优先比较锁文件和运行环境。
第二类是调用选项遗漏。应用把 subgraphs=True 写在某个调试脚本里,却没有写进生产 worker;或者封装函数只转发了 stream_mode,没有转发 subgraphs。这时升级不会自动改变行为,修复应在调用边界补上显式选项,并为封装函数增加参数测试。
第三类是消费端解析错误。升级后事件恢复了,但 namespace、v1/v2 输出格式或多模式元组被旧代码错误解包,页面仍然看不到内容。此时原始流日志中会有事件,问题发生在适配层。可以把 流断后 LangGraph 如何避免漏事件 当作另一条排查线索:断线恢复关注游标和后续事件,子图回归关注事件是否从正确 namespace 到达,不能用一个问题的修法覆盖另一个问题。
如果故障只发生在工具调用,不要先把工具错误归因给模型。先检查 tools 事件是否进入父图流、工具参数是否完整、消费端是否在收到终止事件前提前执行,以及异步迭代器是否被异常关闭。若普通文本能流出而工具事件缺失,往往说明事件过滤或协议适配存在差异;若所有子图事件都缺失,才更像是 subgraphs 选项或版本回归。
给生产接入的修改顺序
第一步,记录版本与调用形状:langgraph 版本、Python 版本、同步或异步 API、stream_mode、subgraphs 值、是否有独立 configurable,以及实际运行的容器镜像。第二步,用最小图复现,不要把数据库、模型中转和前端同时带进来。第三步,升级到包含修复的版本后运行同步和异步回归。第四步,再把原始事件接回应用的聚合器,检查 namespace、消息块和工具事件的解析。
对于长任务 Agent,建议把“最终状态”和“流式观察”设置成两个可独立验收的契约。最终状态用于恢复和重放,流式观察用于用户界面、审计和进度;前者成功不能掩盖后者缺事件。若 UI 允许用户在工具执行中操作,还应记录工具调用的唯一标识,避免重连或重复消费时把同一次调用显示成两次。
结论可以压缩成一句话:LangGraph 1.2.22 修复的是“带独立运行配置的子图,在父图 subgraphs=True 流中丢失事件”的具体回归,不是一次全局的流式语义改写。升级后仍要显式打开子图流,固定输出格式,按 namespace 验证事件,并把同步、异步、文本和工具路径分开测试。