iT邦幫忙

2026 iThome 鐵人賽

DAY 26
0
AI Engineering

基於 Multi-Agent 協作之蘇格拉底式程式學習與自動化評測系統系列 第 26 篇

Day 26:把第 2 期重讀一遍,發現三件當時沒想清楚的事

  • 分享至 

  • xImage
  •  

今天沒寫程式。寫的是 docs/notes/phase-2-code-walkthrough.md——
第 2 期(評測 agent)的程式碼導讀。

第 1 期做完的時候我寫過一份,格式是固定的:
做了什麼 → 在哪一行 → 為什麼這樣寫 → 不這樣寫會怎樣。
最後那段是重點。API 名稱查文件就有,取捨的理由查不到。

本來以為這是一天的整理工作。結果重讀自己的程式碼,翻出三件當時沒想清楚的事。

一、fan-in 不等於並行

第 1 期的筆記留了一個待辦:

messages 要不要加 Annotated[list, add](reducer)?
第 2 期 tutor 會變成 fan-in 匯合點,到時候「多個來源同時更新同一個欄位
要怎麼合併」才是真問題,reducer 的選擇要在那時一起決定。

現在 fan-in 真的出現了——tutor 有三個上游(START、grade、solved)。
按照當時的計畫,今天該回答這個問題。

答案是:仍然不要加。

因為那三條路徑不會同時到達 tutor。它們是互斥的分支,不是並行的分叉——
一次互動只會走其中一條。而 reducer 解決的是「多個 node 在同一步各自回傳同一個欄位」,
那件事在這裡根本沒發生。

我當時把「多個上游」和「並行寫入」當成同一件事了。 圖上長得很像:
好幾條線指向同一個框。但一個是路徑的匯合,一個是資料的競爭。

差點就為了一個不存在的競爭狀況付出抽象的代價——而且會付兩次:
tutor_node 要改成只回傳新訊息,之後發現不對再改回來。

二、設計文件跟實作已經不一樣了,而我沒發現

設計文件的第 3 期寫著:

"submit" ─▶ grade ──┬─ 未全過 ───▶ tutor
                    └─ 全部通過 ─▶ set_problem ─▶ END

全部通過就直接出下一題。實作卻是:

"submit" ─▶ grade ──┬─ 未全過 ───▶ tutor
                    └─ 全部通過 ─▶ solved ─▶ tutor

記下已解,然後題目留在原地,交給導師講一句肯定。

我當時是有理由才改的,理由也寫在註解裡:一換題,那份「你過了」的判定就會被蓋掉,
學生什麼都沒看到;而且通過之後學生需要聽到一句肯定,不是只看到一塊綠色。

但我沒有回去更新設計文件,也沒在任何地方記下「這裡與文件不同」。
今天照著文件畫流程圖,畫到一半才發現圖跟程式碼對不上。

導讀筆記最後寫成這樣:

設計文件與實作不一致時,改的是文件還是實作? 這裡選擇改實作、並把理由寫進註解。
判準是:文件寫的是「怎麼做」,而做下去之後發現它違反了更上層的「為什麼」
(學生要看得到自己的成果)。上層的理由贏。

判斷本身我現在仍然認為是對的。錯的是沒有把這個分歧記在文件那一側——
註解只有讀到那段程式碼的人看得到,而看設計文件的人會被誤導。

三、我寫進文件的驗證指令,是錯的

筆記最後照慣例附了「想動手驗證的話」:

pytest tests\test_graph.py -k visiting 

寫完順手跑了一次:

75 deselected

一支都沒選到。 因為 visiting 只出現在 helper 函式 _visiting_graph 的名字裡,
而 -k 比對的是測試函式的名字。改成能用的版本之後才真的選到 14 支。

這件事小,但性質不小:文件裡的指令是可執行的東西,它也會壞。
而壞掉的文件比沒有文件糟——照著做不會動的時候,讀的人會懷疑自己,不會懷疑文件。

同樣的理由,筆記裡每一個 graph.py:158 這種行號我都回去對了一次。
對的方法是寫個小腳本,把文件裡所有 檔名:行號 抓出來、印出那一行:

graph.py:128 | "user_message": "",          # 消化掉,否則下一輪會重送同一句
tests/test_graph.py:578 | def test_intent_persists_in_the_checkpoint_and_must_be_resent():

抓出九個錯的。原因很單純:我這幾天在 tests/test_graph.py 中間插了新測試,
後面所有測試的行號都往下推了。而我引用的是插入之前的記憶。

行號會過期,這是導讀筆記這種東西的固有成本。所以第 1 期那份開頭就寫著
「對應 commit 0ff4597,行號以該 commit 為準」——今天這份也一樣,釘在 5b49a19。
釘住版本,行號就從「會爛的東西」變成「某個時間點的事實」。

導讀筆記到底在寫什麼

寫給三個月後的自己。三個月後我會記得 add_conditional_edges 怎麼用
(查文件就有),但不會記得:

  • 為什麼 route() 裡未知的 intent 是當場拋例外,而不是走預設路徑
  • 為什麼降級的條件是 not out.parsed_ok 而不是 not out.all_passed
  • 為什麼 source 的預設值是「推測」而不是「執行」

這些都是當時想過、選了一邊、而另一邊看起來也很合理的決定。
不寫下來的話,三個月後的我會看著它們想「這裡幹嘛寫這麼囉唆」,然後改掉。

所以每一節的最後一段都是「不這樣寫會怎樣」。那一段才是筆記的本體,
前面的「做了什麼」只是讓你找得到位置。

今天學到的

  1. 把重讀當成一個工序,不是收尾。 三件事都不是寫文件時「順便」發現的,
    是因為要把流程畫出來、要把行號指對,才被迫逐行重看。
  2. 圖上長得像,不代表是同一件事。 fan-in 與並行寫入。
  3. 改了實作就要回頭改文件,或至少記下分歧。 註解只服務讀程式碼的人。
  4. 文件裡的指令會壞,行號會過期。 前者要跑過,後者要釘 commit。

下一份是第 3 期(出題 agent、閉環讀黑板)。程式碼沒動,測試維持 327 支全綠。


上一篇
Day 25:把狀態放到行程活不過的地方
下一篇
Day 27
系列文
基於 Multi-Agent 協作之蘇格拉底式程式學習與自動化評測系統 共 27 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言