iT邦幫忙

2026 iThome 鐵人賽

DAY 28
0
Build on Google AI

給藥袋裝一張嘴:30 天用 Android 與 Google VLM 實作高齡語音用藥助手系列 第 28 篇

Day 28|準確度與防禦力升級!多模態 VLM 提示詞工程調優與邊緣異常防衛

  • 分享至 

  • xImage
  •  

✏️【本日實作紀錄:VLM Prompt Engineering 優化與極端圖片防衛】

在 Day 27 完成 Grafana Alerting 與 LINE 即時告警連動 後,PrescriptionVLM 系統已具備可觀測性與自動化運營告警能力。

然而,進入專案最後階段,我們在實際測試長輩真實拍攝的藥袋圖片時發現,現實中的挑戰比標準測試複雜:

  1. 極端拍攝品質:長輩拍攝時常見嚴重晃動、反光、強光遮蔽、藥袋皺折或傾斜角度。
  2. 非藥袋圖片干擾:長輩可能誤上傳風景照、寵物照或無關文字,若直接送入 Gemini VLM 解析,容易產生錯誤結果。
  3. 結構化 JSON 輸出穩定度:在圖片品質不佳時,VLM 模型容易產出格式破損或欄位缺失的 JSON。

今天我們將針對 Gemini VLM 進行 提示詞工程(Prompt Engineering)優化,並在 Python 後端建立 邊緣異常防衛機制。完成項目包含:

  • Few-Shot Prompting 與 Chain-of-Thought 系統提示詞升級 — 導入思考鏈引導與邊緣範例,強制 VLM 在面對模糊圖片時優先辨識關鍵用藥資訊。
  • 圖像品質與類別前置防衛 — 透過 OpenCV 快速檢測圖片清晰度,提前攔截無效圖片。
  • Pydantic 強制 Validation 與預設 fallback 降級 — 結合 Day 17 的 Pydantic Schema,確保不論 VLM 輸出品質如何,前端與 LINE 卡片均不會崩潰。

一、提示詞工程與邊緣防衛設計

在結合多模態 AI 的生產環境中,AI 的輸出品質高度依賴前端防衛與 Prompt 設計:

  • Chain-of-Thought 思考鏈:要求 Gemini VLM 在輸出 JSON 成果前,先步驟化分析圖片內容,大幅降低模型錯誤。
  • Few-Shot 邊緣案例:在 Prompt 中寫入真實邊緣案例,教導模型在資訊不全時標記為不確定,而非虛構資訊。
  • 前置影像過濾:在呼叫費用較高且耗時的 Gemini API 前,先用 OpenCV 計算影像清晰度,低於閾值直接回傳提示。

二、實作 OpenCV 圖像模糊與無效圖片過濾(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" # 若檢測模組發生異常,降級放行避免阻斷主流程

三、全面調優 Gemini VLM 系統提示詞(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字內,語氣親切)"
}
"""

四、整合邊緣防線至 Flask 主流程(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"
  • 預期成果:被 OpenCV 拉普拉斯防衛牆攔截,直接回傳 HTTP 400 與清晰度不足的提示,成功省去一次 Gemini VLM 呼叫費用。

2. 邊緣遮蔽藥袋解析測試

使用一張部分摺疊的藥袋照片進行測試:

  • 預期成果:Gemini VLM 依循思考鏈,在 safety_warnings 中正確產出 "部分藥袋文字遮蔽,請確認服用頻率",且 spoken_summary 依然保持適合長輩收聽的親切語調。

六、版本控制與提交 GitHub

測試通過後,將 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!


上一篇
Day 27|即時告警防線!實作 Grafana Alerting 系統異常告警與 LINE 即時通知推播
下一篇
Day 29|開放生態與對接標準!實作 Flask-RESTX OpenAPI / Swagger 互動式 API 文件與規範
系列文
給藥袋裝一張嘴:30 天用 Android 與 Google VLM 實作高齡語音用藥助手 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言