Day 1 我丟了一個問題出來:做出來之後,你怎麼知道它是對的?
三十天後我的答案是:沒辦法一次知道。你只能一直問,而且要讓系統自己也學會問。
這篇不加新功能。我想把三十天攤在桌上重看一次,看每一段到底留下了什麼,也看哪些地方我當初講得太滿。
Day 2 把一顆 26B 的 Gemma 4 丟進去直接讀整頁,什麼架構都不做。
純文字頁覆蓋率 99.4%,有框線的財務表 98% 以上。單看這兩個數字,你會以為問題已經解決了。
然後是第 9 頁,無框線的圖例清單,76.2%。還有 26 條逐字錯誤:負號不見、科目名稱掛錯、化學品名被換成另一個真實存在但完全不對的名字。這些錯沒有一條會讓程式噴 exception,finish_reason 全是 stop。
我到現在還記得整理那張錯誤表的感覺。最可怕的不是它錯,是它錯得理直氣壯。
Day 3 把硬體攤開:GB10 有 128GB 統一記憶體,但頻寬只有 273 GB/s。那時候我說三十天有一半的決策會被這個數字逼出來,回頭看,真的沒有誇張。想用更大的模型硬救?頻寬不答應。想多跑幾次取共識?時間不答應。Day 5 的結論只有一句:單一模型不夠,不是模型不強,是單一模型的架構裡沒有「檢查」這個環節。
Day 6 到 Day 8 我在證明「進階」不是行銷詞。
字型變體、罕用字、直橫混排、繁簡混排、異體字。最後落成一張錯誤分類表:9 頁、87 個 diff 塊。其中 37 個落在 other,是我的規則式分類器說不出種類的錯。
這個 42.5% 我一直記著。它提醒我,自己寫的分類器也只是一把有盲區的尺。到了 Day 27 設計抽樣,other 就變成最該讓人看的那一層。
Day 7 還留下一條規矩,後面每一天都在用:正規化只能用在比較鍵,原始輸出永遠不改。
Day 10 把速度量清楚了:26B MoE 的 decode 收斂在 48.5 tok/s,而且跟頁面類型無關。表格區慢,是因為它輸出的東西只有 42% 是內容,剩下都在畫 Markdown 框線。
Day 11 本來要讓 gpt-oss:20b 當 Verifier。結果我連它都跑不起來:本機顯卡 4GB,GB10 上跑著主模型,我也沒有權限另開服務。只能先把介面訂好,老實寫一句「design-only, not yet wired」。
Day 12 是這系列我最不後悔的一天。我把「不用中國 origin 模型」這句原本只是憑印象覆誦的話,拆成四個可以查證的維度:訓練資料揭露、授權限制、公司轄權、第三方證據。查下去才知道台灣國安局實測 Qwen 在 15 項指標裡違規 11 項。也順便發現 Nemotron 的訓練資料揭露反而是三個候選裡最透明的,這跟我原本的預設完全相反。
Day 13 把硬體跟供應鏈兩張表疊成一棵決策樹。它最誠實的地方在 Verifier 那一列:我只能寫「介面已設計,等硬體」,沒辦法寫「已選定」。決策樹不會幫你生出一台機器,它只保證機器到的那天,你不用把判斷重新做一次。老實說這張表寫出來的時候有點洩氣,但後面幾天回頭查它的次數,比查任何一張效能表都多。
這是我今年加進來的軸線,也是最常讓我改寫草稿的一段。
Day 14 拿台積電繁中年報第 61 頁測,整頁 OCR 的 CER 是 0.9792,三個公告字號加三筆罰鍰金額,0/6。我差點寫出「這顆模型讀不懂中文財報」這種很有戲劇性的結論,是獨立驗證抓到真正的原因:跨頁對開的寬圖被壓進固定數量的視覺 token,字太密,模型看不清,只好靠語言習慣編故事。
Day 15 緊裁單欄、DPI 拉到 400,CER 掉到 0.0845,6/6 全中。同一顆模型、同一頁內容。
這一對數字我覺得比整個系列任何架構圖都有說服力。問題常常不在模型夠不夠聰明,在你怎麼把東西餵給它。
Day 16、17 往上加語意層:金額、日期、簽約對象的一致性,還有拿 MOPS 的 XBRL 當第二把尺。這裡要誠實講,XBRL 的比對邏輯寫好了,也用合成資料測過,但真實的 XBRL instance 到今天還沒抓下來。規則有了,真實比對結果沒有。
Day 18 用 PyMuPDF、Docling 跟 Region Routing 把流程接起來,Day 19 立刻指出它的天花板:單向流程錯了就是錯了,沒有任何一個環節會回頭。
後面幾天把回頭的能力一塊一塊加上去。Commander 負責拆任務、分派,Subagent 各管一種區塊,四重驗證(Checksum、Logprob、Tesseract 交叉比對、MOPS 領域語意 Verifier)一層疊一層,訊息協定規定角色之間怎麼講話、失敗怎麼重試,Day 24 把這些組成完整的 Harness。
Day 1 那句話,到這裡才真正有了形狀:
Agents need feedback loops, not perfect prompts.
回頭看這一段,我覺得最關鍵的一步其實是 Day 19,不是 Day 24。組裝本身是工程,花時間但不難想;難的是承認前一天辛苦接好的 Pipeline 不夠。那種感覺很像把一面剛砌好的牆拆掉一半,只為了在中間開一扇會回頭的門。Day 2 那 26 條錯誤之所以安靜,就是因為當時的流程沒有門。
Day 25 的 diff-driven flywheel 把錯誤變成可以累積的東西,Day 26 用 JSONL 記事件、SQLite 做索引,讓每一筆錯都能回到原本那一頁、那一個區塊。
Day 27 算抽樣的時候,我推翻了自己寫在規劃書上的「2-5%」:抽樣需要的是固定的絕對數量,在 5% 預期錯誤率、±2 個百分點的條件下大約 440 筆,百分比只是換算。Day 28、29 回到 Router,發現現成方案在解「送去哪最划算」,而我要解的是「哪裡不能送」。最後寫的程式,是一段讓系統在不確定時停下來的程式。
從 LLM 到 Harness。開賽時我以為這是一條從簡單到複雜的路。現在覺得比較像是從「相信模型」走到「不相信任何單一元件」。Harness 本身沒有比較聰明,它只是讓每個元件都有人盯著,包括盯著的那個人自己也要被抽查。
隱私。一開始我以為是「不上雲」。後來它長成三層:資料不出機房、模型要審供應鏈、Router 跟紀錄也要審。Day 28 我才驚覺,花了一整天審模型,卻差點直接裝一個看得到所有流量的 Router。紀錄檔也一樣,為了保護個資而建的紀錄,一不小心就變成最大的個資清單。
可信任。Day 1 我寫「可信任是疊出來的」。現在我會補一句:疊出來的東西也要定期拆開檢查。四重驗證加上人工抽樣,加上每一筆決策都能回放,才勉強撐得起「可以信」這三個字。而且那是一個會變動的狀態,不是一張證書。
繁中進階。這個詞的份量在 Day 6 到 Day 8,但它真正發揮作用是在後面:異體字對照變成比較鍵的規則,other 那 42.5% 變成抽樣的分層依據。繁中的難,最後都變成了設計的理由。
還有 MOPS 那條線。讀出字跟讀懂年報之間,隔著欄位一致性、公告字號的交叉引用、XBRL 的外部對照。這條線我只走了一半,但它讓我確定一件事:OCR 正確不等於語意正確,兩件事一定要分開驗。就拿第 61 頁那三筆罰鍰來說,40 萬、45 萬、45 萬,數字長得這麼像,OCR 把其中一筆讀成另一筆,字元錯誤率只差一個字,語意上卻是把一張罰單的金額掛到另一張罰單上。這種錯,字元層的指標再漂亮也看不出來。
做出來之後,怎麼知道它是對的?
│
├─ 從 LLM 到 Harness(架構演進)
│ ├─ Day 2 單模型 baseline:99.4% / 76.2%,錯得理直氣壯
│ ├─ Day 3 273 GB/s:頻寬逼出架構
│ ├─ Day 5 單模型不夠,缺的是「檢查」
│ ├─ Day 18 Pipeline:PyMuPDF + Docling + Region Routing
│ ├─ Day 19 單向流程的天花板
│ └─ Day 20-24 Commander / Subagent / 四重驗證 / 訊息協定 / 組裝
│
├─ 繁中進階
│ ├─ Day 6 字型、罕用字、版面:9 頁 87 個 diff 塊
│ ├─ Day 7 異體字:正規化只進比較鍵,原文不改
│ └─ Day 8 難點倒推設計決策
│
├─ 隱私
│ ├─ Day 12 供應鏈四維度審查
│ ├─ Day 13 隱私 × 效能決策樹
│ ├─ Day 28 Router 也要被審
│ └─ Day 29 敏感分級、fail-closed、HMAC 紀錄
│
├─ 可信任
│ ├─ Day 11 Verifier 不能同源
│ ├─ Day 22 Checksum / Logprob / Tesseract / 領域語意
│ ├─ Day 25 diff 飛輪
│ ├─ Day 26 JSONL 事件帳 + SQLite 索引
│ └─ Day 27 抽樣稽核:約 440 筆,百分比只是換算
│
└─ MOPS 年報語意理解
├─ Day 14 讀不到字:0/6,密度對上固定視覺 token
├─ Day 15 緊裁 + 400 dpi:6/6,公告字號抽取
├─ Day 16 金額、日期、簽約對象一致性
└─ Day 17 XBRL 當第二把尺(真實 instance 未取得)
所有分支最後都匯到同一件事:每個失敗留下紀錄,變成下一輪的測試。
五個分支分開畫,但它們一直在互相借東西。Day 7 的「不改原文」被 Day 16、Day 25、Day 27 反覆引用;Day 12 的審查清單到 Day 28 被拿去審 Router;Day 14 那次差點寫錯的結論,是 Day 11「不能自己驗證自己」在我自己身上應驗了一次。
照慣例把空白列出來,這次不列表了,直接講。
gpt-oss:20b 到今天沒有跑起來,所以 Day 29 那張 fail-closed 表格裡,L2 以上的區塊現在全部都得進人工佇列。MOPS 的 XBRL 真實資料沒抓下來,Day 17 的比對器只驗證過合成資料。Day 27 的抽樣流程還沒真的讓另一個人照著跑一次,兩人一致率是多少,我不知道。敏感分級的內容偵測只有身分證字號跟銀行帳號兩條規則。
還有一件我比較難承認的事。Day 18 以後的篇幅,程式碼寫得愈來愈多,實測數字卻愈來愈少。前半段每個結論背後都有 experiments/ 裡的一個檔案撐著;後半段很多東西是設計,是用合成資料確認邏輯會照著走,不是用真實年報跑出來的成績。兩種東西的份量不一樣,我盡量在每一篇都講清楚是哪一種,但讀者一路看下來,很容易把它們混在一起。如果你只記得這系列的一件事,我希望是這個區分,比任何一張架構圖都重要。
這些東西,我在 Day 1 就說過會有。那時候我寫「做不到的地方我會直說」,現在清單比我預期的長,但我覺得這份清單本身也是這系列的產出之一。它標出了下一輪實驗該從哪裡開始。
Day 1 結尾我引用了一句話:每個失敗都是免費的引導機會,大多數人浪費掉了。
三十天下來,我撿起來的失敗大概有:一張錯誤分類表、一次差點寫錯的歸因、一個憑感覺寫錯的抽樣比例、一個差點沒審就接上的 Router。每一個都讓系統多了一道檢查。
GB10 還在機房裡跑著那顆 26B 的模型。下一份年報丟進去的時候,它還是會錯,但這次錯的地方會有紀錄,會被抽到,會有人看。