AI 很擅长快速写接口,比如你告诉它:
"增加一个创建用户接口。"
它很可能几分钟就能给出一段能运行的代码,但如果项目里没有统一规范,不同接口很快就会变成这样:
json
{
"success": true
}
另一个接口返回:
json
{
"code": 0,
"msg": "ok",
"result": {}
}
再一个接口出错时直接返回:
json
{
"error": "user not found"
}
甚至有些地方直接把数据库异常抛给前端。
单个接口都"能跑",整个系统却越来越难维护。
所以,AI 编程里有一类非常重要、却经常被忽略的基础架构:
统一返回值 + 全局异常处理 + 统一参数校验。
它们的作用,就是提前规定:
成功怎么返回,失败怎么返回,非法输入在哪里拦截。
📌 技术名片
统一异常与接口封装架构
指通过统一响应模型、统一异常体系和统一入参校验机制,让整个系统的接口具有一致的输入、输出和错误处理方式。
这里常见三个核心概念:
- API Response Envelope:接口响应封装,即规定所有接口统一返回什么结构;
- Global Exception Handling:全局异常处理,即异常集中处理,而不是每个接口各写一遍;
- Validator:校验器,用于在数据进入业务逻辑之前检查格式、范围和合法性。
💡 一句话理解
可以把 API 想象成机场安检。
旅客进入机场以后:
text
检查证件
↓
检查行李
↓
符合规则
↓
进入候机区
如果有问题:
text
证件错误
行李违规
身份异常
不会让每一个登机口自己决定:
"这个人该怎么办?"
而是由统一的安检规则处理。
软件系统也一样:
text
用户请求
↓
参数校验
↓
业务处理
↓
统一返回
发生异常时:
text
业务异常
系统异常
↓
统一异常处理
↓
标准错误响应
这对 AI 尤其重要。因为如果没有规则,AI 很容易:
每写一个接口,就重新发明一次返回格式和错误处理方式。
一、反面教材:AI 的"自由发挥"
假设让 AI 实现一个创建用户接口:
用户名至少 3 个字符,密码必须符合安全规则。如果用户名已经存在,要给出错误提示。
没有架构约束时,AI 很可能写成:
python
@router.post("/users")
async def create_user(data: dict):
if len(data["username"]) < 3:
return {
"success": False,
"message": "username too short"
}
if len(data["password"]) < 8:
return {
"code": 400,
"error": "invalid password"
}
user = await db.get_user(data["username"])
if user:
raise HTTPException(
status_code=400,
detail="user already exists"
)
try:
new_user = await db.create_user(data)
return {
"result": new_user,
"status": "ok"
}
except Exception as e:
return {
"error": str(e)
}
功能似乎完整。
但里面已经出现了很多问题。
问题 1:返回格式不统一
同一个接口里甚至出现了:
json
{
"success": false
}
和:
json
{
"code": 400
}
以及:
json
{
"status": "ok"
}
前端每调用一个接口,都要重新猜:
这次到底应该判断
success、code还是status?
问题 2:参数校验散落在业务代码里
python
if len(data["username"]) < 3:
python
if len(data["password"]) < 8:
这些本质上属于:
输入是否合法?
却混进了业务逻辑。
下一个 AI 再写一个"修改用户"接口,很可能又复制一遍。
问题 3:异常处理方式不一致
用户名存在:
python
raise HTTPException(...)
密码错误:
python
return {...}
数据库异常:
python
except Exception as e:
return {"error": str(e)}
三种错误,三套处理方式。
问题 4:系统内部错误直接暴露给用户
python
str(e)
可能把:
text
数据库表名
SQL 语句
服务器路径
内部配置
直接返回前端。
这不仅难看,还可能带来安全风险。
问题 5:每个接口都在重复造轮子
当系统有 100 个接口以后,就可能出现:
text
100 套参数判断
100 套 try / except
20 种返回结构
10 种错误格式
真正的问题不是 AI 不会写代码。
而是:
没有统一规范时,AI 会非常高效地制造不一致。
二、架构规则:人类先把"设计图纸"画好
解决这个问题,可以把整个请求过程标准化:
text
客户端请求
↓
Schema / Validator
入参校验
↓
API
↓
Service
↓
业务异常
↓
Global Exception Handler
全局异常处理
↓
ApiResponse
统一返回
然后给 AI 明确几条规则。
规则 1:所有普通 API 使用统一返回结构
例如统一规定:
json
{
"code": 200,
"message": "success",
"data": {}
}
三个字段各司其职:
text
code
业务 / 状态代码
message
给人看的提示信息
data
真正返回的数据
成功时:
json
{
"code": 200,
"message": "success",
"data": {
"id": 12,
"username": "tom"
}
}
失败时:
json
{
"code": 404,
"message": "User '12' not found"
}
前端不用再猜。
规则 2:API 不要到处自己拼 JSON
不要:
python
return {
"success": True,
"result": data
}
也不要:
python
return {
"status": "ok",
"payload": data
}
统一使用:
python
return ApiResponse(data=data)
这样返回格式只有一份定义。
规则 3:输入格式由 Schema 和 Validator 负责
例如:
python
class UserCreate(BaseModel):
username: str = Field(
...,
min_length=3,
max_length=100
)
这里的 Field 可以理解为:
字段规则。
它直接规定:
text
username
最少 3 个字符
最多 100 个字符
AI 不需要在每一个 API 里重新写:
python
if len(username) < 3:
规则 4:复杂字段使用 Validator
有些校验不是简单长度就能完成。
例如密码可能要求:
text
至少多少位
包含大小写字母
包含数字
包含特殊字符
这时候就适合使用:
Validator(校验器)
把密码规则集中在一个地方。
以后:
text
创建用户
重置密码
修改密码
全部复用。
规则 5:业务错误使用统一异常类型
例如:
text
用户不存在
Agent 不存在
知识库不存在
它们都属于:
Not Found(资源不存在)
可以统一使用:
python
NotFoundError
而:
text
用户名已存在
Agent 名称重复
可以统一归入:
python
AlreadyExistsError
这样 Service 只需要表达:
"发生了什么业务错误。"
不用关心 HTTP 最终应该返回 404、409 还是其他状态码。
规则 6:异常集中到全局处理
业务代码可以:
python
raise NotFoundError(...)
全局异常处理器负责:
text
NotFoundError
↓
HTTP 404
↓
ApiResponse
而不是每一个接口:
python
try:
...
except:
...
重复几十遍。
三、这样做有什么好处?
对于传统开发,这叫工程规范。对于 AI 编程,它还有更直接的价值。
1. AI 不再随意创造返回结构
项目里只有:
python
ApiResponse
这一套规则。
AI 看到现有代码以后,更容易继续写:
python
return ApiResponse(data=result)
2. 前端调用变得非常简单
前端可以统一认为:
text
code
message
data
永远存在。
于是统一 HTTP Client 就可以统一处理:
text
成功
错误
Token 失效
提示信息
不用每个页面单独适配。
3. 校验规则不会散落
例如密码规则如果修改:
最低 8 位改成最低 12 位。
如果规则集中在 Validator,只改一个地方即可。
如果散落在:
text
注册
创建用户
修改密码
重置密码
管理员后台
五个接口中,就很容易漏改。
4. Service 更干净
业务代码不需要反复:
python
try:
...
except HTTPException:
...
它只处理业务:
text
用户不存在
↓
抛 NotFoundError
用户已存在
↓
抛 AlreadyExistsError
错误怎样转换成 HTTP,由外层统一完成。
5. AI 更容易理解"什么错误属于哪里"
可以建立非常清楚的边界:
text
输入格式错误
→ Validator
业务规则错误
→ Domain Error
系统未知错误
→ Global Exception Handler
响应格式
→ ApiResponse
这相当于把错误处理也进行了分层。
四、AI 最容易出现的"失控现场"
一个非常典型的失控过程是:
第一次 AI 写:
python
return {"success": True}
第二次看到另一个项目习惯,于是写:
python
return {
"code": 0,
"message": "ok"
}
第三次又直接:
python
raise HTTPException(...)
第四次为了"保险":
python
try:
...
except Exception:
...
最后系统里可能出现:
text
API A → HTTPException
API B → 自定义 JSON
API C → ApiResponse
API D → try / except
API E → 返回 None
而 AI 有一个非常现实的特点:
它经常会模仿项目里已经存在的代码。
项目里如果同时存在五种写法,后续 AI 就很难知道:
哪一种才是真正的标准?
所以统一异常架构还有一个非常重要的价值:
给 AI 提供大量一致的正确范例。
五、提示词落地:把规则教给 AI
仅仅告诉 AI:
"注意异常处理。"
几乎没有实际作用。
更有效的是写成具体工程规则。
例如可以加入项目级规则:
text
## API 响应和验证规则
所有标准的 JSON API 必须使用项目统一的
ApiResponse 响应信封。
标准响应字段:
- code
- message
- data
规则:
- 除非现有协议明确要求,否则请勿创建其他响应结构,例如:success/result/payload/status。
- API 路由应返回 ApiResponse,而不是手动构建响应字典。
- 请求负载必须使用现有的 Pydantic schema。
- 简单的输入约束(例如 length、range 和 required 字段)应在 Pydantic 字段定义中声明。
- 可重用或复杂的验证规则应使用项目现有的验证器。
- 请勿在 API 路由中重复验证逻辑。
- 业务错误必须使用项目的域异常类型,例如:NotFoundError 和 AlreadyExistsError。
- 请勿在服务中将业务错误转换为 HTTPException。
- 不要为每个 API 路由添加重复的 try/except 代码块。
- 让全局异常处理机制将已知的异常转换为标准化的 API 响应。
- 意外的内部异常不得在生产环境中暴露敏感的实现细节。
以后让 AI 开发接口时,可以进一步提示:
text
实现"创建用户"接口。
请严格遵守项目现有的统一接口规范:
1. 请求参数使用已有 Pydantic Schema;
2. 字段长度、范围等规则放在 Schema / Validator;
3. API 返回统一使用 ApiResponse;
4. 用户已存在等业务错误使用现有 Domain Error;
5. 不要在 Router 中重复编写 try/except;
6. 不要自行创建新的错误返回格式;
7. 优先复用项目已有 Validator 和异常类型。
实现前先检查:
app/schemas/common.py
相关业务 Schema
现有 Service
全局异常处理代码。
这时候 AI 不再是在"自由设计一个接口",而是在:
现有异常与响应协议下增加一个接口。
六、正面产出:miniagent 是怎么做的?
miniagent 已经把:
text
统一响应
业务异常
全局异常处理
Pydantic 参数校验
几个部分组合起来了,参见下图:
1. 统一返回值:ApiResponse
miniagent 在 backend/app/schemas/common.py 定义了统一顶层响应模型:
python
class ApiResponse(BaseModel, Generic[T]):
"""
Generic top-level API response envelope.
"""
code: int = Field(
200,
description="Business status code, 200 = success"
)
message: str = Field(
"success",
description="Human-readable status message"
)
data: Optional[T] = Field(
None,
description="Response payload"
)
也就是说,普通接口统一围绕:
json
{
"code": 200,
"message": "success",
"data": {}
}
展开。
这里还使用了:
python
Generic[T]
Generic 的意思是泛型。
可以简单理解成:
data可以装不同类型的数据,但外面的code / message / data外壳保持不变。
例如:
text
ApiResponse[UserOut]
ApiResponse[AgentOut]
ApiResponse[PageResult]
内部数据不同,外层协议统一。
2. 分页结果也统一封装
同一个文件里还定义了:
python
class PageResult(BaseModel, Generic[T]):
total: int
page: int
page_size: int
data: List[T]
因此分页接口不需要今天返回:
text
rows
count
current
明天又变成:
text
items
total
pageNum
而是统一:
json
{
"total": 100,
"page": 1,
"page_size": 20,
"data": []
}
这对 AI 特别重要,因为 AI 新增分页接口时,可以直接复用现有结构。
3. 业务异常也有统一基类
miniagent 定义:
python
class BaseDomainError(Exception):
"""
Business Logic Exception Base Class
"""
即:
领域业务异常基类。
然后继续派生:
python
class NotFoundError(BaseDomainError):
...
class AlreadyExistsError(BaseDomainError):
...
class EmptyDataError(BaseDomainError):
...
class ReadOnlyError(BaseDomainError):
...
class InvalidValueError(BaseDomainError):
...
这样业务代码遇到错误时,可以不需要重新创造:
text
UserNotExistException
MissingAgentException
KBNotFoundException
NoDocumentException
各种完全不同的错误体系,而是尽量归入现有语义。
4. 错误信息还统一接入 I18n
这里的 I18n 是:
Internationalization,国际化。
BaseDomainError 可以通过:
python
def to_detail(self) -> str:
return _translate(...)
把异常转成对应语言的提示信息。
这意味着:
text
业务异常
↓
统一错误类型
↓
统一国际化文案
而不是每个 AI 新写一个接口就硬编码:
python
"User not found"
七、全局异常:异常只需要"抛",不用到处"接"
miniagent 在应用入口中提供了:
python
def handle_exception(exc: Exception) -> JSONResponse:
统一处理不同类型异常,例如:
text
Service
↓
NotFoundError
↓
Global Exception Handler
↓
HTTP 404
↓
ApiResponse
对于:
python
AlreadyExistsError
则统一转成:
text
HTTP 409 Conflict
其中 Conflict 的意思是:
资源状态冲突。
例如创建一个已经存在的用户名,就很适合这种语义。
未知异常也统一兜底
如果不是已知业务异常:
python
error_data = {
"error":
str(exc)
if settings.debug
else t("common.error_500")
}
这里体现了一个非常重要的生产环境原则:
开发环境:
text
可以看到详细错误
方便调试
生产环境:
text
隐藏内部实现
返回统一错误信息
避免直接把内部异常暴露给普通用户。
八、Validator:错误输入尽量不要进入业务层
再看 miniagent 的用户 Schema。
它没有在创建用户的 API 里面写:
python
if len(username) < 3:
而是直接定义:
python
class UserCreate(BaseModel):
username: str = Field(
...,
min_length=3,
max_length=100
)
nickname: Optional[str] = Field(
None,
max_length=100
)
avatar: Optional[str] = Field(
None,
max_length=500
)
这意味着:
用户名长度不合法时,请求还没真正进入核心业务逻辑,就已经被数据模型拦下。
密码校验进一步复用 Validator
miniagent 当前定义:
python
PasswordValue = Annotated[
str,
Field(max_length=128),
AfterValidator(validate_password)
]
这里出现两个术语。
Annotated
Annotated 可以理解成:
给一个数据类型附加额外规则。
这里基础类型仍然是:
python
str
但同时增加:
text
最大长度 128
+
密码 Validator
AfterValidator
AfterValidator 可以理解成:
基础类型校验完成以后,再执行一个自定义校验函数。
这里调用的是:
python
validate_password
于是创建用户:
python
class UserCreate(BaseModel):
password: PasswordValue
重置密码:
python
class UserPasswordReset(BaseModel):
password: PasswordValue
都复用同一套密码规则。
这就是典型的:
一次定义,到处复用。
九、最终形成了一条非常清晰的请求链
把 miniagent 这些真实实现组合起来,可以得到:

于是不同职责变得非常清楚:
text
Schema / Validator
负责"输入是否合法"
Service
负责"业务能不能做"
Domain Error
负责"业务出了什么问题"
Global Exception Handler
负责"错误怎样转换成 HTTP 响应"
ApiResponse
负责"最后返回长什么样"
这就是统一异常架构真正的价值。
十、架构赋能后的 AI 会怎么写?
假设现在再告诉 AI:
"增加一个修改用户名功能。"
没有架构时,AI 可能从头设计:
text
参数判断
返回 JSON
try / except
错误信息
有了 miniagent 这样的规则以后,它应该优先思考:
text
1. UserUpdate 是否已有 username 字段?
↓
2. Field 是否已经规定长度?
↓
3. UserService 中增加业务操作
↓
4. 重名时抛 AlreadyExistsError
↓
5. Router 调用 Service
↓
6. 返回 ApiResponse
而不是重新创造一套规则。
这就是所谓:
架构赋能后的 AI 输出。
不是 AI 突然"更聪明"了,而是:
我们把它可以自由决定的事情减少了。
结语
统一异常与接口封装,看起来只是几个不起眼的基础类:
text
ApiResponse
PageResult
BaseDomainError
Validator
但对于 AI 编程来说,它们实际上建立了一套非常重要的"交通规则"。
它告诉 AI:
text
输入怎么检查
成功怎么返回
业务错误怎么表达
系统错误在哪里处理
前端最终看到什么
如果没有这些规则,AI 会在每一个接口里自由发挥;而 AI 越能写代码,这种"不一致"产生得越快。
所以:
好的异常架构,不是为了让代码多几个基类,而是为了让整个系统只有一套错误语言和接口语言。
在 AI 编程时代,更应该把这套规范提前固化进 项目规则 / Project Rules、系统提示词 / System Prompt 或项目上下文中。
最终形成:
text
输入不合法
→ Validator 拦截
业务不允许
→ Domain Error 表达
系统发生异常
→ Global Handler 兜底
无论成功失败
→ 统一协议返回
这样 AI 每新增一个接口,实际上都在复用同一套工程规则。
不要让 AI 每写一个接口,就重新发明一次"什么叫成功,什么叫失败"。
先统一规则,再让 AI 写业务;这才是统一异常与封装架构在 AI 编程中的真正价值。
开源代码
🪐祝您好运🪐