首页
/ FastAPI 进阶实战:用 `responses` 参数声明额外响应,自定义 OpenAPI 的 responses 结构

FastAPI 进阶实战:用 `responses` 参数声明额外响应,自定义 OpenAPI 的 responses 结构

2026-09-06 14:01:09作者:卓艾滢Kingsley

本文基于 FastAPI 官方文档 Advanced 章节的 Additional Responses 主题,讲解如何通过 path operation decoratorresponses 参数为 API 声明额外状态码、额外媒体类型与自定义描述,并完整呈现生成的 OpenAPI 结构。读完本文,你将掌握 model 键的用法、多种响应信息来源(response_modelstatus_coderesponses)的合并规则,以及基于源码的底层实现原理,能够写出可被 API 文档与代码生成工具直接消费的完整响应契约。

响应信息合并后在 API 文档中的展示效果:404 显示自定义描述,200 显示自定义 example

一、为什么需要声明额外响应

这是一个相对进阶的话题。如果你是刚开始使用 FastAPI,短期内可能用不到它;但当你需要让 OpenAPI Schema(以及交互式 API 文档)如实描述端点所有可能的响应——而不只是成功的那一个——时,它就是标准工具。

responses 参数允许你声明额外的响应:带额外状态码、媒体类型、描述等信息。这些额外响应会被写入 OpenAPI Schema,从而出现在 API 文档中。

它的取值是一个 dict

responses: dict[int | str, dict[str, Any]] | None = None
  • 是响应的状态码(如 404302),可以是 int 也可以是 str
  • 是对应响应的 dict,结构遵循 OpenAPI 的 Response Object(可含 descriptionheaderscontentlinks 等键),FastAPI 额外支持一个非 OpenAPI 的 model 键。

一个必须牢记的前提:对这些额外响应,你需要直接返回一个 Response 对象(例如 JSONResponseFileResponse),并自行设置状态码和内容。responses 参数只负责"声明契约",FastAPI 不会替你拦截或自动生成这些响应。

二、带 model 键的额外响应

path operation decorator 中传入 responses 参数,每个响应的 dict 里可以有一个 model 键,其值是 Pydantic 模型,用法与 response_model 完全一致。FastAPI 会拿到该模型,生成其 JSON Schema,并放到 OpenAPI 的正确位置。

例如,为 /items/{item_id} 声明一个状态码 404、使用 Pydantic 模型 Message 的额外响应(完整文件见 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"})

注意两处细节:

  1. 必须直接返回 JSONResponse——model 只影响 OpenAPI 声明,运行时行为由你返回的 Response 决定;
  2. model 键不属于 OpenAPI。FastAPI 会从这里取出 Pydantic 模型,生成 JSON Schema 后放在正确位置:
    • content 键,其值是另一个 JSON 对象(dict),包含:
      • 媒体类型键(例如 application/json),其值又是一个 JSON 对象,包含:
        • schema 键,其值就是模型的 JSON Schema——这就是正确的位置。
          • FastAPI 不会直接内联该 Schema,而是放一个指向 OpenAPI 全局 Schemas(components/schemas)的引用($ref)。这样其他应用和客户端可以直接复用这些 JSON Schema,获得更好的代码生成工具支持等。

源码视角:model 键如何被处理

model 键的处理分为两个阶段,可以从源码中逐一印证。

阶段一:路由注册时提取模型fastapi/routing.py):

response_fields = {}
for additional_status_code, response in route.responses.items():
    assert isinstance(response, dict), "An additional response must be a dict"
    model = response.get("model")
    if model:
        assert is_body_allowed_for_status_code(additional_status_code), (
            f"Status code {additional_status_code} must not have a response body"
        )
        response_name = f"Response_{additional_status_code}_{route.unique_id}"
        response_field = create_model_field(
            name=response_name, type_=model, mode="serialization"
        )
        response_fields[additional_status_code] = response_field

这里有三点值得注意:

  • 每个响应的值必须是 dict,否则直接断言失败;
  • 如果 model 对应的状态码是不允许响应体的(如 204304304 系列),会抛出断言错误——即 model 只能配在允许响应体的状态码上
  • 提取出的模型被创建为序列化模式的 ModelField,存入 route.response_fields,供 OpenAPI 生成阶段使用。

阶段二:OpenAPI 生成时合并fastapi/openapi/utils.py):

if route.responses:
    operation_responses = operation.setdefault("responses", {})
    for (
        additional_status_code,
        additional_response,
    ) in route.responses.items():
        process_response = copy.deepcopy(additional_response)
        process_response.pop("model", None)
        status_code_key = str(additional_status_code).upper()
        if status_code_key == "DEFAULT":
            status_code_key = "default"
        openapi_response = operation_responses.setdefault(
            status_code_key, {}
        )
        ...
        field = route.response_fields.get(additional_status_code)
        if field:
            additional_field_schema = get_schema_from_model_field(field=field, ...)
            media_type = route_response_media_type or "application/json"
            additional_schema = (
                process_response.setdefault("content", {})
                .setdefault(media_type, {})
                .setdefault("schema", {})
            )
            deep_dict_update(additional_schema, additional_field_schema)
        description = (
            process_response.get("description")
            or openapi_response.get("description")
            or status_text
            or "Additional Response"
        )
        deep_dict_update(openapi_response, process_response)
        openapi_response["description"] = description

从源码结构看,可以确认几个行为细节:

  • model 键在生成 Schema 后被 pop 掉,不会进入最终 OpenAPI 输出
  • 状态码键会被转为大写字符串,特殊值 DEFAULT 会被转成 OpenAPI 的 default 键;
  • 媒体类型推断规则media_type = route_response_media_type or "application/json"——若路由指定了带媒体类型的自定义响应类,则沿用之;否则(例如自定义响应类的媒体类型是 None)默认使用 application/json
  • 描述回退链:你在 responses 中显式写的 description → 主响应已有的 description(如 "Successful Response")→ HTTP 标准状态短语 → 兜底的 "Additional Response"
  • 最终通过 deep_dict_update深度合并,所以 responses 里写的 descriptionexampleheaders 等信息会与主响应的 Schema 信息共存于同一个响应对象中,而不是互相覆盖。

三、生成的 OpenAPI 结构详解

以第二节的示例端点为例,FastAPI 生成的 responses 部分为:

{
    "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"
                    }
                }
            }
        }
    }
}

其中:

  • 404 是你在 responses 中声明的,因为没写 description,走了回退链的兜底值 "Additional Response"
  • 200 来自 response_model=Item
  • 422 是 FastAPI 自动追加的验证错误响应——从 fastapi/openapi/utils.py 可以看到,只有当路由有参数或请求体、且 responses 中尚未存在 4224XXdefault 任一键时,才会注入该默认项。相关测试见 test_additional_responses_default_validationerror.py

对应的 JSON Schema 被放到 OpenAPI 的 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"
                        }
                    }
                }
            }
        }
    }
}

这印证了前文的结论:Message 模型与 Item 一样,以 $ref 的形式被提升到全局 Schema 区,供客户端与代码生成工具直接引用。

四、为主响应添加额外媒体类型

同一个 responses 参数也可以用来为同一个主响应声明多种媒体类型。例如声明 path operation 既可以返回 JSON 对象(application/json),也可以返回 PNG 图片(image/png)(完整文件见 tutorial002_py310.py):

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"}

这里有两条注意点:

  1. 图片必须用 FileResponse 直接返回——与 JSONResponse 同理,额外媒体类型下的真实响应完全由你返回的 Response 决定;
  2. 媒体类型的默认推断:除非你在 responses 中显式指定了不同的媒体类型,FastAPI 会假设额外响应与主响应类使用相同媒体类型(默认 application/json);但如果你指定了一个媒体类型为 None 的自定义响应类,FastAPI 会对任何带有关联 model 的额外响应使用 application/json。这与源码中 media_type = route_response_media_type or "application/json" 的逻辑一一对应(见 fastapi/openapi/utils.py)。

上例中 200 响应的 content 会同时包含 application/json(来自 response_model=Item)与 image/png 两个媒体类型键,description 则被你的自定义文案替换为 "Return the JSON item or an image."

五、组合多来源信息:response_model + status_code + responses

你可以把来自多个地方的响应信息组合起来,包括 response_modelstatus_coderesponses 参数。例如:先声明 response_model(使用默认 200 状态码,或按需自定义),然后在 responses 中为同一个响应直接补充 OpenAPI 层面的额外信息。

FastAPI 会保留 responses 中的额外信息,并与模型的 JSON Schema 合并。例如:声明一个状态码 404、使用 Pydantic 模型且带自定义 description 的响应;同时声明一个状态码 200、使用 response_model 但包含自定义 example 的响应(完整文件见 tutorial003_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, "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"})

这些信息会被全部合并进 OpenAPI 并展示在 API 文档中(即文首配图所示效果):

  • 404description 显示为 "The item was not found"content 下挂 Message 模型的 $ref
  • 200description 显示为 "Item requested by ID",同时保留 Item 模型的 Schema 引用,并额外带上你提供的 example 对象——这正体现了前文提到的 deep_dict_update 深度合并:example 与自动生成的 schema 在同一个媒体类型对象内并存,互不冲突。

六、复用预定义响应:dict 解包与 Router 级 responses

如果你有若干预定义响应需要应用到多个 path operation 上,同时每个端点又要叠加各自的自定义响应,可以使用 Python 的 dict 解包技巧 **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 operation,即可复用预定义响应并叠加自定义响应(完整文件见 tutorial004_py310.py):

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"}

这里顶层的 responses 字典集中定义了 404/302/403 三个共享响应,每个端点在装饰器中解包它,再按需追加自己的 200 媒体类型声明。

更进一步,FastAPI 在 Router 层面原生支持同样的合并机制,甚至不需要手写解包。从源码结构看(fastapi/routing.py):

responses = responses or {}
combined_responses = {**self.responses, **responses}
  • APIRouter 构造时就可以接收 responses 参数,作为该路由下所有端点的默认额外响应;
  • include_router 时,被包含路由的 responses 会与父级路由的 responses{**self.responses, **responses} 顺序合并,子路由的同状态码声明覆盖父级
  • 路由与 Router 的响应最终在构建 APIRoute 时按 {**include_context.responses, **route.responses} 合并(见 fastapi/routing.py)。

也就是说,"预定义响应"既可以像上例那样在模块级 dict 中手动解包复用,也可以直接挂在 APIRouter(prefix=..., responses=...) 上由框架自动合并——后者在大型应用中更结构化。

七、responses 值中可以放什么:OpenAPI 规范速查

responses 中每个响应的值,就是 OpenAPI 3.1 规范中的 Response Object(而整个 responses 对应 Responses Object)。按照规范,你可以直接在这些 dict 中包含:

  • description:响应描述;
  • headers:额外响应头声明;
  • content:按媒体类型区分的响应内容,其中声明不同媒体类型及其 JSON Schema;
  • links:基于响应内容链接到其他操作。

此外,键(状态码)除了具体数值外,还可以使用范围键(如 4XX5XX)或 default 键来兜底,FastAPI 在生成 OpenAPI 时对 default 做了大小写归一化处理(见 fastapi/openapi/utils.py)。

八、实践要点清单

综合文档与源码,使用 responses 参数时请牢记:

  1. 运行时契约由你负责:为额外响应返回的内容,必须直接返回 JSONResponseFileResponseResponse 对象,并自行设置状态码与内容;responses 参数只影响 OpenAPI 声明与 API 文档展示;
  2. model 是 FastAPI 扩展键,不属于 OpenAPI:它会被提取、生成 JSON Schema 后从输出中移除;且只能搭配允许响应体的状态码使用,否则路由注册时会断言失败;
  3. 信息是深度合并而非覆盖responses 中为同一状态码写的 descriptionexampleheaders 会与 response_model/status_code 生成的 Schema 信息合并到同一个 OpenAPI 响应对象中;
  4. 422 自动注入且可抑制:只要路由存在参数或请求体,且你没有声明 4224XXdefault 任一键,FastAPI 会自动追加验证错误响应;声明其中任一键即可接管这一默认行为;
  5. 媒体类型有默认值:未显式指定时,额外响应沿用主响应类的媒体类型;主响应类媒体类型为 None 且额外响应带 model 时,使用 application/json
  6. 描述有回退链:自定义 description → 主响应描述 → HTTP 标准状态短语 → "Additional Response",因此不写 description 也不会缺少必填字段。

相关测试覆盖了默认 422 注入、自定义模型、Router 级响应等多种组合场景,可作为行为基准参考,目录见 tests/test_additional_responses_default_validationerror.pytests/test_additional_responses_custom_model_in_callback.pytests/test_additional_responses_router.py;教程源码集中于 docs_src/additional_responses/,本文对应的英文原文档为 docs/en/docs/advanced/additional-responses.md

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388