iT邦幫忙

2026 iThome 鐵人賽

DAY 24
0

昨天 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。


Agreement 高,不代表輸出一定能用

前幾天的 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 拆成三個問題:

  1. Contract validity:輸出格式是否合法?
  2. Sampling uncertainty:多次回答是否穩定?
  3. Semantic correctness:答案實際上對不對?

前兩個可以在不知道 Gold 的情況下執行。

第三個則是 benchmark scoring 才會知道。

如果把這三件事情混在一起,很容易讓我們誤以為「confidence 很高」就代表整個答案都沒問題。


Answer Contract 是什麼?

我今天做了一個 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,就自己決定這題只能回 integer

這一點很重要。

假設 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

這次我反而希望 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。


VALID 不代表 Correct

這次還有一個我特別想守住的界線:

格式合法,不代表答案正確。

例如 contract 是三個整數的 ordered sequence。

模型輸出:

29,14,5

Parser 可以說:

VALID

因為它確實符合:

三個整數、逗號分隔、有順序。

但如果 Gold 是:

5,14,29

那答案還是錯的。

Answer Contract 不應該偷偷做 correctness scoring。

它只需要回答:

這份 output 符不符合 task 宣告的介面?

這樣之後真正 scoring 時,Gold 才負責回答:

內容到底對不對?

把兩層分開後,整個 pipeline 會乾淨很多。


失敗也不能全部叫 Incorrect

實作上我也不想讓 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

這些差異就全部不見了。


今天跑的是 Offline / Synthetic Test,不是新的 REAL Experiment

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 到底是不是正確答案,不是它的工作。


接著把 Contract 和 Agreement 接在一起

有 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 也不穩。

所以兩個原因一起留下。

這就是我今天最想做出來的效果。


那 H22-003 和 H22-004 現在被「修好」了嗎?

沒有。

這件事一定要講清楚。

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 修改規則而已。


Answer Contract 不是拿來讓模型分數變漂亮的

我覺得這一點很重要。

如果我的目的只是把 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。


現在 Reliability Pipeline 開始有分層了

做到 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 裡,最後會很難知道到底是哪一層出問題。


多一層 Gate,也可能讓 Coverage 更差

當然,事情沒有因為加 Answer Contract 就全部變好。

Day 23 最大的問題就是 Coverage 太低。

62.5%。

現在如果我再加一個 Contract Layer,理論上有機會抓到更多問題。

但同時也可能讓更多答案被送 REVIEW。

所以未來真正要驗證 multi-layer pipeline 時,不能只看:

「多抓到幾個 error?」

也要看:

  • Contract Valid Rate
  • Contract Failure Rate
  • Contract Review Count
  • Uncertainty Review Count
  • Contract + Uncertainty overlap
  • Final Coverage
  • Selective Risk
  • Error Capture
  • Escaped Errors

不然很容易做出一個「幾乎所有東西都 REVIEW」的系統,然後宣稱 reliability 很高。

那其實沒有什麼意義。


Day 24 做完後,方向比昨天清楚很多

Day 23 的失敗原本讓我第一個想到的是:

「是不是 Agreement threshold 選錯?」

做到今天,我反而覺得 threshold 可能不是最重要的問題。

真正的問題是,我們一直想用同一個 uncertainty signal 處理不同種類的 failure。

但:

一致,不代表格式合法。

格式合法,也不代表答案正確。

Agreement 只能回答其中一部分。

所以 Day 24 我先停下 REAL experiment,把 Answer Contract 這一層做成 deterministic prototype。

今天完成:

  • Answer Contract abstraction
  • 多種 deterministic contract type
  • 明確 unit alias handling
  • Contract reason codes
  • Contract + Agreement routing
  • 40 個 synthetic/offline tests
  • 4-case synthetic pipeline demo
  • 0 model/API requests

但今天沒有:

  • 修改 Day 23 結果
  • 重算 Day 23 Accuracy
  • 宣稱 H22-003 / H22-004 已被修正
  • 新的 prospective validation

Day 23 仍然是失敗的 validation。

Day 24 做的是:

看完失敗之後,重新把系統設計得更完整。

接下來真正有意思的問題會變成:

如果把 Answer Contract、Sampling Uncertainty,甚至 deterministic verifier 疊在一起,能不能真的在新的 unseen data 上降低 escaped errors,同時又不要把 Coverage 壓到幾乎不能用?

這會是下一階段真正要驗證的東西。


上一篇
Day 23|第一次真的驗證失敗了:Gate 抓到 88% 錯誤,但 Coverage 只有 62.5%
系列文
AI Reliability Lab:30 天用 Python 把「AI 好像很準」變成可以量的工程指標 共 24 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言