如何一键将LLM生成代码无缝整合进源代码?
摘要:背景 在大语言模型越来越火的今天,越来越多的应用场景开始使用大语言模型来解决实际问题。而辅助编程可以算是大语言模型应用得最成功的场景之一了。早先的时候,更多使用的还是代码补全的能力,但是现在,各家产品都开始支持Chat和Agent的能力了。
背景
在大语言模型越来越火的今天,越来越多的应用场景开始使用大语言模型来解决实际问题。而辅助编程可以算是大语言模型应用得最成功的场景之一了。早先的时候,更多使用的还是代码补全的能力,但是现在,各家产品都开始支持Chat和Agent的能力了。
之前一直有个疑问,生成的代码明明只是片段,也没有一个很好的规则能直接定位到源文件的位置,甚至有些生成的代码和现有代码没有任何重叠的部分,那这些代码是怎么精准地合并到源代码中的呢?今天就带着大家一起看一下,在Chat、Agent的场景下,如何将生成的代码精准且快速地合并到现有的代码文件中。
从全量重写到Planning+Applying的转变
一个最暴力的方法就是,每次在Chat/Agent里,都让模型生成完整的代码,然后直接全量替换,这样就不用考虑代码合并的问题了。
但是,这种方法的缺点也很明显:
成本高:每次模型都需要输出大量的代码,如果源文件有1000行,那每次模型生成代码就需要输出1000行,这样一次就用掉了大量的token,成本是不可接受的。
速度慢:大模型的输出模式是一个一个token地输出,如果源文件有1万token,那每次模型生成代码就需要走1万次的Decoding的过程,这是极其耗时的。
显然,现在的产品都没有使用这种模式,我们在Chat/Agent界面中看到的都是代码片段。所以,我们先将这个问题进行拆解一下,分成两个步骤:
Planning:大模型先生成代码片段
Applying:将代码片段合并到原始代码中
可能的代码片段形式
接下来我们会从这个原始代码出发,讲一下几种可能的代码片段形式。
import json
def main(args):
# show a greeting
print("Hello!")
return
if __name__ == '__main__':
main("")
Code Diff Patch
--- a/greeting.py
+++ b/greeting.py
@@ -10,4 +10,4 @@
def main(args):
# show a greeting
- print("Hello!")
+ print("Goodbye!")
return
让模型生成完整的Code Diff Patch,然后直接通过Patch程序合并到原始代码中。
这样做的好处是,Applying的过程非常简单,只需要调用Patch程序合并即可。
缺点也很明显,Patch程序对于输入的格式要求非常严格,如果模型生成的Code Diff格式不正确,那合并就会失败。大语言模型对于数字的敏感程度不够,很容易产生幻觉,插入的位置有时候即使是偏了一行,合并出来的代码也就不可用了。
Unified Diff
@@ ... @@
def main(args):
# show a greeting
- print("Hello!")
+ print("Goodbye!")
return
这个Diff的格式来自于Aider。和完整的Diff相比,Unified Diff删除了位置信息,只保留了代码的修改。合并的方式也很简单,只要将以空格和减号开始的行,替换成以空格和加号开始的行即可。这样,就可以有效避免模型关于数字的幻觉了。
这两种代码片段有一个共同的缺点,就是他们的数据都属于不常见的类型。code diff更多还是我们平时用git diff命令看一眼用的,并不会将其存成文件留存下来,模型自然也就很少见到这种数据,所以也就很难准确生成类似的数据了。模型更喜欢的还是生成一段完整的代码片段。
Lazy Format
# ... existing code ...
def main(args):
# show a greeting
print("Goodbye!")
return
# ... existing code ...
相信如果大家让模型生成过代码,就会发现模型生成代码的时候会更偏向于这种Lazy Format的形式。就是用# ... existing code ...来表示代码的上下文,然后只生成中间相关的代码。
这种形式的好处是,模型可以更专注于生成代码,而不需要受到代码变更的干扰,也就是能提升生成的代码的准确性。
缺点也很明显,Applying就变成了一个比较复杂的过程。
