我们需要结构化的 Agentic Coding 吗?

本文纯人工撰写,无 LLM 成分。请放心阅读。

现如今,我们有大量基于层次化拆解需求的 Agent 框架,比如 Spec Kitoh-my-openagent 的 Prometheus,以及 Kiro 的 Spec 模式等。它们通过事先调研,把一个大型需求拆解为数十甚至上百个子任务,为每个任务制定严格的完成标准,随后通过子 Agent 根据依赖关系串行或并行地执行这些任务。笔者曾大量使用 Prometheus,但逐渐发现这种方法带来的问题往往多于好处。现在我反而回到了最朴素的用法:在 plan 模式下与 LLM 讨论需求,然后直接生成。考虑到前沿 LLM 的现状,我认为各种结构化的开发模式很可能不仅带不来好处,反而阻碍了 Agent 的工作。下面我会逐一分析结构化开发模式的基本假设在当下 Agentic Coding 的实际情况中是否适用。

关于并行

在传统的软件开发中,实际编写代码需要消耗程序员大量的人力劳动,往往是一个项目中比较费时的部分。因此我们需要在这部分中安排许多人手,于是就不得不设计一套机制以恰当地协调他们的工作。一个程序员无法在可接受的时间里完成一个大型项目的开发,这是我们只能加以接受的事实。

然而在 Agent 时代,这一情况发生了改变。单个 Agent 开发代码的速度往往达到或超过了一个人深入思考并合理设计需求的速度。此外,目前前沿 LLM 的价格较为昂贵,大规模的并行 Agent 往往是个体开发者以及需要降本增效的企业所承受不起的。因此我认为,在大部分场景下,聚焦于如何让一个或少数几个 Agent 高质量地工作,比如何协调大规模 Agent 并行工作,对于通常的软件开发更加重要。

任务的粒度

这些结构化开发方法通常会在项目开始时,由一个会话深入研究用户的模糊需求,生成一份需求文档,而这份文档会在随后的开发阶段被视为几乎不可违背的金标准。早期的 LLM 实现质量很不可靠,因此需要用这样的方法时刻加以约束,防止其跑偏。

但如今的 LLM 性能已经大幅增强,并且普遍配备了 1M token 的上下文,而非此前的 200K。上下文长度对 Agent 能力的影响会随任务形式而变化:对于一个本来就能在 200K 上下文内完成的简单任务,提高上下文长度不会有任何改变;对于一个需要 100K token 的背景知识才能开展的工作,把上下文长度从 200K 提升到 1M token,意味着九倍的实际可用空间;而如果这个任务比较难,需要 300K token 的背景知识,那么 200K 上下文的模型就只能被迫在未充分理解相关信息的前提下工作。总的来说,上下文长度的增长意味着你可以向单个会话分派更多的工作,而不必强行把高度相关的一大块工作拆成两部分。因此你可以期待新一代的 LLM 可以在一个会话内完成更多的开发工作,交付更大块的成果。在这样的前提下,结构化开发方法中对任务的细粒度划分就显得过于低效了。

如果 Spec 出错

结构化开发工具的常见用途是从零开始开发一款小软件,或者在已有软件上开发一个新功能。这里涉及到的对象通常是从技术角度看比较常规的软件,其各组成部分的开发难度与最终目标是比较容易推测的。然而当 LLM 的实力逐渐增强,我们开始期待它从事一些更有挑战性的、带有一定研究性质的任务。当一个开发任务具有研究性质时,结构化开发方法很有可能会成为严重的阻碍。

我们粗略地定义一个任务的研究性为:从头开始达到最终结果所需的时间,与给定一份对最终方案的详细说明(但没有任何代码)后实现该方案所需时间的比值。一个典型的研究性强的任务是学术论文:从一个初步的想法到敲定最终方案的过程需要大量的智力劳动与实验试错,然而最终的产物可能只包含很少的代码。对于研究性强的任务,其各组件所需要满足的指标、甚至是组件划分本身,都是需要通过实验探索才能确定的。当我们把结构化开发方法应用到这种任务上时,要么得到一个欠考虑的 Spec,要么让初步研究并生成 Spec 的过程变得如此之长,以至于抵消了后续开发阶段节省的时间。实际的情况往往是前者。

当一个 Spec 设计不合理,同时又被要求强制遵守时,往往会发生很严重的后果。LLM 一向很不愿意承认自己无法达成任务,相反,它们会尽一切努力尝试绕过限制,以有违常理的方式强行宣称达到了任务目标。当大量这样的组件被组合起来时,LLM 可能直到最后的阶段才能发现,看似正常推进的项目已经千疮百孔,而这时开始补救往往困难重重,甚至只能推倒重来。这显然是我们所不愿看到的。

持续迭代

书不尽言,言不尽意。很多人抱怨 LLM 理解模糊指令的能力很差,这固然是一个可以改良的问题,但我觉得它是无法消除的。你永远无法根据几句话构建出完全符合自己心意的软件,这就需要开发者在软件构建的过程中恰当地给出反馈,引导开发过程走向自己所期望的方向。而结构化的开发方法恰恰反对这一做法,相反,它期待在编码工作开始之前,也就是需求确定阶段,就解决所有这类问题。对应到实际情况,就是期望用户认真阅读 LLM 所生成的每一行 Spec,并针对自己不满意的部分作出指示。这实在是抵消了 Agent 编程带来的很多好处,也要求用户必须有基本的技术背景以便正确审阅 Spec;而当用户缺乏此种能力,或者不愿操心直接一路通过时,就只好等待可能长达数小时的完整开发流程结束,然后在最终验收时才发现结果与自己的预期不相符合了。

如果一个任务需求模糊,那么就需要在执行过程中持续与用户同步并收集反馈;如果一个任务富于研究性,那么就需要不断地根据最新的实验结果修正整体计划。这两类问题都强调开发工作流的持续迭代能力,而这正是结构化的开发方法所不擅长的。

最佳的协作

上述讨论说明,静态的树状划分是一种低效且不能充分利用 LLM 实例的方式。我认为我们需要把更多精力投入到如何协调少数几个 Agent 之间的高效协作上,正如《人月神话》中所推荐的外科手术室团队一般。我在实践中经常使用以下两种双 Agent 工作模式,姑且称为 Reviewer 模式和 Orchestrator 模式。非常有趣的是,这两种模式恰好形成了某种形式的对偶关系。

在 Reviewer 模式中,主 Agent(也就是直接接收用户输入的 Agent)进行主要的开发工作,而当其进行到一定阶段时,便会调用一个只读的子 Agent 检查其上一阶段的工作,确保没有严重偏离原本意图,或引入了不应有的设计失误。诚然,这需要依赖主 Agent 为子 Agent 撰写提示词,不过据我观察,LLM 在大部分时候都会以诚实的态度撰写提示词,而不会暗示 Reviewer 粉饰太平。这种模式比较适合较短的任务,或者大量依赖屏幕前开发者即时反馈的任务,因为你可以在主 Agent 的界面中直接看到开发过程并加以干预。

在 Orchestrator 模式中,主 Agent 负责从全局角度理解一个任务,并将具体的开发工作分派给子 Agent,只提供很少的必要上下文以明确工作的范围。这一模式允许总体架构的存在,但同时保留了更大的弹性,允许根据某一子 Agent 给出的预期之外的结果来修改后续的整体计划。这一模式更适合需要长时间自主运行的任务。

除了 Agent 的组织方式本身,这两种模式还具有一些实用角度的优点:它天然地包含两个异构的角色,你可以很容易地为它们指派不同的模型——比如某种模型擅长编码、而另一种擅长分析,或某种模型额度充足、而另一种额度有限,这些特性都可以很好地被利用。此外,两个模型共同参与决策,也可以减轻单一模型所具有的偏见。以 Anthropic 为代表的部分公司禁止逆向其订阅服务接口并接入其他工具,而这种方法既然不需要额外的辅助设施,也可以规避掉这一问题——直接让主 Agent 通过 CLI 调用子 Agent 就好了。这种方法简单易行,且完全没有逆向接口带来的灰色地带风险。

结语

Spec 类方法的优点在于遵循软件工程的最佳实践、允许并行开发、并且具有可控的质量保证。但人类的软件工程未必适用于 LLM;前沿模型的高速度和高成本使得并行开发并非必选项;开放型任务中 Spec 提供的质量保证反而会成为对质量的伤害。因此,我认为近似串行的工作流与一到两个 Agent 的角色分工,才是最适于发挥 LLM 潜力的协作方式,并在此分享了自己的一些实践心得。