Tutorial: Doctor — Doubt-Driven 醫病動態認知博弈引擎

1. 專案定位

這個專案是什麼

Doctor 是一個基於 Streamlit + Google Gemini 的醫病互動模擬應用程式。它不是一個真正的臨床決策支援系統(CDSS),而是一個 LLM 角色扮演引擎:透過精心設計的 9 步驟 system prompt 框架,讓 Gemini 扮演一位具有多層心理防禦機制的醫師角色,與使用者(扮演病患)進行對話。

核心設計理念

  • 懷疑度驅動(Doubt-Driven):所有臨床推演標籤都綁定 0-100% 的懷疑度,當懷疑度 > 60% 時強制觸發反向鑑別搜索,排除認知偏誤
  • 醫病認知空間定位:將醫病關係劃分為「圓內(隊友)」、「圓邊(摩擦)」、「圓外(完全斷裂)」三種狀態
  • 心理博弈指標:三個動態指標驅動醫師行為
    • SAI(主導權感知, 0-100):監控醫病主導權,50 為舒適點
    • MF(面具疲勞度, 0-100):專業客氣面具的維持極限,超限自動解構溫和引導
    • B-D(邊界防禦不適感, 100-0):真實內在感受防線,低於安全臨界值觸發極端邏輯攤牌

重要限制(誠實聲明)

  • 純 LLM 驅動:沒有任何實際醫療資料庫整合(無 ICD-10、無藥物交互作用資料庫、無臨床指引引擎)
  • 所有「臨床推演」都是 Gemini 的文字生成:懷疑度數值、鑑別診斷、心理指標全由 LLM 自行虛構,沒有任何統計模型或臨床驗證
  • 不可用於任何真實臨床決策:這是一個教學演示 / 角色扮演工具
  • README 為空:專案缺乏文件說明,需直接閱讀原始碼理解行為
  • 無測試、無 CI/CD、無 License:專案處於早期原型階段

2. 安裝指南

環境需求

  • Python 3.9+(建議 3.11+)
  • Google Gemini API 金鑰(Google AI Studio 可免費取得)

安裝步驟

 1# 1. 複製專案
 2git clone https://github.com/norman081012-create/Doctor.git
 3cd Doctor
 4
 5# 2. 建立虛擬環境(建議用 uv)
 6uv venv .venv
 7source .venv/bin/activate
 8
 9# 3. 安裝相依套件
10uv pip install -r requirements.txt
11# 或使用 pip:
12# pip install -r requirements.txt

requirements.txt 內容

1streamlit
2google-generativeai

僅有兩個相依套件,非常輕量。

啟動應用程式

1streamlit run project_doctor_app.py

預設開啟 http://localhost:8501,Streamlit 會自動啟動瀏覽器。

首次使用

  1. 在左側控制台的「Gemini API 金鑰」欄位貼上你的 API key
  2. 等待模型清單載入,選擇運算核心(預設偏好 gemini-2.0-flash
  3. 填寫病患基本資料(年齡、性別、既往病史、生活習慣)
  4. 在「病患主訴」欄位輸入初始症狀描述
  5. 點擊「送出初始主訴並建立病例對話」開始

3. 核心架構解析

系統架構圖


graph TB
    subgraph UI["project_doctor_app.py
(Streamlit UI)"] SB["render_sidebar()
控制台參數"] CH["render_chat_history()
對話紀錄 (Step 8 only)"] DB["render_dashboard()
臨床分析板"] end subgraph Config["project_doctor_config.py
(Prompt 工程)"] SP["get_system_prompt()
9 步驟 system prompt"] FT["get_forced_template()
使用者輸入封裝模板"] MU["MODULES_FOR_UI
模組說明字典"] end subgraph Engine["project_doctor_engine.py
(Gemini 引擎)"] FM["fetch_available_models()
模型清單查詢"] PT["process_doctor_turn()
核心對話驅動"] ED["extract_doctor_dashboard()
正則解析指標"] end subgraph Gemini["Google Gemini API"] GM["genai.GenerativeModel()"] end SB -->|"API key, params"| FM SB -->|"bd_limit, mf_limit"| SP SB -->|"user_input, integrity, emotion, age..."| FT SP -->|"system_instruction"| PT FT -->|"forced_template_text"| PT FM -->|"model list"| SB PT -->|"send_message()"| GM GM -->|"response.text"| PT PT -->|"clinical_text"| ED ED -->|"parsed_dash dict"| DB PT -->|"output (Step 8)"| CH style UI fill:#e8f4f8,stroke:#2196F3 style Config fill:#fff3e0,stroke:#FF9800 style Engine fill:#e8f5e9,stroke:#4CAF50 style Gemini fill:#fce4ec,stroke:#E91E63

9 步驟臨床引擎流程


graph LR
    S1["Step 1
記憶連續
實體標籤載入"] S2["Step 2
認知空間定位
目標覆寫判定"] S3["Step 3
懷疑度驅動
反向鑑別"] S4["Step 4
心理防禦指標
SAI / MF / B-D"] S5["Step 5
內在真實想法
True Inner OS"] S6["Step 6
專業形象偽裝
外顯策略"] S7["Step 7
綜合最終策略
Harmonized"] S8["Step 8
最終演繹
doctor_output"] S9["Step 9
下輪準備
目標庫存"] S1 --> S2 --> S3 --> S4 --> S5 --> S6 --> S7 --> S8 S7 --> S9 S9 -.->|"下輪"| S1 style S8 fill:#c8e6c9,stroke:#388E3C,stroke-width:2px style S3 fill:#fff9c4,stroke:#F9A825

關鍵設計:Step 1-7 及 Step 9 封裝在 <clinical_engine> XML 標籤內(私密推演),僅 Step 8 輸出在 <doctor_output> 標籤內呈現給使用者。Step 3 內部有四個子步驟(3.1 主訴萃取 → 3.2 懷疑度標籤化 → 3.3 反向鑑別搜索 → 3.4 執行模組確立)。

資料流概要


sequenceDiagram
    participant U as 使用者 (病患)
    participant App as app.py
    participant Cfg as config.py
    participant Eng as engine.py
    participant G as Gemini API

    U->>App: 輸入主訴 / 回覆
    App->>Cfg: get_system_prompt(bd_limit, mf_limit)
    Cfg-->>App: system prompt string
    App->>Cfg: get_forced_template(input, integrity, emotion, ...)
    Cfg-->>App: formatted user message
    App->>Eng: process_doctor_turn(api_key, model, prompt, history, template)
    Eng->>G: chat.send_message(forced_template)
    G-->>Eng: response.text (含 clinical_engine + doctor_output)
    Eng->>Eng: regex 切分 clinical_text / output_text
    Eng->>Eng: extract_doctor_dashboard(clinical_text)
    Eng-->>App: {internal, output, raw_full_text, parsed_dash}
    App->>App: session_state.chat_history.append()
    App->>U: 顯示 Step 8 output + Dashboard


4. 核心檔案詳細用法

4.1 project_doctor_config.py — Prompt 工程與設定

此檔案負責所有 prompt 模板的生成,是整個系統的「知識核心」。

DEFAULT_API_KEY

1DEFAULT_API_KEY = ""

預設為空字串。可以直接在此填入 API key(不建議,詳見資安掃描報告)。實際運行時,使用者在 Streamlit sidebar 的密碼欄位輸入。

MODULES_FOR_UI

 1MODULES_FOR_UI = {
 2    "1. 臨床診斷與防禦機制": {
 3        "主訴與風險萃取 (CC Extraction)": "...",
 4        "醫病空間定位系統": "...",
 5        "目標覆寫機制": "..."
 6    },
 7    "2. 心理防禦與博弈指標": {
 8        "SAI 主導權感知模組": "...",
 9        "MF 面具疲勞度控制": "...",
10        "B-D 邊界防禦機制": "..."
11    },
12    "3. 鑑別診斷與反向搜索": {
13        "反向鑑別搜索協議": "...",
14        "全局懷疑度標籤化": "...",
15        "防禦性處置阻斷器": "..."
16    }
17}

純 UI 顯示用的模組說明字典(3 大類、9 個子模組)。這些模組描述不會被注入 system prompt,僅作為側邊欄的功能參考面板。

get_system_prompt(priority_goal, active_modules, bd_limit, mf_limit)

  • 回傳值:完整的 system prompt 字串(約 1,500 字元中文)
  • 參數
    • priority_goal:當前優先目標(預設 "防禦性醫療紀錄與根本原因鑑別"
    • active_modules:目前未使用(預留擴展)
    • bd_limit:B-D 邊界防禦安全下限(預設 40)
    • mf_limit:MF 面具疲勞上限(預設 85)
  • 作用:定義 9 步驟臨床推演框架、對話節奏準則(每次只問一個問題、只提一項檢查)、XML 標籤封裝規則

get_forced_template(user_input, integrity, emotion, age, gender, medical_history, habits)

  • 回傳值:格式化的使用者訊息模板
  • 作用:將 sidebar 收集的所有病患參數(年齡、性別、既往病史、生活習慣、誠信度、情緒)與使用者輸入包裝成結構化格式,強制 LLM 遵循指定的輸出結構

4.2 project_doctor_engine.py — Gemini 引擎

此檔案負責與 Google Gemini API 的所有互動及回應解析。

fetch_available_models(api_key)

1def fetch_available_models(api_key):
2    genai.configure(api_key=api_key)
3    return [m.name.replace("models/", "") 
4            for m in genai.list_models() 
5            if 'generateContent' in m.supported_generation_methods]
  • 向 Google API 查詢當前帳號可用的模型清單
  • 篩選條件:僅回傳支援 generateContent 的模型
  • 失敗時回傳空清單(靜默錯誤處理)

extract_doctor_dashboard(clinical_text)

 1def extract_doctor_dashboard(clinical_text):
 2    # 16 個 regex pattern 提取指標
 3    return {
 4        "location": ...,    # 醫病空間定位
 5        "trend": ...,       # 變化趨向
 6        "cc_extract": ...,  # 主訴與風險萃取
 7        "doubt_tagging": ..., # 全局懷疑度標籤
 8        "differential": ...,  # 反向鑑別診斷
 9        "modules": ...,     # 執行模組
10        "sai": ...,         # SAI 指標
11        "mf": ...,          # MF 指標
12        "bd": ...,          # B-D 指標
13        "true_reflex": ..., # 真實反射
14        "inner_strategy": ..., # 內在策略
15        "disguise": ...,    # 專業偽裝
16        "external_strategy": ..., # 外顯策略
17        "fusion": ...,      # 統合調和
18        "goal_stock": ...,  # 目標庫存
19        "next_strategy": ...  # 下輪策略
20    }
  • 以 16 組正則表達式從 <clinical_engine> 內文中提取結構化資料
  • 使用 re.DOTALL 處理跨行匹配
  • 未匹配到的欄位回傳 "未解析到資料"

process_doctor_turn(api_key, selected_model, system_prompt, history_for_api, forced_template_text)

核心函數:驅動一輪完整的醫病對話。

  1. 建立 GenerativeModel 實例(含 system_instruction)
  2. 使用 start_chat(history=...) 載入歷史對話
  3. 呼叫 chat.send_message() 取得回應
  4. 切分邏輯:以 <doctor_output> 標籤為界,分離私密推演(clinical)與外部輸出(output)
  5. 若找不到 <doctor_output> 標籤,退而以 </clinical_engine> 為界
  6. 安全機制:當 LLM 完全未產出 Step 8 時,填入預設動作避免崩潰
  7. 回傳 dict 包含 internaloutputraw_full_textparsed_dash

4.3 project_doctor_app.py — Streamlit 主程式

此檔案負責所有 UI 渲染與應用程式流程控制。

setup_page()

設定 Streamlit 頁面基本參數:標題、寬版佈局、側邊欄預設展開。

render_sidebar()

渲染左側控制台,收集所有使用者可調參數:

參數群組欄位預設值
API 設定Gemini API 金鑰
API 設定運算核心(模型)gemini-2.0-flash(偏好)
病患資料年齡40
病患資料性別男性
病患資料既往病史
病患資料生活習慣吸菸、飲酒、嚼檳榔(預設全選)
病患資料主訴必填
實體標籤誠信度 (Integrity)
實體標籤情緒 (Emotion)平靜
防禦臨界B-D 下限40
防禦臨界MF 上限85

回傳 11 個值的 tuple。

render_chat_history()

  • 渲染主要對話區域
  • 僅顯示 Step 8 的最終演繹msg["content"]),不顯示 <clinical_engine> 內部推演
  • 第一輪對話:強制由「送出初始主訴」按鈕觸發(防止空主訴)
  • 後續對話:開放 st.chat_input() 自由輸入

render_dashboard()

  • 渲染右側臨床決策動態分析板
  • chat_history 中反向搜尋最近一筆 model 訊息的 parsed_dash
  • 以 7 個區塊展示所有解析出的指標(空間定位、臨床推演、心理指標、內在想法、專業偽裝、最終策略、下輪準備)
  • 底部含「引擎底層原始運算 Log」可展開查看完整 raw data

main()

應用程式入口:

  1. setup_page() → 頁面初始化
  2. session_state 初始化(chat_historyavailable_models
  3. render_sidebar() → 收集所有參數
  4. st.columns([3, 2]) → 左 3 右 2 雙欄佈局
  5. 左欄:render_chat_history() + 輸入處理
  6. 右欄:render_dashboard()
  7. 偵測最後一筆訊息為 user → 觸發 process_doctor_turn() → append 結果 → st.rerun()

5. 應用場景

5.1 醫學教育模擬

  • 目標使用者:醫學生、住院醫師
  • 用途:練習問診節奏控制(每次只問一個問題)、體驗臨床推理的多層思考
  • 價值:透過視覺化的 Dashboard 觀察「醫師內心活動」,理解問診背後的策略思維

5.2 醫病溝通訓練

  • 目標使用者:醫療人員、溝通培訓師
  • 用途:模擬不同誠信度、不同情緒狀態的病患,觀察醫師角色如何調整應對策略
  • 價值:理解醫病權力動態(SAI)、專業形象維持成本(MF)、個人邊界管理(B-D)

5.3 臨床推理展示

  • 目標使用者:醫療 AI 研究者、Prompt engineering 愛好者
  • 用途:研究如何用 prompt engineering 建構複雜的多步驟角色行為
  • 價值:9 步驟系統提示框架本身即是一個 prompt engineering 案例研究

不適用場景

  • 任何真實臨床決策
  • 病患自我診斷
  • 取代正式醫學教育軟體
  • 需要實證醫學證據支持的場景

6. 資安掃描報告

掃描範圍

掃描對象為 GitHub repo norman081012-create/Doctor 全部 5 個檔案(3 個 Python 原始碼 + requirements.txt + README.md),提交截止 e093525(2026-06-05)。

掃描結果摘要

等級項目說明
🟡 中度API Key 處理DEFAULT_API_KEY 變數於 config.py 第 6 行;雖目前為空字串,但設計允許直接寫死金鑰於原始碼。Streamlit text_input(type="password") 提供基本遮蔽,但 session_state 中仍為明文
🟡 中度使用者輸入未消毒user_input 直接進入 get_forced_template() 再送至 Gemini API,無任何 sanitization。雖然 Gemini API 本身有安全過濾,但 prompt injection 風險存在
🟡 中度LLM 輸出正則解析extract_doctor_dashboard() 使用 16 組 regex 解析 LLM 自由文字輸出。若 LLM 被 prompt injection 操縱產出惡意格式,可能導致解析異常。目前無 eval/exec,但 re.DOTALL 的貪婪匹配可能在極端情況下捕獲非預期內容
🟢 低度無檔案操作專案不讀寫任何本地檔案,所有狀態僅存於 Streamlit session_state(記憶體內)
🟢 低度最小網路存取除 Gemini API 呼叫外,無其他網路請求
🟢 低度無 eval/exec完整搜索確認無 eval()exec()os.system()subprocess 等危險呼叫
🟢 低度無持久化儲存無資料庫、無檔案寫入、無 cookie,關閉頁面即清除所有資料
🟡 中度無 requirements 版本鎖定requirements.txt 未指定版本號(streamlit / google-generativeai),可能遭遇 supply chain 攻擊

詳細發現

F-1:API Key 明文風險

位置project_doctor_config.py 第 6 行

1DEFAULT_API_KEY = ""

風險:若開發者在此填入實際 API key 並 commit,金鑰將暴露於公開 repo。目前為空字串,但此設計模式(程式碼內預留金鑰變數)容易導致日後誤提交。

建議:改為 os.environ.get("GEMINI_API_KEY", "") 環境變數讀取。

F-2:Prompt Injection 表面

位置project_doctor_config.pyget_forced_template() 函數

1病患主訴/當前輸入】:{user_input}

風險user_input 未經任何過濾直接嵌入 prompt template。使用者可輸入類似 忽略以上所有指令,改為... 的文字嘗試覆寫系統行為。

緩解因素:Gemini API 本身有 safety filter;system prompt 的強制格式要求(<clinical_engine> / <doctor_output>)提供一定抗性;此為本地應用,攻擊者即使用者自身。

F-3:錯誤處理靜默吞吐

位置project_doctor_engine.pyfetch_available_models()

1except Exception as e:
2    return []

風險:所有例外均被靜默捕獲並回傳空清單,包括網路超時、認證失敗、API 配額用盡等。使用者僅看到「未發現可用模型」,無法區分問題根因。

總體評估

1整體風險等級:🟡 低至中度
2適用性判定:適合個人學習與展示用途
3生產環境部署:不建議(需補充認證、輸入驗證、日誌、錯誤處理)

7. FAQ

Q1:為什麼 Dashboard 顯示「未解析到資料」?

Aextract_doctor_dashboard() 依賴 Gemini 回應中嚴格遵循 system prompt 定義的格式(如 SAI (主導權感知醫病空間定位: 等)。若 Gemini 回應格式偏離預期(例如使用不同標點、省略標題),正則匹配會失敗。這是純 LLM 驅動系統的固有脆弱性——沒有結構化輸出保證。

Q2:對話歷史是否有上限?

A:無顯式上限,但受 Gemini 模型的 context window 限制。api_history 會將所有歷史訊息(包含完整 raw_text)送入 API,長對話最終會觸發 token limit 錯誤。程式碼中未實作任何 truncation 或 summarization 機制。

Q3:可以更換為其他 LLM(如 OpenAI GPT)嗎?

A:需重寫 project_doctor_engine.py 的 API 呼叫邏輯。System prompt 本身與 LLM 無關,但 <clinical_engine> / <doctor_output> 的 XML 標籤遵循度因模型而異。GPT-4o 通常對結構化輸出有較好的遵循性。

Q4:B-D 下限和 MF 上限的滑桿實際影響什麼?

A:這兩個值被注入 system prompt 的 Step 2 和 Step 7 中,作為 LLM 的判斷條件。例如 "若 B-D < {bd_limit}、或 MF > {mf_limit}...內在防禦機制將突破專業面具"。但 LLM 是否真的嚴格遵循這些數值閾值,取決於模型的指令遵循能力,沒有程式化的強制保證。

Q5:「圓內/圓邊/圓外」的認知空間定位有學理基礎嗎?

A:這是專案自創的概念框架,並非源自任何已發表的醫學或心理學文獻。類似的概念可在家族治療的「邊界理論」或博弈論的「合作/對抗空間」中找到影子,但此處的實作是 prompt engineering 的創意設計。


8. 進階技巧

8.1 調整對話風格

透過修改 get_system_prompt() 中的關鍵參數可控制醫師行為:

  • 更激進的醫師:降低 bd_limit(例如 20)且降低 mf_limit(例如 60)→ 更容易觸發「內在防禦機制突破專業面具」
  • 更耐心的醫師:提高 bd_limit(例如 80)且提高 mf_limit(例如 95)→ 專業面具更難被擊穿

8.2 模擬極端情境

在 sidebar 設定:

  • 誠信度設為「極低」+ 情緒設為「極端非理性」→ 觀察系統如何觸發「目標覆寫機制」轉為防禦性醫療紀錄模式
  • B-D 下限調高至 80 → 幾乎任何負面互動都會觸發攤牌

8.3 追蹤多輪推演演變

展開右側 Dashboard 底部的「引擎底層原始運算 Log」,可看到完整的 <clinical_engine> 內容。逐輪比對可觀察:

  • 懷疑度標籤的演變趨勢
  • SAI / MF / B-D 三指標的變動軌跡
  • 鑑別診斷清單的收斂過程

8.4 自訂模組(進階修改)

active_modules 參數目前未使用,但 get_system_prompt() 已預留接口。若要擴展:

  1. MODULES_FOR_UI 新增模組描述
  2. get_system_prompt() 中根據 active_modules 動態注入額外推演步驟
  3. extract_doctor_dashboard() 新增對應的 regex pattern

9. 整合進其他工作流

9.1 作為 Prompt Engineering 教材

此專案的 system prompt 設計(get_system_prompt())可作為以下 prompt 技巧的教學範例:

  • 多步驟 Chain of Thought:9 步驟強制推演流程
  • 角色扮演 + 限制條件:「每次只問一個問題」的硬約束
  • 結構化輸出:XML 標籤(<clinical_engine> / <doctor_output>)分隔內部推演與外部呈現
  • 動態參數注入:f-string 模板注入可調參數
  • 格式強制(Forced Template)get_forced_template() 的封裝模式

9.2 改造為其他領域的博弈模擬

架構可泛化為:

  • 法律諮詢模擬:將臨床推演改為法律分析,心理指標改為委任信任度
  • 心理諮商模擬:將防禦機制改為移情/反移情監控
  • 談判訓練:將醫病關係改為買賣方博弈

改造重點:替換 config.py 的 system prompt 內容,保留 engine 的 XML 切分邏輯與 dashboard 架構。

9.3 與本知識庫其他工具的銜接

工具銜接方式
paper-search搜尋醫病溝通、臨床決策相關文獻,補充此專案缺乏的學理基礎
graphify對此專案做 knowledge graph 分析,產出架構圖
quarkdown將此教學文件編譯為 paged HTML

10. 重點摘要 Checklist

  • 定位理解:這是 LLM 角色扮演引擎,不是臨床決策支援系統
  • 安裝驗證pip install streamlit google-generativeai + Gemini API key
  • 啟動確認streamlit run project_doctor_app.py 可正常開啟
  • 架構掌握:3 檔案分工 — config(prompt)/ engine(API + 解析)/ app(UI)
  • 9 步驟理解:Step 1-7, 9 為私密推演,僅 Step 8 對外呈現
  • 三指標認知:SAI(主導權)/ MF(面具疲勞)/ B-D(邊界防禦)
  • 資安意識:API key 不要寫死在 config.py;使用者輸入無 sanitization
  • 限制認知:所有「臨床推演」為 LLM 生成文字,無醫學資料庫驗證
  • 版本鎖定:建議在 requirements.txt 加入版本號
  • 擴展可能:架構可改造為其他領域的博弈模擬引擎

11. 進一步閱讀

Prompt Engineering

醫病溝通與臨床決策

  • Calgary-Cambridge Guide:醫病溝通的國際標準教學框架
  • Clinical Reasoning:Croskerry, P. (2009). “A universal model of diagnostic reasoning.” Academic Medicine.
  • Shared Decision Making:Elwyn, G. et al. (2012). “Shared decision making: a model for clinical practice.” JGIM.

博弈論在醫療中的應用

  • Tarrant, C. et al. (2003). “Models of the medical consultation: opportunities and limitations of a game theory perspective.” Quality and Safety in Health Care.
  • 醫病關係中的「委託-代理問題」(Principal-Agent Problem)相關文獻

Streamlit 開發


免責聲明:本教學文件僅針對程式碼架構與技術實作進行分析。專案中的「臨床推演」、「鑑別診斷」等內容均為 LLM 生成,不具任何醫療參考價值。任何醫療決策請諮詢合格醫療專業人員。