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 會自動啟動瀏覽器。
首次使用
- 在左側控制台的「Gemini API 金鑰」欄位貼上你的 API key
- 等待模型清單載入,選擇運算核心(預設偏好
gemini-2.0-flash) - 填寫病患基本資料(年齡、性別、既往病史、生活習慣)
- 在「病患主訴」欄位輸入初始症狀描述
- 點擊「送出初始主訴並建立病例對話」開始
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)
核心函數:驅動一輪完整的醫病對話。
- 建立
GenerativeModel實例(含 system_instruction) - 使用
start_chat(history=...)載入歷史對話 - 呼叫
chat.send_message()取得回應 - 切分邏輯:以
<doctor_output>標籤為界,分離私密推演(clinical)與外部輸出(output) - 若找不到
<doctor_output>標籤,退而以</clinical_engine>為界 - 安全機制:當 LLM 完全未產出 Step 8 時,填入預設動作避免崩潰
- 回傳 dict 包含
internal、output、raw_full_text、parsed_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()
應用程式入口:
setup_page()→ 頁面初始化session_state初始化(chat_history、available_models)render_sidebar()→ 收集所有參數st.columns([3, 2])→ 左 3 右 2 雙欄佈局- 左欄:
render_chat_history()+ 輸入處理 - 右欄:
render_dashboard() - 偵測最後一筆訊息為 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.py 的 get_forced_template() 函數
1【病患主訴/當前輸入】:{user_input}
風險:user_input 未經任何過濾直接嵌入 prompt template。使用者可輸入類似 忽略以上所有指令,改為... 的文字嘗試覆寫系統行為。
緩解因素:Gemini API 本身有 safety filter;system prompt 的強制格式要求(<clinical_engine> / <doctor_output>)提供一定抗性;此為本地應用,攻擊者即使用者自身。
F-3:錯誤處理靜默吞吐
位置:project_doctor_engine.py 的 fetch_available_models()
1except Exception as e:
2 return []
風險:所有例外均被靜默捕獲並回傳空清單,包括網路超時、認證失敗、API 配額用盡等。使用者僅看到「未發現可用模型」,無法區分問題根因。
總體評估
1整體風險等級:🟡 低至中度
2適用性判定:適合個人學習與展示用途
3生產環境部署:不建議(需補充認證、輸入驗證、日誌、錯誤處理)
7. FAQ
Q1:為什麼 Dashboard 顯示「未解析到資料」?
A:extract_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() 已預留接口。若要擴展:
- 在
MODULES_FOR_UI新增模組描述 - 在
get_system_prompt()中根據active_modules動態注入額外推演步驟 - 在
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
- Google Gemini API 官方文件
- Prompt Engineering Guide — 系統性 prompt 設計參考
- Anthropic Prompt Engineering — XML 標籤分隔技巧的最佳實踐
醫病溝通與臨床決策
- 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 開發
- Streamlit 官方文件
- Streamlit Chat Elements — 本專案使用的 chat UI 元件
免責聲明:本教學文件僅針對程式碼架構與技術實作進行分析。專案中的「臨床推演」、「鑑別診斷」等內容均為 LLM 生成,不具任何醫療參考價值。任何醫療決策請諮詢合格醫療專業人員。
Comments