Day 09 / 共 68 天 · 阶段 1 编程与工具地基
FastAPI:做一个自己的服务
前两天你学会了调别人的 API(Day 07)、以及让请求并发不干等(Day 08)。今天完成阶段 1 的收官——用 FastAPI 把你自己的功能做成一个 API 服务,让别人(甚至你自己的 Agent)能调你。学完就正式进入阶段 2:大模型(Day 10)。
📍 你在 68 天里的位置(阶段 1:编程与工具地基 · Day 3-9 收官)
D03 Python 基础→
D06 命令行&Git→
D07 调 API→
D08 异步→
D09 FastAPI
💡 先用一个类比兜住今天(今天世界观:从"顾客"变"开店老板")
前两天你是去别人餐厅点餐的顾客;今天你自己开一家店。FastAPI 是这家店的经营系统:
@app.get/@app.post 是你菜单上的一道道菜(能被点的接口);pydantic 是点单员,客人下单时它先核对"这单填得对不对、缺没缺料",不合格当场退回;uvicorn 是把卷帘门拉开、开门营业的那个动作;/docs 则是店门口自动立起来的带图菜单牌,客人照着就会点。记住"开店"这个画面,今天全通。L01
从"调 API"到"做 API"
🤔 痛点你写了个很棒的函数(比如"给一段文字算情感分"),但它只能在你自己的电脑、你自己的脚本里跑。别人怎么用?前端网页怎么调?
💡 本质把它包成一个 API 服务——对外开一个网址,别人发 HTTP 请求就能用你的功能,不用看你的代码、不用装你的环境。"做 API"就是把一个函数变成一道"谁都能点的菜"。
FastAPI 是目前 Python 最流行的做 API 的框架,特点正好命中我们前两天学的:
- 快、天生异步:底层就是 Day 08 的
async,扛并发强; - 自带数据校验:靠 pydantic 自动检查请求数据对不对(L04);
- 自动生成文档:白送一个交互式接口文档页
/docs(L06); - Agent 生态标配:后面你把 Agent 部署上线,几乎都是包成 FastAPI 服务。
👶 一句话“做 API” = 把你的函数开一扇门对外营业,别人发个请求就能用,像开了家外卖店。
L02
第一个接口:@app.get
💡 本质先装:
pip install "fastapi[standard]"(会一并装上运行用的 uvicorn)。一个接口 = 一个普通函数 + 一行 装饰器 @app.get("/路径"),意思是"当有人 GET 访问这个路径,就运行下面这个函数"。# 存成 main.py
from fastapi import FastAPI
app = FastAPI() # 造一个应用,就是“这家店”本体
@app.get("/") # 有人 GET 访问根路径 "/" 就跑下面这个函数
def home():
return {"message": "你好,这是我的第一个服务"} # 返回字典,FastAPI 自动转成 JSON
@app.get("/hello/{name}") # {name} 是路径参数,会自动接住
def hello(name: str): # name: str 声明类型,FastAPI 据此校验
return {"greeting": f"你好,{name}"}
三个要点:①
@app.get(...) 上面加一行就把普通函数变成了"能被点的菜";② 直接 return 一个字典,FastAPI 帮你转 JSON、设好响应头;③ 参数写上类型注解(name: str),FastAPI 会自动校验和转换。📝 例子:访问会得到什么
浏览器打开
打开
http://127.0.0.1:8000/ → {"message":"你好,这是我的第一个服务"}打开
http://127.0.0.1:8000/hello/小明 → {"greeting":"你好,小明"}(小明 被 {name} 接住了)
L03
uvicorn:拉开卷帘门开门营业
🤔 痛点代码写好了,但直接
python main.py 什么也不会发生——谁来"守在门口"接收网络请求?💡 本质需要一个 ASGI 服务器来运行它,最常用的是 uvicorn。它就是"开门营业"的开关:常驻在某个端口(默认 8000),把进来的 HTTP 请求交给你的
app 处理。# 在 main.py 所在目录运行:
uvicorn main:app --reload
# └────┬───┘ └──┬───┘
# 文件名:变量名 --reload=你改代码后自动重启(开发期很爽)
# 看到这行就成功了:
# Uvicorn running on http://127.0.0.1:8000
现在服务在跑了。用昨天学的 requests 或浏览器都能访问——你昨天当"顾客"调的接口,今天变成了你自己"开的店":
图注:uvicorn 守在 8000 端口收请求,FastAPI 按路径分给对应的函数,结果再原路返回。
L04
pydantic:请求数据的"点单员"
🤔 痛点别人给你的接口发数据,万一少填了字段、或把年龄填成"三十岁"这种非数字,你的函数不就崩了?总不能每个字段都手写一堆
if 检查吧。💡 本质用 pydantic:你只需声明"我期望的数据长什么样"(哪些字段、什么类型),它就在请求进函数之前自动核对,不合格直接回
422 错误,根本进不了你的函数。pydantic 是站在门口的点单员,帮你把关。from pydantic import BaseModel
class AskRequest(BaseModel): # 继承 BaseModel,就成了一个“数据模型”
question: str # 必填,字符串
max_words: int = 100 # 可选,整数,不填默认 100
# 客户端发来的 JSON 会被自动校验并转成这个对象
📝 例子:点单员当场退回不合格的单
客户端发
客户端发
客户端漏了
{"question": "你好", "max_words": 50} → ✅ 通过,max_words 是 50。客户端发
{"question": "你好", "max_words": "很多"} → ❌ FastAPI 自动回 422,说"max_words 应该是整数"——你一行校验代码都没写。客户端漏了
question → ❌ 自动回 422,说"question 是必填的"。
为什么这对 Agent 特别重要:pydantic 这套"声明结构 + 自动校验"后面到处都是——结构化输出(Day 14)、function calling 的参数 schema(Day 29)、LangGraph 的 State,全建立在它之上。今天先混个脸熟。
L05
@app.post:接收提交的内容
💡 本质回忆 Day 07:提交内容用 POST。把上面 pydantic 模型写进函数参数,FastAPI 就会自动把请求体的 JSON 解析成对象、校验好,再交给你——这就是之后"调大模型"接口的标准长相。
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class AskRequest(BaseModel):
question: str
max_words: int = 100
@app.post("/ask") # 有人 POST /ask 就跑这个
def ask(req: AskRequest): # 参数类型写成模型,FastAPI 自动解析+校验请求体
# 这里将来会换成“调大模型”,现在先用假逻辑演示
answer = f"(示例回答)你问的是:{req.question},限 {req.max_words} 字"
return {"answer": answer, "length": len(answer)} # 返回字典 → 自动转 JSON
用 Day 07 的 requests 来调自己刚开的这家"店",闭环就成了:
import requests
resp = requests.post("http://127.0.0.1:8000/ask",
json={"question": "什么是 Agent?", "max_words": 30},
timeout=10)
print(resp.status_code) # 200
print(resp.json()["answer"]) # (示例回答)你问的是:什么是 Agent?,限 30 字
👶 小白:FastAPI 不是天生异步吗?为什么这里的函数用的是普通 def 而不是 async def?
👨🏫 老师:好问题!两种都行。函数里如果有 await(比如要 await 调大模型、查数据库),就写 async def;如果只是普通同步逻辑,写普通 def 也完全 OK,FastAPI 会自动妥善处理、不会堵住别的请求。规律:函数体里要 await 就用 async def,否则普通 def。后面接大模型时你就会大量用到 async def。
L06
/docs:白送的交互式菜单牌
💡 本质FastAPI 最讨人喜欢的一点:只要服务在跑,浏览器打开
http://127.0.0.1:8000/docs,就有一个自动生成的交互式文档页——列出所有接口、每个要传什么、还能直接在网页上点 "Try it out" 发请求测试。你一行文档都没写,它照着你的类型注解和 pydantic 模型自动生成。它解决了真实开发里一个大痛点:前端/同事不用问你"这接口咋调",打开 /docs 自己就看明白了。这也是把 Agent 交付给别人时最省事的"说明书"。
📝 例子:/docs 上你会看到
一条
POST /ask 接口,展开后自动显示:需要传 question(字符串,必填) 和 max_words(整数,默认100);点 "Try it out" 填好点 "Execute",页面当场显示返回的 JSON 和状态码。调试接口再也不用切到别的工具。
顺带一提:还有个
/redoc 是同样内容的另一种排版风格。两者都自动免费,随便用。L07
今日小结 + 动手 10 分钟(阶段 1 收官)
🧠 今天你应该能回答
- "做 API"和"调 API"有啥区别?(做 = 把自己的函数对外开成服务;调 = 用别人的)
@app.get/@app.post干嘛的?(把普通函数注册成一个能被访问的接口,取信息用 get、提交内容用 post)- uvicorn 是什么角色?(运行 FastAPI 的服务器,守在端口收请求,
uvicorn main:app --reload) - pydantic 帮你做了什么?(声明数据结构,自动校验请求数据,不合格回 422)
/docs是什么?(自动生成的交互式接口文档,能直接测试)
✋ 动手:15 行代码开一家自己的"店"
# 1. 安装(含 uvicorn)
pip install "fastapi[standard]"
# 2. 存成 main.py
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class AskRequest(BaseModel):
question: str
max_words: int = 100
@app.get("/")
def home():
return {"status": "ok", "hint": "打开 /docs 试试"}
@app.post("/ask")
def ask(req: AskRequest):
return {"answer": f"你问了:{req.question}", "limit": req.max_words}
# 3. 启动服务
uvicorn main:app --reload
# 4. 浏览器打开这个地址,直接在网页上点 Try it out 调你的接口:
# http://127.0.0.1:8000/docs
进阶挑战:再加一个 @app.get("/health") 返回 {"ok": True}(真实线上服务几乎都有这么个"健康检查"接口,供监控探活)。刷新 /docs 看它自动出现在文档里。
🎉 阶段 1 完成!明天预告 · Day 10 大模型是什么:地基打好了——你会 Python、会命令行/Git、会调 API、懂异步、能自己开一个服务。明天正式踏进阶段 2,掀开主角的盖头:大模型到底是什么?为什么说它只是在"预测下一个词"?什么是 token、上下文窗口?"幻觉"又是从哪冒出来的?用大白话把这个神秘黑箱讲透。