2.1 Prompt Engineering——高质量指令让 AI 产出稳定的秘密

7 阅读18分钟

模块二:AI 协作方法论 | 第01讲:Prompt Engineering——高质量指令让 AI 产出稳定的秘密

项目:VibeNote 智能笔记 · 技术栈:Next.js / React / TypeScript / Tailwind
本讲定位:把「会聊天」升级成「会下指令」——Prompt 不是玄学,是可复制的工程输入。


一、开场:为什么 Prompt 仍然值得你认真学

如果你读过我们参考库里那组「核心概念与范式演进」的梳理,会有一个共识:AI 时代拉开差距的往往不是「模型会不会写代码」,而是你能不能把目标、边界、验收标准组织成模型能稳定执行的输入。Addy Osmani 在谈 Agent 与 Agentic Engineering 时反复强调:负责任的做法不是把实现外包出去再把理解也外包出去,而是计划先行、审查回归、测试与所有权仍在人这边。Prompt Engineering(提示工程)在这个语境里,不是「找一句魔法咒语」,而是把模糊意图翻译成可验证的任务规格的第一道工序。

再补一句容易被忽略的现实:Coding Agents 真正好用的团队,往往不是 Prompt 写得最花,而是最会把委托、检查点与反馈机制写进流程(参考文章列表里 Devin 团队那篇「入门指南」的核心判断)。所以你可以把本讲理解为:我们先打磨「单次委托的质量」,后面的 Workflow、上下文工程、规则文件,都是在把单次质量乘以一个可重复的系数

老程序员都懂一个道理:带新人最怕指令不清。你跟他说「把这个接口优化一下」,他可能给你加缓存、改字段、顺手重构三层——每个方向都「合理」,但都不是你要的。大模型在协作里的角色,很多时候就像一个超快、超自信、偶尔幻觉的高级新人:你给的信息越少,它越会用自己的先验去补全,补着补着就偏了。换句话说,你不是在「问 AI」,你是在「派活」;派活不清,返工必多。

所以本讲我会用「带团队」的隐喻贯穿:Prompt 就是你对 AI 的任务单。任务单上有五件事写清楚,返工率会断崖式下降:你是谁(角色)、你已知什么(上下文)、你要我做什么(任务)、你要我以什么形态交付(格式)、有什么红线不能踩(约束)。下面所有技巧,都是围绕这五件事展开的;把它练成肌肉记忆,比收藏一百条提示词更有用。


二、Prompt 解剖学:RCTFC 五要素

我习惯把一条高质量指令压成五个字母:Role、Context、Task、Format、Constraints。你也可以把它理解成「结构化 PRD 的微缩版」。

1. Role(角色)

让模型知道以什么身份、什么水平、什么立场回答。比如:「你是负责 VibeNote 的资深全栈工程师,熟悉 Next.js App Router 与 TypeScript 严格模式。」

角色不是为了「显得专业」,而是为了激活更匹配的知识子空间:同样一个问题,「你是面试官」和「你是写教程的作者」输出结构会完全不同。

2. Context(上下文)

上下文不是「字数越多越好」(下一讲我们会专门拆成本),而是与决策相关的最小充分信息:当前文件路径、相关接口签名、数据模型、用户场景、已尝试的方案、错误日志的关键片段。

参考文章列表里反复出现的一个判断:很多协作问题表面像工具问题,根子上是规格不清。上下文的作用,就是把规格里「默认大家都知道」的东西显式化。

3. Task(任务)

任务必须是可完成的动作,最好带边界:改什么、不改什么、做到哪一步算停。反例:「优化笔记编辑器」。正例:「在 NoteEditor 中增加 Markdown 分栏预览,不引入新重型编辑器依赖,保持现有 textarea 方案。」

4. Format(格式)

明确交付物形态:要点列表、表格、diff 思路、分步骤 patch、还是「先给方案后给代码」。对编码任务,我通常会要求:

  • 先输出文件变更清单(路径 + 职责一句)
  • 再输出关键接口与类型
  • 最后才给实现片段

这能显著降低「一上来就糊一坨代码」导致的失控。

5. Constraints(约束)

约束是质量与安全的护栏:禁止改哪些目录、必须兼容 Node 版本、必须处理空状态与错误态、不得新增未解释依赖、必须给出测试步骤。Vibe Coding 文章里也提醒:快不等于随便;约束就是在速度上加纪律。

flowchart LR
  R[Role 角色] --> I[Intent 意图对齐]
  C[Context 上下文] --> I
  T[Task 任务] --> I
  F[Format 格式] --> O[Output 输出]
  C2[Constraints 约束] --> O
  I --> O
flowchart TD
  A[收到需求] --> B{RCTFC 是否齐全?}
  B -->|否| C[补齐上下文与验收标准]
  C --> B
  B -->|是| D[生成方案/代码]
  D --> E{对照约束自检}
  E -->|失败| F[迭代 Prompt: 追加反例/边界]
  F --> D
  E -->|通过| G[人工审查与运行验证]

三、Zero-shot、Few-shot、Chain-of-Thought:别只用一个招

Zero-shot(零示例)

直接描述任务。适合:规则清晰、领域常规的活,比如「把这段 TypeScript 改成严格模式可编译」。

Few-shot(少示例)

给 1~3 个「输入—输出」对,让模型对齐风格与细节。适合:团队有固定写法的场景,比如你们 VibeNote 的 API 统一返回 { data, error },或者错误码约定。

注意:示例会占用上下文窗口;示例要高质量,坏的示例会把模型带沟里。

Chain-of-Thought(思维链)

要求模型先推理再结论,适合复杂调试与方案权衡。实践里我更喜欢「工程化 CoT」:先输出假设列表 → 再输出验证步骤 → 再输出结论,而不是让它长篇散文。

这和参考实践文章里的「方案先行、实现为后」是同一逻辑:让模型先把思路写下来,后面生成代码时更不容易前后打架


四、System Prompt vs User Prompt:边界放对地方

通俗理解:

  • System:长期稳定规则,像「员工手册」——项目栈、目录规范、禁止事项、输出结构。
  • User:这次任务的变量,像「工单」——具体需求、相关文件、报错、验收标准。

为什么分离很重要?因为你在 Cursor / Claude / ChatGPT 里,System(或规则文件)是被动上下文,每次对话都在;User 是当次输入。把「永远成立」的东西写进 System 或 AGENTS.md.cursorrules,能显著减少你每次复制粘贴重复约束的脑力消耗(模块二后面几讲会展开)。


五、写给编程任务的常见 Prompt 模式(可直接套)

下面不是「万能模板」,而是高频骨架;你可以按项目裁剪。

模式 A:先方案后代码(强烈推荐)

  1. 让模型输出:模块边界、数据流、风险点、文件列表
  2. 你 Review 后再让它实现
    这与参考方法论中「方案先行、实现为后」一致:方案文本会成为后续生成的锚点

模式 B:约束式重构

明确「只改 X,不改 Y」「保持对外 API 不变」「补最小测试」。

模式 C:对齐现有代码风格

@ 相关文件,让模型「模仿既有模式新增」,而不是发明新架构。

模式 D:错误驱动修复(下一讲会深挖流程)

完整错误栈 + 复现步骤 + 相关代码片段,并要求「先定位根因再给补丁」。


六、坏 Prompt vs 好 Prompt:同一需求的两种命运

场景:给 VibeNote 加「AI 扩写当前段落」

坏例子:

帮我加 AI 功能。

问题:任务不明、无边界、无格式、无约束。模型可能引入新依赖、改路由、写一套你不想要的 UI。

好例子(骨架示意):

角色:你是 VibeNote(Next.js App Router + TS + Tailwind)项目的实现者。
上下文:NoteEditortextarea 保存 markdown,选区通过 selectionStart/End 可取。
任务:新增「扩写选中文本」:点击工具栏按钮,把选中文本发送到服务端 API,用流式响应替换选区;未选中文本则提示。
格式:先给「文件变更清单 + API 设计」,再给代码;代码按文件分块。
约束:优先 Vercel AI SDK;密钥只放服务端;不得把 API Key 暴露到客户端 bundle;需要 loading 与错误 toast;不引入新的 Markdown 编辑器依赖。

你会发现:好例子本质上是在写一张可执行的工单


七、Prompt 迭代方法论:我常用的三轮循环

  1. 第一轮:广度——先跑通最小链路,允许丑。
  2. 第二轮:约束——把「不要什么」补上,清理幻觉依赖与不必要抽象。
  3. 第三轮:验收——用测试步骤与边界用例逼模型补齐。

如果同一问题上模型改了三遍还不对,停。参考实践文章说得很直白:这往往是错误假设堆叠而不是「再试一次就会好」。此时该做的是你亲自定位根因,再发带根因说明的新指令(第05讲会系统讲)。


八、可运行示例:用 TypeScript 生成「结构化 Prompt」

下面这段代码不依赖框架,Node 可直接跑。它演示了如何把 RCTFC 固化成函数,避免每次手敲漏项——Prompt Engineering 也可以工程化

// scripts/build-prompt.ts
// 运行: npx tsx scripts/build-prompt.ts

type PromptParts = {
  role: string;
  context: string[];
  task: string;
  format: string[];
  constraints: string[];
};

function buildCodingPrompt(p: PromptParts): string {
  const lines: string[] = [];
  lines.push(`## Role\n${p.role.trim()}`);
  lines.push(`## Context\n${p.context.map((c) => `- ${c}`).join("\n")}`);
  lines.push(`## Task\n${p.task.trim()}`);
  lines.push(`## Output format\n${p.format.map((c) => `- ${c}`).join("\n")}`);
  lines.push(`## Constraints\n${p.constraints.map((c) => `- ${c}`).join("\n")}`);
  return lines.join("\n\n");
}

const prompt = buildCodingPrompt({
  role: "你是 VibeNote 项目的资深全栈工程师(Next.js App Router + TypeScript 严格模式 + Tailwind)。",
  context: [
    "VibeNote V1.0 已有笔记创建/列表页,编辑器当前为 textarea 保存 markdown 字符串。",
    "项目要求:AI 相关密钥只能出现在服务端路由(Route Handler),不得泄漏到客户端。",
  ],
  task: "实现「扩写选中文本」的最小可用版本:服务端流式输出,客户端替换选区。",
  format: [
    "先输出:变更文件列表(路径 + 一句话职责)与 API 草案。",
    "再输出:分文件的代码块,避免混在一个块里。",
    "最后输出:本地验证步骤(含边界:未选中、网络错误、空文本)。",
  ],
  constraints: [
    "优先使用 Vercel AI SDK(如项目未安装,请在方案里声明需要新增的依赖)。",
    "不引入新的 Markdown 编辑器(保持 textarea)。",
    "必须处理 loading / error / empty selection。",
    "不要改动与需求无关的模块(例如笔记列表分页逻辑)。",
  ],
});

console.log(prompt);

你可以在团队里把 buildCodingPrompt 扩成「工单生成器」:从 issue 模板填字段,一键生成给 AI 的结构化指令。


九、本讲小结:把「灵感」变成「输入质量」

  • Prompt 的本质是任务规格,不是口号。
  • RCTFC 是五要素检查清單:角色、上下文、任务、格式、约束。
  • Zero-shot / Few-shot / CoT 是工具,工程里最有性价比的组合通常是「CoT 方案 + Few-shot 风格对齐」
  • System/User 分离,是在为「长期搭档」铺路:稳定规则沉淀,临时变量外置。
  • 迭代三轮是节奏;三连跪停手是纪律(否则进入无效补丁循环)。

十、放到 VibeNote 里的三套「完整 Prompt 样例」(从真实协作里抽出来)

下面三段都按 RCTFC 写满,你可以直接复制改参数用。它们分别覆盖:API 设计前端交互小步重构

样例 1:为 VibeNote 增加「保存时生成摘要」的 Route Handler

Role:你是 VibeNote 的后端实现者,熟悉 Next.js Route Handler 与 Zod 校验。
Context:笔记正文是 Markdown 字符串,字段名 content,标题 title;摘要写入 excerpt 供列表展示;项目已使用 Vercel AI SDK。
Task:新增 POST /api/notes/summarize,输入 { title, content },输出 { excerpt };摘要 60~120 字中文,避免复述标题。
Format:先给 OpenAPI 风格草案,再给 route.ts 完整实现,最后给 curl 验证命令。
Constraints:必须校验输入长度上限(例如 content ≤ 32KB);必须处理模型失败(返回 502 与可读错误);不得记录完整正文到日志;超时 20s。

这段样例的关键是:把「看起来像产品需求」的一句话,翻译成可测试的接口契约。你会发现模型一旦知道上限与失败语义,就不太容易写出「默默吞错」的代码。

样例 2:编辑器工具栏的「改写语气」交互

Role:你是注重可访问性与边界态的前端工程师。
Context:编辑器为 textarea,已有粗体/斜体按钮;需要新增「改写:更正式 / 更口语 / 更简短」三档。
Task:实现 UI(Segmented control 或下拉),调用已有 /api/ai/rewrite,流式替换选中文本。
Format:先给组件状态机(空闲/加载/错误/成功),再给 RewriteToolbar.tsx 与最小父组件改动点。
Constraints:无选区禁用按钮;加载中显示 aria-busy;错误可重试;禁止在客户端拼接密钥。

这里刻意要求先输出状态机,是在用 CoT 的工程化变体:UI bug 十有八九来自状态不完整,而不是 CSS。

样例 3:目录结构调整的小步重构

Role:你是擅长渐进式重构的维护者。
Contextlib/notes.ts 同时被 Server Component 与 Client Component 引用,导致 bundle 体积异常。
Task:拆分 lib/notes.server.tslib/notes.shared.ts,保持对外导入路径兼容(或提供过渡 re-export)。
Format:先列出依赖图(哪些文件 import 了谁),再给每一步最小 diff 策略,最后给验证清单(typecheck、build、关键页面手工点验)。
Constraints:一个 PR 内完成;不得改变数据库 schema;不得改变 API JSON 形状。

这类 Prompt 的亮点是强制模型先画依赖图。依赖图一出来,很多「想当然的重构」会被你自己当场否决。


十一、再谈两句:Prompt 长度与「信息密度」

很多人把「上下文不够」误解成「字数不够」。我的经验恰恰相反:先追求信息密度,再考虑是否加字。同样 800 字,贴一整段无关日志,不如给这三样:

  1. 最小复现步骤(点击路径/输入数据)
  2. 最短相关代码(只保留失败路径)
  3. 你已经排除的假设(避免模型回到死胡同)

参考文章也提醒:工具链讨论最终会回到「模型在正确时机拿到正确上下文」。Prompt 迭代时,你可以问自己一个狠问题:如果我把这段话读给同事听,他会不会反问三个关键问题? 会,就把答案提前写进 Context。


十二、System / User 分工对照表(落地版)

信息类型更适合 System / 规则文件更适合 User / 当次工单
技术栈与目录约定
安全红线(密钥、PII)可重申关键点
代码风格与测试要求
本次需求与验收标准
具体报错栈与复现
「不要改动的文件清单」高频重复则 System一次性则 User

这张表的价值在于:减少重复劳动,同时避免 System 无限膨胀(膨胀会带来注意力稀释,第03讲会讲窗口与成本)。


十三、本讲与「Agentic Engineering」的接口

你在 01-core-concepts.md 里会看到一条主线:高水平工程实践在 AI 时代不是减弱,而是被放大。Prompt Engineering 不是替代 TDD、不是替代 Code Review,它只是把「意图」翻译成「可执行」的入口。入口翻译得越准,后面的审查与测试越省力。

如果把协作画成流水线,Prompt 位于最上游:上游含沙射影,下游就只能靠加班补。

flowchart LR
  H[Human 意图] --> P[Prompt 规格化]
  P --> A[AI 生成]
  A --> R[Human Review]
  R --> V[Verify 测试/运行]
  V --> S[Ship 交付]
  V -->|失败| P

十四、反模式清单:这些 Prompt 习惯会把项目带进沟里

  1. 「你看着办」式需求:没有验收标准,模型只能猜优先级;最后往往得到「过度设计 + 一堆用不上的抽象」。
  2. 把报错全文不加裁剪地粘贴:噪声会把真正关键的一行淹没;正确做法是保留首尾栈帧 + 指明你认为相关的文件。
  3. 同时下达三个互不相关任务:模型会串线,A 任务的假设被 B 任务污染;应拆工单。
  4. 要求一步到位且禁止它输出中间思考:复杂任务反而更不稳定;应要求「先清单后代码」。
  5. Few-shot 复制了过期代码:例如 Next.js Pages Router 示例用于 App Router 项目,会诱发 API 误用。
  6. 用羞辱性语气替代约束:「你怎么这么笨」不会提高正确率;把能量换成「约束 + 反例 + 验收步骤」。
  7. 把密钥、内网地址、用户隐私贴进对话:这是安全事故,不是提示词技巧问题;应对脱敏与最小化引用。

十五、从「提示词」到「提示词资产」:建议你今天就开始积累的三类文本

第一类:成功工单:把一次高质量对话里最终有效的 Prompt 抽出来,去掉隐私,留下可复用骨架。
第二类:失败对照:记录「坏 Prompt → 改写 → 变好」的差异,你会很快形成团队语感。
第三类:领域词表:VibeNote 这种产品会有自己的名词(笔记、标签、知识库、剪藏)。统一词表会减少模型误解。

这三类文本未来会自然流进 AGENTS.md、规则文件与模板库(第04讲会把它体系化)。


十六、迷你 FAQ:同事最常问我的 6 个 Prompt 问题

Q1:Prompt 是不是越长越稳?
A:不是。长但松散,不如短而密。先满足 RCTFC,再按需加长。

Q2:要不要每次都说「你是专家」?
A:可以,但别空洞。把「专家」具体化成栈与约束,比形容词有用。

Q3:模型输出很爱讲废话怎么办?
A:在 Format 里写死:「不要前言后语,不要道歉,不要重复我的需求」。

Q4:中文需求英文代码会不会混?
A:可以混。通常中文描述需求,代码标识符仍用英文;在约束里写明「用户可见文案用简体中文」。

Q5:我能不能让模型自己生成 Prompt?
A:可以,而且很香。先让它把你的目标改写成 RCTFC,再让它按改写后的版本执行,相当于自动「工单润色」。

Q6:团队要不要统一 Prompt 模板?
A:要。统一的不是语气,是字段;字段一致,Review 成本会下降,沉淀也更容易检索。


十七、对比表:同一任务的 Zero-shot vs Few-shot 取舍

维度Zero-shotFew-shot
启动成本中(要维护示例)
风格一致性依赖模型先验更可控
上下文占用随示例增长
适合任务规则清晰、常见模式团队有「样板代码」
风险模型按默认习惯发挥坏示例会污染

十八、给工程师的 CoT「裁剪版」:五种可执行套路

  1. 假设—验证:列出最多 3 条可能根因,每条给 1 个验证动作(读哪个文件、加哪条日志)。
  2. 接口先行:先写请求/响应类型与错误码,再写实现;适合 API 与集成点。
  3. 状态机先行:UI 先把 idle/loading/error/success 画清,再写 JSX。
  4. 数据流先行:从「用户动作 → Server Action / Route Handler → DB → 回到 UI」按箭头写一遍。
  5. 风险清单收尾:让模型在最后输出「我可能猜错的三件事」,你只看这三条就能快速拦截幻觉。

这些套路的共同点是:把思考固定在工程工件上,而不是让它写散文;散文对模型轻松,对你难验收。


十九、和 VibeNote 对齐:把 Prompt 写进「验收清单」

当你在做笔记产品时,很容易把需求描述成「像 Notion 一样好用」。这句话对人类有共鸣,对模型没有可执行性。建议你为每个功能准备 5 条可勾选验收,并写进 Prompt 的 Constraints:

  1. 空笔记能否保存?是否提示?
  2. 超长文本(例如 5 万字粘贴)是否降级?
  3. 离线或请求失败时,用户输入会不会丢?
  4. 移动端软键盘弹出时,编辑区是否还能操作主按钮?
  5. AI 功能是否在 UI 上明确标注「由 AI 生成,可能不准确」?

这五条并不完整,但它们代表一种思维方式:把「好用」翻译成「可验证」。Prompt 写得再漂亮,最后仍要落回这几类边界;不如一开始就写进去,让模型第一次生成就更接近可交付。


二十、思考题

  1. 回顾你最近三次让 AI 写代码失败的对话:分别缺了 RCTFC 里的哪一块?如果补一句最有效的话,你会补哪句?
  2. 你会把哪些约束从「每次 User Prompt」迁移到「System / 规则文件」?迁移的判据是什么?
  3. Few-shot 示例如果包含历史错误写法,会对模型产生怎样系统性偏差?你如何设计「正例 + 反例」而不污染上下文?

二十一、下讲预告

下一讲我们进入模块二的核心转折:Workflow 比 Prompt 更重要。你会看到一张「设计先行 → 审查 → 实现 → 验证」的流水线如何把个人经验复制成团队节奏,并把参考实践里的「方案先行、实现为后」嵌进日常开发。Prompt 再漂亮,没有流程,只会得到高质量的随机漫步


参考阅读reference/articles/01-core-concepts.md(Agent / Agentic Engineering / 规范与质量纪律);课程原稿 课程内容/2.1 不会提问AI就像不会带团队:高质量指令实战.md(已在本讲中重构为可执行方法)。