triAIge:評審報告

triAIge 是一套在邊緣運算上執行的 AI 交接系統,處理台灣急救最前面的幾分鐘。救護車上的救護員(EMT)把看到的情況說出來或打字輸入,系統就填寫消防局的救護紀錄表,並傳到急診(ED)。檢傷護理師打開的是預先填好、附上建議檢傷級數的評估表,醫師拿到的是一份簡短的交接摘要。

這份報告的讀者大概只會讀一次,所以每一項說法都附上檔案出處,路徑都相對於 repository 根目錄;repository 裡沒有的數字,報告也不寫。

這是黑客松原型,不是經過臨床驗證的產品。哪些有量測、哪些沒有,第 7 節逐項說明。

1. 目前完成了什麼?

狀態位置
救護端 app、急診檢傷 app、醫師畫面(web)已完成;/ 連真後端,/internal 連瀏覽器內的模擬後端(mock)web/README.md、docs/specs/2026-10-13-frontend-restructure/decision.md
後端:FastAPI、PostgreSQL、36 個 API 操作、即時推播(SSE)已完成docs/api/README.md、docs/specs/2026-10-13-backend/design.md
在一台 DGX Spark 上用 Nemotron 3 Super(NVFP4)擷取欄位已完成,已評測eval/README.md
以規則產生的 TTAS 建議、完整性檢查、交接摘要與醫師摘要已完成。摘要由規則從紀錄組出,不呼叫模型server/src/triaige_server/engine/views.py、engine/ttas.py
在欄位上複查 AI 的值(沒有阻斷式對話框)、每個欄位的來源與變更歷程、確認/選取/復原操作已完成(還在規劃的部分見 §3 第 3 步)docs/specs/2026-10-14-inline-review/plan.md
由 token 對數機率算出的欄位信心等級分數已經計算並儲存,但等級還沒有顯示,因為門檻尚未校準docs/specs/2026-10-14-inline-review/plan.md §4.4、docs/architecture/README.md §5
語音辨識不在這個 repository 裡。 API 接受語音訊息(inputMode: voice),規劃是在瀏覽器內辨識,但 web app 還沒有辨識器,展示版用文字訊息操作docs/api/README.md §6
鑑別診斷、給護理師的 AI 提問、醫學參考資料的檢索(RAG)不在執行中的系統裡(階段 2)。檢索只出現在 eval/docs/specs/2026-10-13-backend/spec.md §1.3
真實的醫院系統(HIS/EMR、健保卡)用虛構病人代替docs/api/README.md §1.3

web app 的線上展示版(資料存在瀏覽器,沒有真實病人):https://triaige-demo.vercel.app(README.md)。

2. 要解決什麼問題?

在台灣,消防局的救護員一邊處置病人,一邊在紙本格式的救護紀錄表上記錄;到了急診,同樣的問題又被問一次。急診檢傷護理師要從台灣急診檢傷與急迫度分級量表(Taiwan Triage and Acuity Scale,TTAS)的五個級數中選一個,醫師則要很快掌握關鍵事實。docs/product/triaige.md §2 列出這個流程出問題的地方:

參考表單是救護紀錄表與 TTAS 判定標準,兩者都是公開的政府文件(docs/references/、README.md)。

產品的定位很明確:AI 只負責擷取與結構化,凡是有公開規則可循的臨床決定,都由確定性規則處理(docs/product/triaige.md §3.3)。AI 不設定檢傷級數;級數由護理師送出,改動建議級數時要附上理由(docs/api/data-model.md §10)。

3. 系統從頭到尾做了哪些事?

flowchart LR
  EMT["救護員平板:救護端 app"] -->|"一句話 = 一則訊息"| API["DGX Spark 上的 FastAPI 伺服器"]
  API --> SCH["排程器:同時最多 2 個模型呼叫"]
  SCH --> NAT["NeMo Agent Toolkit 工作流程"]
  NAT --> LLM["透過 vLLM 執行的 Nemotron 3 Super"]
  LLM --> CHK["確定性檢查"]
  CHK --> REC["PostgreSQL 裡的救護紀錄:每個欄位的來源、證據、歷程"]
  REC -->|"推播事件"| EMT
  REC -->|"送出"| NURSE["急診檢傷護理師:預先填好的評估表"]
  NURSE --> TTAS["TTAS 規則引擎:建議級數與理由"]
  TTAS -->|"護理師決定並送出"| DOC["醫師交接摘要,由規則組成"]
圖表載入中…
  1. 接收輸入。 救護員一次送出一句話,並標上說話者(救護員、病人、旁人)。伺服器先存下這句話、立刻回應,再在背景執行 AI,所以畫面不必等模型(docs/specs/2026-10-13-backend/design.md〈Extraction flow〉)。
  2. 擷取欄位。 每一句只呼叫一次模型,回傳 12 個紀錄欄位的 JSON:年齡、性別、主訴、體溫、心跳、呼吸速率、SpO₂、收縮壓與舒張壓、疼痛、過去病史、重點用藥類別,以及藥物過敏(docs/specs/2026-10-13-backend/spec.md §1.3、docs/api/field-mapping.md §6)。每一句的提示詞會帶上最多三句前文,以及紀錄上已有的內容,所以「不對,是一百三」能更正到正確的那個值(eval/README.md〈R1 上下文(X1)〉)。
  3. 在欄位上複查,不用阻斷式對話框。 救護員說的內容直接寫進紀錄表:寫進空欄位,或取代還沒複查的值。這個欄位會標成「待複查」,附上來源句與說話者,救護員可以確認、修改、改選另一個候選值、清除或復原。人填的值或已確認的值不會被覆蓋,AI 給的不同值以替代選項列在旁邊。病人或旁人說的話不會直接寫入,而是成為建議,等救護員採用。缺漏的必填欄位與 TTAS 參考級數固定顯示在一個區域,不用彈出視窗。規劃中的後續切片(每種說話者的寫入規則、重新評分先前的值)還沒有完成(docs/api/data-model.md §2、docs/specs/2026-10-14-inline-review/plan.md §0、§15 S3b)。
  4. 檢傷。 紀錄送出後,護理師看到院前紀錄已預先填入檢傷表;救護端沒人確認過的欄位會標成「未複查」,並附上來源。TTAS 引擎算出建議級數,每一項理由都附上觸發的規則,需要人判斷的條件(例如「有沒有休克?」)會列出來,引擎不替人決定(docs/api/data-model.md §10、server/src/triaige_server/engine/ttas.py)。
  5. 醫師摘要。 目前狀況、院前經過與危險徵象,由規則從紀錄與檢傷資料組出,每一句都附上來源欄位的參照(server/src/triaige_server/engine/views.py)。

救護端與急診端的畫面共用同一組元件。根路徑連真伺服器,/internal 連瀏覽器內的模擬後端,兩邊的案件規則相同,並由漂移測試確保兩個引擎一致(docs/architecture/README.md §2、server/tests/unit/test_engine_parity.py)。

4. 用了哪些 <帳號> 技術?

元件用途狀態位置
Nemotron 3 Super 120B-A12B,NVFP4流程中唯一的模型:擷取欄位,以及在背景把主訴翻譯成繁體中文執行中infra/inventory/group_vars/servers.yml、docs/architecture/README.md §6
一台 DGX Spark(GB10)上的 vLLM v0.31.0以 OpenAI 相容 API 提供模型服務;多 token 預測(multi-token prediction)的推測解碼(3 個 token)、前綴快取執行中infra/README.md、infra/inventory/group_vars/servers.yml
NeMo Agent Toolkit 1.9.0每一次模型呼叫都是在 FastAPI 行程內執行的 NAT 函式,不另外起服務執行中docs/specs/2026-10-13-backend/design.md〈Implementation notes〉、server/src/triaige_server/ai/workflow.yml
NeMo Guardrails(透過 NAT 的 guardrails 中介層)中介層已經包在工作流程外面。self check output 護欄已經寫好,但預設關閉,因為每次擷取要多一次模型呼叫(設計文件估計約 1–3 秒)。一直開著的安全檢查是 §6 的確定性檢查已接上,護欄關閉docs/specs/2026-10-13-backend/design.md、infra/inventory/group_vars/servers.yml(triaige_self_check_output: false)
Nemotron 3.5 Lightning 30B-A3B評估過能否當作較快的擷取模型,沒有採用(見 §7)已評測eval/README.md〈服務參數實測〉
NeMo Evaluator、Nemotron embedding 模型加 pgvector、OpenShell 沙箱規劃在階段 2。資料庫已經有 kb_passages 表,預留了存放向量的欄位規劃中docs/specs/2026-10-13-backend/decision.md
NemoClaw沒有整合。調查後發現它還在 alpha 階段,也沒有文件說明如何接上我們自己的智慧體只在規劃docs/specs/2026-10-13-backend/research-<帳號>-stack.md

模型、伺服器與資料庫都跑在同一台 Spark 上,推論不使用任何雲端模型(docs/product/triaige.md §3.2)。

5. 模型換版時,程式怎麼不受影響?

模型更新得很快,為某一個模型做的調整,不該分散在整個程式裡。現行模型的所有設定都集中在套件 server/src/triaige_server/llm/ 的 ModelProfile 物件裡,包括提示詞、取樣參數、怎麼關閉推理、怎麼把模型的回覆整理成一個 JSON 物件、怎麼讀取對數機率、逾時,以及同時請求數(docs/architecture/README.md §6)。

flowchart TB
  API["api:路由、推播事件"] --> AI["ai:排程、NAT 黏合、寫入閘門"]
  API --> ENG["engine:案件規則、TTAS"]
  AI --> ENG
  AI --> TR["task_rules:合格答案長什麼樣"]
  AI --> LLM["llm:ModelProfile、提示詞、回覆清理、對數機率評分"]
  ENG --> TR
  LLM --> TR
  INFRA["infra:模型部署手冊與變數"] -.-> LLM
圖表載入中…

6. 安全上做了哪些設計?

機制做法位置
由人決定AI 的值是標成「待複查」的草稿。人填的值或已確認的值不會被覆蓋。病人與旁人的陳述要等救護員採用。最終檢傷級數只能由護理師送出。不採用建議級數時要附上理由docs/api/data-model.md §2、§10
每個模型回覆都經過確定性檢查輸出必須是 JSON 物件。未知欄位、超出範圍的生命徵象與不合法的選項值都會被丟掉。系統不修補回覆的內容,也不猜測server/src/triaige_server/task_rules/record_checks.py、docs/architecture/README.md §7
「無過敏」要有依據來源句裡沒有提到過敏的字眼時,「無過敏」與「未知」都會被丟掉,因為錯誤的「無」會關掉缺漏過敏的提醒。只提到非藥物過敏的句子,不會勾選「無過敏」server/src/triaige_server/task_rules/record_checks.py(模組 docstring)、eval/README.md〈R1 三項待決〉
問句不是陳述「他有沒有藥物過敏?」不能變成資料。我們在提示詞裡用範例修正,再用測試固定下來。另外有可選的路由分類器與寫入閘門,把可疑的句子改成待確認,不直接寫入;分類器預設關閉eval/README.md〈後續:問句不是陳述〉、eval/routing/README.md、docs/api/README.md §6
失敗就明確失敗,不默默降級max_retries 是 0,也沒有備援模型。呼叫失敗會以 assistant.failed 事件結束,手動編輯照常可用;連續失敗後,畫面顯示「AI 暫停」。設定了不認得的 profile id,或向不能回傳對數機率的 profile 要求對數機率,伺服器在啟動時就會停止docs/architecture/README.md §6、§10、server/src/triaige_server/ai/service.py
翻譯檢查紀錄保留英文主訴,繁體中文翻譯只是並列顯示,標成「AI 翻譯」。譯文能不能顯示,由一組確定性檢查決定:左右側、身體部位、沒有多出數字、簡體字。依使用者的決定,主訴只要含數字或明確的次數,就只顯示英文server/src/triaige_server/task_rules/complaint_guard.py、docs/api/README.md §6、eval/complaint_zh/README.md
用對數機率計算信心對選項欄位(性別、過敏狀態、病史項目),伺服器沿著模型實際寫出的 token,把機率相乘,再乘上「這個欄位該不該出現」的判斷點。如果 token 拼不回模型的回覆,就不給分數,並記錄這次失敗,不隱藏docs/specs/2026-10-14-inline-review/plan.md §4.3、§4.7、server/src/triaige_server/llm/choice_scoring.py
稽核紀錄每一次寫入、複查操作、AI 失敗與檢傷送出,都是一筆稽核事件。每個欄位保留來源(人工、AI、已確認)、證據句與歷程。急診看得到誰在什麼時候改了哪個救護欄位,但看不到草稿內容server/src/triaige_server/engine/views.py(audit_for_ed)、docs/api/data-model.md
部署防護每個請求都要帶存取碼(至少 20 個字元);失敗次數限制(60 秒內失敗 10 次後回 429);有期限的簽章 token(預設 12 小時);請求大小上限(256 KiB);日誌不記錄 query string;模型端點只接受本機與 tailnet 的連線docs/specs/2026-10-13-backend/design.md〈公開上線的加固〉、infra/README.md
資料最小化API 只接收文字,音訊依設計留在平板上。展示用的病人是虛構的,真實病人資料只能放在 git 忽略的 private/ 資料夾docs/api/README.md §6、README.md

還不能算是安全檢查的部分:Guardrails 自我檢查護欄(關閉),以及信心等級的顯示(未校準)。

7. 評測結果與限制

用的是什麼資料?

請先讀這一段。eval/README.md〈誠實規則〉要求我們這樣描述資料:

量到了什麼?

問題結果來源
整段口述擷取,現行設定(vLLM v0.31.0、精簡輸出、關閉推理)12 個欄位的 Macro-F1 為 0.954(132 段口述,seed 1)與 0.957(133 段,seed 2)。幻覺欄位是 1127 個中 4 個、1147 個中 4 個。呼叫時間中位數 2.89 秒,平均輸出 71 個 tokeneval/README.md〈輸出格式與 vLLM 版本〉;eval/results/serving-2026-10-12/predictions-super-v031-compact-draft.report.json、…-seed2.report.json、…metrics.json
各欄位在 seed 1 的報告裡,11 個結構化欄位落在 0.984 到 1.000。chief_complaint 是 0.493,因為字面比對只是下界,有 78 筆等待人工判讀,它也拉低了 macro 平均同一份 …-draft.report.json、eval/README.md
透過真後端逐句處理,47 段保留集口述(163 句)11 個結構化欄位 F1 1.000,幻覺欄位 401 個中 0 個,AI 失敗或逾時 0 次,每句中位數 1.42 秒、p95 2.08 秒eval/results/r1-context-fix-2026-10-14/yardstick-replay.txt、replay-holdout-cand.metrics.json、eval/README.md
推理開或關關閉:中位數 4.2 秒,F1 0.956。完整推理:中位數 24.9 秒,F1 0.954。在這個任務上,推理沒有提高準確度,所以產品關閉推理eval/README.md〈推理強度〉
精簡 JSON 輸出和完整格式相比,延遲低約 31%、輸出 token 少 40%,F1 與幻覺不變eval/README.md〈輸出格式與 vLLM 版本〉
為了更正帶上前文(70 句的合成資料集,沒有臨床人員審閱)保留集 34 句:更正召回率 0.909 → 1.000;沒有指名欄位的更正,3/9 → 8/9;非預期的欄位 7 → 1。這裡評的是模型對最後一句的輸出,不是合併後的紀錄。用事先宣告的 yardstick 來看,47 段保留集口述沒有變差eval/results/r1-context-fix-2026-10-14/corrections-holdout-verdict.txt、eval/README.md
藥物過敏與否定(71 句,標準答案由工程師草擬,等待醫師審閱)只靠提示詞擋不住錯誤的「無」:3 次整段口述中有 2 次,對一個從沒提到過敏的案例寫了「無」。這說明依據檢查必須用確定性的程式,不能只寫在提示詞裡。在 24 句保留集上:狀態準確率 1.000,錯誤的「無」為零,藥名召回率 1.000。這個資料集太小,不能算是驗證eval/README.md〈R1 採用標準與重新判定〉、〈後續:問句不是陳述〉
問句被寫進紀錄仍產生欄位的非紀錄句:15 句中 6 句 → 15 句中 0 句(保留集)。加上可選的寫入閘門做端到端測試,49 句中 17 句 → 49 句中 1 句eval/README.md〈後續:問句不是陳述〉、eval/routing/README.md
路由分類器的召回率(判斷句子屬於紀錄內容)0.981(51/52),目標是 0.99,沒有達標。漏掉的那一句被寫入閘門攔下,變成一張待確認卡eval/routing/README.md
主訴翻譯檢查(工程師草擬的檢查,等待臨床人員審閱)在 69 個從未用於調整的新主訴上,63 個通過(91.3%);左右側 18/18 保留,沒有漏掉數字或項目;luq/rlq 被誤擋 2 次。之後又收緊了好幾輪,已提交的結果中 258 組不同的英文/中文配對,有 206 組通過現行檢查,其餘只顯示英文eval/complaint_zh/README.md
要求回傳對數機率的成本保留集 163 句,單一請求:中位數 ×1.007,A/A 對照是 ×1.004,量不到成本eval/results/choice-confidence-latency-2026-10-14/README.md
服務效能單一請求的解碼速度約 31.1 tok/s(v0.31.0),v0.28.0 是 29.8。Super 在 gpu-memory-utilization 0.77 下佔 95.4 GiB,留給主機、伺服器與資料庫約 16 GiBeval/README.md〈輸出格式與 vLLM 版本〉
更快的小模型Nemotron 3.5 Lightning 30B-A3B 快約 4.3 倍(中位數 1.01 秒對 4.39 秒),F1 是 0.938 對 0.955;在只說出舒張壓的 9 個案例裡,它把舒張壓留空,所以沒有採用。它也不能和 Super 同時放在一台 Spark 上eval/README.md〈服務參數實測〉
臺北市到院前救護手冊的檢索(只在評測中)281 個段落、45 個自己寫的查詢:章節層級 recall@5 0.978、MRR 0.924。其中 8 個查詢的標準答案是看過結果後才更正的,所以這不是事先未經調整的分數eval/ems_protocol/README.md

哪些還沒有量?

8. 怎麼部署?

架構是:平板或瀏覽器連到 Vercel 上的 web app,再透過 Tailscale Funnel 連到 API。在 Spark 上,triaige-server(port 8100,記憶體上限 1536 MB)連到 PostgreSQL(port 5433,上限 512 MB),以及執行 Nemotron 3 Super 的 vLLM(port 8000)(infra/inventory/group_vars/servers.yml、docs/architecture/README.md §10)。

9. 已知限制與下一步

限制:

  1. repository 裡沒有語音辨識器(§1),評測的是文字路徑。
  2. 評測資料是美國急診的案例卡,加上模型寫的口述稿,沒有臨床人員審閱(§7)。
  3. TTAS 規則集是草稿,涵蓋第一層的生命徵象調節變數與疼痛;依主訴而定的判定標準還沒有資料來源(server/src/triaige_server/engine/ttas.py)。每個級數仍由護理師決定。
  4. 醫師摘要是規則組出的文字,沒有鑑別診斷,護理師端的 AI 建議也還沒做(docs/specs/2026-10-13-backend/spec.md §1.3)。
  5. 門檻在保留集上校準之前,信心等級不會顯示給使用者;規劃中讓每種說話者的值都能寫進表單、之後再重新評分的寫入規則(切片 S3b)也還沒做(docs/specs/2026-10-14-inline-review/plan.md §4.4–4.5、§15)。
  6. Guardrails 自我檢查與路由分類器目前關閉,因為兩者都要在以秒計的延遲預算裡,多一次模型呼叫。
  7. 只有一台 Spark,沒有備援。同時送給模型的請求上限是 2,因為第三個同時請求曾觸發一次至今原因不明的 CUDA 錯誤;之後 3 個同時請求的壓力測試都通過,但不能因此排除這個問題(eval/README.md〈服務參數實測〉、docs/architecture/README.md §12)。
  8. 紀錄裡的主訴保留英文。中文顯示採保守做法,像「up and down」這類片語,是已記錄的已知缺口(eval/complaint_zh/README.md)。
  9. 兩份參考資料有著作權:Tintinalli's Emergency Medicine Manual(第 7 版)與臺北市到院前救護手冊。它們不進版本控制,也不放在伺服器上,授權問題還沒有解決(eval/README.md、eval/ems_protocol/README.md)。
  10. 提示詞與對數機率 fixture 是 Nemotron 3 Super 專用的(§5)。

下一步,依優先順序:

  1. 請臨床人員審閱標準答案、TTAS 規則集與翻譯檢查(docs/team-discussion-todo.md)。
  2. 在保留集上校準對數機率門檻,再顯示「高/需留意」等級(docs/specs/2026-10-14-inline-review/plan.md §15,C2 與 C3)。
  3. 依 docs/api/README.md §6 的規格,在平板的瀏覽器內做語音辨識。
  4. 階段 2:用醫院自行取得授權的知識庫做檢索、附引用來源的鑑別診斷、護理師端的建議、NeMo Evaluator、沙箱(docs/specs/2026-10-13-backend/decision.md)。
  5. 依 docs/architecture/README.md §9 的步驟,試第二個模型 profile。

10. 想查細節時,去哪裡找?

問題檔案
產品構想與 MVP 範圍docs/product/triaige.md
系統分層、換模型的步驟docs/architecture/README.md(繁體中文)
後端決策與設計docs/specs/2026-10-13-backend/decision.md、design.md
欄位複查與信心設計docs/specs/2026-10-14-inline-review/plan.md
API 介面規格與資料規則docs/api/README.md、openapi.yaml、data-model.md
每一項評測,附事先宣告的通過標準eval/README.md
部署infra/README.md