对代码行数变化量施加硬性上限,迫使AI智能体编写更精简的代码并主动删除过时逻辑。
AI translation, not an official translation. Refer to the original for technical details.
Adapted from @paoloanzn# 𝛥LOC <= 𝑁 将修复智能体产生的所有软件冗余 如果你想让AI智能体停止无休止地膨胀你的代码库,你需要引入可编程的元规则。给它们一个硬性约束和一个预算: \Delta LOC \le N 仅凭这一条,就能修复大量智能体产生的冗余。 LOC 表示代码行数,且: \Delta LOC = LOC(added) - LOC(removed) 𝑁 是你自定义的任意最大值。 对于普通的功能开发或重构,我可能会使用 𝑁 ≈ 300。对于较大的实现,也许是 600–700。 重要的是,这是一个硬性不变式,而非建议。 智能体需要完整地端到端实现该功能,不留任何 TODO、占位符、模拟代码或延后工作。在每一轮结束时,它运行一个工具来计算源文件的实际 ΔLOC。 智能体被迫持续迭代,直到满足约束条件。这会产生一些非常有趣的副作用。 (1)首先,智能体自然需要更深入地思考实现方案,而不是直接跳向训练数据或当前上下文所暗示的显而易见的路径。添加另一个辅助函数、抽象层、适配器、兼容层或特殊分支不再是免费的。每增加一行都会消耗稀缺的预算。 (2)更重要的是,如果智能体判断该功能无法用少于 𝑁 行新增代码来实现,那么它只能改变另一个变量: \Delta LOC = LOC(added) - LOC(removed) 如果 LOC(added) 无法再降低,那么减少 ΔLOC 的唯一方法就是增加 LOC(removed)。这正是为什么这种方法能极其有效地缓解"智能体不愿删除代码"的问题。 智能体现在有了寻找过时代码的真实动机。 由于模型对破坏现有行为极为保守——保守到往往完全回避删除操作——它们通常会认真努力地识别可以安全删除的代码:旧的代码路径、重复逻辑、已被取代的抽象、兼容性代码,以及不再需要的行为。这正是我们想要的行为。 构建一个工具或像这样的自动钩子: 示例输出: ## 为什么智能体会持续膨胀你的代码库 首先你需要理解的是,AI编程智能体往往会增加代码库的熵。你那些"保持实现最简化"或"只用几行代码完成这件事"的提示词,通常无法让你从这种现象中幸免。因为这不是你可以通过后训练来修复的问题。 你需要将模型视为在交付给你之前经过预处理的东西。将一个"原始"语言模型想象成一台补全机器。它本身并不理解对话、编程任务或工具调用智能体的概念。你提供文本,它预测下一个最可能的词元。为了让该模型始终如一地表现得像一个基于回合制的助手——或一个编辑文件、运行测试、并持续工作直到问题解决的智能体——它需要经过后训练,通常是通过监督微调(SFT),使其在行为上产生偏向。 其中一种行为是:当被给予一个任务时,做出某些改变。另一种是对现有行为的保守性。这两种倾向在软件开发中结合起来会产生不良效果。 假设某段逻辑已经过时。人类维护者可能会直接将其删除。而智能体则更有可能这样做: 而不是完全删除 old_behavior()。 从模型的角度来看,这样做更安全。它在满足新需求的同时,保留了旧路径,以防仍有某些地方依赖它。这一行为现在也已通过实证得到验证。针对大量删除操作的编程任务的研究发现,智能体往往能准确定位到应该删除的代码,却依然回避删除,转而在其周围添加守卫条件或备用路径。而且显而易见,仅仅告诉模型"保持改动最小化",对解决这一问题几乎毫无效果。 ## 一种可编程的方法 实际可行的方法是引入我所称的**计算式编码元规则检查**。这些是对代码库实际源文件进行的自动化度量。它们衡量的不是软件的行为,而是源代码本身的属性——因此称为"元(meta)"。 示例可以包括: ΔLOC <= 300 智能体在每轮结束时运行这些检查,并将检查失败作为反馈循环加以利用。一条计算规则一旦未通过,任务即告失败。这一差异改变了智能体的优化问题。 智能体现在必须回答:"我如何在让测试通过的同时,使系统规模不超出这一上限?" 一旦代码增长被赋予了明确的代价,删除代码就不再是你寄希望于模型自觉去做的事,而是对智能体而言在"经济上"切实有利的选择。这便是核心思想:不要要求智能体少写代码,而要让过度的代码增长成为一种无效解。 ** 我称之为元值(meta-value),是因为它们衡量的不是代码性能,也不是代码的功能行为,而是对代码源文件本身的度量,故用"元"这一词。参见 dictionary.cambridge.org(https://dictionary.cambridge.org/dictionary/english/meta)