FastAPI 面试必会:ASGI、Pydantic、Depends 和异步请求链路一次讲清

在这里插入图片描述

开场:别只把 FastAPI 背成“Python 版 Spring Boot”

前几篇我们已经把 Python 对象模型、装饰器、生成器、上下文管理器、GIL、线程、进程、协程和 asyncio 讲完了。

今天终于可以接到后端框架:FastAPI。

很多 Java 后端刚看 FastAPI,会有一个很自然的类比:

Spring Boot:
Controller -> 参数绑定 -> Validation -> Service -> Response

FastAPI:
@app.get -> 参数绑定 -> Pydantic -> Depends -> Response

这个类比能帮你快速入门,但如果只停在这里,面试很容易被追问卡住:

  • FastAPI、Starlette、Uvicorn、Pydantic 分别负责什么?
  • ASGI 和 WSGI 的区别到底在哪里?
  • 为什么函数参数写个类型,框架就知道从 path、query、body 里取值?
  • Depends 是不是和 Spring 的 @Autowired 一样?
  • async def 一定比 def 快吗?
  • 在 Agent 后端里,为什么 FastAPI 经常和 SSE、WebSocket、流式响应一起出现?

这篇不写 Hello World,也不按 API 清单背。

我们直接抓住一条主线:

FastAPI 的核心不是“装饰器写路由”,而是把 Python 类型标注、ASGI 异步协议、Pydantic 数据校验和函数式依赖注入串成一条请求处理链路。

读完以后,你应该能把 FastAPI 讲成一套后端机制,而不是一组语法糖。

一、先看四层分工:Uvicorn、ASGI、Starlette、FastAPI、Pydantic

先把几个名字拆开。

在这里插入图片描述

1. Uvicorn:负责把网络请求变成 ASGI 调用

Uvicorn 是 ASGI Web Server。

它更像 Java 里的 Tomcat、Netty 服务入口,负责监听端口、接收 HTTP/WebSocket 连接,然后按照 ASGI 规范调用应用。

ASGI 应用的底层形状大概是这样:

async def app(scope, receive, send):
    ...

三个参数很关键:

  • scope:连接信息,比如协议类型、路径、方法、请求头。
  • receive:从客户端接收消息的异步通道。
  • send:向客户端发送响应消息的异步通道。

所以 ASGI 的重点不是“异步”这个词本身,而是它把 Web Server 和应用框架之间的交互标准化了。

Uvicorn 可以跑 FastAPI,也可以跑其他 ASGI 框架。FastAPI 本身不负责监听 socket。

2. ASGI:异步 Web 应用和服务器之间的协议

以前 Python Web 常讲 WSGI。

WSGI 应用是同步 callable,适合传统短请求响应。它对 WebSocket、长连接、流式响应这类场景支持得不自然。

ASGI 则把连接抽象成 scope + receive + send。这让框架可以处理:

  • 普通 HTTP 请求。
  • WebSocket 长连接。
  • 长轮询。
  • 流式响应。
  • 需要等待网络 IO 的异步任务。

这也是 FastAPI 特别适合大模型应用后端的原因之一。LLM 接口经常不是“请求进来,算完一次性返回”,而是边生成边返回、连接保持一段时间、甚至双向通信。

3. Starlette:负责 Web 层能力

FastAPI 建在 Starlette 之上。

Starlette 提供的是 Web 框架底座,包括:

  • 路由。
  • Request / Response 对象。
  • Middleware。
  • WebSocket。
  • 静态文件。
  • 异常处理。
  • 测试客户端。
  • 线程池处理同步端点等。

你可以把 Starlette 理解为 FastAPI 的 Web 内核。

FastAPI 不是从零实现所有 HTTP 能力,而是在 Starlette 的基础上加了更强的 API 开发体验。

4. Pydantic:负责数据校验、类型转换和 schema

Pydantic 是数据校验库。

FastAPI 用它来处理请求体、响应模型、类型转换、错误信息和 JSON Schema。

比如你写:

from pydantic import BaseModel

class CreateUserRequest(BaseModel):
    name: str
    age: int

FastAPI 不只是把它当成普通 class。它会把请求 JSON 交给 Pydantic:

  • 字段是否存在。
  • 类型能否转换。
  • 是否满足约束。
  • 错误应该定位到哪个字段。
  • OpenAPI 文档里的 schema 怎么生成。

截至 2026-06-09,Pydantic 官方文档页面显示的版本是 v2.13.4。现在写 FastAPI 文章和项目时,要默认按 Pydantic v2 的思路理解,不要再把旧版 Configorm_mode 等写法当成唯一标准。

5. FastAPI:把类型、路由、依赖和文档串起来

FastAPI 真正做的是胶水层和增强层:

  • 读取路由函数签名。
  • 根据参数位置和声明判断数据来源。
  • 调 Pydantic 做转换和校验。
  • 解析 Depends 依赖图。
  • 处理 def / async def 调用方式。
  • 生成 OpenAPI 和交互式文档。
  • 复用 Starlette 的 Web 能力。

一句话总结:

Uvicorn 管网络入口,ASGI 定通信协议,Starlette 管 Web 机制,Pydantic 管数据契约,FastAPI 把这些能力组合成面向 API 的开发模型。

二、一次请求在 FastAPI 里怎么走?

面试里如果被问“讲一下 FastAPI 请求生命周期”,不要只说“先到路由,再到函数”。

可以按下面这条链路回答。

在这里插入图片描述

第一步:Uvicorn 接收连接,构造 ASGI 事件

用户请求进来:

GET /users/100?verbose=true

Uvicorn 接收连接后,会把这次请求整理成 ASGI 的 scope,里面包含:

  • type: http
  • method: GET
  • path: /users/100
  • query_string: verbose=true
  • headers: 请求头
  • client: 客户端地址

请求体不是一次性强塞给应用,而是可以通过 receive() 异步读取。

这就是流式请求体和异步 IO 的基础。

第二步:Starlette 中间件包住整个应用

请求进入应用后,会先经过 middleware 链。

中间件适合做“所有请求都要经过”的横切逻辑:

  • CORS。
  • 请求日志。
  • Trace ID。
  • 统一耗时统计。
  • 全局安全头。
  • 全局异常包装。
  • 代理头、HTTPS 重定向等基础设施逻辑。

它的位置很靠外,能看到原始 request,也能包住 response。

所以它适合处理“全局通道层逻辑”,不适合承载太多业务判断。

第三步:Starlette / FastAPI 做路由匹配

然后框架根据 method 和 path 找到对应 endpoint。

比如:

@app.get("/users/{user_id}")
async def get_user(user_id: int, verbose: bool = False):
    ...

/users/100 会匹配到 /users/{user_id}

但到这里还没有直接调用你的函数。FastAPI 还要先分析函数签名。

第四步:FastAPI 解析函数签名

FastAPI 会看这个函数:

async def get_user(user_id: int, verbose: bool = False):
    ...

它会判断:

  • user_id 出现在路径模板里,所以来自 path。
  • verbose 没出现在路径里,也不是 body model,所以默认来自 query。
  • user_id: int 表示要转成整数。
  • verbose: bool = False 表示 query 参数可选,默认 False

这就是 FastAPI 看起来“魔法”的地方。

其实它不是凭空猜,而是在利用 Python 的类型标注、默认值和 Annotated 元数据。

更完整的写法会是:

from typing import Annotated

from fastapi import FastAPI, Query

app = FastAPI()

@app.get("/users/{user_id}")
async def get_user(
    user_id: int,
    verbose: Annotated[bool, Query(description="是否返回详细信息")] = False,
):
    return {"user_id": user_id, "verbose": verbose}

第五步:Pydantic 做转换和校验

HTTP 请求里的 path、query、header 本质上都是字符串。

FastAPI 看到 user_id: int,会尝试把字符串 "100" 转成整数 100

如果用户传的是:

/users/foo

它会返回结构化校验错误,而不是让你的业务函数收到一个脏值。

请求体也是一样。

from pydantic import BaseModel, Field

class CreateOrderRequest(BaseModel):
    sku_id: str
    count: int = Field(gt=0, le=100)
    remark: str | None = None

@app.post("/orders")
async def create_order(req: CreateOrderRequest):
    return req

这里 FastAPI 会:

  • 读取请求体 JSON。
  • 转换字段类型。
  • 校验 count 范围。
  • 把校验后的对象传给 req
  • 生成 OpenAPI schema。
  • /docs 里展示接口文档。

这就是为什么 FastAPI 文章里总强调 type hints。

它不是为了“看起来有类型”,而是让函数签名变成 API 契约。

第六步:解析 Depends 依赖图

如果函数里有 Depends

from typing import Annotated

from fastapi import Depends, Header, HTTPException

async def get_token(authorization: Annotated[str | None, Header()] = None) -> str:
    if not authorization:
        raise HTTPException(status_code=401, detail="Missing token")
    return authorization.removeprefix("Bearer ").strip()

@app.get("/me")
async def me(token: Annotated[str, Depends(get_token)]):
    return {"token": token}

FastAPI 会先调用 get_token,拿到返回值,再把它注入 me()

依赖函数本身也可以继续声明参数和依赖,形成一张依赖图:

endpoint
  ├─ get_current_user
  │   └─ get_token
  └─ get_db_session

这和 Spring 的 Bean 容器有相似处,但不要完全等同。

FastAPI 的依赖注入更偏函数式:

  • 依赖通常是函数、类或可调用对象。
  • 很多依赖按请求解析。
  • 依赖可以拿 path/query/header/body。
  • 依赖可以嵌套。
  • 依赖可以配合 yield 做资源释放。
  • 测试时可以覆盖依赖。

它不是一个完整的企业级 IoC 容器,更像“围绕请求函数签名构建的依赖解析系统”。

第七步:调用 endpoint,区分 defasync def

到这里,参数和依赖都准备好了,才会真正调用你的业务函数。

如果 endpoint 是:

@app.get("/items")
async def list_items():
    ...

FastAPI 可以在事件循环里 await 它。

如果 endpoint 是:

@app.get("/legacy")
def legacy_api():
    ...

FastAPI 不会直接在事件循环里执行它,而是把它放到外部线程池里运行,再等待结果。

官方文档里也明确说了:普通 def 的 path operation function 会在外部 threadpool 中运行,避免阻塞 server。

这点很重要,下一节单独讲。

第八步:序列化响应,走回 ASGI send

你的函数返回:

return {"user_id": 100, "name": "Ming"}

FastAPI 会把返回值转成响应对象,必要时用 response model 做输出校验和序列化,再交给 Starlette Response,最终通过 ASGI 的 send 发回客户端。

如果是流式响应,就不是一次性发完,而是多次发送 body chunk。

这也是 Agent 后端常用 SSE 或 StreamingResponse 的基础。

三、路径、查询、请求体:FastAPI 参数绑定的判断规则

FastAPI 参数绑定最容易混,但规则其实很稳定。

在这里插入图片描述

1. 出现在路径模板里的参数,来自 Path

@app.get("/items/{item_id}")
async def read_item(item_id: int):
    return {"item_id": item_id}

item_id 出现在 /items/{item_id} 中,所以它来自路径。

如果传入 /items/foo,而类型要求是 int,框架会返回校验错误。

2. 简单类型且不在路径里,默认来自 Query

@app.get("/items")
async def list_items(skip: int = 0, limit: int = 20):
    return {"skip": skip, "limit": limit}

请求:

/items?skip=10&limit=50

skiplimit 来自 query。

它们在 URL 里天然是字符串,但 FastAPI 会按类型转换并校验。

3. Pydantic model 通常来自 Body

class UpdateItemRequest(BaseModel):
    name: str
    price: float

@app.put("/items/{item_id}")
async def update_item(item_id: int, req: UpdateItemRequest):
    return {"item_id": item_id, "item": req}

item_id 来自 path,req 来自 body。

这套规则的面试说法可以是:

FastAPI 根据路由模板、函数参数、类型标注、默认值和额外声明,决定参数来自 path、query、header、cookie、body、form 还是 dependency。

如果要显式声明,就用:

from typing import Annotated

from fastapi import Body, Header, Path, Query

async def demo(
    item_id: Annotated[int, Path(gt=0)],
    q: Annotated[str | None, Query(max_length=50)] = None,
    trace_id: Annotated[str | None, Header()] = None,
    payload: Annotated[dict | None, Body()] = None,
):
    ...

实际项目里,建议优先用 Annotated,它更清楚地把“类型”和“参数元数据”分开。

四、Depends:它解决的是“每个请求怎么拿到上下文”

很多人第一次看 Depends,会把它理解成 Spring 的 @Autowired

这个理解只对了一半。

在 FastAPI 里,Depends 更常用于解决“这次请求需要哪些上下文”:

  • 当前用户是谁。
  • token 是否有效。
  • 当前租户是什么。
  • 数据库 session 怎么创建和释放。
  • 这个接口需要什么权限。
  • 当前请求的 trace id / request id 是什么。
  • 某个 route 需要哪个 service 或 client。

一个更像真实项目的 Depends 写法

from collections.abc import AsyncIterator
from typing import Annotated

from fastapi import Depends, Header, HTTPException
from pydantic import BaseModel

class User(BaseModel):
    id: str
    role: str

async def get_token(authorization: Annotated[str | None, Header()] = None) -> str:
    if not authorization:
        raise HTTPException(status_code=401, detail="Missing Authorization")
    return authorization.removeprefix("Bearer ").strip()

async def get_current_user(token: Annotated[str, Depends(get_token)]) -> User:
    # 真实项目里这里会查缓存、认证服务或 JWT
    if token == "admin-token":
        return User(id="u_1", role="admin")
    raise HTTPException(status_code=403, detail="Invalid token")

async def get_db() -> AsyncIterator[str]:
    db = "db-session"
    try:
        yield db
    finally:
        # 真实项目里这里关闭 session / rollback / release connection
        pass

@app.get("/admin/orders")
async def admin_orders(
    user: Annotated[User, Depends(get_current_user)],
    db: Annotated[str, Depends(get_db)],
):
    if user.role != "admin":
        raise HTTPException(status_code=403, detail="Forbidden")
    return {"db": db, "user": user.id}

这段代码里,Depends 做了三件事:

  1. 把认证逻辑从 endpoint 里拿出去。
  2. 把 token、user、db session 组成依赖图。
  3. 让资源释放逻辑可以跟请求生命周期绑定。

这就是它比“工具函数调用”更有价值的地方。

工具函数只能被你手动调用。

依赖函数会被框架识别、排序、缓存、注入、清理,也能在测试中覆盖。

Depends 和 Middleware 怎么选?

这是 Agent 后端面试里很常见的问题。

可以用一个简单判断:

所有请求都要经过,且与具体路由关系不大 -> Middleware
某些接口需要,且要把结果注入业务函数 -> Depends

比如:

场景 更适合
CORS Middleware
请求耗时统计 Middleware
Trace ID 注入到 request.state Middleware
全局安全响应头 Middleware
当前用户解析 Depends
权限校验 Depends
数据库 session Depends
当前租户上下文 Depends
LLM client / retriever / service 注入 Depends

在大模型接口里,一个比较稳的组合是:

  • Middleware 负责 trace id、日志、耗时、跨域、全局异常边界。
  • Depends 负责用户、权限、租户、数据库 session、向量库 client、模型服务 client。
  • Lifespan 负责应用启动时初始化连接池、模型资源、长期复用的客户端。

不要把所有逻辑都塞进中间件。中间件太外层,业务上下文弱,测试和路由级差异会变难。

也不要把所有逻辑都塞进 Depends。全局日志、CORS、响应头这种通道逻辑放 Depends 里会重复且不自然。

五、defasync def:不是谁高级,而是看调用链

上一篇讲 asyncio 时已经埋了伏笔:async def 不是加速器。

FastAPI 官方文档给出的判断非常直接:

  • 如果你调用的第三方库需要 await,就用 async def
  • 如果你调用的库是同步阻塞库,没有 await 支持,就用普通 def
  • 如果不知道,用普通 def 更稳。
  • FastAPI 允许同一个项目里混用 defasync def

在这里插入图片描述

正确例子:异步 HTTP 客户端

import httpx

@app.get("/profile/{user_id}")
async def profile(user_id: str):
    async with httpx.AsyncClient(timeout=3) as client:
        resp = await client.get(f"https://user-service/users/{user_id}")
    return resp.json()

这里网络 IO 支持 await,所以 async def 是合适的。

请求等待下游服务时,事件循环可以去处理其他请求。

危险例子:在 async def 里调用阻塞库

import requests

@app.get("/profile/{user_id}")
async def profile(user_id: str):
    resp = requests.get(f"https://user-service/users/{user_id}", timeout=3)
    return resp.json()

这段代码看起来是 async,但里面的 requests.get() 是同步阻塞调用。

结果是:事件循环被卡住。

这就是典型“伪异步”。

如果短期内只能用同步库,更稳的写法是:

import requests

@app.get("/profile/{user_id}")
def profile(user_id: str):
    resp = requests.get(f"https://user-service/users/{user_id}", timeout=3)
    return resp.json()

FastAPI 会把普通 def endpoint 放到线程池里执行,避免直接堵住事件循环。

线程池不是无限的

这里还有一个坑。

Starlette 文档里写得很清楚:它会在若干场景使用线程池来避免阻塞事件循环,包括同步 endpoint、文件响应、上传文件、同步后台任务等。

但默认线程池 capacity limiter 只有 40 个 token。FastAPI 的同步依赖也会消耗这个线程容量。

所以不能误以为:

我全部写 def,框架会自动帮我无限扩容。

同步阻塞调用多了以后,线程池会成为瓶颈。

真正高吞吐的接口,要么使用异步库,要么把重活交给进程池、任务队列或专门的 worker。

CPU 密集任务不要靠 async 硬扛

比如:

  • 大文件解析。
  • 图片处理。
  • 本地模型推理。
  • 向量批量计算。
  • CPU 密集 rerank。

这些任务如果直接写在 endpoint 里,会拖垮请求线程或事件循环。

更稳的方案是:

  • 短小 CPU 任务:考虑进程池。
  • 长耗时任务:交给任务队列。
  • 模型推理:独立推理服务或专门 worker。
  • 在线接口:只做参数校验、任务提交、状态查询、流式转发。

面试回答时可以这样收:

FastAPI 的 async 优势主要体现在高并发 IO,不是让 CPU 计算自动并行。async def 里必须一路使用可 await 的库,否则就是伪异步;同步库用 def 或显式线程池,CPU 重活交给进程池或后台 worker。

六、Lifespan:初始化模型、连接池和客户端,不要塞进全局裸变量

FastAPI 以前常见 startup / shutdown 事件,现在官方更推荐用 lifespan

lifespan 的形状像一个异步上下文管理器:

from contextlib import asynccontextmanager

from fastapi import FastAPI

@asynccontextmanager
async def lifespan(app: FastAPI):
    app.state.model_client = await create_model_client()
    app.state.vector_store = await create_vector_store()
    yield
    await app.state.model_client.aclose()
    await app.state.vector_store.aclose()

app = FastAPI(lifespan=lifespan)

这和前面讲过的上下文管理器可以连起来理解:

  • yield 前:应用启动时执行。
  • yield 后:应用关闭时执行。
  • 中间:应用正常处理请求。

Agent 后端里,lifespan 很适合放:

  • LLM API client。
  • 向量数据库连接池。
  • Redis 连接池。
  • 数据库 engine。
  • embedding / rerank 组件。
  • 需要复用的工具注册表。

然后通过 Depends 读取:

from fastapi import Depends, Request

def get_model_client(request: Request):
    return request.app.state.model_client

@app.post("/chat")
async def chat(client = Depends(get_model_client)):
    ...

注意一个生产细节:如果你用多个 worker 进程,每个进程都会有自己的应用实例和 lifespan 执行过程。不要以为全局变量天然就是全局唯一资源。

七、流式响应:Agent 后端为什么喜欢 FastAPI

传统 CRUD 接口通常是:

request -> 等业务处理完 -> response

但大模型接口更常见的是:

request -> 模型开始生成 -> token/chunk 持续返回 -> 完成

这时一次性 JSON 响应体验很差。

FastAPI 可以用 StreamingResponse

from collections.abc import AsyncIterator

from fastapi.responses import StreamingResponse

async def stream_answer(prompt: str) -> AsyncIterator[bytes]:
    async for chunk in llm_client.stream(prompt):
        yield f"data: {chunk}\n\n".encode("utf-8")

@app.get("/chat/stream")
async def chat_stream(q: str):
    return StreamingResponse(
        stream_answer(q),
        media_type="text/event-stream",
    )

这里的关键点是:生成器里要有真正的 await 让出事件循环。

FastAPI 官方文档在 StreamingResponse 部分也提醒:异步任务只有到达 await 时才有机会处理取消。如果大流或无限流里没有 await,客户端断开后任务可能不能及时取消。

如果需要双向通信,比如浏览器持续发送用户输入,服务端持续推送状态,就可以用 WebSocket:

from fastapi import WebSocket

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    while True:
        message = await websocket.receive_text()
        await websocket.send_text(f"echo: {message}")

面试里可以这样区分:

  • 普通 JSON:一次请求一次响应。
  • StreamingResponse / SSE:服务端持续向客户端推送,适合 LLM token 流。
  • WebSocket:双向长连接,适合实时聊天、协作、状态同步。

不要为了“高级”强行上 WebSocket。很多大模型聊天接口,SSE 已经够用,部署和调试也更简单。

八、FastAPI 常见面试坑

在这里插入图片描述

坑 1:以为加了 async 就不会阻塞

async def 只是允许你在函数里使用 await

如果里面调用的是同步阻塞库,事件循环照样会被卡住。

判断标准不是函数前面有没有 async,而是慢操作有没有可 await 的让出点。

坑 2:把 Depends 当成全局单例容器

FastAPI 的 Depends 不是 Spring Bean 容器的完整替代品。

它更适合围绕请求做依赖解析。

对于连接池、模型 client 这种生命周期更长的资源,建议用 lifespan 初始化,然后 Depends 读取,而不是在依赖函数里每次重新创建。

坑 3:把业务权限全塞 Middleware

Middleware 适合全局逻辑。

如果某些路由才需要管理员权限、某些路由需要租户上下文、某些路由允许匿名访问,Depends 往往更合适。

坑 4:把 Pydantic model 当 ORM 实体

Pydantic model 适合做接口输入输出的数据契约。

ORM model 负责数据库映射。

两者可以转换,但不建议混成一个东西。否则接口字段、数据库字段、权限脱敏、内部状态很容易纠缠。

坑 5:把 BackgroundTasks 当可靠任务队列

FastAPI 的 BackgroundTasks 适合响应返回后做轻量附加动作,比如写日志、发送非关键通知。

如果任务要求可靠重试、状态追踪、长耗时执行、失败恢复,就应该考虑专门的任务队列或 worker。

坑 6:不知道线程池容量会成为瓶颈

同步 endpoint 和同步 dependency 会进线程池。

线程池能保护事件循环,但不是无限资源。同步阻塞代码过多时,吞吐会被线程池容量、下游连接池和 CPU 一起限制。

九、面试回答模板:用一条链路讲清 FastAPI

最后给一个可以直接背、也经得起追问的回答框架。

在这里插入图片描述

如果面试官问:

你讲一下 FastAPI 的请求处理流程。

可以这样答:

FastAPI 应用一般由 Uvicorn 这类 ASGI server 启动。
Uvicorn 接收 HTTP 或 WebSocket 连接后,会按 ASGI 协议把请求变成 scope、receive、send 三个对象。

进入应用后,请求先经过 Starlette/FastAPI 的中间件链,再做路由匹配。
匹配到 endpoint 后,FastAPI 会分析函数签名,根据路径模板、参数类型、默认值和 Annotated 元数据判断参数来自 path、query、header、cookie、body 还是 dependency。

请求数据会交给 Pydantic 做类型转换和校验,校验失败会返回结构化错误。
如果 endpoint 声明了 Depends,FastAPI 会先解析依赖图,执行依赖并把结果注入函数。

最后 FastAPI 根据 endpoint 是 def 还是 async def 决定调用方式:
async def 可以直接 await;
普通 def 会放到线程池,避免阻塞事件循环。

函数返回后,FastAPI/Starlette 把结果序列化成 Response,再通过 ASGI send 发回客户端。

如果面试官继续问:

中间件和 Depends 怎么选?

可以答:

中间件适合全局通道逻辑,比如 CORS、日志、Trace ID、耗时统计、统一响应头。
Depends 适合路由上下文逻辑,比如当前用户、权限、租户、数据库 session、模型 client。

我的判断是:所有请求都要经过、且不强依赖具体业务路由的,放中间件;
只有部分接口需要、并且需要把结果注入业务函数的,放 Depends。

如果问:

Agent 后端为什么常用 FastAPI?

可以答:

因为 Agent 后端通常有三类需求:

第一,接口契约变化快,FastAPI 的类型标注、Pydantic 校验和 OpenAPI 文档很适合快速迭代。

第二,LLM、向量库、工具服务大多是网络 IO,FastAPI 基于 ASGI 和 async/await,适合高并发 IO。

第三,LLM 输出常常需要流式返回,FastAPI 可以用 StreamingResponse/SSE,也支持 WebSocket。

但我不会简单把所有接口都写 async def。是否 async 取决于调用链是否真正支持 await;同步阻塞库用 def 或线程池,CPU 重活交给 worker。

这套回答比“FastAPI 很快、自动生成文档、支持异步”更有信息量。

它说明你理解框架,也理解后端运行时。

总结:FastAPI 要按请求链路理解

FastAPI 最值得掌握的不是某个装饰器,而是这张心智图:

Uvicorn
  -> ASGI scope / receive / send
  -> Starlette middleware / routing
  -> FastAPI signature analysis
  -> Path / Query / Header / Body binding
  -> Pydantic validation
  -> Depends dependency graph
  -> def threadpool or async def await
  -> Response serialization
  -> ASGI send

把这条链路讲清楚,很多问题都会变简单:

  • 参数从哪里来?
  • 为什么校验错误能定位字段?
  • Depends 到底解决什么?
  • 中间件和依赖怎么分工?
  • async 为什么不是万能的?
  • Agent 接口为什么适合流式响应?

如果你是 Java 后端转 Python / FastAPI,不要只把它类比成 Spring Boot。

更准确的理解是:

FastAPI 是一套基于 ASGI 的 API 框架,它用 Python 类型系统描述接口契约,用 Pydantic 执行数据校验,用 Starlette 承接 Web 层能力,用 Depends 把请求上下文组织成可测试、可复用的依赖图。

这才是面试和项目里真正能用上的 FastAPI。

参考资料

  • FastAPI 官方文档首页:https://fastapi.tiangolo.com/
  • FastAPI Concurrency and async / await:https://fastapi.tiangolo.com/async/
  • FastAPI Path Parameters:https://fastapi.tiangolo.com/tutorial/path-params/
  • FastAPI Query Parameters:https://fastapi.tiangolo.com/tutorial/query-params/
  • FastAPI Request Body:https://fastapi.tiangolo.com/tutorial/body/
  • FastAPI Dependencies:https://fastapi.tiangolo.com/tutorial/dependencies/
  • FastAPI Lifespan Events:https://fastapi.tiangolo.com/advanced/events/
  • FastAPI WebSockets:https://fastapi.tiangolo.com/advanced/websockets/
  • FastAPI StreamingResponse:https://fastapi.tiangolo.com/advanced/custom-response/
  • Uvicorn ASGI Concepts:https://www.uvicorn.org/concepts/asgi/
  • Uvicorn 官方文档:https://www.uvicorn.org/
  • Starlette Thread Pool:https://starlette.dev/threadpool/
  • Starlette Requests:https://www.starlette.io/requests/
  • Starlette Middleware:https://www.starlette.io/middleware/
  • Pydantic 官方文档:https://docs.pydantic.dev/latest/
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐