/recommend
和 /log-ab-event
兩個端點。在前兩天 (Day 22–23),我們:
/recommend
。/log-ab-event
,可以把使用者行為存進 workspace/logs/ab_events.csv
。但目前這兩個 API 都屬於「prototype」版本:
今天我們要讓它變得「像企業 API 一樣」穩健、可維護。
📂 修改 src/api/main.py
為以下內容(完整可用版):
from fastapi import FastAPI, HTTPException, Query
from pydantic import BaseModel, ValidationError
import mlflow
import mlflow.pyfunc
import pandas as pd
import os, csv
from datetime import datetime
# === 初始化 FastAPI ===
app = FastAPI(
title="Anime Recommender API",
description="FastAPI + MLflow 企業級推薦系統",
version="2.2.0"
)
# === Pydantic 輸入模型 ===
class RecommendRequest(BaseModel):
anime_titles: list[str]
class ABEvent(BaseModel):
user_id: str
model_version: int
recommended_title: str
clicked: bool
timestamp: datetime = datetime.utcnow()
# === 設定 MLflow ===
mlflow.set_tracking_uri("http://mlflow:5000")
model_cache = {} # 模型快取,避免重複載入
def get_model(model_name: str):
"""依照模型名稱載入模型,若不存在則回傳 404"""
if model_name not in model_cache:
model_uri = f"models:/{model_name}/Staging"
print(f"📦 Loading {model_uri} ...")
try:
model_cache[model_name] = mlflow.pyfunc.load_model(model_uri)
except Exception:
raise HTTPException(status_code=404, detail=f"Model '{model_name}' not found in Registry.")
return model_cache[model_name]
# === 健康檢查 ===
@app.get("/health")
def health_check():
return {"status": "ok", "message": "FastAPI is running 🚀"}
# === 推薦 API ===
@app.post("/recommend")
def recommend(request: RecommendRequest, model_name: str = Query("AnimeRecsysModel")):
try:
if not request.anime_titles:
raise HTTPException(status_code=400, detail="anime_titles cannot be empty.")
model = get_model(model_name)
df = pd.DataFrame(request.anime_titles)
result = model.predict(df)
return {
"model_name": model_name,
"input": request.anime_titles,
"recommendations": result[0]
}
except ValidationError as ve:
raise HTTPException(status_code=422, detail=ve.errors())
except HTTPException as he:
raise he
except Exception as e:
raise HTTPException(status_code=500, detail=f"Internal Error: {str(e)}")
# === AB Test 記錄 API ===
@app.post("/log-ab-event")
def log_ab_event(event: ABEvent):
LOG_DIR = "/usr/mlflow/workspace/logs"
os.makedirs(LOG_DIR, exist_ok=True)
log_path = os.path.join(LOG_DIR, "ab_events.csv")
try:
file_exists = os.path.isfile(log_path)
with open(log_path, mode="a", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
if not file_exists:
writer.writerow(["timestamp", "user_id", "model_version", "recommended_title", "clicked"])
writer.writerow([
event.timestamp.isoformat(),
event.user_id,
event.model_version,
event.recommended_title,
event.clicked
])
return {"message": "Event logged successfully ✅", "event": event.dict()}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to write log: {e}")
功能 | 說明 |
---|---|
model_cache |
避免每次都重新載入 MLflow 模型,提高效能。 |
HTTPException |
統一錯誤格式,避免 Python Traceback。 |
驗證空輸入 | 防止空列表造成模型出錯。 |
模型不存在處理 | 找不到模型名稱時回傳 HTTP 404。 |
CSV 寫入錯誤處理 | 目錄不存在、磁碟滿等錯誤會回傳 HTTP 500。 |
打開 Postman,設定環境變數:
base_url = http://127.0.0.1:8000
Method: POST
URL: {{base_url}}/recommend?model_name=AnimeRecsysModel
Body (JSON):
{
"anime_titles": ["Naruto", "Bleach"]
}
{
"anime_titles": []
}
POST /recommend?model_name=WrongModel
URL: {{base_url}}/log-ab-event
Body (JSON):
{
"user_id": "u123",
"model_version": 8,
"recommended_title": "Steins;Gate",
"clicked": true
}
{
"user_id": "u123"
}
測試名稱 | 方法 | URL | 狀況 | 預期 |
---|---|---|---|---|
推薦成功 | POST | /recommend | 正常輸入 | HTTP 200 |
模型不存在 | POST | /recommend?model=Wrong | 錯誤模型名 | HTTP 404 |
空輸入 | POST | /recommend | 空清單 | HTTP 400 |
紀錄成功 | POST | /log-ab-event | 正確輸入 | 寫入 CSV |
缺欄位 | POST | /log-ab-event | 少欄位 | HTTP 422 |
所有錯誤現在都會以 FastAPI 預設結構回傳:
{
"detail": "anime_titles cannot be empty."
}
或複雜格式:
{
"detail": [
{
"loc": ["body", "model_version"],
"msg": "field required",
"type": "value_error.missing"
}
]
}
這讓前端(Streamlit 或 Web App)能輕鬆解析錯誤狀態。
若未來要在企業環境使用,可以加上:
@app.exception_handler(Exception)
自訂統一格式,例如:
{"status":"error","message":"Internal Server Error","code":500}
並透過 Loguru 或 Sentry 收集 log。
/recommend
與 /log-ab-event
已具備完整錯誤處理。Day 25 我們將進入最後一篇 FastAPI 部分:
建立第二個模型並實作 A/B 測試分流機制
透過時間戳記的不同,可以看到不同模型的推薦內容。