今天,我們要讓整個推薦系統從後端 API,
正式長出一個「可互動的前端」!
透過 Streamlit,我們將實作一個簡潔、即時互動的 UI,
使用者可以輸入暱稱、挑選喜歡的動畫,
系統會呼叫 FastAPI /recommend
,
並以條列清單輸出推薦結果。
整體結構如下圖所示:
📦 anime-recsys/
┣ 📁 docker/ # Docker 設定
┃ ┣ Dockerfile.streamlit
┃ ┗ requirements-streamlit.txt
┣ 📁 notebooks/ # 放置資料集 (anime_clean.csv)
┃ ┗ data/anime_clean.csv
┣ 📁 src/api/ # FastAPI + Streamlit 應用程式
┃ ┣ main.py # FastAPI 主程式
┃ ┗ main_streamlit.py # Streamlit 主程式
┣ docker-compose.yml
┗ mlruns/ # MLflow artifact 儲存區
建立 docker/requirements-streamlit.txt
:
streamlit==1.38.0
requests==2.32.3
pandas==2.2.2
建立 docker/Dockerfile.streamlit
:
FROM python:3.10-slim
WORKDIR /src/api
COPY docker/requirements-streamlit.txt .
RUN pip install --no-cache-dir -r requirements-streamlit.txt
此 Dockerfile 僅負責安裝 Streamlit 所需環境,
指令與程式的啟動交由 docker-compose 控制。
在現有 MLflow + FastAPI 架構中,加入新的 streamlit
服務:
streamlit:
build:
context: .
dockerfile: docker/Dockerfile.streamlit
container_name: streamlit_app
ports:
- "8501:8501"
environment:
- FASTAPI_URL=http://fastapi:8000
volumes:
- ./src/api:/src/api
- ./notebooks:/src/api/notebooks
working_dir: /src/api
command: >
streamlit run main_streamlit.py
--server.port=8501
--server.address=0.0.0.0
--server.runOnSave=true
depends_on:
- fastapi
networks:
- mlops-net
🔸
streamlit
使用本地掛載./notebooks
,
以讀取anime_clean.csv
。
建立 /src/api/main_streamlit.py
:
import os
import pandas as pd
import streamlit as st
import requests
FASTAPI_URL = os.getenv("FASTAPI_URL", "http://localhost:8000")
ANIME_CSV_PATH = "/src/api/notebooks/data/anime_clean.csv" # 固定資料路徑
st.set_page_config(page_title="🎬 Anime Recommender", layout="wide")
st.title("🎬 Anime Recommendation System")
st.markdown("""
這是一個基於 **MLflow + FastAPI** 的推薦系統展示 🎯
請輸入暱稱並選擇你喜歡的動畫,我們會為你推薦相似作品!
""")
# --- Step 1. 使用者輸入暱稱 ---
nickname = st.text_input("請輸入你的暱稱 👤", placeholder="例如:Josh、小智、NekoFan")
if not nickname:
st.info("請輸入暱稱後再繼續。")
# --- Step 2. 載入動畫清單 ---
@st.cache_data
def load_anime_list():
if not os.path.exists(ANIME_CSV_PATH):
st.error(f"❌ 找不到資料檔案:{ANIME_CSV_PATH}")
return []
try:
df = pd.read_csv(ANIME_CSV_PATH)
if "name" not in df.columns:
st.error("❌ anime_clean.csv 缺少 'name' 欄位。請確認資料格式。")
return []
return df["name"].dropna().unique().tolist()
except Exception as e:
st.error(f"❌ 讀取 anime_clean.csv 失敗:{e}")
return []
anime_list = load_anime_list()
# --- Step 3. 使用者選擇動畫 ---
selected_anime = st.multiselect(
"選擇你喜歡的動畫(最多5部) 🎥",
anime_list,
max_selections=5,
placeholder="例如:Naruto、Bleach、Attack on Titan..."
)
# --- Step 4. 按下推薦按鈕 ---
if st.button("🚀 取得推薦結果"):
if not nickname:
st.warning("請先輸入暱稱。")
elif not selected_anime:
st.warning("請至少選擇一部動畫。")
else:
payload = {
"user_id": nickname, # ✅ 使用暱稱當 user_id
"anime_titles": selected_anime
}
params = {"model_name": "AnimeRecsysModel"}
try:
res = requests.post(f"{FASTAPI_URL}/recommend", json=payload, params=params)
if res.status_code == 200:
data = res.json()
recs = data.get("recommendations", [])
if not recs:
st.warning("⚠️ 沒有推薦結果,請確認模型是否已正確部署。")
else:
st.markdown("---")
st.subheader(f"✨ 為 **{nickname}** 推薦的動畫:")
for i, title in enumerate(recs[:10], 1):
st.markdown(f"{i}. **{title}**") # ✅ 條列式輸出
else:
st.error(f"❌ 無法取得推薦結果:{res.text}")
except requests.exceptions.RequestException as e:
st.error(f"❌ 無法連線至 FastAPI:{e}")
1️⃣ 確保 anime_clean.csv
位於:
notebooks/data/anime_clean.csv
2️⃣ 啟動專案:
docker-compose up --build
3️⃣ 開啟瀏覽器:
4️⃣ 在 UI 中:
即可看到:
主題 | 說明 |
---|---|
🧩 Streamlit | 建立互動式推薦介面 |
👤 輸入暱稱 | 暱稱作為 user_id 傳入 API |
🎥 多選動畫 | 使用 multiselect 輸入喜好 |
🧠 推薦結果 | 條列式顯示推薦清單 |
🐳 Docker 一鍵啟動 | 完整整合 MLflow + FastAPI + Streamlit |
🎉 Day 26 完成!
我們成功打造出一個可互動的推薦系統前端。
明天(Day 27),我們將進一步串接 /log-ab-event
,
記錄使用者點擊與行為,進入 A/B 測試與行為追蹤階段。