2025年10月25日 星期六

從零開始:用 FastAPI 打造一個專業級 AI 服務骨架

從零開始:用 FastAPI 打造一個專業級 AI 服務骨架

摘要

厭倦了從頭寫 API、驗證、限流和模型載入嗎?本文將解析一個完整的 FastAPI AI 服務骨架 (fastapi-ai-starter) 的核心程式碼,讓您在最短的時間內掌握如何建構一個具備:API Key 驗證、速率限制、Mock/Real 模型切換、異步批次推理 等功能的專業級 AI 服務。

您可以透過以下 GitHub 連結檢閱本專案的原始碼:https://github.com/BpsEason/fastapi-ai-starter.git


一、 服務的入口與門戶:app/main.py

這是整個應用程式的啟動點。

程式碼片段
from fastapi import FastAPI
from app.api import predict
# ... 略 ...

def create_app() -> FastAPI:
    """【核心 1:服務啟動入口】初始化 FastAPI 應用程式,並註冊所有路由模組。"""
    app = FastAPI(title="FastAPI AI Starter")
    # 註冊 /v1 路由,包含核心的 /v1/predict
    app.include_router(predict.router, prefix="/v1")
    # ... 略 ...
    return app

app = create_app()

@app.get("/ping")
async def ping():
    """標準健康檢查:確認服務是否運行 (Status: OK)"""
    return {"status": "ok"}
  • 學習重點: 您只需要知道 uvicorn 會運行這個檔案來啟動服務。路由模線(如 predict.router)都是在這裡註冊進來的。

  • 初學建議: 無需修改。


二、 請求與回應的規格:app/schemas.py

這個檔案使用 Pydantic 來定義 API 輸入 (Request) 和輸出 (Response) 的 JSON 資料結構,確保了資料的型別和格式正確性。

程式碼片段
from pydantic import BaseModel, Field
from typing import Optional

class PredictRequest(BaseModel):
    """【核心 3:API 請求的輸入格式】"""
    prompt: str = Field(..., min_length=1, max_length=2000, description="使用者輸入的文字或問題。")
    max_tokens: Optional[int] = Field(128, ge=1, le=2048, description="AI 回應的最大長度。")

class PredictResponse(BaseModel):
    """【核心 3:API 回應的輸出格式】"""
    text: str = Field(..., description="AI 模型生成的文字回應。")
  • 學習重點: Pydantic 提供了自動的資料驗證。

  • 如何修改: 如果您的 AI 模型需要使用者傳入新的參數(例如 temperature: floatlanguage: str),請直接在 PredictRequest 中新增欄位。


三、 API 請求的處理器:app/api/predict.py

這是處理 /v1/predict 請求的函式。它的主要職責是驗證、轉發請求、接收結果並返回

程式碼片段
from fastapi import APIRouter, Depends, HTTPException
# ... 略 ...

router = APIRouter()

@router.post("/predict", response_model=List[PredictResponse], dependencies=[Depends(require_api_key)])
async def predict_endpoint(payload: List[PredictRequest], ai_client = Depends(get_ai_client)):
    """【核心 2:AI 預測 API 處理器】這是接收所有 AI 推理請求的主要路由。"""
    results = []
    try:
        # 1. 提取所有 prompt
        texts = [p.prompt for p in payload]
        
        # 2. 呼叫 AI 核心模組進行批次預測 (關鍵:將所有 AI 邏輯抽象化)
        responses = await ai_client.predict_batch(texts)
        
        # 3. 格式化結果
        for r in responses:
            results.append(PredictResponse(text=r))
            
    except Exception:
        raise HTTPException(status_code=500, detail="Inference failed")
        
    return results
  • 學習重點:

    • dependencies=[Depends(require_api_key)]:這是啟用 API Key 驗證的方式。

    • ai_client = Depends(get_ai_client):FastAPI 會自動注入正確的 AI 服務實例(可能是 Mock 或 Real)。

    • ai_client.predict_batch(texts):所有的 AI 複雜性都被抽象化到這個調用中。


四、 身份驗證與限流:app/middleware.py

這個模組提供了企業級的 API 安全保障,處理金鑰驗證和速率限制,支持內存模式(測試)和 Redis 模式(生產環境)。

程式碼片段
# ... 略 ...
RATE_LIMIT = int(os.getenv("API_RATE_LIMIT_PER_MIN", "120"))  # per key per minute

def require_api_key(request: Request):
    """
    【核心驗證函式】API Key 驗證與速率限制 (Rate Limiting)
    
    檢查 x-api-key 是否存在且有效,並依據 Redis 或內存限制每分鐘請求次數。
    """
    key = request.headers.get("x-api-key")
    if not key:
        raise HTTPException(status_code=401, detail="Missing x-api-key header")

    if USE_REDIS:
        # --- Redis 驗證與限流邏輯 (分佈式) ---
        # ... 略 ...
        pass
            
    else:
        # --- 內存驗證與限流邏輯 (單一程序) ---
        if key not in _KEY_STORE:
            raise HTTPException(status_code=401, detail="Invalid API key")
            
        minute = int(time.time()//60)
        k = (key, minute)
        _RATE_STORE.setdefault(k, 0)
        _RATE_STORE[k] += 1
        
        if _RATE_STORE[k] > RATE_LIMIT:
            raise HTTPException(status_code=429, detail="Rate limit exceeded")
            
    return key
  • 學習重點: 所有的安全檢查都在請求到達 AI 核心邏輯之前完成。

  • 初學建議: 無需修改。 直接使用它提供的強大安全功能。


五、 AI 邏輯的核心:app/ai_client.py

這是您專注於 AI 實作的地方。它巧妙地將模擬模型和真實模型進行了分離。

程式碼片段
# ... 略 ...

class MockModel:
    """【AI 核心-模擬模型】這是您入門 AI 服務的第一個修改點!"""
    def generate(self, prompt: str, max_tokens: int = 128) -> str:
        """
        **初學任務:** 將回傳的字串修改為你喜歡的內容!
        例如:return f"AI 說:收到您的訊息:{s},請稍候!"
        """
        s = prompt.strip()
        # ... 略 ...
        return f"MockReply: {s}" # <--- 修改此行來改變 AI 的回應

class RealAIClient:
    """【AI 核心-真實模型】這是整合 PyTorch/TensorFlow/Hugging Face 等模型的地方。"""
    # ... 載入模型邏輯 ...
    async def predict_batch(self, prompts: List[str], max_tokens: int = 128) -> List[str]:
        # 優化:利用執行緒池並行執行多個推理任務
        loop = asyncio.get_event_loop()
        # ... 略 ...
        return await asyncio.gather(*tasks)

def get_ai_client():
    """依據環境變數 USE_REAL_MODEL 動態決定返回 RealAIClient 或 MockAIClient。"""
    # ... 略 ...
    return MockAIClient() # 預設返回 MockAIClient
  • 學習重點:

    1. Mock 模式 (起步):修改 MockModel.generate 即可控制 AI 回覆。

    2. Real 模式 (進階):在 RealAIClient 中載入您的模型,並修改 predict_batch 進行實際的推理計算。

    3. 異步處理:藉由 asyncioloop.run_in_executor 確保 AI 推理的阻塞操作不會卡住整個 API 服務(這是 FastAPI 服務高併發的關鍵技巧)。


六、 應用程式設定:app/core/config.py

集中的環境變數管理,讓部署和測試變得容易。

程式碼片段
import os
from pydantic import BaseSettings

class Settings(BaseSettings):
    """【應用程式核心設定】所有環境變數的集中管理點。"""
    
    # ... API 安全與驗證設定 ...
    APP_API_KEY: str = os.getenv("APP_API_KEY", "changeme")
    
    # ... 模型控制設定 ...
    USE_REAL_MODEL: bool = os.getenv("USE_REAL_MODEL", "0") == "1"
    """'1' (True) 啟用真實模型,'0' (False) 啟用 Mock 模型。"""
    
    MODEL_PATH: str = os.getenv("MODEL_PATH", "models/real")
    
settings = Settings()

七、 模型載入的預留點:app/models/model_loader.py

雖然目前只有 MockModel,但這個檔案標示了未來擴展真實模型載入的乾淨位置。

程式碼片段
class MockModel:
    """【AI 核心邏輯 - 模擬模型】這是未來 RealModel 載入邏輯的結構參考。"""
    def generate(self, prompt: str, max_tokens: int = 128) -> str:
        # ... 略 ...
        return f"MockReply: {summary}" 

八、 測試與質量保證:tests/test_predict.py

自動化測試是專業開發的標準。這個檔案確保您的 API 始終按預期運行。

程式碼片段
# ... 略 ...
def test_predict_missing_key():
    """【測試案例 2】測試 API Key 遺失時的錯誤處理 (預期 401 錯誤)。"""
    r = client.post("/v1/predict", json=[{"prompt": "hello"}])
    assert r.status_code == 401
    assert r.json()["detail"] == "Missing x-api-key header"

def test_predict_success():
    """【測試案例 3】測試 AI 預測成功並批次處理的能力 (預期 200 成功)。"""
    headers = {"x-api-key": "test-key"}
    # ... 略 ...
    r = client.post("/v1/predict", json=payload, headers=headers)
    
    assert r.status_code == 200
    assert len(r.json()) == 2
    assert r.json()[0]["text"].startswith("MockReply")
  • 學習重點: 運行 pytest 來驗證 API 路由、金鑰驗證和回應格式。


結語

這個 fastapi-ai-starter 專案骨架為您提供了一個功能強大且結構清晰的起點。您無需擔心基礎架構,只需專注於一個檔案:app/ai_client.py,開始將您的 AI 模型整合進去!

現在,您已經擁有了所有程式碼和詳細的教學文件,立即開始您的 AI 服務開發之旅吧!

沒有留言:

張貼留言

📦 LogiFlow WMS:打造 SaaS 多租戶倉儲管理系統的技術實踐

📦 LogiFlow WMS:打造 SaaS 多租戶倉儲管理系統的技術實踐 在企業數位化的浪潮下,倉儲管理系統 (WMS) 不再只是單一公司的內部工具,而是需要支援 多租戶 (Multi-Tenant) 的 SaaS 架構。這意味著系統必須在共享基礎設施的同時,保有嚴格的資...