分层整洁架构:标准化工程目录结构,防范 AI 越界调用

文章目录

    • [📌 技术名片](#📌 技术名片)
      • [💡 一句话理解](#💡 一句话理解)
    • [一、为什么 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 才真正拥有了一张可以长期施工的工程地图。

开源代码


🪐祝您好运🪐

相关推荐
新知图书1 小时前
8.1 智能体的心跳执行模式:以定时器为核心(智能体工程)
人工智能·agent·ai agent·智能体
Dr.kangder1 小时前
嵌入式面试总结(二十二)——指针
java·面试·职场和发展·架构·嵌入式
墨心@1 小时前
阶段 4:事件总线
人工智能·语言模型·大语言模型·agent·codex·harness
AbrahamCS1 小时前
从角色协同到可运行图:Graph Engineer 与多智能体编排
前端·人工智能·智能体
MacroZheng1 小时前
几行代码给项目集成AI功能,Spring AI 2.0太香了!
java·人工智能·spring boot
zed_231 小时前
给接口加把锁:Bearer Token 鉴权(附冒烟验收)
人工智能
今日无bug1 小时前
AI Loop —— 别再写 Prompt 了,去设计一个 Loop
ai编程
u0103055272 小时前
昇腾Model Agent模型适配指南
人工智能
Neptune232 小时前
Agent死循环6小时:从“相信LLM判断”到“焊死硬件条件止
人工智能