从 Prompt 到平台:我使用的架构规则

发布: (2026年1月20日 GMT+8 15:36)
10 min read
原文: Dev.to

Source: Dev.to

请提供您希望翻译的完整文本内容(除代码块和 URL 之外),我将把它翻译成简体中文并保持原有的格式和 Markdown 语法。谢谢!

“构建 → 惊喜 → 重构 → 重复” 循环

这个循环在开始时非常惊人。但过了一段时间后,它感觉像两个小丑在互相抢戏:越来越搞笑,笑声不断……直到其中一个掏出火焰喷射器来进行最后一次恶作剧,笑声变得有点尴尬。

这种迭代方式很有趣,直到它不再有趣。所以我去寻找指导。

LangGraph 教程的经验

大多数示例展示了如何:

  1. 构建图。
  2. 定义一些节点。
  3. 将它们连接起来。
  4. 发布它。

非常适合原型开发。

它们没有说明在以下情况下该把东西放在哪里:

  • 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 条专门用于强制执行架构边界。

这能实现什么

这里变得有趣了。

你可能会想:故事很酷,但……

...but why?

当你的架构可预测且可强制执行时,会出现一些有趣的现象:编码代理不再是负担,而是变得有用。

  • 当每个节点遵循相同的模式时…
  • 当每个状态更新都经过验证器时…
  • 当每个边界都定义明确且经过测试时…

…AI 代理无法在不被测试捕获的情况下意外破坏你的架构。它不能导入被禁止的模块、跳过验证或绕过合约——否则就会导致测试套件失败。

这些规则不再只是文档;它们是对人类和 AI 的双重护栏。

想要完整内容?

接下来

当你让 Claude Code 面对它无法破解的架构时会发生什么。

CLAUDE.md 文件不仅仅是一堆指令的集合——它是一个在开发过程中保持上下文并强制边界的合约。我为它构建了一个框架,并取得了可衡量的成果。

即将推出:CLAUDE.md 成熟度模型。

这属于我的 “从提示到平台” 系列,记录 SageCompass 的构建过程。从序章开始.

Back to Blog

相关文章

阅读更多 »

Rapg:基于 TUI 的密钥管理器

我们都有这种经历。你加入一个新项目,首先听到的就是:“在 Slack 的置顶消息里查找 .env 文件”。或者你有多个 .env …

技术是赋能者,而非救世主

为什么思考的清晰度比你使用的工具更重要。Technology 常被视为一种魔法开关——只要打开,它就能让一切改善。新的 software,...

踏入 agentic coding

使用 Copilot Agent 的经验 我主要使用 GitHub Copilot 进行 inline edits 和 PR reviews,让我的大脑完成大部分思考。最近我决定 t...