架构模式
子图 (Subgraphs)
使用子图实现逻辑封装和状态隔离,构建可复用的复杂系统
📚 学习目标
学完这篇文章后,你将能够:
- 理解子图在系统解耦中的作用
- 学会创建并嵌套
StateGraph - 掌握父图与子图之间的状态传递机制
- 解决多代理系统中的状态污染问题
前置知识
在开始学习之前,建议先阅读:
你需要了解:
- 软件工程中的模块化/封装概念
1️⃣ 什么是子图?
子图 本质上就是一个普通的 Graph,但它被当作另一个 Graph 的节点来使用。
为什么要用子图?
- 封装性:父图不需要知道子图的内部细节,只关心输入输出。
- 状态隔离:子图可以有自己的 Schema,避免与父图或其他子图的状态冲突。
- 复用性:定义一次子图,可以在多个地方引用。
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,只向父图返回最终结论。
💡 练习题
- 代码题:创建一个“研究员子图”(负责生成长文)和一个“编辑子图”(负责润色),然后在一个“主编父图”中串联它们。尝试直接添加子图和通过 bridge node 添加两种方式。
- 思考题:如果子图抛出异常,父图会发生什么?如何隔离故障?
📚 参考资源
官方文档
✅ 总结
本章要点:
- 子图是 LangGraph 实现模块化的核心机制。
- 编译后的 Graph 就是一个 Runnable,可以像普通函数一样被调用。
- 通过子图可以有效解决 Context 污染问题。
下一步:如何让多个节点或子图同时跑起来?学习并行处理。
登录以继续阅读
解锁完整文档、代码示例及更多高级功能。