LLM Agent 的 EDIT 工具替代方案
原文由 Salvatore Sanfilippo 于 發布,訂閱此部落格
編按:這當然早就有人做過了!我本來就沒什麼懷疑,只是在 Twitter 上又被大家確認了一次 :) 不過,還是繼續看下去吧:最後提到的 CRC32 折衷方案是個有趣的取捨,而且這個討論本身就很值得聊聊。
我現在正在為我的 DS4 專案打造一個 agent。在本機推論的環境下,token 非常吃緊,是一場分秒必爭的最佳化戰場。讓我有點意外的是,現在大家都在用的 EDIT 工具,竟然會強迫 LLM 一字不漏地把舊版文字再輸出一次。這種 CAS(check and set)模式,也就是用 EDIT old="foo" new="bar" 的方式來操作,之所以必要,是因為常常會發生編輯衝突(使用者可能同時也在編輯,或是切換到不同的分支等等),也因為 LLM 本來就可能會幻覺,以為某一行是某個內容。
這基本上意味著,光靠行號是非常脆弱的:像「把第 22 行改成 new="foobar"」這樣的做法並不可靠。但我也不想讓本機的 LLM 每次都浪費 token 去重寫舊文字,更何況有時候舊文字裡有很多特殊字元和空白,模型很容易寫錯;一旦寫錯,工具就會執行失敗,逼得 LLM 又得重做一次同樣的編輯。所以我(重新)設計了一個以 tag 為基礎的 EDIT 工具,依然維持 CAS 風格,但在 token 使用上更有效率。
READ 和 SEARCH 工具會回傳像這樣的內容:
10:Q8fA int count = 10;
11:rA3_ if (count > limit) {
12:Kq9z count = limit;
13:PX0b }可以看到有行號和 tag。tag 是 4 個字元,平均約 2.5 個 LLM token,代表該行的校驗和。現在 LLM 就可以這樣進行編輯:
{
"tool": "edit",
"path": "/tmp/example.c",
"line": 10,
"tag": "Q8fA",
"new": "int count = 11;"
}或是像這樣一次編輯多行:
{
"tool": "edit",
"path": "/tmp/example.c",
"lines": "11:rA3_\n12:Kq9z\n13:PX0b",
"new": "if (count > limit)\n return limit;"
}省下的 token 相當可觀,尤其當 agent 要刪除大量文字時,但在一般情況下也很有感。不過,因為多了行號和 tag,確實會有一些額外開銷。這中間存在一些取捨,也許 tag 應該用 8 個字元、並把行號也納入雜湊計算,還得仔細檢視碰撞機率和 tokenization 來評估到底能賺多少,但我蠻喜歡 line:tag 這種格式,因為 LLM 後續常常能以各種方式利用行號資訊,例如在接下來的工具呼叫中取得範圍。也許 tag 還有其他利用方式,像是:這一行還是不是 dj4_?
有趣的是,DeepSeek v4 Flash 能夠非常有效地使用這個工具,看起來對它來說相當自然。雖然我沒有精確量化省了多少,但在實際使用中,我發現編輯速度快了很多,甚至也更可靠了。
另一個替代方案是每次只回傳整個檔案的 CRC32(基本上就是把 tag 變成檔案層級的 tag,即使是部分讀取也一樣)。這樣我們就只需要用行號加上 CRC 來操作,編輯時只要指定 11,12,13,14 就好。當然 token 用得更少。不過,這會迫使我們每次都得重新計算整個檔案的 CRC32,但對於大小合理的檔案來說,成本還算便宜。但這個做法也有其限制:就算發生的是*不相關*的變更,編輯也會失敗,所以這裡有很強烈的取捨。替整個檔案的方法說句公道話,它可以讓我們用 10:23 這種方式來指定範圍,這是一大優勢。
我感覺只有透過足夠的實務驗證,在多個工作階段中用 ds4-agent 實際測試這兩種系統,才能判斷哪一個更好。就目前而言,也許先提供一個可以切換編輯模式的命令列選項,是最正確的第一步。
隨機一篇部落格
留言
登入後參與討論