FastAPI 建立商品描述優化 API

Published February 12, 2025 by 徐培鈞
Python

在現代電商環境中,優質的商品描述能夠提高轉換率,增強搜尋引擎優化(SEO),並提升消費者的購物體驗。

然而,撰寫高品質的商品描述往往需要大量時間與精力。

因此,我們可以運用 AI 來自動優化商品描述,確保內容簡潔、精準,並符合品牌風格。

本篇文章將帶領你使用 FastAPI,搭建一個 AI 驅動的商品描述優化 API,透過 OpenAI GPT-4o 自動優化商品描述,並確保輸出符合 JSON 結構。

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import openai
import os
from tenacity import retry, stop_after_attempt, wait_fixed
from dotenv import load_dotenv
import json
import logging

load_dotenv()
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")

# 初始化 FastAPI
app = FastAPI(title="商品描述優化 API")

# 定義請求 Body
class DescriptionRequest(BaseModel):
    description: str

# 讀取 Prompt 文件
def load_prompt():
    with open("optimize_description_prompt.txt", "r", encoding="utf-8") as file:
        return file.read()


# Retry 機制(API 失敗時最多重試 3 次,每次間隔 2 秒)
@retry(stop=stop_after_attempt(3), wait=wait_fixed(2))
def optimize_description(text: str) -> str:
    prompt = load_prompt().replace("{商品描述}", text)

    try:
        client = openai.OpenAI(api_key=OPENAI_API_KEY)
        response = client.chat.completions.create(  
            model="gpt-4o",
            response_format={"type": "json_object"},
            messages=[{"role": "system", "content": prompt}],
            max_tokens=500
        )

        logging.info(f"OpenAI API 回應: {response}")
        print(f"🔍 OpenAI API 回應: {response}")

        if not response.choices or not response.choices[0].message.content:
            raise HTTPException(status_code=500, detail="❌ OpenAI API 回應為空")

        optimized_text =response.choices[0].message.content.strip() if response.choices else ""

        if not optimized_text:
            raise HTTPException(status_code=500, detail="❌ OpenAI API 回應為空")
        
        try:
            return json.loads(optimized_text)
        except json.JSONDecodeError as e:
            logging.error(f"❌ JSON 解析錯誤: {e}")
            raise HTTPException(status_code=500, detail=f"OpenAI API 回應格式錯誤: {str(e)}")

    except Exception as e:
        logging.error(f"❌ OpenAI API 失敗: {e}")
        raise HTTPException(status_code=500, detail=f"OpenAI API 失敗: {str(e)}")

@app.post("/summarized-description")
async def summarize_description(request: DescriptionRequest):
    if not request.description.strip():
        raise HTTPException(status_code=400, detail="❌ 商品描述不可為空")

    optimized_text = optimize_description(request.description)
    return optimized_text

# 啟動方式(開發模式)
if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

環境設定與安裝 FastAPI

在開始之前,請確保你的系統已安裝 Python 3.8 以上版本,並準備好虛擬環境(建議使用 venv)。

接下來,我們需要安裝 FastAPI 及相關依賴:

安裝必要套件

在終端機執行以下指令,安裝 FastAPI 及 OpenAI 相關套件:

pip install fastapi uvicorn openai pydantic tenacity python-dotenv
  • fastapi:FastAPI 框架
  • uvicorn:ASGI 伺服器(用來啟動 FastAPI 應用)
  • openai:與 OpenAI API 互動
  • pydantic:數據驗證與模型處理
  • tenacity:實現 API 重試機制
  • python-dotenv:載入環境變數(儲存 API 金鑰)

初始化 FastAPI 專案

在專案目錄下,建立一個 Python 檔案 optimize_description.py,並加入以下基本架構:

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import openai
import os
from tenacity import retry, stop_after_attempt, wait_fixed
from dotenv import load_dotenv
import json
import logging

載入環境變數

為了保護 OpenAI API 金鑰,我們將其存放於 .env 檔案:

load_dotenv()
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")

並在 .env 檔案中新增:

OPENAI_API_KEY=你的API金鑰

定義 FastAPI 服務與請求格式

初始化 FastAPI 應用

app = FastAPI(title="商品描述優化 API")

我們將使用 Pydantic 定義請求結構:

class DescriptionRequest(BaseModel):
    description: str

這樣 API 就能接收一個 description 參數,並確保它是 str 類型。


建立 OpenAI API 呼叫邏輯

我們需要讀取 prompt 文件,並將用戶輸入的商品描述嵌入到 prompt 中:

def load_prompt():
    with open("optimize_description_prompt.txt", "r", encoding="utf-8") as file:
        return file.read()

接著,使用 tenacity 增強 API 呼叫的穩定性:

@retry(stop=stop_after_attempt(3), wait=wait_fixed(2))
def optimize_description(text: str) -> str:
    prompt = load_prompt().replace("{商品描述}", text)

    try:
        client = openai.OpenAI(api_key=OPENAI_API_KEY)
        response = client.chat.completions.create(
            model="gpt-4o",
            response_format={"type": "json_object"},
            messages=[{"role": "system", "content": prompt}],
            max_tokens=500
        )

        logging.info(f"OpenAI API 回應: {response}")

        if not response.choices or not response.choices[0].message.content:
            raise HTTPException(status_code=500, detail="❌ OpenAI API 回應為空")

        optimized_text = response.choices[0].message.content.strip() if response.choices else ""

        if not optimized_text:
            raise HTTPException(status_code=500, detail="❌ OpenAI API 回應為空")
        
        try:
            return json.loads(optimized_text)
        except json.JSONDecodeError as e:
            logging.error(f"❌ JSON 解析錯誤: {e}")
            raise HTTPException(status_code=500, detail=f"OpenAI API 回應格式錯誤: {str(e)}")

    except Exception as e:
        logging.error(f"❌ OpenAI API 失敗: {e}")
        raise HTTPException(status_code=500, detail=f"OpenAI API 失敗: {str(e)}")

自動重試機制(@retry 裝飾器)

@retry(stop=stop_after_attempt(3), wait=wait_fixed(2))

這行使用了 tenacity 套件提供的 @retry 裝飾器,確保 API 請求發生錯誤時:

  • 最多重試 3 次stop_after_attempt(3)
  • 每次重試間隔 2 秒wait_fixed(2)

這樣可以增加 API 呼叫的穩定性,避免因網路問題或 OpenAI 服務臨時不可用而導致請求失敗。

讀取 Prompt 並替換商品描述

prompt = load_prompt().replace("{商品描述}", text)

這行程式碼:

  1. 調用 load_prompt() 讀取 optimize_description_prompt.txt 檔案內容
  2. 使用 .replace("{商品描述}", text),將 {商品描述} 替換為用戶提供的 text

這樣就可以確保發送給 OpenAI 的 prompt 內容是完整的。

呼叫 OpenAI API

client = openai.OpenAI(api_key=OPENAI_API_KEY)
response = client.chat.completions.create(
    model="gpt-4o",
    response_format={"type": "json_object"},
    messages=[{"role": "system", "content": prompt}],
    max_tokens=500
)

這段程式碼:

  1. 初始化 OpenAI 客戶端,使用 OPENAI_API_KEY 進行身份驗證。
  2. 呼叫 OpenAI API 來生成優化後的商品描述:
    • 使用的模型是 gpt-4o(最新的 OpenAI 生成模型)。
    • 設定回應格式為 JSONresponse_format={"type": "json_object"}),確保回傳的結果是結構化數據。
    • 將 Prompt 發送給 OpenAI,作為 messages 參數中的 system 消息。
    • 限制最大 Token 數量為 500,避免輸出過長。

檢查 OpenAI API 回應

logging.inf(f"OpenAI API 回應: {response}")

這行程式碼將 OpenAI API 的回應記錄在日誌中,方便偵錯。

if not response.choices or not response.choices[0].message.content:
    raise HTTPException(status_code=500, detail="❌ OpenAI API 回應為空")

這行程式碼:

  1. 檢查 response.choices 是否存在,確保 API 有返回結果。
  2. 檢查 response.choices[0].message.content 是否有內容,確保 AI 確實產生了文字。
  3. 如果沒有回應內容,就 拋出 HTTP 500 錯誤,通知用戶發生錯誤。

處理 OpenAI 回傳的內容

optimized_text = response.choices[0].message.content.strip() if response.choices else ""

這行程式碼:

  1. 從 API 回應中取得內容
  2. 使用 .strip() 去除前後空白,確保輸出格式乾淨。
  3. 如果內容為空,則拋出錯誤
if not optimized_text:
    raise HTTPException(status_code=500, detail="❌ OpenAI API 回應為空")

嘗試解析 JSON

try:
    return json.loads(optimized_text)
except json.JSONDecodeError as e:
    logging.error(f"❌ JSON 解析錯誤: {e}")
    raise HTTPException(status_code=500, detail=f"OpenAI API 回應格式錯誤: {str(e)}")

這段程式碼:

  1. 使用 json.loads() 解析 API 回應內容,確保回傳結果是有效的 JSON。
  2. 如果 JSON 解析失敗(格式錯誤),則記錄錯誤並拋出 HTTP 500 錯誤。

全域錯誤處理

except Exception as e:
    logging.error(f"❌ OpenAI API 失敗: {e}")
    raise HTTPException(status_code=500, detail=f"OpenAI API 失敗: {str(e)}")

這段程式碼:

  1. 捕捉所有異常(Exception),避免未處理的錯誤導致 API 崩潰。
  2. 記錄錯誤訊息,方便日誌追蹤。
  3. 返回 HTTP 500 錯誤,告知請求失敗。

定義 FastAPI 端點

接下來,我們建立一個 POST API 端點,接收商品描述並返回優化結果:

@app.post("/summarized-description")
async def summarize_description(request: DescriptionRequest):
    if not request.description.strip():
        raise HTTPException(status_code=400, detail="❌ 商品描述不可為空")

    optimized_text = optimize_description(request.description)
    return optimized_text

這個 API 端點:

  1. 檢查請求內容,確保 description 不是空白
  2. 呼叫 optimize_description(),透過 OpenAI 進行優化
  3. 返回優化結果(JSON 格式)

啟動 API 服務

在終端機執行以下指令,啟動 FastAPI 伺服器:

uvicorn optimize_description:app --host 0.0.0.0 --port 8000 --reload

或者在 Python 代碼中加入:

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

當服務成功啟動,會顯示類似:

INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

你可以透過瀏覽器或 curl 測試:

curl -X 'POST' 'http://127.0.0.1:8000/summarized-description' \
     -H 'Content-Type: application/json' \
     -d '{"description": "這是一款高品質的無線藍牙耳機,具備降噪功能"}'

API 測試與錯誤處理

我們可以透過 pytest 測試 API 是否正常運行,並確保 JSON 格式正確:

def test_api():
    import requests
    url = "http://127.0.0.1:8000/summarized-description"
    data = {"description": "高效能筆記型電腦,適合遊戲與商務使用"}
    
    response = requests.post(url, json=data)
    assert response.status_code == 200
    print(response.json())

常見錯誤處理:

  • API 金鑰無效 → 確保 .env 設定正確
  • 輸入內容為空 → 返回 400 Bad Request
  • OpenAI API 失敗 → 自動重試(最多 3 次)

結論

本篇文章介紹了如何使用 FastAPI 搭建 商品描述優化 API,透過 OpenAI GPT-4o 自動優化內容,並確保 API 穩定運行。