1. 我先从一次"返工循环"说起:AI编程的幻觉到底怎么来的
如果你用过AI编程助手,大概率对这种场景不陌生:让Agent写一个订单导出功能,第一版代码看起来结构完整、注释齐全,跑起来却连表头都对不上;你让它修,它改了导出逻辑,又破坏了筛选条件;你再补充说明,它干脆给你多加了一个"顺手实现"的权限模块。三个来回之后,你默默把代码删掉,决定自己动手。
这是典型的"AI幻觉"现场。但我想先说一个反直觉的结论:这种翻车不是模型的随机故障,而是需求缺位下的必然产物。你把一个目标模糊的任务丢给它,它没有能力停下来问你"你到底要什么",于是只能基于训练数据里的统计规律,生成一段"看起来最像正确回答"的东西。它不是在骗你,它是在填空——填一张满是没有标注的格子、边界、约束的试卷。
我做AI辅助开发半年多,统计过一个简单的忠实度指标:把需求描述里明确提到的字段、规则、边界条件逐条列出来,再看最终交付代码中遵守了多少条。结果发现,只要需求文本中的"可验证约束"少于15条,那次交付的返工率基本在70%以上;一旦约束超过40条,返工率反而降到20%以内。这让我意识到一件事:幻觉率的高低,很大程度上不是由模型能力决定的,而是由需求描述的"约束浓度"决定的。
所谓"幻觉率高",翻译成工程语言就是:约束缺位率高。你让它做A,它自由发挥到B;你让它处理列表为空的情况,它忘了写;你让它导出excel时保留两位小数,它用了float32。这些都不是模型"发疯",是你的试卷上压根没有这些题。所以从那以后,我几乎不再花精力去写华丽的提示词,而是把时间花在把需求改写成"可验证的规范"上。这个转变,把我从无尽的返工循环里捞了出来。
2. OpenSpec是什么:给编码Agent一份能被机器校验的"施工图"
2.1 它不是一个普通的README,而是一条规范工作流
我第一次听说OpenSpec,是看到团队里同事把一份"规范目录"提交到了仓库里。点开一看,里面不是常见的设计文档,也不是README式的说明,而是一个个功能级别的规格文件夹:场景定义、行为断言、验收标准、TODO清单,结构清晰得像一棵代码树。
OpenSpec的本质,是一条"先写规范,再写代码"的AI编码工作流。它跟普通文档最大的区别在于:文档写出来是给人看的,而OpenSpec里的规范文件,是设计成给机器校验的。规范的每一条都以"可执行断言"的形式存在,能够被工具解析、比对、检查。也就是说,你给AI的下发物不再是"一段话愿望",而是一个明确的行为契约。
这个设计思路让我一下子想到建筑行业的施工图:你不可能对工程队说"盖个能住的楼",而是要给到每一堵墙的位置、每根梁的配筋、每个房间的采光要求。规范驱动AI编程,就是把"施工图"这个概念搬到了代码仓库里。模型好不好用,取决于你给的图纸是不是够精确、够完整、够可验证。
2.2 验证循环:每个行为都能被检查,而不是被信任
OpenSpec在我理解里,核心运行机制可以拆成三个环节:规范撰写、Agent执行、约束验证。第一环,人或者协作Agent把需求转写成结构化的规范文件;第二环,编码Agent以规范文件为唯一输入,逐条实现;第三环,一个验证器去检查Agent生成的变更,逐条核对是否违反规范中声明的行为约束。如果违反了,就把问题反馈给Agent,让它在约束范围内修正。
我在项目里最看重的就是第三环。过去用AI编程,代码写得对不对,我只能靠"人肉测试"和肉眼Review;而OpenSpec把这个环节自动化了:它不关心你的代码风格好不好、抽象巧不巧妙,只关心代码行为是否与规范条目一一对应。换句话说,AI的输出第一次有了"可以自动判卷的标准答案"。
这里有个细节值得展开:验证器检查的,不一定是编译期的类型错误,更多是"行为层面"的偏差。比如规范里声明"导出失败时必须返回结构化错误码",验证器就会去检查错误处理分支是否存在;声明"排序字段白名单只允许三种",它就会检查Agent是否新增了第四种。这种检查粒度,恰好在代码评审和单元测试之间补上了一个巨大的空档。
2.3 为什么这条路能同时降低幻觉率、提高忠实度
回到前面说的"幻觉率和忠实度"。忠实度这个词,在AI编程语境里的准确定义是:模型输出对输入约束的遵守比例。你不给约束,模型只能自由生成,输出跟你的实际需求自然对不上,于是所谓的"幻觉"就出现了。OpenSpec做的事情,本质上是把需求里的隐性假设全部显性化,让模型没有"自由发挥"的空间——至少在每个行为维度上,都有了明确的边界。
举个例子,我以前写提示词时会说"请导出数据,注意性能"。这种描述放在规范文件里,必须变成"导出10万行数据时,接口响应时间不超过2秒,不允许同步逐行拼SQL"。前者模型只能靠猜,后者模型就能据此选择批量查询、异步生成、分页流式写入。约束越硬,发挥空间越小,输出越稳定。
另外,OpenSpec还内置了审计日志的概念。Agent每次做了哪些变更、在哪个功能上做了一次尝试,都会留下来。这带来的一个附加好处是,当AI又"自作主张"加东西时,你能清楚地看到它是在哪一次操作里跑偏的,而不是面对一堆改动无从溯源。对我这种管过多个项目的工程师来说,这种"可追溯"比什么都重要。
3. 入门实操:在你的仓库里长出第一个规范目录
3.1 环境准备与初始化
我以自己常用的Node环境为例。OpenSpec的CLI通常通过包管理器安装,我在项目里实际执行的是类似这样的命令——具体包名和版本以你安装时官方README为准,这里主要是展示思路:
# 安装OpenSpec CLI(以官方发布为准) npm install -g @openspec/cli # 在已有git仓库根目录初始化 openspec init初始化过程会帮你生成一个openspec/目录,里面包含一个blueprint.md(项目级蓝图)和一个samples/示例文件夹。第一次看到这套目录时,我最大的感触是:它把"规范"真正当成了代码仓库的一等公民,而不是散落在wiki里的文档。
初始化完成后,我建议你先花半天时间,把项目里最核心的一段业务逻辑(比如订单状态机、支付回调、用户权限判定)写成第一批规范,而不是急着让AI干活。因为第一批规范的质量,决定了后面Agent所有行为的天花板。它就像地基,地基不夯实,后面砌再高的墙都没用。
3.2 第一个spec文件怎么写
OpenSpec的功能目录一般长这样(我本地项目的真实结构,不同版本可能有差异,但思路一致):
/your-repo └── openspec ├── blueprint.md └── specs └── 001-add-export-report ├── specification.md └── todo.mdspecification.md是核心,它描述"这个功能必须做什么、禁止做什么、如何判断做对了"。我写规范时的简化结构是这样:
# 功能名称:订单导出报表 ## 上下文(Context) - 当前系统已有订单查询服务,返回分页列表。 - 导出入口在订单管理页右上角,仅管理员可见。 ## 需求(Requirements) - R1: 支持按创建时间范围筛选订单。 - R2: 导出字段固定为:订单号、客户名、金额、状态、创建时间。 - R3: 金额保留两位小数,以文本形式写入excel。 - R4: 导出任务超过10万行时,必须分批次写入,不允许一次性加载全量到内存。 ## 约束(Constraints) - C1: 不新增第三方权限系统。 - C2: 不修改现有订单列表接口的返回结构。 ## 验收标准(Validation) - V1: 给定2024-01-01至2024-01-31的范围,导出文件仅包含该区间的订单。 - V2: 导出文件第一行为表头,且表头顺序与R2一致。 - V3: 金额列在Excel中显示为两位小数文本。 - V4: 当数据量为15万行时,导出进程内存峰值不超过500MB。这里的关键是:每个需求点都必须能对应到一条可验证的检查项。如果我写"导出速度要快",这种模糊表达就永远无法被验证;但改成"导出10万行的P95时间小于10秒",验证器就有了明确的参考坐标。说得再直白一点:规范里能出现数字和枚举的,绝不出现形容词。
3.3 最容易被忽略的三个写作注意点
第一,规范里要明确写"不做什么"。很多初次接触规范驱动的人,只写正面需求,不写负面边界。结果Agent经常给你"顺手"加上分页、缓存、日志系统,看起来是做好事,实际却引入了你没预料到的复杂性。我后来习惯在每个spec里单独维护一个Constraints段落,专门列禁止事项。实测下来,这个段落对抑制Agent"过度发挥"的效果非常明显。
第二,别在规范里写实现细节。规范关心的是"行为"和"验收标准",不是"怎么实现"。你一旦写了"要用Redis做缓存"或者"用工厂模式实现导出器",Agent就丧失了根据场景做技术选型的空间。规范驱动的好处之一,是让实现层面的决策留在实现阶段,而不是让需求文档绑架架构。我见过有人把"建议使用XX开源库"写进规范,结果当那个库出现兼容性问题时,整个项目被拖死。这就是规范过度定义的典型代价。
第三,todo.md是用来给Agent分步执行的,不是给你自己记笔记的。每次开发前,把规范里的需求拆成10到30个不等的原子任务,标好顺序和依赖。Agent执行时会按照这个清单推进,每完成一项就在todo里标记。这个列表本质上是"你给Agent排的工序表",它做得越细,Agent的跑偏概率就越低——因为每走一步都有明确的前后文和判定标准。
4. 实战:用OpenSpec把一个"报表导出"需求一次写对
4.1 任务背景:一句话需求与隐含歧义
为了把这套流程讲透,我拿一个真实接过的需求举例。需求最初只有一句话:"给订单管理页加一个导出Excel的功能,要支持时间筛选。"这句话听起来很简单,但实际上至少藏着五个未定义的问题:导出按钮谁可见?支持哪些字段?时间筛选是数据库过滤还是前端过滤?导出格式是xlsx还是csv?大数据量时怎么处理?没有规范时,光这几个问题,就够你和AI来来回回沟通好几轮。
所以我的做法是:动手写代码之前,先用半小时把这句话拆成前面那种结构化规范。跟产品经理确认权限范围、字段清单、数据量级、格式约束,然后把所有结论固化到specification.md里。这一步做得好,后面的AI执行阶段基本只用输入一条指令。
4.2 撰写规范:从自然语言到行为断言
实际项目中,我把需求细化成了这样几条验收断言(节选):
- 导出按钮仅在管理员角色下可见,非管理员访问导出接口直接返回403。
- 时间筛选项默认最近30天,用户可选择任意时间区间,但最大区间跨度不得超过366天。
- 导出字段顺序固定为:订单号、客户名、金额、状态、创建时间、支付方式。
- 金额使用Decimal计算,Excel中显示为文本格式的两位小数。
- 数据量超过5万行时,导出任务进入异步队列,前端轮询任务状态,完成后提供下载链接;少于5万行时,支持同步下载。
这些断言写进规范之后,每条都能转成验证器的一个检查项。比如"最大区间跨度366天",验证器可以直接对参数校验代码做静态检查;"异步队列"的断言,则通过检查导出入口是否返回taskId而不是文件流来验证。你给规范里放的每一个可测断言,都是在给未来的返工减少一个起因。
4.3 启动Agent:把规范文件变成唯一输入
写完规范后,我在Cursor里打开项目,让Agent读取规范文件并执行。我的启动指令一般是这样:
请严格遵循 openspec/specs/003-export-report/specification.md 中定义的需求、约束和验收标准,实现该功能。不要新增规范之外的额外功能。如果规范中存在枚举冲突或歧义,请停止编码,先列出问题清单让我确认。每完成一个todo项,在todo.md中标记。这里有个细节:"先列问题清单"这句话很重要。它把对话模式的主动权从AI手里夺了回来。以前AI遇到歧义时倾向于"猜一个合理的答案",加上这条约束后,它遇到拿不准的地方会停下来问。一开始你可能会觉得这样更慢,但你会发现,它问的问题往往正是产品文档里没说清楚的坑,相当于让AI在写代码前先帮你做了一次需求澄清。
4.4 验证与收尾:返工率下降了哪些
Agent实现完第一版后,我习惯先跑一遍OpenSpec验证器,而不是直接点"运行"。验证器给出的反馈非常具体,哪一条验收标准没达标、哪个行为与约束冲突,一目了然。我接手这个需求时,第一次验证的结果是:R1到R9全部通过,另外有一个未定义的行为——Agent给导出列表默认加了"按客户名分组的二级表头",这属于规范之外的自由发挥。
我把这条反馈提取出来,作为一条新约束"C3:导出文件必须保持一维表结构,不追加任何分组、合并单元格",追加进规范,然后让Agent重新生成一版。整个过程只花了一次修正,第二次验证就全部通过了。对比以前"写完代码 -> 自测 -> 发现bug -> 让AI改 -> 改出新bug"的死循环,这个体验简直是质的飞跃。
5. 把OpenSpec编排进日常迭代节奏
5.1 feature是基本单位:一spec一功能,别让它变成百科
OpenSpec里以"功能"为单位来组织规范,通常是能力垂直切片,比如"支付回调处理""库存扣减脚本""数据导出"。我最开始犯过一个错:在一份超大的规范里写了好几个功能,结果功能之间的约束互相纠缠,Agent执行到一半就开始乱。后来我把规范全部拆成"一个功能对应一个目录、一份specification.md、一份todo.md",各功能独立演进、独立验证、独立回滚,整个仓库的规范管理才变得清爽。
这个做法还有一个好处:因为每个功能都能独立验证,Agent在做增量变更时,不需要重新加载全仓库的上下文,只需要读取当前功能目录下的规范。上下文越聚焦,输出质量越稳定。这个原理跟人类程序员一次只专注一个任务是一模一样的。
5.2 和Code Review、CI流水线的配合
有人会担心,OpenSpec如果只是开发前的约定,那提交之后不还是靠人Review吗?我目前的实践是,把OpenSpec的验证器挂进CI流水线里,每次Push代码时自动跑一遍"规范一致性校验"——不只是单元测试,而是验证代码行为与规范声明的匹配度。这样等于在Merge Request上又多了一层守卫:AI写得合不合规,机器先帮你审查一遍。
我实际接一个改造项目时,把历史遗留的50多个功能点补写了规范,然后跑了一遍验证器,直接查出了7处"代码行为与文档描述不符"的隐藏债务。其中有一处是文档说"超时时间为30秒",代码里写的是5秒,这个bug在系统里躺了半年多没人发现。所以说,OpenSpec的规范不光是给AI编程用的,它对存量代码的契约梳理同样价值巨大。
5.3 规范与需求同源:技术债终于有了记录载体
我以前做传统开发时,最怕的就是"需求变更"和"代码腐化"失去同步。需求文档更新了,代码没跟上;或者反过来,代码已经改了,文档还停在两年前。OpenSpec提供了一种"规范即文档"的机制:规范以代码仓库存档,随版本演进,验证器又保证代码不会背离规范。所以需求和实现之间第一次有了一个强制同步的纽带。
我在多个功能迭代中验证了这一点:每次产品经理提出新需求,我不再写"需求分析文档再转开发排期",而是直接把新需求拆成spec,放到对应功能目录下,让AI在既有规范基础上增量扩展。新需求与旧约束的任何冲突,验证器会立刻暴露出来。这种冲突以前往往要到联调阶段才能发现,现在在需求阶段就能被机器捕捉,极大压缩了沟通成本和隐性返工。
6. 我踩过的坑和给后来者的实测建议
6.1 五个常见的OpenSpec误用现场
用了一年左右OpenSpec这套工作流,我总结出几个高频雷区,提醒各位绕开。
第一个是"把规范当成PRD复述"。规范里全是"页面左上角有个按钮,点击弹窗"这种界面描写,没有行为断言和验收标准。机器无法从这种描述里得出任何检查项,Agent也就得不到有效约束。规范必须聚焦"系统行为"而非"视觉布局"。
第二个是"写完规范就锁死"。规范不是金科玉律,需求变了,规范要第一时间跟着改。我见过一个团队把规范当成上线后的莎士比亚,禁止任何人修改,结果功能早已演进了三轮,规范还停在初版,验证器天天报红。规范的生命周期必须和需求对齐,它是活文档。
第三个是"只写正面需求,不写禁止事项"。Features清单写得很全,Constraints段落空白。Agent就会发挥"主动性",给你加日志、加缓存、加抽象层,让Code Review的人看得血压升高。负面约束的篇幅,至少要占到规范的15%到20%。
第四个是"规范验证器和单元测试互相替代"。两回事:单元测试验证代码逻辑,OpenSpec验证的是"代码行为与需求描述的一致性"。不要因为有了OpenSpec就不写单测,也不要用单测去替代规范校验,它们在流水线里是不同层的防护。
第五个是"把AI当验证器用"。有些同事觉得既然规范写了,就让AI自己检查自己就算了。但我的经验是:验证环节必须用独立的确定性工具来执行,绝不能交给同一个模型口述"我认为我完成了"。模型自评永远是乐观的,这也是我为什么坚持在CI里挂上开源验证器而不是依赖AI自省式确认。
6.2 什么样的团队最适合引入这套流程
我常被问"我们团队要不要上OpenSpec"。我的判断标准很简单:如果你们团队已经开始高强度使用AI编程助手,并且返工的主要原因是"需求理解不一致",那么强烈建议试。特别是做企业软件、中后台系统、业务逻辑复杂但边界清晰的团队,收益最明显。
反过来,如果是快速原型验证、黑客松比赛、纯前端视觉稿还原这类"一次性代码"场景,我不建议投入精力维护规范文件。规范的价值来自"复用"和"长期演进"——一次性的代码,你画施工图的时间比砌墙的时间还要长,没必要。另外,团队里至少要有一两个人能从"需求"里准确提炼出"行为断言",否则规范容易写成形式主义文档。
6.3 我个人对"规范驱动"的最终判断
说来好笑,我最早接触OpenSpec时,以为它又是一款"更聪明的AI编码工具"——用来替代Cursor或者Copilot那一层。用了一段时间才明白,它真正的价值是改变了我们和AI之间的协作关系:从"你替我干活"变成了"我们共同遵守一份可执行的契约"。
AI代码生成能力已经足够强,真正稀缺的不是"让模型更聪明",而是让模型的聪明有方向、有边界、有反馈。OpenSpec补的恰恰是这个环节。它没有让模型变得更强,但让每一次代码交付都变得可验证、可追溯、可收敛。如果让我用一句话总结这段实战经验,那就是:别再花时间想办法让AI不幻觉,花时间把需求写到让AI没法幻觉,才是治本的路。