在 Day 27 完成 Grafana Alerting 與 LINE 即時告警連動 後,PrescriptionVLM 系統已具備可觀測性與自動化運營告警能力。
然而,進入專案最後階段,我們在實際測試長輩真實拍攝的藥袋圖片時發現,現實中的挑戰比標準測試複雜:
今天我們將針對 Gemini VLM 進行 提示詞工程(Prompt Engineering)優化,並在 Python 後端建立 邊緣異常防衛機制。完成項目包含:
在結合多模態 AI 的生產環境中,AI 的輸出品質高度依賴前端防衛與 Prompt 設計:
guardrails.py)在專案根目錄建立 guardrails.py,處理影像質量檢測:
import cv2
import numpy as np
from logger import logger
# 設定拉普拉斯變異數閾值(低於此數值代表圖片過於模糊)
BLUR_THRESHOLD = 50.0
def check_image_quality(image_bytes: bytes) -> tuple[bool, str]:
"""檢測上傳圖片之品質與清晰度"""
try:
# 將二進位圖片解碼為 OpenCV 格式
nparr = np.frombuffer(image_bytes, np.uint8)
img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
if img is None:
return False, "無法讀取圖片檔案,請確認格式是否為 JPG 或 PNG"
# 轉為灰階圖像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 計算拉普拉斯變異數以量化模糊程度
variance = cv2.Laplacian(gray, cv2.CV_64F).var()
logger.info(f"影像清晰度分析 Variance score: {variance:.2f}")
if variance < BLUR_THRESHOLD:
return False, f"照片過於模糊(清晰度分數: {variance:.1f}),請在光線充足處重新拍攝藥袋"
return True, "OK"
except Exception as e:
logger.error(f"圖像品質檢測發生異常: {str(e)}")
return True, "OK" # 若檢測模組發生異常,降級放行避免阻斷主流程
prompts.py)在專案根目錄建立 prompts.py,寫入高強度的 CoT + Few-Shot 提示詞:
SYSTEM_PROMPT_V2 = """
你是一位專為高齡長者服務的資深臨床藥師與多模態 AI 專家。你的任務是精準解析藥袋圖片上的醫囑資訊。
請遵循以下 Chain-of-Thought 步驟進行分析:
1. 影像評估:檢查圖片是否為藥袋或處方籤。若非藥袋,請將 status 設為 "invalid_image"。
2. 藥品識別:辨識藥品名稱、單位劑量與數量。若文字模糊,僅擷取可辨識部分,絕不虛構。
3. 服法釐清:整理服用時間與飯前/飯後。
4. 安全警語:特別標示「警語/副作用」等注意事項。
【Few-Shot 範例 - 破損/部分模糊藥袋】
輸入:藥袋右下角遭摺疊,僅能看到「阿斯匹靈 100mg... 每日一次」
輸出欄位調整:需要在 safety_warnings 中加入 "部分藥袋資訊遮蔽,請與藥師二次確認"。
【輸出 JSON Schema 要求】
請嚴格輸出符合以下 JSON 格式的內容,不得包含 Markdown 標記以外的字串:
{
"is_prescription": true,
"confidence_score": 0.95,
"reasoning": "步驟 1... 步驟 2...",
"medicines": [
{
"name": "藥品名稱",
"dosage": "1 錠",
"timing": "早餐後"
}
],
"safety_warnings": ["警語 1"],
"spoken_summary": "專為長輩朗讀的簡短語音摘要(100字內,語氣親切)"
}
"""
app.py)更新 app.py 中的 /analyze-prescription 路由,掛載圖片品質預檢與新版提示詞:
from guardrails import check_image_quality
from prompts import SYSTEM_PROMPT_V2
@app.route("/analyze-prescription", methods=["POST"])
@limiter.limit("10 per minute")
def analyze_prescription():
req_id = getattr(g, "request_id", "N/A")
logger.info(f"收到藥單解析請求 [ReqID: {req_id}]")
if "image" not in request.files:
return jsonify({"error": "未提供圖片檔案"}), 400
image_file = request.files["image"]
image_bytes = image_file.read()
image_file.seek(0)
# ── 防衛牆 1: 前置圖像品質與清晰度預檢 ──
is_valid, quality_msg = check_image_quality(image_bytes)
if not is_valid:
logger.warning(f"圖像品質預檢未通過: {quality_msg}")
return jsonify({
"status": "rejected",
"reason": quality_msg,
"request_id": req_id
}), 400
# ── 防衛牆 2: 計算 Hash 與查詢 Redis 快取 ──
img_hash = compute_image_hash(image_bytes)
cache_key = f"prescription_cache:{img_hash}"
# ... (快取查詢邏輯) ...
# ── 防衛牆 3: 帶入 SYSTEM_PROMPT_V2 呼叫 Gemini ──
try:
# 呼叫 Gemini API 並帶入 SYSTEM_PROMPT_V2
# ... (解析回傳並過濾 Pydantic Schema) ...
pass
except Exception as e:
logger.error(f"VLM 解析過程發生異常: {str(e)}")
# 回傳友善降級 JSON
return jsonify({
"status": "fallback",
"message": "系統目前無法清晰辨識該藥袋,已通知關懷照護人員協助確認",
"request_id": req_id
}), 200
1. 模糊照片防衛測試
使用一張刻意搖晃拍攝導致模糊的藥袋照片進行測試:
curl -i -X POST http://127.0.0.1:5000/analyze-prescription \
-F "image=@blur_rx.jpg"
2. 邊緣遮蔽藥袋解析測試
使用一張部分摺疊的藥袋照片進行測試:
safety_warnings 中正確產出 "部分藥袋文字遮蔽,請確認服用頻率",且 spoken_summary 依然保持適合長輩收聽的親切語調。測試通過後,將 Day 28 的修改提交至 GitHub:
git add guardrails.py prompts.py app.py
git commit -m "保留雙引號 改填寫自己要記錄的標記 ex.鐵人賽第二十八天"
git push
今天我們透過 OpenCV 影像品質防衛 與 Chain-of-Thought 提示詞工程,提升了 PrescriptionVLM 系統在面對真實世界複雜拍攝情境時的穩定性,既節省了不必要的 API 費用,又確保了長輩用藥安全的精準度。
明天(Day 29),我們將進入專案技術總結前夕,實作 OpenAPI / Swagger 互動式 API 文件自動化生成與系統架構評測,讓開發團隊與第三方醫療關懷平台能無縫對接 API!