從原物料追溯到 AI 智能查詢的最小可行性專案(MVP)
建立一套食材履歷追溯系統,結合小型 LLM 提供智能查詢功能,讓客戶、員工、消費者、供應商都能透過自然語言查詢食材來源、檢驗報告等資訊。
- 前端: React + Next.js + Tailwind CSS
- 後端: NestJS + PostgreSQL + Prisma
- LLM: Python (FastAPI) + Ollama
- 容器化: Docker + Docker Compose
- 未來部署: Kubernetes
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Next.js │─────▶│ NestJS │─────▶│ PostgreSQL │
│ (Frontend) │ │ (Backend) │ │ │
└─────────────┘ └─────────────┘ └─────────────┘
│
▼
┌─────────────┐
│ Python │
│ LLM Service │
└─────────────┘
目標: 建立基礎的食材資料模型與 CRUD API
model Ingredient {
id String @id @default(uuid())
batchNumber String @unique @map("batch_number")
name String // 品名:芒果、香蕉
origin String // 產地:台南、屏東
supplier String // 供應商
productionDate DateTime @map("production_date")
expiryDate DateTime @map("expiry_date")
testResult String @map("test_result") // 合格/不合格
testDetails Json? @map("test_details") // JSON 格式的詳細檢驗數據
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("ingredients")
}重點: CRUD 操作(Create, Read, Update, Delete)
GET /ingredients- 列出所有食材GET /ingredients/:batchNumber- 根據批號查詢POST /ingredients- 新增食材PUT /ingredients/:id- 更新食材DELETE /ingredients/:id- 刪除食材
seed script:
// backend/prisma/seed.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
await prisma.ingredient.createMany({
data: [
{
batchNumber: 'MG20241201-001',
name: '愛文芒果',
origin: '台南玉井',
supplier: '玉井果農合作社',
productionDate: new Date('2024-12-01'),
expiryDate: new Date('2024-12-15'),
testResult: '合格',
testDetails: {
pesticide: '0.01ppm',
heavyMetal: '未檢出',
bacteria: '陰性',
},
},
],
});
}
main()
.catch((e) => console.error(e))
.finally(() => prisma.$disconnect());目標: 建立查詢介面與管理後台
功能:
- 輸入批號查詢食材資訊
- 顯示產地、日期、檢驗結果
- 美觀的卡片式 UI
功能:
- 列表顯示所有食材
- 新增/編輯食材表單
- 刪除功能
使用 fetch 或 axios 串接後端 API
// lib/api.ts
const API_URL = process.env.NEXT_PUBLIC_API_URL;
export async function getIngredientByBatch(batchNumber: string) {
const res = await fetch(`${API_URL}/ingredients/${batchNumber}`);
return res.json();
}目標: 實作 AI 對話查詢功能
cd llm-service
mkdir llm-service && cd llm-service
# requirements.txt
fastapi==0.109.0
uvicorn==0.27.0
sqlalchemy==2.0.25
psycopg2-binary==2.9.9
ollama==0.1.0 # 如果用 Ollamallm-service/main.py
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class ChatRequest(BaseModel):
question: str
batch_number: str | None = None
@app.post("/chat")
async def chat(request: ChatRequest):
# 1. 從資料庫查詢相關資料
# 2. 組合 prompt 給 LLM
# 3. 返回 LLM 回應
return {"answer": "這批芒果農藥殘留為 0.01ppm,符合標準"}使用 SQLAlchemy 連接 PostgreSQL,讓 LLM 能取得食材資料
方案 A: Ollama(本地 LLM)
import requests
def get_llm_response(question, ingredient_data):
prompt = f"食材資料:{ingredient_data}\n問題:{question}"
response = requests.post('http://localhost:11434/api/generate', json={
'model': 'llama3.2',
'prompt': prompt
})
return response.json()['response']// backend/src/chat/chat.service.ts
async askLLM(question: string, batchNumber?: string) {
const response = await fetch('http://llm-service:8000/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ question, batch_number: batchNumber })
});
return response.json();
}app/chat/page.tsx
- 簡單的聊天 UI
- 顯示對話記錄
- 輸入框 + 送出按鈕
- 批號查詢正常運作
- 管理後台 CRUD 正常
- AI 對話回應正確
- 批號格式檢查
- 日期邏輯驗證(生產日期 < 到期日期)
- 必填欄位檢查
- API 錯誤回傳適當訊息
- 前端顯示友善錯誤提示
- LLM 服務異常時的降級處理
目標: 將 Docker Compose 專案轉換為 K8s
- 先跑起來再優化: 不要一開始就追求完美,先讓功能運作
- 頻繁測試: 每完成一個小功能就測試,避免積累 bug
- 版本控制: 記得 commit,寫清楚的 commit message
- 先在 Docker Compose 測試生產版 Dockerfile: 確保 build 沒問題
- 使用 Minikube 本地測試: 不要急著上雲端
- 一次部署一個服務: 先讓 backend + postgres 跑起來,再加其他服務
- 善用 kubectl logs: 出問題先看 logs
- 學習階段: 用 Ollama(理解本地 LLM 運作)
- 生產階段: 用 Ollama
完成 MVP 後可以考慮:
-
功能擴充
- 檔案上傳(檢驗報告 PDF)
- 多語言支援
- 行動版 App(React Native)
-
技術優化
- Redis 快取 LLM 回應
- 使用 RAG(Retrieval-Augmented Generation)提升 LLM 準確度
- 實作 SSO 登入
-
K8s 進階
- 設定 Horizontal Pod Autoscaler(自動擴展)
- 使用 Helm 管理部署
- CI/CD pipeline(GitHub Actions)
- 部署到雲端(GKE、EKS、AKS)
-
監控與可觀測性
- Prometheus + Grafana
- 日誌收集(ELK Stack)
- Distributed Tracing(Jaeger)