昨天 Day 23 的 prospective validation 沒有通過。
我們在 Day 22 先把 48 題 holdout、Gate 規則、評分方式和成功標準全部 freeze,Day 23 才真的讓模型去跑。最後 384 個 REAL requests 全部完成,沒有 retry,P2 Gate 的 Error Capture 是 15/17,也就是 88.24%;但 Coverage 只有 30/48,也就是 62.50%。
因為事前寫好的 operating target 是:
Coverage >= 80%
而且:
Error Capture >= 80%
所以 Day 23 的正式結果就是:
DOES_NOT_MEET_PREREGISTERED_OPERATING_TARGET
這個結果不能因為不好看就重算,也不能看到 Coverage 太低,就偷偷把 threshold 從 0.8 改成 0.7。
不過昨天的 17 個 errors 裡,有兩題我一直很在意。
H22-003:
456 seconds
Gold:
456
H22-004:
441 seconds
Gold:
441
這兩題在 Day 23 frozen scoring 下都被判錯,而且更麻煩的是,它們的七次 samples 全部一致。
Agreement:
7/7
也就是對現在這條只看 Agreement 的 Gate 來說,它們看起來反而是「最穩定」的答案,所以兩題最後都直接 ACCEPT,成為 escaped errors。
這讓我發現一件事:
Sampling Agreement 可以處理「模型不穩定」,但它處理不了所有 reliability failure。
所以 Day 24 我先不跑新的 API,也不重新調 Gate,而是補另一層東西:
Answer Contract Layer。
前幾天的 reliability pipeline 可以簡化成:
Model Output
→ 多抽幾次 Sample
→ 算 Agreement
→ Agreement 太低就 REVIEW
→ 否則 ACCEPT
這個方法有用,但它其實只回答了一個問題:
模型重複回答時,答案有多一致?
它沒有回答:
這個輸出的格式到底符不符合系統要求?
更沒有回答:
這個答案到底對不對?
這三件事情其實完全不一樣。
假設某個 downstream API 只接受 integer,但模型回:
456 seconds
即使它連續七次全部都回 456 seconds,Agreement 還是 1.0。
對 sampling uncertainty 來說,這是一個超穩定的答案。
但對 downstream system 來說,它可能根本不是一個合法 integer。
反過來,如果模型回:
457
格式非常漂亮、完全符合 integer contract,但真正答案是 456,那它依然是錯的。
所以 Day 24 我開始把 reliability 拆成三個問題:
前兩個可以在不知道 Gold 的情況下執行。
第三個則是 benchmark scoring 才會知道。
如果把這三件事情混在一起,很容易讓我們誤以為「confidence 很高」就代表整個答案都沒問題。
我今天做了一個 deterministic 的 Answer Contract prototype。
概念很簡單。
每種 task 在模型作答以前,就先宣告:
我期待你最後回傳什麼格式?
例如:
INTEGER
代表我要的是純整數。
COORDINATE_PAIR
代表我要兩個座標。
ORDERED_SEQUENCE
代表我要一串有順序的值。
INSUFFICIENT_INFORMATION
則代表我要固定 token。
目前 prototype 支援的 contract 類型包括:
EXACT_TOKEN
INTEGER
NUMBER
INTEGER_WITH_OPTIONAL_UNIT
NUMBER_WITH_OPTIONAL_UNIT
ORDERED_SEQUENCE
UNORDERED_SET
COORDINATE_PAIR
BOOLEAN
INSUFFICIENT_INFORMATION
不過重點其實不是「有十種 type 很厲害」。
真正重要的是:
Contract 必須在模型回答以前就宣告,不能看完 Gold 才猜。
這一點很重要。
假設 Gold 是:
456
我們不能看到這個 Gold 後,就說:
「喔,那這題的 contract 一定是 INTEGER。」
因為原始題目可能其實允許:
456 seconds
甚至可能明確要求回答:
456 s
所以正確設計應該是題目 metadata 事先就寫:
answer_contract = INTEGER
或者:
answer_contract = INTEGER_WITH_OPTIONAL_UNIT
如果是後者,還可以明確宣告:
Canonical unit:
seconds
允許 aliases:
s
sec
second
seconds
這樣:
456
456 s
456 seconds
都可以被 deterministic parser 正規化成相同 typed value。
但:
456 meters
就不應該因為「反正也是數字加英文」被偷偷接受。
這就是 Answer Contract 和事後亂修 normalizer 最大的差別。
這次我反而希望 parser 保守一點。
所以目前沒有讓 LLM 幫忙判斷格式,也沒有做什麼通用自然語言 unit parser。
它只接受 contract 裡明確宣告過的東西。
例如:
如果 contract 是:
INTEGER
那:
456
合法。
但:
456 seconds
不合法。
如果 contract 是:
INTEGER_WITH_OPTIONAL_UNIT
而且明確允許 seconds,那:
456
456 s
456 seconds
都合法。
可是:
456 meters
還是不合法。
我不希望系統看到任何「看起來差不多」的東西就幫模型修好。
因為 reliability system 如果修得太積極,很可能會把真正錯誤的答案偷偷變成正確格式,最後反而更難 audit。
這次還有一個我特別想守住的界線:
格式合法,不代表答案正確。
例如 contract 是三個整數的 ordered sequence。
模型輸出:
29,14,5
Parser 可以說:
VALID
因為它確實符合:
三個整數、逗號分隔、有順序。
但如果 Gold 是:
5,14,29
那答案還是錯的。
Answer Contract 不應該偷偷做 correctness scoring。
它只需要回答:
這份 output 符不符合 task 宣告的介面?
這樣之後真正 scoring 時,Gold 才負責回答:
內容到底對不對?
把兩層分開後,整個 pipeline 會乾淨很多。
實作上我也不想讓 verifier 只回:
True / False
所以 ContractResult 會保存比較細的 reason。
例如:
VALID
INVALID_FORMAT
UNPARSABLE
另外還有更具體的 reason code,例如:
UNIT_NOT_ALLOWED
代表這個 contract 根本不允許 unit。
UNIT_MISMATCH
代表允許 unit,但是這個 unit 不在允許清單裡。
CARDINALITY_MISMATCH
代表 sequence 應該有三個元素,結果模型只回兩個。
TOKEN_MISMATCH
則是固定 token 不符合。
這些資訊之後滿有用。
因為「數學答錯」和「答案格式錯」其實是完全不同的工程問題。
一個可能要改善 reasoning。
另一個可能只需要改善 output schema 或 structured output。
如果最後全部只記成:
incorrect = true
這些差異就全部不見了。
Day 24 我沒有重新送模型 API。
今天完成的是一個獨立的 Python prototype,以及一組 deterministic tests。
測試結果:
40 passed
這些都是:
SYNTHETIC / OFFLINE TESTS
不是新的 REAL benchmark,也不能拿來說模型 accuracy 變高了。
測試裡包含像這些情況:
| Contract | Input | Result |
|---|---|---|
| INTEGER | 456 |
VALID |
| INTEGER | 456 seconds |
UNIT_NOT_ALLOWED |
| INTEGER_WITH_OPTIONAL_UNIT | 456 |
VALID |
| INTEGER_WITH_OPTIONAL_UNIT | 456 s |
VALID |
| INTEGER_WITH_OPTIONAL_UNIT | 456 seconds |
VALID |
| INTEGER_WITH_OPTIONAL_UNIT | 456 meters |
UNIT_MISMATCH |
| ORDERED_SEQUENCE | 5,14,29 |
VALID |
| ORDERED_SEQUENCE | 29,14,5 |
VALID |
最後兩個都 VALID 是故意的。
因為 parser 只檢查格式。
至於 29,14,5 到底是不是正確答案,不是它的工作。
有 Answer Contract 之後,pipeline 就不再只有一層 threshold。
現在可以變成:
Model Output
→ Answer Contract
→ Sampling Signal
→ ACCEPT / REVIEW
Contract 先跑。
如果 output 本身不合法:
REVIEW_CONTRACT
如果格式合法,但 sampling 很不穩:
REVIEW_UNCERTAINTY
如果兩個問題同時存在:
兩個 reason 都保留。
最後才是都沒問題:
ACCEPT
為了確認 routing 沒寫錯,我另外做了四個 synthetic cases。
| Fixture | Answer | Agreement | Contract | Routing |
|---|---|---|---|---|
| A | 456 |
1.0 | VALID | ACCEPT |
| B | 456 |
0.5714 | VALID | REVIEW_UNCERTAINTY |
| C | 456 seconds |
1.0 | INVALID_FORMAT | REVIEW_CONTRACT |
| D | 456 seconds |
0.5714 | INVALID_FORMAT | REVIEW_CONTRACT + REVIEW_UNCERTAINTY |
這四個 case 很直接。
A:
格式正常,而且 sampling 穩定。
所以 ACCEPT。
B:
格式正常,但 sampling 不穩。
所以 REVIEW_UNCERTAINTY。
C:
sampling 非常穩,Agreement = 1.0。
但格式不符合這個 synthetic task 的 contract。
所以還是 REVIEW_CONTRACT。
D:
格式錯,而且 sampling 也不穩。
所以兩個原因一起留下。
這就是我今天最想做出來的效果。
沒有。
這件事一定要講清楚。
Day 23 的正式結果完全沒有改。
H22-003 和 H22-004 還是 Day 23 的 escaped errors。
Primary Accuracy 還是:
31 / 48
Coverage:
62.50%
Error Capture:
88.24%
Day 23 結論也還是:
DOES_NOT_MEET_PREREGISTERED_OPERATING_TARGET
今天不能因為我做出新的 Contract Layer,就回頭把昨天的兩題改成 correct。
因為 Day 24 的設計本身就是看過 Day 23 結果後才做的。
這就是 development。
不是 prospective validation。
而且還有另一個更重要的問題:
雖然 456 seconds 在一個純 INTEGER contract 下會被攔住,但我們不能只因為 H22-003 的 Gold 是 456,就事後宣布:
「這題本來就應該使用 INTEGER contract。」
真正要判斷這件事,必須回到原始 task specification,看模型回答以前有沒有明確宣告答案格式。
不然我們只是換一種方式 post-hoc 修改規則而已。
我覺得這一點很重要。
如果我的目的只是把 Day 23 的 64.58% Accuracy 修漂亮,最快的方法其實不是做 Answer Contract。
直接把 normalizer 改成:
「遇到數字後面的 seconds 就刪掉」
可能更快。
但那沒有什麼 reliability engineering 的意義。
因為下一次可能出現:
30 meters
50 kg
3 apples
甚至更奇怪的格式。
真正有價值的是建立一個規則:
task 在 inference 前就宣告自己接受什麼。
Verifier 照那份 contract 執行。
不是 verifier 看到答案以後自己猜。
這樣未來每一個 ACCEPT / REVIEW decision 才能被 audit。
做到 Day 24,我現在比較傾向把 AI Reliability Lab 的架構拆成至少三層。
第一層:
Answer Contract
確認 output 是否符合 task interface。
第二層:
Sampling Uncertainty
用 Agreement、entropy、vote margin 判斷 repeated outputs 是否穩定。
第三層:
Task-specific Verification
例如 Day 20 的 deterministic trace。
對某些可以明確驗證狀態轉移的 task,可以再做更強的檢查。
這三層處理的是不同問題。
Contract failure:
可能是 format / schema / unit。
Sampling failure:
是 repeated generations 不穩。
Task verifier failure:
可能是 deterministic rule 根本對不上。
如果全部丟進同一個 confidence score 裡,最後會很難知道到底是哪一層出問題。
當然,事情沒有因為加 Answer Contract 就全部變好。
Day 23 最大的問題就是 Coverage 太低。
62.5%。
現在如果我再加一個 Contract Layer,理論上有機會抓到更多問題。
但同時也可能讓更多答案被送 REVIEW。
所以未來真正要驗證 multi-layer pipeline 時,不能只看:
「多抓到幾個 error?」
也要看:
不然很容易做出一個「幾乎所有東西都 REVIEW」的系統,然後宣稱 reliability 很高。
那其實沒有什麼意義。
Day 23 的失敗原本讓我第一個想到的是:
「是不是 Agreement threshold 選錯?」
做到今天,我反而覺得 threshold 可能不是最重要的問題。
真正的問題是,我們一直想用同一個 uncertainty signal 處理不同種類的 failure。
但:
一致,不代表格式合法。
格式合法,也不代表答案正確。
Agreement 只能回答其中一部分。
所以 Day 24 我先停下 REAL experiment,把 Answer Contract 這一層做成 deterministic prototype。
今天完成:
但今天沒有:
Day 23 仍然是失敗的 validation。
Day 24 做的是:
看完失敗之後,重新把系統設計得更完整。
接下來真正有意思的問題會變成:
如果把 Answer Contract、Sampling Uncertainty,甚至 deterministic verifier 疊在一起,能不能真的在新的 unseen data 上降低 escaped errors,同時又不要把 Coverage 壓到幾乎不能用?
這會是下一階段真正要驗證的東西。