Hotdry.
ai-systems

用六边形架构模式构建可维护的 LangGraph 多 Agent 系统

拆解 LangGraph 六边形架构模式在多 Agent 编排中的解耦设计,110 个测试用例验证的可复用组件划分与依赖注入实践。

在 LangGraph 项目从原型走向生产的道路上,开发者常常会遇到一个令人困惑的现象:教程和示例代码展示了如何构建一个简单的图结构,定义几个节点,将它们连接起来,然后交付使用。然而,当项目规模扩大到八个节点、三个 Agent、五个工具,并且需要在子图之间共享状态时,这套方法论似乎瞬间失效。代码开始变得难以维护,每一次需求变更都可能引发连锁反应,团队成员对代码库的理解也越来越割裂。

这个问题并非 LangGraph 所独有,它是几乎所有复杂软件系统都会面临的架构挑战。Cleverhoods 团队在构建 SageCompass(一个增强型 AI 决策框架)的过程中,总结出一套基于六边形架构的设计模式,通过 110 个测试用例(其中 11 个专门用于架构边界 enforcement)验证了其可行性。这套方法论的核心思想是将应用编排层与平台基础设施层彻底分离,让业务逻辑保持纯粹,同时让运行时 concerns 变得可替换、可测试。

六边形架构的分层设计原则

六边形架构(又称端口与适配器架构)的核心理念是将应用程序划分为内层(核心领域)与外层(基础设施和用户界面),两者通过明确的接口进行通信。在 LangGraph 项目中,这一原则被具体化为两个主要层次的划分:应用层(app/)与平台层(platform/)。

应用层包含了所有与 LangGraph 框架直接交互的组件。agents 目录存放 Agent 工厂函数,负责构建不同类型的 Agent 实例。graphs 目录定义主图、子图和执行阶段,这是整个编排逻辑的入口。nodes 目录包含节点工厂函数,每个节点负责特定的处理步骤。states 目录使用 Pydantic 模型定义状态结构,确保类型安全。tools 目录封装了 Agent 可以调用的外部工具。middlewares 目录则处理横切关注点,如内容审核、数据脱敏等。

平台层是整个架构中最关键的部分,它独立于 LangGraph 框架而存在。core 子目录包含纯类型定义、合约验证器和业务策略 —— 这里没有任何框架依赖,也没有任何对上层组件的引用。adapters 子目录负责在 DTO(数据传输对象)与状态模型之间进行转换。runtime 子目录提供证据收集、状态辅助操作等运行时工具。config 子目录管理环境变量和路径配置。observability 子目录处理日志记录和监控。

这种分层带来的直接好处是:当你需要将 Agent 从 OpenAI 切换到 Claude,或者将图执行引擎从 LangGraph Studio 迁移到其他平台时,只需要修改平台层的适配器代码,而应用层的编排逻辑可以保持完全不变。更重要的是,core 目录的纯净性使得业务逻辑可以在脱离 LangGraph 环境的情况下进行单元测试。

核心纯净性 enforcement 机制

六边形架构的成功关键在于确保核心层真正保持纯净。仅仅在文档中约定 "不要从 core 导入上层模块" 是远远不够的 —— 在项目压力下,约定很快就会被打破。Cleverhoods 团队采用了一种简单而有效的方法:用测试来强制执行架构边界。

核心纯净性测试的原理非常直接。它遍历 core 目录下的所有 Python 文件,检查文件内容中是否包含被禁止的导入语句。被禁止的导入包括所有应用编排层的模块(如 app.states、app.graphs、app.nodes、app.agents 等)以及平台层的接线模块。一旦检测到任何违规导入,测试就会立即失败,并明确指出是哪个文件违反了哪条规则。

这种 enforcement 机制的力量在于它的绝对性。测试不会给你留下任何灰色地带 —— 要么完全遵守架构边界,要么测试失败。在持续集成环境中,任何试图走捷径的提交都会被拦截。更重要的是,它为新加入团队的成员提供了一个明确的指导:当你编写 core 层代码时,如果你需要某个功能但无法从 core 内部导入,那说明你的设计可能存在问题,需要重新思考职责划分。

除了 import 检查,测试套件还包含适配器边界测试和导入时构造测试。适配器边界测试确保从外向内的数据转换遵循预定义的合约。导入时构造测试防止在模块加载阶段产生副作用,确保 core 模块可以安全地被并行加载和测试。

运行时合约验证体系

将核心逻辑与框架解耦只是第一步,更重要的是确保状态变更的安全性。在一个复杂的多 Agent 系统中,状态是所有 Agent 共享的资产,如果某个 Agent 意外修改了不属于自己的状态字段,或者写入了不合法的数据格式,整个系统可能会陷入不一致的状态。

运行时合约验证体系通过在每个状态更新点插入验证调用来解决这个问题。核心合约目录包含四个主要的验证函数:validate_state_update 负责检查状态更新是否来自授权的拥有者;validate_structured_response 确保结构化输出在持久化之前经过验证;validate_phase_registry 检查阶段键是否与声明的 Schema 匹配;validate_allowlist_contains_schema 验证工具白名单的完整性。

以状态更新验证为例,当一个节点准备返回新的状态时,它必须先调用 validate_state_update 函数,传入更新内容和自己的身份标识(即 "拥有者" 标识)。验证函数会检查这个更新是否只修改了授权范围内的字段,修改的值是否符合预期的类型和约束。如果验证通过,更新会被允许执行;如果验证失败,系统会抛出明确的错误,指出是哪个节点试图执行非法更新。

这种设计将状态完整性从依赖开发者的自觉转变为依赖系统的强制执行。即使是新加入的开发者不熟悉代码库,也不会因为误操作而破坏状态结构。合约验证器的代码本身也经过充分测试,覆盖了正常路径、边界条件和异常场景,确保验证逻辑本身是可靠的。

面向 AI 编码助手的架构保护

在 AI 辅助编程日益普及的今天,六边形架构还有一层独特的价值:它让 AI 编码助手能够安全地参与代码贡献。当架构边界清晰、规则明确时,AI 助手生成的代码如果试图违反这些规则,测试套件会立即捕获问题。换句话说,架构规则成为了人类和 AI 共同遵循的契约。

这解决了许多团队在使用 AI 编码助手时的顾虑:AI 生成的代码可能引入隐藏的技术债务,或者破坏现有的架构一致性。在传统项目中,AI 生成的代码需要经过有经验的开发者仔细审查才能合入。而在六边形架构的保护下,AI 可以更自由地探索实现方案,因为有测试作为最后的安全网。

具体到 LangGraph 项目,当 AI 助手被要求实现一个新节点时,它只需要遵循简单的模式:调用平台服务进行验证,使用平台运行时工具收集证据,应用平台策略做出决策,最后通过适配器返回结果。AI 不需要了解状态存储的细节,不需要知道验证规则的具体实现,也不需要担心自己的实现会意外影响其他组件。这种约束反而让 AI 的输出更加可靠和可预测。

测试组织的分层策略

110 个测试用例被组织为三层结构:单元测试、集成测试和端到端测试。每一层都有明确的职责划分和运行条件。

单元测试进一步分为三个子类别。架构测试负责边界 enforcement,包括核心纯净性测试、适配器边界测试和导入时构造测试。编排测试针对 agents、nodes、graphs 等应用层组件进行测试。平台测试覆盖 core 和 adapters 中的纯业务逻辑。单元测试的特点是快速、隔离、无外部依赖,适合在每次代码提交时运行。

集成测试跨越组件边界,可能会使用测试 fixture 来模拟外部依赖。例如,测试适配器与 core 合约的交互时,可能需要模拟状态存储的行为。集成测试的运行时间比单元测试长,但能够捕获组件之间的接口问题。

端到端测试验证完整的管道流程,包括从用户输入到最终输出的全链路。这类测试耗时最长,通常只在发布前或关键里程碑时运行。测试标记系统允许开发者运行特定类别的测试,例如只运行架构相关的单元测试来快速验证架构合规性。

这种分层测试策略使得在项目规模扩大时仍然能够保持测试套件的可维护性。当发现一个新的边界问题时,只需添加到架构测试类别;当发现组件间接口问题时,添加到集成测试类别;当发现端到端流程问题时,添加到 E2E 测试类别。测试的组织方式与代码的组织方式保持一致,降低了定位问题的认知负担。

可落地的项目结构模板

将上述原则具体化,以下是一个可以直接用于 LangGraph 项目的目录结构模板:

整个项目根目录下的 langgraph 文件夹包含主要的 Python 代码。应用层组件位于 app 子目录:agents 目录存放 build_agent_ 开头的工厂函数,每个函数返回一个配置好的 Agent 实例;graphs 目录包含 main.py(入口图定义)、subgraphs/ 子目录(按功能组织的子图)和 phases/ 子目录(执行阶段定义);nodes 目录包含 make_node_ 开头的工厂函数,每个函数实现一个节点的业务逻辑;states 目录使用 Pydantic 定义状态模型;tools 目录包含工具函数和工具类;middlewares 目录包含 guardrails/(内容审核)和 redaction/(数据脱敏)等横切组件。

平台层位于 app/platform 子目录,其核心子目录不依赖任何上层模块。core/contract 目录包含状态验证、Schema 验证等合约;core/dto 目录定义纯数据传输对象,使用 frozen dataclass 确保不可变性;core/policy 目录包含纯业务决策函数。adapters 目录实现 DTO 到状态的转换逻辑。runtime 目录提供证据收集、状态辅助操作等工具函数。config 目录读取环境变量和配置文件。observability 目录封装日志记录和指标上报。

测试目录按照类型和类别组织:tests/unit/architecture/ 包含边界 enforcement 测试;tests/unit/orchestration/ 包含应用层组件测试;tests/unit/platform/ 包含平台层测试;integration/ 和 e2e/ 目录分别存放集成测试和端到端测试。

这种结构的每个目录都有明确的职责定义,新成员可以快速定位到需要修改的代码位置。同时,它为自动化工具(无论是 AI 助手还是静态分析工具)提供了清晰的约束,降低了意外引入架构退化的风险。

规模化多 Agent 系统的必由之路

当一个 LangGraph 项目从原型阶段进入生产阶段,它面临的挑战不再是 "如何让图跑起来",而是 "如何让图持续演进而不崩溃"。八节点、三 Agent、跨子图共享状态只是规模化的起点 —— 真正的挑战在于团队协作、长期维护和技术债务控制。

六边形架构模式提供了一种系统化的解决方案。它不是银弹,无法自动解决所有问题,但它提供了一套可操作的规则和可验证的约束。核心纯净性确保业务逻辑可测试;运行时合约确保状态变更安全;分层测试确保问题早发现;清晰的目录结构降低协作摩擦。

Cleverhoods 团队的经验表明,当架构变得可预测和可强制执行时,AI 编码助手可以成为有价值的贡献者而非风险源。这对于正在探索 AI 辅助软件开发的团队来说,是一个值得参考的方向。毕竟,在一个有明确规则的世界里,人类和 AI 都可以更高效地工作。


参考资料

查看归档