AI 的编程能力已经得到了证明,但还并不完美。近日,BuzzFeed 的资深数据科学家 Max Woolf 发现,如果通过提示词不断要求模型写更好的代码(write better code),AI 模型还真能写出更好的代码!
这篇文章在网络上引发了热议,著名 AI 科学家在看完这篇文章中更是发出了 matters 三连:迭代很重要,提示词设计很重要,代码执行能力很重要。他表示:「一些更简单的算法优化从未被考虑,同时一些过度的优化技术却又被过早引入了。」
Woolf 写了一篇深度博客介绍自己的发现,并分析了这种现象的原因。文中相关实验的代码也已发布在 GitHub。
如果不断要求 LLM 写更好的代码,它能写更好吗?
2023 年 11 月,OpenAI 为 ChatGPT 添加了使用 DALL-E 3 生成图像的功能。之后一段时间,出现了一类短暂的 meme:用户为 LLM 提供一张基础图像,并不断要求模型「使其更 X」,其中 X 可以指代任何东西。尽管这个潮流很快就熄火了,因为这些图像都非常相似且无趣,即不管使用什么起始图像和提示词,所有样本都会最终收敛成某种宇宙感十足的东西。尽管这个流行昙花一现,但学术界的兴趣要持久得多,他们想知道:为什么这样一个没多大意义且含义模糊的提示词能对最终图像产生显而易见的影响?
如果对代码也采用类似的技术,会发生什么呢?如果通过迭代提示要求 LLM「让这些代码更好」确实能让代码质量提升,那么有望极大地提升生产力。如果情况果然如此,那要是迭代次数过多又会怎样呢?最终的代码也会出现某种「宇宙感」吗?只有试过才知道。
常规方式使用 LLM 写代码
尽管早在 ChatGPT 诞生前,就已经有研究者在围绕 LLM 研发工具了,但我一直以来都不喜欢使用 GitHub Copilot 等 LLM 代码助手来辅助编程。你的想法会在「LLM 自动完成了我的代码,真棒」、「应该怎样向 LLM 提问」以及「LLM 生成的代码究竟对不对,还是幻觉产生的正确代码」等之间来回切换,让人难以集中精神专注工作,以至于使用 AI 带来的生产力提升至多只能算是中性的。这里还没有涉及使用 LLM 的高昂成本。
Claude 3.5 Sonnet 的出现改变了我的想法。或许是 Anthropic 在训练中使用了什么秘方,Claude 3.5 Sonnet 的最新版本(claude-3-5-sonnet-20241022)具有出色的指令遵从能力,尤其是对于编程提示词。编程基准已经证实,当 Claude 3.5 Sonnet 与 GPT-4o 比较时,Claude 更胜一筹;而且我在多种不同的技术和创意任务上都有类似的体验。
初始请求
为了此实验,我们将向 Claude 3.5 Sonnet 提供一个面试风格的编程提示词(使用 Python):问题既很简单 —— 新手软件工程师也能实现,但也可被显著优化。这个简单提示词可以代表软件工程师使用 LLM 的典型方式。此外,另一个要求是这个测试提示词应该足够新颖,绝不能从 LeetCode 或 HackerRank 等代码测试库中取用,因为 LLM 在训练时可能就已经看过这些问题了,完全可以根据记忆引用这些答案。
因此,这是我自己动手写的测试提示词:
编写 Python 代码来解决这个问题: 给定一个包含 100 万个随机整数的列表,这些整数的取值范围是 1 到 100,000,找出各位数总和为 30 的最小数和最大数之间的差值。
将此作为用户提示词提供给 Claude API 并设置温度值为 0(可获得最好 / 最确定的答案),可得到如下结果:
[图片:初始代码实现截图]
这个实现是正确的且与大多数 Python 新手程序员编写的差不多,并且还多了个附加功能,可处理没有符合条件的有效数字的情况。对于列表中的每个数,检查各位数总和是否为 30:如果是,则检查它是否大于最近看到的最大数或小于最近看到的最小数,并相应地更新这些变量。搜索完列表后,返回差值。
我敢肯定,很多程序员看到这个实现都会摇头,想要对其进行优化。digit_sum() 函数就是个例子:虽然该实现是一个有趣的 Python 式单行代码,但 str 和 int 之间的类型转换会导致很多不必要的开销。
在我的 M3 Pro Macbook Pro 上,运行此代码平均需要 657 毫秒。我们将使用此性能作为基准来比较未来的实现版本。(剧透:它们都更快)
第 1 次迭代
现在,来让 Claude 改进这段代码,做法是将当前答案以及之前的内容都放入对话提示词中,并增加一个迭代提示词:write better code。是的,不开玩笑,真就这三个单词。
Claude 现在会输出修改后的代码,并且还表示这是「使用了几项改进的优化版代码」。Claude 并没有将所有代码都重新放置到函数中,而是决定将其重构为 Python 类并使其更加面向对象:
[图片:第一次迭代代码截图]
其中,该代码实现了 2 项聪明的算法改进:
- 执行各位数之和时,它使用了整数运算,并且避开了之前提到的类型转换。
- 预先计算所有可能的各位数之和,并将它们存储在一个字节数组中(有点不寻常,而不是列表)以便后面查找,这意味着当那 100 万个数中有重复时,无需进行重复计算。由于这个数组是作为字段存储在类中,因此在搜索新的随机数列表时不需要重新计算。
这些优化将代码的运行速度提升了 2.7 倍。
第 2 次迭代
再来一次 write better code,Claude 发现了更显而易见的优化方法(为方便阅读有所裁剪):


