(三十八)把保存模型接回 FastAPI
训练完成后,服务启动只加载选定权重,后续请求做输入转换和前向预测。接口复用已经写好的 Predictor:
# 项目文件:training_project/src/readingtorch/api.py
from contextlib import asynccontextmanager
import os
from fastapi import FastAPI
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field
from .predict import Predictor
class ReadingInput(BaseModel):
minutes: float = Field(gt=0, allow_inf_nan=False)
sessions: int = Field(ge=1, le=14)
class ReadingOutput(BaseModel):
pages: float
def create_app(checkpoint=None):
@asynccontextmanager
async def lifespan(app):
# 模型在启动时装入一次,后续请求复用只做推理的 Predictor。
app.state.predictor = Predictor(checkpoint or os.environ["READING_MODEL"])
yield
app = FastAPI(title="阅读实验预测", lifespan=lifespan)
@app.exception_handler(RequestValidationError)
async def input_error(request, error):
# 错误响应只组织字段位置和说明,NaN 等原始输入也不会进入 JSON。
detail = [
{key: item[key] for key in ["type", "loc", "msg"]}
for item in error.errors()
]
return JSONResponse({"detail": detail}, status_code=422)
@app.post("/predict", response_model=ReadingOutput)
def predict(body: ReadingInput):
return {"pages": app.state.predictor.pages(body.minutes, body.sessions)}
return applifespan 在启动时创建 Predictor 并保存到应用状态;路由读取同一个对象。ReadingInput 把分钟数与次数转换为合格数值,ReadingOutput 说明响应页数。字段错误响应只留下位置和原因,输入中的 NaN 等原始值不会被再次写进 JSON。
# 在 training_project 中安装接口依赖,选择刚才验证集选中的回归模型。
uv sync --locked --extra serve
$env:READING_MODEL = (Resolve-Path runs/regression/best.pt).Path
uv run uvicorn readingtorch.api:create_app --factory --host 127.0.0.1 --port 8776打开 接口文档,向 /predict 提交:
{
"minutes": 35,
"sessions": 3
}
其中 minutes 是阅读分钟数,sessions 是次数,响应包含约 84.011 的 pages。也可以在另一个终端请求:
# HTTPX 发送 JSON,请求中的字段由同一个输入模型检查。
uv run python -c "import httpx; print(httpx.post('http://127.0.0.1:8776/predict', json={'minutes':35,'sessions':3}).json())"
这里每次请求都处于评估与推理流程,没有反向或优化步骤;训练仍从独立命令进入。模型从文件来到服务,与前面从数据库读取记录再响应的结构已经能够接起来。