关注点分离(Separation of Concerns)示例(AI Agent 架构、流程编排、数据契约、提示词工程)
Agent 功能的架构约定图放 services/agent/graphs/必须定义显式 TypedDict 输入/输出 state schemaprompt 一律放 services/agent/prompts/不许内联在图代码里。为什么要这么设计文章目录这套 Agent 架构约定的设计意图1. Graph 放 services/agent/graphs/ 显式 TypedDict State Schema为什么不直接用 dict 或 TypedDict 内联在函数签名里2. Prompt 放 services/agent/prompts/禁止内联① 角色分离写 Prompt 的 ≠ 写 Graph 的② 版本管理 A/B 实验③ 可观测性 成本追踪④ 防止 Prompt 与逻辑耦合3. 整体目录结构的意图一句话总结这套 Agent 架构约定的设计意图你描述的这条规范本质上是在做关注点分离Separation of Concerns把 Agent 系统拆成了三个正交的维度流程编排、数据契约、提示词工程。下面逐条拆解为什么要这么做。1. Graph 放services/agent/graphs/ 显式 TypedDict State Schema为什么不直接用dict或TypedDict内联在函数签名里# ❌ 松散做法 — 上下游节点靠默契传字段defnode_a(state:dict)-dict:state[foo]barreturnstate# ✅ 规范做法 — 显式契约classAgentState(TypedDict):messages:list[BaseMessage]plan:strtool_results:list[dict]is_complete:bool好处说明编译期可检查Mypy / Pyright 能在 CI 里直接报出字段拼写错误、类型不匹配而不是等到运行时KeyError节点间契约清晰每个 node 的input → output一目了然新人不用顺着整个 graph 追数据流Graph 可视化/序列化LangGraph 等框架依赖显式 state 做 checkpoint、time-travel、状态恢复隐式 dict 做不到可测试性单测一个节点时TypedDict 就是 mock 数据的 schema不用猜该传什么2. Prompt 放services/agent/prompts/禁止内联这是整条规范里最实用的一条原因至少有四层① 角色分离写 Prompt 的 ≠ 写 Graph 的Prompt 工程师 / 产品人员 → 改 prompts/xxx.txt 后端工程师 → 改 graphs/xxx.pyPrompt 调优是高频迭代如果 prompt 写在 Python 代码里每次改个措辞都要触碰业务代码、跑完整 CI、有合并冲突风险。② 版本管理 A/B 实验prompts/ planner_v1.txt planner_v2.txt ← 灰度实验直接切文件 tool_router.txt summarizer.txt放在独立文件里可以用 Git 对 prompt 单独做 diff / blame / 回滚按版本命名做 A/B test未来迁移到 Prompt 管理平台LangSmith、Helicone 等零成本③ 可观测性 成本追踪集中管理后很容易加一层统一的 loader顺带做Token 计数 / 成本预估Prompt 注入检测变量注入审计哪些{variable}被填充了什么值如果 prompt 散落在各个.py文件里这些横切逻辑就没地方挂。④ 防止 Prompt 与逻辑耦合# ❌ 内联 — prompt 和流程控制混在一起改 prompt 可能误改逻辑defplanner_node(state):responsellm.invoke(f你是一个规划助手。 用户的请求是:{state[input]}如果涉及代码请调用 code_tool... # ← 这是 prompt 还是业务规则 )...# ✅ 分离 — 各管各的# prompts/planner.txt# graphs/planner.pydefplanner_node(state:PlannerInput)-PlannerOutput:promptload_prompt(planner,inputstate[input])responsellm.invoke(prompt)...3. 整体目录结构的意图services/agent/ ├── graphs/ ← 流程编排怎么走 │ ├── main_agent.py │ └── sub_graphs/ ├── prompts/ ← 提示词怎么说 │ ├── planner.txt │ └── tool_router.txt ├── tools/ ← 工具实现用什么做 └── schemas.py ← TypedDict 定义数据长什么样这四个目录对应了 Agent 系统的四个独立变化频率目录变化频率改动人graphs/低频架构定下来很少改后端schemas.py中频新增字段时改后端prompts/高频持续调优Prompt 工程师 / 产品tools/中频新增能力时改后端变化频率不同的东西不应该放在同一个文件里——这是软件设计里最朴素也最重要的原则之一。一句话总结这套约定的核心目的是让 prompt 调优、图编排、数据契约三件事可以独立演进、独立测试、独立 review互不拖累。在 Agent 系统这种 prompt 改动频率远高于代码的系统里这种分离不是洁癖而是生存需要。