如何编写遵守规格的 AI 编程提示词
为什么 AI 编程工具会加上你根本没要求的校验逻辑,把下游还有消费者的字段随手重命名,还生成一堆超出任务范围的辅助函数?因为你丢给它的是一个"要解决的问题",而不是一份"要遵守的 spec"。模型的优化目标是"完整性"——而没有约束的完整性,在每一条提示词上都会演变成范围漂移。解法不是换一个模型,而是写提示词时以一份 spec 为根基,让边界与任务本身一样清晰可读。
现场笔记:最能防止越界的一句提示词
最有用的提示词通常并不漂亮:写清模型可以改哪些文件,以及不能改变哪些行为。没有这句话,助手很容易把“顺手清理”当成授权。
提示词边界: 只允许修改: - src/billing/refunds.ts - tests/billing/refunds.spec.ts 不得修改: - 公开 API 响应字段 - 数据库结构 - 权限规则 - 重试时间
AI 编程工具为什么会漂移
当你丢给模型一个没有 spec 的功能需求时,你其实是在让它从零开始解决问题。它不知道你已经有什么、你刻意没加什么,也不知道这个任务在哪里结束。它的训练让它倾向于"完整"——它会补上那些没被要求但在类似代码库里常见的东西。
这种漂移很微妙,粗看评审会错过。多出的参数被合进来了。被改名的字段拖垮了下游消费者。等你追回源头时,问题已经上了生产环境,而模型已经在三轮对话之前就忘了自己加过什么。
解法不是换模型,而是写一份更好的提示词——具体来说,是一份以 spec 为根基、把约束表达得和任务本身一样清晰的提示词。一旦你见过这种输出质量的落差,你就再也不会发模糊的提示词了。
没有 Spec 的提示词
"给我们的 CRM 实现一个联系人去重功能。"
带 Spec 的提示词
"按以下 spec 实现联系人去重:
Goal: 按 email 精确匹配合并重复项
Non-goals: 模糊匹配、手动合并 UI
AC: Given 两个联系人使用同一 email,
when 去重运行, then 保留最近活跃的那条。"
Spec 是提示词里最有价值的输入
一份软件 spec——目标、非目标、验收标准、数据模型约束、边界条件——恰好就是模型保持在范围内所需要的一切。大多数提示词把这些全省了。它们描述要造什么,但对"不要造什么"、"哪些字段不能改名"、"哪些行为本次不在范围内"只字未提。
在为实现任务写任何 AI 提示词之前,你的 spec 至少应该具备:
- 目标——一句话,点明用户或系统层面的产出
- 非目标——一份明确的、本次不在范围内的清单,不是"未来工作",而是从本次实现里刻意排除的内容
- 验收标准——可测试、二元的条件,采用 Given/When/Then 格式
- 数据模型约束——已有的字段名、类型,以及所有不能被模型修改的内容
- 边界条件——必须被处理的边界输入与失败状态,明确点名
当这些内容真的存在时,你就可以在提示词里直接引用它们。直接引用和转述不是一回事。转述会引入解读——是你的解读,不是 spec 的。模型需要的是原文,这样它才能始终锚在那句话上。
提示词结构:system role + spec + 任务边界
一份能稳定产出符合 spec 代码的提示词由三部分组成:设定约束姿态的 system role、定义要造什么的 spec 内容,以及封闭范围的任务边界。
System role 告诉模型它处在什么工作模式。大致是这样:"你是一名按书面 spec 实现功能的软件工程师。你唯一的工作就是产出完全符合 spec 的代码。不要添加功能。不要重构相邻代码。不要重命名已有的标识符。" 这很重要,因为大多数模型的默认模式就是"乐于助人"——也就是加东西。你是在把这股冲动引导回来。
Spec 内容就是你的 spec 本体,粘贴进来。不要总结。不要转述。把相关章节——目标、非目标、验收标准、命名约束——原文直接复制过去。如果 spec 很长,就挑出与本次任务直接相关的章节。别压缩它们。
任务边界声明本次提示词你到底想要什么:一个函数,一个接口,一次迁移。把每条提示词都聚焦在单一、有界的交付物上,能让输出可以逐条对照 spec 去评审,也让漂移更容易被看到。"实现整个功能"是在邀请模型自己发明范围。"写一个名为 X 的 PostgreSQL 函数,实现第 2 条和第 3 条验收标准"——就不是。
一份完整的提示词示例
下面是一条用于实现联系人去重函数的完整提示词。注意它包含了什么——以及明确禁止了什么:
SYSTEM:
You are a software engineer implementing a feature from a written spec.
Your job is to produce code that satisfies the spec exactly.
Do not add logic not described in the spec.
Do not rename existing fields or functions.
Do not refactor code outside the scope of this task.
If something is unclear, say so — do not guess and implement.
SPEC — Contact Deduplication (v1):
Goal:
Identify duplicate contact records where two or more rows share the same
normalized email address. Mark newer records as duplicates of the oldest match.
Non-goals (do not implement):
- Do not merge contact records
- Do not delete any records
- Do not deduplicate on name, phone, or any field other than email
- Do not add a UI for reviewing duplicates
- Do not send notifications when duplicates are found
Data model (existing — do not modify field names or types):
- contacts.id (uuid, primary key)
- contacts.email (varchar, nullable)
- contacts.created_at (timestamptz)
- contacts.duplicate_of (uuid, nullable, foreign key → contacts.id)
Acceptance criteria:
- Given two contacts share the same normalized email (lowercase, trimmed)
When the deduplication job runs
Then the contact with the later created_at has duplicate_of set to the id
of the contact with the earliest created_at
- Given a contact has a null email
When the deduplication job runs
Then that contact is not modified
- Given three contacts share the same email
When the deduplication job runs
Then both later contacts have duplicate_of pointing to the earliest one
- Given the job runs twice on the same data
When no new contacts have been added
Then no rows are modified on the second run
Edge cases:
- Email normalization: strip whitespace, lowercase only
- Do not treat "[email protected]" as duplicate of "[email protected]"
- A contact where duplicate_of is already set should not be used as the
canonical record when new duplicates are found
TASK:
Write a PostgreSQL function named find_and_mark_duplicates() that implements
the deduplication logic above. Return the count of rows updated.
Do not create any additional functions, triggers, or tables.
这条提示词没给"乐于助人"留任何出错的空间。每个字段名都被锁住。每一项不在范围内的特性都被明确点名。每一条验收标准都可以直接测。输出可以逐行对照 spec 评审。
真正有效的约束句式
"Do not add features" 对模型来说太容易自我合理化。模糊的约束会被善意解读。下面这些更具体的形式,则很难被忽视:
- "Do not add anything not listed in the spec."
- "Do not rename existing fields. The field names in the spec are the field names in the codebase."
- "Do not add error handling beyond what the acceptance criteria describe."
- "Do not add logging, metrics, or instrumentation unless explicitly listed in the spec."
- "Do not modify any file not directly required to implement the acceptance criteria."
每一条都针对一类具体的漂移。重命名是最常见的之一——模型看到 duplicate_of,就觉得 canonical_id 更"干净"。日志是另一种——它看起来像好的工程实践,但其实是偏离 spec 的改动,会把 diff 变得更难评审,也让回滚更难推理。
把这些约束一次性加进共享的提示词模板,你就不用每个任务都再想一遍。它们会成为项目里 AI 辅助实现的默认操作姿态。
提示词中的边界条件
边界条件是 AI 输出最容易偏离 spec 意图的地方。当提示词里没写边界行为,模型就会用训练数据里出现得最多的模式去填补。这种模式在抽象层面往往看起来合理,但放到你的具体上下文里就是错的。
把 spec 的边界条件章节原文照抄进去。不要总结。边界条件的措辞本身往往就是关键——尤其是在那些"排除 null email"和"对 null email 跳过归一化"会改变实现含义的边界上。
对于 spec 没覆盖的情况:让模型停下来,而不是去猜。"If you encounter a case not covered by the acceptance criteria or edge case section, output a comment noting the uncovered case and leave the implementation decision to the engineer." 这会产生一个可见的产物——代码里的注释——而不是一个埋在逻辑里的沉默假设。一条写着"spec 未定义 duplicate_of 已设置的联系人遇到 null email 时的行为"的注释,很容易被发现;而一个藏在 if 分支里的隐式决策,则不容易被发现。
漂移之后:重新锚定
即使是结构良好的提示词,在多轮会话中也会漂移。到第三、第四轮时,模型已经开始把自己之前的输出当作"事实基础"。如果它在第一轮里加了一个多余的参数,到第三轮时这个参数已经成为假定接口的一部分,后续的修改全都叠在它之上。
纠正的套路是:明确地以 spec 为依据重新锚定,而不是以你的个人偏好。不要只说"把 dry_run 参数去掉",要说"spec 规定函数签名必须是 find_and_mark_duplicates() 且不带参数。你之前的输出加了一个 dry_run 参数。删除它,并且不要再添加 spec 里没有的参数。" 引用 spec 这件事本身很重要。在会话内部,它在训练模型把书面 spec 当作权威来源,而不是它自己之前的输出。
对于已经积累了大量漂移的长任务,考虑用完整提示词开一个新会话,而不是继续在旧会话里一轮一轮纠正。把一个严重漂移的会话拉回来的成本,通常比重开一个干净会话、从头产出稳定输出要高。
写提示词前的 spec 检查清单
实现完成之后才补写的 spec,作为提示词输入毫无用处。它需要在你开始写提示词之前就存在,并且包含那些否则会被 AI 推测出来的决策。在为任何实现任务写第一条 AI 提示词前,先过一遍下面这份清单:
- 目标是不是一句话、点明了一个具体产出?
- 非目标是不是具体的——点出了被排除的功能,而不仅仅是"未来工作"?
- 验收标准是不是二元、可观察,不需要再去找作者对齐就能判断?
- 字段名和函数名是不是已经锁死了?类型是不是指定了?
- 边界条件章节是不是覆盖了 null 输入、空集合、权限边界、并发访问?
- 错误行为是不是已经指定了——返回什么、记录什么、不发生什么?
- 这个任务是不是小到可以在一次评审中验证完?
在写提示词之前 spec 里缺的每一项,都会变成 AI 代你做出的决策。对于风险较低的细节,这有时可以接受。但对于字段命名、错误行为和范围边界,AI 的默认答案不太可能和团队达成的约定一致。先把这些写下来。
对照 spec 评审输出
最后一步是结构化的验证——把输出逐条对照验收标准。这不是一次普通的代码评审,而是一项具体的检查:这份实现是否满足了每一条标准,且仅满足每一条标准?
一个好用的做法是,在评审实现之前,先根据验收标准写出测试用例。因为标准是 Given/When/Then 格式,每一条都能直接映射成一个测试:Given 搭好前置条件,When 调用函数,Then 是断言。先把这些测试写出来,那些"实现得太多"的输出就很难在评审里蒙混过关。
当输出通过所有验收标准、且没有引入 spec 之外的行为时,任务就算完成了。当它加入了额外的行为——哪怕这些行为看起来是对的——要么把这些行为作为刻意决策加回 spec,要么从实现里删掉。spec 才是参照物。实现要向它对齐,或者先更新 spec,而不是反过来。
提示词评审示例:先约束,再生成
AI 编码有没有偏离,常常取决于生成前的两段约束。我现在会同时放入规格片段和“不要自行发明行为”的说明,再让模型写代码。
提示词补充: 只实现下面规格中明确写出的行为。 不要新增字段、路由、后台任务、重试逻辑或 UI 状态。 每一处代码改动都要映射到: - Goal - Non-goal - Acceptance criterion - Edge case 如果你认为某个行为必要但规格没有写,请先提问,不要实现。
这样规格就变成了边界。AI 仍然可能犯错,但评审者至少有明确材料可以逐项对照。
可复制产物:AI 编码评审包
在 AI 生成 diff 进入代码评审前使用。它把提示词范围、允许变更和证据要求合并成一个可审查产物。
AI 编码评审包:如何编写遵守规格的 AI 编程提示词 本次要做的决策: - 确认 AI 只在批准范围内生成变更,并为每条验收标准提供证据。 责任人检查: - 产品责任人: - 工程责任人: - QA 或运维评审: 范围边界: - 本次包含: - 本次不包含: - 仍需确认的假设: 验收证据: - 测试或 fixture: - 日志、指标或截图: - 人工复核步骤: AI 边界:生成变更必须留在书面范围内,每条验收标准都要能找到证据。 评审追问: - 没参加需求会的人还会误解哪里? - 哪个证据能证明这次改动足够安全,可以发布?
旗舰使用路径
这是 Spec Coding 用来承接「受规格约束的 AI 提示词」主题的核心参考页之一。建议把它放到真实工单、PR 或发布评审里使用,而不是只当背景文章阅读。
- 适合从这里开始:AI 编码工具即将修改实现代码。
- 建议复制:提示词边界块和非目标指令。
- 需要附上的证据:diff 复核能证明每个生成改动都映射到规格行。
- 搭配使用:AI 编码治理 Hub 与 Spec Skills。
旗舰页使用路径: - 在计划或评审时打开本文。 - 把对应产物复制到工单或 PR。 - 用自己的系统、责任人和失败模式替换示例值。 - 如果证据行仍为空,就不要进入实现。
二次审阅记录:提示词需要非目标
这次检查时,我避免把提示词质量写成文字技巧。真正的控制点更窄:提示词必须带上范围、非目标、证据和可评审的输出格式。
提示词复核: - 目标告诉模型要实现什么。 - 非目标告诉模型在哪里停下。 - 证据告诉评审者该看什么。 - 输出格式让第一次生成后的内容仍然可用。
编辑复核记录
复核日期:2026-04-29。本次补充了可复用产物,按相关主题 Hub 检查了文章定位,并收紧下一步链接,让页面更像可操作参考,而不是孤立长文。
专题阅读路径
这篇文章归入 AI 编码治理 主题。先读 Hub,再结合下面的清单、模板或工具落到具体项目里。
延伸阅读
填写表单,生成完整的功能规格 Markdown——免费使用,无需注册。
编辑说明
本文为软件交付团队介绍如何编写遵守规格的 AI 编程提示词。文中示例均为说明性工程场景,不构成法律、税务或投资建议。
- 作者信息:Daniel Marsh
- 编辑政策:我们如何审核和更新文章
- 纠错:联系编辑
本页合并覆盖的主题
为了让文章库更聚焦,这篇主文章现在作为「如何编写遵守规格的 AI 编程提示词」的规范入口,同时覆盖下面这些原本分散的相关主题。读者可以在一个页面里完成判断、复制和评审,不必在多篇相似文章之间来回跳转。
- 面向产品团队的 Spec Skills Prompt 库
- Spec Skills Prompt 模式:规格工作流提示模板