
这只乌龟的表情,就是我看待我们这个行业时的样子
距离真正能够帮你构建完整项目的编程智能体登场,已经差不多过去一年了。此前也有像 Aider 和早期 Cursor 这样的先驱,但它们更像是助手,而不是智能体。新一代产品极具诱惑力,我们很多人也花了大量业余时间,去构建那些自己一直想做、却始终没空做的项目。
而我觉得这没什么问题。用业余时间做东西本来就非常有趣,而且大多数时候你也确实不必太在意代码质量和可维护性。它还能让你有机会去学习一套新的技术栈,如果你想的话。
圣诞假期期间,Anthropic 和 OpenAI 都发了一些“免费试用”,想把人们吸进它们那台令人上瘾的老虎机里。对很多人来说,那是他们第一次体验到智能体编程的魔力。这个圈子正在不断扩大。
如今,编程智能体也开始被引入生产代码库。12 个月过去了,我们现在终于开始看到这一切“进步”带来的效果。以下是我当前的看法。
虽然这些大多只是轶闻式观察,但软件确实越来越像一堆脆弱不堪的烂摊子:98% 的可用性(uptime)似乎正从“例外”变成“常态”,大服务也不例外。用户界面里还会出现各种离谱到你觉得 QA 团队本该抓出来的鬼畜 bug。我承认,这种情况在智能体出现之前就已经存在了。但现在我们似乎正在加速坠落。
我们无法接触公司的内部情况。但时不时总会有些东西泄露给新闻记者。比如这篇关于 AWS 宕机据称由 AI 引发 的报道。随后 AWS 立刻进行了“纠正”。结果转头又在内部启动了一个 90 天重整计划。
微软 CEO Satya Nadella 一直在谈论 微软现在有多少代码是由 AI 编写的。虽然我们没有直接证据,但 Windows 正在一路下滑的感觉实在太明显了。微软自己似乎也认同这一点,从这篇漂亮的博客文章就能看出来。
那些声称自己产品代码现在 100% 由 AI 编写的公司,持续不断地推出你能想象到的最烂垃圾。我不点名了,但动不动就是以 GB 计的内存泄漏、UI 故障、残破不堪的功能、程序崩溃:这根本不是他们以为的“质量认证章”。而且,这对那种“让智能体替你包办一切工作”的狂热幻梦来说,也绝不是什么好广告。
私下里你会越来越常听到各种大小软件公司的人说,他们已经靠“智能体式编程”把自己逼进死胡同了。没有代码审查,设计决策全权下放给智能体,再加上一大堆没人要求的功能。那当然会出事。
我们基本上已经为了某种上瘾感,放弃了所有纪律和自主性,仿佛唯一的最高目标,就是在最短时间里产出最多的代码。至于后果,随它去吧。
你正在搭一个编排层(orchestration layer),准备指挥一整支自治智能体大军。你装了 Beads,完全没意识到那玩意儿基本上就是个卸不掉的恶意软件(malware)。因为互联网让你这么做。你就该这么工作,否则你就没戏了(ngmi)。你在疯狂呕吐式地跑循环(ralphing the loop)。看啊,Anthropic 用一群智能体构建了一个 C 编译器。虽然有点坏,但下一代 LLM 肯定能修好它。天哪,Cursor 用一整营智能体造了个浏览器。对,当然,它其实根本没怎么正常工作,而且中间还需要人类时不时手动拨一下轮盘。但下一代 LLM 肯定能修好它。拉钩保证!分布式、分而治之、自主化、黑灯工厂(dark factories)、软件问题再过 6 个月就彻底解决了。SaaS 已死,我奶奶刚让她的 Claw 给她自己做了个 Shopify!
再说一遍,这套东西用于你的边角料副项目当然可能行得通——反正几乎也没人用,包括你自己。嘿,也许这世界上真有那么个人,能把这套方法用在一个不是滚烫垃圾堆、且真有人高强度使用的软件产品上。
如果那个人就是你,那祝你好运、向你致敬。但至少在我身边这圈同行里,我至今还没见到这种鬼玩意儿真正奏效的证据。也许是我们都太菜了吧。
智能体的问题在于,它们会犯错。这本身没什么,人类也会犯错。也许它们犯的只是正确性错误(correctness errors),这种通常容易识别、容易修复。额外再补一条回归测试(regression test),那就更好了。又或者它犯的是代码异味(code smell),而你的 linter 抓不到:这里一个没用的方法,那里一个毫无意义的类型,别处再来一段重复代码。单独看,这些都无伤大雅。人类也会犯这种低级错误。
但铁罐头(clankers)不是人。
人类会重复犯同样的错误几次,但最终会学会不再继续犯。要么是因为有人开始对他吼了,要么是因为他确实在一条真实的学习路径上前进。
智能体没有这种学习能力。至少开箱即用时没有。它会一遍又一遍地持续犯同样的错误。取决于训练数据,它甚至还可能把不同错误“插值组合”出一些全新的辉煌变体。
当然,你可以尝试“教育”你的智能体。把“不要再犯这个低级错误”写进你的 AGENTS.md。捣鼓出一个极其复杂的记忆系统,让它去查过去犯过的错误和所谓最佳实践。对某一类特定错误来说,这可能确实有效。但前提是,你得先真的观察到智能体犯了那个错。
而铁罐头和人类之间还有一个更重要的区别:人类本身是一个瓶颈。人类不可能在几个小时里拉出 2 万行代码。就算这个人犯低级错误的频率很高,他一天之内往代码库里注入的低级错误数量也终究有限。这些错误会以非常缓慢的速度累积。通常,一旦这些低级错误带来的痛苦变得太大,那个讨厌痛苦的人类就会花点时间去修补它们。或者那个人被开了,由别人来修。于是痛苦就消失了。
但如果是由一整支经过编排的智能体大军来干活,那就没有瓶颈,也没有人类痛感。这些原本微不足道、看起来无害的小低级错误,突然会以一种不可持续的速度快速累积。你已经把自己移出了回路,所以你甚至都不知道这些看似无辜的小错误已经联合起来,长成了一个怪物级代码库。等你真正感受到痛苦时,通常已经太迟了。
然后有一天,你回头想加一个新功能。可这时的架构——说到底,基本已经是由各种低级错误堆出来的——根本不允许你的智能体大军以一种正常可用的方式做出修改。或者你的用户正在对你怒吼,因为最新版本里某个东西坏了,还把用户数据删掉了。
这时你会意识到,你已经无法再信任这个代码库了。更糟的是,你会发现那些由铁罐头们帮你写出来的海量单元测试(unit tests)、快照测试(snapshot tests)和端到端测试(e2e tests)同样不值得信任。此时唯一还算可靠的“这玩意儿到底能不能用”的衡量方式,只剩下手工测试产品。恭喜,你把自己(还有你的公司)搞砸了。
你他妈根本不知道发生了什么,因为你已经把所有自主性都委托给了你的智能体。你让它们自由奔跑,而它们就是复杂性的贩子。它们在训练数据里见过无数糟糕的架构决策,在 RL 训练过程中也反复接触过这些东西。你让它们来为你的应用做架构设计。那结果还能是什么?
结果就是海量的复杂性,是一大锅可怕的、靠“行业最佳实践”邪教式模仿拼凑出来的大杂烩,而你在一切为时已晚之前根本没有及时收住它。但事情比这还糟。
你的智能体彼此之间看不到对方的运行过程,也永远看不到你的整个代码库,更看不到在它们做出修改之前,你或者其他智能体曾经做过的所有决策。因此,智能体做出的决策天然都是局部性的,而这恰恰会导向前面提到的那些低级错误:大量重复代码,为了抽象而抽象的抽象层。
所有这些东西累积起来,最终就会变成一个无法挽回的复杂性烂摊子。也就是你在人类打造的企业级代码库里看到的那种一模一样的烂摊子。人类的企业代码库会走到那一步,是因为痛苦被分摊到了大量的人身上。单个个体的痛苦达不到“我必须修这个”的阈值。个体甚至可能根本没有修复问题的权限或能力。而组织的痛苦耐受度又奇高无比。但人类打造的企业代码库,通常要花很多年才会烂成那样。组织会和复杂性一起,以一种近乎痴呆般的协同关系缓慢演化,并逐渐学会怎么和它共处。
而有了智能体,再加上一个 2 人小团队,你只需要几周就能抵达同样的复杂度。
于是你开始寄希望于智能体来收拾这个烂摊子,重构它,让它恢复整洁。但这时候你的智能体也已经搞不定它了。因为代码库和复杂度都太大,而它们看到的永远只是这个烂摊子的局部视角。
而且我说的还不只是上下文窗口(context window)大小,或者长上下文注意力机制在面对一个 100 万行代码怪兽时直接失效。这些都只是显而易见的技术限制。真正的问题更阴险。
在智能体真正尝试帮你修复这个烂摊子之前,它首先得找到所有需要修改的代码,以及所有已有的、可以复用的代码。我们把这称为 Agentic 搜索。智能体具体怎么做,取决于你给了它什么工具。你可以给它一个 Bash 工具,让它用 ripgrep 在代码库里一路搜过去。你也可以给它一个可查询的代码库索引、一个 LSP 服务器、一个向量数据库。说到底其实差别不大。代码库越大,召回率(recall)就越低。召回率低,意味着你的智能体事实上找不到完成高质量工作所需的全部代码。
这也是为什么那些代码异味式的低级错误一开始就会发生。智能体漏掉了现有代码,重复造轮子,引入不一致性。然后这些东西再一路绽放,开成一朵华丽的复杂性粪花。
那我们该怎么避免这一切?
编程智能体就像海妖(sirens),用它们飞快的代码生成速度和锯齿状的智能诱你靠近;面对简单任务时,它们往往能以极高质量、极高速度完成工作。问题是在你开始这么想的时候出现的:“哎呀,这玩意儿真棒。电脑,替我干活吧!”
显然,把任务委托给智能体本身并没有错。适合交给智能体的任务,通常有几个共同特征:任务边界足够清晰,智能体不需要理解整个系统;回路可以闭合,也就是说,智能体有办法评估自己的工作;输出结果并非关键任务(mission critical),只是某个临时工具,或者某个内部软件,没有人的生命或收入依赖它。又或者,你只是需要一个“橡皮鸭”来帮你碰撞思路——本质上就是拿你的想法去和互联网压缩后的智慧以及合成训练数据做对照。如果以上任一条成立,那你就找到了一个非常适合智能体的任务,前提是你这个人类依然是最终的质量把关者。
Karpathy 的 auto-research 被用来加快你的应用启动时间?很好!前提是你得清楚,它吐出来的代码根本还没达到生产可用(production-ready)的程度。Auto-research 之所以能工作,是因为你给了它一个评估函数(evaluation function),使智能体可以根据某个指标——例如启动时间或者损失(loss)——来衡量自己的结果。但这个评估函数只捕捉了一个非常狭窄的指标。对于评估函数没有纳入的任何指标,智能体都会愉快地无视,比如代码质量、复杂度,甚至如果你的评估函数写得一塌糊涂,它连正确性都能一起无视。
关键点在于:让智能体去做那些无聊的事,那些不会教会你任何新东西的事,或者去尝试那些你原本没时间试的不同方案。然后由你来评估它得出的结果,把其中真正合理且正确的想法拿出来,最后完成实现。是的,当然,你也可以在这个最后步骤继续使用智能体。
而我想建议的是:他妈的,慢下来,才是正确的路。给自己时间去思考你究竟在构建什么、为什么要构建它。给自己一个机会说出:操,不,我们不需要这个。给自己设定一个每天允许铁罐头生成代码的上限,这个上限要和你真正有能力审查代码的能力相匹配。
任何定义你系统整体形态(gestalt)的东西,也就是架构、API 等等,都请手写。实在不行,最多用用 Tab 自动补全,满足一下怀旧情绪。或者和你的智能体做一点结对编程(pair programming)。你得待在代码里。因为光是“必须亲手把这个东西写出来”或者“看着它一步一步被搭起来”这个过程,本身就会引入一种摩擦,而这种摩擦能让你更好地理解自己究竟想构建什么,以及这个系统“手感”如何。也正是在这里,你的经验和品味开始发挥作用——而当前最先进的 SOTA 模型还远远无法取代这一点。放慢下来,承受一些摩擦,正是你学习和成长所需要的条件。
最终得到的,将会是仍然可以持续维护的系统和代码库,至少会像智能体出现之前我们的老系统那样可维护。是的,那些系统也并不完美。可你的用户会感谢你,因为你的产品现在带来的是愉悦,而不是一堆劣质糊状物(slop)。你做出的功能会更少,但会是对的那些。学会说“不”,本身就是一种能力。
这样一来,你就能安心入睡,因为你仍然大致知道他妈到底发生了什么,而且你还保有自主性。你的理解力能够帮助你弥补 Agentic 搜索的召回率问题,从而让铁罐头产出的结果更好,也更少需要后期揉搓修补。真要出了大事,你也有能力亲自进去把它修好。或者,如果你最初的设计并不理想,你也明白它为什么不理想,以及该如何把它重构成更好的东西。有没有智能体都无所谓,根本不重要。
所有这一切,都需要纪律与自主性。
所有这一切,都需要人类。