从 Prompt 到平台:我使用的架构规则
Source: Dev.to
请提供您希望翻译的完整文本内容(除代码块和 URL 之外),我将把它翻译成简体中文并保持原有的格式和 Markdown 语法。谢谢!
“构建 → 惊喜 → 重构 → 重复” 循环
这个循环在开始时非常惊人。但过了一段时间后,它感觉像两个小丑在互相抢戏:越来越搞笑,笑声不断……直到其中一个掏出火焰喷射器来进行最后一次恶作剧,笑声变得有点尴尬。
这种迭代方式很有趣,直到它不再有趣。所以我去寻找指导。
LangGraph 教程的经验
大多数示例展示了如何:
- 构建图。
- 定义一些节点。
- 将它们连接起来。
- 发布它。
非常适合原型开发。
它们没有说明在以下情况下该把东西放在哪里:
- 8 个节点
- 3 个代理
- 5 个工具
- 跨子图的共享状态
- 用于安全防护的中间件
- 保持框架独立的平台层
我搜索过,找到了零散的内容,但没有完整的全景图。所以我自己实现了它。
Source: …
可扩展的文件夹结构
下面是我的 LangGraph 组件的目录结构:
app/
├── agents/ # Agent factories (build_agent_*)
├── graphs/ # Graph definitions (main, subgraphs, phases)
├── nodes/ # Node factories (make_node_*)
├── states/ # Pydantic state models
├── tools/ # Tool definitions
├── middlewares/ # Cross‑cutting concerns (guardrails, redaction)
└── platform/
├── core/ # Pure types, contracts, policies (no wiring)
│ ├── contract/ # Validators: state, tools, prompts, phases
│ ├── dto/ # Pure data‑transfer objects
│ └── policy/ # Pure decision logic
├── adapters/ # Boundary translation (DTOs ↔ State)
├── runtime/ # Evidence hydration, state helpers
├── config/ # Environment, paths
└── observability # Logging
为什么采用这种结构?
它呼应了 LangGraph 的思维模型:agents 就是 agents;nodes 就是 nodes;graphs 就是 graphs。在编排层,内容易于查找,职责保持分离。
真正的关键在于 platform/ 层。
平台层:它为何存在
将 LangGraph 组件拆分很容易;而把 wiring(线路)拆分却很困难。结构并不是在第一天就出现的——它是在多次迭代后逐渐浮现的。每一次循环都会暴露出缺失的架构规则,而这些规则的缺失导致随着新组件的加入,重构变得越来越痛苦。
没有平台层——一切都变得乱七八糟
# WITHOUT PLATFORM LAYER – everything mixed together
def problem_framing_node(state: SageState) -> Command:
# Guardrail logic mixed with state management
if "unsafe" in state.messages[-1].content:
state.gating.guardrail = GuardrailResult(is_safe=False, ...)
# Evidence hydration mixed with node orchestration
store = get_store()
for item in phase_entry.evidence:
doc = store.get(item.namespace, item.key)
# ... inline hydration logic
# Validation mixed with execution
if "problem_framing" not in state.phases:
raise ValueError("Invalid state update!")
# ... good luck writing tests for it!
有平台层——清晰的分离
# WITH PLATFORM LAYER – clean separation
def problem_framing_node(state: SageState) -> Command:
# Use platform contracts for validation
validate_state_update(update, owner="problem_framing")
# Use platform runtime helpers for evidence
bundle = collect_phase_evidence(state, phase="problem_framing")
# Use platform policies for decisions
guardrail = evaluate_guardrails(user_input)
# Use adapters for state translation
context = guardrail_to_gating(guardrail, user_input)
# Node only orchestrates – all logic lives in platform!
节点因此变成了它应有的模样:仅负责编排。没有领域逻辑,没有直接访问存储,没有内联验证。
六角形拆分
解决该问题的模式是 六角形(端口‑与‑适配器)架构。核心保持纯净——没有框架依赖,也不从外层导入任何内容。其他所有层都可以依赖核心,但核心不依赖任何东西。这使得边界可测试,规则可强制执行。
┌─────────────────────────────────────────────────────────┐
│ APPLICATION LAYER │
│ (app/nodes, app/graphs, app/agents, app/middlewares) │
│ - LangGraph orchestration │
│ - Calls platform services via contracts │
└───────────────────────────────┬─────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ PLATFORM LAYER │
│ ┌───────────┐ ┌───────────┐ ┌─────────┐ ┌───────────┐ │
│ │ Adapters │ │ Runtime │ │ Config │ │Observabil.│ │
│ │ DTO↔State│ │ helpers │ │ env/paths│ │ logging │ │
│ └─────┬─────┘ └─────┬─────┘ └────┬────┘ └─────┬─────┘ │
│ │ │ │ │ │
│ └─────────────┴──────┬─────┴────────────┘ │
│ ▼ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ Core (PURE – no framework dependencies) │ │
│ │ - Contracts and validators │ │
│ │ - Policy evaluation (pure functions) │ │
│ │ - DTOs (frozen dataclasses) │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
规则: core/ 不能 从上层导入任何内容——不允许应用编排(agents、nodes、graphs 等)、不允许 wiring,也不允许 adapters。依赖只能 向内 指向。
这不仅仅是一个指导原则;它是被强制执行的。
如何强制执行指南?
简洁: 为其编写一个能够捕获违规的测试:
# tests/unit/architecture/test_core_purity.py
FORBIDDEN_IMPORTS = [
"app.state",
"app.graphs",
"app.nodes",
"app.agents",
# ... all app orchestration and platform wiring
]
def test_core_has_no_forbidden_imports():
"""Core layer must remain pure – no wiring dependencies."""
core_files = Path("app/platform/core").rglob("*.py")
for file in core_files:
content = file.read_text()
for forbidden in FORBIDDEN_IMPORTS:
assert forbidden not in content, (
f"{file} imports {forbidden} – core must stay pure"
)
如果你突破了边界,测试会失败。没有例外。
除了指南,你还可以定义会在运行时进行校验的契约。
可在运行时校验的契约
core/contract/ 目录下包含在运行时强制执行契约规则的验证器:
| Contract | 功能描述 |
|---|---|
validate_state_update() | 限制仅授权所有者可以进行状态变更 |
validate_structured_response() | 在持久化之前强制进行结构化响应的校验 |
validate_phase_registry() | 确保阶段键与声明的 schema 相匹配 |
validate_allowlist_contains_schema() | 确保工具白名单的正确性 |
这些不是可选的——每个节点都会调用它们:
# 每一次状态更新都要经过契约校验
update = {"phases": {phase_key: phase_entry}}
validate_state_update(update, owner="problem_framing")
return Command(update=update, goto=next_node)
契约本身也有相应的测试(验证逻辑、阶段依赖、失效级联)。完整的测试套件请参见
test_state.py。
可扩展的测试结构
测试按类型(unit、integration、e2e)和类别(architecture、orchestration、platform)组织。这使得覆盖盲点一目了然,并且可以运行针对性的子集。
tests/
├── unit/
│ ├── architecture/ # 边界强制
│ │ ├── test_core_purity.py
│ │ ├── test_adapter_boundary.py
│ │ └── test_import_time_construction.py
│ ├── orchestration/ # Agents、nodes、graphs
│ └── platform/ # Core + adapters
├── integration/
│ ├── orchestration/
│ └── platform/
└── e2e/
Pytest 标记
# pyproject.toml
# 用于按目的和范围对测试进行分类的标记
markers = [
# Test Type Markers (by scope)
"unit: Fast, isolated tests with no external dependencies",
"integration: Tests crossing component boundaries (may use test fixtures)",
"e2e: End‑to‑end workflow tests (full pipeline validation)",
# Test Category Markers (organizational categories)
"architecture: Hexagonal architecture enforcement (import rules, layer boundaries)",
"orchestration: LangGraph orchestration components (agents, nodes, graphs, middlewares, tools)",
"platform: Platform layer tests (hexagonal architecture – core, adapters, runtime)",
]
单独运行 unit‑architecture 测试:
uv run pytest -m "unit and architecture"
该架构已通过 110 条测试验证,其中 11 条专门用于强制执行架构边界。
这能实现什么
这里变得有趣了。
你可能会想:故事很酷,但……

当你的架构可预测且可强制执行时,会出现一些有趣的现象:编码代理不再是负担,而是变得有用。
- 当每个节点遵循相同的模式时…
- 当每个状态更新都经过验证器时…
- 当每个边界都定义明确且经过测试时…
…AI 代理无法在不被测试捕获的情况下意外破坏你的架构。它不能导入被禁止的模块、跳过验证或绕过合约——否则就会导致测试套件失败。
这些规则不再只是文档;它们是对人类和 AI 的双重护栏。
想要完整内容?
- 46 条分层架构原则: Architecture principles
- 平台合同 README: Platform contracts
- 架构测试: Architecture tests
接下来
当你让 Claude Code 面对它无法破解的架构时会发生什么。
CLAUDE.md 文件不仅仅是一堆指令的集合——它是一个在开发过程中保持上下文并强制边界的合约。我为它构建了一个框架,并取得了可衡量的成果。
即将推出:CLAUDE.md 成熟度模型。
这属于我的 “从提示到平台” 系列,记录 SageCompass 的构建过程。从序章开始.