1. API
前面我们已经使用 Python 编写过函数,也使用 asyncio 执行过异步任务。但网页不能直接导入电脑里的 Python 文件,更不能像调用普通函数一样跨越浏览器和 Python 进程。
如果希望网页把用户问题交给 Python,再取得模型回复,就需要在二者之间建立一组可以通过网络调用的接口,也就是 API,如上案例所示,你可以在本地电脑启动本专栏的案例服务,然后体验接口请求
一次最简单的调用过程如下:
1网页发送 HTTP 请求2↓3FastAPI 找到对应的 Python 函数4↓5Python 函数完成处理6↓7FastAPI 返回 HTTP 响应
HTTP 规定请求和响应如何在网络中传输,JSON 负责表达其中的结构化数据,FastAPI 则负责把 HTTP 世界与 Python 函数连接起来。
例如,网页不能直接执行下面的函数:
1def greeting(name: str) -> str:2return f"你好,{name}"
使用 FastAPI 后,可以为它增加一个 HTTP 入口:
1GET /greeting/小微
收到这个请求时,FastAPI 会提取路径中的名字、调用 Python 函数,再把结果转换成 HTTP 响应。后面的文章会逐步实现这些能力。
2. FastAPI
FastAPI 是一个 Python Web 框架。它主要负责:
- 根据 URL 路径和 HTTP 方法匹配 Python 函数;
- 从路径、查询字符串、请求头和请求体中提取数据;
- 根据 Python 类型提示和 Pydantic 模型解析、校验数据;
- 把 Python 返回值序列化成 HTTP 响应;
- 根据真实接口声明生成 OpenAPI 文档。
在后续项目中,网页只与 FastAPI 通信,FastAPI 再把经过校验的数据交给 LangChain
3. 接口
我们继续使用前面文章创建的 uv 项目。如果项目中还没有 FastAPI,可以安装标准依赖:
1uv add "fastapi[standard]"
standard 会安装 FastAPI 常用的标准依赖,其中包括 FastAPI CLI 和 Uvicorn。依赖声明会写入 pyproject.toml,解析出的具体版本会记录在 uv.lock 中。
在项目根目录创建 main.py:
01from fastapi import FastAPI0203app = FastAPI(04title="Local Agent API",05description="供网页调用的本地 AI 助手服务",06version="0.1.0",07)0809@app.get("/")10async def root() -> dict[str, str]:11return {"message": "Hello FastAPI"}
启动开发服务:
1uv run fastapi dev main.py
FastAPI CLI 会导入 main.py,寻找其中的 FastAPI 应用对象,再使用 Uvicorn 启动开发服务器。默认访问地址是:
1http://127.0.0.1:8000
127.0.0.1 表示当前电脑,8000 是服务监听的端口。http://localhost:8000 通常也会指向同一台电脑。
开发模式会监听代码变化并自动重启。终端需要保持运行,按 Ctrl+C 可以停止服务。
4. 请求
现在打开浏览器,访问:
1http://127.0.0.1:8000/
页面会显示:
1{"message":"Hello FastAPI"}
也可以打开另一个终端,通过 curl 发送同一个请求:
1curl http://127.0.0.1:8000/
如果希望同时查看状态码和响应头,可以添加 -i:
1curl -i http://127.0.0.1:8000/
你会看到类似下面的结果:
1HTTP/1.1 200 OK2content-type: application/json34{"message":"Hello FastAPI"}
至此,我们已经完成了一次完整调用:客户端发送 HTTP 请求,FastAPI 执行 Python 函数,然后返回 JSON 响应。
5. 语法
下面逐行拆解第一个接口。
1from fastapi import FastAPI
这行代码从 fastapi 包中导入 FastAPI 类。它提供了创建应用、注册路由、声明依赖和处理异常等能力。
1app = FastAPI(2title="Local Agent API",3description="供网页调用的本地 AI 助手服务",4version="0.1.0",5)
FastAPI() 调用类并创建实例,变量 app 保存这个实例。它是整个 Web 应用的入口,后续路由、中间件和异常处理器都会注册到这个对象上。
title、description 和 version 是接口文档元数据,也会进入 OpenAPI 描述。这里的 version 表示当前 API 应用版本,不是 Python、FastAPI 或模型版本。
1@app.get("/")2async def root() -> dict[str, str]:3return {"message": "Hello FastAPI"}
@app.get("/") 是路径操作装饰器。它告诉 FastAPI:收到路径为 / 的 GET 请求时,调用下面的 root() 函数。
这几部分分别表示:
| 代码 | 含义 |
|---|---|
app | 当前 FastAPI 应用 |
get | HTTP GET 方法 |
/ | 请求路径 |
root | 处理请求的 Python 函数 |
dict[str, str] | 响应数据的类型声明 |
return | 函数交给 FastAPI 的结果 |
函数名不决定 URL,URL 由装饰器中的路径决定。不过函数名会成为路由名称,并参与默认 OpenAPI operationId 的生成,因此应该清晰、唯一并尽量保持稳定。
6. 时机
初学 FastAPI 时,很容易混淆「启动服务时执行的代码」和「每次请求时执行的代码」。
启动服务时,Python 会导入 main.py:
1导入 FastAPI2↓3创建 app 实例4↓5创建 root 函数6↓7装饰器把 root 注册到 app8↓9开始等待请求
收到 GET / 后,FastAPI 才会执行 root():
1收到请求2↓3匹配 GET 和 /4↓5调用 root()6↓7校验并序列化返回值8↓9发送 HTTP 响应
因此,模块顶部的代码通常在导入时执行,路径函数内部的代码则在请求到来时执行。
下面这种写法会在应用导入时运行一次,而不是每次请求运行:
1print("正在导入 main.py")23app = FastAPI()
开发服务器自动重载后会重新导入模块,所以开发过程中可能看到它打印多次。昂贵模型初始化、连接池和需要清理的资源不应该随意散落在模块顶部,后面的「工程」文章会使用 lifespan 管理它们。
7. HTTP
一次 HTTP 交互分为请求和响应。
客户端发送的请求可以简化为:
1GET / HTTP/1.12Host: 127.0.0.1:8000
其中:
GET是 HTTP 方法,表示读取数据;/是路径,表示当前站点根路径;Host表示目标主机和端口。
服务端返回的响应可以简化为:
1HTTP/1.1 200 OK2Content-Type: application/json34{"message":"Hello FastAPI"}
其中:
200是状态码,表示请求成功;Content-Type表示响应正文采用 JSON;- 空行之后是响应体;
- JSON 是网络文本格式,不是 Python 字典本身。
路径函数返回的是 Python 字典:
1{"message": "Hello FastAPI"}
FastAPI 会根据响应类型校验数据,再把它序列化成 JSON 字节并交给服务器发送。客户端最终收到的是 HTTP 响应,而不是 Python 对象。
8. 类型
FastAPI 会读取函数参数和返回值的类型提示,并把它们用于运行时处理。
在 main.py 中增加一个路径参数接口:
01from fastapi import FastAPI0203app = FastAPI(title="Local Agent API")0405@app.get("/")06async def root() -> dict[str, str]:07return {"message": "Hello FastAPI"}0809@app.get("/users/{user_id}")10async def get_user(user_id: int) -> dict[str, int]:11return {"user_id": user_id}
访问:
1http://127.0.0.1:8000/users/42
路径中的 42 原本来自 URL 文本。FastAPI 根据 user_id: int 把它转换成整数,再传给 get_user()。
返回类型 dict[str, int] 也不只是编辑器提示。FastAPI 会使用它校验和序列化响应,并为 OpenAPI 生成响应 Schema。
如果访问:
1http://127.0.0.1:8000/users/hello
hello 无法转换成整数,FastAPI 会在执行 get_user() 之前返回 422 校验错误。也就是说,路径函数拿到的已经是通过声明规则解析的数据。
简单类型可以直接写在函数签名中。字段较多或存在嵌套结构时,后面会使用 Pydantic 模型描述请求和响应。
9. 文档
服务运行后,可以访问三个文档地址:
| 地址 | 内容 |
|---|---|
/docs | Swagger UI,可填写参数并直接发送请求 |
/redoc | ReDoc 提供的另一种文档界面 |
/openapi.json | 机器可以读取的 OpenAPI Schema |
这三个概念的关系是:
1路径、参数、类型和响应声明2↓3FastAPI 生成 OpenAPI Schema4↓5Swagger UI 和 ReDoc 渲染接口文档
OpenAPI 是描述 HTTP API 的标准,/openapi.json 是当前应用按照该标准生成的数据。Swagger UI 和 ReDoc 是读取这份数据后展示出来的文档界面。
打开 /docs,找到 /users/{user_id},点击 Try it out,输入 42 并执行。你可以直接看到最终请求 URL、响应状态码和响应体。
再输入 hello,观察 422 响应。这个实验能直观看到类型提示不仅用于编辑器,也参与真实 HTTP 请求处理。
自动文档不是额外维护的一份说明。类型、默认值和响应结构越准确,生成的接口契约就越可信。
10. 原理
FastAPI 应用并不是独自完成网络监听、数据校验和异步调度。当前项目中的主要组件可以这样理解:
| 组件 | 职责 |
|---|---|
| FastAPI | 路由、参数解析、依赖、异常和响应 |
| Starlette | FastAPI 使用的底层 Web 与 ASGI 能力 |
| Pydantic | 数据建模、解析、校验和序列化 |
| Uvicorn | 监听端口并运行 ASGI 应用 |
| ASGI | Web 服务器与 Python 应用之间的接口规范 |
浏览器发送请求后,完整调用关系可以简化为:
01浏览器或 curl02↓ HTTP03Uvicorn04↓ ASGI05FastAPI06↓ 路由匹配与数据校验07路径函数08↓ Python 返回值09FastAPI10↓ ASGI11Uvicorn12↓ HTTP13浏览器或 curl
ASGI 规定 Uvicorn 如何把请求事件交给 Python 应用,以及应用如何把响应事件交还给服务器。它是一套接口规范,不是另一个需要单独编写的业务框架。
app = FastAPI() 创建的是 ASGI 应用对象,不是一个已经监听端口的服务器。只有运行 fastapi dev 或其他 ASGI 服务器命令后,程序才会开始监听网络请求。
11. 异步
路径函数可以使用 async def,也可以使用普通 def:
1@app.get("/async")2async def async_route():3result = await async_service()4return result56@app.get("/sync")7def sync_route():8return sync_service()
这里只需要回顾选择原则:
- 调用原生异步 API 时,使用
async def并await; - 必须调用同步阻塞库时,可以使用普通
def路径函数; - 不要在
async def中直接执行耗时同步 IO; - FastAPI 已经管理事件循环,不要在路径函数中调用
asyncio.run()。
上一大章已经解释了事件循环、线程池、超时和 worker。FastAPI 大章会重点学习 HTTP 接口设计,不再重复 asyncio 的内部原理。
12. 错误
第一次运行时,常见问题可以根据状态码或终端信息判断:
| 现象 | 含义 | 检查方向 |
|---|---|---|
404 Not Found | 没有匹配到路径 | 检查 URL 是否与装饰器路径一致 |
405 Method Not Allowed | 路径存在,但方法不匹配 | 检查使用的是 GET、POST 还是其他方法 |
422 | 参数无法通过解析或校验 | 查看响应中的字段位置和错误类型 |
Address already in use | 端口已被其他程序占用 | 停止旧服务或更换端口 |
| 无法导入应用 | 文件、模块或 app 定位失败 | 检查命令、目录和变量名 |
| 修改后没有变化 | 可能没有运行开发重载服务 | 确认当前终端运行的是 fastapi dev |
终端中的 Uvicorn 日志会显示请求方法、路径和状态码。排查时先判断请求有没有到达服务,再判断是路由、校验还是函数内部执行失败。
14. 边界
第一个接口很短,但已经形成了一条完整链路:
1HTTP 请求 -> ASGI 服务器 -> FastAPI -> Python 函数 -> HTTP 响应
学习后续内容时,要始终区分三个层次: