triAIge 架構說明(給團隊成員)

救護車上的救護員說「男性,胸痛半小時,血壓一百六十、九十,心跳一百一」,幾秒後,救護紀錄表上的性別、主訴與生命徵象就填好了,每一格都標明是 AI 填入、等人複查;病人送到急診時,檢傷護理師打開同一個案件,看到的是預先填好、附上建議檢傷級數的評估表。triAIge 做的就是這件事。前端是救護端、急診端與醫師端的網頁;後端是一個 FastAPI 服務,在一台 DGX Spark 上用 Nemotron 3 Super 擷取欄位,臨床判斷交給確定性規則與護理師。這份文件說明系統現在的組成:程式分成哪幾層、一句話經過哪些層、每一類規則放在哪裡,以及新增提示詞、新增檢查或換模型時要改哪裡。

1. 這份文件給誰、怎麼讀

讀者有兩種:開發者會用到全部章節;臨床組員只需要讀第 2 章(系統全貌)、第 4 章(一句話的處理過程)與第 12 章(已知限制),第 5 到第 10 章講程式內部結構與操作,可以跳過。

這份文件遵守 eval/README.md 的誠實規則:評測用的逐字稿是模型寫的草稿,案例卡來自 MIMIC,還在等醫師審閱,所以這裡不說「已通過臨床驗證」。

2. 系統全貌

flowchart LR
  subgraph Browser["前端(iPad/瀏覽器)"]
    WEB["/ 正式介面(真後端)"]
    MOCK["/internal 模擬版(瀏覽器內 mock)"]
  end
  FUNNEL["Tailscale Funnel"]
  subgraph Spark["DGX Spark"]
    subgraph SRV["triaige-server(FastAPI,:8100)"]
      API["api/"]
      AI["ai/"]
      ENG["engine/"]
      TR["task_rules/"]
      LLM["llm/"]
    end
    NAT["NeMo Agent Toolkit<br/>WorkflowBuilder + guardrails middleware"]
    VLLM[("vLLM:Nemotron 3 Super<br/>:8000")]
    PG[("PostgreSQL :5433<br/>cases JSONB、kb_passages")]
  end
  EVAL["eval/(離線評測)"]
  WEB --> FUNNEL --> API
  API --> AI
  API --> ENG
  AI --> NAT --> VLLM
  SRV --> PG
  EVAL -.-> VLLM
圖表載入中…

決策背景見 docs/specs/2026-10-13-backend/decision.md 與 design.md,部署參數見 infra/inventory/group_vars/servers.yml。

3. 分層與依賴方向

哪些程式碼該知道「現在用的是哪個模型」?我們的答案是只有 llm/。其餘各層分成「任務本身的規則」與「執行任務的程式」,兩者都不能出現模型名稱、取樣參數,或處理模型輸出習慣的程式碼。

flowchart TB
  subgraph Interface["介面層"]
    API["api/(FastAPI 路由、SSE)"]
  end
  subgraph Application["應用層 ai/"]
    SVC["service.py 編排<br/>scheduler、sequencer、route、gate、recorded、mapping"]
    RT["NAT 執行期黏合<br/>extractor.py、nat_*.py、workflow.yml<br/>(workflow.yml 只有 functions 與 guardrails)"]
  end
  subgraph Task["任務層(與模型無關)"]
    ENGN["engine/(案件規則,與 web mock 對齊)"]
    TRN["task_rules/(只用標準函式庫)<br/>詞彙、record/statement/route 檢查、主訴翻譯檢查"]
  end
  subgraph Model["模型適配層 llm/(只用標準函式庫)"]
    PROF["profiles/nemotron_3_super/<br/>ModelProfile"]
    PS["prompt_sets/ems_compact/<br/>record、statement、classify、translate、self_check"]
    ADP["output.py、wire.py、choice_scoring.py"]
  end
  INFRA["infra/ 部署手冊與 group_vars"]
  VLLM[("vLLM")]
  API --> SVC
  API --> ENGN
  SVC --> ENGN
  SVC --> TRN
  SVC --> RT
  SVC --> PROF
  RT --> PROF
  RT -- "OpenAI 相容 HTTP" --> VLLM
  ENGN --> TRN
  PROF --> PS
  PROF --> ADP
  PS --> TRN
  ADP --> TRN
  INFRA -. "served name、port、profile id" .-> RT
圖表載入中…
層套件負責什麼可以 import
介面api/HTTP 路由、SSE、驗證ai、engine
應用ai/排程、編排、NAT 黏合、寫入閘門engine、task_rules、llm
任務engine/案件規則、寫入來源規則、與網頁模擬後端(mock)對齊的邏輯task_rules
任務task_rules/「這個 AI 任務的合格答案長什麼樣」只有標準函式庫
模型llm/提示詞、取樣參數、輸出修補、對數機率(logprob)讀取、信心評分task_rules 與標準函式庫

這些依賴方向由 server/tests/unit/test_architecture.py 強制,沒有例外清單:

每一條掃描都另外用刻意放入的違規範例測試過,確認它確實抓得到。

4. 一句話的處理過程

救護人員說了一句話,從送出到畫面更新,中間經過哪些步驟?

sequenceDiagram
  participant U as 前端
  participant A as api/(介面)
  participant S as ai/ 排程與編排(應用)
  participant N as NAT(應用)
  participant V as vLLM
  participant L as llm/(模型)
  participant T as task_rules/(任務)
  participant E as engine/(任務)
  U->>A: POST /cases/{id}/messages
  A->>E: 建立訊息(寫入來源規則)
  A-->>U: 201(先回,AI 在背景跑)
  A-->>U: SSE message.created、assistant.started
  A->>S: schedule(case, message)
  S->>S: scheduler 依 P0/P1/P2 排隊,空位數是 `settings.ai_concurrency`,預設取自 profile 的 max_streams
  S->>S: sequencer 讓同一案件的 R1 句子依序處理
  S->>N: 呼叫擷取
  N->>V: 提示詞與取樣參數來自 profile
  V-->>N: 回覆(含 logprobs)
  N-->>S: 回覆文字
  S->>L: profile.reply_text 修補輸出
  S->>T: task_rules 檢查(範圍、詞彙、過敏接地)
  S->>L: choice_scoring 評分選項欄位
  S->>E: 寫入 record(人工或已確認的值優先)
  A-->>U: SSE record.updated、assistant.completed
  S->>S: P2 背景:主訴翻譯
圖表載入中…

圖中每一步都標了所屬的層。要注意修補與檢查的順序:先用模型層的 reply_text 把回覆整理成一個 JSON 物件,再交給任務層做嚴格檢查;評分與稽核紀錄則用沒有修補過的原始回覆(raw)。整個後端只有 llm/output.py 的 json_span 會取出 {…},其他地方再寫一份,會被架構測試擋下。

5. 各關注點在哪裡

關注點位置層
系統提示詞llm/prompt_sets/ems_compact/(record.py、statement.py、classify.py、translate.py、consolidate.py、hospital_level.py、self_check.py),由 PromptSet 彙整模型
取樣參數(max_tokens、temperature)llm/profiles/nemotron_3_super/__init__.py 的 sampling;llm/nat_config.py 的 llm_entries 組成 NAT 的四個 LLM,ai/extractor.py 的 build_config_dict 呼叫它模型
關閉推理的開關同一個設定檔(profile)裡 Sampling.extra_body模型
取出 JSONllm/output.py(json_span、strip_think_then_json_span),由 ModelProfile.reply_text 選用;嚴格解析在 task_rules/record_checks.py 的 parse_json_object模型/任務
對數機率讀取llm/wire.py 的 reply_logprobs模型
選項信心評分llm/choice_scoring.py模型
信心門檻(τ、cuts)尚未建立:Confidence.thresholds 已預留檔案路徑,目前是 None;校準(C2)後放在 llm/profiles/<id>/confidence_thresholds.json,見 docs/specs/2026-10-14-inline-review/plan.md §4.4 的備註模型
生命徵象合理範圍、過敏「無」要有原文依據task_rules/record_checks.py任務
病患與旁人陳述的檢查、路由標籤與問題模式的檢查task_rules/statement_checks.py、task_rules/route_checks.py;欄位詞彙在 task_rules/vocabulary.py任務
主訴翻譯檢查task_rules/complaint_guard.py、task_rules/complaint_words.json任務
建議追問(救護員、護理師還該問什麼)的提示詞與檢查llm/prompt_sets/ems_compact/followup.py;回覆形狀與問句規則(繁體中文問句、不含治療與診斷字眼)在 task_rules/followup_checks.py模型/任務
建議追問的案件規則(欄位已填不問、去重、忽略過不再回來、每頻道最多 3 題開著、舊題結案)engine/followups.py;時機(對話停下來、送出)與 P2 排程在 ai/followups.py任務/應用
護理師輸入擷取(檢傷畫面打的一段話 → 檢傷欄位提案)的提示詞與檢查llm/prompt_sets/ems_compact/nurse_note.py;白名單欄位、值的範圍、數值要在原文、無過敏要有依據在 task_rules/nurse_note_checks.py模型/任務
護理師輸入擷取的案件規則(提案掛在護理師那則訊息、不蓋掉護理師已填的值、套用時再檢查一次)engine/nurse_notes.py、engine/triage.py 的 apply_proposals;呼叫在 ai/nurse_notes.py(P1)任務/應用
寫入規則(人工值優先、supersedes、拒絕記憶)engine/record.py、field_rules.py、review.py任務
急救責任醫院分級建議:輸入欄位、臨床下限(到院前檢傷 1–2 級、危急、疑似 STEMI 不建議一般)、寫入規則engine/hospital_level.py(mock 在 web/src/mock/engine/hospital-level.ts,mock_parity.json 的 hospitalLevel 比對);回覆形狀與引用已填欄位的檢查在 task_rules/hospital_level_checks.py任務
TTASengine/ttas.py、engine/ems_ttas.py任務
稽核身分(AI 名稱與版本)engine/state.py 的預設值是中性的 unset;CaseStore 必須傳入 ai_actor(store.py),app.py 依 settings.llm_model 與提示詞版本組出應用
逾時與併發config.py:預設值取自 profile.serving,環境變數仍可覆蓋模型(值)/應用(機制)
Guardrailsai/workflow.yml 只放 functions、middleware、workflow;政策由 ai/extractor.py 依旗標補上應用
部署手冊infra/playbooks/deploy-nemotron-super.yml。triaige_model_profile、vllm_served_names、llm_port 在 infra/inventory/group_vars/servers.yml,後端與 vLLM 的部署腳本(playbook)共用;vllm_stack 選 vLLM 服務哪一個模型基礎設施
醫師端鑑別診斷(agent)提示詞、搜尋工具定義與工具結果格式:llm/prompt_sets/ems_compact/differential.py;取樣(開推理)與逾時:設定檔的 differential、differential_timeout_s;agent 迴圈(NAT 串流函式 triaige_differential):ai/nat_differential.py;知識庫檢索(可換實作):kb.py;案件事實與狀態:engine/differential_facts.py、engine/differentials.py;輸出檢查:task_rules/differential_checks.py;觸發與排程(IDLE,送出時與檢傷送出後各一次,連續要求合併):ai/service.py 的 differentials_requested模型/應用/任務
評測eval/scripts/model_profile.py 提供 --profile,各正式任務的腳本都經由它讀取模型與取樣參數評測

6. 模型適配層(llm/)

ModelProfile 是這條邊界的核心:後端要服務一個模型時需要知道的事,都收在這個物件裡。它是不可變的(frozen),建立時就檢查四個取樣角色是否齊全、上限是否為正數。

classDiagram
  class ModelProfile {
    id
    display_name
    reply_text(raw) str
    evidence
  }
  class Serving {
    api
    served_name
    default_base_url
    timeout_s
    max_streams
    recipe
  }
  class Sampling {
    max_tokens
    temperature
    extra_body
  }
  class Confidence {
    top_logprobs
    thresholds
    fixtures
  }
  class PromptSet {
    id
    record
    statement
    classify
    translate
    self_check
  }
  ModelProfile --> Serving
  ModelProfile --> "4" Sampling : extraction、route、translate、rail
  ModelProfile --> Confidence
  ModelProfile --> PromptSet
圖表載入中…

應用層有三條固定規則,不由設定檔決定:max_retries 一律是 0(失敗就送出 assistant.failed);沒有備援模型;請求逾時與 asyncio.timeout 用同一個 settings.llm_timeout_s。這三條都來自「失敗要明確,不默默降級」的原則。

現行設定檔:nemotron_3_super

欄位值依據
模型<帳號> Nemotron 3 Super 120B-A12B NVFP4,vLLM v0.31.0,DGX Sparkinfra/inventory/group_vars/servers.yml
服務名稱(served name)nemotron-3-super設定檔的 serving
base URLhttp://<主機名稱>:8000/v1同上
逾時15.0 秒(約 3 秒的 no-think 延遲留出餘裕)同上
同時串流數2(第 3 條串流曾觸發 CUDA 錯誤)設定檔的 max_streams、eval/README.md
取樣擷取 300、路由 48、翻譯 96、護欄 8 個 token;temperature 0.0;enable_thinking=falsellm/profiles/nemotron_3_super/__init__.py
輸出修補json_span(取第一個 { 到最後一個 })llm/output.py 的 json_span
對數機率top_logprobs=5設定檔的 Confidence.top_logprobs
信心門檻尚未校準(thresholds=None)inline-review 計畫 §4.4
證據eval/README.md〈服務參數實測〉、〈R1 採用標準〉、〈X1 審查修正後的重新驗收〉;eval/results/serving-2026-10-12/、yardstick-2026-10-13/、r1-context-fix-2026-10-14/、choice-confidence-latency-2026-10-14/;eval/complaint_zh/設定檔的 evidence

這些值由 server/tests/unit/test_model_profile.py 固定,設定檔裡列出的每個路徑也要存在。

候選設定檔:nemotron_3_5_super_vl(評估中,不是預設)

Nemotron 3.5 Super VL NVFP4,只用文字輸入。為了讓評估只比較模型本身,它和 Super 只差在服務名稱 nemotron-3.5-super-vl;取樣、token 上限、提示詞集、關閉推理的開關、json_span、top_logprobs=5、逾時與 2 條串流都和 Super 相同,由同一個測試固定。它有自己的信心門檻檔,還沒校準(enabled=false,每個欄位的 tau 都是 null)。對數機率測試資料(fixture)要在評估窗口錄製。vLLM 啟動參數的出處、切換與回滾,見 infra/README.md 的「評估 Nemotron 3.5 Super VL」。在保留集上不比 Super 差,才把 triaige_model_profile 換成它。

第二個設定檔要填什麼

下面是示意,不是會上線的程式碼:

PROFILE = ModelProfile(
    id="<vendor_model_size_quant>",
    display_name="…, vLLM vX, DGX Spark",
    serving=Serving(api="openai_chat", served_name="<served-name>",
                    default_base_url="http://<主機名稱>:8000/v1",
                    timeout_s=<實測 p99 × 餘裕>, max_streams=<實測穩定串流數>,
                    recipe="infra/playbooks/deploy-<model>.yml"),
    prompts=ems_compact.PROMPT_SET,   # 先沿用;有保留集證據才分岔
    sampling={ "extraction": Sampling(max_tokens=..., extra_body={...}), ... },
    reply_text=output.strip_think_then_json_span,   # 模型會輸出推理區塊時才需要
    confidence=Confidence(top_logprobs=5 或 None, thresholds=None,
                          fixtures="server/tests/fixtures/logprobs/<id>"),
    evidence=("eval/results/<date>-<id>-…",),
)

提示詞集與稽核版本

提示詞以提示詞集(prompt set)為單位,設定檔以 id 引用(ems_compact)。多個設定檔可以共用同一個提示詞集;只有保留集的證據顯示是模型讀錯時,才另外分出新的提示詞集,並提高 PROMPT_VERSION。稽核紀錄裡的 AI 身分是 name = 服務名稱、version = 提示詞版本,設定檔 id 只寫進啟動日誌與評測結果。

7. 檢查放哪一層

新增一條檢查時,怎麼決定它放在哪裡?先問一個問題:如果模型是完美的,這條檢查還需要嗎?

flowchart TD
  Q1{"換成完美的模型,<br/>這條檢查還需要嗎?"}
  Q1 -- 需要 --> TASK["任務層:task_rules/ 或 engine/<br/>影響畫面時要同步 web mock"]
  Q1 -- 不需要 --> Q2{"它在修補或量測<br/>這個模型或伺服器的輸出習慣嗎?"}
  Q2 -- 是 --> MODEL["模型層:profile.reply_text 或信心評分"]
  Q2 -- 否 --> RUNTIME["執行期:傳輸、排程、可用性<br/>留在 ai/"]
圖表載入中…

目前主要的檢查分類如下:

檢查位置層理由
取出第一個 { 到最後一個 } 之間的內容llm/output.py模型容許模型在 JSON 外多寫文字。已存的 Super 回覆都是乾淨的 JSON,所以對 Super 只是容錯
JSON 必須是物件task_rules/任務每個任務的輸出契約
未知欄位拒收、null 代表未陳述task_rules/record_checks.py、statement_checks.py任務輸出 schema
生命徵象範圍、sex 列舉、過敏狀態與藥名限制task_rules/record_checks.py、statement_checks.py任務生理與記錄限制
過敏「無」或「未知」要有原文依據task_rules/record_checks.py、statement_checks.py任務安全規則,任何模型都需要
路由標籤合法、問題模式回退 othertask_rules/route_checks.py任務分類器輸出契約
主訴翻譯:英文字詞白名單、數量、重複片語、中文數字/方位/身體部位比對task_rules/complaint_guard.py任務對任何翻譯者都成立的顯示規則
自我檢查護欄(Yes/No、max_tokens 8)llm/prompt_sets/ems_compact/self_check.py、設定檔的 rail 取樣模型依賴這個模型一個 token 就答
對數機率讀取、token 與回覆對不上時不給分數llm/wire.py、llm/choice_scoring.py模型依賴 vLLM/OpenAI 的對數機率格式與分詞
寫入閘門、前置路由(空白或標點略過)ai/gate.py、ai/route.py任務(留在 ai/)產品規則,確定性路由
人工值優先、supersedes、拒絕記憶engine/任務案件規則,與模擬後端對齊
逾時、HTTP 錯誤、NAT 回傳型別不明ai/extractor.py執行期傳輸;失敗就失敗,不重試
連續失敗門檻(aiAvailable=false)、每案件 8 個待辦上限、排程空位ai/service.py、ai/scheduler.py執行期可用性與排程

系統裡沒有「JSON 壞掉就重試」或「改用備援模型」的程式,也不打算加。

8. 新增提示詞或檢查

9. 換模型操作手冊

換模型時要改的是 llm/ 裡新增的設定檔,加上 infra/ 裡那個模型自己的部署手冊;其他層不必改。

flowchart TD
  S1["1 可行性<br/>端點、對數機率、推理開關、記憶體、授權"] --> S2["2 部署手冊<br/>部署腳本、模板、group_vars;<br/>bench_serving 與 3 串流壓測決定 max_streams"]
  S2 --> S3["3 寫 profile 並登錄 registry"]
  S3 --> S4["4 不需模型的本機檢查"]
  S4 --> S5["5 錄對數機率測試資料"]
  S5 --> G{"6 宣告門檻後,<br/>保留集全部跑完<br/>達標?"}
  G -- 否 --> S7["7 在根因那一層修;<br/>不加個案字眼"] --> G
  G -- 是 --> S8["8 信心校準"]
  S8 --> S9["9 部署(--check --diff)"]
  S9 --> S10["10 記錄證據"]
圖表載入中…
  1. 可行性。 確認端點是 OpenAI 相容、logprobs 與 top_logprobs(含 bytes)能不能回、推理怎麼關、context 長度、記憶體(Super 現在約 95–103 GiB;後端與資料庫上限合計 2 GiB,見 triaige_server_mem_limit 與 triaige_db_mem_limit)、授權。資料來源見 docs/specs/2026-10-13-backend/research-<帳號>-stack.md 與 group_vars/servers.yml 的註解。
  2. 部署手冊。 新增 infra/playbooks/deploy-<model>.yml、模板與 group_vars 區塊,先 --check --diff,部署到 Spark,再跑 eval/scripts/bench_serving.py 與 3 條串流壓測,之後才決定 max_streams。
  3. 寫設定檔。 新增 llm/profiles/<id>/__init__.py,內容:身分與服務名稱、各角色的 Sampling(max_tokens 取該角色在校準集上輸出 token 的 p99 × 1.5)、reply_text、信心能力、逾時與串流數、prompts(先引用 ems_compact)、證據;然後在 llm/registry.py 登錄。
  4. 不用模型的本機檢查。 用 TRIAIGE_MODEL_PROFILE=<id> 跑後端的 pytest(StubLlm 與設定檔無關)、架構測試、設定檔契約測試(server/tests/unit/test_model_profile.py:每個角色都有取樣、路徑存在)、eval pytest、cd web && bun run check。
  5. 對數機率測試資料。 執行 server/scripts/record_logprobs_fixtures.py --profile <id>(--profile 預設是 nemotron_3_super),寫到 server/tests/fixtures/logprobs/<id>/。測試資料專屬的預期值要重新推導,性質測試不用改。
  6. 開跑前先宣告門檻(誠實規則)。以評測標準(yardstick) cb8bb62 為準,全部在保留集:基準是現行設定檔跑 3 次取平均;容忍度是 3 次的最大值減最小值,不設下限;候選模型跑 1 次。要跑的有:eval/scripts/run_extraction.py(整段口述)、eval/scripts/replay_backend.py(逐句,含正常與連發)、run_corrections.py 與 score_corrections.py、路由 T1/T3、過敏 T2、eval/complaint_zh、延遲 A/B(ab_latency.py、ab_logprobs.py)。一律在 lockf ~/.local/state/triaige-qa/super.lock 內執行,串流數不超過 max_streams,結果寫到 eval/results/<date>-<id>/。
  7. 失敗怎麼修。 在根本原因所在的那一層修正(2026-10-13 的使用者規則):模型讀錯了才改提示詞,而且只寫通則、只在校準集上調整、保留集只跑一次;切句或上下文的問題改擷取架構;合併的問題改 engine;標準答案有疑問就交給臨床組員。不能加只針對個案的字眼。
  8. 信心校準。 執行 calibrate_choice_confidence.py --profile <id>(尚未實作,屬於 C2 工作),寫入該設定檔的門檻檔。達到 inline-review 計畫 的 D7 目標之前維持 enabled=false。重新產生模擬後端用的副本,並跑漂移測試。
  9. 部署。 把 triaige_model_profile 指向新模型(infra/inventory/group_vars/servers.yml;服務名稱從 vllm_served_names 查,vLLM 的 vllm_stack 跟著它),--check --diff 後部署,再照 infra/README.md 跑部署後的煙霧測試,並重播兩個案例。回滾就是回到前一個設定檔 id 與前一個映像標籤。
  10. 記錄。 在 eval/README.md 加一節、更新設定檔的 evidence、更新本文第 6 章的表。

10. 部署與設定開關

flowchart LR
  subgraph Host["DGX Spark 主機"]
    TS["Tailscale Funnel"]
    subgraph Docker
      SRV["triaige-server :8100<br/>記憶體上限 1536m"]
      DB[("PostgreSQL :5433<br/>記憶體上限 512m")]
      VLLM[("vLLM Nemotron 3 Super :8000")]
    end
  end
  TS --> SRV
  SRV --> DB
  SRV -- "TRIAIGE_LLM_BASE_URL" --> VLLM
圖表載入中…

部署由 infra/playbooks/deploy-triaige-server.yml、deploy-nemotron-super.yml 與 infra/playbooks/templates/triaige/docker-compose.yml.j2 負責,參數在 infra/inventory/group_vars/servers.yml。後端映像在 Spark 上原生建置,標籤是 git revision。正式環境目前執行映像 triaige-server:03c2ed7c0f49,設定檔是 nemotron_3_super。

環境旗標(server/src/triaige_server/config.py):

旗標預設作用
TRIAIGE_MODEL_PROFILEnemotron_3_super選設定檔;未知值啟動失敗
TRIAIGE_R1_CONTEXT開擷取提示詞帶上前文(X1);關掉時 R1 只收那一句,改用不帶前文的提示詞
TRIAIGE_CHOICE_CONFIDENCE開要求回傳對數機率並對選項欄位評分;設定檔不支援對數機率時啟動失敗
TRIAIGE_COMPLAINT_TRANSLATION開啟用主訴英翻中
TRIAIGE_FOLLOWUP_SUGGESTIONS、TRIAIGE_FOLLOWUP_IDLE_S開、8 秒建議追問:救護端對話停下來 8 秒(擷取都結束)、護理師端送出時,背景請模型列出還該問的事,放進既有的建議清單(type = question,不彈出、不語音)。P2 最低優先級,被即時擷取擠掉後從頭再跑;失敗只記稽核 followup.skipped,不重試
TRIAIGE_NURSE_EXTRACTION開護理師輸入擷取:護理師在檢傷畫面打的一段話,一次模型呼叫(P1)擷取成檢傷欄位提案,掛在那則訊息的 proposedChanges(message.updated),護理師按「套用」才寫入;只提案白名單欄位、不蓋掉護理師已填的值。關掉時 nurse channel 只儲存並回覆固定文字
TRIAIGE_SELF_CHECK_OUTPUT關啟用 Yes/No 輸出護欄
TRIAIGE_ROUTE_CLASSIFIER關啟用路由分類器
TRIAIGE_MEMO_CONSOLIDATE關啟用備忘整理:對話停下來(TRIAIGE_MEMO_IDLE_S,預設 20 秒;還沒整理的句子不到 TRIAIGE_MEMO_MIN_LINES(4)句時,要等最舊的一句滿 TRIAIGE_MEMO_TAIL_S(60 秒))或送出時,一次讀一段話寫案件備忘;只在 AI 閒置時跑,任何工作一到就讓開,等了 TRIAIGE_MEMO_STALE_S(預設 180 秒)才升成一般背景工作;保留集通過之後才在 infra/ 打開
TRIAIGE_HOSPITAL_LEVEL關啟用急救責任醫院分級建議:紀錄表欄位變動後(TRIAIGE_HOSPITAL_LEVEL_DELAY_S,預設 2 秒內的變動合成一次),以 P2 背景工作讓模型讀已填欄位,寫 /triage/neededHospitalLevel(來源 ai,待複查);輸入沒變不再問、失敗只記稽核 hospital_level.failed、不重試(level_llm 的 NAT 重試次數降為 1)
TRIAIGE_AI_SUMMARY開醫師交接摘要的 AI 整理:救護端送出與檢傷送出之後,背景(P2,被即時句子擠掉就重跑)把送出的紀錄表、備忘與檢傷評估整理成幾句附來源代號的繁體中文;提示詞與取樣參數在 llm/prompt_sets/ems_compact/summary.py 與 sampling["summary"],確定性檢查(來源存在、數字出自引用欄位、不含診斷與處置用語)在 task_rules/summary_checks.py,狀態與稽核(ai_summary.generated、ai_summary.failed)在 engine/ai_summary.py;結果放在 PhysicianSummary.aiSummary,規則版摘要照常,失敗時明說、不重試、沒有備援模型
TRIAIGE_DIFFERENTIALS關醫師端鑑別診斷:案件送出與檢傷送出後,背景 agent 搜尋 kb_passages 並整理常見與最致命各 5 項(開推理,約 2–5 分鐘);設定檔沒有 differential 取樣時啟動失敗;TRIAIGE_DIFFERENTIAL_TIMEOUT_S(預設取自設定檔)、TRIAIGE_DIFFERENTIAL_STALE_S(預設 60:等這麼久還沒輪到閒置時段就升成一般背景工作)。知識庫匯入見 infra/README.md「知識庫」
TRIAIGE_LLM_BASE_URL、TRIAIGE_LLM_MODEL、TRIAIGE_LLM_TIMEOUT_S、TRIAIGE_AI_CONCURRENCY取自設定檔部署時可覆蓋

11. 證據索引

12. 已知限制