
文章目录一、为什么是 FastAPI1.1 FastAPI 是什么1.2 核心组成Starlette Pydantic二、环境安装与启动命令2.1 安装2.2 启动命令逐段解析三、Demo 01第一个接口 —— Hello World3.1 完整代码3.2 逐行拆解3.3 路径参数与类型注解四、Demo 02请求体与 Pydantic —— 对接大模型接口4.1 为什么需要 BaseModel4.2 完整代码FastAPI LangChain 调用 DeepSeek4.3 关键步骤说明4.4 校验效果五、Demo 03参数校验 —— Annotated Path / Query5.1 Annotated 是什么5.2 Path给路径参数加校验5.3 Query查询参数校验5.4 三种参数工具对比六、Demo 04请求体字段校验 —— Field 与 model_dump6.1 完整代码6.2 可选字段的读法6.3 Field给字段加规则6.4 model_dump 与字典展开6.5 校验效果七、Demo 05综合实战 —— Todo 待办接口7.1 完整代码修正版7.2 数据设计思路7.3 response_model声明返回值结构7.4 查单条与 404八、async 异步FastAPI 高性能的秘密8.1 通俗理解8.2 两种写法怎么选九、全文总结十、核心知识点复盘本文基于一套循序渐进的实战 Demo01~05从第一个 Hello World 接口开始经过请求体模型、参数校验、字段规则最终完成一个 Todo 待办应用并讲清楚 async 异步的原理。每个 Demo 的代码都完整可运行坑点均来自真实调试过程。一、为什么是 FastAPI1.1 FastAPI 是什么FastAPI 是 Python 生态中的高性能 Web 接口框架性能比肩 Go 和 Node.js。它的定位非常专一只做后端 API不用折腾繁琐配置开发效率极高特别适合配合 LangChain / LangGraph 开发大模型后端服务。它打动人的三个特性特性说明高性能基于异步无阻塞模型天然适合高并发场景省代码写少量代码即可完成接口自带类型提示自动文档自动生成交互式接口文档Swagger是前后端 API 约定的利器1.2 核心组成Starlette Pydantic【核心原理】FastAPI 本身不直接处理网络它是站在两个库肩膀上的组装框架StarletteWeb 底层基座。负责接收 HTTP 请求、路由匹配、返回响应、处理网络自带异步能力。Pydantic类型校验库。功能类似前端 JS 生态里的 zod负责校验用户输入路径参数、查询参数、请求体底层依赖 Python 类型注解。请求的完整流转路径浏览器请求 ↓ Starlette接收 HTTP / 路由匹配 / 异步调度 ↓ FastAPI调度框架串联一切 ↓ Pydantic校验参数不合格直接返回 422 ↓ 你写的业务函数只关心业务逻辑 ↓ JSON 响应一个类比帮助记忆Starlette 是发动机Pydantic 是安检仪FastAPI 是把二者组装好、让你只写业务逻辑的整车厂。二、环境安装与启动命令2.1 安装pipinstallfastapi[standard]# 安装 fastapi 全家桶含常用依赖pipinstalluvicorn[standard]# uvicorn异步 Web 服务器用来运行 FastAPI 项目【重点】FastAPI 只是一个 Python 包自己不会监听端口。必须由 uvicorn 这类 ASGI 服务器来托管运行。可以理解为FastAPI 是应用uvicorn 是跑应用的容器二者缺一不可。2.2 启动命令逐段解析python-muvicorn main:app--reload--port8080片段含义python -m uvicorn用当前 Python 环境启动 uvicorn比裸敲uvicorn更不容易用错解释器main:app在main.py文件里找app这个变量冒号读作在……里面--reload热重载代码保存后自动重启服务仅限开发环境--port 8080监听 8080 端口【易错点】启动日志里常见的0.0.0.0不是用来在浏览器访问的地址。host0.0.0.0的含义是监听本机所有网卡是服务端的绑定配置。浏览器测试请访问http://127.0.0.1:8000或http://localhost:8000。三、Demo 01第一个接口 —— Hello World3.1 完整代码fromfastapiimportFastAPI# 1. 创建 FastAPI 应用实例整个服务的入口对象appFastAPI()# 2. 装饰器把下面的函数注册为 / 路径的 GET 接口app.get(/)asyncdefroot():# 返回字典FastAPI 自动转成 JSON 响应return{message:World}# 3. 路径参数{name} 是占位符请求 /hello/tom 时 name tomapp.get(/hello/{name})asyncdefsay_hello(name:str):return{message:fHello,{name}!}if__name____main__:importuvicorn uvicorn.run(main:app,host127.0.0.1,port8000,reloadTrue)3.2 逐行拆解app FastAPI()创建应用实例路由、文档都挂在它身上。app.get(/)装饰器是 Python 的语法糖等价于root app.get(/)(root)作用是把函数注册进路由表。当GET /请求到来时Starlette 查路由表找到root函数并执行。return {message: World}返回字典会被自动序列化为 JSON并设置响应头Content-Type: application/json你不用手动json.dumps。【重点】装饰器路由是 FastAPI 的核心写法本质就是函数注册app.get(/路径)声明什么请求路径、什么 HTTP 方法交给哪个函数处理。3.3 路径参数与类型注解/hello/{name}中的{name}是占位符。类型注解name: str有两个作用自动接收访问/hello/FastAPI函数里name就是字符串FastAPI自动转换如果注解是intFastAPI 会把 URL 里的字符串转成整数转换失败直接返回 422 错误。四、Demo 02请求体与 Pydantic —— 对接大模型接口4.1 为什么需要 BaseModelGET 请求的参数很简单但 POST 通常传结构化的 JSON。手写校验长这样dataawaitrequest.json()promptdata.get(prompt)ifnotisinstance(prompt,str):return{error:prompt 必须是字符串}用BaseModel一行类型注解就能替代上面全部代码。4.2 完整代码FastAPI LangChain 调用 DeepSeekfromdotenvimportload_dotenv# 读取 .env 文件的工具importosfromfastapiimportFastAPIfrompydanticimportBaseModel# 数据校验基类fromlangchain_openaiimportChatOpenAI# 把 .env 里的键值对加载进环境变量load_dotenv()appFastAPI(titleLangChain FastAPI)# 初始化大模型客户端DeepSeek 兼容 OpenAI 协议llmChatOpenAI(api_keyos.getenv(DEEPSEEK_API_KEY),# 从环境变量读密钥base_urlos.getenv(DEEPSEEK_BASE_URL),# 接口地址modelos.getenv(DEEPSEEK_MODEL),# 模型名temperature0.7,# 随机性越大回答越发散)# 定义请求体模型要求 JSON 里必须有 prompt 字段且为字符串classChatReq(BaseModel):prompt:str# POST 接口请求体经过 ChatReq 校验app.post(/chat)asyncdefchat(req:ChatReq):respllm.invoke(req.prompt)# 调用大模型return{input:req.prompt,reply:resp.content}if__name____main__:importuvicorn uvicorn.run(main:app,host127.0.0.1,port8000,reloadTrue)4.3 关键步骤说明load_dotenv()把项目下.env文件的键值对注入环境变量再用os.getenv()读取。密钥不写死在代码里避免提交到 Git 泄露。ChatOpenAI(...)初始化一个大模型客户端之后llm.invoke(问题)就能拿到模型回答。class ChatReq(BaseModel)继承BaseModel后ChatReq就是一个自带校验能力的请求模型prompt: str表示必填且必须是字符串。req: ChatReqFastAPI 看到参数类型是 Pydantic 模型会自动完成解析 JSON → 校验 → 实例化三步。4.4 校验效果请求体结果{prompt: 你好}通过req.prompt 你好{}422缺少必填字段 prompt{prompt: 123}422数字不能当作字符串{prompt: hi, extra: 1}通过多余字段默认忽略【核心原理】校验发生在你的函数执行之前。不合法的请求直接被挡在门外返回 422你的函数拿到的req一定是干净可用的数据——这就是类型即校验的含义。五、Demo 03参数校验 —— Annotated Path / Query5.1 Annotated 是什么Annotated是 Python 标准库typing提供的类型注解工具作用是给类型附加元数据额外信息Annotated[真实类型,元数据1,元数据2,...]第一个参数是真正的数据类型后面的元数据是附加说明。FastAPI 会读取这些元数据把Path()、Query()的校验规则应用上去。5.2 Path给路径参数加校验fromfastapiimportFastAPI,Path,QueryfromtypingimportAnnotated appFastAPI()app.get(/p/{article_id})asyncdefarticle_detail(article_id:Annotated[int,Path(ge2)]):# intarticle_id 必须是整数自动转换# Path(ge2)值必须 2return{article_id:article_id}比较运算符的含义写法规则缩写含义Path(ge2) 2greater than or equalPath(le100) 100less than or equalPath(gt0) 0greater thanPath(lt100) 100less thanPath(ge2, le100)2 ~ 100 之间组合使用实际访问效果请求结果/p/5通过返回{article_id: 5}/p/1422小于 2/p/abc422无法转成整数【重点】为什么用Annotated而不是旧写法article_id: int Path(ge2)因为新写法把类型和规则分开语义清晰必填参数也不会被误认为有默认值。5.3 Query查询参数校验查询参数是 URL 问号后面的部分多个参数用连接/article/list?page2size5。app.get(/article/list)asyncdefarticle_list(page:Annotated[int,Query(1,ge1)]1,# 默认第 1 页最小为 1size:Annotated[int,Query(10,ge1)]10,# 默认每页 10 条最小为 1):return{page:page,size:size}Query(1, ge1)第一个参数1是默认值ge1是校验规则。不传page时自动用默认值。【易错点】原 Demo 里size写的是Query(10, ge10)规则与默认值冲突——它意味着size最小只能是 10传 5 会报错。如果业务本意是每页最少 1 条这是典型手误。校验规则必须和业务语义对齐。5.4 三种参数工具对比工具用在哪示例Path()URL 路径参数/p/{id}Annotated[int, Path(ge2)]Query()查询参数?page1Annotated[int, Query(1, ge1)]Field()请求体模型的字段Field(..., min_length6)一句话记忆URL 上的用 Path / QueryJSON 请求体里的用 Field。六、Demo 04请求体字段校验 —— Field 与 model_dump6.1 完整代码fromfastapiimportFastAPIfrompydanticimportBaseModel,FieldfromtypingimportAnnotated appFastAPI()# 商品模型description / tax 是可选字段classItem(BaseModel):name:str# 必填description:str|NoneNone# 可选str 或 None默认 Noneprice:float# 必填自动转浮点数tax:float|NoneNone# 可选app.put(/items/{item_id})asyncdefupdate_item(item_id:int,item:Item):# model_dump() 把模型实例转成普通字典** 再展开合并进结果result{item_id:item_id,**item.model_dump()}returnresult# 登录模型Field 给字段加校验规则classLogin(BaseModel):# ... 表示必填email:Annotated[str,Field(...,description邮箱地址)]password:Annotated[str,Field(...,min_length6,max_length20,description密码6~20位)]app.post(/login)asyncdeflogin(data:Login):return{email:data.email,password:data.password}if__name____main__:importuvicorn uvicorn.run(main:app,host127.0.0.1,port8000,reloadTrue)6.2 可选字段的读法description: str | None None三段式读法类型是str或None默认值是None即可以不传。而name: str没有默认值就是必填。6.3 Field给字段加规则Field是 Pydantic 提供的函数给模型字段添加校验规则、默认值和说明。其中...是 Python 的省略号对象Ellipsis在Field里表示必填、无默认值。Field(...)# 必填Field(None)# 可空默认 NoneField(18,ge0,le150)# 默认 18范围 0~150常用参数参数作用...必填min_length/max_length字符串长度限制gt/lt/ge/le数值范围pattern正则表达式校验格式description字段说明显示在 /docs 文档里6.4 model_dump 与字典展开item.model_dump()把 Pydantic 模型实例转成普通字典Pydantic v2 的方法v1 时代叫.dict()。{item_id: item_id, **item.model_dump()}**把字典展开与前面的键值对合并成一个新字典。【重点】model_dump在很多场景都会遇到比如把大模型返回的消息对象转成字典存进对话历史——对象变字典是高频操作。6.5 校验效果请求体结果{email: ab.com, password: 123456}通过{password: 123456}422缺少必填的 email{email: ab.com, password: 123}422密码少于 6 位七、Demo 05综合实战 —— Todo 待办接口7.1 完整代码修正版原 Demo 存在几个小问题第十一节逐条分析下面是修正后的完整可运行版本fromfastapiimportFastAPI,HTTPException,PathfrompydanticimportBaseModel,FieldfromtypingimportAnnotated,List appFastAPI(titleTodo增删改查)todos[]# 内存列表当数据库next_id1# 自增 ID# 用户提交用的模型没有 id防止用户伪造编号classTodoCreate(BaseModel):title:Annotated[str,Field(min_length1,max_length100,description待办标题1~100个字符)]done:boolFalse# 可选默认未完成# 存储和返回用的模型继承 TodoCreate服务端补上 idclassTodo(TodoCreate):id:int# 创建待办app.post(/todos,summary创建待办,response_modelTodo)asyncdefcreate_todo(data:TodoCreate):globalnext_id# 声明修改模块级变量todoTodo(idnext_id,**data.model_dump())# 服务端分配 idnext_id1todos.append(todo)returntodo# 查询所有待办app.get(/todos,summary查询所有待办,response_modelList[Todo])asyncdefget_all_todos():returntodos# 查询单个待办app.get(/todos/{todo_id},summary查询指定ID待办,response_modelTodo)asyncdefget_todo(todo_id:Annotated[int,Path(gt0,descriptionID必须大于0)]):foritemintodos:ifitem.idtodo_id:returnitem# 没找到返回标准的 404 错误而不是 raise 一个字典raiseHTTPException(status_code404,detailTodo not found)if__name____main__:importuvicorn uvicorn.run(main:app,host127.0.0.1,port8000,reloadTrue)7.2 数据设计思路todos列表充当内存数据库服务重启就清空——学习够用生产环境要换成真正的数据库。TodoCreate用户提交无 id与Todo服务端存储与返回带 id分成两个模型id 由服务端分配避免用户提交伪造的编号。Todo继承TodoCreate获得全部字段再加一个id。模型里没写id却在路径参数上用Field校验是常见误用——路径参数应该用Path对比表见 5.4 节。7.3 response_model声明返回值结构response_modelList[Todo]声明这个接口返回的是 Todo 数组有三大作用校验返回数据函数返回后FastAPI 按模型检查每一项不符合直接报错把问题拦在后端过滤多余字段返回数据里模型之外的字段会被自动删掉内部字段、敏感信息不会漏给前端生成响应文档/docs 页面自动展示响应的 JSON 结构前端不用问就知道格式。summary查询所有待办则是纯文档用途只显示在 /docs 的接口标题上不影响任何逻辑。7.4 查单条与 404查单个待办用for循环匹配 id。找不到时必须返回 404 语义标准做法是抛出HTTPExceptionraiseHTTPException(status_code404,detailTodo not found)【易错点】raise后面只能跟异常对象不能 raise 一个字典如raise {detail: ...}那会直接抛TypeError。八、async 异步FastAPI 高性能的秘密8.1 通俗理解把服务器想象成奶茶店的服务员同步没有 async接一单 → 站着等奶茶做完 → 才接下一单。等待期间什么都干不了其他客人全在排队。异步async接一单 → 把单子递给后台 →转头继续接下一单。奶茶好了再回来取。同步与异步处理请求的时间线对比同步一个线程死等其余请求全部排队 线程 ├──请求A──等待A(卡住)──完成A──请求B──等待B(卡住)──完成B──▶ ↑ A 在等待时B 只能干等 异步等待时让出控制权一个线程穿插处理多个请求 线程 ├──请求A──让出去→处理B──返回A──完成A──返回B──完成B──▶ ↑ A 等待的时间被用来处理 B总耗时大幅缩短async def定义的函数就是会安排事情的服务员遇到耗时操作数据库查询、文件读写、网络请求先让出控制权去处理别的请求结果好了再回来继续。8.2 两种写法怎么选函数写法FastAPI 怎么处理适用场景async def跑在事件循环里函数内可用await内部使用异步库异步数据库驱动、httpx 等def普通自动丢到线程池执行不会阻塞服务内部是同步阻塞调用如 requests、同步 ORM选型原则一句话函数里要await就写async def全是同步阻塞调用就写普通def。两种 FastAPI 都能正确调度不必强求全 async。九、全文总结本文沿一条主线走完了 FastAPI 的入门路径Hello World路由注册、路径参数→ 请求体模型BaseModel 对接大模型→ 参数校验Annotated Path / Query→ 字段规则Field、model_dump→ 综合实战Todo 接口 response_model→ 异步原理async / await。贯穿始终的核心思想只有一个FastAPI 把路由、参数解析、数据校验、接口文档全部自动化你只需要写业务函数本身。类型注解不再是装饰而是被框架真正消费的配置——写对类型校验、转换、文档全部免费获得。十、核心知识点复盘知识点一句话要点FastAPI 组成Starlette 负责 Web 底层与异步Pydantic 负责类型校验uvicornASGI 服务器FastAPI 应用必须由它托管运行装饰器路由app.get(/路径)把函数注册进路由表路径参数{name}占位符 类型注解自动转换请求体模型继承BaseModel字段类型即校验规则Annotated给类型附加元数据Annotated[int, Path(ge2)]Path / Query / FieldURL 路径参数 / 查询参数 / 请求体字段各管一摊...Field 里的省略号表示必填model_dump模型实例转字典对象变字典response_model校验返回值、过滤多余字段、生成响应文档HTTPException标准错误返回方式如 404async / await有异步库用async def纯同步代码用def祝你在 FastAPI 的路上一路畅通。