文章目录
-
- [📌 技术名片](#📌 技术名片)
-
- [💡 一句话理解](#💡 一句话理解)
- [一、为什么 AI 特别需要"分层"?](#一、为什么 AI 特别需要“分层”?)
- 二、架构规范:给每一层明确职责
-
- [1. API 层:负责"接待"](#1. API 层:负责“接待”)
- [2. Service 层:负责"业务"](#2. Service 层:负责“业务”)
- [3. Repository / 数据访问层:负责"怎么拿数据"](#3. Repository / 数据访问层:负责“怎么拿数据”)
- [4. Schema / DTO:负责数据长什么样](#4. Schema / DTO:负责数据长什么样)
- [5. Runtime:负责复杂运行能力](#5. Runtime:负责复杂运行能力)
- [6. Infrastructure:负责技术基础设施](#6. Infrastructure:负责技术基础设施)
- 三、真正重要的是:调用要有方向
-
- [规则 1:API 不直接操作数据库](#规则 1:API 不直接操作数据库)
- [规则 2:API 不承载核心业务逻辑](#规则 2:API 不承载核心业务逻辑)
- [规则 3:Repository 不负责业务决策](#规则 3:Repository 不负责业务决策)
- [规则 4:上层通过稳定接口调用下层](#规则 4:上层通过稳定接口调用下层)
- [规则 5:禁止为了方便跨层调用](#规则 5:禁止为了方便跨层调用)
- 四、这样做有什么好处?
-
- [1. AI 更容易判断文件应该放在哪里](#1. AI 更容易判断文件应该放在哪里)
- [2. 修改范围更小](#2. 修改范围更小)
- [3. 更容易测试](#3. 更容易测试)
- [4. 更换技术实现时影响更小](#4. 更换技术实现时影响更小)
- [5. AI 更容易理解已有项目](#5. AI 更容易理解已有项目)
- [6. 多个 AI 更容易保持一致](#6. 多个 AI 更容易保持一致)
- [五、AI 最容易出现的"失控现场"](#五、AI 最容易出现的“失控现场”)
- [六、提示词落地:把层级规则写给 AI](#六、提示词落地:把层级规则写给 AI)
- [七、正面产出:miniagent 的真实分层是怎样的?](#七、正面产出:miniagent 的真实分层是怎样的?)
-
- [1. API 层明确声明:这里只处理 HTTP](#1. API 层明确声明:这里只处理 HTTP)
- [2. 业务逻辑进入 AgentService](#2. 业务逻辑进入 AgentService)
- [3. 删除 Agent 的业务动作也发生在 Service](#3. 删除 Agent 的业务动作也发生在 Service)
- [4. Service 再调用数据访问能力](#4. Service 再调用数据访问能力)
- [5. 复杂能力单独进入 Runtime](#5. 复杂能力单独进入 Runtime)
- [八、分层和上一篇 DDD 到底有什么区别?](#八、分层和上一篇 DDD 到底有什么区别?)
- 结语
- 开源代码
AI 写代码有一个很常见的问题:
它知道功能应该怎么实现,却不一定知道代码应该放在哪里。
比如你告诉 AI:
"增加一个删除 Agent 的功能。"
如果没有清晰的工程结构,它可能直接在 API 接口里:查数据库、判断业务规则、清理缓存 ...
功能可能很快就跑起来了。但随着 AI 一次次这样写,项目最终会变成:
text
API 里有业务逻辑
Service 里也有数据库操作
Repository 里又夹着业务判断
工具类到处被引用
这时候,我们就需要另一种非常基础、却极其重要的架构思想:
分层。
📌 技术名片
分层架构 / Layered Architecture
将系统按照不同职责划分成若干层,例如接口层、业务层、数据访问层、基础设施层,并规定各层应该负责什么、可以调用谁。
MVC、整洁架构(Clean Architecture)虽然具体形式不同,但都包含一个共同思想:
不同类型的代码,不要混在一起。
💡 一句话理解
可以把一个软件系统想象成一家餐厅。
顾客不会直接跑进仓库拿食材,服务员也不会跑进厨房自己炒菜。
通常是:
text
顾客
↓
服务员
↓
厨师
↓
仓库
每一层都有自己的工作。
软件也是如此:
text
用户请求
↓
API / Controller
↓
Service
↓
Repository
↓
Database
最重要的不是"多建几个文件夹"。
而是:
谁负责接请求,谁负责业务,谁负责数据库,要提前规定清楚。
对于 AI 来说,这相当于在项目里画出了明确的楼层:
你可以在自己的楼层工作,但不要随便穿墙。
一、为什么 AI 特别需要"分层"?
人类开发者看到一段代码时,往往会凭经验判断:
"这段 SQL 不应该写在 Controller 里。"
AI 如果没有明确规则,却可能认为:
"写在这里最快,而且能够完成任务。"
它更关注当前问题是否解决,而不是整个项目半年以后会不会变乱。
所以如果一个项目没有明确分层,AI 很容易逐渐写成:
text
API
├── 参数校验
├── 权限
├── 业务逻辑
├── SQL
├── 缓存
└── 第三方 API
最后一个接口文件几百甚至几千行。分层架构真正解决的,就是:
先规定代码应该住在哪里。
二、架构规范:给每一层明确职责
一个容易理解的 Web 后端,可以先简化成下面几层:
text
API / Controller
↓
Service
↓
Repository
↓
Database
复杂一些的系统还可以加入:
text
Schema
Runtime
Infrastructure
但原则没有变化。
1. API 层:负责"接待"
API 层主要负责:
text
接收请求
参数转换
身份 / 权限入口
调用 Service
返回结果
它不应该负责核心业务。
比如:
python
@router.delete("/{agent_id}")
async def delete_agent(
agent_id: int,
svc: AgentService = Depends(get_service),
):
await svc.delete_agent(agent_id)
return ApiResponse()
从阅读角度看非常简单:
text
收到请求
↓
调用 AgentService
↓
返回结果
这就是一个健康的 API 层。
2. Service 层:负责"业务"
Service 回答的问题是:
这件事应该怎么做?
例如删除 Agent 可能不仅仅是:
text
DELETE FROM agents
还可能包括:
text
检查 Agent 是否存在
删除数据
清理关系
失效缓存
记录业务结果
这些都属于业务过程。
因此应该集中在 Service,而不是散落在 API。
3. Repository / 数据访问层:负责"怎么拿数据"
Repository 负责:
text
查询
新增
更新
删除
事务
数据库访问
它更关心:
数据怎样存、怎样查。
而不是:
这个业务为什么要这样做。
例如:
text
Service:
删除 Agent 后要清缓存
Repository:
执行 Agent 删除操作
两者职责不同。
4. Schema / DTO:负责数据长什么样
例如:
text
AgentCreate
AgentUpdate
AgentOut
它们负责描述:
text
输入参数
输出数据
字段类型
校验结构
这样就不会让各种 dict 在项目中自由传播。
5. Runtime:负责复杂运行能力
对于普通增删改改查类的管理功能,这一层可能并不需要单独存在。但 AI Agent、RAG、工作流等系统通常会有:
text
AgentRunner
Retrieval Pipeline
LLM Runtime
Conversation Runtime
Tool Execution
这些东西既不是普通数据库的增删改查功能,也不是简单 API,因此可以独立形成 Runtime 层。
6. Infrastructure:负责技术基础设施
Infrastructure 通常包括:
text
数据库连接
缓存
日志
配置
事件总线
外部服务连接
文件存储
这些属于系统运行需要的技术能力,业务代码应该使用它们。
三、真正重要的是:调用要有方向
仅仅建立:
text
api/
services/
repositories/
几个目录,还不够,更重要的是规定:
哪些层可以调用哪些层。
最容易理解的规则是:
text
API
↓
Service
↓
Repository
↓
Database
而不要变成:
text
API ───────→ Database
↑ ↓
Repository ← Service
否则目录虽然分层了,代码实际上还是一团乱。
因此可以给 AI 几条很直接的规则。
规则 1:API 不直接操作数据库
错误:
python
@router.delete("/{id}")
async def delete(id: int):
await db.execute(...)
推荐:
python
await service.delete(id)
规则 2:API 不承载核心业务逻辑
不要写成:
python
if agent.is_active:
...
if has_tools:
...
if user.role:
...
await db...
cache.clear()
这些应该进入 Service。
规则 3:Repository 不负责业务决策
Repository 可以:
text
查 Agent
删 Agent
更新 Agent
但尽量不要在里面决定:
"管理员不能删除默认 Agent。"
这是业务规则,更适合 Service。
规则 4:上层通过稳定接口调用下层
例如:
text
API
↓
AgentService.delete_agent()
API 不需要知道 Service 内部究竟:
text
调用几个 Repository
是否清缓存
是否发事件
这样以后 Service 内部改变,API 可以保持稳定。
规则 5:禁止为了方便跨层调用
这是对 AI 非常重要的一条。
例如 AI 在 API 中发现:
text
container.agent_db
就在 Router 里直接调用。
虽然"拿得到",并不代表"应该用"。
架构规则应该明确:
可访问,不等于允许访问。
四、这样做有什么好处?
分层带来的好处,不只是代码看起来漂亮;对于 AI 编程,它会直接影响代码长期质量。
1. AI 更容易判断文件应该放在哪里
当 AI 要增加:
Agent 查询功能
它就可以判断:
text
HTTP 接口
→ api
业务规则
→ services
数据库查询
→ repositories
输入输出模型
→ schemas
不需要每次重新设计工程结构。
2. 修改范围更小
如果只是修改:
Agent 的业务规则
通常重点检查:
text
AgentService
而不必把 API、数据库和前端全部推翻。
这使 AI 更容易执行"小范围修改"。
3. 更容易测试
Service 不依赖 HTTP 后,就可以单独测试,Repository 可以单独测试数据库。
API 可以测试:
text
路由是否正确
权限是否正确
输入输出是否正确
测试目标会非常清楚。
4. 更换技术实现时影响更小
例如以后:
text
SQLite
↓
PostgreSQL
理想情况下,主要修改:
text
Repository / Infrastructure
而不是把业务代码一起重写。
同样,如果:
text
FastAPI
未来更换其他接口框架,核心业务层也不应该全部推倒。
5. AI 更容易理解已有项目
对 AI 来说,目录本身就是一种信息。
看到:
text
api/
services/
repositories/
schemas/
runtime/
infra/
它马上能推断:
这是一个有明确职责分层的系统。
比所有 Python 文件都堆在:
text
app/
下面更容易理解。
6. 多个 AI 更容易保持一致
今天使用一个模型,明天换另一个模型,后天可能使用 Coding Agent 自动修改代码。
只要架构规则稳定:
text
API 就是 API
Service 就是 Service
Repository 就是 Repository
不同 AI 的代码风格虽然可能不同,但整体工程结构不容易漂移。
五、AI 最容易出现的"失控现场"
假设我们告诉 AI:
增加一个删除 Agent 的接口。
如果没有分层规则,AI 很可能写成:
python
@router.delete("/{agent_id}")
async def delete_agent(agent_id: int):
agent = await db.get_agent(agent_id)
if not agent:
raise HTTPException(404)
await db.delete_agent(agent_id)
await db.delete_agent_tools(agent_id)
await db.delete_user_agent_relations(agent_id)
cache.remove(f"agent:{agent_id}")
return {
"success": True
}
从功能角度看:
好像完全正确。
但它已经把:
text
HTTP
业务规则
数据库
关系维护
缓存
异常
全部塞进一个 Router。
下一次让 AI:
批量删除 Agent。
它可能复制一遍。
再下一次:
修改 Agent。
又复制类似逻辑。
久而久之:
text
Router = API + Service + Repository + Cache
所谓"分层"彻底失效。
更危险的是:
AI 会学习项目已有代码。
一旦项目里已经出现大量错误范例,后续 AI 很可能继续模仿这种写法...
六、提示词落地:把层级规则写给 AI
只告诉 AI:
"使用整洁架构。"
依然太抽象。
真正有用的是明确职责和调用方向,例如可以把下面的规则放入项目规则:
text
## 后端分层规则
后端采用严格的分层架构。
层级:
- app/api:
HTTP 路由、请求解析、授权入口、依赖注入、响应转换。
- app/services:
业务逻辑和用例编排。
- app/repositories:
数据库访问和持久化操作。
- app/schemas:
请求/响应 DTO 和验证模型。
- app/runtime:
智能体、对话、LLM、检索和其他长时间运行的运行时功能。
- app/infra:
数据库、缓存、日志记录、配置、存储和基础设施集成。
规则:
- API 路由不得直接访问数据库。
- API 路由必须将业务操作委托给服务。
- 服务不得依赖于 FastAPI 请求或 HTTP 详细信息。
- 仓库代码应专注于持久化,而非业务规则。
- 不要仅仅因为可以直接访问存储库或数据库对象就绕过服务。
- 在创建新的服务和存储库之前,请优先重用现有的服务和存储库。
- 保持依赖关系在已建立的架构中流动。
- 在编写代码之前,请确定每个职责对应的正确层级。
然后任务 提示词 可以这样写:
text
为 Agent 增加删除功能。
严格遵守项目现有的分层架构:
API 只负责路由、权限和响应;
业务逻辑放在 AgentService;
数据库操作复用现有数据访问层;
不要在 Router 中直接访问数据库或处理缓存。
实现之前先检查现有 Agent API、Service、Schema 和数据访问代码。
这时候 AI 得到的是一条非常清晰的施工路线:
text
先找 API
↓
再找 Service
↓
需要数据时找 Repository
↓
需要基础设施时走现有能力
而不是:
"哪个对象拿得到,就调用哪个。"
七、正面产出:miniagent 的真实分层是怎样的?
以实际项目 miniagent 为例。
miniagent 当前后端并不是传统三层 MVC(即:Model、View、Controller) 架构,而是针对 Agent 系统的复杂度采用下面的分层架构:
#mermaid-svg-9xgQ4Y4vNnLPTNqR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9xgQ4Y4vNnLPTNqR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .error-icon{fill:#552222;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .marker.cross{stroke:#333333;}#mermaid-svg-9xgQ4Y4vNnLPTNqR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9xgQ4Y4vNnLPTNqR p{margin:0;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .cluster-label text{fill:#333;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .cluster-label span{color:#333;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .cluster-label span p{background-color:transparent;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .label text,#mermaid-svg-9xgQ4Y4vNnLPTNqR span{fill:#333;color:#333;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .node rect,#mermaid-svg-9xgQ4Y4vNnLPTNqR .node circle,#mermaid-svg-9xgQ4Y4vNnLPTNqR .node ellipse,#mermaid-svg-9xgQ4Y4vNnLPTNqR .node polygon,#mermaid-svg-9xgQ4Y4vNnLPTNqR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .rough-node .label text,#mermaid-svg-9xgQ4Y4vNnLPTNqR .node .label text,#mermaid-svg-9xgQ4Y4vNnLPTNqR .image-shape .label,#mermaid-svg-9xgQ4Y4vNnLPTNqR .icon-shape .label{text-anchor:middle;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .rough-node .label,#mermaid-svg-9xgQ4Y4vNnLPTNqR .node .label,#mermaid-svg-9xgQ4Y4vNnLPTNqR .image-shape .label,#mermaid-svg-9xgQ4Y4vNnLPTNqR .icon-shape .label{text-align:center;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .node.clickable{cursor:pointer;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .arrowheadPath{fill:#333333;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9xgQ4Y4vNnLPTNqR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9xgQ4Y4vNnLPTNqR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9xgQ4Y4vNnLPTNqR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .cluster text{fill:#333;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .cluster span{color:#333;}#mermaid-svg-9xgQ4Y4vNnLPTNqR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9xgQ4Y4vNnLPTNqR rect.text{fill:none;stroke-width:0;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .icon-shape,#mermaid-svg-9xgQ4Y4vNnLPTNqR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .icon-shape p,#mermaid-svg-9xgQ4Y4vNnLPTNqR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .icon-shape .label rect,#mermaid-svg-9xgQ4Y4vNnLPTNqR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9xgQ4Y4vNnLPTNqR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9xgQ4Y4vNnLPTNqR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9xgQ4Y4vNnLPTNqR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} REST / SSE
前端应用
Management / Workplace
接口层 API Layer
app/api
路由 · 参数解析 · 权限入口 · 响应转换
业务服务层 Service Layer
app/services
业务逻辑 · 用例编排 · 跨模块协调
运行时层 Runtime Layer
app/runtime
AgentRunner · Conversation · LLM · Retrieval · Tool
数据访问层 Repository Layer
app/repositories
查询 · 新增 · 更新 · 删除 · 持久化
数据模型 Schema / DTO
app/schemas
请求模型 · 响应模型 · 数据校验
核心能力 Core
app/core
配置 · 安全 · DI · i18n · 日志
基础设施层 Infrastructure Layer
app/infra
ORM · 数据库初始化 · 缓存 · 存储
数据与外部资源
SQLite · DuckDB · ChromaDB · BM25 · Files · LLM APIs
可以简化为:
#mermaid-svg-W5VXyNPXp16SC9vG{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-W5VXyNPXp16SC9vG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-W5VXyNPXp16SC9vG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-W5VXyNPXp16SC9vG .error-icon{fill:#552222;}#mermaid-svg-W5VXyNPXp16SC9vG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-W5VXyNPXp16SC9vG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-W5VXyNPXp16SC9vG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-W5VXyNPXp16SC9vG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-W5VXyNPXp16SC9vG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-W5VXyNPXp16SC9vG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-W5VXyNPXp16SC9vG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-W5VXyNPXp16SC9vG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-W5VXyNPXp16SC9vG .marker.cross{stroke:#333333;}#mermaid-svg-W5VXyNPXp16SC9vG svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-W5VXyNPXp16SC9vG p{margin:0;}#mermaid-svg-W5VXyNPXp16SC9vG .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-W5VXyNPXp16SC9vG .cluster-label text{fill:#333;}#mermaid-svg-W5VXyNPXp16SC9vG .cluster-label span{color:#333;}#mermaid-svg-W5VXyNPXp16SC9vG .cluster-label span p{background-color:transparent;}#mermaid-svg-W5VXyNPXp16SC9vG .label text,#mermaid-svg-W5VXyNPXp16SC9vG span{fill:#333;color:#333;}#mermaid-svg-W5VXyNPXp16SC9vG .node rect,#mermaid-svg-W5VXyNPXp16SC9vG .node circle,#mermaid-svg-W5VXyNPXp16SC9vG .node ellipse,#mermaid-svg-W5VXyNPXp16SC9vG .node polygon,#mermaid-svg-W5VXyNPXp16SC9vG .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-W5VXyNPXp16SC9vG .rough-node .label text,#mermaid-svg-W5VXyNPXp16SC9vG .node .label text,#mermaid-svg-W5VXyNPXp16SC9vG .image-shape .label,#mermaid-svg-W5VXyNPXp16SC9vG .icon-shape .label{text-anchor:middle;}#mermaid-svg-W5VXyNPXp16SC9vG .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-W5VXyNPXp16SC9vG .rough-node .label,#mermaid-svg-W5VXyNPXp16SC9vG .node .label,#mermaid-svg-W5VXyNPXp16SC9vG .image-shape .label,#mermaid-svg-W5VXyNPXp16SC9vG .icon-shape .label{text-align:center;}#mermaid-svg-W5VXyNPXp16SC9vG .node.clickable{cursor:pointer;}#mermaid-svg-W5VXyNPXp16SC9vG .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-W5VXyNPXp16SC9vG .arrowheadPath{fill:#333333;}#mermaid-svg-W5VXyNPXp16SC9vG .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-W5VXyNPXp16SC9vG .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-W5VXyNPXp16SC9vG .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-W5VXyNPXp16SC9vG .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-W5VXyNPXp16SC9vG .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-W5VXyNPXp16SC9vG .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-W5VXyNPXp16SC9vG .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-W5VXyNPXp16SC9vG .cluster text{fill:#333;}#mermaid-svg-W5VXyNPXp16SC9vG .cluster span{color:#333;}#mermaid-svg-W5VXyNPXp16SC9vG div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-W5VXyNPXp16SC9vG .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-W5VXyNPXp16SC9vG rect.text{fill:none;stroke-width:0;}#mermaid-svg-W5VXyNPXp16SC9vG .icon-shape,#mermaid-svg-W5VXyNPXp16SC9vG .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-W5VXyNPXp16SC9vG .icon-shape p,#mermaid-svg-W5VXyNPXp16SC9vG .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-W5VXyNPXp16SC9vG .icon-shape .label rect,#mermaid-svg-W5VXyNPXp16SC9vG .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-W5VXyNPXp16SC9vG .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-W5VXyNPXp16SC9vG .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-W5VXyNPXp16SC9vG :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 接口层
app/api
HTTP 路由 · 请求 / 响应处理
业务服务层
app/services
业务逻辑 · 用例编排
运行时 / 数据访问层
app/runtime · app/repositories
Agent 运行时 · 数据访问
基础设施 / 数据层
app/infra
SQLite · DuckDB · ChromaDB · 文件存储
这不是为了目录漂亮,而是在告诉开发者和 AI:
不同代码应该在哪一层工作。
1. API 层明确声明:这里只处理 HTTP
miniagent 当前的 Agent API 文件:
text
backend/app/api/admin/agent.py
文件开头直接写着:
python
# Agent API Router -- HTTP layer only,
# all logic lives in AgentService
这句话本身其实就是非常好的 AI 架构提示词:
这里只处理 HTTP,所有业务逻辑进入 AgentService。
例如删除 Agent:
python
@router.delete(
"/{agent_id}",
response_model=ApiResponse,
summary="Delete agent [agent:delete]"
)
async def delete_agent(
agent_id: int,
svc: AgentService = Depends(get_service),
caller_id: int = Depends(_delete),
):
await svc.delete_agent(agent_id)
return ApiResponse()
可以看到 Router 做的事情很少:
text
接收 agent_id
↓
权限检查
↓
取得 AgentService
↓
调用 delete_agent()
↓
返回 ApiResponse
它没有自己:
text
操作 Agent 数据库
清理缓存
实现 Agent 业务规则
这就是非常典型的"守住 API 层"。
2. 业务逻辑进入 AgentService
对应的:
text
backend/app/services/admin/agent.py
文件开头同样明确说明:
python
# Agent Service -- business logic layer
# (no HTTP / FastAPI imports)
而 AgentService 的说明则是:
python
class AgentService:
"""
Encapsulates all business logic for the Agent resource.
"""
也就是说:
Service 层明确不依赖 HTTP / FastAPI,并负责 Agent 业务逻辑。
这是一个非常重要的边界。如果以后把 AgentService 用在:
text
HTTP API
后台任务
脚本
测试
其他内部服务
它都不需要知道:
当前请求是不是来自 FastAPI。
3. 删除 Agent 的业务动作也发生在 Service
例如:
python
async def delete_agent(
self,
agent_id: int
) -> None:
await self._agent_db.delete_agent(agent_id)
self._cache.on_agent_changed(agent_id)
这里就可以看出职责区别。
API 层只知道:
text
我要删除 Agent
Service 则知道:
text
删除 Agent
+
Agent 发生变化后让相关缓存失效
以后如果再增加:
text
记录审计
发布事件
清理关联资源
这些业务动作仍然可以由 Service 组织,API 不需要因此越来越胖。
4. Service 再调用数据访问能力
AgentService 初始化时取得:
python
self._agent_db = container.agent_db
self._user_agent_relation_db =
container.user_agent_relation_db
self._agent_tool_relation_db =
container.agent_tool_relation_db
self._tool_db = container.tool_db
self._cache =
container.object_cache_invalidator
于是形成一条非常清楚的调用链:
text
Agent API
↓
AgentService
↓
Agent DB / Relation DB
↓
Database
同时缓存也是通过现有基础能力处理:
text
AgentService
↓
Object Cache Invalidator
而不是 API 自己:
python
redis.delete(...)
5. 复杂能力单独进入 Runtime
miniagent 和普通管理系统还有一个不同点:
它真正需要运行:
text
Agent
LLM
RAG Retrieval
Conversation
Tool
SQL Agent
因此项目把这些长期运行或执行型能力单独放在:
text
app/runtime/
Runtime 包含 Agent、Session、LLM、Retrieval 等运行时组件。
这样就不会把:
text
AgentRunner
RetrievalPipeline
LLM Client
硬塞进普通 Service。
这也是一种很重要的分层思想:
架构应该服务于业务复杂度,而不是机械套模板。
八、分层和上一篇 DDD 到底有什么区别?
这两个概念非常容易混在一起。可以用两个问题区分,DDD 主要回答:
这段业务属于谁?
例如:
text
Agent
Knowledge Base
Tool
Conversation
User
这是业务边界。
而分层架构主要回答:
这段代码属于哪一层?
例如:
text
API
Service
Repository
Runtime
Infrastructure
这是技术职责边界。
可以简单理解成:
text
DDD
解决横向边界
分层架构
解决纵向边界
两个结合以后,AI 得到的是一张更加清晰的地图:
text
先判断:
这是哪个领域?
再判断:
这是哪一层代码?
结语
分层架构看起来只是几个目录:
text
api/
services/
repositories/
但它真正的意义远不止如此,它是在持续告诉 AI:
接请求的地方不要写业务;
写业务的地方不要关心 HTTP;
需要数据时走数据访问层;
需要基础设施时复用统一能力。
AI 的编码能力越强,一次能够修改的文件越多,这种边界反而越重要。否则,一个错误的"方便调用",可能很快被 AI 复制到几十个地方。
因此:
标准化目录结构只是表象,真正重要的是标准化职责和依赖方向。
对于 AI 编程来说,可以把它总结成一句非常简单的话:
DDD 告诉 AI"这是谁的事",分层架构告诉 AI"这件事应该在哪一层做"。
两者结合,AI 才真正拥有了一张可以长期施工的工程地图。
开源代码
🪐祝您好运🪐