英泰移动通信学校
400-6160-599
咨询热线:
教育引领未来
实时热点

让 GPT-4 修改文件,真的太难了

发表时间:2023-11-06 09:18

自 GPT-4 发布以来,我们一直在尝试让其修改长篇的代码文件。尽管它在解决复杂问题或从零开始创建复杂系统方面表现出色,但在向一个 200 行代码的 Flask 服务器中插入日志时,它却举步维艰。然而,显然后者更为实用。

我们经常听到的一种抱怨是:“ChatGPT 可以完成这项任务,但你们的 Sweep.AI 却不能”。这是因为 GPT-4 并不能一致地编辑长篇文件,它往往会在中途写入“#Rest of the code”,或错误地复制一段代码,而使用ChatGPT的人类可以轻松解决这个算法无法解决的问题。因此,我们不能简单地通过从头开始重写文件的方式来修改文件。

以下是我们做过的所有让 GPT-4 修改文件的尝试,以及由于 GPT-4 未能正确格式化或计数而导致的成功和失败。

01 版本 0:简单地重写整个文件

如前所述,完全重写文件存在两个主要问题:

1、对于超过 50 行的文件,GPT-4 最终会生成类似“#Rest of the code”的内容。

2、文件太长。拥有 k 个令牌的文件将需要 k 个输入令牌和 k 个输出令牌。

3、GPT-4 会错误地复制代码。它有时会删除或添加额外的注释或空白,或更改缩进。

我们来看一个如何解决**个问题的示例。

在本文中,我将使用以下简短的 Flask 服务器实现作为我们正在编辑的文件的示例。出于简洁考虑,我选择了一个简短的示例,因此对于这个特定示例,GPT-4 也许不会出现这些错误,但在较大的文件中经常会出现类似的错误。

要求 GPT-4 添加日志,我们可能会得到以下内容:

显然,我们不能仅凭这段代码创建拉取请求(PR)!我们必须撤销所有“#Rest of the code”的修改。

02 版本 1:使用 difflib 修复“rest of the code”

救命稻草 difflib

最简单的解决方案似乎是检查两个文件的差异,并回滚所有带有“Rest of the code”、“Remaining of the code”的部分。

上面示例的差异如下所示:

现在,我们只需撤销每个删除后面带一个形如 + # Rest of test 注释的部分。具体来说,我们使用以下方法检查这些注释:

在这个示例中,它解决了问题:我们最终得到了我们所期望的结果,即在每个函数的开头有一个打印语句。

限制

不幸的是,这个差异回滚系统的能力仍然相当有限。首先,有时 GPT-4 会写下诸如“More unti tests here”,“Complete the implementation”,“...”等注释,有无限多可能。其次,有些情况下,差异算法无法找到具体应当被替换的行。

例如,让我们要求 GPT-4 添加一个删除端点,它的回应是:

但差异算法返回的内容如下:

回滚该差异只会产生:

显然,这完全不是我们所期望的。在 Sweep.AI 的最初几周中,由于这个问题,Sweep 会随机删除大段的代码。

也许我们可以编写一个更智能的差异算法来捕捉这些讨厌的“Rest of code”注释。但即使如此,从算法角度来看,也不可能确定GPT-4的意图是要删除一切并添加新的 delete_task 端点,还是要将 update_task 端点替换为 delete_task 端点。

根本的问题在于,我们无法确定# Rest of code的意思是替换直到 update_task 的所有代码,还是仅仅是替换 create_task 端点。我们需要不同的输入。我们需要让 GPT-4 指出每个替换和修改标签的覆盖范围。

03 版本 2:以行为单位修改或复制

思路

如果可以让 GPT-4 编写一组具体的替换说明,我们就可以用新代码进行替换。最初,我们采用了以下格式:

这段指令的意思是使用新的代码替换从 i(包含)到 j(不包含)的行。

通常,我们更喜欢从 GPT-4 获得基于 XML 的响应,因为它们:

可以使用正则表达式轻松解析。我们的模式通常类似于:<update_lines start="(??<start>\d+)" end="(?P<end>\d+)">(?P<code>.*?)</update_lines>。

在训练数据(从网上获取的数据)中很常见,因此大型语言模型非常了解它们。

可以处理引号和换行符,这些符号在代码中很常见。XML 不像 JSON 那样需要对符号进行转义。此外,XML 的结束标记通常很少出现。

大型语言模型很难破坏 XML 格式。

例如,向上面的 Flask API 端点添加更多示例数据,GPT-4 会给出:

而插入新的代码,比如删除端点,GPT-4 会给出:

GPT-4 无法复制行号

当然,我们在提示中添加了代码的行号,以帮助模型正确计数。然而,即便如此,GPT-4 也会复制不正确的行号。这可能导致代码缺失一行或多出一行,如下所示,缺少 return 语句:

或者产生重复的代码行,如下所示:

我们尝试了一些办法,但都无法很好地解决这个问题:

1、删除重复行:如果出现重复较小的行,我们将尝试去除重复行。不幸的是,这并不完全可的,有时会错误删除有意重复的代码。而且它无法处理缺失行的情况。

2、通过另一个模型运行以修复代码:我们将代码输入到 GPT-3.5-16k 中,以验证更改并修复应该修复的内容。不幸的是,这会导致复制中的随机错误,并偶尔出现随机的“#Rest of code”。所以这条路也行不通。

我们还尝试了其他方法,但感觉不太自然,即从文件中复制旧的代码行,然后自然地编写剩下的部分,如下所示:

但同样会受到错误行号的影响。

04 版本 3:aider diff

这个时候,我们碰巧看到了 aider 创建者的博客文章,aider 是类似于 Sweep 的工具,但是它在本地运行。Aider 要求 GPT-4 生成以下格式的搜索和替换对:

然后只需在代码中搜索原始代码块,并用新代码块替换。例如,为了生成更多的测试数据,它可能生成如下内容:

这种新方法在我们以前的尝试中效果明显更好,我认为主要原因是:

1、对于 LLM 来说,复制代码比选择正确的行号要容易得多。

2、一旦代码被复制到ORIGINAL代码块中,Sweep 就可以非常容易地修改代码,因为 ORIGINAL 原始代码更接近 GPT-4 编写的代码的地方,并且可以用作参考。很有可能,位置嵌入减少了 LLM 在修改代码块过程中的噪声。

这种格式与 git 合并冲突的格式相似,这可能是 GPT-4 的训练数据的一部分。

然而,我们仍然有一些问题:

可能无法正确地复制 ORIGINAL 代码。

最初,我们考虑构建一个模糊匹配算法。然后,我们构建了 V4 来进一步解决这个问题。

ORIGINAL 代码块可能会多次出现在代码中。

默认情况下,我们会匹配**个项。

我们还提示 Sweep 在 ORIGINAL 代码块前后多复制几行以消除歧义。

此外,通常不建议在多个地方重复使用中等大小的代码块,而是应该使用辅助函数。

在重新编写较长的部分时,它仍然偶尔会写入“#Rest of code”。

我们提示 GPT-4 进行多个小的更改,而不是较大的更改。

代码仍然太长。

○对于超过 600 行的文件,我们会要求 GPT-4 一次处理 400 行代码。由此产生了一些与上下文相关的问题,但这解决了目前的问题。有关此问题的更多信息,请参见下文。

05 版本 4:搜索并替换

我们目前的算法是在 Aider diff 的基础上进行了一些扩展。主要问题是,对于中等大小的文件,Sweep 经常会复制错误的行。

Aider diff 存在的问题

例如,如果要求 Sweep 向端点添加日志:

此处,ORIGINAL 代码块中的 create_task 被无意间更改为 start_task。本质上是 GPT-4 错误地复制了行,然后在错误复制的行上应用了转换。

更准确地说,GPT-4 本来想把子字符串 S 替换成 R(S),其中 R: str → str 是需要进行的变换。但是,它生成了 S',然后替换成了 R(S')。这就导致 S 被替换成了 R(S'),这经常会导致代码无法编译,或者导致不可预见的错误。

aider diff 的改进

一个解决方案是更早地开始流式传输,即使用 200 行的块而不是 400 行的块,但这会导致更多的问题,如算法缺少上下文、性能较差和成本较高。

最终我们的解决方案是分别生成 S 和 R(S)。首先让 GPT-4 生成 S',然后通过模糊匹配,在代码中用 S' 搜索 S。然后要求 GPT-4 在 S 上执行相应的变换,这样就生成了 R(S)。

具体而言,新算法执行以下操作:

1、生成一系列的小段代码:S'1, S'2, ..., S'n,供GPT-4编辑,然后使用模糊匹配算法,找到正确的行:S1, S2, ..., Sn。

a.如果模糊匹配对于某个 S'i 产生的相似度分数过低(< 50%),则抛弃。未来也可以向 GPT-4 重新提示该问题。

2、然后将真正的代码片段发给 GPT-4 进行编辑。

因此,我们会要求 GPT-4 生成类似于以下内容:

此时生成省略号(...)是允许的,因为我们的匹配算法通常可以正确匹配代码片段。然后,我们会在代码库中找到真正的代码片段,并呈现给 GPT-4 进行编辑,如下所示:

然后,我们会回复以下内容,要求 GPT-4 进行编辑:

这样可以确保不会出现意外编辑,比如将变量从 create_task 重命名为 start_task。

06 其他障碍

以下是我们遇到的其他不太重要的障碍:

格式错误:我们设置了一个退避系统,可以再次提示 GPT-4 以更高的准确度以正确的格式提供响应。

缩进:GPT-4 往往会取消缩进代码,这会使 Python 代码无法解析。我们通过匹配原始缩进来修复这个问题,根据 ORIGINAL 代码块和原始文件的匹配部分之间的缩进差异来进行匹配。

尽管我们解决了大部分问题,但仍然存在一些文件太长的问题。我们自己的代码库中就有多个超过 1000 行的文件。

每次流式传输 400 行只是一个权宜之计,但并不能完全解决问题,因为它会分割代码的语义。此外,模型有时不会修改代码的任何部分,有时会修改代码的多个部分。在没有文件其余部分的上下文的情况下,修改文件的一部分非常困难。

对于 Python,我们建立了基于实体的编辑。最近,我们建立了一个用于更好地理解 Python 代码的库级别的调用图系统。我们在规划阶段使用它,让 LLM 决定要编辑文件的哪个类或函数。

07 结论

让 GPT-4 正确修改代码是一场艰苦的战斗,很容易出现各种错误。自发布以来,我们一直在与这些错误作斗争,但只能缓解常见的错误。

分享到: