triAIge 技術導讀:架構、取捨與評審問答
模型為什麼不決定檢傷級數?現場擷取為什麼不讓模型推理,醫師端的鑑別診斷卻要?Guardrails 為什麼關著?F1 0.95 用的是什麼資料?評審追問的多半是這類設計取捨。這份導讀用白話說明現在的系統怎麼組成、每個設計決定的理由與我們放棄的做法,也逐一講解三個畫面與每一項 AI 功能,最後整理關鍵數字、評審可能問的問題,以及代替產品負責人示範時的備援腳本。
內容以 2026-10-22 的 main 為準:階段 2 與 UAT 修正已經合併並部署(be3614d2)。每個數字都附上 repository 裡的出處。繁體中文口述稿由模型寫成,還沒有臨床人員審閱,所以這份文件不說「已通過臨床驗證」(eval/README.md〈誠實規則〉)。程式結構的細節在〈架構說明〉,給評審的正式說明在〈評審報告〉。三份報告同一天依同一版程式更新;若彼此對不上,以程式碼為準。
1. 整個系統怎麼運作?
救護員在平板的救護端網頁送出一句話,這句話送到醫院端一台 DGX Spark 上的 FastAPI 服務。服務先存下這句話、立刻回應,再在背景透過 NeMo Agent Toolkit 呼叫 vLLM 上的 Nemotron 3 Super,把這句話擷取成救護紀錄表的欄位。模型的回覆先通過一組確定性檢查,再依「人填的值優先」的規則寫進紀錄表,經 SSE 推回畫面,標成「待複查」。救護員送出紀錄後,急診檢傷護理師看到預先填好的檢傷表與規則算出的 TTAS 建議級數,由護理師決定級數;護理師送出檢傷後,醫師看到規則組出的摘要、模型改寫的「AI 整理」,以及一個會搜尋醫學知識庫的智慧體(agent)整理的鑑別診斷:常見 Top 5 與最致命 Top 5。
回答時,有四件事要先講清楚:
- 模型整理資料、提供參考,臨床決定由規則與人負責。 TTAS 建議由規則計算,級數由護理師送出;鑑別診斷只是供醫師參考的選項,不含機率,每一項附上依據的病人資料與知識庫出處。
- AI 填的值都要經過複查,人填的值不會被覆蓋。 AI 的建議直接顯示在欄位上,不用對話框打斷現場人員。
- 現場擷取求快,醫師端的鑑別診斷求深。 擷取每句一次呼叫、關閉推理,延遲以秒計;鑑別診斷是會呼叫工具的智慧體,開啟推理,背景執行,約需 1 到 3 分鐘。兩者的取捨在 §4.2。
- 系統全部跑在一台 DGX Spark 上,模型可以替換,各層的邊界由測試強制。 推論不使用雲端模型;與模型有關的設定集中在一個
ModelProfile物件裡。
1.1 三個畫面各做什麼?
正式介面在根目錄 /,首頁選角色。每個畫面頂列都有「重置 demo 資料」,會清空所有案件,示範前後要留意。
救護員(E0 案件清單、E1 對話、E2 紀錄表)。 新增案件後,用文字(語音輸入還沒做)一句一句送出對話,每句標說話者:救護員、病人或旁人。AI 即時把內容填進 E2 紀錄表,欄位旁標「待複查」;救護員可以在欄位上確認、修改或刪除。右側欄常駐三樣東西:TTAS 參考級數、完成度與缺漏欄位、建議詢問與危險訊號。選送往的醫院時,選單上會多一行「AI 建議需要重度/中度/一般急救責任醫院」。必填欄位補齊後「送出至急診」。送出必填共六項:性別、年齡、創傷或非創傷、主訴、至少一組含意識與脈搏的生命徵象、藥物過敏史(寫「不清楚」也算),另外還要選送往醫院(engine/completeness.py)。送出後播「救護車」轉場,落在急診檢傷台。
檢傷護理師(N1 待接收清單、N2 檢傷台)。 N1 依預估到院時間列出院前預通知,即時收到新案件,可預覽交接包,並用「讀取健保卡(模擬)」確認身分(20 張虛構的卡)。N2 的左側有檢傷評估、院前紀錄與時間軸三個分頁,右側是 AI 助理。檢傷表由救護紀錄預填,沒有救護員確認過的欄位標「未複查」。TTAS 列顯示規則算出的建議級數與每一條依據;護理師接受或調整,與建議不同時必須寫理由。護理師也可以在 AI 助理裡打一段話,AI 擷取成欄位提案,勾選「套用」後才寫入。若救護端在檢傷中又送出新版本,會出現「救護端有更新」橫幅,只提示、不自動套用。送出檢傷後播「移到看診區」轉場,落在醫師摘要。
急診醫師(D1 病人清單、D2 摘要)。 D1 只列已完成檢傷的病人,依最終級別排序,1 級在前。D2 由上而下是:交接摘要(含「AI 整理」)、待追蹤事項、鑑別診斷與建議檢查、主訴、檢傷、病史、生命徵象、時間軸與稽核紀錄。每一句摘要、每一項診斷都附來源標籤,點了回到原始欄位。
三個角色在 demo 環境各自用固定的虛構使用者登入,不需要輸入帳號;護理師與醫師可以處理三家示範醫院(重度、中度、一般急救責任醫院)的案件(migrations/004_user_hospitals.sql)。
1.2 AI 在哪裡出手?
| 功能 | 誰看到 | 觸發 | 優先級 | 開關 | 詳細 |
|---|---|---|---|---|---|
| 欄位擷取 | 救護員 | 每一句對話 | P0 | 一直開 | §3.1 |
| 主訴翻譯 | 救護員、急診 | 擷取完成後 | P2 背景 | 開 | §3.1 |
| 建議詢問 | 救護員、護理師 | 對話停 8 秒;護理師端送出 | P2 背景 | 開 | §3.2 |
| 護理師輸入擷取 | 護理師 | 護理師送出一段話 | P1 | 開 | §3.2 |
| 急救責任醫院建議 | 救護員 | 紀錄表欄位變動 | P2 背景 | 開 | §3.2 |
| 醫師「AI 整理」 | 醫師 | 救護端送出、檢傷送出 | P2 背景 | 開 | §3.2 |
| 鑑別診斷與建議檢查 | 醫師 | 救護端送出、檢傷送出 | 閒置(IDLE) | 開 | §3.3 |
| 路由分類器、案件備忘整理、Guardrails 自我檢查 | — | — | — | 關 | §4.9 |
開關的正式環境值在 infra/inventory/group_vars/servers.yml。
2. 系統由哪些部分組成?
flowchart LR
subgraph Front["前端(Vercel)"]
LIVE["/ 正式介面<br/>救護端、急診檢傷、醫師"]
MOCK["/internal 模擬版<br/>瀏覽器內的模擬後端"]
end
TUNNEL["Cloudflare Tunnel"]
subgraph Spark["醫院端 DGX Spark"]
API["FastAPI 服務<br/>api、ai、engine、task_rules、llm"]
NAT["NeMo Agent Toolkit<br/>(同一個行程)"]
VLLM[("vLLM<br/>Nemotron 3 Super NVFP4")]
PG[("PostgreSQL<br/>案件 JSONB+知識庫 kb_passages")]
end
LIVE --> TUNNEL --> API
API --> NAT --> VLLM
NAT -- "搜尋知識庫" --> PG
API --> PG
API -- "SSE 推播" --> LIVE- 前端是 Next.js 16 的網頁,部署在 Vercel(
triaige-demo.vercel.app,另有自訂網域triaige.wayneh.tw)。同一組畫面掛在兩個路徑下:/連真後端,/internal連瀏覽器內的模擬後端,沒有 Spark 時也能展示,醫師也在這裡的引導流程裡評述。 - 後端是單一 FastAPI 服務、單一行程,NeMo Agent Toolkit 內嵌在同一個行程裡,不另外起服務。程式分五層:
api/(路由與 SSE)、ai/(排程與 NAT 的銜接)、engine/(案件規則、TTAS)、task_rules/(任何模型都需要的檢查)、llm/(與特定模型有關的一切)。 - 模型由同一台 Spark 上的 vLLM 提供,後端用 OpenAI 相容的 HTTP 介面呼叫它。vLLM 啟動時已開啟工具呼叫(tool calling)與推理解析,鑑別診斷的智慧體靠這個功能運作(
infra/playbooks/templates/nemotron/docker-compose.yml.j2)。 - 資料庫是 PostgreSQL,一個案件存成一列 JSONB;
kb_passages表存醫學知識庫,共 3,714 段,用 PostgreSQL 內建的全文檢索(full-text search)搜尋,來源見 §10。資料庫映像含 pgvector,但目前沒有使用向量檢索。 - 對外入口只有一個:Cloudflare Tunnel 把
https://triaige-api.wayneh.tw轉到只聽本機的 FastAPI。模型端點只開放給本機與 tailnet 介面。 - 維運儀表板另外有一個唯讀的公開頁(首頁的「系統狀態」連結),顯示主機負載、vLLM 流量與每一次模型呼叫的紀錄;後端用
TRIAIGE_LLM_CALL_LOG為每個模型請求寫一行 JSON 給它。頁面會顯示輸入輸出的摘錄,所以示範時不要輸入真實病患資料(infra/README.md〈部署維運儀表板〉)。
3. AI 工作怎麼跑?
3.1 一句話從送出到畫面更新,經過哪些步驟?
sequenceDiagram
participant W as 救護端網頁
participant A as FastAPI
participant D as PostgreSQL
participant S as 排程與依序
participant M as Nemotron 3 Super
W->>A: 打開案件頁:連上 SSE(GET /cases/{id}/events)
A-->>W: SSE:心跳(每 15 秒,帶 aiAvailable)
W->>A: 送出一句話(POST,帶 clientMessageId)
A->>D: 交易:存下這句話+空的 AI 回覆(pending)
A-->>W: 提交後 SSE:message.created
A->>S: 排進 AI 工作(不等它執行)
A-->>W: 201,先回應
Note over S: 等排程名額(正式環境同時 3 個)
S-->>W: SSE:assistant.started
Note over S: 救護員的話:等同一案件前面的句子寫完
S->>M: 一次呼叫:這句話+前三句+紀錄表現況
M-->>S: 一行 JSON(附 token 機率)
S->>D: 交易:取出 JSON、確定性檢查、依來源規則寫入
S-->>W: 提交後 SSE:record.updated、message.updated、assistant.completed
S->>M: 背景(P2):主訴翻成繁中
S-->>W: 翻好、通過檢查後 SSE:record.updated- 前端依序送出。 送出的句子進入先進先出的佇列,一次送一則;前一則還沒送達,後面的就不會先送,所以 AI 讀到的對話順序不會顛倒。每則訊息帶
clientMessageId,重送時伺服器會排除重複。斷線時句子留在畫面上,恢復連線後自動送出(web/src/hooks/outbox-store.ts)。 - 伺服器先存檔再回應。
POST /cases/{id}/messages存下訊息就回 201,AI 在背景執行,所以畫面不必等模型(server/src/triaige_server/ai/service.py)。 - 確定性分流,不呼叫模型。 空白或只有標點的句子直接略過。說話者決定用哪一組提示詞、值上要標記誰說的,但不決定寫不寫:救護員的話用 R1(含生命徵象),病人與旁人的話用 R2。不論誰說的,擷取到的值都可以填進空欄位、標「待複查」,並記下說話者(救護員、病人、旁人,或辨識信心低於 0.6 的救護員),畫面上會標出「家屬說的」這類來源(
engine/routes.py、engine/record.py、docs/api/data-model.md§7)。 - 排程。 送給模型的請求有固定名額,正式環境同時 3 個。工作分四個優先級:P0 是即時的句子,P1 是說話者修正後的重跑與護理師提案,P2 是主訴翻譯、摘要、追問、醫院分級建議這類背景工作(同時最多占 1 個名額),閒置(IDLE)只有在沒有任何工作執行或等待時才啟動。即時的句子等不到名額時,執行中的 P2 會被取消,之後重新排隊;任何其他工作一到,閒置的工作也會被取消、從頭重跑(
ai/scheduler.py)。 - 同一個案件依序處理。 R1 的提示詞要帶上前三句與它們在紀錄表上的內容,模型才知道「不對,是一百三」要更正哪一個值,所以同一個案件的 R1 句子要等前一句寫完才呼叫模型。不同案件之間、病人與旁人的話,可以同時處理(
ai/sequencer.py)。 - 呼叫模型一次。 現場擷取每句話只呼叫一次模型,不經過智慧體的多輪迴圈:溫度設 0、關閉推理、最多輸出 300 個 token,並回傳每個 token 前五名候選的機率。輸出是一行精簡的 JSON,涵蓋年齡、性別、主訴、生命徵象、疼痛、病史、用藥與過敏等 12 個欄位(
llm/profiles/nemotron_3_super/)。 - 整理回覆,再嚴格檢查。 模型層先把回覆整理成一個 JSON 物件,任務層再嚴格檢查:未知的欄位、超出合理範圍的生命徵象、沒有原文依據的「無過敏」,都會被丟掉。選項欄位(性別、過敏狀態、病史)另外用 token 機率計算信心分數,分數會存下來,但目前不顯示(見 §4.9)。
- 依來源規則寫入。 空欄位直接寫入並標「待複查」,附上來源句與說話者。還沒有人複查的 AI 值,可以被看過相關句子的新判斷取代;人填的或已確認的值不會被覆蓋,AI 給的不同值以替代選項列在旁邊。人刪掉的 AI 值會被記住,AI 不會再寫回去(
engine/record.py、engine/retroactive.py)。 - 推播。 寫入的交易提交之後,才依序推播
record.updated、assistant.completed等事件,前端據此更新快取(server/src/triaige_server/bus.py、web/src/api/live.ts)。 - 背景翻譯。 英文主訴由背景工作翻成繁體中文,譯文並列顯示、標「AI 翻譯」,紀錄表本身一律存英文。譯文能不能顯示,由一組確定性檢查決定;主訴含數字或明確次數時不翻譯(
engine/complaint_translation.py、task_rules/complaint_guard.py)。
模型呼叫失敗時怎麼辦? 每次呼叫的逾時是 15 秒,失敗就推播 assistant.failed,不重試:所有 LLM 角色的 num_retries 都設為 1,也就是只嘗試一次(llm/nat_config.py)。連續失敗 3 次,心跳訊息會帶出 aiAvailable=false,畫面顯示「AI 暫停」;之後只要成功一次就恢復。手動填表任何時候都能用。
3.2 送出前後的其他 AI 功能
下面五項都用同一個 Super、關閉推理、不重試。失敗時都不會擋住任何操作,也不會算進「AI 暫停」。
| 功能 | 觸發與排程 | 模型輸出 | 確定性檢查 | 失敗時 | 使用者看到什麼 |
|---|---|---|---|---|---|
建議詢問(ai/followups.py) | 救護端對話停 8 秒且擷取都完成;護理師端送出。P2,最多 3 題同時開著 | 一行 JSON,最多 3 題,每題 40 字以內 | 必須是繁中問句,不能是治療、劑量、診斷;欄位已有值、與開著或忽略過的相同的題不開(task_rules/followup_checks.py) | 只寫稽核,不重試;同一份證據不會再自動重跑 | 救護端右側「建議詢問」、護理師端 AI 助理;可標「已問」或「忽略」,忽略過的不會回來。護理師端第一題會優先提醒尚未做的 TOCC |
護理師輸入擷取(ai/nurse_notes.py) | 護理師在檢傷畫面送出一段話。P1 | 檢傷欄位提案,最多 12 項 | 只能落在白名單欄位,值要合法;數值必須是護理師原文裡寫出的數字(task_rules/nurse_note_checks.py) | 占位回覆換成失敗訊息,寫稽核 | AI 助理的回覆下掛提案清單,護理師勾選後「套用」才寫入,已有人填的值不會被蓋掉 |
急救責任醫院建議(engine/hospital_level.py) | 紀錄表欄位變動後等 2 秒。P2。只給救護端 | {level, reason, fields},約 160 token | 理由 80 字內,引用的欄位必須是送給模型的已填欄位(task_rules/hospital_level_checks.py) | 沒有建議,欄位維持空白 | 送往醫院選單上一行「AI 建議需要重度/中度/一般」附理由,標待複查;救護員確認或改選,改了之後 AI 不再動它 |
醫師「AI 整理」(engine/ai_summary.py) | 救護端送出、檢傷送出。P2;依據沒變就不再呼叫 | 3 到 5 句,每句附事實代號,約 300 token,實測 9 到 12 秒,逾時 60 秒 | 最多 6 句、每句 160 字;來源必須是有內容的事實;句中數字要出現在引用的欄位;不能出現診斷、建議、劑量等用語(task_rules/summary_checks.py) | 顯示簡短原因,下面的規則版摘要照常;不重試 | D2 摘要最上方「AI 整理」,每句可點開來源;救護端或檢傷有新資料時標「更新中」 |
急救責任醫院建議還有一道臨床下限:到院前檢傷 1 到 2 級、危急個案、心電圖疑似 STEMI 三者任一成立時,模型建議的「一般」一律改成「中度」,稽核記下調整。這個欄位與理由是救護端的參考,不進送出快照,急診看不到。
3.3 鑑別診斷:會搜尋知識庫的智慧體
這是整個系統裡唯一的智慧體,只給醫師。
- 觸發。 救護端送出(院前預通知)與檢傷送出時,後端先在同一個交易裡把狀態寫成「整理中」,醫師畫面馬上看得到新的一輪,再排進背景工作。案件事實(最新送出的紀錄、檢傷評估、確認的身分與院內既往資料)算出指紋,指紋沒變就不重做;整理中又有要求,整理完再看一次(
engine/differential_facts.py、ai/service.py)。 - 排程。 優先級是閒置:沒有任何其他 AI 工作在跑也沒有在等時才開始,任何其他工作一到就被取消(vLLM 會中止請求)、從頭重跑。等了 60 秒,或被取消 3 次,就升為 P2。這樣做是因為一次要花好幾分鐘,不能擋住現場的擷取。
- 智慧體迴圈。 NAT 函式
triaige_differential讓模型看到編了號的案件事實(f1、f2…),並提供一個搜尋工具search_knowledge(query, sources, k)。模型最多搜尋 4 次,每次可以指定來源(Tintinalli、臨床指引、臺北市手冊)與回傳段落數(1 到 8,預設 3);次數用完就收回工具,要求直接作答。每次呼叫最多輸出 8192 個 token,開啟推理(並設low_effort,單次呼叫的輸出從 4,133 降到 1,843 個 token,見模型設定檔註解),整次逾時 600 秒(ai/nat_differential.py、llm/profiles/nemotron_3_super/__init__.py)。 - 輸出。 一個 JSON 物件,常見與最致命各 5 項。每項有診斷名稱、支持的發現、不符合或尚缺的資料、2 到 4 項建議檢查、依據的案件事實編號,以及引用的知識庫段落 id。
- 確定性檢查(
task_rules/differential_checks.py):依據必須指向案件裡存在且有值的欄位,不認得的編號拿掉,一個都不剩的項目整個丟掉;引用的段落只留這次執行裡搜尋工具真的回傳過的 id,並換成「來源、章節、頁碼」的出處字串,書中原文不會進輸出;每組最多 5 項、同組診斷不重複;診斷名稱含百分比、其他文字含「機率 30%」這類說法的項目丟掉;兩組有一組完全沒有項目,整次失敗,不輸出缺一半的卡片。 - 失敗。 逾時、模型錯誤、檢查沒過都明確標成失敗並寫稽核,沒有重試、沒有備援模型;畫面說明原因,並標明下面若還顯示舊結果,不是最新的。伺服器重啟時,上一個行程留下的「整理中」會結算成失敗。
- 使用者看到什麼。 每搜尋一次,後端推播一次
differentials.updated(只推給醫師),D2 的「AI 整理過程」逐步顯示:搜尋了什麼、讀取了哪些出處。完成後呈現兩欄清單,附「不含機率或信心分數,需醫師判斷」。「移到看診區」轉場在這段時間等它:顯示讀取紀錄、查詢知識庫、閱讀段落、推理、整理五個階段與進度條,最多等 150 秒,任何時候都能按「略過」(web/src/guide/transition-wait.ts)。 - 多久。 開發時實測約 70 到 110 秒,正式環境一次現場驗證約 2.5 分鐘;設計上限是 600 秒。時間主要花在推理與 3 到 4 次搜尋後再推理。
3.4 送出之後的規則流程
檢傷與看診的決定仍由規則與人負責,不呼叫模型。急診看到預先填好的檢傷表,救護端沒人確認過的欄位標成「未複查」並附上來源。TTAS 引擎依規則算出建議級數,每一項理由都附上觸發的規則;需要人判斷的條件(例如「收縮壓低於 90 是否伴隨休克?」)會列出來交給護理師,引擎不替人決定。護理師送出的級數和建議不同時,必須填寫理由。醫師只看得到已完成檢傷的案件,規則版摘要與危險訊號由規則從紀錄組出;危險訊號就是最新一組救護量測裡,所有落在危急範圍的生命徵象(engine/ttas.py、engine/views.py、engine/vital_tint.py)。
4. 為什麼這樣設計?
4.1 為什麼模型不做臨床決定?
產品的定位是:凡是有公開規則可循的臨床決定,都交給確定性規則,最後由人決定(docs/product/triaige.md §3.3)。TTAS 有公開的判定標準,生命徵象的異常界線也有 TTAS、NEWS2、PALS 等公開來源,所以這些都用規則計算,結果可以逐條查到依據,也不會因為模型換版而改變。
模型擅長兩件事:把口語整理成結構化資料,這是現場最花時間的工作;以及讀大量文字後列出選項。所以模型負責欄位擷取、翻譯、把已有資料改寫成幾句摘要、建議還該問什麼,以及醫師端的鑑別診斷。這些輸出都不是決定:擷取的值待複查,摘要不能出現診斷與建議,鑑別診斷是附出處的選項、不含機率。這樣分工,模型出錯時最壞的結果是一個待複查的欄位填錯,或醫師多看到一個不合適的選項;檢傷級數不會因此算錯。
4.2 為什麼現場擷取一句只呼叫一次、不推理,鑑別診斷卻是開推理的智慧體?
兩件事的時間預算差了兩個數量級,所以做法不同。
現場擷取:求快。 救護員一句話說完,欄位要在幾秒內出現。我們比較過推理強度:關閉推理時,擷取延遲中位數 4.2 秒、F1 0.956;完整推理時 24.9 秒、F1 0.954。在這個任務上,推理沒有提高準確度(eval/README.md〈推理強度〉,舊設定的數字)。智慧體迴圈也一樣,每多一輪,就多一次要花好幾秒的呼叫。所以擷取每句只呼叫一次,系統層的智慧體行為交給模型外的確定性程式負責:追蹤案件狀態與缺漏欄位;依說話者選擇提示詞,並標記每個值的來源;AI 的新值和人填的值不同時,列成替代值而不覆蓋;出現更正時,判斷要不要回頭重評舊值(必要時送第二次呼叫);依優先級排程。
鑑別診斷:求完整。 這項工作在檢傷之後、醫師看診之前背景執行,結果是給醫師的參考,不在現場的關鍵路徑上,可以花一到三分鐘。它需要的又正好是智慧體的能力:先讀病人資料,決定要查什麼,查完讀段落,發現還有不確定的危險診斷時再查一次,最後才寫出兩份清單。一次搜尋結果無法預先決定,下一步查什麼取決於上一步讀到的內容,所以是迴圈而不是固定流程。推理則用在把病人資料與查到的段落逐項比對,這是擷取不需要的工作。
要注意的是兩者的證據不對稱:擷取有 133 筆案例的評測與保留集數字;鑑別診斷還沒有準確度數字。評分流程已經備好,但標準答案要由醫師提供,目前沒有。我們能說的是它的檢查(出處必須真實、依據必須存在、不含機率)都由程式強制,不能說它診斷得準。
4.3 為什麼 AI 的值直接寫進欄位,而不跳出確認卡?
救護員一邊處置病人一邊用平板,任何彈出視窗都會打斷工作。所以 AI 的值直接顯示在要填的欄位上,標「待複查」;救護員要做的是複查,不必在卡片彈出的當下決定接受與否。缺漏的必填欄位與 TTAS 參考級數固定顯示在畫面上的一個區域,送出時還沒複查的欄位,也只在送出列上常駐提示,不用對話框擋住。醫院分級建議、建議詢問與護理師的欄位提案也遵守同一個原則:提示放在原地,由人決定接受、修改或忽略。
為了讓這樣做是安全的,寫入規則很保守:人填的值不會被 AI 覆蓋;AI 的值一律待複查,並標出是誰說的(病人、旁人說的也一樣);刪掉的 AI 值不會再被寫回去;每個欄位都保留來源句、說話者與變更歷程。
4.4 為什麼失敗不重試,也沒有備援模型?
我們認為默默降級比明確失敗更危險。如果 JSON 壞了就重試,或改用比較弱的備援模型,畫面上的值看起來一樣,品質卻可能不同,使用者也無從得知。所以每個 LLM 角色的 num_retries 都設為 1,也就是只嘗試一次(NAT 的 LLM client 預設會對 429、5xx 自動重試共 5 次,我們特地降掉;llm/nat_config.py):失敗就推播 assistant.failed,連續失敗就顯示「AI 暫停」,手動填表照常可用。背景功能失敗時,也明確顯示失敗或空白,不補一段看起來像成功的內容。啟動時也採同樣的原則:設定了不認得的模型設定檔,或要求不能回傳 token 機率的模型計算信心,伺服器在啟動時就會停止。
4.5 為什麼安全規則寫成程式,而不寫進提示詞?
我們試過只靠提示詞擋下錯誤的「無過敏」:3 次整段口述中有 2 次,模型對一個從沒提到過敏的案例寫了「無」(eval/README.md〈R1 採用標準與重新判定〉)。錯誤的「無過敏」會關掉缺漏過敏的提醒,後果嚴重,所以這條檢查改用程式執行:來源句裡沒有提到過敏的字眼時,「無」與「未知」都會被丟掉。階段 2 的功能沿用同一個做法:摘要的數字必須出現在引用的欄位裡,鑑別診斷的依據必須是案件裡真的有值的欄位,醫院分級的理由只能引用送給模型的欄位。
一條檢查該放在哪一層?我們問的是:如果模型完美無缺,這條檢查還需要嗎?仍然需要的檢查(生命徵象範圍、過敏要有依據、翻譯檢查)放在任務層 task_rules/,只用標準函式庫,換模型也不必改;只是為了修補這個模型輸出習慣的檢查,才放在模型層 llm/(docs/architecture/README.md §7)。
4.6 為什麼要有模型適配層?
模型更新得很快,為某一個模型做的調整,不該分散在整個程式裡。現行模型的所有設定,包括提示詞、取樣參數、怎麼關閉推理、怎麼把回覆整理成 JSON、怎麼讀取 token 機率、工具呼叫的定義、逾時與同時請求數,都集中在 server/src/triaige_server/llm/ 的 ModelProfile 物件裡。server/tests/unit/test_architecture.py 強制這條邊界:llm/ 以外不能出現模型名稱、取樣參數與端點字串,下層也不能 import 上層,沒有例外清單。
Nemotron 3.5 Super VL 推出後,我們實際換過一次:只新增一個設定檔、改一個部署變數,就把它換上正式環境評估,再用同一份部署腳本(playbook)換回來,前後約 57 分鐘。評估結果比 3 Super 差,所以正式環境維持 3 Super(eval/results/nemotron-3-5-super-vl-2026-10-18/README.md)。
4.7 為什麼一個案件存成一列 JSONB,而且只有一個行程?
一個案件本身就是一份文件:紀錄表、對話、建議、稽核與檢傷都屬於同一個案件,幾乎每次讀寫都以案件為單位,拆成多張正規化的表,只會讓交易更複雜。每次寫入時,在交易內用 SELECT … FOR UPDATE 鎖住那一列,套用規則後寫回,提交之後才推播。資料庫的鎖在提交時就釋放了,為了讓推播順序與寫入順序一致,同一個案件的寫入另外在行程內排隊,所以後端只有一個行程(server/src/triaige_server/store.py)。
這是依一台 Spark、一家醫院的規模做的選擇。事件不保存、不補送;前端連上 SSE 之後再讀一次快照,所以連線建立後的事件不會遺漏。
4.8 為什麼前端要有兩套後端?
真後端要有 Spark 才能執行,但展示、設計審查與醫師評述不該依賴它。/internal 在瀏覽器內用 TypeScript 實作同一套案件規則,資料存在 IndexedDB。兩邊的規則必須一致,所以有一份共用的測試案例(server/tests/fixtures/mock_parity.json),逐筆比對後端與模擬後端的結果;生命徵象色塊、備忘、回溯更正、急救責任醫院建議等規則都包含在內。模擬後端裡的 AI 回覆是預先寫好的,不是即時模型輸出。
4.9 為什麼有些功能做好了卻沒有開啟?
每一項 AI 功能上線前,我們先寫下採用標準與保留集,保留集只跑一次,沒通過就不開啟。下面幾項是沒通過或還沒準備好的,評審問到臨床風險時可以用來說明。階段 2 新增的功能(鑑別診斷、AI 整理、建議詢問、護理師輸入擷取、醫院分級建議)在正式環境都已開啟,但它們沒有保留集數字,原因在 §4.2 與 §8。
| 功能 | 狀態 | 原因 |
|---|---|---|
| Guardrails 自我檢查護欄 | 已接上,關閉 | 每次擷取要多一次模型呼叫,設計估計多 1–3 秒;安全檢查改由確定性程式負責 |
| 路由分類器(判斷一句話是不是病歷內容) | 關閉 | 召回率 0.981,沒達到 0.99 的目標,而且要多一次呼叫(eval/routing/README.md) |
| 選項欄位的信心等級 | 分數已計算並儲存,等級不顯示 | 校準後發現,模型幾乎對每個值都給很高的機率,包括少數錯誤的值,門檻分不出對錯(eval/results/choice-confidence-2026-10-18/summary.md) |
| AI 寫案件備忘 | 人可以寫備忘,AI 寫備忘的功能關閉 | 兩個版本都沒通過保留集:一版降低了擷取 F1 並增加延遲,另一版寫出太多不需要的備忘(eval/README.md〈M5 的結果〉、〈案件備忘 第 3 版〉) |
4.10 為什麼知識庫只顯示出處,不顯示書中原文?
三類來源的授權不同,我們依各自的條件使用(詳細清單在 §10):Tintinalli 有版權,版權頁禁止未經同意重製或存入檢索系統,所以書的內容不進 repository、只放在不進版控的 private/,介面只顯示來源、章節與頁碼;開放指南只收 Creative Commons 授權的全文,同樣只在介面顯示出處;臺北市手冊的版權屬臺北市政府消防局,引用需事先書面申請,沒有公開授權,介面也只顯示章節與頁碼。
檢索層只依賴一個介面:查詢進去,回傳段落 id、出處與文字。未來醫院換成自己購買授權的知識庫,或把全文檢索換成向量檢索,只要換一個實作,智慧體、提示詞與檢查都不用動(server/src/triaige_server/kb.py、ai/kb_binding.py)。
5. 用了哪些 <帳號> 技術,用到什麼程度?
| 元件 | 用途 | 狀態 |
|---|---|---|
| DGX Spark(GB10,128 GB 統一記憶體) | 醫院端唯一的主機,模型、後端與資料庫都在上面 | 執行中 |
| Nemotron 3 Super 120B-A12B NVFP4 | 流程中唯一的模型:欄位擷取、主訴翻譯、追問、護理師輸入、醫院分級、AI 整理(關閉推理),以及鑑別診斷智慧體(開啟推理、工具呼叫) | 執行中 |
| vLLM v0.31.0 | 提供 OpenAI 相容 API;多 token 預測(MTP)的推測解碼一次 3 個 token、前綴快取、fp8 KV cache;開啟自動工具選擇與工具呼叫解析、推理解析,支援智慧體的 function calling | 執行中 |
| NeMo Agent Toolkit 1.9.0 | 每一次模型呼叫都是一個 NAT 函式,在 FastAPI 行程內由 WorkflowBuilder 載入;鑑別診斷是一個 NAT 串流函式,內含工具呼叫的智慧體迴圈,搜尋工具接 PostgreSQL 知識庫;最多九個 LLM 角色(含鑑別診斷)由模型設定檔組成 | 執行中 |
| NeMo Guardrails(透過 NAT 的 guardrails 中介層) | 中介層包在每個函式外面;自我檢查護欄已經寫好 | 已接上,護欄關閉 |
| Nemotron 3.5 Super VL | 換上正式環境評估 | 比 3 Super 差,沒有採用 |
| Nemotron 3.5 Lightning 30B-A3B | 評估能否當作較快的擷取模型 | 準確度較低,也不能和 Super 放在同一台機器上,沒有採用 |
| pgvector | 資料庫映像已含,kb_passages 預留向量欄位 | 已安裝,未使用:檢索目前是全文檢索,向量檢索要先用一批案例比對再決定 |
| NeMo Evaluator、Nemotron embedding、OpenShell | 評測框架、向量檢索、智慧體沙箱 | 規劃中 |
| NemoClaw | 調查後發現還在 alpha 階段,也沒有說明如何接上自行開發的智慧體 | 沒有整合 |
為什麼鑑別診斷用 NAT?NAT 讓函式、LLM 設定、中介層與串流事件接在同一個框架裡:擷取、翻譯、追問用同一套載入與設定方式,鑑別診斷只是多一個會呼叫工具的函式,搜尋工具與知識庫的接法透過 kb_binding 登記,不必另外寫一套服務。
為什麼用 NVFP4?BF16 的 Super 約 240 GB,一台 Spark 放不下;NVFP4 的權重是 74.8 GiB,載入並配置 KV cache 後約佔 95 GiB,留給主機、後端與資料庫約 16 GiB(docs/product/slides-outline.md 第 10 頁、eval/README.md〈輸出格式與 vLLM 版本〉)。
單一請求的解碼速度約 31 tok/s,已經接近這台機器的上限:Spark 的記憶體頻寬約 273 GB/s,Super 每產生一個 token 要讀約 6 到 7 GB 權重,單一請求的上限約 40 tok/s。所以降低延遲的主要做法是讓模型少寫字:改成精簡的 JSON 輸出後,輸出 token 少約 40%、延遲少約 31%,F1 不變(eval/README.md〈服務參數實測〉)。鑑別診斷的推理無法用這個辦法縮短,所以改成背景執行,並在畫面上逐步顯示搜尋與閱讀的過程。
6. 前端怎麼組成?
| 項目 | 選擇 |
|---|---|
| 框架 | Next.js 16(App Router)、React 19 |
| 樣式與元件 | Tailwind CSS 4、Base UI |
| 資料存取 | TanStack Query 5;SSE 事件直接更新查詢快取 |
| API 型別 | 由 docs/api/openapi.yaml 產生,前後端共用同一份介面規格(42 個操作);推播事件定義在 asyncapi.yaml |
| 模擬後端 | 瀏覽器內的 TypeScript 規則引擎,資料存在 IndexedDB |
| 醫師評述 | 引導流程裡的評述存在 Neon(PostgreSQL),由 Drizzle 管理 |
| 部署 | GitHub Actions 跑完 bun run check 後部署到 Vercel |
現場畫面有兩條規則。首先,任何操作都不阻塞畫面,AI 的結果經推播逐步出現。其次,救護與急診的現場畫面不用浮動通知,提示放在畫面中固定佔位的提示列,不會蓋住任何按鈕。
階段之間的銜接是兩個轉場。救護端送出後播「救護車」轉場,最多等 6 秒,讓急診端的交接包與檢傷評估讀回來;檢傷送出後播「移到看診區」轉場,等鑑別診斷整理,最多等 150 秒。兩個轉場都有一顆隨時可按的「略過」。轉場只是等待的畫面,不影響後端是否完成(web/src/components/flow/stage-advance.tsx、web/src/guide/transition-wait.ts)。
7. 關鍵數字速查
| 數字 | 意思 | 出處 |
|---|---|---|
| F1 0.954/0.957 | 整段口述擷取 12 個欄位的宏平均 F1(種子 1 的 132 段/種子 2 的 133 段) | eval/README.md〈輸出格式與 vLLM 版本〉 |
| 幻覺欄位 4/1127、4/1147 | 同上兩個種子中,模型填了口述沒說的欄位數 | 同上 |
| 2.89 秒 | 整段口述擷取的延遲中位數,平均輸出 71 個 token | 同上 |
| F1 1.000、幻覺 0/401 | 透過真後端逐句處理 47 段保留集口述(163 句):F1 是 11 個結構化欄位的結果,幻覺以全部預測欄位計算 | eval/results/r1-context-fix-2026-10-14/ |
| 1.42 秒/2.08 秒 | 同上,每句延遲的中位數/p95 | 同上 |
| 3.89 秒 | 同時 3 個請求、壓力測試 30 分鐘的擷取 p95;CUDA 錯誤、重啟、逾時都是 0 | docs/specs/2026-10-13-ai-routing/soak-results.md |
| 3 | 正式環境同時送給模型的請求數(設定檔預設 2) | infra/inventory/group_vars/servers.yml |
| 4.2 秒對 24.9 秒 | 關閉推理與完整推理的擷取延遲,F1 0.956 對 0.954(舊的完整輸出格式;現行約 2.9 秒) | eval/README.md〈推理強度〉 |
| −31% 延遲、−40% token | 精簡 JSON 輸出與完整格式相比,F1 不變 | eval/README.md〈輸出格式與 vLLM 版本〉 |
| 0.909 → 1.000 | 帶上前文後,回溯更正的召回率(保留集 34 句中的 11 句回溯更正);沒有指名欄位的更正 3/9 → 8/9 | eval/results/r1-context-fix-2026-10-14/ |
| 6/15 → 0/15 | 救護員不是病歷內容的話(問病人、問 AI、閒聊)被寫成欄位的句數(保留集);其中問句 4/5 → 0/5 | eval/README.md〈後續:問句不是陳述〉 |
| 2/3 | 只靠提示詞時,對沒提到過敏的案例寫出「無過敏」的次數 | eval/README.md〈R1 採用標準與重新判定〉 |
| ×1.007 | 要求回傳 token 機率的延遲成本;同樣設定重跑的對照組是 ×1.004 | eval/results/choice-confidence-latency-2026-10-14/README.md |
| 17/163 | 溫度 0 時重跑同一批句子,輸出文字不同的句數 | 同上 |
| 31 tok/s | 單一請求的解碼速度;依頻寬推算的上限約 40 tok/s | eval/README.md〈服務參數實測〉 |
| 74.8 GiB/約 95 GiB | NVFP4 權重大小/載入並配置 KV cache 後的佔用 | docs/product/slides-outline.md、eval/README.md |
| 4.3 倍、F1 0.938 | Lightning 比 Super 快的倍數,以及它的 F1(Super 0.955) | eval/README.md〈服務參數實測〉 |
| 8.7% 對 1.6% | 3.5 Super VL 與 3 Super 的重複填入率 | eval/results/nemotron-3-5-super-vl-2026-10-18/README.md |
| 133 張 | 評測案例卡,來自 MIMIC-IV-ED demo 中 47 位病人的救護車到院紀錄 | eval/README.md〈目前產出〉 |
| 3,714 段 | 知識庫總段數:Tintinalli 1,745、開放指南 1,688(31 份)、臺北市手冊 281 | server/scripts/export_kb.py 的輸出 |
| 0.978/0.924 | 臺北市手冊檢索,45 題自擬查詢的章節層級 recall@5/MRR;金標有 8 題是看過結果後依內文校正的,不是未調整的成績 | eval/ems_protocol/README.md |
| 4 次、8192 token、600 秒 | 鑑別診斷智慧體的搜尋次數上限、每次呼叫的輸出上限、整次逾時 | llm/prompt_sets/ems_compact/differential.py、llm/profiles/nemotron_3_super/__init__.py |
| 70–110 秒;約 2.5 分鐘 | 鑑別診斷開發時實測的時間;正式環境一次現場驗證的時間(單一案例,不是統計) | 模型設定檔與轉場常數的註解;現場驗證為團隊回報,repository 沒有紀錄檔 |
| 9–12 秒 | 醫師「AI 整理」一次呼叫的實測延遲(逾時 60 秒) | llm/profiles/nemotron_3_super/__init__.py 註解 |
| 150 秒、6 秒 | 「移到看診區」與「救護車」轉場最多等多久 | web/src/guide/transition-wait.ts |
| 42 | API 操作數 | docs/api/openapi.yaml |
| 約 1,530/2,020/440 | 後端、網頁、評測的測試數(以 def test_、it(、test( 計算定義數,2026-10-22 計算) | server/tests/、web/src/、eval/ |
8. 評審問答
回答時注意三件事:不說「臨床驗證過」,只說「在模型寫的繁中口述上量過」;不把規劃中的功能說成已經完成(OpenShell、NeMo Evaluator、向量檢索、語音輸入都還沒做);只引用 repository 裡有出處的數字。鑑別診斷、AI 整理、建議詢問這幾項階段 2 的功能沒有準確度數字,被問到時直接說沒有,並說明我們用程式強制了哪些檢查。
場景與產品
問:這跟「語音轉文字之後交給 ChatGPT 填表」有什麼不同?
差別在模型外的規則。每個 AI 值都標成待複查,附上來源句與說話者(病人、旁人說的會特別標出);人填的值不會被覆蓋;沒有依據的「無過敏」會被丟掉;檢傷級數由規則建議、由護理師決定。另外,整套系統跑在醫院端的一台 DGX Spark 上,推論不使用雲端模型。
依據:§3、§4.3、docs/api/data-model.md §2。
問:AI 填錯了,誰負責?
AI 的值是草稿,標成「待複查」,救護員確認後才算數;送到急診時,沒人確認過的欄位會標成「未複查」。檢傷級數只能由護理師送出,改動建議級數時要附上理由,每一次寫入、複查與送出都有稽核紀錄。責任仍然在人身上,和現在一樣;系統讓人看得到哪些值是 AI 填的、依據的是哪一句話。
依據:docs/api/data-model.md §2、§10。
問:救護員在現場真的有空複查嗎?
我們刻意不要求救護員當下就決定。AI 的值直接出現在欄位上,不跳出視窗;救護員可以在運送途中有空時一次複查,送出列上也會常駐顯示還有哪些欄位沒複查。現場畫面不打斷使用者,是我們定下的介面原則。
依據:§4.3。
問:為什麼不直接串接醫院的 HIS/EMR?
黑客松期間沒有真實的醫院系統可以串接,健保卡查詢與院內既往資料都用虛構病人代替。API 裡已經有查詢與確認身分的操作,導入時再換成院方的介面。
依據:docs/api/README.md §1.3。
問:整個流程裡,AI 到底做了哪幾件事?
七件事,都在背景或畫面上有明確的位置:欄位擷取、主訴翻譯(救護端);追問建議(救護端與護理師);護理師打的一段話擷取成欄位提案;醫院分級建議(救護端選醫院時);醫師的「AI 整理」摘要;鑑別診斷。每一項的觸發、優先級、檢查與失敗時的畫面見 §1.2 與 §3.2、§3.3。
依據:§1.2。
智慧體
問:這算智慧體嗎?看起來只是一個擷取 API。
有兩層。現場擷取刻意做成單次呼叫,因為延遲預算以秒計,而推理並沒有提高準確度;系統層的智慧體行為包括持續追蹤案件狀態與缺漏欄位、依說話者選擇提示詞並標記來源、AI 值與人填的值不同時列成替代值、遇到更正時判斷要不要回頭重評舊值、依優先級排程即時與背景工作。
醫師端的鑑別診斷則是完整的工具呼叫智慧體:模型讀病人資料,自己決定搜尋什麼、查哪個來源,讀回傳的段落,必要時再搜尋(最多 4 次),最後輸出兩份清單。搜尋次數用完就收回工具,要求直接作答。
依據:§3.3、§4.2、ai/nat_differential.py、engine/retroactive.py、ai/scheduler.py。
問:為什麼不讓模型多推理一點?
現場擷取我們量過:完整推理時,擷取延遲中位數 24.9 秒;關閉推理時 4.2 秒,F1 是 0.954 對 0.956(舊的完整輸出格式;現行設定約 2.9 秒)。在這個任務上,開啟推理慢了約六倍,準確度沒有提高。鑑別診斷則開啟推理,因為它在背景執行、結果是給醫師的參考,花一到三分鐘可以接受,而且需要把病人資料與查到的段落逐項比對。
依據:eval/README.md〈推理強度〉、§4.2。
問:鑑別診斷準不準?
我們沒有準確度數字。評分流程(常見 Top 5 是否含最終診斷、最致命 Top 5 是否列出不可漏診斷)已經備好,但標準答案要由醫師提供,目前沒有。能說的是它受到哪些檢查:每一項的依據必須是案件裡真的有值的欄位;引用的出處必須是這次搜尋真的回傳過的段落;不含機率或信心分數;兩組清單缺一組就整次失敗,不輸出半張卡片。輸出是給醫師考慮的選項,不是診斷或醫囑。
依據:eval/README.md〈鑑別診斷評測〉、task_rules/differential_checks.py。
問:鑑別診斷會不會誤導醫師?
會有風險,所以設計上有幾道限制。清單不含機率,避免給人過度的確定感;「最致命」那一組本來就要列出可能性不高但不能漏的診斷,所以名單出現罕見診斷是設計,不是錯誤;每一項都寫明「不符合的地方或尚缺的資料」與建議檢查,並附上依據的病人資料與知識庫出處,醫師可以點回原始欄位核對;失敗時明確顯示失敗,不顯示看起來像成功的內容。最終判斷仍然是醫師的。
依據:task_rules/differential_checks.py、web/src/screens/physician/DifferentialCard.tsx。
問:智慧體搜尋的知識庫是什麼?怎麼搜?
知識庫在同一個 PostgreSQL 的 kb_passages 表,共 3,714 段,來源是 Tintinalli 急診手冊第 7 版(1,745 段)、31 份開放授權的臨床指引(1,688 段)和臺北市到院前緊急救護作業程序手冊第六版(281 段)。英文用 PostgreSQL 的英文全文檢索,中文手冊用相鄰兩字切詞的全文檢索;沒有用向量。完整的來源、版本與授權在 §10。
依據:§10、server/src/triaige_server/kb.py。
問:為什麼不用向量檢索(embedding)?
目前的檢索是全文檢索加排序,足以讓智慧體用精確的臨床詞彙查到段落,也不需要再多部署一個 embedding 模型。已知的限制是同義詞:例如臺北市手冊的內文用「呼吸困難」與藥名,不出現「氣喘」,字面比對就找不到。向量檢索排在後續,要先用一批案例比較再決定;pgvector 已經裝在資料庫映像裡,但沒有使用。
依據:eval/ems_protocol/README.md。
問:知識庫的內容有版權,怎麼處理?
書與手冊的全文只放在不進版控的 private/ 和 Spark 的資料庫裡;repository 只有轉換與檢索程式,以及指引的來源清單。介面上只顯示來源、章節與頁碼,從不顯示書中原文,這也是為什麼畫面的出處格式是「Tintinalli 7e, Ch. 章號 章名, pp. 起–迄」。Tintinalli 的版權頁禁止未經同意重製與存入檢索系統,所以這本書只用於示範,正式產品的方向是改用醫院自行購買授權的知識庫(檢索層可以抽換)。開放指引只收 Creative Commons 授權的全文。臺北市手冊的版權屬臺北市政府消防局,引用需事先書面申請;repository 裡沒有取得書面同意的紀錄。
依據:§4.10、§10、eval/README.md〈Tintinalli 檢索〉、docs/team-discussion-todo.md A16。
問:鑑別診斷要等多久?現場等得起嗎?
一個案例約 70 到 110 秒,正式環境一次現場驗證約 2.5 分鐘,上限是 600 秒。它在背景執行,不在現場的關鍵路徑上:救護端送出時就開始整理,檢傷送出後再把檢傷資料算進去。醫師看到的是逐步出現的搜尋與閱讀過程,不是空白轉圈;「移到看診區」轉場最多等 150 秒,之後放行,結果出來時會在畫面上更新。
依據:§3.3、web/src/guide/transition-wait.ts。
邊緣運算與 <帳號>
問:為什麼一定要在邊緣執行?雲端模型不是更強?
病人的個資、病史與醫院的病歷都在院內,醫院購買的臨床資料庫也多半限院內使用。模型放在資料所在的地方,資料就不必離開醫院。我們也量過,一台 Spark 跑得動 120B 等級的模型:透過真後端,每句擷取的延遲中位數是 1.42 秒。展示用的前端放在 Vercel,後端經 Cloudflare Tunnel 對外,所以展示時請求會經過 Cloudflare 的網路;模型推論本身不離開那台 Spark,展示用的病人全是虛構的。
依據:docs/product/triaige.md §3.2、本文 §7。
問:一台 Spark 能支援幾台救護車?
我們量的是同時 3 個請求、連續 30 分鐘:沒有崩潰、沒有逾時,擷取 p95 是 3.89 秒;同時 4 個請求時,p95 升到 4.70 秒。這是只跑擷取的數字。階段 2 加入背景功能之後,鑑別診斷等所有其他工作都閒著才會開始,會讓出名額給即時的句子,但我們沒有重新量過包含鑑別診斷的混合負載。能支援幾台救護車,取決於每台車說話的頻率,我們沒有實測多台救護車同時口述的情境,所以不提供這個數字。
依據:docs/specs/2026-10-13-ai-routing/soak-results.md、ai/scheduler.py。
問:為什麼選 Super,不選更快的 Lightning?
Lightning 快約 4.3 倍,但 F1 是 0.938 對 0.955;口述只講舒張壓時,它有 9 筆把舒張壓留空。它也不能和 Super 同時放在一台 Spark 上,記憶體不夠。
依據:eval/README.md〈服務參數實測〉。
問:Nemotron 3.5 Super 推出了,為什麼不換?
我們換上正式環境評估過。它快約 23%,但用事先宣告的標準來看,整段口述的三項指標都沒有通過;重複填入率是 3 Super 的 5 倍(8.7% 對 1.6%),路由分類的召回率從 0.952 降到 0.857。依照我們的規則,在保留集上不比現行模型差才切換,所以維持 3 Super。
依據:eval/results/nemotron-3-5-super-vl-2026-10-18/README.md。
問:NeMo Agent Toolkit 在這裡做了什麼?只是多包一層嗎?
每一次模型呼叫都是一個 NAT 函式,由 NAT 的 WorkflowBuilder 在 FastAPI 行程內載入,LLM 的設定由模型設定檔組成。擷取、翻譯等函式是單次呼叫;鑑別診斷是一個串流函式,內含工具呼叫的智慧體迴圈,每次搜尋就送出一個事件,後端據此更新案件並推播給醫師。Guardrails 以 NAT 中介層的形式包在每個函式外面。好處是護欄、評測與檢索都接在同一個地方,不必各接一套。
依據:server/src/triaige_server/ai/workflow.yml、ai/extractor.py、ai/nat_differential.py。
問:Guardrails 為什麼沒有開?
自我檢查護欄已經寫好、接上中介層,但每次擷取要多一次模型呼叫,設計估計多 1 到 3 秒。一直開著的安全檢查是確定性的程式:格式、範圍、過敏要有依據、翻譯檢查、摘要與鑑別診斷的出處檢查,這些不花模型的時間,也不會因為模型換版而改變。
依據:§4.9、infra/inventory/group_vars/servers.yml(triaige_self_check_output: false)。
問:NemoClaw、OpenShell 呢?
我們調查過 NemoClaw,它還在 alpha 階段,也沒有說明如何接上我們自己的智慧體,所以沒有整合。OpenShell 排在後續試裝,構想是把智慧體放進沙箱,只允許連到資料庫與 vLLM;目前智慧體只有一個搜尋工具,而且工具只能讀 kb_passages。
依據:docs/specs/2026-10-13-backend/decision.md、research-<帳號>-stack.md。
模型與評測
問:F1 0.95 是怎麼量的?資料可信嗎?
案例卡來自公開的 MIMIC-IV-ED demo,是美國急診 47 位病人的 133 次救護車到院紀錄;繁中口述稿由模型依案例卡寫成,經過程式檢查,還沒有臨床人員審閱。所以這個數字說明的是這條流程從乾淨的繁中口述擷取欄位的能力,不代表臨床驗證,也不代表在吵雜語音或台灣案例上的表現。大多數結果只跑過一次,千分之幾的差異是雜訊。這個數字只涵蓋欄位擷取,不涵蓋鑑別診斷、摘要與追問。
依據:eval/README.md〈誠實規則〉、〈目前產出〉。
問:怎麼防止模型編造口述裡沒說的內容?
有三層。提示詞要求沒提到的欄位不寫;任務層丟掉未知欄位、超出範圍的值與沒有依據的「無過敏」;寫入規則讓每個 AI 值都標成待複查並附上來源句。整段口述的幻覺欄位是 1127 個中 4 個;透過真後端逐句處理的保留集,全部 401 個預測欄位中是 0 個。摘要與鑑別診斷用另一種做法:模型只能用編號引用事實,程式把編號換回欄位,編造的編號會被丟掉;摘要裡的數字還要出現在引用的欄位裡。
依據:§3、§7、task_rules/summary_checks.py。
問:病人說的話和救護員說的話怎麼區分?
每句話都帶說話者:救護員、病人或旁人。說話者決定用哪一組提示詞,也記在每個值上,畫面會標出「家屬說的」這類來源,救護員複查時就知道這個值是誰說的。不過,不論誰說的,值都一樣先填進空欄位、標成待複查,已複查或人填的值不會被覆蓋。目前文字輸入的說話者預設是救護員,可以在對話裡修改,改了之後那句話會依新的說話者重新處理;規劃中由語音端的說話者辨識帶入,信心低於 0.6 時標成「說話者不確定」。
依據:engine/routes.py、docs/api/README.md §6。
問:救護員說錯後更正,例如「不對,是一百三」,系統怎麼處理?
擷取時,提示詞會帶上前三句與它們在紀錄表上的內容,模型因此知道要更正哪一個值。保留集 34 句裡有 11 句回溯更正,召回率從 0.909 提高到 1.000;沒有指名欄位的更正,從 9 句對 3 句提高到 9 句對 8 句。如果舊值的依據不在這次看過的句子裡,系統會送第二次呼叫重評,不會直接覆蓋。
依據:eval/results/r1-context-fix-2026-10-14/、engine/retroactive.py。
問:「他有沒有藥物過敏?」這種問句會不會被寫成資料?
以前會,這是我們在後端測試中自己發現的問題。修改提示詞之後,保留集裡被抽出欄位的過敏問句,從 5 句中 4 句降到 0 句;範圍放寬到救護員所有不是病歷內容的話(問病人、問 AI、閒聊),被寫成欄位的從 15 句中 6 句降到 0 句,並用測試固定下來。
依據:eval/README.md〈後續:問句不是陳述〉。
問:為什麼不用 vLLM 的結構化輸出(structured output),以 JSON schema 強制格式?
我們試過,結果更差。不允許 null 時,模型只要寫出一個填不出值的欄位,就只能填數字,口述沒提到的生命徵象變成 0,F1 降到 0.884;允許 null 時 F1 是 0.912,體溫大量漏填。不加 schema 時解析失敗是 0 次,沒有非用不可的理由。
依據:eval/README.md〈輸出格式與 vLLM 版本〉。
問:信心分數呢?為什麼沒有顯示?
分數已經在計算,也存了下來:沿著模型實際寫出的 token,把每個決定點的機率相乘。要求回傳 token 機率幾乎沒有成本(×1.007,對照組 ×1.004)。但校準後發現,模型幾乎對每個值都給很高的機率,包括少數錯誤的值,門檻分不出對錯,所以「高/需留意」的等級不顯示;分不出對錯的信心等級,反而會誤導使用者。鑑別診斷同樣不顯示機率。
依據:eval/results/choice-confidence-2026-10-18/summary.md、docs/specs/2026-10-14-inline-review/plan.md §4。
問:溫度設 0,為什麼同一句話的結果還會不一樣?
GPU 上的貪婪解碼並不完全確定。同一批 163 句重跑,有 17 句的輸出文字不同;壓力測試時,同一批訊息在單一請求下重送,欄位一致率也只有 97.8%。所以我們以 3 次的平均與範圍當作基準,不根據單次的小差異下結論。
依據:eval/results/choice-confidence-latency-2026-10-14/README.md、docs/specs/2026-10-13-ai-routing/soak-results.md。
問:知識庫的檢索品質量過嗎?
只量過臺北市手冊:45 題自擬查詢(口吻是 EMT 或護理師會問的問題),章節層級 recall@5 是 0.978、MRR 是 0.924。要誠實說明:題目與標準答案由工程端依手冊內文自擬,沒有臨床人員審過,而且有 8 題的標準答案是看過結果後才依內文校正的,所以這不是未調整的成績。Tintinalli 與開放指引的檢索品質,repository 裡沒有量化的評測;只有一次單一案例的實跑,確認工具呼叫正常、查詢合理、沒有編造段落 id。
依據:eval/ems_protocol/README.md、eval/README.md〈Tintinalli 檢索〉。
問:醫師「AI 整理」的摘要,會不會下診斷或編造?
提示詞要求只改寫事實清單,不下診斷、不建議。程式再檢查:每一句都要附來源,來源必須是這次給模型看的、有內容的事實;句子裡的數字必須出現在引用的欄位裡;出現「建議」「疑似」「診斷」「劑量」這類用語,除非引用的欄位本身就有,否則整句丟掉;通不過就顯示失敗原因,下面的規則版摘要照常。這一項同樣沒有保留集數字。
依據:task_rules/summary_checks.py。
問:醫院分級建議安全嗎?
它只是救護員選醫院時的參考:標「待複查」,附一句理由與依據的欄位,救護員可以直接確認,也可以改成別的分級,改了之後 AI 不再動它。規則另設下限:到院前檢傷 1 到 2 級、危急個案、心電圖疑似 STEMI,三者任一成立時不會建議「一般」,會改成「中度」。這個欄位不進送出的快照,急診看不到。
依據:engine/hospital_level.py、task_rules/hospital_level_checks.py。
安全與可靠度
問:錯誤的「無過敏」怎麼擋?
只靠提示詞擋不住,3 次整段口述中有 2 次寫出錯誤的「無」。所以現在改用程式規則:來源句沒有提到過敏的字眼時,「無過敏」與「未知」都會被丟掉;只提到非藥物過敏的句子,不會勾選「無過敏」。另外,在 24 句的過敏保留集上(只看模型輸出),錯誤的「無」是 0,但這個集合太小,不能算是驗證。
依據:§4.5、task_rules/record_checks.py。
問:模型故障了怎麼辦?
現場擷取每次呼叫的逾時是 15 秒,失敗就明確告訴使用者,不重試,也不換備援模型(每個角色只嘗試一次)。連續失敗 3 次,畫面顯示「AI 暫停」,手動填表照常可用;之後只要成功一次就恢復。背景功能失敗各有自己的畫面:鑑別診斷顯示失敗原因,AI 整理顯示簡短原因並保留規則版摘要,追問與醫院分級建議則不出現,不擋任何操作。伺服器重啟時,被中斷的鑑別診斷會結算成失敗,不會永遠停在「整理中」。
依據:§3.1、§3.3、§4.4。
問:資料安全怎麼做?
後端只聽本機,對外只有 Cloudflare Tunnel 這一個入口;模型端點沒有 API 金鑰,所以只開放給本機與 tailnet 介面。登入後拿到有期限的簽章 token(預設 12 小時);請求大小上限 256 KiB;日誌不記錄 query string。API 只接收文字,展示用的病人都是虛構的。智慧體的搜尋工具只能讀知識庫,不能寫入,也沒有其他工具。維運儀表板的公開唯讀頁會顯示日誌與模型輸入輸出的摘錄,所以示範用的輸入不要放真實病患資料。
依據:docs/specs/2026-10-13-backend/design.md〈公開上線的加固〉、infra/README.md。
問:主訴翻譯會不會翻錯?
紀錄表一律存英文,譯文只是並列顯示,標成「AI 翻譯」。一組確定性檢查會比對左右側、身體部位、數字與簡體字,沒通過就只顯示英文;含數字或明確次數的主訴不翻譯。在 69 個當時沒用來調整的新主訴上,63 個通過,左右側 18 個全部正確;這是 2026-10-13 那一版檢查的結果,之後的檢查更嚴,擋下的譯文更多。這組檢查由工程師草擬,還在等臨床人員審閱。
依據:eval/complaint_zh/README.md。
工程與部署
問:換一個模型要多久、要改哪裡?
程式只需要在 llm/ 新增一個設定檔,再加上 infra/ 裡那個模型的部署腳本。花時間的是評測:先宣告標準,再跑完保留集,比現行模型差就不換。3.5 Super VL 從換上到換回約 57 分鐘,其中冷啟動 12 分鐘。
依據:docs/architecture/README.md §9、本文 §4.6。
問:怎麼確定展示版和真後端的行為一致?
兩邊共用一份測試案例檔,後端測試逐筆比對 Python 引擎與 TypeScript 模擬後端的結果,規則不一致時測試就會失敗。模擬後端的 AI 回覆是預先寫好的,所以它展示的是畫面與規則,不是模型的即時表現。
依據:server/tests/unit/test_engine_parity.py、test_drift.py。
問:救護車上斷網怎麼辦?
斷線時畫面會顯示提示,已輸入的句子與欄位修改都留在畫面上,恢復連線後依序自動送出,伺服器以 clientMessageId 排除重複。限制是:佇列存在網頁的記憶體裡,換頁還在,關掉分頁就沒了;斷線期間也沒有 AI,因為模型在醫院端。簡報提過「存在裝置上的離線佇列」,這部分還沒做。
依據:web/src/hooks/outbox-store.ts、web/src/screens/ems/CaseBanners.tsx。
問:語音辨識呢?
不在這個 repository 裡。API 已經接受語音訊息的欄位,規劃是在平板的瀏覽器內辨識,音訊不離開平板;展示版用文字操作,評測的也是文字路徑。示範時的台詞面板(§9)用來代替語音,一次送一句。救護端目前有的是反方向的功能:透過耳機用語音念出新的提醒。
依據:docs/api/README.md §6、web/src/screens/ems/announcer.ts。
問:TTAS 建議準不準?
我們沒有量。規則集是 TTAS-adult@105-draft,涵蓋第一層的生命徵象調節變數與疼痛;依主訴而定的判定標準還沒有資料來源。評測資料裡的檢傷標記是 AI 草擬、還沒審閱的,不能當作標準答案。級數最後由護理師決定。
依據:engine/ttas.py、eval/README.md。
問:怎麼確保程式品質?
後端約有 1,530 個測試、網頁約 2,020 個、評測約 440 個,bun run check 沒通過就不能部署。架構邊界、提示詞的內容、模型設定檔的值,都由測試固定下來;改動伺服器、介面規格或提示詞時要經過獨立審查,只有 P0 到 P2 的問題會阻擋部署。
依據:server/tests/、web/src/、.github/workflows/deploy-demo.yml。
問:部署長什麼樣子?
後端、資料庫與 vLLM 在同一台 DGX Spark 上用容器執行,用 Ansible 部署,不靠手動步驟;前端在 Vercel。後端只聽本機,經 Cloudflare Tunnel 對外;每一次模型呼叫寫一行紀錄,維運儀表板顯示主機負載、vLLM 流量與呼叫紀錄。知識庫另外在部署時用 -e triaige_kb_import=true 匯入,匯入可以重複執行。
依據:infra/README.md。
9. 示範備援:代替產品負責人帶一遍
這一節假設你要在評審面前把產品從頭到尾帶一遍,所有畫面和功能都已經在 §1 到 §3 說明過。示範用真後端的根目錄 /,不用 /internal。
9.1 開場前檢查
- 打開首頁
/,確認角色選單正常,點「系統狀態」連結看 vLLM 與後端是否正常(頁面會顯示輸入輸出摘錄,不要在螢幕上共享給評審看日誌)。 - 若上一場留下案件,按頂列的「重置 demo 資料」清空。重置會清掉所有案件,包括別人的。
- 開三個分頁:
/ems、/ed、/physician。三個路徑各自用固定的虛構使用者登入,不需要輸入帳號。 - 示範期間不要同時讓別人在同一台後端打字或跑別的案件:鑑別診斷只在其他 AI 工作都閒著時才開始,有人送句子就會把它中斷、重跑,等待時間會變長(§3.3)。
9.2 示範腳本
用內建的劇本「胸痛:62 歲男性」,18 句台詞加一個收尾步驟。劇本面板在救護端案件畫面,平常收合,按畫面最上層的按鈕展開;每按一次「下一步」送一句,前一句的 AI 還沒處理完時按鈕暫停。
| 步驟 | 操作 | 可以怎麼說 |
|---|---|---|
| 1 | 救護員 → 新增案件,進入對話;展開劇本面板,選「胸痛:62 歲男性」 | 「語音輸入我們還沒做,劇本一句一句代替救護員、病人和家屬說話,每句都標說話者。」 |
| 2 | 送「主訴」段(病人、家屬、救護員各幾句),看右邊紀錄表 | 「AI 邊聽邊填,填進去的值都標『待複查』,家屬說的會標『家屬說的』。人填的值 AI 永遠不會覆蓋。」 |
| 3 | 送「生命徵象」段,包括口誤那句「不對不對,剛剛血壓講錯了……是一百零五、七十」 | 「救護員講錯後更正,系統帶著前三句判斷要改哪個值,血壓從 125 改成 105。這是回溯更正,保留集召回率 0.909 到 1.000。」 |
| 4 | 送「病史用藥」段(有救護員的問句與家屬的回答) | 「救護員問的問句不會被寫成資料;家屬說的病史、用藥才會。」 |
| 5 | 送「過敏與處置」段:家屬說「我真的不知道」 | 「模型沒聽到過敏就不能寫『無過敏』,這條是程式強制的,不靠提示詞。下一步的收尾會把過敏標成『不清楚』。」 |
| 6 | 按最後一步「補齊送出必填」 | 「語音沒有,意識、GCS、創傷與否和送往醫院這幾項用一個按鈕補齊,和手動填寫走同樣的寫入。」(它填入非創傷、意識清醒、GCS E4V5M6、過敏不清楚、送往重度急救責任醫院) |
| 7 | 看右側欄與送往醫院選單 | 「TTAS 參考級數由規則算,每一條理由可查;缺漏欄位和建議詢問常駐在這裡;醫院選單上這一行是 AI 建議的急救責任醫院分級,附理由,救護員可以改。」 |
| 8 | 按「送出至急診」 | 「送出後播救護車轉場,急診端同時收到預通知,鑑別診斷這時就已經在背景開始跑。」 |
| 9 | 檢傷護理師 N1:預覽交接包、開啟檢傷;N2:看預填的檢傷表、TTAS 建議、建議詢問;在 AI 助理輸入一段含數值的話,勾選提案後「套用」;用「讀取健保卡(模擬)」選林國華(62 歲男性,與劇本一致) | 「檢傷表是預填的,沒有救護員確認過的欄位標『未複查』。護理師打的一段話,AI 只提案、不直接寫;數值必須是護理師寫過的數字。級數由護理師決定,與建議不同要寫理由。」 |
| 10 | 接受或調整級別後送出檢傷 | 「檢傷級別由人送出,沒有任何一步是模型決定的。」 |
| 11 | 「移到看診區」轉場:看五個階段與搜尋步驟,可以等,也可以按「略過」 | 「這時智慧體在搜尋知識庫、讀段落、推理。這是系統裡唯一的智慧體,只給醫師,大約一到三分鐘。」 |
| 12 | 醫師 D2:看「AI 整理」、規則版摘要、鑑別診斷兩欄、「AI 整理過程」,點一個來源標籤回到原始欄位 | 「摘要每一句都附來源。鑑別診斷分常見和最致命各五項,不含機率;每一項寫了支持與不符合的地方、建議檢查,並附病人資料與知識庫出處。畫面只顯示出處,不顯示書中原文。」 |
這個流程裡用到的是真模型:欄位擷取每句約 1.4 秒;AI 整理約 10 秒;鑑別診斷 1 到 3 分鐘。
9.3 示範時出狀況怎麼辦
| 狀況 | 為什麼 | 怎麼做、怎麼說 |
|---|---|---|
| 畫面頂端出現「AI 暫停」 | 連續 3 次模型呼叫失敗;成功一次就恢復 | 說明這是設計:失敗就明確失敗,不重試、不換備援模型,手動填表照常可用。可以直接手動填一個欄位示範人工值 |
| 某句話沒有填進預期的欄位,或填的值不同 | 溫度 0 的輸出也不完全確定(17/163 句文字不同);檢查也可能丟掉不合格的值 | 說明 AI 的值只是待複查的草稿,手動改就好,這正是「人填優先」要處理的情況;不要重送同一句 |
| 鑑別診斷等了很久,轉場放行時結果還沒出來 | 轉場最多等 150 秒;背景工作被其他請求中斷會重跑 | 說明結果出來會直接更新 D2,「AI 整理過程」可以看到目前搜尋到哪;先講 AI 整理和規則版摘要,再回頭看清單 |
| 鑑別診斷顯示「整理失敗」 | 逾時、模型錯誤或檢查沒通過;設計上不重試 | 唸出畫面上的失敗原因,說明這是刻意的:不補一份看起來成功的內容。AI 整理與規則版摘要不受影響。接著改用 /internal 的模擬案例(預先寫好的鑑別診斷)示範畫面,並說清楚那是模擬 |
| 「AI 整理」沒有出現 | 還沒整理好(約 10 秒),或失敗 | 失敗時畫面寫明原因,下面的規則版摘要照常;說明兩者的差別:規則版由程式組出,AI 整理只改寫已有資料 |
| 醫院分級建議沒有出現 | 它只是參考,失敗時不顯示,也沒有重試 | 直接略過,不影響送出 |
| 轉場卡在「救護車」 | 急診端讀交接包與檢傷評估讀不到;最多 6 秒 | 按「略過」,檢傷台會自己顯示錯誤與重試 |
| 整個後端連不上 | Spark、Tunnel 或網路中斷;畫面顯示連線中斷橫幅 | 改用 /internal:同一組畫面連瀏覽器內的模擬後端,AI 回覆是預先寫好的,要明說這一點;已輸入的句子留在畫面上,恢復後自動送出 |
| 評審要看語音輸入 | 語音辨識還沒做 | 直說:API 有語音訊息的欄位,規劃在平板瀏覽器內辨識;今天的展示用文字劇本代替 |
| 評審問鑑別診斷的準確度 | 沒有準確度數字 | 直說沒有;評分流程備好、等醫師提供標準答案;說明我們強制的檢查(§8) |
| 評審問資料、版權 | 見 §4.10、§10 | 畫面只顯示出處;Tintinalli 僅供示範;臺北市手冊的書面同意沒有紀錄 |
9.4 這個產品不做、也不要說成做了的事
- 語音輸入、離線佇列、與醫院 HIS/EMR 的串接,都還沒做。
- 臨床驗證:口述稿是模型寫的,沒有臨床人員審閱;TTAS 建議的準確度沒量過。
- 鑑別診斷、AI 整理、建議詢問的準確度沒有數字。
- OpenShell、NeMo Evaluator、向量檢索、NemoClaw 沒有執行,也沒有整合。
- 模擬後端(
/internal)的 AI 回覆是預先寫好的,不是即時模型輸出。
10. 附錄:鑑別診斷用的知識庫來源
鑑別診斷的智慧體只搜尋 PostgreSQL 的 kb_passages 表,共 3,714 段,分三個來源(server/scripts/export_kb.py 的輸出):
| 來源代號 | 完整名稱 | 版本與出版者 | 段數 | 語言 | 授權依據 |
|---|---|---|---|---|---|
tintinalli | Tintinalli's Emergency Medicine Manual(急診醫學手冊) | 第 7 版,McGraw-Hill,2012 | 1,745 | 英文 | 有版權,只供示範 |
guidelines | 31 份開放授權的臨床指引與共識(清單見 §10.2) | 2013 至 2026 年各期刊,取自 PubMed Central(PMC) | 1,688 | 英文 | 每份是 Creative Commons 授權 |
ems_protocol | 《臺北市政府緊急救護技術員到院前緊急救護作業程序手冊》 | 第六版,115 年 4 月,臺北市政府消防局,140 頁 | 281 | 繁體中文 | 版權屬臺北市政府消防局,引用需書面申請 |
三個來源共用的做法:全文只放在不進版控的 private/ 與 Spark 的資料庫,repository 只放轉換與檢索程式、指引的來源清單;智慧體讀到的是段落全文,醫師與評審在介面上只看到出處字串,從不顯示書中原文;模型被要求不得引用原文(llm/prompt_sets/ems_compact/differential.py),確定性檢查也只讓真的被搜尋工具回傳過的段落 id 進輸出,並換成出處。
10.1 Tintinalli 急診醫學手冊
- 取了什麼。 把 PDF 依目錄轉成 188 章的 Markdown,切成約 2,000 段(每段依標題切,超過 450 字在段落邊界再切,記錄章、標題路徑與書頁範圍)。匯出時預設排除第 9 篇「兒科」(21 章、255 段),因為示範的病人是成人,兒科段落只會擠掉成人的結果;留下 167 章、1,745 段。需要時可用
--include-pediatric匯出。 - 出處長什麼樣。
Tintinalli 7e, Ch. 章號 章名, pp. 起–迄(印在書上的頁碼;PDF 頁碼是書頁加 19)。 - 怎麼索引、怎麼搜。 資料庫用英文全文檢索(標題權重 A、內文權重 B);查詢去掉停用字後以 OR 連接,五個字母以上的字用前綴比對,使其變形(如 neutropenic、neutropenia)會連在一起;依
ts_rank_cd排序,智慧體預設要 3 段,最多 8 段,每段給模型讀最多 1,800 字。智慧體用英文臨床詞彙查詢。 - 授權與使用。 版權頁明文禁止未經同意重製、改作或存入檢索系統,只允許個人非商業使用,所以這本書只用於示範,且從不以原文呈現;正式產品的方向是改用醫院自行購買授權的知識庫,檢索層可抽換。出版年是 2012,與較新指引不一致時,提示詞要求以指引為準,最後由醫師決定。
- 出處:
eval/README.md〈Tintinalli 檢索〉、eval/scripts/tintinalli_rag.py、server/scripts/export_kb.py。
10.2 開放授權臨床指引(31 份)
- 取了什麼。 從 PMC 下載 JATS XML 全文,只保留授權為 Creative Commons 的文章,非 CC 者拒收;截至 2026-10-12 候選全部通過,共 31 份、1,688 段。每份取摘要、正文各節、表格標題與儲存格;略過補充資料、致謝、經費、作者、聲明、利益衝突、倫理、同意、資料可得性、縮寫、參考文獻與出版者說明。
- 出處長什麼樣。
簡稱(期刊 年份,PMCID,授權),§ 章節。 - 怎麼索引、怎麼搜。 與 Tintinalli 相同(英文全文檢索,英文查詢)。
- 授權與使用。 授權分 CC BY、CC BY-NC、CC BY-NC-ND 等;NC 與 ND 的條款限制商業使用與改作後散布,全文因此同樣只放
private/,介面只顯示出處。表中授權欄以資料庫為準。 - 範圍的限制。 許多常見的指引(ESC 系列、2021 AHA 胸痛、SSC 2021、ACG 2021、ADA、NICE)不是 CC 授權,所以沒有收;索引用同主題的 CC 指引替代,所以模型引用的來源和案例原本參考的指引不完全相同。
- 出處:
eval/guidelines/sources.json(來源清單,進版控)、eval/scripts/guidelines_rag.py、eval/README.md〈開放指南索引〉。
下表是 31 份文章的完整清單(資料來源:建置完成的索引,標題為期刊原題):
| 簡稱 | PMCID | 標題 | 期刊(年) | 授權 | 段數 |
|---|---|---|---|---|---|
| ANMCO-SIMEU Chest Pain 2016 | PMC5520764 | ANMCO-SIMEU Consensus Document: in-hospital management of patients presenting with chest pain | European Heart Journal Supplements(2017) | CC BY-NC 4.0 | 43 |
| Acute Vertigo ED Consensus 2024 | PMC11364714 | Consensus paper on the management of acute isolated vertigo in the emergency department | Internal and Emergency Medicine(2024) | CC BY 4.0 | 56 |
| Asia-Pacific NVUGIB 2018 | PMC6145289 | Asia-Pacific working group consensus on non-variceal upper gastrointestinal bleeding: an update 2018 | Gut(2018) | CC BY-NC 4.0 | 30 |
| BSG Ascites 2021 | PMC7788190 | Guidelines on the management of ascites in cirrhosis | Gut(2021) | CC BY-NC 4.0 | 70 |
| BTS Outpatient PE 2018 | PMC6018844 | British Thoracic Society Guideline for the initial outpatient management of pulmonary embolism | BMJ Open Respiratory Research(2018) | CC BY-NC 4.0 | 40 |
| BTS Oxygen 2017 | PMC5531304 | British Thoracic Society Guideline for oxygen use in adults in healthcare and emergency settings | BMJ Open Respiratory Research(2017) | CC BY-NC 4.0 | 50 |
| Brazilian Chest Pain ED 2025 | PMC12981354 | Diretriz Brasileira de Atendimento à Dor Torácica na Unidade de Emergência – 2025 | Arquivos Brasileiros de Cardiologia(2025) | CC BY 4.0 | 155 |
| ED Delirium GRADE 2026 | PMC12875304 | GRADE‐Based Clinical Practice Guidelines for Emergency Department Delirium Risk Stratification, Screening, and Brain Imaging in Older Patients With Suspected Delirium | Academic Emergency Medicine(2025) | CC BY 4.0 | 45 |
| ED Hyperkalemia Consensus 2021 | PMC8485984 | Hyperkalemia management in the emergency department: An expert panel consensus | Journal of the American College of Emergency Physicians Open(2021) | CC BY-NC-ND 4.0 | 22 |
| ESE-ES Adrenal Insufficiency 2024 | PMC11180513 | European Society of Endocrinology and Endocrine Society Joint Clinical Guideline: Diagnosis and Therapy of Glucocorticoid-induced Adrenal Insufficiency | The Journal of Clinical Endocrinology and Metabolism(2024) | CC BY-NC 4.0 | 61 |
| ESGE NVUGIH Cascade 2018 | PMC6175685 | Nonvariceal upper gastrointestinal hemorrhage: European Society of Gastrointestinal Endoscopy (ESGE) Cascade Guideline | Endoscopy International Open(2018) | CC BY-NC-ND 4.0 | 16 |
| ESGE Variceal Cascade 2020 | PMC7329372 | Endoscopic treatment of variceal upper gastrointestinal bleeding: European Society of Gastrointestinal Endoscopy (ESGE) Cascade Guideline | Endoscopy International Open(2020) | CC BY-NC-ND 4.0 | 16 |
| EUSEM Syncope 2024 | PMC11198953 | The syncope core management process in the emergency department: a consensus statement of the EUSEM syncope group | European Journal of Emergency Medicine(2024) | CC BY-NC-ND 4.0 | 18 |
| European Trauma Bleeding Guideline 2023 | PMC9977110 | The European guideline on management of major bleeding and coagulopathy following trauma: sixth edition | Critical Care(2023) | CC BY 4.0 | 111 |
| GOLD 2023 | PMC10066569 | Global Initiative for Chronic Obstructive Lung Disease 2023 Report: GOLD Executive Summary | The European Respiratory Journal(2023) | CC BY-NC 4.0 | 68 |
| Hyperglycaemic Crises Consensus 2024 | PMC11343900 | Hyperglycaemic crises in adults with diabetes: a consensus report | Diabetologia(2024) | CC BY 4.0 | 44 |
| J-SSCG 2020 | PMC8384927 | The Japanese Clinical Practice Guidelines for Management of Sepsis and Septic Shock 2020 (J-SSCG 2020) | Journal of Intensive Care(2021) | CC BY 4.0 | 246 |
| JBDS HHS 2023 | PMC10107355 | Management of Hyperosmolar Hyperglycaemic State (HHS) in Adults: An updated guideline from the Joint British Diabetes Societies (JBDS) for Inpatient Care Group | Diabetic Medicine(2022) | CC BY-NC 4.0 | 32 |
| KSHF Acute HF 2023 | PMC10406529 | Korean Society of Heart Failure Guidelines for the Management of Heart Failure: Advanced and Acute Heart Failure | Korean Circulation Journal(2023) | CC BY-NC 4.0 | 34 |
| PERT Consortium PE 2019 | PMC6714903 | Diagnosis, Treatment and Follow Up of Acute Pulmonary Embolism: Consensus Practice from the PERT Consortium | Clinical and Applied Thrombosis/Hemostasis(2019) | CC BY-NC 4.0 | 26 |
| SEOM Febrile Neutropenia 2018 | PMC6339667 | SEOM clinical practice guideline: management and prevention of febrile neutropenia in adults with solid tumors (2018) | Clinical & Translational Oncology(2018) | CC BY 4.0 | 32 |
| SIRM-SIC Acute Chest Pain 2025 | PMC12669346 | SIRM/SIC consensus document on the management of patients with acute chest pain | La Radiologia Medica(2025) | CC BY 4.0 | 22 |
| Scandinavian Head Injury 2013 | PMC3621842 | Scandinavian guidelines for initial management of minimal, mild and moderate head injuries in adults: an evidence and consensus-based update | BMC Medicine(2013) | CC BY 2.0 | 34 |
| TSANZ Oxygen 2015 | PMC4654337 | Thoracic Society of Australia and New Zealand oxygen guidelines for acute oxygen use in adults: ‘Swimming between the flags’ | Respirology(2015) | CC BY-NC 3.0 | 25 |
| WSES AMI 2022 | PMC9580452 | Acute mesenteric ischemia: updated guidelines of the World Society of Emergency Surgery | World Journal of Emergency Surgery(2022) | CC BY 4.0 | 30 |
| WSES Cholecystitis 2016 | PMC4908702 | 2016 WSES guidelines on acute calculous cholecystitis | World Journal of Emergency Surgery(2016) | CC BY 4.0 | 53 |
| WSES Elderly Trauma 2023 | PMC11140935 | The 2023 WSES guidelines on the management of trauma in elderly and frail patients | World Journal of Emergency Surgery(2024) | CC BY 4.0 | 111 |
| WSES Pancreatitis 2019 | PMC6567462 | 2019 WSES guidelines for the management of severe acute pancreatitis | World Journal of Emergency Surgery(2019) | CC BY 4.0 | 73 |
| WSES Peptic Ulcer 2020 | PMC6947898 | Perforated and bleeding peptic ulcer: WSES guidelines | World Journal of Emergency Surgery(2020) | CC BY 4.0 | 50 |
| WSES-AAST Thoracic Trauma 2025 | PMC12522690 | Thoracic trauma WSES-AAST guidelines | World Journal of Emergency Surgery(2025) | CC BY-NC-ND 4.0 | 46 |
| WSES-SICG Elderly Cholecystitis 2017 | PMC6399945 | 2017 WSES and SICG guidelines on acute calcolous cholecystitis in elderly population | World Journal of Emergency Surgery(2019) | CC BY 4.0 | 29 |
10.3 臺北市到院前緊急救護作業程序手冊
- 取了什麼。 第六版手冊(115 年 4 月,140 頁)的 PDF,依章節轉成 Markdown,流程圖是 AI 看圖轉寫的,沒有臨床人員覆核(
docs/team-discussion-todo.mdA16 要求抽查 G7、M7、M6、M1、A3、S7 等流程圖)。切成 281 段,平均約 288 字:每段依##/###標題切,一段不跨標題;編號步驟的流程圖與表格盡量整塊(流程圖段上限 1,400 字,其他 800 字)。 - 出處長什麼樣。
臺北市到院前緊急救護作業程序手冊(第六版),標題路徑,PDF 第 n 頁。 - 怎麼索引、怎麼搜。 中文沒有空白可以斷詞,資料庫把每段連續中文切成相鄰兩字(bigram)存入
tsv_zh,查詢用同一個切法,以 OR 連接,所以兩字以上的詞都能比對(migrations/003_kb_search.sql、kb.py)。查詢含中文字時自動改用這一欄。智慧體用繁體中文查這個來源。 - 檢索品質。 離線評測(用另一種 SQLite 索引,不是正式環境的 PostgreSQL)45 題自擬查詢,章節層級 recall@5 是 0.978、MRR 是 0.924;有 8 題的標準答案是看過結果後校正的,不能當作未調整的成績。已知的缺口是同義詞(例如內文寫「呼吸困難」而查詢是「氣喘」)。
- 授權與使用。 版權頁原文是「版權屬臺北市政府消防局所有/欲參採或引用請事先書面申請」,沒有內部用途的例外,也沒有公開授權;repository 裡沒有取得書面同意的紀錄(
docs/team-discussion-todo.mdA16)。介面只顯示章節與頁碼。另一件事要分開講:生命徵象色塊用到手冊中的數字界線(例如呼吸 ≥ 30 次/分、血糖 < 60 或 ≥ 500 mg/dL),團隊的決定是使用數字界線不算引用手冊文字(docs/references/vital-tint-thresholds.md)。 - 出處:
eval/ems_protocol/README.md、docs/references/ems-protocol-taipei/README.md、eval/scripts/ems_protocol_rag.py。
10.4 知識庫怎麼建、怎麼上線
- 在開發機用 eval 的三支腳本各自建索引(
tintinalli_rag.py build、guidelines_rag.py build、ems_protocol_rag.py build)。 server/scripts/export_kb.py把三份索引合成private/kb/kb_passages.jsonl,每行一段{id, source, lang, title, citation, body},三個來源的段落 id 不重複。- 部署時加
-e triaige_kb_import=true:playbook 把檔案複製到 Spark 的/srv/triaige/kb/(只有後端使用者讀得到),在容器內執行python -m triaige_server.kb_import。匯入在單一交易內,檔案裡出現的來源先整批刪除再寫入,所以重複執行不會留下舊段落;英文的tsv由資料庫產生,中文的tsv_zh在匯入時計算。 - 沒加這個參數時,部署不碰知識庫。知識庫是空的時候,智慧體照樣回答,但沒有任何引用。
11. 想深入了解時,去哪裡找?
| 想知道 | 看這裡 |
|---|---|
| 程式分層、每個關注點在哪個檔案、換模型的步驟 | 〈架構說明〉(docs/architecture/README.md) |
| 給評審的正式說明與全部評測結果 | 〈評審報告〉(docs/judges/README.zh-TW.md) |
| 每一項評測的採用標準與原始數字 | eval/README.md |
| 鑑別診斷的智慧體與搜尋工具 | server/src/triaige_server/ai/nat_differential.py、llm/prompt_sets/ems_compact/differential.py |
| 知識庫的檢索與匯入 | server/src/triaige_server/kb.py、kb_import.py、infra/README.md〈知識庫〉 |
| 各項背景 AI 功能的規則 | engine/ai_summary.py、engine/hospital_level.py、ai/followups.py、ai/nurse_notes.py |
| 欄位複查與信心的設計 | docs/specs/2026-10-14-inline-review/plan.md |
| API 介面規格與資料規則 | docs/api/README.md、openapi.yaml、data-model.md |
| 部署與維運儀表板 | infra/README.md |