关注点分离(Separation of Concerns)示例(AI Agent 架构、流程编排、数据契约、提示词工程)
·
Agent 功能的架构约定图放 services/agent/graphs/,必须定义显式 TypedDict 输入/输出 state schema;prompt 一律放 services/agent/prompts/,不许内联在图代码里。
为什么要这么设计?
文章目录
这套 Agent 架构约定的设计意图
你描述的这条规范本质上是在做关注点分离(Separation of Concerns),把 Agent 系统拆成了三个正交的维度:流程编排、数据契约、提示词工程。下面逐条拆解为什么要这么做。
1. Graph 放 services/agent/graphs/ + 显式 TypedDict State Schema
为什么不直接用 dict 或 TypedDict 内联在函数签名里?
# ❌ 松散做法 — 上下游节点靠"默契"传字段
def node_a(state: dict) -> dict:
state["foo"] = "bar"
return state
# ✅ 规范做法 — 显式契约
class AgentState(TypedDict):
messages: list[BaseMessage]
plan: str
tool_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.py
Prompt 调优是高频迭代,如果 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 可能误改逻辑
def planner_node(state):
response = llm.invoke(f"""你是一个规划助手。
用户的请求是: {state['input']}
如果涉及代码,请调用 code_tool... # ← 这是 prompt 还是业务规则?
""")
...
# ✅ 分离 — 各管各的
# prompts/planner.txt
# graphs/planner.py
def planner_node(state: PlannerInput) -> PlannerOutput:
prompt = load_prompt("planner", input=state["input"])
response = llm.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 改动频率远高于代码的系统里,这种分离不是洁癖,而是生存需要。
更多推荐


所有评论(0)