作者发现,让AI参考一个已有的相似代码库,比长篇累牍的描述需求更管用。指出项目中存在不一致的代码结构就是给AI埋的缺陷,规范的包命名模式是天然的高质量示例。
代码库中的每个不一致,都是你交给 AI 代理的规范中的一个 bug。
在过去的八个月里,我的 prompt 大多是一句相同的话,只是换了两个包名。
Ok, considering `blas/ext/base/gxmy` as the closest reference
implementation, we need to refactor everything in
`blas/ext/base/gxdy`.
The difference is mostly implementation, and some other things
you will figure out when comparing.
But as far as structure goes, `blas/ext/base/gxmy` is the way
to go.
这样的话我写了几十遍,绝大多数都是在 blas/ext/base 目录下。包名如果不熟悉命名规则,看起来就像乱码。打头的字母通常标识数据类型,其余部分描述操作。你不需要记住细节。重要的是相关包遵循可识别的模式。
这不是什么巧妙的 prompt。没有复杂的角色设定,没有冗长的清单,也没有提示技巧。它只是指向一个已经存在的包,然后说:遵循那个模式,有这些区别。
而且效果出奇地好。
我花了一段时间才理解原因。答案比 prompt 本身更有趣。它有效是因为 stdlib 的构建方式。
数千个兄弟
stdlib 是一个专注于数值和科学计算的 JavaScript 和 Node.js 标准库。它由 lib/node_modules/@stdlib 下数千个小包组成,每个都可以独立安装。它们不是数千片不同的雪花。它们是更少几种形态的变体。
一个 strided BLAS routine 看起来和其他 strided BLAS routine 一样。一个 LAPACK auxiliary routine 与其相邻的包遵循相同的总体包结构,包含相似的文件、分区注释、文档、测试和命名约定。
stdlib 的 Zen 说得很清楚:
价值一致性高于一切。
这条原则是为人类写的。实现了一个包的贡献者可以认出其他许多包的形态。一致性成为一种教学工具,也是一个承诺——下一个包不需要从零开始。没有人写这条原则时考虑过语言模型。但是,一个包之间高度相似的代码库,也是你能给 AI 代理的最好的东西之一。
当我指向 blas/ext/base/gxmy 并要求参考它来重构 blas/ext/base/gxdy 时,我并不是在要求模型凭空发明一个包,而是在让它比较两个相关的操作并应用现有的模式。规范不在文档里,而是在兄弟包里,在磁盘上,已经过审查、已合并、已知可以工作。
代码库本身就是 prompt。
混乱的代码库是混乱的 prompt
这就是这个想法让人不舒服的地方。如果先例是你的规范,那么仓库中的每一个不一致都成了这个规范的 bug。这不仅仅是外观问题。一个使用旧基准风格过时的包,同样是一个等待被复制的错误答案。
另一位 stdlib 维护者最近对我说的话改变了我的看法。当约定改变时,你有两个选择:
你可以添加另一条指令告诉模型记住这个异常;或者
你可以更新旧的包,使异常不再存在。
第二种选择在实际可行时更好。告诉模型记住只是一个补丁。它要求模型携带不一致并追踪哪个版本是当前的。修复代码库消除了那种模糊。模型可以直接环顾四周。
对人类也是如此。每一个"忽略那个文件,它使用旧方法"都会给未来每个贡献者增加一点成本。我们一直都知道这一点。AI 代理只是更快地让成本显现出来,因为它们会找到旧的例子并把它当作证据。
我在最近的一次现代化工作中看到了这一点。基准测试名称以前是通过字符串连接构建的,后来我们迁移到使用 format() 的字符串插值,这是一个类似 C 的 printf 的纯 JavaScript 实现。在迁移过程中,代码树混合了两种约定。一个代理在向新包添加基准测试时,复制了它最近的邻居碰巧使用的任何风格,而它这样做是对的。旧文件中没有任何内容说它是旧的方式。
不要把今天能做的事情推迟到明天,因为明天一个代理可能会读取所有版本并认为它们都同样有效。
在 stdlib 参与 METR 研究之后——该研究指出有经验的开发者在可以使用 AI 的任务上慢了 19%——Philipp Burckhardt 写了一篇反思,识别出两个摩擦来源:项目约定的差距,以及代理在每个 session 开始时的空白 slate。我认同这个诊断。代理可能不会可靠地记住前一个 session 中的指令,但它可以检查面前的代码。一致性使得正确的约定在代理开始的地方都是可见的。
这就是为什么我今年的大部分工作从外部看起来都很无聊。更新基准测试名称、现代化测试、清理数百个 pull request 中的旧约定。我以前主要认为这些工作是维护。现在我也把它们看作是改进例子,这样未来每个贡献者——人类或其他——都会从中学习。
包里面没有的东西
先例很强大,但它有局限性。代码展示的是 what。它很少展示 why,而且并不总是展示要验证什么。一个模型可以产生一个正确的实现,但仍然不知道在打开 pull request 之前应该运行哪些检查。
但那些知识并不只存在于人们的头脑中。大部分已经写在仓库里了,只是没有在你恰好编辑的包里面。
stdlib 安装了一个 pre-commit hook,在允许提交之前运行十多个检查。它验证格式化,对源代码、测试、示例和基准测试分别 lint JavaScript,检查包清单,lint Markdown 和 TypeScript 声明,并验证许可证头。
在这个 post 撰写时,这个项目有 126 条自定义 lint 规则,覆盖了一般 linter 没有理由知道的约定:一个 JSDoc 块应该如何构建,doctest 输出应该如何匹配,一个 namespace 应该如何看待其导出。树中的每种语言都有风格指南,还有一份贡献指南,描述了一个可审查的 pull request 是什么样子。
这些都不在包里面。所有这些都是机器可读的。
这个区别比我预期的更重要。一个不知道这些存在的代理会猜测,而它的猜测看起来足够合理,可以在快速审查中存活下来。一个运行 hook 的代理会被告知确切的问题所在,用项目自己的话,不需要任何人写关于它的 prompt。
所以你能为代理做的最有用的事情不是描述你的约定,而是让它们可执行。一个存在于审查者记忆中的约定每次被违反时都必须重复。一个存在于 lint 规则中的约定对人和机器都会自动执行。
工具无法决定的是首先要模仿哪个现有的包。选择正确的参考是我不得不为模型自己写下来的唯一指导。
找到正确的兄弟
我在写这个指导时学到的最有价值的东西是,有价值的部分不是另一份风格规则列表,而是教模型如何找到正确的先例。
对于一个新包,指导是一个简单的阶梯:
查找相同操作在不同精度下的实现。
如果不存在,找一个具有相同包形态和相同精度的 routine。
否则,在同一个 family 中找最接近的操作。
然后,在编辑任何东西之前,说明所选的参考并解释为什么选择它。
这是我重复 prompt 的核心,概括了。不再需要我知道 blas/ext/base/gxmy 是应该遵循的正确包,模型有一个找到正确包的过程。
最后一条指导比看起来更重要。在写代码之前要求模型说明它的参考给了我一个廉价的地方来阻止它。如果它选择了错误的兄弟,我在一句话中就能发现,而不是在审查一个四百行 diff 之后。许多我收到的糟糕输出都是以错误的参考开始的。那个错误在任何代码需要写之前就已经可见了。
我会告诉维护者什么
如果你维护一个项目,并考虑如何为 AI 辅助贡献做准备,这是我目前相信的:
一致性就是基础设施。它帮助人们理解项目,并为机器提供可靠的例子。
修复代码库,而不是记录每个异常。在实际可行时,迁移旧的约定,而不是要求每个未来的贡献者记住哪些例子应该被忽略。
写下过程,而不仅仅是风格。linter 可以强制格式化。它无法解释选择哪个参考,哪些检查证明工作有效,或者哪些更改不应该在同一个 pull request 中。
让代理首先承诺一个参考。任何代码之前的一句话可以防止整个不正确的实现。
为数值工作保留一个真实的参考。约定可以使代码看起来正确。针对可信真实值的差异测试才能告诉你它是否表现正确。
像对待代码一样对 AI 指令进行版本管理。它们会过时。给它们标识符、索引和历史,这样贡献者可以知道哪个指导是当前的。
意外的成功
我进入今年时认为有趣的问题是如何写好 prompt。我不再认为这是主要问题。Prompt 是建立在一个更基本问题之上的薄层:你的项目是否有任何一致的东西可以指向?
stdlib 在这方面异常出色,尽管对 AI 的好处是意外的。多年坚持要求相关包看起来和行为一致,完全是为了人类贡献者的利益,同时也创建了一个有用的机器可读规范。项目对 AI 做出的最佳投资发生在任何人开始思考 AI 之前。它被称为"价值一致性高于一切"。
这引出了一个乐观的结论。让代码库对机器可读与对人类可读是同一件事。它是一致结构、当前约定、文档化流程和真实验证的同一项工作。
我们一直都知道这些事情很重要。我们并不总是把它们当作紧迫的事情来对待。现在有什么东西在读取所有这些内容,并用找到的东西来塑造下一个贡献。
Karan Anand 是 stdlib 的核心贡献者,stdlib 是一个用于数值和科学计算的 JavaScript 库。
stdlib 是一个开源软件项目,致力于提供全面、高性能的库套件,以加速项目的开发,让你在依赖专家精心制作的高质量软件时安心。
如果你喜欢这篇文章,请在 GitHub 上给我们一个星星 🌟 并考虑支持这个项目。您的贡献和持续的支持有助于确保项目的长期成功,我们非常感谢!