FastAPI 中在 OpenAPI 里声明额外 Responses:用 responses 参数完整描述状态码、媒体类型与响应 Schema
导读:本文以 FastAPI 官方文档《OpenAPI 中的额外 Responses》(additional-responses)为主体,系统讲解如何通过 path operation decorator 的
responses参数声明额外的状态码、媒体类型、描述与示例,让 API 文档完整反映接口的真实行为。读完本文,你可以掌握model键的用法、多媒体类型响应声明、response_model与responses的信息合并机制,以及基于**dict解包复用预定义响应的实战技巧,并能从源码层面理解 FastAPI 是如何把这些声明转换为 OpenAPI Schema 的。
1. 核心概念:为什么需要声明额外的 Responses
在 OpenAPI 中,一个接口的 responses 字段用来描述该接口所有可能的响应——不只是成功的 200,还包括 404、403、重定向 302 等额外状态码。
通过 FastAPI 的 responses 参数声明的额外 responses,会直接进入生成的 OpenAPI schema,因此也会自动显示在交互式 API 文档(/docs)中。但有一个关键前提,官方文档用加粗的警告强调过:
- 这些额外 responses 只是对文档和 Schema 的声明,FastAPI 不会替你自动生成这些响应;
- 对于声明的每个额外 response,你需要自己在 endpoint 中显式返回一个
Response实例(如JSONResponse、FileResponse),并携带对应的 status code 和 content。
这一警告同样体现在 additional-responses 文档开头的注意事项中,是一个相当 advanced 的主题——初学 FastAPI 时未必用得上,但在描述复杂接口行为时非常有用。
2. 用 model 键声明带 Pydantic 模型的额外 Response
给 path operation decorator(如 @app.get(...))传入 responses 参数。从源码看,该参数最终保存在 APIRoute 上:APIRoute 字段定义 与 路由注册时的赋值 均为 responses: dict[int | str, dict[str, Any]],其中:
- keys 是每个 response 的 status code(可以是整数如
404,也可以是字符串如"default"); - values 是描述该 response 信息的
dict。
每个 response dict 中可以包含一个 model 键,其值是一个 Pydantic model,用法与 response_model 类似。FastAPI 会取出这个 model、为它生成 JSON Schema,并将其放到 OpenAPI 的合适位置。
以声明一个带 404 状态码和 Message 模型的额外 response 为例(完整示例见 tutorial001_py310.py):
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from pydantic import BaseModel
class Item(BaseModel):
id: str
value: str
class Message(BaseModel):
message: str
app = FastAPI()
@app.get("/items/{item_id}", response_model=Item, responses={404: {"model": Message}})
async def read_item(item_id: str):
if item_id == "foo":
return {"id": "foo", "value": "there goes my hero"}
return JSONResponse(status_code=404, content={"message": "Item not found"})
注意最后两行:成功时返回普通 dict(由 response_model=Item 序列化),失败时必须直接返回 JSONResponse(status_code=404, ...)——这正是第 1 节警告的具体体现。
2.1 model 键不是 OpenAPI 的一部分
官方文档特别注明:model 键不属于 OpenAPI 规范,它只是 FastAPI 的扩展语法糖。FastAPI 会从该键取出 Pydantic model、生成 JSON Schema,并把它放到正确的结构中,即:
content键(值是一个dict),其中包含:- 一个 media type 键,如
application/json(值又是一个dict),其中包含:schema键,其值就是 model 的 JSON Schema——这才是"正确的位置"。- 而且 FastAPI 不会内联展开该 Schema,而是在全局 JSON Schemas(
components.schemas)中生成定义,然后在此处插入一个$ref引用。这样做的好处是:其他应用和客户端工具可以直接复用这些 Schema,更好的代码生成工具也能据此生成类型化的客户端代码。
- 而且 FastAPI 不会内联展开该 Schema,而是在全局 JSON Schemas(
- 一个 media type 键,如
这段逻辑的实现位于 fastapi/openapi/utils.py:在遍历 route.responses 时,FastAPI 会先 pop("model") 把 model 键剥离,再通过 get_schema_from_model_field 生成 schema 并以 $ref 形式挂载到对应 media type 下(详见第 4 节源码剖析)。
2.2 生成的 OpenAPI responses 长什么样
针对上述 path operation,FastAPI 在 OpenAPI 中生成的 responses 为(注意 404 与 200 中的 schema 都是全局引用):
{
"responses": {
"404": {
"description": "Additional Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Message"
}
}
}
},
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Item"
}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
其中两个细节值得注意:
- 当你在
responses中只给了{"model": Message}而没有给description时,FastAPI 会自动补一个默认的"Additional Response"描述(源码中的回退链为:显式description→ 已存在的 description → 状态码标准文本 → 兜底文案"Additional Response",见 utils.py 描述回退逻辑); - 由于该路径操作带有请求参数,FastAPI 还会自动补上
422(Validation Error)响应,除非你已经声明了422、4XX或default之一(见 utils.py 422 自动注入逻辑)。
对应的全局 Schemas 定义(components.schemas)为:
{
"components": {
"schemas": {
"Message": {
"title": "Message",
"required": [
"message"
],
"type": "object",
"properties": {
"message": {
"title": "Message",
"type": "string"
}
}
},
"Item": {
"title": "Item",
"required": [
"id",
"value"
],
"type": "object",
"properties": {
"id": {
"title": "Id",
"type": "string"
},
"value": {
"title": "Value",
"type": "string"
}
}
},
"ValidationError": {
"title": "ValidationError",
"required": [
"loc",
"msg",
"type"
],
"type": "object",
"properties": {
"loc": {
"title": "Location",
"type": "array",
"items": {
"type": "string"
}
},
"msg": {
"title": "Message",
"type": "string"
},
"type": {
"title": "Error Type",
"type": "string"
}
}
},
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {
"$ref": "#/components/schemas/ValidationError"
}
}
}
}
}
}
}
3. 源码剖析:model 键是如何被处理的
注册路由时,fastapi/routing.py 会遍历 route.responses:
- 先断言每个 value 必须是
dict("An additional response must be a dict"); - 若其中包含
model,则断言该 status code 允许携带响应体(例如204、304这类无 body 的状态码不能用model),随后用create_model_field为该模型创建一个名为Response_{status_code}_{route.unique_id}的序列化字段,存入route.response_fields; - 生成 OpenAPI 时(fastapi/openapi/utils.py),FastAPI 对
route.responses逐项copy.deepcopy,pop("model")后:- 把 status code 转为字符串键并大写;
"DEFAULT"会被特殊处理为小写"default"(这是 OpenAPI 中表示"其他所有状态码"的合法键); - 若该状态码在
route.response_fields中有对应字段(即声明了model),则生成 schema 并deep_dict_update合并进content.{media_type}.schema;media type 的取值为路由响应类的 media type,若无则回退为application/json; - 最后用
deep_dict_update把处理后的 response dict 深合并进operation.responses中对应状态码的位置,并保证description始终存在。
- 把 status code 转为字符串键并大写;
这也解释了第 2 节两个 JSON 示例中 $ref 的来源:Message 模型被注册为全局组件,404 响应只持有引用。
4. 为主响应声明额外的 Media Types
同一个 responses 参数还可以用来为主响应(200)声明不同的 media type。例如声明 path operation 可能返回 JSON object(media type application/json)或 PNG 图像:
from fastapi import FastAPI
from fastapi.responses import FileResponse
from pydantic import BaseModel
class Item(BaseModel):
id: str
value: str
app = FastAPI()
@app.get(
"/items/{item_id}",
response_model=Item,
responses={
200: {
"content": {"image/png": {}},
"description": "Return the JSON item or an image.",
}
},
)
async def read_item(item_id: str, img: bool | None = None):
if img:
return FileResponse("image.png", media_type="image/png")
else:
return {"id": "foo", "value": "there goes my hero"}
(完整示例见 tutorial002_py310.py。)
两个来自官方文档的注意事项:
- 图像必须通过
FileResponse直接返回,而不能返回一个Image对象之类的东西再指望 FastAPI 帮你序列化——200声明里的application/json分支由response_model自动覆盖,而image/png分支只是 Schema 层面的声明,实际字节流由你返回的FileResponse提供; - 关于 media type 的默认规则:只要你没有在
responses参数中显式指定其他 media type,FastAPI 会假定该响应的 media type 与主响应类相同(默认即application/json)。但如果你指定了一个 media type 为None的自定义响应类,FastAPI 会对带model的额外 response 回退使用application/json——这与 openapi/utils.py 中的取值逻辑 完全一致:media_type = route_response_media_type or "application/json"。
5. 合并来自多处的 Response 信息
响应信息可以来自多个来源并自动合并:response_model、status_code、responses 参数。例如用默认的 200(或自定义 status code)声明 response_model,同时通过 responses 为同一个 200 响应补充 OpenAPI 层面的额外信息;对 404 则同时使用 Pydantic model 和自定义 description。
FastAPI 会保留 responses 中的额外信息,并将其与 model 生成的 JSON Schema 合并。示例(完整代码见 tutorial003_py310.py):
@app.get(
"/items/{item_id}",
response_model=Item,
responses={
404: {"model": Message, "description": "The item was not found"},
200: {
"description": "Item requested by ID",
"content": {
"application/json": {
"example": {"id": "bar", "value": "The bar tenders"}
}
},
},
},
)
async def read_item(item_id: str):
if item_id == "foo":
return {"id": "foo", "value": "there goes my hero"}
else:
return JSONResponse(status_code=404, content={"message": "Item not found"})
合并后的 200 响应最终同时拥有:response_model=Item 带来的 schema: {"$ref": "#/components/schemas/Item"}、responses 中提供的 description,以及 content 中的 example。这正是 deep_dict_update 深合并的结果。
该行为有测试用例背书:test_tutorial003.py 使用 inline_snapshot 精确断言了 /openapi.json 的完整结构——404 携带 Message 的 $ref 与描述 "The item was not found",200 携带 Item 的 $ref、描述 "Item requested by ID" 和示例 {"id": "bar", "value": "The bar tenders"},同时 422 自动注入。仓库中 test_tutorial001.py 至 test_tutorial004.py 分别覆盖了本节前面 4 个示例的运行时行为与 OpenAPI 输出。
官方文档中还附有一张交互式 API 文档的截图(见本文开头的配图),展示了上述合并信息在 /docs 页面中的实际呈现效果。
6. 复用预定义 Responses 与自定义 Responses 的组合
实际项目中,你往往希望维护一组通用的、可跨多个 path operation 复用的预定义 responses(如 404/403/302),再在单个接口上叠加自定义项。官方文档给出的方案是 Python 的 **dict_to_unpack 字典解包技巧:
old_dict = {
"old key": "old value",
"second old key": "second old value",
}
new_dict = {**old_dict, "new key": "new value"}
new_dict 会同时拥有 old_dict 的全部键值对和新加的键值对:
{
"old key": "old value",
"second old key": "second old value",
"new key": "new value",
}
把这个技巧用到 path operations 上(完整示例见 tutorial004_py310.py):
from fastapi import FastAPI
from fastapi.responses import FileResponse
from pydantic import BaseModel
class Item(BaseModel):
id: str
value: str
responses = {
404: {"description": "Item not found"},
302: {"description": "The item was moved"},
403: {"description": "Not enough privileges"},
}
app = FastAPI()
@app.get(
"/items/{item_id}",
response_model=Item,
responses={**responses, 200: {"content": {"image/png": {}}}},
)
async def read_item(item_id: str, img: bool | None = None):
if img:
return FileResponse("image.png", media_type="image/png")
else:
return {"id": "foo", "value": "there goes my hero"}
这里 404、302、403 三个通用响应被定义在模块级变量 responses 中,装饰器里通过 {**responses, 200: {...}} 一次性合并了预定义项与本接口的 image/png 附加声明。若某个键重复,后出现的键值对会覆盖先出现的——即单个接口的自定义项优先。
值得一提的是,这种"预定义 + 叠加"的模式在 FastAPI 中是框架级内置能力:APIRouter 与 include_router 本身就支持 responses 参数,并且按 Router 的 responses 在前、被 include 方的 responses 在后 的顺序做 **{**parent, **child} 合并(见 APIRouter 的合并逻辑 和 include_router 的合并)。因此你既可以在 APIRouter(prefix=..., responses=common_responses) 上统一声明通用响应,也可以在 app.include_router(router, responses=...) 时再叠加一层,最后由各 path operation 的 responses 做最细粒度的覆盖——与本文示例的 **dict 解包技巧互为表里。
7. responses 里到底能写什么
responses 中每个状态码对应的 dict,可以包含 OpenAPI 规范 Responses Object / Response Object 章节中定义的几乎任何字段。OpenAPI 3.1 规范的 Response Object 主要字段包括:
description:响应的描述(缺省时 FastAPI 按第 2.2 节的回退链自动补齐);headers:该响应可能携带的额外响应头;content:核心字段,一个按 media type(如application/json、image/png)划分的dict,每个 media type 下可声明schema(JSON Schema)、example、examples等;links:指向基于该响应结果的后续操作的链接定义。
也就是说,除了 FastAPI 扩展的 model 键之外,你可以在每个 response 的 dict 里直接写上述规范的任意内容。同时请注意 responses 的 key 除了具体状态码(404、302 等)外,还支持 "default"(源码中同时兼容整数写法,"DEFAULT" 会被规范化为 "default"),用于描述"其他所有状态码"的通用响应。
8. 实践清单与注意事项
结合官方文档与仓库源码,使用 responses 参数时的要点:
- 声明与实际返回必须一致:为每个声明的额外状态码,在 endpoint 中显式返回
JSONResponse、FileResponse等Response实例,带上正确的 status code 和 content; model键仅用于生成 Schema:它不是 OpenAPI 字段,且仅适用于允许携带响应体的状态码(无 body 的状态码会触发断言错误);- media type 默认规则:不显式声明时与主响应类一致(默认
application/json);自定义响应类 media type 为None时,带model的额外响应回退到application/json; - 合并语义是深合并:
response_model、status_code与responses三处的信息会由 FastAPI 深合并(deep_dict_update),同一路径操作内的responses项优先补充/覆盖; - 复用模式:模块级预定义 dict +
**解包,或APIRouter/include_router的responses参数,均可实现"公共响应 + 个性响应"的分层组织; - 验证手段:用
TestClient请求/openapi.json并用inline_snapshot之类工具对完整结构做快照断言,仓库中的 tests/test_tutorial/test_additional_responses/ 提供了 4 组可直接参考的测试写法。
9. 参考文件
| 内容 | 路径 |
|---|---|
| 原始文档(印地语版,本文主体依据) | additional-responses.md |
示例 1:model 键声明 404 响应 |
tutorial001_py310.py |
示例 2:为主响应声明 image/png media type |
tutorial002_py310.py |
示例 3:response_model 与 responses 信息合并 |
tutorial003_py310.py |
示例 4:**dict 解包复用预定义 responses |
tutorial004_py310.py |
路由注册与 responses 校验、model 字段创建 |
fastapi/routing.py |
OpenAPI 生成:responses 深合并、model 剥离、422 注入 |
fastapi/openapi/utils.py |
| 对应测试(OpenAPI 快照断言) | tests/test_tutorial/test_additional_responses/ |
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
