FastAPI + LangChain 实战:从 0 到 1 打造高性能 AI 后端服务 前言大模型应用火了一年多很多人都在用 Python LangChain 写脚本但真要做成产品就必须有后端 API。这个时候问题来了用 Flask太老了用 Django太重了用 Node还得再学一套。FastAPI 就是那个刚刚好的答案——性能比肩 Go/Node类型提示自带文档代码量少到离谱而且跟 LangChain 家族天生一对。这篇文章我会带你从零开始把 FastAPI 的核心用法 与 LangChain 集成的实战经验一次性讲透。一、FastAPI 到底强在哪先给不熟悉的朋友一句大白话总结FastAPI Pydantic类型校验 Starlette异步 Web 内核拆开看组件角色类比StarletteWeb 底层接收 HTTP、路由匹配、返回响应、处理异步相当于 Node 的 KoaPydantic数据校验请求参数、请求体、响应模型相当于前端的 Zod这两个组合起来带来几个杀手级特性自动生成交互式文档Swagger UI 和 ReDoc 白送前后端 API 约定再也不用扯皮原生异步无阻塞async/await一把梭等数据库、等文件 IO、等 LLM 响应都不卡线程类型提示驱动写少量代码就能拿到强类型校验编辑器补全也舒服专为 API 而生不用折腾繁琐配置特别适合给 LangChain/LangGraph 做后端服务一句话总结FastAPI 让写接口这件事回归到写函数本身。二、环境搭建两步走日常开发用标准版就够了# FastAPI 全家桶包含 uvicorn、httpx 等 pip install fastapi[standard] # 如果想要更完整的 uvicorn 特性比如 websocket、watchfiles 热重载 pip install uvicorn[standard]验证一下pip show fastapi启动服务有两种方式# 方式一命令行启动推荐开发用--reload 热重载 python -m uvicorn main:app --reload --port 8080 # 方式二代码里启动见下方完整示例三、从 Hello World 开始不多说先跑通再说from fastapi import FastAPI # 初始化 FastAPI 应用实例 app FastAPI() # 装饰器模式注册路由 app.get(/) async def root(): return {message: Hello World} # 路径参数Pydantic 会自动把 name 转成 str app.get(/hello/{name}) async def hello(name: str): return {message: f你好 {name}}跑起来后访问http://127.0.0.1:8000/docs你会看到自动生成的 Swagger 文档直接就能测试接口。这就是 FastAPI 的第一个爽点接口即文档文档即测试。四、参数校验Annotated 的艺术FastAPI 的参数校验靠的是Annotated这个神器。语法是Annotated[类型, 校验规则]1. 路径参数Pathfrom fastapi import FastAPI, Path from typing import Annotated app FastAPI() app.get(/p/{article_id}) async def article_detail( article_id: Annotated[int, Path(ge2, description文章ID必须≥2)] ): return {article_id: article_id}⚠️踩坑提醒Path(ge2)这种数值约束只能用在int/float上。如果你写成Annotated[str, Path(ge2)]虽然启动不报错但校验逻辑是错的字符串不该比大小。字符串要用min_length/max_length。2. 查询参数Queryfrom fastapi import Query # http://0.0.0.0:8000/article/list?page2size5 app.get(/article/list) async def article_list( page: Annotated[int, Query(ge1)] 1, size: Annotated[int, Query(ge1, le100)] 10, ): return {page: page, size: size}看到了吗类型提示 默认值 校验规则三个东西一行搞定FastAPI 会自动做类型转换和校验参数错了直接返回 422 错误你一行校验代码都不用写。五、请求体校验Pydantic Model 才是主场GET 请求用 Path/Query 就够了但 POST/PUT 的请求体得靠 Pydantic 的BaseModelfrom fastapi import FastAPI from pydantic import BaseModel, Field from typing import Annotated app FastAPI() class Item(BaseModel): name: str description: str | None None price: float tax: float | None None app.put(/items/{item_id}) async def update_item(item_id: int, item: Item): # model_dump() 把实例转成 dict # ** 展开字典合并 result {item_id: item_id, **item.model_dump()} return resultField给字段加规则class LoginIn(BaseModel): email: Annotated[str, Field(..., description邮箱地址)] password: Annotated[str, Field( ..., min_length6, max_length20, description密码6-20位 )] app.post(/login) async def login(data: LoginIn): return {email: data.email, password: data.password}这里的...Ellipsis表示必填。配合Field可以加min_length、max_length、gt、ge、description等一堆约束。金句来了写 FastAPI你的代码就是你的文档。类型定义即校验规则Field里的description直接进 Swagger前端看了直呼内行。六、实战接入 LangChain5 分钟搞定一个 Chat API好戏开始了。我们把 DeepSeek 接进来做一个最简版的 Chat 接口import os from dotenv import load_dotenv from fastapi import FastAPI from pydantic import BaseModel from langchain_openai import ChatOpenAI load_dotenv() app FastAPI(titleLangchain FastAPI) # 初始化 LLM llm ChatOpenAI( api_keyos.getenv(DEEPSEEK_API_KEY), base_urlos.getenv(DEEPSEEK_BASE_URL), modelos.getenv(DEEPSEEK_MODEL), temperature0.7, ) class ChatReq(BaseModel): prompt: str app.post(/chat) async def chat(req: ChatReq): resp llm.invoke(req.prompt) return { input: req.prompt, reply: resp.content, # 注意不是 replay } if __name__ __main__: import uvicorn uvicorn.run(main:app, host0.0.0.0, port8000, reloadTrue).env文件长这样DEEPSEEK_API_KEYsk-xxxxxxxx DEEPSEEK_BASE_URLhttps://api.deepseek.com/v1 DEEPSEEK_MODELdeepseek-chat启动后在/docs里直接试一发{ prompt: 用一句话解释什么是 FastAPI }为什么这么配因为ChatOpenAI兼容 OpenAI 协议DeepSeek、通义千问、Kimi 之类只要提供 OpenAI 兼容接口都能这么接。换个base_url就换模型非常丝滑。进阶提示llm.invoke()是同步阻塞调用如果并发量大建议改成异步resp await llm.ainvoke(req.prompt)配合 FastAPI 的异步能力单机轻松扛住几十上百并发。七、实战写一个完整的 Todo CRUD光说不练假把式我们来写个 Todo 的增删查from fastapi import FastAPI, HTTPException from pydantic import BaseModel, Field from typing import Annotated, Optional, List app FastAPI(titleTodo 增删改查) todos: List[dict] [] next_id 1 class Todo(BaseModel): title: Annotated[str, Field( min_length1, max_length100, description待办事项的标题1-100 字符 )] done: Annotated[Optional[bool], Field(defaultFalse, description是否完成)] app.get(/todos, summary查询所有待办, response_modelList[Todo]) def read_todos(): return todos app.get(/todos/{todo_id}, summary查询单个待办, response_modelTodo) def get_todo( todo_id: Annotated[int, Field(..., gt0, descriptionTodo ID, 必须大于 0)] ): for item in todos: if item[id] todo_id: return item # ✅ 注意return 要写在 for 循环外面 raise HTTPException(status_code404, detail找不到这条 todo)原笔记里的两个坑一起聊聊坑 1return写在for循环里面原代码是这样for item in todos: if item[id] todo_id: return item return {msg: 找不到这条todo} # ❌ 第一次循环就返回了这会导致一个问题只要第一次循环的 id 不匹配就直接返回找不到后面的 todo 永远查不到。正确写法是return挪到循环外面配合raise HTTPException更规范。坑 2response_model与dict混用response_modelTodo表示返回结果会被 Pydantic 按Todo模型过滤和序列化。如果你return {msg: ...}这种不含title的字典FastAPI 会直接报响应校验错误。所以找不到的时候用HTTPException更符合语义。八、总结为什么 FastAPI 值得你投入最后收个尾把本文几个关键点串一下架构清晰Starlette 管 Web 通信Pydantic 管数据校验各司其职写少做多AnnotatedBaseModelField三件套校验 文档全自动异步友好原生async/await接 LLM、数据库、文件 IO 都能无阻塞AI 后端首选和 LangChain 集成就是几行代码的事换模型只需改base_url自动文档Swagger UI 白送前后端对接效率翻倍FastAPI 让你把接口校验和接口文档这两件最烦的事变成了写代码时的副产品。