代理开发的合理规格程度
本文探讨了在代理开发中规格说明的必要性,指出零规格和过度规格都有成本,最佳平衡点取决于任务类型,并强调了规格验证和可执行测试的重要性。
在关于代理的讨论中,我不断看到同一个观点:详细的规格说明已经是旧世界的开销了。给模型一个粗略的目标,让它探索,修复返回的结果,然后继续前进。这听起来很高效,但它也隐藏了成本。
简单的提示看起来便宜且诱人,因为它能立即开始实现。然后修正循环开始了。你审查输出、澄清意图、要求修改、重新运行测试、发现下一个差距,然后再次重复。仍然需要有人决定结果是否符合真正的目标。那个人成为了预言机。
在另一个极端,完整的正式规格说明显然前期成本很高。编写验收标准、契约测试或行为驱动开发(BDD)场景需要真正的努力。但下游成本不同,因为更多的预言机是可执行的。测试每次检查相同的条件。它不会在午餐前五分钟感到疲倦、匆忙或乐观。
这才是真正的权衡。问题不在于规格是好是坏。而在于最小总成本在哪里。对于大多数代理工作,它处于中间位置:足够的结构来约束工作,足够的例子使意图具体,以及足够的可执行检查,使得审查不会变成猜测。
零规格不是智能和精益的;它只是成本高昂的“氛围编码”。
瓶颈转移了,但没有消失
软件工程从来不仅仅是打字甚至生成代码。它关乎决定应该存在什么、永远不应该发生什么、哪些权衡重要、以及一旦问题触及现实世界,“完成”意味着什么。
多年来,团队通过人类摩擦发现了缺失的规格。审查者注意到边缘情况,QA发现了没有被描述的路径,高级工程师在头脑中承载了一半的真实需求,并一次会议地翻译它们。这些都不优雅,但它确实迫使模糊性暴露出来。
代理从根本上改变了这一点。它们使实现变得更便宜、更快。这也意味着一个未充分说明的想法可以在任何人真正同意系统应该意味着什么之前变成一个看似合理的系统。
在旧世界中,模糊的需求遇到了人类的缓慢。在代理世界中,模糊的需求遇到了机器的速度。
这就是为什么规格突然又感觉重要了。它一直很重要。我们只是用实现成本作为粗略的强制函数,并称结果为流程。
随着实现变得更便宜,更多的困难转移到决定什么是对的并可靠地检查它。
编写规格是不够的
这是我认为人们最常跳过的部分。他们谈论时似乎顺序很简单:编写规格,然后让代理实现它。缺失的步骤是昂贵的部分。
规格本身需要审查。
即使是精心编写的规格也可能以熟悉的方式失败。它可能自相矛盾,或者只覆盖快乐路径而对重试、速率限制或部分失败毫无帮助。它可能描述听起来精确但实际无法验证的行为。有时它在完全错误的方向上精确:它说了你写的东西,而不是你的意图。
当代理忠实地执行有缺陷的规格时,故障更难以诊断。实现可能看起来连贯。它甚至可能通过你提供的检查。但真正的问题在上游,在规格中,所以修复它意味着同时解开代码和推理。
这就是为什么我认为规格验证应该有自己的项目。在实现开始之前,需要有人问几个简单的问题。这内部一致吗?对于这个任务足够完整吗?哪些部分是可测试的?我们还在哪些地方依赖人类判断?哪些失败模式因为每个人都默默假设而缺失?
代理可以在这里帮忙,但只有当我们用它们来做比“编写需求”更有用的事情时。那个提示通常会产生打磨过的迷雾。更好的提示要具体得多:
起草最小的规格,使得另一个代理能够安全地实现它。包括假设、非目标、验收标准、边缘情况、可观察结果和开放问题。标记哪些声明可以变成自动化测试,哪些仍需要人工审查。
之后,将草稿交给另一个代理,并告诉它攻击结果:
找出矛盾、模糊术语、隐藏依赖、不可测试的声明、缺失的失败模式,以及实现可能通过书面标准但违反意图的地方。
即使这个简单的工作流程也降低了达到值得人类判断的规格的成本。
代理并没有消除对规格的需求。它们使达到实际上有用的详细程度变得更便宜。
为什么多代理系统需要更强的合约
一个在小的、有界任务上工作的单个代理通常可以从宽松的指令中恢复。循环紧密,爆炸半径局部化,人类通常可以在它偏离时将其引导回来。人类甚至可以很容易地发现偏离。
多代理系统是一个非常不同的问题。一旦一个代理的输出成为另一个代理的输入,解释性偏差开始累积。代理B不知道代理A误解了需求的10%。它只是将输出视为基本事实并继续前进。到人类看到结果时,原始错误可能已经埋藏在几层看起来很能干的工作之下。
此时,规格不再只是指导,而更像是一个合约。
那个合约需要的不止一段意图。它需要模式、不变量、允许的歧义、验证规则和明确的失败行为。在许多情况下,还需要契约测试、类型化接口和机器可检查的交接格式。交接是产品的一部分,这不如人们希望的那么光鲜,但更接近现实。
这也是BDD和可执行验收测试所属的地方。它们的价值不仅仅是方法论,而是它们将部分人类预言机变成了可重复的东西。当行为足够稳定可以精确指定时,可执行规格通常比另一轮审查更便宜。
一旦代理开始将工作交给其他代理,交接本身就需要像真实接口一样被指定和验证。
规格应该有有效期
团队在这里犯的另一个错误是:当他们继续在规格曲线上推进时,仿佛更多文本总是更安全。事实并非如此。至少对于当前的模型不是。
Chroma关于上下文腐烂的工作揭示了问题的第一部分:模型性能随着输入增长而变得不太可靠,即使在简单任务上也是如此。在编码项目中,还有第二个问题。你放入上下文的设计散文、示例、计划、注释、工单和旧验收标准越多,哪些部分是指令、哪些部分是工件就越不明显。
我不会称其为安全意义上的提示注入。没有人试图攻击模型。它更接近于自我造成的指令漂移。上下文中包含旧的设计意图、当前实现、一半有效的示例、三个会话前生成的计划,以及可能仍然描述已不存在类的过时软件设计文档。此时,模型不是在读一个规格,而是在多个相互竞争的真相来源之间取平均。
这就是过度规格停止帮助并开始混淆模型的时候。代理无法判断一个段落是活跃的需求、历史记录,还是代码已经替换的东西。
设计文档在早期很有用,因为代码还不存在。后来,它需要缩小。一旦接口、测试和不变量成为现实,详细的构建计划应该开始消失。“保留代码本身难以表达的部分:业务原理、非目标、安全约束、外部合约,以及你不想通过试错重新发现的少数不变量。删除那些只是重复类和方法已经做的事情的散文。
否则,你最终会有两个规格。人类会在审查中抱怨这一点。代理通常会试图同时服从两者。
API可以让代码表现得像规格
这个故事也有一个更乐观的版本。一些代码库比其他的更快达到“代码即规格”的程度,而API设计是一个重要原因。
如果内部API通过约定、弱类型参数、设置魔法和通用错误隐藏行为,代理无法将代码视为规格。它必须从分散的散文和试错中重建规则。这对人类很慢,对模型更糟。
反之亦然。一个具有显式名称、任务级方法、强类型、可读验证、有用示例和可操作错误的API给代理提供了具体的基础。如果代理可以检查表面区域,看到方法的作用,理解什么输入是合法的,并从错误中恢复而无需猜测,那么代码自己承担了更多的规格负载。
这就是AI友好的API设计思想在实践中重要的地方。显式的可发现性优于约定。方法应该与实际任务对齐,而不是迫使代理通过十几个脆弱的步骤。类型和验证应该显示合法输入的样子。错误消息应该指向下一步修复,而不仅仅是宣布失败。内省和示例帮助模型从已有的代码库中学习API的形状。性能透明度也很重要,因为如果API没有给出线索,代理会愉快地围绕昂贵调用编写正确但糟糕的循环。
这不仅关乎公共SDK。它适用于内部服务边界、库客户端、仓库抽象,甚至大型单体仓库中的辅助类。API越容易发现和检查,代理就越容易将代码视为权威规格,而不是将更多散文拖入上下文。我之前更深入地写过所有这些,如果你感兴趣的话。
投资方向
我坚信的是,没有单一的合适规格程度。答案取决于你正在做的工作类型。对于小的、有界的任务,最佳点通常是结构化意图:目标、几个例子、非目标和清晰的验收标准。这通常足以让代理保持高效,而不会使设置比任务本身更重。
对于确定性工作,如CRUD流程、API集成和数据转换,最优值向右移动。这些领域容易约束且容易测试。更多规格很快就能自偿,因为它减少了重复审查和返工。这是BDD、契约测试和可执行验收标准最有帮助的地方。
对于探索性工作,如架构选项、研究综合或新颖产品想法,最优值向左移动。过度规格可能会扼杀使代理有用的灵活性。在这种情况下,我宁愿指定边界而不是结果:必须为真的事情、绝不能发生的事情、需要哪些证据,以及哪些决策仍然需要人类。
对于多代理管道,最优值再次向右移动。每个代理之间的边界需要一个合约。没有这个,你不是在协调一个系统。你是在堆叠解释,并希望它们互相抵消。
没有普遍的最优值。合适的规格程度取决于工作是探索性的、有界的、确定性的还是多代理的。
所有四种情况下的共同规则很简单:在扩展实现之前验证规格。
从敏捷和极限编程中幸存下来的部分
我不认为代理使敏捷或极限编程变得无关紧要。它们使有用的部分更容易与人们已经容忍的部分分离。
第一个牺牲品是那些主要是为了协调人类努力而存在的仪式。