跟優柔寡斷的 AI 寫程式代理玩耍
我用 AI 代理來幫我修 GoAWK 裡一些不太簡單的 bug,算是有些成效。不過它有時真的非常優柔寡斷(Mitchell Hashimoto 前幾天才在推文中提到這點)。
我問了它下面這個問題(我用的是 Claude Opus 4.6,搭配 GoLand IDE 裡的 GitHub Copilot 外掛):
GoAWK 執行 Awk 程式
BEGIN { a["x"]=1; for (NR in a) print NR, a[NR] }時印出"0\n0\n",而不是"x 1\n",為什麼?
它在幾段話內就診斷出了問題,比我自己還快。它指出,我把這些特殊變數存成原生的 Go int,所以會遺失原本的字串形式。
接著它就開始嘗試修復。在接下來的幾分鐘裡,我愈看愈覺得好笑,它一共提出了 7 種截然不同的修復方案,而且在這些方案之間反覆搖擺了不下 25 次。
以下是它想出來的選項:
- 選項 A:為特殊變數保留字串形式
- 選項 B:將特殊變數存成 value 型別
- 選項 C:當特殊變數被設為字串時,儲存字串覆寫值
- 選項 D:專門修正 ForIn 這個 opcode
- 選項 E:在側邊欄位儲存原始值
- 選項 F:只把 lineNum(NR)和 fileLineNum(FNR)改成 value
- 選項 G:新增一個 value 型別的特殊覆寫 map
它每隔幾秒就會說「其實,最簡單的修法是……」或「但不對,真正的問題是……」。就這樣沒完沒了,直到我把它取消掉。
公平來說,它最常提的是選項 B(26 次裡有 11 次),而那也幾乎可以確定才是正確的解法。我已經在這個 PR 中實作了。
為了讓大家參考,我在下方附上對話紀錄的節錄版本。內容經過大幅刪減,只保留它提出另一種選項的時刻。我用粗體標示出來,像這樣:選項 A。
看這個 bug,問題出在 ForIn opcode 的處理器裡……
但等一下,讓我更仔細看看實際的 ForIn 實作……
現在我可以清楚看到這個 bug 了……
這證實了這個 bug。以下是完整的狀況:……
根本原因是像 NR、NF、FNR 等特殊變數是以原生的 Go int 型別儲存,天生就會遺失字串形式。……
修正必須在 setSpecial 和 getSpecial 裡進行——我們需要為這些數值型的特殊變數保留字串形式。…… 選項 A
不過,有個更簡單、更聚焦的做法:這個問題特別跟特殊變數的 for-in 有關。但實際上,任何像 NR = “x” 這樣的賦值都會有一樣的問題——字串會遺失。讓我想想最好的修法。
最乾淨的修法是把這些特殊變數存成 value 型別……但那會是個大規模的重構。選項 B
一個更精準的手術式修法:當數值型的特殊變數透過字串被設定時,儲存一個「字串覆寫值」。但那有點取巧。選項 C
其實,讓我換個角度想想。……
正確的修法是把值存成 value 型別,只在內部需要時才取出 int。…… 選項 B
其實,保留現有架構下最簡單的修法是:針對 for-in 迴圈,我們可以改變做法。選項 D
但不對,真正的問題更廣泛——任何對數值型特殊變數的字串賦值都應該保留字串。
讓我想個最小但正確的修法。最聚焦的做法:把數值型特殊變數的儲存從 int 改成 value,並更新少數幾個讀寫它們的地方。…… 選項 B
這比我想要做的重構還要大。讓我想個更簡單的做法。……
其實,最務實、最小的修法是在 ForIn 層級處理。…… 選項 D
其實,我覺得正確的修法就是把這些數值型的特殊變數改成以 value 型別來儲存。…… 選項 B
全部都改是有風險的。讓我想個更簡單的做法。
其實,最簡單且正確的修法是:在 setSpecial 裡,對於數值型的特殊變數,把原始值存在一個側邊欄位,這樣 getSpecial 在符合條件時就能回傳它。…… 選項 E
好,讓我更仔細想想。……
這變得有點複雜了。讓我採取更簡單的做法:只把 lineNum 和 fileLineNum 改成 value 型別,因為它們是最有可能成為 for-in 目標的變數。…… 選項 F
等等,其實還有個更簡單的做法。……
好,讓我採取務實的做法。針對這個特定 bug 最乾淨、最小的修法:把數值型的特殊變數(lineNum、fileLineNum、matchLength、matchStart、argc、numFields)改成以 value 型別儲存。…… 選項 B
其實等等,numFields 在內部大量被當作 int 使用。讓我試試不同的方法。……
其實,這裡有個最簡單且正確的做法:對於每個數值型的特殊變數,同時儲存 int(供內部使用)和 value(供 AWK 層級的呈現)。…… 選項 E
但那還是要改很多地方。讓我想得更簡單一點……
能處理這個特定案例的最簡單正確修法是:在 setSpecial 裡,對於數值型的特殊變數,如果值是字串(無法轉成有意義的數字、或是 typeStr),就直接儲存一個 value 來保留字串。…… 選項 B
好,我覺得最乾淨、最小的修法是:在 interp 結構中新增一個 specialOverrides map[int]value 欄位。…… 選項 G
其實等等——那是把事情複雜化了。讓我重新檢視這個特定案例。……
讓我直接來做正確的修法。我會把數值型的特殊變數改成以 value 型別而非 int 來儲存。…… 選項 B
讓我採取一個不同、簡單得多的做法。我只要透過新增一個 specialValues map,讓 setSpecial 和 getSpecial 能正確處理 value 就好…… 選項 G
其實,不需要動到內部邏輯的最簡單做法是:只要改 ForIn 的處理器就好。……但問題是 getSpecial 總是從 int 重建……
好,讓我直接做正確的修法。我會採用「特殊值覆寫」map 的做法:…… 選項 G
好,其實讓我重新考慮一下做法。……最乾淨、符合這個想法的做法是:我只要把數值型的特殊變數從 int 改成 value。…… 選項 B
等等,其實,讓我再務實一點。做法是:保留 int 欄位供內部使用,但另外新增一個平行的 value 欄位來追蹤 AWK 層級的值。…… 選項 E
其實不對,那是雙重記帳。讓我乾脆硬著頭皮把相關欄位改成 value 型別。…… 選項 B
嗯,其實,讓我退一步想想什麼是最簡單且正確的修法。最精簡的修法:只處理 for-in 的情況。…… 選項 D
不對等等,問題是 setSpecial 會截斷成 int,所以字串值無論如何都會遺失。好,這是我的計畫。我只把 lineNum 和 fileLineNum(最有可能用在 for-in 迴圈的候選者——也就是 NR 和 FNR)從 int 改成 value。…… 選項 F
其實,讓我乾脆全部都好好修正。這是我的計畫:…… 選項 B
讓我只專注在 lineNum 和 fileLineNum,也就是最有可能被用在 for-in 裡的變數(NR 和 FNR)。選項 F
其實,使用者的 bug 回報特別是關於 NR。讓我把所有數值型的特殊變數都修好,但對於大量使用的那些,採取更簡單的做法。選項 B(大致上)
其實,你知道嗎,讓我乾脆採取能修好所有情況的最簡單做法:新增一個 map[int]value 欄位來儲存使用者設定的特殊變數覆寫值。…… 選項 G
有趣!已經有一個 specialOverrides 欄位以及對它的 delete 呼叫。這些在我修改之前就已經存在了。……
到這裡它開始變得非常混亂。它已經修改了程式碼,加入了 specialOverrides map,卻又忘了自己做過這件事。到這個時候,我就直接把它取消掉了。
整段對話裡出現了 19 次「其實」。這些機器真的很厲害,但我可不希望有個這麼優柔寡斷的朋友!
更廣泛來說,我很想知道大型語言模型和代理究竟是怎麼知道該何時停下來的。對我而言,token 預測器會一直預測下一個 token,一點也不讓人意外……
隨機一篇部落格
留言
登入後參與討論