iT邦幫忙

2026 iThome 鐵人賽

DAY 28
0

昨天寫到「同一個錯誤犯第二次就不是意外,是習慣缺口」——
指的是設計文件與實作分岔,兩天撞到兩次。今天把那個缺口補掉。

打開 docs/superpowers/specs/2026-09-08-multiagent-tutor-design.md,
那是三期開工前的設計。從第 0 步之後就沒再動過。

一行一行比對現況,八處對不上。

第一個決定:不要直接改

直覺的做法是把錯的段落改成對的,文件就乾淨了。

我沒有這樣做。理由是:

這份文件記錄的不是「系統長什麼樣」,而是「當時為什麼這樣決定」。
而分歧本身就是最有價值的部分——它說明了哪些預測是錯的。
直接覆寫會把那段歷史一起刪掉。

系統長什麼樣,有別的東西負責:三份程式碼導讀、data/README.md、
以及程式碼本身。設計文件不需要再當一次現況文件,它會輸給程式碼,
而且是悄悄地輸——沒有任何機制會告訴你文件過期了。

所以做法改成:原文一字不動,在每個分歧處底下加一段引言,標明
已被取代(日期) 並寫出理由。沒標的部分,就是至今仍然成立的部分。

文件開頭加一段讀法說明,因為一份半對半錯的文件,比全錯的更危險——
讀的人不知道該相信哪一半。

八處分歧,分成三種

比對完之後,我發現它們不是同一種錯。

一、預測錯了(3 處)

出題從「生成」變成「挑」。 設計寫的是模型產出 {title, description, tests}。
實作是從題庫挑 {chosen_id, reason}。理由是 Day 27 那條傳染鏈:
錯的測資會讓評測跟著錯,再經由摘要傳染給導師,學生最後對著一份正確的程式碼
被要求找出不存在的錯。

全部通過時不換題。 設計的圖是 grade → set_problem(跳過導師)。
實作是 grade → solved → tutor。一換題,那份「你過了」的判定就會被
set_problem_node 清掉,學生什麼都沒看到;而且通過之後導師仍要開口說一句肯定。

錯誤觀念清單整批換掉。 從函式練習題的分類(dict-access、missing-return、
recursion-base-case)換成 CPE 式的(io-eof、io-parse、output-format)。

這三處的共同點:當初的判斷在當初的前提下是對的,是前提變了。

二、猜對了方向,猜錯了細節(2 處)

設計文件有一節叫「沙箱的預留介面」,寫著未來的簽名會是:

make_grade_node(client)   # 第 2 期:LLM 推測
make_grade_node(runner)   # 未來:真實執行

實際上是 make_grade_node(client, runner=sandbox.run_all)——兩個都要。

方向猜對了(沙箱會換掉判定),但猜錯了一件事:以為模型會被淘汰,
實際上是換工作。執行器給事實,模型把失敗解讀成一句人話。

同一節下面那句「沙箱替換的只有『過/沒過』這個事實判斷,不是整個 agent」
其實已經講對了,只是上面的程式碼片段沒跟上。同一節裡的兩句話互相矛盾,
而寫的時候完全沒感覺。

TutorState 也類似:欄位名對,但少了四個,grade 的形狀從 3 個欄位長成 9 個。

三、限制被換掉了(3 處)

這一種最值得記。設計文件的「已知限制」寫著:

評測是 AI 推測而非實際執行 ⇒ 會漏掉執行時才出現的錯誤,也可能誤判。

這條解決了。但解決它的東西帶來一條更嚴重的:

sandbox 是執行器,不是安全沙箱 ⇒ 擋得住無窮迴圈與崩潰,
擋不住記憶體耗盡、網路、讀取其他檔案、fork 炸彈。
學生在自己電腦上跑自己寫的程式沒有新增風險;部署到雲端就是遠端程式碼執行。

在補上真正的隔離之前,公開部署必須設 runner=None。

第二條:「左欄文案不能聲稱執行了測試」——反過來了。
現在真的執行了,硬說成「推測」一樣是謊,而且會讓學生不敢相信真的結果。
現行規則是措辭跟著 source 走。

第三條是全新的:題庫會用完。 出題改成從題庫挑之後,題目做完就沒得換了。
解法是加題,不是改程式。

一條限制消失,換來兩條新的、一條反轉。「限制清單變短」從來不是進度的指標。

順便補上的兩處

比對時還發現文件的兩張表停在開工那天。

黑板多了一個 collection:students/{學號}/solved/{題號}。
值得記的是它和 misconceptions 刻意不合併——clear_misconceptions
只清弱點統計,不動 solved:

做過哪些題是事實紀錄,弱點統計是推論。推論可以重來,事實不行。

檔案表少了四個模組,而它們有一個共同的出生理由:
misconceptions.py、problems.py、sandbox.py——
每一個都是因為有多個地方必須講同一套規則才獨立出來的。
詞彙表要給導師、題庫、挑題三方共用;比對規則要同時給評測用、又要講給模型聽。

規則散在兩個地方時,不會報錯,只會安靜地不一致。

至於「只有 app.py 依賴 streamlit」這條約束,三期走完沒有破例。
第一天定下的約束,到最後仍然成立的沒有幾條,這是其中一條。

今天學到的

  1. 設計文件不要被改成現況文件。 它會輸給程式碼,而且是悄悄地輸。
    讓它專心當「當時為什麼這樣決定」的紀錄,現況交給導讀與程式碼。
  2. 標註分歧比修掉分歧有價值。 修掉之後,文件看起來像是一路都對——
    那才是真正的資訊損失。
  3. 一份半對半錯的文件比全錯的更危險。 所以開頭那段「怎麼讀」是必要的。
  4. 限制清單變短不是進度。 這次是一條換兩條,其中一條比原本更嚴重。
  5. 同一節裡的兩句話可以互相矛盾,而寫的時候毫無感覺。 這是第三次撞到
    「橫著比較才看得見」這件事了(Day 27 是跨 agent,今天是跨段落)。

程式碼沒有動,只有文件。三期之後最大的一塊未完成工作已經寫在限制清單最上面:
真正的沙箱隔離。


上一篇
Day 27
下一篇
Day 29
系列文
基於 Multi-Agent 協作之蘇格拉底式程式學習與自動化評測系統 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言