FastAPI + LangChain 实战:从 0 到 1 打造高性能 AI 后端服务

前言

大模型应用火了一年多,很多人都在用 Python + LangChain 写脚本,但真要做成产品,就必须有后端 API。这个时候问题来了:用 Flask?太老了;用 Django?太重了;用 Node?还得再学一套。

FastAPI 就是那个"刚刚好"的答案------性能比肩 Go/Node,类型提示自带文档,代码量少到离谱,而且跟 LangChain 家族天生一对。

这篇文章,我会带你从零开始,把 FastAPI 的核心用法 + 与 LangChain 集成的实战经验,一次性讲透。


一、FastAPI 到底强在哪?

先给不熟悉的朋友一句大白话总结:

FastAPI = Pydantic(类型校验)+ Starlette(异步 Web 内核)

拆开看:

组件 角色 类比
Starlette Web 底层:接收 HTTP、路由匹配、返回响应、处理异步 相当于 Node 的 Koa
Pydantic 数据校验:请求参数、请求体、响应模型 相当于前端的 Zod

这两个组合起来,带来几个杀手级特性:

  1. 自动生成交互式文档:Swagger UI 和 ReDoc 白送,前后端 API 约定再也不用扯皮
  2. 原生异步无阻塞 :async/await 一把梭,等数据库、等文件 IO、等 LLM 响应都不卡线程
  3. 类型提示驱动:写少量代码就能拿到强类型校验,编辑器补全也舒服
  4. 专为 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. 路径参数(Path)

复制代码
from fastapi import FastAPI, Path
from typing import Annotated

app = FastAPI()

@app.get("/p/{article_id}")
async def article_detail(
    article_id: Annotated[int, Path(ge=2, description="文章ID,必须≥2")]
):
    return {"article_id": article_id}

⚠️ 踩坑提醒 :Path(ge=2) 这种数值约束只能用在 int/float 上。如果你写成 Annotated[str, Path(ge=2)],虽然启动不报错,但校验逻辑是错的(字符串不该比大小)。字符串要用 min_length / max_length。

2. 查询参数(Query)

复制代码
from fastapi import Query

# http://0.0.0.0:8000/article/list?page=2&size=5
@app.get("/article/list")
async def article_list(
    page: Annotated[int, Query(ge=1)] = 1,
    size: Annotated[int, Query(ge=1, le=100)] = 10,
):
    return {"page": page, "size": size}

看到了吗?类型提示 + 默认值 + 校验规则,三个东西一行搞定,FastAPI 会自动做类型转换和校验,参数错了直接返回 422 错误,你一行校验代码都不用写。


五、请求体校验:Pydantic Model 才是主场

GET 请求用 Path/Query 就够了,但 POST/PUT 的请求体,得靠 Pydantic 的 BaseModel:

复制代码
from 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 result

Field:给字段加规则

复制代码
class LoginIn(BaseModel):
    email: Annotated[str, Field(..., description="邮箱地址")]
    password: Annotated[str, Field(
        ...,
        min_length=6,
        max_length=20,
        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,前端看了直呼内行。


六、实战:接入 LangChain,5 分钟搞定一个 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(title="Langchain & FastAPI")

# 初始化 LLM
llm = ChatOpenAI(
    api_key=os.getenv("DEEPSEEK_API_KEY"),
    base_url=os.getenv("DEEPSEEK_BASE_URL"),
    model=os.getenv("DEEPSEEK_MODEL"),
    temperature=0.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", host="0.0.0.0", port=8000, reload=True)

.env 文件长这样:

复制代码
DEEPSEEK_API_KEY=sk-xxxxxxxx
DEEPSEEK_BASE_URL=https://api.deepseek.com/v1
DEEPSEEK_MODEL=deepseek-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(title="Todo 增删改查")

todos: List[dict] = []
next_id = 1

class Todo(BaseModel):
    title: Annotated[str, Field(
        min_length=1,
        max_length=100,
        description="待办事项的标题,1-100 字符"
    )]
    done: Annotated[Optional[bool], Field(default=False, description="是否完成")]

@app.get("/todos", summary="查询所有待办", response_model=List[Todo])
def read_todos():
    return todos

@app.get("/todos/{todo_id}", summary="查询单个待办", response_model=Todo)
def get_todo(
    todo_id: Annotated[int, Field(..., gt=0, description="Todo ID, 必须大于 0")]
):
    for item in todos:
        if item["id"] == todo_id:
            return item
    # ✅ 注意:return 要写在 for 循环外面
    raise HTTPException(status_code=404, detail="找不到这条 todo")

原笔记里的两个坑,一起聊聊

坑 1:return 写在 for 循环里面

原代码是这样:

复制代码
for item in todos:
    if item["id"] == todo_id:
        return item
    return {"msg": "找不到这条todo"}   # ❌ 第一次循环就返回了

这会导致一个问题:只要第一次循环的 id 不匹配,就直接返回"找不到",后面的 todo 永远查不到 。正确写法是 return 挪到循环外面,配合 raise HTTPException 更规范。

坑 2:response_model 与 dict 混用

response_model=Todo 表示返回结果会被 Pydantic 按 Todo 模型过滤和序列化。如果你 return {"msg": "..."} 这种不含 title 的字典,FastAPI 会直接报响应校验错误。所以找不到的时候,用 HTTPException 更符合语义。


八、总结:为什么 FastAPI 值得你投入

最后收个尾,把本文几个关键点串一下:

  1. 架构清晰:Starlette 管 Web 通信,Pydantic 管数据校验,各司其职
  2. 写少做多 :Annotated + BaseModel + Field 三件套,校验 + 文档全自动
  3. 异步友好 :原生 async/await,接 LLM、数据库、文件 IO 都能无阻塞
  4. AI 后端首选 :和 LangChain 集成就是几行代码的事,换模型只需改 base_url
  5. 自动文档:Swagger UI 白送,前后端对接效率翻倍

FastAPI 让你把"接口校验"和"接口文档"这两件最烦的事,变成了写代码时的副产品。

相关推荐
forest_091 小时前
Agent 系列 21|部署、发布与运维:把 Agent 变成一个能上线、能放量、能撤回的服务
人工智能·机器学习·架构·prompt·agi
企业数字化笔记1 小时前
AI视频增强后画质怎么验收?插值基线、伪细节与帧间闪烁的检查方法
人工智能·算法·音视频
十@八九1 小时前
VS Code 接入 AMD Vivado 2026.1 AI 助手:从 VSIX 到 MCP 实战
人工智能·fpga·vs code·vivado·mcp
Binovate缤江数智1 小时前
电动自行车入户充电如何识别?数智开关与AI负荷识别的社区应用实践
人工智能·物联网
布吉岛的石头1 小时前
Java 程序员第 50 阶段3:数据工程:预训练语料清洗与去重
java·开发语言·人工智能·深度学习·transformer
Yula99331 小时前
从“撰写耗时”到“认知卸载”:企业邮件AI的效率边界与工程化路径
人工智能·神经网络·机器学习·数据挖掘·系统安全·信息与通信
橙色云-智橙协同研发1 小时前
核心Know-How不再随人走——智橙AI“智淀“功能详解
大数据·人工智能·ai
武子康2 小时前
Vercel Cache Reason:相同 MISS,怎样决定下一步查什么
人工智能·后端·agent