模块二: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:先方案后代码(强烈推荐)
- 让模型输出:模块边界、数据流、风险点、文件列表
- 你 Review 后再让它实现
这与参考方法论中「方案先行、实现为后」一致:方案文本会成为后续生成的锚点。
模式 B:约束式重构
明确「只改 X,不改 Y」「保持对外 API 不变」「补最小测试」。
模式 C:对齐现有代码风格
@ 相关文件,让模型「模仿既有模式新增」,而不是发明新架构。
模式 D:错误驱动修复(下一讲会深挖流程)
贴完整错误栈 + 复现步骤 + 相关代码片段,并要求「先定位根因再给补丁」。
六、坏 Prompt vs 好 Prompt:同一需求的两种命运
场景:给 VibeNote 加「AI 扩写当前段落」
坏例子:
帮我加 AI 功能。
问题:任务不明、无边界、无格式、无约束。模型可能引入新依赖、改路由、写一套你不想要的 UI。
好例子(骨架示意):
角色:你是 VibeNote(Next.js App Router + TS + Tailwind)项目的实现者。
上下文:NoteEditor用textarea保存markdown,选区通过selectionStart/End可取。
任务:新增「扩写选中文本」:点击工具栏按钮,把选中文本发送到服务端 API,用流式响应替换选区;未选中文本则提示。
格式:先给「文件变更清单 + API 设计」,再给代码;代码按文件分块。
约束:优先 Vercel AI SDK;密钥只放服务端;不得把 API Key 暴露到客户端 bundle;需要 loading 与错误 toast;不引入新的 Markdown 编辑器依赖。
你会发现:好例子本质上是在写一张可执行的工单。
七、Prompt 迭代方法论:我常用的三轮循环
- 第一轮:广度——先跑通最小链路,允许丑。
- 第二轮:约束——把「不要什么」补上,清理幻觉依赖与不必要抽象。
- 第三轮:验收——用测试步骤与边界用例逼模型补齐。
如果同一问题上模型改了三遍还不对,停。参考实践文章说得很直白:这往往是错误假设堆叠而不是「再试一次就会好」。此时该做的是你亲自定位根因,再发带根因说明的新指令(第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:你是擅长渐进式重构的维护者。
Context:lib/notes.ts 同时被 Server Component 与 Client Component 引用,导致 bundle 体积异常。
Task:拆分 lib/notes.server.ts 与 lib/notes.shared.ts,保持对外导入路径兼容(或提供过渡 re-export)。
Format:先列出依赖图(哪些文件 import 了谁),再给每一步最小 diff 策略,最后给验证清单(typecheck、build、关键页面手工点验)。
Constraints:一个 PR 内完成;不得改变数据库 schema;不得改变 API JSON 形状。
这类 Prompt 的亮点是强制模型先画依赖图。依赖图一出来,很多「想当然的重构」会被你自己当场否决。
十一、再谈两句:Prompt 长度与「信息密度」
很多人把「上下文不够」误解成「字数不够」。我的经验恰恰相反:先追求信息密度,再考虑是否加字。同样 800 字,贴一整段无关日志,不如给这三样:
- 最小复现步骤(点击路径/输入数据)
- 最短相关代码(只保留失败路径)
- 你已经排除的假设(避免模型回到死胡同)
参考文章也提醒:工具链讨论最终会回到「模型在正确时机拿到正确上下文」。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 习惯会把项目带进沟里
- 「你看着办」式需求:没有验收标准,模型只能猜优先级;最后往往得到「过度设计 + 一堆用不上的抽象」。
- 把报错全文不加裁剪地粘贴:噪声会把真正关键的一行淹没;正确做法是保留首尾栈帧 + 指明你认为相关的文件。
- 同时下达三个互不相关任务:模型会串线,A 任务的假设被 B 任务污染;应拆工单。
- 要求一步到位且禁止它输出中间思考:复杂任务反而更不稳定;应要求「先清单后代码」。
- Few-shot 复制了过期代码:例如 Next.js Pages Router 示例用于 App Router 项目,会诱发 API 误用。
- 用羞辱性语气替代约束:「你怎么这么笨」不会提高正确率;把能量换成「约束 + 反例 + 验收步骤」。
- 把密钥、内网地址、用户隐私贴进对话:这是安全事故,不是提示词技巧问题;应对脱敏与最小化引用。
十五、从「提示词」到「提示词资产」:建议你今天就开始积累的三类文本
第一类:成功工单:把一次高质量对话里最终有效的 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-shot | Few-shot |
|---|---|---|
| 启动成本 | 低 | 中(要维护示例) |
| 风格一致性 | 依赖模型先验 | 更可控 |
| 上下文占用 | 小 | 随示例增长 |
| 适合任务 | 规则清晰、常见模式 | 团队有「样板代码」 |
| 风险 | 模型按默认习惯发挥 | 坏示例会污染 |
十八、给工程师的 CoT「裁剪版」:五种可执行套路
- 假设—验证:列出最多 3 条可能根因,每条给 1 个验证动作(读哪个文件、加哪条日志)。
- 接口先行:先写请求/响应类型与错误码,再写实现;适合 API 与集成点。
- 状态机先行:UI 先把
idle/loading/error/success画清,再写 JSX。 - 数据流先行:从「用户动作 → Server Action / Route Handler → DB → 回到 UI」按箭头写一遍。
- 风险清单收尾:让模型在最后输出「我可能猜错的三件事」,你只看这三条就能快速拦截幻觉。
这些套路的共同点是:把思考固定在工程工件上,而不是让它写散文;散文对模型轻松,对你难验收。
十九、和 VibeNote 对齐:把 Prompt 写进「验收清单」
当你在做笔记产品时,很容易把需求描述成「像 Notion 一样好用」。这句话对人类有共鸣,对模型没有可执行性。建议你为每个功能准备 5 条可勾选验收,并写进 Prompt 的 Constraints:
- 空笔记能否保存?是否提示?
- 超长文本(例如 5 万字粘贴)是否降级?
- 离线或请求失败时,用户输入会不会丢?
- 移动端软键盘弹出时,编辑区是否还能操作主按钮?
- AI 功能是否在 UI 上明确标注「由 AI 生成,可能不准确」?
这五条并不完整,但它们代表一种思维方式:把「好用」翻译成「可验证」。Prompt 写得再漂亮,最后仍要落回这几类边界;不如一开始就写进去,让模型第一次生成就更接近可交付。
二十、思考题
- 回顾你最近三次让 AI 写代码失败的对话:分别缺了 RCTFC 里的哪一块?如果补一句最有效的话,你会补哪句?
- 你会把哪些约束从「每次 User Prompt」迁移到「System / 规则文件」?迁移的判据是什么?
- Few-shot 示例如果包含历史错误写法,会对模型产生怎样系统性偏差?你如何设计「正例 + 反例」而不污染上下文?
二十一、下讲预告
下一讲我们进入模块二的核心转折:Workflow 比 Prompt 更重要。你会看到一张「设计先行 → 审查 → 实现 → 验证」的流水线如何把个人经验复制成团队节奏,并把参考实践里的「方案先行、实现为后」嵌进日常开发。Prompt 再漂亮,没有流程,只会得到高质量的随机漫步。
参考阅读:reference/articles/01-core-concepts.md(Agent / Agentic Engineering / 规范与质量纪律);课程原稿 课程内容/2.1 不会提问AI就像不会带团队:高质量指令实战.md(已在本讲中重构为可执行方法)。