主题
6.5 子图架构总结与工程化指南
经过前面四节的深入探讨,我们已经全面了解了 LangGraph 中子图的架构设计、状态映射、测试策略和动态选择机制。这一节作为第6章的收尾,我们会把所有内容整合成一个完整的工程化指南,帮助你在实际项目中系统地应用子图架构。
子图架构决策框架
当你在面对一个需要划分子图的系统时,可以按照下面的决策流程来系统地进行架构设计:
第一步:评估是否需要子图
│
├─ 节点总数 < 5 且逻辑简单 → 可能不需要子图
│ └─ 直接用单层图即可
│
└─ 节点总数 ≥ 5 或 有复用需求 → 需要子图
第二步:识别功能域(功能内聚)
│
├─ 列出所有业务步骤
├─ 按职责分组(每个组回答"它做什么?")
├─ 每个组就是一个候选子图
└─ 确保每组可以用一句话描述其目的
第三步:定义接口(接口稳定)
│
├─ 为每个子图定义 Input 类型(需要的字段)
├─ 为每个子图定义 Output 类型(产生的字段)
├─ 区分公共字段和私有字段
└─ 接口一旦确定,尽量避免频繁修改
第四步:选择通信模式
│
├─ 字段名一致?→ 自动匹配模式
├─ 字段名不同但转换简单?→ 包装函数手动映射
├─ 多个子图共享基类?→ 继承模式
└─ 运行时动态决定?→ 注册表/工厂模式
第五步:规划测试策略
│
├─ 每个子图:单元测试(独立、隔离、Mock 外部依赖)
├─ 关键路径:集成测试(验证数据流正确性)
├─ 全链路:端到端测试(完整流程验证)
└─ CI/CD 中加入覆盖率检查完整项目结构参考
一个使用子图架构的典型 LangGraph 项目目录结构如下:
project/
├── graphs/
│ ├── __init__.py
│ ├── parent/
│ │ ├── __init__.py # 父图定义
│ │ ├── orchestrator.py # 编排各子图的包装节点
│ │ └── state.py # 父图的状态类型定义
│ │
│ ├── classification/
│ │ ├── __init__.py
│ │ ├── graph.py # 分类子图定义
│ │ ├── state.py # 分类子图的状态类型
│ │ ├── nodes/
│ │ │ ├── categorize.py # 各节点的实现
│ │ │ └── extract_entities.py
│ │ └── tests/
│ │ └── test_classification.py
│ │
│ ├── retrieval/
│ │ ├── __init__.py
│ │ ├── graph.py
│ │ ├── state.py
│ │ ├── nodes/
│ │ └── tests/
│ │
│ ├── generation/
│ │ ├── __init__.py
│ │ ├── graph.py
│ │ ├── state.py
│ │ ├── nodes/
│ │ └── tests/
│ │
│ └── quality/
│ ├── __init__.py
│ ├── graph.py
│ ├── state.py
│ ├── nodes/
│ └── tests/
│
├── registry.py # 子图注册表
├── config.py # 配置管理
└── tests/
├── test_integration.py # 集成测试
└── test_e2e.py # 端到端测试这种结构的核心原则是每个子图是一个独立的包,包含自己的状态定义、图定义、节点实现和测试。父图在单独的 parent/ 包中,只负责编排。registry.py 提供全局的子图注册和查找机制。
常见反模式速查表
最后,我们把整个第6章讨论过的反模式整理成一个速查表,供你在设计和审查子图架构时快速对照:
| 反模式 | 问题 | 正确做法 |
|---|---|---|
| God Graph(20+节点的平铺图) | 认知负担重、难维护 | 按 3-7 个节点一组拆分为子图 |
| 子图之间直接访问内部状态 | 高耦合、改一处波及多处 | 通过明确定义的接口通信 |
| 所有子图共享同一个 State 类型 | 字段污染、语义不清 | 每个子图有独立的 Input/Output 类型 |
| 包装函数中硬编码字段映射 | 映射逻辑分散、难以追踪 | 使用统一的映射工具类或配置 |
| 子图没有独立的测试 | 集成问题发现晚、修复成本高 | 每个子图有完整的单元测试套件 |
| 动态选择时没有默认回退 | 未知类型导致崩溃 | 始终提供 default handler 或明确报错 |
| 注册表分散在各模块 | 重复注册或遗漏 | 集中式 Registry 单例管理 |
| 工厂函数创建的子图没有缓存 | 重复创建浪费资源 | 缓存已编译的子图实例 |
| 子图嵌套超过 3 层 | 调试困难、状态复杂 | 扁平化为最多 2 层嵌套 |
| 忘记在包装函数中处理异常 | 子图异常冒泡到父图 | 在包装函数中 try-except 并优雅降级 |
从这里到哪里:后续章节预告
子图架构是构建复杂 LangGraph 系统的基础设施。掌握了子图之后,你还需要了解:
- 持久化与时间旅行(第7章):如何让子图的执行状态可以被保存和恢复?特别是当子图中包含 Interrupt 时,checkpointing 的行为是怎样的?
- 多 Agent 协作(第8章):多个 Agent(每个 Agent 本身可能就是一个复杂的子图)如何协同工作?Supervisor 模式、Map-Reduce 并行等高级编排方式。
- 实战项目(第9-10章):把前面学到的所有知识综合运用到两个完整的项目中——智能客服工单系统和自主研究助手。
这些章节都会大量用到本章讨论的子图技术,所以确保你对本章的内容有了扎实的理解后再继续前进。