子图组合模式
子图组合模式
学习目标
读完本章后,你将能够:
- 理解子图在多 Agent 编排中的模块化价值
- 设计父子图之间的状态传递协议
- 掌握子图的状态命名空间隔离机制
- 评估子图组合与扁平图设计的取舍
1. 概念定义
子图(Subgraph) 指将一个编译后的图作为节点嵌套到另一个父图中。子图有独立的状态空间和执行逻辑,父图只看到子图的输入和输出。
2. 子图的嵌套方式
2.1 编译为节点
子图通过 add_node("name", subgraph.compile()) 作为节点注册到父图:
- 父图将子图视为一个普通节点
- 父图不关心子图内部的节点数量和结构
- 子图的执行是原子性的:父图等待子图完全结束才继续
2.2 状态映射
子图与父图之间的状态传递遵循映射规则:
- 输入:父图将当前状态传递给子图的输入 schema
- 输出:子图返回的部分状态被合并回父图的状态
- 隔离:子图内部的状态字段不泄漏到父图
3. 状态命名空间隔离
子图使用命名空间隔离其状态,避免与父图或其他子图冲突:
- 子图的 checkpoint 使用父图
thread_id+ 子图路径作为命名空间前缀 - 同一父图的不同子图实例共享 thread_id 但命名空间不同
- 子图嵌套子图时,命名空间层级递增
4. Send 到子图实例
通过 Send 原语,可以向多个子图实例分发任务:
return [ Send("子图节点", {"query": "问题1"}), Send("子图节点", {"query": "问题2"}),]每个 Send 创建子图的一个独立实例,各实例有隔离的状态和执行轨迹。
5. Command 跨子图传播
Command 对象可以在子图之间传播:
- 子图内部节点返回
Command,可以指定流向(继续父图、跳到特定节点) Command(goto=...)在子图内跳转Command(send_to=...)向父图或其他子图发送
6. 适用场景
| 场景 | 说明 |
|---|---|
| 模块化 | 将复杂工作流拆分为独立可测试的子模块 |
| 多 Agent 分工 | 每个子图代表一个 Agent 的完整工作流 |
| 复用 | 同一子图可在多个父图中复用 |
| 动态实例化 | 通过 Send 按需创建多个子图实例 |
7. 陷阱与对策
陷阱 1:命名空间冲突
问题:如果子图与父图使用相同的 Channel 名称,可能导致状态混淆。
对策:子图和父图的 Channel 名称应有明确区分,或在子图输入/输出映射时做显式转换。
陷阱 2:子图 checkpoint 与父图共享 thread_id
问题:子图的 checkpoint 共享父图的 thread_id,如果时间旅行恢复父图的某个 checkpoint,子图的状态也需要回滚到对应版本。
对策:确保子图和父图的 checkpoint 版本一致,或在 fork 时显式指定子图的 checkpoint_id。
陷阱 3:子图错误传播不透明
问题:子图内部的错误可能不会直接暴露给父图,导致父图无法正确决策。
对策:在子图出口设置错误处理节点,将错误信息编码为状态字段返回父图。
8. 设计取舍
优势
- 关注点分离:每个子图独立开发、测试和部署
- 状态隔离:子图内部状态不影响父图和其他子图
- 可组合性:子图可以嵌套任意层级
代价
- 调试复杂度:多层嵌套时追踪执行路径困难
- 状态映射开销:父子图之间的状态转换需要额外设计
- 性能开销:每层子图有独立的 checkpoint 和上下文切换
替代方案
- 扁平图:所有节点在同一个图中定义,适合简单工作流
- 条件分支:通过条件边实现多路径,而非子图嵌套
9. 参考来源
- 文档: LangGraph Subgraphs, https://docs.langchain.com/oss/python/langgraph/subgraphs
- 论文: Pregel: A System for Large-Scale Graph Processing, Google, 2010