在昨天的文章中,我們順利架設了 FastAPI 微服務專案的骨架,完成了系統健康檢查端點,並驗證了 Swagger UI 自動生成文件的便利性。
既然我們的目標是打造一個專屬的 美食餐飲推薦 Agent,那麼後端的核心工作之一,就是確保流經系統的資料結構明確且嚴謹。今天我們將利用 FastAPI 內建的強大型別工具 Pydantic,定義使用者的點餐需求與餐廳餐點的模型結構(Schema),並實作第一隻模擬推薦 API!
在串接 AI Agent 與外部應用(如 LINE Bot、Webhook)時,使用者輸入的資料經常五花八門(例如忘記帶預算、字串格式不對、缺漏必填欄位等)。Pydantic 具備以下優勢:
"200",Pydantic 能自動轉換為整數 200,格式不符則即時拋出清楚的 422 錯誤。Field 輕鬆設定最小值、最大值、長度限制與預設值。/docs 互動文件上,利於除錯與後續團隊協作。我們在 app 資料夾下新增一個 schemas 模組,專門管理所有資料結構定義。
請確認當前虛擬環境已啟用,目錄結構新增如下:
agent-backend/
├── app/
│ ├── schemas/
│ │ ├── __init__.py
│ │ └── food.py # 美食與推薦相關的 Pydantic 模型
建立 app/schemas/food.py,定義三個關鍵模型:
UserPreference:使用者的偏好與查詢條件(預算、飲食偏好、所在區域等)。
RestaurantItem:單一餐廳或餐點的詳細資訊。
RecommendationResponse:最終回傳給使用者或 Agent 的推薦清單。
from pydantic import BaseModel, Field
from typing import List, Optional
from enum import Enum
class CuisineType(str, Enum):
ALL = "全部"
JAPANESE = "日式"
KOREAN = "韓式"
TAIWANESE = "台式"
DESSERT = "甜點/下午茶"
CAFE = "咖啡廳"
WESTERN = "西式"
class UserPreference(BaseModel):
user_id: str = Field(..., description="使用者唯一識別碼", example="user_12345")
cuisine: CuisineType = Field(default=CuisineType.ALL, description="料理類型")
budget_max: int = Field(default=500, ge=50, le=5000, description="每人預算上限 (50~5000 元)")
location: str = Field(default="板橋區", description="目標搜尋區域或商圈")
notes: Optional[str] = Field(None, description="特殊飲食備註,例如:不吃香菜、要甜點、寵物友善")
class RestaurantItem(BaseModel):
id: str = Field(..., description="餐廳唯一識別碼")
name: str = Field(..., description="餐廳名稱")
cuisine: CuisineType = Field(..., description="料理分類")
price_avg: int = Field(..., description="平均每人消費")
location: str = Field(..., description="所在地址或區域")
rating: float = Field(..., ge=1.0, le=5.0, description="Google 評分 (1.0~5.0)")
signature_dish: str = Field(..., description="招牌餐點")
reason: Optional[str] = Field(None, description="推薦理由")
class RecommendationResponse(BaseModel):
status: str = Field(default="success")
total_found: int = Field(..., description="符合條件的數量")
recommendations: List[RestaurantItem] = Field(..., description="推薦餐廳清單")
有了 Schema 之後,我們來實作模擬推薦端點。這裡先準備一批精選的模擬餐廳資料庫(Mock DB),並根據使用者的偏好進行過濾篩選。
建立 app/routers/recommendation.py:
from fastapi import APIRouter, HTTPException
from typing import List
from app.schemas.food import UserPreference, RestaurantItem, RecommendationResponse, CuisineType
router = APIRouter(
prefix="/api/v1/recommend",
tags=["Food Recommendation"]
)
MOCK_RESTAURANTS: List[RestaurantItem] = [
RestaurantItem(
id="rest_01",
name="恬淡日常法式甜點",
cuisine=CuisineType.DESSERT,
price_avg=280,
location="板橋區",
rating=4.7,
signature_dish="草莓戚風蛋糕、伯爵生乳捲",
reason="巷弄隱密甜點店,氣氛溫馨悠閒,適合放鬆聊天。"
),
RestaurantItem(
id="rest_02",
name="炙燒極味鐵板燒",
cuisine=CuisineType.TAIWANESE,
price_avg=320,
location="江子翠",
rating=4.5,
signature_dish="香煎比目魚、起司牛排卷",
reason="江子翠捷運站旁平價鐵板燒首選,師傅現炒香氣十足。"
),
RestaurantItem(
id="rest_03",
name="沐木手沖咖啡館",
cuisine=CuisineType.CAFE,
price_avg=190,
location="台中市西區",
rating=4.8,
signature_dish="淺焙花香手沖、焦糖肉桂卷",
reason="採光極佳的老宅選店,甜點與單品咖啡皆在水準之上。"
),
RestaurantItem(
id="rest_04",
name="春日豚骨拉麵",
cuisine=CuisineType.JAPANESE,
price_avg=260,
location="新莊區",
rating=4.6,
signature_dish="特濃黑蒜油豚骨拉麵",
reason="湯頭濃郁甘醇,麵條硬度與濃淡可自由客製。"
),
]
@router.post("/", response_model=RecommendationResponse)
async def recommend_food(preference: UserPreference):
根據使用者輸入的預算、料理偏好與區域,過濾並回傳推薦餐廳清單
matched_results: List[RestaurantItem] = []
for item in MOCK_RESTAURANTS:
# 1. 預算過濾
if item.price_avg > preference.budget_max:
continue
# 2. 料理類型過濾 (若非 ALL 且不符則略過)
if preference.cuisine != CuisineType.ALL and item.cuisine != preference.cuisine:
continue
# 3. 區域模糊比對 (若設定的區域與餐廳區域不匹配)
if preference.location not in item.location and item.location not in preference.location:
continue
matched_results.append(item)
# 若找不到符合條件的店家,回傳自訂訊息或拋出提示
return RecommendationResponse(
status="success",
total_found=len(matched_results),
recommendations=matched_results
)
修改 app/main.py,將新建好的 recommendation.py 路由掛載至 FastAPI 應用實例:
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.routers import health, recommendation
app = FastAPI(
title="Food Agent Microservice API",
description="智慧餐飲推薦與工作流核心後端服務",
version="1.0.0"
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 註冊路由
app.include_router(health.router)
app.include_router(recommendation.router)
@app.get("/")
async def root():
return {
"message": "Welcome to Food Agent API",
"docs_url": "/docs"
}
確認 Uvicorn 伺服器仍在執行中(或於終端機執行 uvicorn app.main:app --reload):
1.打開瀏覽器訪問 http://localhost:8000/docs。
2.在 Food Recommendation 分組下展開 POST /api/v1/recommend/。
3.點擊 Try it out,輸入測試 JSON 參數(例如想在板橋找 400 元內的甜點店):
{
"user_id": "wanglulu_01",
"cuisine": "甜點/下午茶",
"budget_max": 400,
"location": "板橋",
"notes": "想找適合放鬆的安靜甜點店"
}
1.點擊 Execute,確認伺服器回傳 200 OK,並精準過濾出「恬淡日常法式甜點」:
{
"status": "success",
"total_found": 1,
"recommendations": [
{
"id": "rest_01",
"name": "恬淡日常法式甜點",
"cuisine": "甜點/下午茶",
"price_avg": 280,
"location": "板橋區",
"rating": 4.7,
"signature_dish": "草莓戚風蛋糕、伯爵生乳捲",
"reason": "巷弄隱密甜點店,氣氛溫馨悠閒,適合放鬆聊天。"
}
]
}
今日結語與明日預告
今天我們透過 Pydantic 定義出了嚴謹的餐飲推薦資料規範,並在 FastAPI 中實作了具備篩選邏輯的推薦端點。這代表我們的後端系統已經具備了真正的商業邏輯運算能力!
明天 Day 06,我們將進入下一個重頭戲:串聯自動化流程!在 n8n 中調用 FastAPI 推薦端點,實現多系統間的資料流轉,敬請期待!