你只需要背压

Hacker News Top 新闻

摘要

本文介绍了‘背压’——利用自动化测试和类型系统——作为使用AI编码代理的第三种方式,通过减少持续人工审查的需求,使其更安全、更自主。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/05/31 13:34

# 背压就是一切 来源:https://www.lucasfcosta.com/blog/backpressure-is-all-you-need 使用编程智能体有两个*显而易见*的方式,但两者都不好。第一种是让 LLM 无人值守运行,并希望仓库能幸存下来。这种方法快速、刺激,但也很愚蠢。它会导致缺陷、混乱的变更,以及大量人类无法及时审查的 PR,除非最终降低标准并合并那些他们并不真正理解的东西。第二种方法是把智能体当成高级的自动补全,强迫人类审查每一步微小的动作。这种方法更安全,但速度慢到部分违背了使用智能体的初衷。如果你仍然需要引导每一个细小的决策,那就没有真正委派多少工作。 在这篇文章中,我将介绍第三种不那么显而易见的方法:构建让智能体在人类介入之前更多地自我验证其工作的方式。目标是让更长的无人值守会话足够安全,既有用又不完全把人类排除在外,同时减少你的同事需要审查的低质量 PR 数量——这些 PR 中本应由智能体自行捕获的细节。 ## 什么是背压,以及它如何帮助 **在系统工程中,背压是一种机制,下游组件通过它向上游发出信号,表示无法接受更多工作,迫使生产者减速、缓冲或卸载负载。** 只要没有背压,生产者就可以随意产生工作,而消费者不得不吸收这种不匹配。结果,消费者要么落后,要么在负载下崩溃,要么通过偷工减料来加速。 在我们的工作中,背压通常表现为机器拒绝接受生产者尚未清理干净的工作。最简单的形式是自动化测试:你通常不会提交有失败测试的 PR。理想情况下,你的同事甚至不应该在所有测试变绿之前审查 PR。在这种情况下,测试套件就是背压机制,让人类在请求审查之前先清理自己的代码。 序列时间线:开发者编写代码,自动化测试在本地运行并快速反馈循环,然后审阅者才进行人工审查。(https://www.lucasfcosta.com/assets/backpressure-is-all-you-need/tests-backpressure.png) 自动化测试就是背压:开发者针对快速的本地测试反馈进行迭代,因此审阅者只看到已经变绿的代码。 除了自动化测试,类型也是一种强大的背压形式。回想一下编写纯 JavaScript 的日子。那时,很容易把错误的 prop 形状传入组件,直到很久以后,当有人点击按钮并碰到 `props.onSubmit is not a function` 时才发现。在 TypeScript 之前,在投产前捕获该 bug 的唯一方法是让审阅者追踪 prop、回调、调用方、调用方的调用方,然后希望不匹配在 diff 中可见。我们中的一些人从这些艰难时期吸取了教训,开始使用类型来使不可能状态变得不可能(https://www.youtube.com/watch?v=IcgmSRJHu_8)。其他人看着同样的教训,严肃地点了点头,然后继续传递字典,不过这大概是另一个故事了。 无论如何,关键是 TypeScript 添加了背压,迫使生产者在推进代码之前面对消费者的期望。现在,如果一个组件需要一个函数,你不能随意给它一个字符串、对象或什么都不给,然后希望审阅者抓住 bug。相反,机器会在你引入类型不匹配的边界处拒绝该工作,无需昂贵的人工审查。 同样的时间线,在测试之前添加了一条 TypeScript 通道:类型检查器首先向开发者施加压力,然后是自动化测试,最后是更短的人工审查。(https://www.lucasfcosta.com/assets/backpressure-is-all-you-need/types-backpressure.png) 类型在测试之前增加了另一层背压,在边界处拒绝不匹配,使最终的人工审查更短、更安全。 随着时间的推移,我们不断向流程中添加更多自动化的防护栏,比如 linter、端到端测试、金丝雀发布等等。然后我们将这些防护栏捆绑到 CI 管道中。这样,我们可以停止审查那些甚至还没准备好的代码,并将人类注意力集中在机器无法检查的事情上,比如可读性、复杂性和整体设计。 今天,当我们谈论编译器、自动化测试、CI 以及(对于我们中的忠实信徒而言)类型时,这个教训很容易被识别。然而,当生产者是以比任何人阅读速度都快的速度编写代码的 LLM 时,这似乎更难识别。这就是为什么,大多数时候,**LLM 的背压仍然是我们**。我们看着编辑器中的代码,让模型修复那些看起来不对劲的部分(多次),打开 PR,修复任何失败的检查,然后某人再以更严肃的表情看同样的代码。 序列时间线:人类和智能体:人类提示智能体,智能体工作并返回结果,人类审查并发送反馈,重复每个循环。(https://www.lucasfcosta.com/assets/backpressure-is-all-you-need/human-backpressure.png) 没有自动化的背压,人类就是背压——手动审查智能体的输出并在每个周期中反馈修正。 通常,为了额外的安全性,我们会安装一个审查机器人来检查第一个 AI 的代码。然后,我们把机器人的反馈复制回编码智能体。这样,我们讽刺地把自己提升为一个昂贵的剪贴板,做着两台机器之间的机械工作。 **AI 辅助软件开发的下一步是停止让人类成为 AI 循环中的默认背压**。我们需要能够及早失败的测试、能够施加压力的类型、能够捕获回归的基准测试,以及在问题变成人类的问题之前将糟糕的补丁发回的审查智能体。正是这种机制使委派成为可能,并释放我们的时间,让我们专注于更高级的反馈和设计决策,而不是低层的正确性和质量问题。 序列时间线,包含三个通道——人类审阅者、智能体背压和智能体。智能体编写代码,背压层将自动化反馈推送回去,循环多次,最后人类审阅者进行手动审查。(https://www.lucasfcosta.com/assets/backpressure-is-all-you-need/agentic-backpressure.png) 有了自动化的背压,智能体针对快速的自动化反馈进行迭代,人类只在最终手动审查时介入。 接下来,我将解释我在工作中是如何构建这种机制的,你如何也能做到,以及我尚未探索的有趣方法。你可以通过在终端中运行 `npx @lucasfcosta/backpressured` 来安装这篇文章的背压技能。然后在 Claude 中运行 `/backpressured `——或者明确要求 Claude 使用背压技能——来启动循环。该技能将自动向目标迭代,同时运行本文中描述的背压检查。你也可以通过向项目添加一个 `BACKPRESSURE.md` 文件(用纯英文编写)来定制检查和迭代过程。 ## 在实践中创建背压 我第一次将背压应用于 LLM 时,使用的是 Claude 的 `/goal` 命令(https://code.claude.com/docs/en/goal)。该命令让你给 Claude 一个目标,并让它持续工作直到认为目标完成。最初,我的 `/goal` 提示看起来像这样: ``` /goal implement support for <feature>. You should only consider the task done when all of the following criteria are met: 1. <criterion 1> 2. <criterion 2> 3. <criterion 3> ``` 这类提示的问题在于它过于关注功能本身,而没有足够关注必要的测试、可能的边缘情况以及实现的整体质量。本质上,没有防护栏来防止模型过早宣布胜利,而它确实经常这样做。于是,就由我来审查代码,并手把手引导模型处理每个边缘情况、添加测试和重构代码,直到足够好可以发布。这违背了 `/goal` 的目的——它本应让我可以委派工作,只在最后参与审查最终产品。 就在那时,我意识到我是在浪费自己的时间,作为一个缓慢的背压机制,而不是在循环中构建自动化的背压。我成了 `/goal` 的瓶颈! 注意到这一点后,我开始在 `/goal` 循环中添加以下背压机制: 1. Linting、测试和简单的验证脚本 2. 使用 `cURL` 和实际浏览器的手动测试 3. 基准测试 4. 审查智能体(功能性、测试、类型、简洁性) 5. 规划阶段审查 6. 视觉设计审查 7. 拉取请求监控 下面我将更详细地介绍每种机制,但总体思路是,我不断向循环中添加越来越多的自动检查和审查,这样模型就必须更频繁地面对消费者的期望,并在它们变成我的问题之前自行捕获问题。 ### 1. Linting、测试和简单的验证脚本 这些是最简单、最明显的背压形式。如果你的项目已经有测试套件和 linter,你可以立即将它们用作背压机制。事实上,Claude 大多数时候已经能自动获取测试,但随着进行,它有时会忘记保持它们变绿。因此,我决定明确地在提示中扩展检查,包括测试。然后,我还添加了其他容易实现的目标,比如 linting 和运行其他简单的验证脚本,比如提交消息检查器。 另一个重要的发现是,**让模型在每次迭代中都运行检查,而不仅仅是在最后,非常有用**。通过在每次迭代中运行检查,我迫使模型更频繁地面对消费者的期望,这使其更有可能及早发现问题并在进入下一步之前修复它们。 ``` /goal implement support for <feature>. Here are the feature's acceptance criteria: 1. <criterion 1> 2. <criterion 2> 3. <criterion 3> +The task is not done until all of the above acceptance criteria are satisfied. Additionally, the following quality criteria must also be met: +1. The linting is passing +2. Tests are all green +3. The new behavior is covered by tests +4. The commit_check.sh script is passing + +Run these quality checks in _each_ iteration. Do NOT wait until the end to run them. You should run them after writing each patch, and you should not write a new patch until all checks are passing. + +If any of the above criteria are not met, you must inspect the failure, fix the issue, and run the check again. Do not stop after writing the patch. Stop only after the acceptance criteria are satisfied, or after you can explain exactly what is blocking you. ``` 现在,我的提示有了下面的结构。一个单一的迭代阶段,包含功能检查(要求 1 和 2)和质量检查(linting、测试、commit_check)。(https://www.lucasfcosta.com/assets/backpressure-is-all-you-need/01-iteration.png) 起点:一个单一的迭代阶段,每个补丁必须满足功能要求并通过质量检查。 ### 2. 使用 `cURL` 和实际浏览器的手动测试 尽管我写了500 页关于自动化测试的书(https://www.manning.com/books/testing-javascript-applications),我深知它的局限性。自动化测试非常适合捕获各种问题,但它们无法捕获所有问题,而且肯定不如在真实浏览器中点击或对真实 API 运行 `cURL` 命令那样有代表性。 我通过在循环中添加手动测试来弥补这一差距。为此,**我需要教会模型如何在本地运行我的前端和后端应用程序**。我还必须教会它如何运行我的 `docker-compose` 文件,设置数据库模式,并排查在本地运行应用程序时常见的故障。我使用了 `obra/superpowers` 构建器(https://github.com/obra/superpowers/tree/f2cbfbefebbfef77321e4c9abc9e949826bea9d7/skills/writing-skills)来构建教会智能体如何运行我的应用程序的技能。然后,我更新了提示,让 Claude 使用这些技能进行手动检查。 ``` /goal implement support for <feature>. Here are the feature's acceptance criteria: 1. <criterion 1> 2. <criterion 2> 3. <criterion 3> The task is not done until all of the above acceptance criteria are satisfied. Additionally, the following quality criteria must also be met: 1. The linting is passing 2. Tests are all green 3. The new behavior is covered by tests 4. The commit_check.sh script is passing Run these quality checks in _each_ iteration. Do NOT wait until the end to run them. You should run them after writing each patch, and you should not write a new patch until all checks are passing. +After you're done iterating, use the `run_local_dependencies`, `run_backend`, and `run_frontend` skills to run the application locally and test the new behavior manually. You can use `cURL` commands to test the API endpoints and the Playwright MCP to test the front-end on a real browser. You should run these manual checks at least once before considering the task done, but you can run them more than once if you think it's necessary to catch issues that automated tests might have missed. +If any of the above criteria are not met, you must inspect the failure, fix the issue, and run the check again. Do not stop after writing the patch. Stop only after the acceptance criteria are satisfied, or after you can explain exactly what is blocking you. ``` 鉴于手动测试比自动化测试慢,你可以看到我告诉模型要谨慎使用它。在实践中,这通常意味着在任务接近尾声时使用。请注意,这些变更在流程中添加了一个新阶段。之前,模型只是迭代编写代码并运行自动化检查,直到认为完成。现在,在那个迭代阶段之后,它必须在本地运行应用程序并手动测试新行为,之后才能认为任务完成。 一个迭代阶段,随后是一个包含 cURL 和 Playwright 的迭代后阶段。(https://www.lucasfcosta.com/assets/backpressure-is-all-you-need/02-post-iteration.png) 使用 cURL 和真实浏览器的手动测试成为一个新的迭代后阶段,在迭代循环稳定后运行。 ### 3. 基准测试 我使用的一些应用程序对性能敏感,因此我也为它们添加了基准测试到循环中。将其写入提示很容易,但让基准测试套件易于运行和解释则需要更多工作。不过,我投入了大量时间来改进我们的基准测试工具,使它们能够: 1. **通过单个命令轻松运行**,这样模型就可以频繁运行它们而不会陷入困境。 2. **包含多个具有不同时间预算的套件**,这样模型就不会在只需要快速检查时而花 10 分钟运行基准测试。 3. **将结构化输出写入磁盘和控制台**,这样模型就能很容易地理解一个变化是改进、回归还是持平。 **我还专门创建了一个用于运行基准测试并解释结果的技能**。该技能包括选择哪个套件的说明、解释结果的启发式方法,以及对于什么算作回归、改进或持平的清晰验收标准。有了这个技能,我更新了提示,让模型为任何性能敏感的应用程序运行基准测试。 ``` /goal implement support for <feature>. Here are the feature's acceptance criteria: 1. <criterion 1> 2. <criterion 2> 3. <criterion 3> The task

相似文章

AI编码循环的形式化验证门控

Hacker News Top

文章认为,结构性反压(例如编译器、类型检查器)比改进AI模型更能确保代码正确性,并介绍了Shen-Backpressure作为一种实现该方法的工具。

如何避免AI代码质量下降

Reddit r/ArtificialInteligence

本期通讯文章讨论了AI生成代码速度超过人工代码审查速度所导致的“AI代码质量下降”问题,并提供了平衡速度与质量的策略。

最终瓶颈

Armin Ronacher

一篇反思性博客文章,探讨了代码生成中AI加速如何压倒审查流程,在软件工程中创造了新的瓶颈。并与历史上的工业瓶颈进行了类比,建议将抑制输入作为必要回应。

用AI写更好的代码,但更慢

Lobsters Hottest

Nolan Lawson认为,AI编程助手可以通过使用多个模型进行彻底的代码审查和漏洞检测,从而更慢地编写高质量代码,提升代码库的健康状况,而不是最大化输出速度。