O'Reilly 系列文章探讨 Agent 工程和 AI 驱动开发的实践。阐述 AI 驱动开发的工程化方向。
[编者按:我们将在博客中开设一个周五专栏,定期分享来自开发者社区内部的观点,无论作者来自 Stack Overflow 还是社区之外。这是该专栏的第一篇文章,转载自 O’Reilly Media 的博客 Radar。今后,我们每个月都会转载一篇他们的文章。]
本文最初发表于 O’Reilly Radar,经作者授权在此转载。
围绕 AI 与软件开发,已经出现了大量炒作,而且主要分成两派。一派说:“我们全完了,Claude Code 这样的工具将在一年内让软件工程成为过去。”另一派说:“别担心,一切都好,AI 只是工具箱里的又一种工具。”两种说法都不诚实。
二十多年来,我一直面向从业者撰写软件开发相关内容,主题涵盖编码、架构、项目管理和团队协作等方方面面。过去两年,我将重点放在了 AI 上:培训开发者有效使用这些工具,并通过书籍、文章和报告探讨哪些方法有效、哪些无效。但我不断遇到同一个问题:对于经验丰富的开发者究竟应该如何使用这些工具,我还没有找到任何人能给出一套连贯的答案。技巧很多,炒作也很多,但真正的体系却少之又少,几乎没有什么可供练习、教授、批评或改进的内容。
我一直在观察开发者如何在工作中使用 AI,他们取得成功的程度各不相同。我逐渐意识到,我们需要开始把这件事当作一门独立的学科来思考。Tesla 前 AI 负责人、OpenAI 创始成员 Andrej Karpathy 最近提出,可以用“智能体工程”这个术语来指代以严谨方式借助 AI 智能体进行开发,Addy Osmani 等人也开始认同这一说法。Osmani 的定义是:AI 智能体负责实现,但人类掌控架构、审查每一处 diff,并坚持不懈地进行测试。我认为这是对的。
不过,在过去两年里,我花了大量时间教开发者如何使用 Claude Code、Copilot 中的智能体模式、Cursor 等工具,而我反复听到的反馈是:他们已经知道自己应该审查 AI 的输出、维护架构、编写测试、及时更新文档,并始终掌控代码库。理论上,他们知道该怎么做。但当他们试图把这些原则应用到实践中时,就会陷入困境:究竟要怎样审查数千行 AI 生成的代码?连续数周使用多种 AI 工具协作时,如何保持架构的一致性?怎样判断 AI 什么时候是在自信满满地胡说八道?而且,在智能体工程方面遇到困难的并不只有初级开发者。我接触过难以适应智能体工具转变的高级工程师,也见过能够自然而然上手的中级开发者。造成差异的未必是从业年限,而在于他们是否摸索出了一套有效且结构化的方法来使用 AI 编程工具。对于许多工程师来说,知道开发者在智能体工程中应该做什么,与知道如何将这些工作融入日常实践之间的差距,正是目前焦虑的真正来源。本系列文章试图填补的,就是这一差距。
尽管智能体工程领域的大量炒作都在告诉你相反的结论,但这种开发方式并不会消除对开发者专业能力的需求;恰恰相反。要想有效地与 AI 智能体协作,开发者需要具备的知识门槛实际上更高了。我曾在早先一篇名为《The Cognitive Shortcut Paradox》的 O’Reilly Radar 文章中讨论过这种经验差距。最能从 AI 编程工具中获益的开发者,正是那些已经知道优秀软件应该是什么样子,而且往往能够判断 AI 是否写出了优秀软件的人。
AI 工具在由经验丰富的开发者驾驭时效果最佳,这一观点与我的所有观察完全吻合。它听起来确实如此,而我想用其他开发者最容易理解的方式来证明这一点:构建软件。因此,我开始设计一种具体、实用、可供开发者遵循的智能体工程方法,然后将它付诸检验。我用这套方法从零构建了一个生产系统,并制定了一条规则:所有代码都由 AI 编写。我需要一个足够复杂、能够对这套方法进行压力测试的项目,同时它还必须足够有趣,让我能在最困难的阶段保持投入。我想应用自己学到的一切,同时找出自己仍然不知道的东西。就在这时,我重新想起了蒙特卡洛模拟。
从孩提时代起,我就一直痴迷于蒙特卡洛模拟。我的父亲是一名流行病学家——他的整个职业生涯都在从杂乱的群体数据中寻找规律,这意味着统计学始终是我们生活的一部分(也意味着我在很小的时候就学会了 SPSS)。大约在我 11 岁时,他向我讲述了“醉酒水手问题”:一名水手离开码头上的酒吧,每次随机朝水边或自己的船迈出一步。他最终会掉进水里,还是能回到船上?你无法从任何一次单独的运行中得知答案。但如果把模拟运行一千次,规律就会从噪声中浮现出来。单次结果是随机的,整体结果却是可预测的。
我还记得自己在 TRS-80 Color Computer 2 上用 BASIC 编写这个模拟:一个像素块构成的小水手在屏幕上踉踉跄跄地移动,前进两步,后退一步。“醉酒水手”就是蒙特卡洛模拟领域的“Hello, world”。蒙特卡洛是一种用于处理无法通过解析方法求解的问题的技术:你将问题模拟数百次或数千次,然后衡量汇总结果。每次单独运行都是随机的,但随着样本量增加,统计结果会逐渐收敛到真实答案。我们用这种方法对许多事物进行建模,从核物理、金融风险,到疾病在人群中的传播。
如果今天只需要用简单的英语描述,就能运行这种模拟,会怎么样?不是一个玩具演示,而是数千次迭代,并通过设定随机种子保证结果可复现;输出会经过验证,结果则会汇总成真正可用的统计数据。或者,也可以构建这样一条流水线:一个 LLM 生成内容,另一个 LLM 对其评分,任何未通过的内容都会被退回重试。
我的实验目标就是构建这样一个系统,我将它命名为 Octobatch。眼下,整个行业一直在寻找新的、真实的智能体工程端到端案例研究,而我希望 Octobatch 正是这样一个案例。
我将自己在教学和观察开发者使用 AI 的过程中学到的一切付诸检验,从零构建了一个真实系统,并把从中获得的经验整理成一套结构化的智能体工程方法。我将其称为“AI 驱动开发”,即 AIDD。这是本系列的第一篇文章;整个系列将探讨智能体工程在实践中是什么样子、它对开发者提出了哪些要求,以及你该如何把它应用到自己的工作中。
最终成果是一个功能完备、经过充分测试的应用程序,由分布在数十个文件中的约 21,000 行 Python 代码组成,并配有完整的规范、近一千项自动化测试,以及高质量的集成测试和回归测试套件。我使用 Claude Cowork 审查了整个项目期间的全部 AI 对话,结果发现,我在七周时间里用了大约 75 个小时的实际开发时间,就构建出了整个应用程序。作为对比,我构建 Octobatch 所花的时间,仅略多于去年玩 Blue Prince 所用时间的一半。
但这个系列讨论的不只是 Octobatch。我在每个层面都集成了 AI 工具:Claude 和 Gemini 协作设计架构,Claude Code 编写实现代码,LLM 则生成在其协助构建的系统上运行的流水线。本系列将讲述我从这个过程中学到的东西:哪些模式行之有效,哪些失败让我收获最大,以及贯穿其中、将一切联系起来的编排思维。每篇文章都会从这次实验中提炼出一项不同的经验,涵盖验证架构、多 LLM 协作,以及让项目始终沿着正确方向推进的价值观。
大多数人在谈论使用 AI 编写代码时,指的是以下两种方式之一:一种是 GitHub Copilot、Cursor 或 Windsurf 之类的 AI 编程助手——它们早已不再只是自动补全工具,而是进化成了能够执行多文件编辑会话、定义自定义智能体的智能体工具;另一种是“凭感觉编程”,也就是用自然语言描述你的需求,然后直接接受返回的任何结果。这些编程助手确实令人印象深刻,而凭感觉编程也确实能够极大地提升生产力。
在真实项目中有效地使用这些工具,尤其是在数千行 AI 生成代码中维持架构连贯性,是一个完全不同的问题。AIDD 旨在帮助解决这个问题。它是一种结构化的 AI 智能体工程方法,其中 AI 工具推动实现、架构甚至项目管理的大部分工作,而你(人在回圈中)决定构建什么以及是否有效。
我所说的"结构化"是指一套开发者可以学习和遵循的实践、一种了解 AI 输出是否真正有效的方式,以及在整个项目生命周期中保持正轨的方法。如果 AI 智能体工程是学科,AIDD 就是其中一种实践方式。
在 AI 驱动开发中,开发者不是单纯接受建议或希望输出正确。他们给特定工具分配特定角色:一个 LLM 用于架构规划,另一个用于代码执行,一个编码智能体用于实现,人类负责愿景、验证以及需要理解整个系统的决策。
"驱动"这个词很字面。AI 编写了几乎所有代码。我为 Octobatch 实验制定的基本规则之一是让 AI 编写全部代码。我对代码质量有很高的标准,实验的一部分是看 AIDD 是否能产生符合这些标准的系统。人类决定构建什么、评估是否正确,并维持保持系统连贯性的约束。
对于开发者需要在多大程度上参与,意见并不一致,频谱的完全自主一端已经产生了一些警示案例。Anthropic 的 Nicholas Carlini 最近让 16 个 Claude 实例在完全没有人类参与的情况下并行构建 C 编译器。经过 2,000 个会话和 20,000 美元的 API 成本,这些智能体产生了一个 10 万行的编译器,可以构建 Linux 内核,但不是任何东西的直接替代品。当所有 16 个智能体都卡在同一个 bug 上时,Carlini 不得不重新介入并自己分配工作。即使是完全放手、凭感觉驱动的 AI 智能体工程的强烈拥护者也可能认为这走得太远了。问题是你需要多少人的判断来使代码值得信赖,以及什么具体的实践可以帮助你有效地应用这种判断。
如果你想让开发者以正确的方式思考 AI 智能体工程,你必须从他们如何思考与 AI 合作开始,而不仅仅是他们使用什么工具。这是我开始建立结构化方法的地方,也是我从习惯开始的原因。
我为此开发了一个名为 Sens-AI Framework 的框架,以 O'Reilly 报告(《Coding with AI 的批判性思维习惯》)和 Radar 系列的形式出版。它围绕五个实践构建:提供上下文、在提示前开展研究、精确框架化问题、刻意迭代输出,以及对 AI 生成的一切内容进行批判性思考。我从这里开始是因为习惯是你锁定自己对工作方式思考的方式。没有它们,AI 驱动开发会产生看似合理但经不起仔细审查就会崩溃的代码。有了它们,它会产生单个开发者在相同时间范围内无法独自构建的系统。
习惯是基础,但不是全部。AIDD 还具有实践(具体的技术,如多 LLM 协调、上下文文件管理以及使用一个模型验证另一个模型的输出)和价值观(这些实践背后的原则)。如果你使用过 Scrum 或 XP 等敏捷方法,这种结构应该很熟悉:实践告诉你日常如何工作,习惯是你开发的反射,使实践变成自动化。
价值观似乎往往很理论化,但它们是拼图的重要一部分,因为当实践没有给你明确答案时,它们会指导你的决策。目前 AI 智能体工程周围出现了一种新兴文化,你为项目带来的价值观要么与该文化相匹配,要么与之相冲突。理解价值观的来源是使实践坚持下去的原因。所有这些都导致了一种全新的思维方式,我称之为编排思维。本系列构建了所有四个层次,以 Octobatch 作为实验场地。
Octobatch 是 AIDD 的一个故意实验。我将项目设计为整个方法的测试用例,看纪律严明的 AI 驱动工作流能产生什么以及它会在哪里中断,我用它来应用和改进实践和价值观,使它们有效且易于采用。无论是出于直觉还是巧合,我为这个实验选了完美的项目。
Octobatch 是一个批处理编排器。它协调异步任务、管理故障中的状态、跟踪管道步骤之间的依赖关系,并确保验证的结果在另一端输出。这种系统很有趣设计,但很多细节,如状态机、重试逻辑、崩溃恢复和成本会计,可能很繁琐实现。这正是 AIDD 应该闪耀的工作类型,因为模式是众所周知的,但实现是重复且容易出错的。
编排——协调多个独立过程以实现连贯结果的工作——演变成了 AIDD 背后的核心思想。我发现自己编排 LLM 的方式与 Octobatch 编排批任务的方式相同:分配角色、管理交接、验证输出、从故障中恢复。我正在构建的系统和我用来构建它的过程遵循相同的模式。我在开始时没有预料到,但构建一个编排 AI 的系统原来是学习如何编排 AI 的好方法。这就是"意外编排器"中的意外部分。这个平行关系贯穿本系列的每篇文章。
我没有通过开始完整的端到端蒙特卡洛模拟来开始 Octobatch 项目。我从大多数人开始的地方开始:在聊天界面中输入提示。我在尝试不同的模拟和生成想法来给项目一些结构,其中一些坚持下来了。二十一点策略比较成为多步蒙特卡洛模拟的一个很好的测试用例。为角色扮演游戏的 NPC 对话生成给了我一个具有主观质量的创意工作负载来测量。两者都有相同的形状:一组结构化输入,每个都以相同的方式处理。所以我让 Claude 写了一个简单的脚本来自动化我之前手工做的工作,我用 Gemini 来二次检查工作,确保 Claude 真的理解我的要求,并修复幻觉。
它在小规模工作很好,但一旦我开始运行一百多个单位,我一直会遇到速率限制,即提供者对你每分钟可以发出多少 API 请求的限制。这是推动我使用 LLM 批 API 的原因。与其一次一个地发送单个提示并等待每个响应,主要的 LLM 提供者都提供批 API,让你一次提交一个包含所有请求的文件。提供者按自己的时间表处理它们;你等待结果而不是立即获得,但你不必担心速率限制。我很高兴发现它们的成本也低 50%,这是我开始认真跟踪令牌使用和成本的时候。
但真正的惊喜是,在规模上,批 API 的性能优于实时 API。一旦管道处理量超过 100 到 200 个单位,批处理开始明显快于实时处理。提供者在其基础设施上并行处理整个批量,因此你不再受往返延迟或速率限制的影响。转向批 API 改变了我对大规模协调 LLM API 调用整个问题的思考方式,导致了可配置管道的想法。我可以将阶段链接在一起:一个步骤的输出可以成为下一个的输入,我可以启动整个管道并返回到完成的结果。
事实证明我不是唯一做出转向批 API 转变的人。在 2024 年 4 月至 2025 年 7 月之间,OpenAI、Anthropic 和 Google 都推出了批 API,汇聚在相同的定价模式上:实时费率的 50%,以换取异步处理。
你可能没有注意到,三大 AI 提供商都发布了批处理 API。行业讨论主要围绕 AI 智能体、工具使用、MCP 和实时推理展开。批处理 API 的发布相对低调,但它们代表了我们使用 LLM 方式的一次真正转变。我们不再只是把它们当作对话伙伴或一次性 SaaS API,而是可以将其视为处理基础设施——更接近 MapReduce 作业,而不是聊天机器人。你向它们提供结构化数据和提示词模板,它们会处理所有数据并返回结果。关键在于,你现在可以可靠地、大规模运行数以万计的此类转换,而不必管理速率限制或连接故障。
既然批处理 API 如此有用,为什么不能直接写一个 for 循环来提交请求并收集结果?当然可以,而且对于简单场景,一个带有 for 循环的简短脚本完全够用。但一旦开始运行规模更大的工作负载,问题就会接踵而至。事实证明,解决这些问题是形成结构化智能体工程方法过程中最重要的经验之一。
首先,批处理作业是异步的。提交作业后,结果可能要数小时后才会返回,因此脚本需要记录提交了哪些内容,并轮询作业是否完成。如果脚本中途崩溃,这些状态就会丢失。其次,批处理作业可能出现部分失败。也许 97% 的请求成功了,另有 3% 失败。代码需要找出失败的是哪 3%,将其提取出来,并且只重新提交这些项目。第三,如果你正在构建一个多阶段流水线,其中一个步骤的输出会成为下一步骤的输入,就需要跟踪各个阶段之间的依赖关系。第四,你需要进行成本核算。当运行数以万计的请求时,你会想知道花了多少钱;理想情况下,在批处理刚开始时就知道将要花费多少。这些问题中的每一个,都与智能体工程中的工作直接对应:跟踪多个 AI 智能体同时在做什么;处理代码故障和 bug;在 AI 编码工具只关注当前上下文中的局部内容时,确保整个项目仍然保持一致;以及退后一步,从更宏观的项目管理视角审视全局。
所有这些问题都可以解决,但你肯定不想一遍又一遍地解决它们——无论是在编排 LLM 批处理作业,还是在编排 AI 编码工具。通过代码解决这些问题,也为智能体工程的整体方法带来了一些有趣的启示。批处理将复杂性从连接管理转移到了状态管理。实时 API 的难点在于速率限制和重试;批处理 API 的难点则在于必须跟踪哪些任务正在执行、哪些已经成功、哪些失败了,以及接下来该做什么。
开始开发之前,我查找了能够处理这一系列问题的现有工具,因为我不想浪费时间重复造轮子。但我没有找到能够满足需求的工具。Apache Airflow 和 Dagster 等工作流编排器可以管理 DAG 和任务依赖关系,但它们假设任务是确定性的,也不提供 LLM 特有的功能,例如提示词模板渲染、基于 schema 的输出验证,或者由语义质量检查触发的重试逻辑。LangChain 和 LlamaIndex 等 LLM 框架围绕实时推理链和智能体循环设计,并不负责管理异步批处理作业的生命周期、在进程崩溃后持久化状态,或者在分块层面处理部分失败的恢复。而各提供商自己的批处理 API 客户端库,只负责单个批次的提交和结果获取,不支持多阶段流水线、跨步骤验证或提供商无关的执行方式。
在我找到的所有工具中,没有一个覆盖多阶段 LLM 批处理工作流的完整生命周期:从提交和轮询,到验证、重试、成本跟踪和崩溃恢复,并且同时支持三大 AI 提供商。这正是我构建的东西。
作为智能体工程和 AI 驱动开发系列的第一篇文章,本文的目标是介绍 Octobatch 实验的假设与结构。本系列后续文章将深入探讨我从中获得的经验:验证架构、多 LLM 协作、工作过程中形成的实践与价值观,以及将所有这些内容联系起来的编排思维。其中有几条早期经验尤为突出,因为它们说明了 AIDD 在实践中是什么样子,以及为什么开发者体验比以往任何时候都更加重要。
你必须实际运行程序并检查数据。还记得那个醉酒的水手——蒙特卡洛模拟中的“Hello, world”吗?有一次,我注意到通过 Octobatch 运行模拟时,77.5% 的水手掉进了水里。随机游走的结果应该是 50/50,因此显然出了严重问题。事实证明,随机数生成器在每次迭代时都会使用连续的种子值重新设置种子,从而在不同运行之间产生了相关性偏差。我没有立刻定位到问题;我使用 Claude Code 作为测试运行器执行了大量测试,让它生成每个测试、运行测试并记录结果;随后 Gemini 检查了这些结果并找到了根本原因。Claude 很难提出一个效果良好的修复方案,最后建议采用一种变通办法:在流水线中放入一长串预先设定种子的随机数值。Gemini 在审查了我与 Claude 的对话后,提出了一个基于哈希的修复方案,但它看起来过于复杂。理解问题并否定它们提出的解决方案后,我认为最佳修复方案比这两个 AI 的建议都更简单:为每个模拟单元使用一个持久化的随机数生成器,让它自然地沿着自身序列推进。为了评估这三种方案,我需要同时理解统计学和代码。看似合理的输出与正确的输出并不是一回事,你必须具备足够的专业知识,才能分辨二者。(我们会在本系列的下一篇文章中进一步讨论这种情况。)
LLM 经常高估复杂性。有一次,我想在分析流水线中加入对自定义数学表达式的支持。Claude 和 Gemini 都表示反对,告诉我:“这对 v1.0 来说属于范围蔓延”和“把它留到 v1.1 吧”。Claude 估计实现它需要三个小时。因为我熟悉这个代码库,所以知道我们已经在其他地方使用 asteval——一个为数学表达式和简单 Python 语句提供安全、简洁求值器的 Python 库——来计算表达式,因此这看起来只是直接复用已有库即可。两个 LLM 都认为解决方案会比实际情况复杂得多、耗时也长得多;最终只需要向 Claude Code 提交两个提示词(由 Claude 生成),总共大约五分钟就完成了实现。这个功能最终顺利发布,并显著增强了工具的能力。AI 之所以表现得保守,是因为它们不了解我掌握的系统架构上下文。经验告诉我,这项集成微不足道。如果没有这些经验,我可能会听从它们的建议,推迟一个只需五分钟就能完成的功能。
AI 往往偏向于添加代码,而不是删除代码