作者 | Kevin Lu
譯者| 彎月
出品 | CSDN(ID:CSDNnews)
自 GPT-4 發(fā)布以來,我們一直在嘗試讓其修改長篇的代碼文件。盡管它在解決復(fù)雜問題或從零開始創(chuàng)建復(fù)雜系統(tǒng)方面表現(xiàn)出色,但在向一個 200 行代碼的 Flask 服務(wù)器中插入日志時,它卻舉步維艱。然而,顯然后者更為實用。
我們經(jīng)常聽到的一種抱怨是:“ChatGPT 可以完成這項任務(wù),但你們的 Sweep.AI 卻不能”。這是因為 GPT-4 并不能一致地編輯長篇文件,它往往會在中途寫入“#Rest of the code”,或錯誤地復(fù)制一段代碼,而使用ChatGPT的人類可以輕松解決這個算法無法解決的問題。因此,我們不能簡單地通過從頭開始重寫文件的方式來修改文件。
以下是我們做過的所有讓 GPT-4 修改文件的嘗試,以及由于 GPT-4 未能正確格式化或計數(shù)而導(dǎo)致的成功和失敗。
版本 0:簡單地重寫整個文件
如前所述,完全重寫文件存在兩個主要問題:
1、對于超過 50 行的文件,GPT-4 最終會生成類似“#Rest of the code”的內(nèi)容。
2、文件太長。擁有 k 個令牌的文件將需要 k 個輸入令牌和 k 個輸出令牌。
3、GPT-4 會錯誤地復(fù)制代碼。它有時會刪除或添加額外的注釋或空白,或更改縮進(jìn)。
我們來看一個如何解決第一個問題的示例。
在本文中,我將使用以下簡短的 Flask 服務(wù)器實現(xiàn)作為我們正在編輯的文件的示例。出于簡潔考慮,我選擇了一個簡短的示例,因此對于這個特定示例,GPT-4 也許不會出現(xiàn)這些錯誤,但在較大的文件中經(jīng)常會出現(xiàn)類似的錯誤。
要求 GPT-4 添加日志,我們可能會得到以下內(nèi)容:
顯然,我們不能僅憑這段代碼創(chuàng)建拉取請求(PR)!我們必須撤銷所有“#Rest of the code”的修改。
版本 1:使用 difflib 修復(fù)“rest of the code”救命稻草 difflib
最簡單的解決方案似乎是檢查兩個文件的差異,并回滾所有帶有“Rest of the code”、“Remaining of the code”的部分。
上面示例的差異如下所示:
現(xiàn)在,我們只需撤銷每個刪除后面帶一個形如 + # Rest of test 注釋的部分。具體來說,我們使用以下方法檢查這些注釋:
在這個示例中,它解決了問題:我們最終得到了我們所期望的結(jié)果,即在每個函數(shù)的開頭有一個打印語句。
限制
不幸的是,這個差異回滾系統(tǒng)的能力仍然相當(dāng)有限。首先,有時 GPT-4 會寫下諸如“More unti tests here”,“Complete the implementation”,“...”等注釋,有無限多可能。其次,有些情況下,差異算法無法找到具體應(yīng)當(dāng)被替換的行。
例如,讓我們要求 GPT-4 添加一個刪除端點(diǎn),它的回應(yīng)是:
但差異算法返回的內(nèi)容如下:
回滾該差異只會產(chǎn)生:
顯然,這完全不是我們所期望的。在 Sweep.AI 的最初幾周中,由于這個問題,Sweep 會隨機(jī)刪除大段的代碼。
也許我們可以編寫一個更智能的差異算法來捕捉這些討厭的“Rest of code”注釋。但即使如此,從算法角度來看,也不可能確定GPT-4的意圖是要刪除一切并添加新的 delete_task 端點(diǎn),還是要將 update_task 端點(diǎn)替換為 delete_task 端點(diǎn)。
根本的問題在于,我們無法確定# Rest of code的意思是替換直到 update_task 的所有代碼,還是僅僅是替換 create_task 端點(diǎn)。我們需要不同的輸入。我們需要讓 GPT-4 指出每個替換和修改標(biāo)簽的覆蓋范圍。
版本 2:以行為單位修改或復(fù)制思路
如果可以讓 GPT-4 編寫一組具體的替換說明,我們就可以用新代碼進(jìn)行替換。最初,我們采用了以下格式:
這段指令的意思是使用新的代碼替換從 i(包含)到 j(不包含)的行。
通常,我們更喜歡從 GPT-4 獲得基于 XML 的響應(yīng),因為它們:
- 可以使用正則表達(dá)式輕松解析。我們的模式通常類似于:<update_lines start="(??<start>d+)" end="(?P<end>d+)">(?P<code>.*?)</update_lines>。
- 在訓(xùn)練數(shù)據(jù)(從網(wǎng)上獲取的數(shù)據(jù))中很常見,因此大型語言模型非常了解它們。
- 可以處理引號和換行符,這些符號在代碼中很常見。XML 不像 JSON 那樣需要對符號進(jìn)行轉(zhuǎn)義。此外,XML 的結(jié)束標(biāo)記通常很少出現(xiàn)。
- 大型語言模型很難破壞 XML 格式。
例如,向上面的 Flask API 端點(diǎn)添加更多示例數(shù)據(jù),GPT-4 會給出:
而插入新的代碼,比如刪除端點(diǎn),GPT-4 會給出:
GPT-4 無法復(fù)制行號
當(dāng)然,我們在提示中添加了代碼的行號,以幫助模型正確計數(shù)。然而,即便如此,GPT-4 也會復(fù)制不正確的行號。這可能導(dǎo)致代碼缺失一行或多出一行,如下所示,缺少 return 語句:
或者產(chǎn)生重復(fù)的代碼行,如下所示:
我們嘗試了一些辦法,但都無法很好地解決這個問題:
1、刪除重復(fù)行:如果出現(xiàn)重復(fù)較小的行,我們將嘗試去除重復(fù)行。不幸的是,這并不完全可的,有時會錯誤刪除有意重復(fù)的代碼。而且它無法處理缺失行的情況。
2、通過另一個模型運(yùn)行以修復(fù)代碼:我們將代碼輸入到 GPT-3.5-16k 中,以驗證更改并修復(fù)應(yīng)該修復(fù)的內(nèi)容。不幸的是,這會導(dǎo)致復(fù)制中的隨機(jī)錯誤,并偶爾出現(xiàn)隨機(jī)的“#Rest of code”。所以這條路也行不通。
我們還嘗試了其他方法,但感覺不太自然,即從文件中復(fù)制舊的代碼行,然后自然地編寫剩下的部分,如下所示:
但同樣會受到錯誤行號的影響。
版本 3:aider diff
這個時候,我們碰巧看到了 aider 創(chuàng)建者的博客文章,aider 是類似于 Sweep 的工具,但是它在本地運(yùn)行。Aider 要求 GPT-4 生成以下格式的搜索和替換對:
然后只需在代碼中搜索原始代碼塊,并用新代碼塊替換。例如,為了生成更多的測試數(shù)據(jù),它可能生成如下內(nèi)容:
這種新方法在我們以前的嘗試中效果明顯更好,我認(rèn)為主要原因是:
1、對于 LLM 來說,復(fù)制代碼比選擇正確的行號要容易得多。
2、一旦代碼被復(fù)制到ORIGINAL代碼塊中,Sweep 就可以非常容易地修改代碼,因為 ORIGINAL 原始代碼更接近 GPT-4 編寫的代碼的地方,并且可以用作參考。很有可能,位置嵌入減少了 LLM 在修改代碼塊過程中的噪聲。
這種格式與 git 合并沖突的格式相似,這可能是 GPT-4 的訓(xùn)練數(shù)據(jù)的一部分。
然而,我們?nèi)匀挥幸恍﹩栴}:
- 可能無法正確地復(fù)制 ORIGINAL 代碼。
○最初,我們考慮構(gòu)建一個模糊匹配算法。然后,我們構(gòu)建了 V4 來進(jìn)一步解決這個問題。
- ORIGINAL 代碼塊可能會多次出現(xiàn)在代碼中。
○默認(rèn)情況下,我們會匹配第一個項。
○我們還提示 Sweep 在 ORIGINAL 代碼塊前后多復(fù)制幾行以消除歧義。
○此外,通常不建議在多個地方重復(fù)使用中等大小的代碼塊,而是應(yīng)該使用輔助函數(shù)。
- 在重新編寫較長的部分時,它仍然偶爾會寫入“#Rest of code”。
○我們提示 GPT-4 進(jìn)行多個小的更改,而不是較大的更改。
- 代碼仍然太長。
○對于超過 600 行的文件,我們會要求 GPT-4 一次處理 400 行代碼。由此產(chǎn)生了一些與上下文相關(guān)的問題,但這解決了目前的問題。有關(guān)此問題的更多信息,請參見下文。
版本 4:搜索并替換
我們目前的算法是在 Aider diff 的基礎(chǔ)上進(jìn)行了一些擴(kuò)展。主要問題是,對于中等大小的文件,Sweep 經(jīng)常會復(fù)制錯誤的行。
Aider diff 存在的問題
例如,如果要求 Sweep 向端點(diǎn)添加日志:
此處,ORIGINAL 代碼塊中的 create_task 被無意間更改為 start_task。本質(zhì)上是 GPT-4 錯誤地復(fù)制了行,然后在錯誤復(fù)制的行上應(yīng)用了轉(zhuǎn)換。
更準(zhǔn)確地說,GPT-4 本來想把子字符串 S 替換成 R(S),其中 R: str → str 是需要進(jìn)行的變換。但是,它生成了 S',然后替換成了 R(S')。這就導(dǎo)致 S 被替換成了 R(S'),這經(jīng)常會導(dǎo)致代碼無法編譯,或者導(dǎo)致不可預(yù)見的錯誤。
aider diff 的改進(jìn)
一個解決方案是更早地開始流式傳輸,即使用 200 行的塊而不是 400 行的塊,但這會導(dǎo)致更多的問題,如算法缺少上下文、性能較差和成本較高。
最終我們的解決方案是分別生成 S 和 R(S)。首先讓 GPT-4 生成 S',然后通過模糊匹配,在代碼中用 S' 搜索 S。然后要求 GPT-4 在 S 上執(zhí)行相應(yīng)的變換,這樣就生成了 R(S)。
具體而言,新算法執(zhí)行以下操作:
1、生成一系列的小段代碼:S'1, S'2, ..., S'n,供GPT-4編輯,然后使用模糊匹配算法,找到正確的行:S1, S2, ..., Sn。
a.如果模糊匹配對于某個 S'i 產(chǎn)生的相似度分?jǐn)?shù)過低(< 50%),則拋棄。未來也可以向 GPT-4 重新提示該問題。
2、然后將真正的代碼片段發(fā)給 GPT-4 進(jìn)行編輯。
因此,我們會要求 GPT-4 生成類似于以下內(nèi)容:
此時生成省略號(...)是允許的,因為我們的匹配算法通常可以正確匹配代碼片段。然后,我們會在代碼庫中找到真正的代碼片段,并呈現(xiàn)給 GPT-4 進(jìn)行編輯,如下所示:
然后,我們會回復(fù)以下內(nèi)容,要求 GPT-4 進(jìn)行編輯:
這樣可以確保不會出現(xiàn)意外編輯,比如將變量從 create_task 重命名為 start_task。
其他障礙
以下是我們遇到的其他不太重要的障礙:
- 格式錯誤:我們設(shè)置了一個退避系統(tǒng),可以再次提示 GPT-4 以更高的準(zhǔn)確度以正確的格式提供響應(yīng)。
- 縮進(jìn):GPT-4 往往會取消縮進(jìn)代碼,這會使 Python/ target=_blank class=infotextkey>Python 代碼無法解析。我們通過匹配原始縮進(jìn)來修復(fù)這個問題,根據(jù) ORIGINAL 代碼塊和原始文件的匹配部分之間的縮進(jìn)差異來進(jìn)行匹配。
盡管我們解決了大部分問題,但仍然存在一些文件太長的問題。我們自己的代碼庫中就有多個超過 1000 行的文件。
- 每次流式傳輸 400 行只是一個權(quán)宜之計,但并不能完全解決問題,因為它會分割代碼的語義。此外,模型有時不會修改代碼的任何部分,有時會修改代碼的多個部分。在沒有文件其余部分的上下文的情況下,修改文件的一部分非常困難。
- 對于 Python,我們建立了基于實體的編輯。最近,我們建立了一個用于更好地理解 Python 代碼的庫級別的調(diào)用圖系統(tǒng)。我們在規(guī)劃階段使用它,讓 LLM 決定要編輯文件的哪個類或函數(shù)。
結(jié)論
讓 GPT-4 正確修改代碼是一場艱苦的戰(zhàn)斗,很容易出現(xiàn)各種錯誤。自發(fā)布以來,我們一直在與這些錯誤作斗爭,但只能緩解常見的錯誤。
原文鏈接:https://docs.sweep.dev/blogs/gpt-4-modification