如何一键将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就变成了一个比较复杂的过程。
阅读全文