Agent开发范式转型:从spec coding到意图驱动与验证驱动
前几天帮一个团队梳理 Agent 开发流程负责人很认真地跟我讲他们第一步先把业务需求写成一份完整的 spec字段、接口、边界条件全部列清楚然后让 Agent 照着 spec 把代码写出来。听完我只问了一句你的 Agent 在真实环境里跑了一周之后被用户纠正过几次他愣了一下说好像还挺多次的。这就是 spec coding 这条路最典型的幻觉——你以为自己在给 Agent 传递需求实际上只是在给一段静态文本做一次性的押注。我写这篇文章不是要否定规格文档本身而是要说说为什么在 Agent 开发这个场景里spec coding 已经从可用的方法论变成了拖后腿的旧习惯以及现在用意图驱动加验证驱动的方式到底应该怎么落地。如果你正在做 AI Agent 开发或者打算转行做 Agent 方向手里也囤了一堆agent开发学习路线agent开发教程之类的东西大概率会被灌入一个经典流程理清楚业务需求、写规格、建模、再让模型生成代码。这套流程在传统软件工程里没问题但放到 Agent 开发里它恰恰是最容易翻车的一步。这篇文章适合所有想把 Agent 做成能自我纠错、能应对真实需求漂移的开发者也适合那些还在纠结为什么我的 Agent 明明照着 spec 写的跑起来还是一团糟的团队。1. spec coding 曾经是对的为什么先写规格再写代码会有市场1.1 传统软件开发留下的惯性先说清楚我指的 spec coding 是什么。它不是指写接口文档也不是指写 PRD而是指一种更深层的开发方式在让大模型写代码之前先穷尽式地把需求描述为机器可解析的规格——用户故事、验收条件、字段定义、接口签名、异常分支全部落到文字上然后让模型照着这份规格把代码生成出来。说白了就是把大模型当成一个严格的函数输入 spec输出 code。这个思路在传统软件开发里确实有市场也经得起考验。你看传统的软件工程无论是瀑布模型还是敏捷里的用户故事拆解本质都是先定义再实现。因为人的认知带宽有限代码评审、测试用例、维护成本都要求你尽可能早地冻结需求。就连外包行业也一样一份合同如果没有清晰的规格验收的时候就是互相扯皮。所以先写清楚再动手成了绝大多数程序员刻在骨子里的肌肉记忆这套流程也被顺理成章地迁移到了 Agent 开发上。我见过很多团队的早期尝试都是从给 Agent 写 spec 开始的定义好用户输入什么系统返回什么定义好当库存不足时应该报什么错定义好这个路由应该调用哪个 service。在一开始这套做法真的能跑通。因为那时候的 Agent 应用大多是窄场景、单步骤、可枚举的比如一个表单校验器、一个根据关键词检索文档的工具、一个把 Markdown 转成 HTML 的脚本。任务边界清晰失败模式有限规格能把不确定性压到最低。1.2 spec coding 在早期 Agent 时代的真实价值还有一个现实问题早期大模型的指令跟随能力没那么强一个含糊的需求它可能给你生成完全跑不通的代码。Spec 在这里扮演的角色其实是降噪器。你把边界条件列得越细模型越不容易自由发挥也越容易被验证对错。这个阶段的核心诉求是让生成结果稳定而 spec 是当时唯一有效的稳定手段。再加上团队协作上的便利。一个 Agent 项目如果参与的人多总得有份文档同步认知不然前端一个说法、后端一个说法最后模型生成的接口谁也对接不上。Spec 在这里就是一个对齐工具。我记得有个朋友做内部知识库 Agent他们团队就是靠 spec 把十几个检索接口的字段对齐的没有 spec 的话连 prompt 都写不下去。所以我要先把这个背景讲清楚spec coding 不是从一开始就错它在特定条件下确实解决过问题。1.3 你依赖的 spec 其实是在跟编译器说话而不是在跟 Agent 对话但这里有个关键的认知陷阱spec coding 的本质是把 Agent 当成编译器来对待。传统编译器接收的是语法严格、语义确定的源代码你给它一份精确到字节的规格它就能给你一个确定性的输出。可 Agent 不是编译器它是一个在环境中不断感知、推理、行动、再感知的循环体。它面对的不是输入秒返回的纯函数问题而是这个步骤做完之后下一步该做什么的序贯决策问题。你把一份 spec 丢给 Agent就像给一个人发了一张十年前的地图然后让他去一个每天都在拆迁的城市里送外卖。地图没错城市也没错错的是你把静态描述当成了动态现实的替代品。这个区别在传统开发里不明显因为你交付的是一个程序程序的行为在发布那一刻就固化了。但 Agent 交付的是一个持续运行的决策体它在第一分钟、第一小时、第一周面对的世界完全不一样。用 spec 去框定它本质上是在用一个旧时刻的快照去约束一个不断流动的未来。我第一次意识到这个问题是在一个客服工单 Agent 上。当时我们的 spec 写得极其详细甚至连用户骂人的时候应该怎么回复都有模板。结果上线第二天一个用户提出了一个 spec 里完全没有覆盖到的诉求他要求先把一个历史订单拆单再把其中一个子订单改地址然后还要用原订单的优惠券。这个路径在我们的规格表里不存在Agent 当场就卡死了。它不是不会写代码它是没有面对意外路径继续推理的机制。那一刻我才明白spec coding 教会 Agent 的是按图索骥而真实世界教给我们的却是图永远赶不上变化。2. 当 Agent 从执行器变成协作者spec 的粒度假设开始失效2.1 任务不确定性的本质变化为什么 spec 在 Agent 开发里越来越使不上劲最根本的原因是任务不确定性的位置变了。在传统程序里不确定性集中在需求层需求想清楚之后代码就是确定性的翻译。而在 Agent 开发里不确定性分布在三层需求层之外还多了环境层和意图层。环境层的不确定性是 Agent 运行过程中工具返回的结果、用户输入的口语化表达、外部系统的状态随时在变。意图层的不确定性是用户的真实目标往往只通过两三句模糊的话表达出来Agent 必须追问、澄清、判断。这两个层面都没有办法用静态 spec 提前穷举。你见过哪个 spec 能提前写出第 37 次对话时用户会突然把话题从退款转到投诉物流的如果真有人写出来那已经不是 spec 了那是一份剧情脚本。我在做 Agent 开发时有一个很强烈的体感一份规格文档写得越长Agent 面对新情况时的灵活性就越差。因为模型会把规格当成一个强约束凡是规格之外的行为它倾向于不执行。这就像给小孩立了太多规矩结果小孩连基本的自主判断都丧失了。你真正想要的是一个在约束范围内自由行动的 Agent而不是一个在语义牢笼里做文字填空的程序。2.2 需求漂移与规格冻结的矛盾还有个更现实的问题业务需求本身就在漂移。传统软件工程里我们可以通过需求冻结来保护开发周期但 Agent 开发做不到因为 Agent 往往直接面向最终用户需求是长在真实对话里的。用户今天跟你说帮我整理报销单明天可能变成帮我从报销单里找出超预算的项并给财务发提醒后天又变成每周五自动生成本周的报销异常报告。这个演进过程如果每次都要先改 spec、评审 spec、再让 Agent 重读 spec开发节奏根本跑不起来。我可以拿一个真实的 Django 项目举例。我们当时做一个内部订单管理系统打算用 Agent 辅助开发让 Agent 先读 spec 生成 model 和 view。第一版 spec 写了字段、校验规则、接口路径开发速度确实快一天就生成了基本骨架。但第二天业务方就来了新需求订单要加一个取消原因分类而且取消后的库存要回滚到上一个操作节点。按 spec 逻辑我们得先改文档再让 Agent 重新生成迁移脚本。结果 Agent 生成的迁移脚本直接删掉了两个原有字段因为 spec 里没写这两个字段是否要保留。后来我把 spec 里的字段定义改成必须通过的测试用例让 Agent 自己去看现有代码、跑测试、再动手迁移反而再没出过这种低级事故。这个案例给我的启示是Agent 开发里的需求不是被冻结的而是被验证的。与其花三天把 spec 写得完美无缺不如花三小时把验证路径搭好然后让 Agent 在真实环境里不断试错。规格冻结只会让你延迟发现需求已经变了而验证驱动能让你第一时间感知变化。2.3 一份 200 行 spec 被 Agent 改掉 80% 的真实案例去年我帮一个朋友看他的数据分析 Agent他们团队走了完整的 spec coding 路线PRD、接口矩阵、异常码表、级联流程图洋洋洒洒两百行。结果怎么样Agent 生成的代码第一版就跑不通问题恰好出在 spec 最自信的地方——他们把数据清洗规则写死了认为所有输入数据都必须符合日期统一 YYYY-MM-DD金额统一保留两位小数的格式。实际数据里日期有 2024.1.1、有 2024/01/01、还有一月一号金额有三位小数、有货币符号、还有千分位逗号。真实世界的数据根本没有按 spec 出牌。后来他们换了一种方式不再维护那份 200 行的 spec而是把清洗规则写成一堆可执行的测试用例——输入 2024.1.1 应该输出 2024-01-01、输入 1,234.5元 应该输出 1234.50。每次 Agent 修改代码就跑一遍测试集挂了就带着报错信息回去改。改动最频繁的那一周Agent 自己重写了 80% 的清洗逻辑最后正确率从 61% 提到了 92%。那份 spec 的结局是被丢进了 archive 文件夹。这个案例特别典型spec 描述的是你应该怎么做测试用例描述的是你要做到什么效果。对于 Agent 这种会自己想办法的系统后者远比前者重要。你把怎么做规定死了Agent 的自由度没了你把做到什么效果规定清楚Agent 反而能在试错中找到更好的路径。3. 决定落后与否的关键Agent 的调试对象变了3.1 旧模式调试的是代码spec 是一次性需求文档传统开发里程序员调试的是什么调试的是代码。Spec 是需求文档它只参与开发流程的起点代码写完、测试一过spec 基本就成了历史档案。后续维护、排查 bug你读的是代码不是 spec。就算代码和 spec 不一致那也是改代码或者改 spec但调试动作本身永远是围绕代码展开的。到了 Agent 开发里这个逻辑开始失灵。你的 Agent 跑出来的代码只是一条长链中的一个环节。真正需要你调试的是 Agent 的推理链路它看到什么上下文、选择了哪个工具、基于什么依据做出了判断、路径走偏之后有没有自己绕回来。这些东西不是 spec 能描述的也不是静态代码审查能覆盖的。我见过太多团队在 Agent 出了问题之后第一反应是去翻 spec规格上明明写了应该先调用订单接口再调用库存接口。但问题的根源根本不在调用顺序而在 Agent 的上下文里根本没有足够的信息判断当前该调用哪个接口。3.2 新模式调试的是推理链和上下文spec 缩成动态约束集所以在 Agent 开发里核心调试对象变了两样推理链和上下文。推理链指 Agent 从接收任务到最终输出的完整决策路径你必须能随时查看它每一步的理由。这也是为什么现在主流框架都在做 trace、做观察面板的原因——没有 traceAgent 出错了你连从哪下手都不知道。我自己的习惯是每一轮关键行动都让 Agent 输出一个决策摘要我为什么选这个工具、我看到了什么、下一步计划是什么。这样出了问题之后我能像对着 X 光片一样一眼看到是哪一步推理出了偏差。上下文指 Agent 在决策时手里掌握的全部信息包括系统提示、用户消息、工具返回、历史记录、临时备注。这里有个反直觉的点给 Agent 的上下文不是越多越好。上下文一多模型容易丢失关键信号再加上 token 成本、响应延迟实际效果往往比精简上下文还差。Spec 在新范式里的角色其实应该从每时每刻都必须遵守的完整画册变成只在关键决策点引用的一组动态约束。比如金额字段必须始终以分为单位存储所有对外输出都必须经过脱敏检查这类约束保留但清洗规则应该怎么写这类实现细节不再放进 spec而是放进测试用例。3.3 从规格驱动到意图驱动 验证驱动的新路径那正确的路径是什么我现在比较认可的思路是意图驱动 验证驱动的组合。意图驱动不是说不要规格而是把对用户真实目标的持续澄清作为开发的第一优先级。Agent 不能假设它第一次就理解了用户意图它必须主动追问、确认、复述并在后续对话中根据用户的反馈不断修正自己的理解。这个能力比准确的接口返回值更重要。你可以把意图理解想象成谈恋爱你不能因为对方第一天答应了你就认为以后每天都应该按照第一天的约定来生活。关系是动态维护的意图也是。验证驱动则是把效果可测量作为开发的第二优先级。你给 Agent 的每个行为变化都配套一个可执行的验收机制。测试用例是最常见的一种日志断言、工具返回检查、端到端演练也都算。核心原则是Agent 每次改动之后你能在几分钟内知道它改对了没有而不是等到上线之后由用户来替你验收。为了把这两条路径讲得更清楚我做个对比表格开发维度spec coding规格驱动意图驱动 验证驱动需求来源需求被提前穷尽并写进文档需求在交互中持续澄清和演化交付物重心规格文档、接口定义可运行的 Agent、验证用例、trace 日志变更处理先改文档再改代码先改验证逻辑再让 Agent 自我调整出错调试对照 spec 查代码查看推理链、上下文、测试报错不确定性容忍度低依赖穷举高依赖反馈循环核心风险需求漂移、规则之外失能验证不全面、意图误判我之前带过一个 Agent 开发的新人她一开始特别执着于写 spec每次改动需求都要先把文档更新一遍。我告诉她一个很简单的判断标准你写的这段 spec如果删掉之后 Agent 依然能靠测试用例跑对那它就是在浪费体力如果删掉之后 Agent 立刻跑错那它才是真正的约束。从那以后她写的东西越来越少但 Agent 的稳定性反而越来越高了。4. 验证驱动开发在 Agent 场景里的落地做法任务沙盒、测试回环与上下文压缩4.1 把 spec 拆成任务沙盒而不是规格书既然 spec coding 不再是主角实际开发时第一步应该做什么我的答案是搭任务沙盒。任务沙盒可以理解为一个最小可运行的实验环境一个临时的数据源、一组模拟的工具接口、一套可以随时触发的验证脚本以及你自己写的 prompt 骨架。开发一个 Agent 功能时第一步不是铺开文档而是把这个沙盒跑起来哪怕只能处理一个最最简单的场景。举个例子我要让 Agent 辅助开发一个 Django 订单模块。我不会先去写 100 行的接口规格而是先把本地数据库跑起来造几条假订单数据然后给 Agent 一个非常简单的任务提示你是一个 Django 后端助手。当前仓库里已有一个简单的 Order 模型字段包括 order_id、amount、status、created_at。你的任务是 1. 检查现有 model 和 migration 状态 2. 新增一个 pending_reason 字段允许为空 3. 写一个函数 get_pending_orders()返回所有 statuspending 的订单且按创建时间升序排序 4. 跑通 pytest 里的订单测试测试文件在 tests/test_orders.py。 完成后用 git diff 输出你的改动摘要并说明每个测试用例的通过情况。注意这里我没有写字段长度、外键关系、排序方向这些实现细节我只写了目标和验证方式。Agent 可以自己去看现有代码自己决定怎么加字段、怎么处理迁移。然后我在沙盒里配套一个 pytest 文件里面是几条行为断言def test_pending_reason_can_be_null(db): order Order.objects.create(order_idSO-001, amount100.00, statuspending) assert order.pending_reason is None def test_get_pending_orders_sorted(old_order, new_order): orders get_pending_orders() assert list(orders) [old_order, new_order]测试用例就是 Agent 的导航系统。它不需要知道路径只需要知道终点。路径对不对跑一遍测试就知道了。这样做的另一个好处是Agent 改错了测试报错信息会直接回流到它的上下文里它自己就能迭代修复不需要你扮演一个人工调试器。4.2 测试回环把断言焊进 Agent 的工作流任务沙盒搭建好之后第二个落地动作是设计测试回环。这个概念我目前用的是三步循环第一步Agent 执行动作生成代码或修改配置第二步自动运行相关测试和静态检查第三步把失败信息、日志、trace 原样塞回 Agent 的上下文让它基于错误信息自我修复。然后循环直到测试全部通过。这套机制看似简单但有一个关键细节容易忽略你必须给 Agent 设置回环次数上限并且明确规定超过上限后必须停下来向人类求助。否则 Agent 会在同一个坑里无限打转浪费 token 不说还可能越改越乱。我见过很多团队踩这个坑Agent 一句话不说默默重试了 20 次每次都是同样的错误。根源就是没有设置放弃并报告的条件。测试回环里另一个实用技巧是失败快照。每次测试挂掉的时候把当时的输入、上下文摘要、Agent 决策理由、报错输出存下来作为这个 Agent 的学习样本。下次再遇到类似问题时你可以把这些样本作为 few-shot 示例塞进上下文。这比在 spec 里写一万字不要犯这个错误有效得多。因为模型学习的是具体的失败模式不是抽象的行为准则。4.3 上下文管理定期压缩比追加规格更重要还有一个容易忽视的点上下文管理。Agent 的对话历史和工具返回信息会不断堆积如果你一直往上追加内容最后模型会被淹没在噪声里。我的经验是给 Agent 设计一个上下文压缩机制目前最常用的有三种第一种是结论沉淀法。每一轮任务结束后把最终验证过的结论比如orders 表新增的字段是 pending_reason类型为 varchar(255)写入一个摘要节点旧的推理过程直接丢弃。第二种是定期裁剪法。超过 N 轮的历史消息只保留最近的关键决策摘要和用户的原始诉求其他全部删掉。第三种是外部记忆法。把需要长期保留的知识存到向量数据库或记录文件里Agent 在需要时检索而不是把全文放在上下文中。这个操作的意义在于spec coding 的思维是文档越全越好上下文越多越好但实际跑 Agent 的时候上下文质量远比数量重要。你想想如果让一个人带着 50 本操作手册去做客服他能全部记住吗不能。正确的做法是把手册压缩成一页速查表需要细节的时候再去查完整文档。4.4 工具链选型与团队协作上的配套调整在工具链上我目前的推荐组合不会太花哨代码仓库用 git测试框架用 pytestPython 生态或 vitest前端生态Agent 编排框架选择你熟悉的那一种就行重点是必须有 trace 能力。这里的核心不是用什么框架而是能不能看到每一步的推理过程。没有 trace 功能的 Agent 框架用起来就像在没有仪表盘的飞机里开盲飞团队里的工程师迟早会崩溃。团队协作方式的调整同样重要。过去你们可能习惯用文档评审来推进项目现在需要改成测试评审 行为演示。让 Agent 跑一遍关键流程展示它处理的对话记录和决策理由比读 50 行 spec 更能暴露问题。我在团队内部推行了一个规则每个 Agent 功能必须有三个产出物——可见的行为演示、可重复的验证脚本、一段不超过 200 字的运行说明。超出 200 字的说明往往意味着你的 Agent 链路设计得太复杂了。5. 什么时候还在用 spec coding 也合理什么时候必须转型5.1 适合 spec coding 的保留场景与判断标准写到这里我不想制造一个spec 一无是处的错觉。在某些场景里spec coding 依然是最高效的路径而且我不认为它会被彻底淘汰。什么场景我总结下来有三个特点任务边界可枚举、验收标准可自动化、运行环境相对封闭。典型的例子是根据 OpenAPI 文档生成 SDK 客户端。输入是一份规范明确的接口文档输出是确定的客户端代码验收标准就是编译通过 接口字段一一对应这个场景用 spec 驱动效率最高。再比如批量生成 CRUD 接口、根据数据库表结构生成 model 定义、按照设计稿生成固定布局的页面这些都是规格即是需求本身的场景。你想让 Agent 做得越规范越好越可预测越好那就给它一份越精确的 spec 越好。判断标准也很简单如果你的任务在人与人之间交接时只需要一份 excel 或 md 就能说清楚那 spec coding 就还是最合适的。真正需要转型的是那些人与人之间交接都得聊半小时才能对齐的任务——因为这种隐藏的不确定性不是文档能承载的必须靠交互和验证来消化。5.2 团队协作方式的变化从文档评审到小步验证我观察到从 spec coding 转型到意图驱动 验证驱动的团队协作节奏会有一个明显变化。以前是一个大阶段的文档评审后面跟着一个长周期的开发现在是每天都有无数次小步验证每一个改动都立刻在沙盒里跑一遍。这种节奏对工程师的素质要求也在变化你不再需要能把需求翻译成规格的人而是需要能为 Agent 构建反馈回路的人。换句话说你要从写文档让 Agent 听你的变成设计验证让 Agent 自己纠错。我记得有一次我们一个功能上线前业务方临时要求修改审核规则。放在以前这意味着要改一份规格书、重新评审、重新开发至少两三天。那次我们只改了三个测试用例然后让 Agent 自己看着报错调整代码两个小时后新版逻辑就部署到了测试环境。这个效率差本质上不是大模型的进步而是开发范式转变带来的红利。5.3 判断团队 Agent 开发成熟度的三个信号最后给三个信号你可以拿来自检自己的团队是不是还在走 spec coding 的老路。第一个信号需求澄清是否占用了超过一半的交互时间。如果你的 Agent 在对话里只是机械执行指令从不追问、从不复述、从不确认那它在真实环境里多半会频繁出错。成熟的 Agent 应该花大量时间在确认意图上而不是急着执行。第二个信号Agent 运行失败之后你的第一反应是什么。如果第一反应是改 spec补充一条规则然后再跑一遍那说明你还是旧的调试心智。成熟的做法是去看 trace看它在哪一步做了错误判断然后调整上下文结构或验证条件让 Agent 有能力自己修正决策路径。补规则是打地鼠调链路才是治本。第三个信号你的团队还在不在一份 80 页的需求文档上做评审。如果还在说明你潜意识里还是把 Agent 当成编译器而不是协作者。真正成熟的团队评审的是一堆测试用例和几条核心 trace文档是支撑这些素材的注释而不是发布的中心。我个人的体感是这个转型过程有点像是从给孩子画好每一步的路线图变成给他指南针、地图和他自己的脚。Spec 依然可以在关键时刻提供方向但你不可能用一张静态图纸覆盖一条正在不断生长的河流。Agent 开发的价值不在于生成符合规格的代码而在于构建一个能够面对意外、持续修正自己行为的新物种。早点把重心从写规格移到搭验证环上你的 Agent 会给你省下大量的兜底时间这是我现在最深刻的体会。