跳转到内容

子图组合模式

子图组合模式

学习目标

读完本章后,你将能够:

  • 理解子图在多 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. 参考来源