架构模式

子图 (Subgraphs)

使用子图实现逻辑封装和状态隔离,构建可复用的复杂系统

📚 学习目标

学完这篇文章后,你将能够:

  • 理解子图在系统解耦中的作用
  • 学会创建并嵌套 StateGraph
  • 掌握父图与子图之间的状态传递机制
  • 解决多代理系统中的状态污染问题

前置知识

在开始学习之前,建议先阅读:

你需要了解:

  • 软件工程中的模块化/封装概念

1️⃣ 什么是子图?

子图 本质上就是一个普通的 Graph,但它被当作另一个 Graph 的节点来使用。

为什么要用子图?

  1. 封装性:父图不需要知道子图的内部细节,只关心输入输出。
  2. 状态隔离:子图可以有自己的 Schema,避免与父图或其他子图的状态冲突。
  3. 复用性:定义一次子图,可以在多个地方引用。

2️⃣ 创建与使用子图

步骤 1:定义子图

这就和定义普通 Graph 一模一样。

const childState = Annotation.Root({
  localResult: Annotation<string>(),
});

const childGraph = new StateGraph(childState)
  .addNode("child_process", ...)
  .addEdge(START, "child_process")
  .compile(); // 编译得到 Runnable

步骤 2:在父图中使用

将编译好的子图直接作为 addNode 的第二个参数。

const parentState = Annotation.Root({
  globalInput: Annotation<string>(),
});

const parentGraph = new StateGraph(parentState)
  .addNode("subgraph_node", childGraph) // 👈 关键点
  .addEdge(START, "subgraph_node")
  .compile();

3️⃣ 状态传递与转换

父图和子图的状态 Schema 通常不同,LangGraph 如何处理数据流动?

情况 A:Schema 包含关系

如果子图需要的字段,父图都有,LangGraph 会自动透传。

情况 B:显式转换 (Wrapper Node)

如果 Schema 完全不同,建议包裹一层函数节点来做转换。

const bridgeNode = async (state) => {
  // 1. 转换父状态 -> 子输入
  const childInput = { localResult: state.globalInput };
  
  // 2. 调用子图
  const childOutput = await childGraph.invoke(childInput);
  
  // 3. 转换子输出 -> 父状态更新
  return { globalInput: childOutput.localResult + " processed" };
};

// 父图添加的是 bridgeNode,而不是直接添加 childGraph
parentGraph.addNode("bridge", bridgeNode);

4️⃣ 多代理状态隔离示例

在多代理系统中,每个代理都有自己的 messages 历史。如果公用一个状态,Agent A 的思考过程会污染 Agent B 的上下文。

解决方案: 将每个 Agent 封装为子图,它们各自维护 messages,只向父图返回最终结论。


💡 练习题

  1. 代码题:创建一个“研究员子图”(负责生成长文)和一个“编辑子图”(负责润色),然后在一个“主编父图”中串联它们。尝试直接添加子图和通过 bridge node 添加两种方式。
  2. 思考题:如果子图抛出异常,父图会发生什么?如何隔离故障?

📚 参考资源

官方文档


✅ 总结

本章要点

  • 子图是 LangGraph 实现模块化的核心机制。
  • 编译后的 Graph 就是一个 Runnable,可以像普通函数一样被调用。
  • 通过子图可以有效解决 Context 污染问题。

下一步:如何让多个节点或子图同时跑起来?学习并行处理

登录以继续阅读

解锁完整文档、代码示例及更多高级功能。

立即登录

On this page