创建时间: 2026-08-25最后更新: 2026-08-27

1. API

预览
本地服务:心跳检测中

前面我们已经使用 Python 编写过函数,也使用 asyncio 执行过异步任务。但网页不能直接导入电脑里的 Python 文件,更不能像调用普通函数一样跨越浏览器和 Python 进程。

如果希望网页把用户问题交给 Python,再取得模型回复,就需要在二者之间建立一组可以通过网络调用的接口,也就是 API,如上案例所示,你可以在本地电脑启动本专栏的案例服务,然后体验接口请求

一次最简单的调用过程如下:

code.ts
1
网页发送 HTTP 请求
2
↓
3
FastAPI 找到对应的 Python 函数
4
↓
5
Python 函数完成处理
6
↓
7
FastAPI 返回 HTTP 响应

HTTP 规定请求和响应如何在网络中传输,JSON 负责表达其中的结构化数据,FastAPI 则负责把 HTTP 世界与 Python 函数连接起来。

例如,网页不能直接执行下面的函数:

greeting.py
1
def greeting(name: str) -> str:
2
return f"你好,{name}"

使用 FastAPI 后,可以为它增加一个 HTTP 入口:

code.ts
1
GET /greeting/小微

收到这个请求时,FastAPI 会提取路径中的名字、调用 Python 函数,再把结果转换成 HTTP 响应。后面的文章会逐步实现这些能力。

2. FastAPI

FastAPI 是一个 Python Web 框架。它主要负责:

  • 根据 URL 路径和 HTTP 方法匹配 Python 函数;
  • 从路径、查询字符串、请求头和请求体中提取数据;
  • 根据 Python 类型提示和 Pydantic 模型解析、校验数据;
  • 把 Python 返回值序列化成 HTTP 响应;
  • 根据真实接口声明生成 OpenAPI 文档。

在后续项目中,网页只与 FastAPI 通信,FastAPI 再把经过校验的数据交给 LangChain

3. 接口

我们继续使用前面文章创建的 uv 项目。如果项目中还没有 FastAPI,可以安装标准依赖:

install.sh
1
uv add "fastapi[standard]"

standard 会安装 FastAPI 常用的标准依赖,其中包括 FastAPI CLI 和 Uvicorn。依赖声明会写入 pyproject.toml,解析出的具体版本会记录在 uv.lock 中。

在项目根目录创建 main.py:

main.py
01
from fastapi import FastAPI
02
03
app = FastAPI(
04
title="Local Agent API",
05
description="供网页调用的本地 AI 助手服务",
06
version="0.1.0",
07
)
08
09
@app.get("/")
10
async def root() -> dict[str, str]:
11
return {"message": "Hello FastAPI"}

启动开发服务:

dev.sh
1
uv run fastapi dev main.py

FastAPI CLI 会导入 main.py,寻找其中的 FastAPI 应用对象,再使用 Uvicorn 启动开发服务器。默认访问地址是:

code.ts
1
http://127.0.0.1:8000

127.0.0.1 表示当前电脑,8000 是服务监听的端口。http://localhost:8000 通常也会指向同一台电脑。

开发模式会监听代码变化并自动重启。终端需要保持运行,按 Ctrl+C 可以停止服务。

4. 请求

现在打开浏览器,访问:

code.ts
1
http://127.0.0.1:8000/

页面会显示:

response.json
1
{"message":"Hello FastAPI"}

也可以打开另一个终端,通过 curl 发送同一个请求:

request.sh
1
curl http://127.0.0.1:8000/

如果希望同时查看状态码和响应头,可以添加 -i:

response-headers.sh
1
curl -i http://127.0.0.1:8000/

你会看到类似下面的结果:

response.txt
1
HTTP/1.1 200 OK
2
content-type: application/json
3
4
{"message":"Hello FastAPI"}

至此,我们已经完成了一次完整调用:客户端发送 HTTP 请求,FastAPI 执行 Python 函数,然后返回 JSON 响应。

5. 语法

下面逐行拆解第一个接口。

import-fastapi.py
1
from fastapi import FastAPI

这行代码从 fastapi 包中导入 FastAPI 类。它提供了创建应用、注册路由、声明依赖和处理异常等能力。

create-app.py
1
app = FastAPI(
2
title="Local Agent API",
3
description="供网页调用的本地 AI 助手服务",
4
version="0.1.0",
5
)

FastAPI() 调用类并创建实例,变量 app 保存这个实例。它是整个 Web 应用的入口,后续路由、中间件和异常处理器都会注册到这个对象上。

title、description 和 version 是接口文档元数据,也会进入 OpenAPI 描述。这里的 version 表示当前 API 应用版本,不是 Python、FastAPI 或模型版本。

path-operation.py
1
@app.get("/")
2
async def root() -> dict[str, str]:
3
return {"message": "Hello FastAPI"}

@app.get("/") 是路径操作装饰器。它告诉 FastAPI:收到路径为 / 的 GET 请求时,调用下面的 root() 函数。

这几部分分别表示:

代码含义
app当前 FastAPI 应用
getHTTP GET 方法
/请求路径
root处理请求的 Python 函数
dict[str, str]响应数据的类型声明
return函数交给 FastAPI 的结果

函数名不决定 URL,URL 由装饰器中的路径决定。不过函数名会成为路由名称,并参与默认 OpenAPI operationId 的生成,因此应该清晰、唯一并尽量保持稳定。

6. 时机

初学 FastAPI 时,很容易混淆「启动服务时执行的代码」和「每次请求时执行的代码」。

启动服务时,Python 会导入 main.py:

code.ts
1
导入 FastAPI
2
↓
3
创建 app 实例
4
↓
5
创建 root 函数
6
↓
7
装饰器把 root 注册到 app
8
↓
9
开始等待请求

收到 GET / 后,FastAPI 才会执行 root():

code.ts
1
收到请求
2
↓
3
匹配 GET 和 /
4
↓
5
调用 root()
6
↓
7
校验并序列化返回值
8
↓
9
发送 HTTP 响应

因此,模块顶部的代码通常在导入时执行,路径函数内部的代码则在请求到来时执行。

下面这种写法会在应用导入时运行一次,而不是每次请求运行:

import-time.py
1
print("正在导入 main.py")
2
3
app = FastAPI()

开发服务器自动重载后会重新导入模块,所以开发过程中可能看到它打印多次。昂贵模型初始化、连接池和需要清理的资源不应该随意散落在模块顶部,后面的「工程」文章会使用 lifespan 管理它们。

7. HTTP

一次 HTTP 交互分为请求和响应。

客户端发送的请求可以简化为:

request.txt
1
GET / HTTP/1.1
2
Host: 127.0.0.1:8000

其中:

  • GET 是 HTTP 方法,表示读取数据;
  • / 是路径,表示当前站点根路径;
  • Host 表示目标主机和端口。

服务端返回的响应可以简化为:

response.txt
1
HTTP/1.1 200 OK
2
Content-Type: application/json
3
4
{"message":"Hello FastAPI"}

其中:

  • 200 是状态码,表示请求成功;
  • Content-Type 表示响应正文采用 JSON;
  • 空行之后是响应体;
  • JSON 是网络文本格式,不是 Python 字典本身。

路径函数返回的是 Python 字典:

python-value.py
1
{"message": "Hello FastAPI"}

FastAPI 会根据响应类型校验数据,再把它序列化成 JSON 字节并交给服务器发送。客户端最终收到的是 HTTP 响应,而不是 Python 对象。

8. 类型

FastAPI 会读取函数参数和返回值的类型提示,并把它们用于运行时处理。

在 main.py 中增加一个路径参数接口:

main.py
01
from fastapi import FastAPI
02
03
app = FastAPI(title="Local Agent API")
04
05
@app.get("/")
06
async def root() -> dict[str, str]:
07
return {"message": "Hello FastAPI"}
08
09
@app.get("/users/{user_id}")
10
async def get_user(user_id: int) -> dict[str, int]:
11
return {"user_id": user_id}

访问:

code.ts
1
http://127.0.0.1:8000/users/42

路径中的 42 原本来自 URL 文本。FastAPI 根据 user_id: int 把它转换成整数,再传给 get_user()。

返回类型 dict[str, int] 也不只是编辑器提示。FastAPI 会使用它校验和序列化响应,并为 OpenAPI 生成响应 Schema。

如果访问:

code.ts
1
http://127.0.0.1:8000/users/hello

hello 无法转换成整数,FastAPI 会在执行 get_user() 之前返回 422 校验错误。也就是说,路径函数拿到的已经是通过声明规则解析的数据。

简单类型可以直接写在函数签名中。字段较多或存在嵌套结构时,后面会使用 Pydantic 模型描述请求和响应。

9. 文档

服务运行后,可以访问三个文档地址:

地址内容
/docsSwagger UI,可填写参数并直接发送请求
/redocReDoc 提供的另一种文档界面
/openapi.json机器可以读取的 OpenAPI Schema

这三个概念的关系是:

code.ts
1
路径、参数、类型和响应声明
2
↓
3
FastAPI 生成 OpenAPI Schema
4
↓
5
Swagger 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路由、参数解析、依赖、异常和响应
StarletteFastAPI 使用的底层 Web 与 ASGI 能力
Pydantic数据建模、解析、校验和序列化
Uvicorn监听端口并运行 ASGI 应用
ASGIWeb 服务器与 Python 应用之间的接口规范

浏览器发送请求后,完整调用关系可以简化为:

code.ts
01
浏览器或 curl
02
↓ HTTP
03
Uvicorn
04
↓ ASGI
05
FastAPI
06
↓ 路由匹配与数据校验
07
路径函数
08
↓ Python 返回值
09
FastAPI
10
↓ ASGI
11
Uvicorn
12
↓ HTTP
13
浏览器或 curl

ASGI 规定 Uvicorn 如何把请求事件交给 Python 应用,以及应用如何把响应事件交还给服务器。它是一套接口规范,不是另一个需要单独编写的业务框架。

app = FastAPI() 创建的是 ASGI 应用对象,不是一个已经监听端口的服务器。只有运行 fastapi dev 或其他 ASGI 服务器命令后,程序才会开始监听网络请求。

11. 异步

路径函数可以使用 async def,也可以使用普通 def:

path-functions.py
1
@app.get("/async")
2
async def async_route():
3
result = await async_service()
4
return result
5
6
@app.get("/sync")
7
def sync_route():
8
return 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. 边界

第一个接口很短,但已经形成了一条完整链路:

code.ts
1
HTTP 请求 -> ASGI 服务器 -> FastAPI -> Python 函数 -> HTTP 响应

学习后续内容时,要始终区分三个层次:

正在验证登录状态
请稍候,验证完成后将继续显示文章内容