「今天只是有點累。」與一段規則沒有涵蓋的文字,進入目前的本地分類器後,分數都可能是 0。前者得到 NORMAL,後者得到 UNKNOWN。如果介面只拿分數畫一條由綠到紅的刻度,這兩種結果會落在同一個位置,但程式對它們安排的回應路徑並不相同。
昨天我追到的問題,是 AI 回覆能否跨過治療指定的權限界線。今天往前檢查那道界線的輸入:系統說「一般」、「較高風險」或「未知」時,這些名稱到底代表什麼?如果上游已經把沒看懂的內容當成低風險,下游再嚴格限制權限,也是在使用一份被誤解的資料。
這就是今天整理 Risk Taxonomy,也就是風險分類的起點。我想先讓每個標籤的用途說清楚:目前這些值用來選擇程式的回應路徑,沒有對真實個人的安全程度作出保證。NORMAL 只是特定規則的結果,不能讀成「這個人沒有風險」。
這個分寸也有外部參照。NICE 的自傷指引 NG225,在 1.6.1 至 1.6.4 節明確反對以風險工具或低、中、高分層預測未來自殺或再次自傷,也反對用它們決定誰能接受治療或出院。這是臨床照護指引中的限制,並不表示本專案的文字分類已獲驗證。NICE:Risk assessment tools and scales
目前對話分流有六個標籤,從一般支持語句、困擾、高度困擾,到潛在危機、立即危險,再加上未知。名稱看起來像一條程度漸增的軸,但 UNKNOWN 無法放在這條軸上:它說的是分類所需資訊不足,或目前規則不足以解讀。
以下是 src/psychological_support/conversation/types.py 中實際使用的結果結構:
@dataclass(frozen=True)
class TriageResult:
risk_level: RiskLevel
score: int
evidence_codes: tuple[str, ...]
source: str = "SYSTEM_INFERRED_RULES"
這個結構除了標籤,還留下原因碼與來源。缺少可解讀訊號、沒有涵蓋的表達、教育性問句,以及符合特定否定或誇飾模式的語句,都可能落入未知,但原因不同。來源欄位則把這些判定標為系統規則推得的結果,避免讓它看起來像使用者自己作出的陳述。
分數也必須跟著這個用途理解。程式裡的 20、40、70、100 是固定分支使用的規則優先序值,沒有附帶預測機率或臨床校準。70 不能換算成「有七成機率發生某件事」;0 也不等於排除了危險。只保存分數,會把最需要澄清的未知與允許一般支持的分支混在一起。
現有回應規劃因此替未知保留澄清路徑,要求承認不確定、不要假定安全。分類不成功仍然要有一個明確去處,否則未知很容易在下一層被預設值悄悄吞掉。
分類表容易寫,語句的範圍卻沒那麼容易定義。例如測試裡有「這題難到想死」這類合成誇飾句。現有規則會排除符合模式的局部片段,再繼續檢查剩下的文字;它不會因為找到一句否定,就直接放過整則訊息。既有案例也涵蓋否定語句後仍出現其他傷害訊號的組合。
但局部排除仍不等於理解完整語境。今天重新執行分流、正規化、回應規劃、狀態與權限不變量的 26 項本地合成測試,結果通過。接著我做了一組獨立對照,只比較句號與換行的影響;這些都是為檢查程式而使用的文字,沒有真人參與,也沒有呼叫語言模型。
既有句號案例「現在很累。以前曾想死。」得到 POTENTIAL_CRISIS。把第一個句號換成換行,讓兩段文字各占一行,結果卻變成 IMMEDIATE_DANGER。同樣保留「以前」這個詞,分類仍因分隔方式改變。
沿著程式往前看,原因很具體:文字正規化先把換行與其他空白合併成空格,分句步驟之後才執行。句號版把「現在」與傷害語句留在不同片段;換行版的邊界先消失,兩種訊號便可能被同一個片段的規則配在一起。分句規則雖然列了換行,到了那一步,原來的換行已經不在了。
這次結果指出的是前處理與時間語境的未解邊界,不能拿來判定哪個標籤在臨床上正確。連句號版也只是避免這一次跨句配對,沒有真正理解「以前」與「現在」的關係。既有測試證明了列出的案例;換行對照則提醒我,分類定義還必須涵蓋資料經過哪些轉換。今天保留了這個差異,尚未修改產品規則。
另一個容易忽略的地方,是專案裡有兩套風險枚舉。對話層需要區分回應情境,基礎契約則使用另一組標籤。現有 adapter 明寫它們的對應:
| 對話分流標籤 | 基礎契約標籤 |
|---|---|
NORMAL |
LOW |
DISTRESS |
ELEVATED |
HIGH_DISTRESS |
HIGH |
POTENTIAL_CRISIS |
CRITICAL |
IMMEDIATE_DANGER |
CRITICAL |
UNKNOWN |
UNKNOWN |
這張表描述本地程式的轉換,不是臨床風險對照表。最值得保留的是最後一列:未知仍然是未知。基礎契約解析缺值或不認識的風險字串時,也會保留未知;權限政策接到未知後,採取受限回應。它沒有因為資料不完整,就替資料補上一個最低等級。
合併也有代價。潛在危機與立即危險都轉成 CRITICAL,但對話規劃仍使用原始標籤區分指引。只留下轉換後的值,就無法單靠它還原前面的差別。因此,每次映射都需要問:下游只需要知道應限制一般生成,還是仍需要知道限制的具體原因?
這也是分類和政策應分開討論的理由。標籤保存規則觀察到的情況,政策決定這個情況下允許哪些回應。映射到一個名叫危機的模式,並不表示系統已聯絡任何人,更不代表現實世界中有人接手。
走到這裡,我能確認的是有限文字規則、結果結構、未知處理與明確映射已經存在,且今天指定的合成測試通過。完整的風險分類契約仍未完成:本日尚未完成此部分,以下先整理目前設計與下一步。
後續分類草案需要替每個項目寫出納入條件與排除條件,也要交代引用、否定、主體及時間範圍如何處理。條件不足時應保留哪種未知,分類定義改版後舊紀錄如何解讀,都是仍待設計與驗證的部分。這些不能只靠多加幾個枚舉名稱補齊。
例如,基礎契約已列出一些與敏感內容有關的訊號名稱,但欄位能接受某個名稱,不表示文字分類器就能可靠辨認那一類內容。要主張分類契約完成,還需要版本化定義、映射驗證、歧義與未涵蓋案例,以及相應的邊界審查。今天沒有真實模型、真人適用性或臨床驗證可供這些主張使用。
風險分類最後要交出去的,是帶著理由與限制的資料。當資料仍然未知,或者不同規則給出相互衝突的訊號時,下一層政策究竟要依據什麼停止、澄清,或限制原本允許的回應?