← Back to 文字

请求响应模型设计

本文来自《AI 应用开发课程》月份 1 课程文档,已整理为网站文章版本。

学习目标

学完本节后,你应当能够:

  • 用 Pydantic 为 FastAPI 设计请求模型和响应模型。
  • 明确月份 1 API 的最小接口形状。
  • 理解错误响应为什么也要建模。

前置知识

  • 已完成 Pydantic 模块
  • 已能启动 FastAPI 最小服务

1. 月份 1 的 API 目标

本月只要求稳定实现 3 个接口:

  • GET /health
  • POST /chat
  • POST /tools/run

所以你的请求响应模型必须围绕这 3 个接口收束,不要过度设计。

2. 统一模型建议

from typing import Any, Literal

from pydantic import BaseModel, Field


ChatRole = Literal["system", "user", "assistant", "tool"]


class ChatMessage(BaseModel):
    role: ChatRole
    content: str


class ChatRequest(BaseModel):
    messages: list[ChatMessage]
    model: str = "deepseek-chat"
    temperature: float = Field(default=0.2, ge=0, le=2)


class ChatResponse(BaseModel):
    answer: str
    model: str


class ToolRunRequest(BaseModel):
    tool_name: str
    arguments: dict[str, Any]


class ToolRunResponse(BaseModel):
    tool_name: str
    success: bool
    output: str


class ErrorResponse(BaseModel):
    error_code: str
    message: str

3. 为什么错误响应也要有模型

错误如果没有统一结构,客户端和日志都很难稳定处理。

例如:

{
  "error_code": "TOOL_NOT_FOUND",
  "message": "未找到工具 summarize_diff"
}

这比直接返回一段不稳定字符串更适合系统协作。

4. 请求模型设计原则

  • 字段尽量少而必要
  • 默认值要合理
  • 约束能写就写,例如 temperature 范围
  • 名称要和核心业务一致

5. 响应模型设计原则

  • 优先返回调用方真正需要的数据
  • 不要暴露内部实现细节
  • 成功结构和错误结构分开

6. 实操任务

  1. app/models.py 中定义上述模型
  2. /chat 路由里使用 ChatRequestChatResponse
  3. /tools/run 路由里使用 ToolRunRequestToolRunResponse

7. 自测题

  1. 为什么请求模型和响应模型最好分开定义?
  2. 为什么 temperature 应该做范围约束?
  3. 为什么错误响应不能只返回“请求失败”四个字?

8. 作业与验收

验收标准:

  • 核心接口都有请求响应模型
  • 错误结构有统一格式
  • 你能解释每个模型服务于哪个接口

9. 常见错误

  • 一个模型同时兼任请求和响应
  • arguments 字段没有说明用途
  • 错误响应结构和正常响应结构混用

10. 本章与前文关系

上一章起的是“服务壳子”,这一章要往壳子里放“协议结构”。这一步的意义在于:

  • 让客户端知道要发什么
  • 让服务端知道要收什么
  • 让错误也有稳定结构

没有这一层,后面 /chat/tools/run 很快就会退化成“接口能跑,但不知道协议是否清晰”。

11. 本章在研发助手项目中的位置

综合项目里的:

  • CLI 输入
  • FastAPI 请求体
  • LLM service 输入
  • Tool service 输出

都会围绕这些统一模型做映射。你现在设计的不是“接口字段名”,而是项目不同层之间的共同契约。

12. 为什么请求模型和响应模型必须分开

这是一个很基础但很容易被忽略的工程原则。

请求模型关心输入意图

例如:

  • 传了哪些消息
  • 用什么模型
  • 温度是多少

响应模型关心输出结果

例如:

  • 最终回答是什么
  • 使用了哪个模型
  • 工具是否成功执行

如果你把两者混在一个模型里,后面维护会非常混乱。

13. 错误示例 vs 正确示例

错误示例:一个模型包打天下

class ChatData(BaseModel):
    messages: list[dict]
    answer: str | None = None

问题:

  • 输入和输出混在一起
  • 字段边界不清晰
  • 文档自动生成效果也会变差

正确示例:按职责拆开

class ChatRequest(BaseModel):
    ...


class ChatResponse(BaseModel):
    ...

这会让你的协议在结构上更稳定。

14. 进一步解释 ToolRunRequestToolRunResponse

很多人对 /tools/run 的理解会有偏差,以为它只是“传一段文本,看结果”。其实它更像一个结构化执行接口。

ToolRunRequest

回答的问题是:

  • 我现在要执行哪个工具?
  • 工具参数是什么?

ToolRunResponse

回答的问题是:

  • 执行成功了吗?
  • 返回内容是什么?

如果你不把这两个问题拆开,工具接口通常会变得难以维护。

15. 一个更接近工程的增强点

你可以为错误响应加上更明确的错误码:

class ErrorResponse(BaseModel):
    error_code: str
    message: str

这看似简单,但价值很大,因为:

  • CLI 可以据此打印更明确提示
  • API 调用方可以据此分支处理
  • 日志里更好统计错误类型

16. 调试与排错:本章最常见问题

问题一:请求数据和模型字段对不上

这时 422 往往不是坏事,而是在提前告诉你协议没对齐。

问题二:错误响应结构没有统一

这会让调用方很难写稳定处理逻辑。

问题三:模型字段命名不断漂移

月份 1 最需要的是稳定命名,不要每个文件都写一套自己的字段名。

17. 本章完成后你应该具备的能力

完成本章后,你应当做到:

  1. 能解释请求模型、响应模型和错误模型的区别。
  2. 能围绕 /chat/tools/run 设计清晰 schema。
  3. 能理解这些 schema 不只是给 FastAPI 用,也是给项目内部不同层做统一契约。

18. 如果你卡在这里,先回看哪几章

19. 从本章过渡到下一章的桥接说明

接下来进入 03-chat与tools接口实现.md

你现在已经有:

  • 服务壳子
  • 请求响应模型

下一步就是把它们真正接起来:让 /chat 调用聊天 service,让 /tools/run 调用工具 service,并保持路由层、业务层和 client 层边界清晰。

Fin