先把接口规则定清楚,再让 AI 写代码。
在 AI 辅助开发中,接口设计是一个非常容易"看起来能用,最后越来越乱"的地方。
让 AI 帮你写几个接口,它可能很快就生成:
text
/getUser
/createUser
/update_user
/delete-user
/userList
功能都能跑。但项目继续发展,就很容易出现:
text
同一个资源,不同命名
同一个动作,不同 HTTP 方法
同一种错误,不同返回结构
同一个分页,不同字段名称
最后前端、后端、测试人员,甚至 AI 自己都开始猜:
"这个接口到底应该怎么调用?"
这时候真正缺少的,并不是代码能力,而是:
统一的接口契约。
📌 技术名片
RESTful API(Representational State Transfer Application Programming Interface,表述性状态转移应用程序编程接口)
是一种常见的网络接口设计风格。
它强调围绕"资源"设计 URL,并通过 HTTP 方法表达操作。
例如:
text
GET /agents
POST /agents
GET /agents/12
PUT /agents/12
DELETE /agents/12
这里:
GET:读取资源POST:创建资源PUT:整体更新资源PATCH:局部修改资源DELETE:删除资源
而:
Contract First(契约优先)
指的是:
先定义接口长什么样,再实现接口内部逻辑。
也就是说,先确定:
text
URL 是什么?
HTTP 方法是什么?
输入字段是什么?
输出字段是什么?
错误如何返回?
状态码是什么?
然后开发者或者 AI 再按照这份契约实现代码。
一个通俗比喻:接口就是餐厅菜单
可以把软件接口想象成一家餐厅的菜单。
菜单上写着:
text
宫保鸡丁
价格:38 元
辣度:中辣
份量:1 份
顾客不需要知道:
text
厨师是谁
锅是什么牌子
后厨怎么切菜
鸡肉放多少油
顾客只需要知道:
我按照菜单点菜,你按照菜单给我菜。
API 也是一样。
前端不应该关心:
text
数据库怎么查
Service 怎么实现
Repository 怎么写
它只需要知道:
text
请求地址
请求方式
参数格式
返回结果
错误格式
所以接口契约就像:
软件系统对外公布的菜单。
如果菜单每天都变:
text
昨天叫"宫保鸡丁"
今天叫"辣子鸡丁"
明天改成"鸡肉套餐 A"
顾客一定会崩溃,接口也是一样。
一、RESTful 的核心:URL 表达"资源",HTTP 方法表达"动作"
这是理解 RESTful 最重要的一点。
很多项目一开始会这样写:
text
/getAgent
/createAgent
/updateAgent
/deleteAgent
这种写法本质上是:
把动作写进 URL。
而 RESTful 更推荐:
text
GET /agents
POST /agents
PUT /agents/{agent_id}
DELETE /agents/{agent_id}
因为:
text
/agents
代表的是资源:
Agent 集合。
至于"查、增、改、删",交给 HTTP 方法表达。
于是接口语言会变得非常统一:
text
资源 + HTTP 方法
而不是:
text
资源 + 自定义动词 + 自定义命名习惯
二、为什么这种规则对 AI 编程特别重要?
人类开发者在一个项目里工作几年以后,通常会逐渐形成习惯。
可是 AI 不一样,AI 每次生成代码时,本质上都在重新推理:
text
这个接口应该叫什么?
这个更新应该用 POST 还是 PUT?
删除成功返回什么?
分页字段叫什么?
如果没有架构规则,AI 完全可能第一次生成:
text
POST /createAgent
下一次又生成:
text
POST /agents/create
再下一次变成:
text
POST /agent
三套接口都能工作,但项目已经开始失控。
所以:
RESTful 不是为了追求"教科书漂亮",而是为了减少 AI 的自由发挥空间。
三、接口设计规则
1. 统一资源命名
资源名称建议使用:
text
名词
复数
统一命名风格
例如:
text
/users
/agents
/tools
/documents
/knowledge-bases
而不是:
text
/userList
/getAgents
/toolManage
/queryDocument
miniagent 当前后台接口就采用了比较明确的资源式路径。
例如主程序统一注册:
python
app.include_router(
admin_agent_router,
prefix="/api/v1/admin/agents",
tags=["Admin - Agent"]
)
app.include_router(
admin_tool_router,
prefix="/api/v1/admin/tools",
tags=["Admin - Tool"]
)
app.include_router(
admin_document_router,
prefix="/api/v1/admin/documents",
tags=["Admin - Document"]
)
可以看到资源命名保持了:
text
/agents
/tools
/documents
/knowledge-bases
/prompts
/system-settings
而没有把:
text
get
create
delete
update
这些动作塞进 URL。
2. HTTP 方法表达动作
以 miniagent 的 Agent 管理接口为例。
读取列表:
python
@router.get("")
async def list_agents(...):
读取单个 Agent:
python
@router.get("/{agent_id}")
async def get_agent(...):
创建:
python
@router.post("")
async def create_agent(...):
更新:
python
@router.put("/{agent_id}")
async def update_agent(...):
删除:
python
@router.delete("/{agent_id}")
async def delete_agent(...):
组合起来就是:
text
GET /api/v1/admin/agents
GET /api/v1/admin/agents/{agent_id}
POST /api/v1/admin/agents
PUT /api/v1/admin/agents/{agent_id}
DELETE /api/v1/admin/agents/{agent_id}
即使你从来没看过 miniagent 的代码,也基本可以猜出这些接口的含义。
这就是好的接口设计带来的:
可预测性。
3. PUT 和 PATCH 不要乱用
这是 AI 非常容易混淆的地方。
PUT 通常表示:
更新一个资源。
例如:
text
PUT /agents/12
表示更新编号 12 的 Agent。
而 PATCH 表示:
Partial Update(局部更新)
即只修改资源的一部分状态。
miniagent 中就有一个很直观的例子:
python
@router.patch("/{agent_id}/toggle")
async def toggle_agent_active(...):
这里并不是重新提交整个 Agent,而只是:
切换 Agent 是否启用。
所以使用 PATCH 比较符合语义。
对于初学者来说,可以先记住:
text
PUT → 更新资源
PATCH → 局部修改
4. RESTful 也不是"绝对不能出现动作"
很多人学 RESTful 后容易走向另一个极端:
URL 里绝对不能出现任何动作。
其实没有必要。
有些业务操作本身并不是简单的 CRUD。
CRUD 全称:
Create、Read、Update、Delete(创建、读取、更新、删除)
例如:
text
/agents/{id}/toggle
表达:
切换 Agent 状态。
再比如未来可能有:
text
/documents/{id}/reindex
/tasks/{id}/cancel
/agents/{id}/run
这些本质上是业务命令。
重点不是机械追求"纯 REST",而是:
资源型操作遵循统一范式,特殊业务动作有明确且稳定的命名规则。
5. 契约不仅包含 URL
很多项目认为:
URL 统一了,就算接口规范完成了。
其实远远不够,一个完整接口契约至少应该包括:
text
请求路径
HTTP 方法
Path 参数
Query 参数
Request Body
Response Body
HTTP Status Code
错误模型
其中:
Path Parameter:
路径参数
例如:
text
/agents/{agent_id}
Query Parameter:
查询参数
例如:
text
/agents?page=1&page_size=20
Request Body:
请求体
即客户端发送的数据主体。
Response Body:
响应体
即服务端返回的数据主体。
HTTP Status Code:
HTTP 状态码
例如:
text
200 OK
201 Created
404 Not Found
409 Conflict
500 Internal Server Error
6. 查询参数也必须统一
假设一个系统里出现:
text
?page=1&pageSize=20
另一个接口:
text
?pageIndex=1&limit=20
再一个:
text
?offset=0&size=20
每一种都能工作。
但问题是:
前端永远记不住。
miniagent 的 Agent 列表接口采用:
python
page: int = Query(1, ge=1)
page_size: int = Query(20, ge=1, le=100)
并且分页结果定义为:
python
class PageResult(BaseModel, Generic[T]):
total: int
page: int
page_size: int
data: List[T]
这样整个项目都可以统一成:
text
page
page_size
total
data
而不是每个模块重新发明一次。
7. 接口响应也必须有统一"外壳"
这是契约优先中非常重要的一部分。
最容易失控的场景是:
用户接口返回:
json
{
"success": true,
"user": {}
}
Agent 接口返回:
json
{
"code": 0,
"result": {}
}
知识库接口又返回:
json
{
"status": "ok",
"data": {}
}
前端必须针对每个模块分别判断。
这就是:
接口契约碎片化。
miniagent 定义了统一响应模型:
python
class ApiResponse(BaseModel, Generic[T]):
code: int = 200
message: str = "success"
data: Optional[T] = None
因此成功响应可以统一成:
json
{
"code": 200,
"message": "success",
"data": {}
}
没有数据时也可以是:
json
{
"code": 200,
"message": "success"
}
这种设计最重要的价值不是少写几行代码。
而是:
前端只需要学习一次返回协议。
8. HTTP 状态码和业务返回结构要各司其职
miniagent 创建 Agent 时:
python
@router.post(
"",
response_model=ApiResponse,
status_code=status.HTTP_201_CREATED
)
这里使用:
text
201 Created
表示:
服务器成功创建了一个新资源。
而读取或更新通常返回:
text
200 OK
如果资源不存在,则全局异常处理返回:
text
404
已经存在则:
text
409
普通业务请求错误:
text
400
系统内部错误:
text
500
miniagent 在全局异常处理中已经统一映射这些错误类型。
这实际上建立了:
text
领域异常
↓
统一异常处理
↓
HTTP 状态码
↓
统一 ApiResponse
因此业务层不用自己决定:
text
到底返回 200?
还是返回 404?
错误 JSON 长什么样?
9. 契约优先真正解决的是"谁说了算"
如果没有契约,开发过程很容易变成:
text
前端认为应该这样
↓
后端写成另一种
↓
AI 又生成第三种
↓
联调时再修改
而契约优先变成:
text
先定义接口
↓
确定输入
↓
确定输出
↓
确定状态码
↓
前端实现
↓
后端实现
↓
自动测试
接口成为:
前后端共同遵守的协议。
10. FastAPI 天然适合契约优先
FastAPI 是:
一个基于 Python 类型标注构建 Web API 的框架。
其中一个重要特点就是:
可以直接根据代码中的类型定义生成 API 文档。
例如:
python
async def create_agent(
payload: AgentCreate,
):
这里的:
text
AgentCreate
本身就是输入契约。
而:
python
response_model=ApiResponse
则声明:
输出必须符合 ApiResponse 契约。
miniagent 在 FastAPI 初始化中还开启了:
python
docs_url="/docs"
redoc_url="/redoc"
FastAPI 会生成基于 OpenAPI 的接口文档。
OpenAPI:
OpenAPI Specification(开放应用程序接口规范)
是一种机器可读的 API 描述标准。
这样接口契约不只存在于人的脑子里,还可以被:
text
前端
测试工具
API 调试工具
代码生成器
AI
直接读取。
11. RESTful + Schema + ApiResponse 才是一整套接口规范
单独使用 RESTful 并不能解决全部问题。
真正稳定的接口体系通常是:
text
RESTful URL
+
HTTP Method
+
Schema
+
Status Code
+
Unified Response
+
Unified Exception
其中 Schema 可以理解为:
数据结构契约。
例如:
text
AgentCreate
AgentUpdate
AgentOut
分别定义:
text
创建 Agent 允许传什么
更新 Agent 允许传什么
返回 Agent 时包含什么
而不是一个:
python
dict
走遍天下。
四、有什么好处?
统一接口设计后,最明显的变化不是"代码更漂亮",而是整个团队的认知成本下降。
1. 接口可以猜
看到:
text
GET /agents/12
不用查文档也基本知道:
获取 Agent 12。
看到:
text
DELETE /agents/12
也能马上知道:
删除 Agent 12。
这就是:
一致性带来的可预测性。
2. 前后端沟通成本下降
不需要每新增一个接口都讨论:
text
到底叫 getAgentById 还是 queryAgent?
规则已经确定。
3. 测试更容易自动化
接口一旦规范化:
text
POST 创建
GET 查询
PUT 更新
DELETE 删除
自动测试工具更容易批量生成测试。
4. 文档更稳定
FastAPI 可以根据:
text
Router
Schema
Response Model
直接生成 API 文档。
接口定义本身就是文档的一部分。
5. 更适合 AI 编程
这一点尤其重要。
AI 不需要猜:
text
接口应该叫什么?
响应怎么包装?
错误应该怎么办?
分页怎么设计?
它只需要遵循规则。
五、把接口规范直接写进 AI 项目规则
只有架构设计还不够,如果希望 AI 长期遵守,就要把这些约束写进项目规则。
例如:
text
## API 设计规则
1. 遵循 RESTful 资源导向型 API 设计。
2. URL 必须使用名词,而非 CRUD 动词。
3. 在适当情况下使用资源名称的复数形式。
4. 使用 GET 方法读取资源。
5. 使用 POST 方法创建资源。
6. 使用 PUT 方法更新资源。
7. 使用 PATCH 方法进行部分状态更改。
8. 使用 DELETE 方法删除数据。
9. 所有常规 API 响应必须使用 ApiResponse 类型。
10. 分页响应必须使用 PageResult 类型。
11. 请求和响应数据必须使用 Pydantic schema。
12. 路由器不得返回任意字典结构。
13. 路由器仅处理 HTTP 相关请求。
14. 业务逻辑应放在 Service 类中。
15. 业务错误必须作为域异常抛出。
16. 单个路由器内部不得重复异常处理。
17. 在引入新端点之前,重用现有的 API 模式。
简单来说就是:
text
URL 只描述资源
↓
HTTP 方法表达动作
↓
Schema 定义输入输出
↓
ApiResponse 统一返回
↓
领域异常统一处理
↓
Router 不写业务逻辑
这样 AI 每次生成新接口之前,就已经知道:
不能重新设计一套接口风格。
六、提示词怎么写才真正有用?
不要只告诉 AI:
text
请帮我写一个 Agent CRUD 接口。
CRUD 即:
Create、Read、Update、Delete(创建、读取、更新、删除)
更好的提示词是:
text
请为 Agent 资源实现管理接口。
必须遵循项目现有 API 契约:
- RESTful 资源式 URL
- 复用 /api/v1/admin/agents 路径结构
- GET 查询
- POST 创建
- PUT 更新
- DELETE 删除
- 局部状态修改使用 PATCH
- 请求参数使用现有 Pydantic Schema
- 返回值统一使用 ApiResponse
- 分页使用 PageResult
- Router 只处理 HTTP 协议和依赖注入
- 所有业务逻辑放入 AgentService
- 业务异常使用已有领域异常体系
- 禁止在 Router 内重新定义异常返回格式
- 实现前先参考项目中已有 Agent、Tool、KnowledgeBase API 风格
这时候 AI 的任务就从:
"帮我设计并写一个接口。"
变成:
"按照已有合同施工。"
两者生成代码的稳定性完全不同。
七、正面产出:miniagent 已经形成的接口范式
把 miniagent) 当前真实代码组合起来,可以看到一套比较清晰的接口链路。

简单来看:
text
HTTP Request
│
▼
FastAPI Router
│
├── Path / Query
├── Pydantic Schema
├── Permission
└── HTTP Method
│
▼
Service
│
▼
Domain / Repository / Runtime
│
▼
Result
│
▼
ApiResponse
│
▼
HTTP Response
如果业务过程中发生异常:
text
Service
│
▼
Domain Exception
│
▼
Global Exception Handler
│
├── 400
├── 404
├── 409
└── 500
│
▼
ApiResponse
这几个部分都已经在 miniagent 中有真实实现:
- Router 使用
GET / POST / PUT / PATCH / DELETE描述操作。 - 输入输出通过 Pydantic Schema 建模。
- 所有普通响应统一使用
ApiResponse。 - 分页结果统一使用
PageResult。 - 业务逻辑从 Router 下沉到 Service。
- 领域异常由全局异常处理统一转换为 HTTP 状态码和响应结构。
- FastAPI 自动提供
/docs和/redoc接口文档入口。
最终形成的不是几个孤立接口,而是一套:
可预测、可复用、可扩展、可让 AI 持续遵守的接口设计范式。
写在最后
很多人第一次接触 RESTful,会觉得它只是:
text
GET 查
POST 增
PUT 改
DELETE 删
但真正进入工程实践后就会发现:
RESTful 的核心价值其实是统一语言。
而契约优先则进一步解决:
在代码实现之前,先把这套语言固定下来。
对于传统开发团队,这能降低沟通成本;对于 AI 编程,它的价值更大。
因为如果没有接口契约:
text
AI
↓
自己猜 URL
↓
自己猜 HTTP 方法
↓
自己猜参数
↓
自己猜返回结构
↓
自己猜异常处理
↓
项目逐渐出现多套接口风格
而建立接口规范之后:
text
AI
↓
读取 API Rules
↓
遵循 RESTful
↓
复用 Schema
↓
复用 ApiResponse
↓
调用 Service
↓
统一异常处理
AI 不再负责"发明接口",它只负责:
按照已有接口契约完成实现。
这正是架构约束 AI 编程最重要的思想之一:
先统一规则,再扩大生成能力。
当接口路径、HTTP 方法、输入输出、状态码和异常协议都稳定下来之后,AI 写得越快,项目反而越不容易失控。
这才是 RESTful 与契约优先 真正的工程价值。
开源代码
🪐祝您好运🪐