前言

三月初我写了一篇 ChatGPT API 的接入笔记,当时我说”难点全在怎么用好它”。两个多月过去,这句话的分量越来越重。同样一个模型,有人能稳定输出生产级代码,有人只会得到一堆客套话和幻觉——差距不在模型,在 Prompt。

这两个多月我在团队里推了几次 Prompt 分享会,也踩了不少坑。这篇文章把我的方法论沉淀下来:四个基本原则、四个前端高频场景的实战模板、结构化输出的玩法,以及那些我交过学费的反模式。写完这篇我越来越确信一件事:**Prompt 是前端工程师的新”编程语言”**——它不是玄学,是有章法的工程实践。

1. 为什么说 Prompt 是新的编程语言

先讲一个我自己的对比实验。同样让模型”写一个轮播图组件”:

  • 只发这一句话,得到的是一段 80 行的模糊代码:没有任何配置项、用了我没说要用的库、样式内联;
  • 而当我把角色、技术栈、输出格式、示例都写清楚之后,得到的是一份可以直接进项目的组件。

这就是 Prompt 的本质:它是人与模型之间唯一可编程的接口。我们平时编程用的”输入”是类型化的参数,编译器会校验;而模型的输入是自然语言,它只会按概率去”猜”你要什么。你描述得越精确,猜错率越低。

换个角度看,Prompt 和写代码的相似度超乎想象:都要声明上下文(环境/角色)、都要定义输入输出(I/O 契约)、都要处理边界情况(few-shot 示例)、都要迭代调试。区别只是它没有编译器帮你报错——模型永远会”装作听懂了”。所以写 Prompt 的第一素养是:别假设模型懂你,把一切前提写出来。

Prompt 工程四要素:角色设定、示例引导、分步思考、格式约束

2. 四个基本原则

这四条原则是这两个多月里反复试错后沉淀下来的,它们不算高深理论,更像写代码时的输入校验——把”模型猜你要什么”变成”你明确告诉它要什么”。每一条背后都有对应的踩坑经历,下面挨个展开。

2.1 明确角色(Role)

给模型一个具体的职业身份,输出质量会有肉眼可见的提升。原理不复杂:角色激活了预训练语料中对应领域的分布,相当于给模型”戴上了正确的帽子”。

1
2
你是一位有 10 年 React 经验的高级前端工程师,精通 TypeScript 与性能优化。
你的回答风格:直接给结论和代码,不写废话,不确定的地方明确说明"我不确定"。

注意角色的两个加分项:一是具体(”前端工程师”不如”React + TS 方向的资深工程师”),二是加上行为约束(比如”不确定要明说”,能有效压低幻觉)。

2.2 给示例(Few-shot)

与其花 500 字描述”我要什么样的输出”,不如直接给 1~3 个输入输出示例。人类学习如此,模型学习也是如此——这叫 in-context learning,模型会在推理时”照猫画虎”。

1
2
3
4
5
6
7
8
9
10
11
12
13
把下面的用户反馈分类,输出类型:bug / feature / complaint / other。

输入:列表翻页到第三页就白屏了
输出:bug

输入:希望支持暗黑模式
输出:feature

输入:每次更新都闪退,烦死了
输出:complaint

输入:现在开始分类这条反馈:导出的表格列宽不对
输出:

2.3 分步思考(Chain-of-Thought)

模型遇到复杂问题容易”抢答”,第一个 token 就开始输出,方向错了后面全错。让它先想后答,准确率会显著提升。最简单的做法是在 Prompt 末尾加一句:

1
请一步一步分析,先列出需要考虑的因素,再给出最终答案。

实战中我更常用”分段指令”:把复杂任务拆成编号步骤,让模型按步骤执行。比如代码 review 任务的 Prompt 里我会写:先总结这段代码的目的,再逐条列出潜在问题(按严重程度排序),最后给出修改后的代码。

2.4 指定输出格式

这是程序员视角最重要的原则:你的 Prompt 要自带输出契约。明确告诉模型输出结构(比如 Markdown 小节、固定字段、表格),下游处理才不会崩。做到极致就是让模型只吐 JSON,第 4 节专门讲。

3. 前端高频场景实战

下面四个场景是我这两个多月用得最多、也打磨得最成熟的模板,直接可抄。

3.1 生成组件代码

反面教材是”帮我写一个 Modal 组件”。打磨后的模板长这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
你是一位 React + TypeScript 专家。请为以下需求生成一个组件:

需求:可复用的 Modal 弹窗组件
技术栈:React 18 + TypeScript,不用 UI 库
必须满足:
1. Props 包含 visible、title、onClose、children、width(默认 480)
2. 支持 Esc 键关闭和点击遮罩关闭
3. 使用 Portal 渲染到 body,避免父级 overflow 裁剪
4. 内置简单的打开/关闭动画(CSS transition)
5. 附带使用示例代码

输出格式:
## 组件代码(Modal.tsx)
## 样式代码(Modal.css)
## 使用示例(App.tsx)

对比一下:用这个模板生成的代码,Props 定义、Portal、Esc 处理都是齐的,改改就能用;而”一句话版”的输出基本要重写。约束写得越具体,返工越少,这是我最大的心得。

3.2 写单元测试

写单测是我认为性价比最高的 AI 场景——枯燥、模式化、且正确性很容易验证(跑一遍就知道了):

1
2
3
4
5
6
7
8
9
10
11
你是一位测试工程师。为下面的函数编写 Jest 单元测试:

函数代码:
<把代码粘到这里>

要求:
1. 覆盖:正常路径、边界值、异常输入三类
2. 边界值至少考虑:空数组、空字符串、0、null、超长输入
3. 异常输入要断言抛出的错误类型
4. 每个测试用例用中文注释说明"这个用例在测什么"
5. 输出完整的测试文件(describe + it),可直接运行

一个提醒:生成的单测必须真的跑。我遇到过 AI 写的单测里断言了一个和实现不一致的”想象中的行为”——它不是在测你的代码,是在测它幻觉里的代码。

3.3 代码 Review

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
你是一位严格的代码评审者,请 review 以下 diff,重点关注:

1. 正确性:逻辑错误、边界条件、竞态
2. 安全性:XSS、注入、敏感信息泄露
3. 性能:不必要的重渲染、大列表、内存泄漏
4. 可维护性:命名、重复代码、复杂度

输出格式(JSON,见下方 schema):
[{ "severity": "high|medium|low", "file": "文件名", "line": "大致位置",
"problem": "问题描述", "suggestion": "修改建议及示例代码" }]

只输出 JSON,不要输出其他任何文字。

diff 如下:
<粘贴 diff>

第三个场景我把它接进了 CI:MR 提交后跑一遍脚本,把 AI review 的结果以评论形式贴到 MR 里当”第一遍粗筛”。人再精读,效率高不少。

3.4 正则 / SQL 生成

这类”领域专用语言”特别适合 AI:样本多、可验证、人写起来费劲。模板要点是同时给正例和反例:

1
2
3
4
5
6
7
8
9
10
11
请生成一个 JavaScript 正则表达式,并解释每一部分的含义。

需求:校验密码强度
必须满足:
- 匹配(正例):Abc12345、Xy9!@#ab
- 不匹配(反例):12345678(无字母)、abcdefgh(无数字)、AB12(长度不足)

输出格式:
1. 正则表达式(JS 字面量形式)
2. 逐段解释
3. 一个可直接运行的验证函数 test(password),对上面的正例返回 true、反例返回 false

给出反例这一步非常关键。没有反例时,模型给的”宽松正则”经常能通过所有正例但你想要的拒绝项全漏掉——有反例它就会自我校准。

4. 结构化输出:让大模型变成”可解析的接口”

前三个场景里我都强调了输出格式,这里展开讲讲最工程化的形态:让模型输出纯 JSON。这解决了把 AI 接进工程链路的核心问题——程序没法解析”一段话”,但可以解析 JSON。

基础做法是在 Prompt 里用 JSON Schema 约束 + few-shot 给一个完整示例,同时把 temperature 调到 0:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const res = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model: 'gpt-3.5-turbo',
temperature: 0,
messages: [
{
role: 'system',
content: '你是信息抽取引擎。从用户输入的文本中抽取商品信息,'
+ '严格按照以下 JSON schema 输出,不要输出任何解释文字:\n'
+ '{"name": string, "price": number, "tags": string[]}\n'
+ '示例输入:"无线蓝牙耳机 Pro 白色版,售价 399,支持降噪"\n'
+ '示例输出:{"name":"无线蓝牙耳机 Pro","price":399,"tags":["白色","降噪","蓝牙"]}'
},
{ role: 'user', content: text }
]
})
});
const data = await res.json();
// 防御式解析:模型偶尔仍会夹带 ```json 包裹或多余文字
const raw = data.choices[0].message.content.trim();
const jsonStr = raw.replace(/^```(json)?|```$/g, '').trim();
let info;
try {
info = JSON.parse(jsonStr);
} catch {
// 解析失败的重试逻辑:把上次错误输出回喂给模型,让它修复
}

三个实战经验:

  1. temperature 调 0:抽取、分类这类确定性任务不需要”创造力”,0 能显著提升 JSON 合法率;
  2. 永远 try/catch:再好的 Prompt 也不能 100% 保证合法 JSON(比如值里夹个没转义的引号),工程上必须防御。进阶做法是把解析失败的报错回喂给模型:”你上次的输出解析失败,错误是 xxx,请修复后重新输出”;
  3. schema 里的字段尽量都给示例值:模型对”示例”的遵循度远高于对”描述”的遵循度。

5. 反模式与常见的坑

这些是我自己交过学费的,按出现频率排序:

  • 一句话裸奔。期望模型读心是最大的反模式。你省下的每一句约束,都会变成你 later 的每一行返工。
  • 一次问太多。把”改这个 bug、顺便重构、再补文档”塞进一个 Prompt,输出质量会断崖式下跌。一个 Prompt 一个任务,复杂任务拆成多轮对话串行做。
  • 示例自相矛盾。few-shot 的示例必须和你的格式要求完全一致。我踩过一次:文字要求输出三个字段,示例里只有两个,模型坚定地学了示例——它跟示例走,不跟描述走。
  • 把 Prompt 当咒语。网上流传的”咒语式 Prompt”(什么”你是拥有一百年经验的绝世高手”)大多没用,角色要服务于任务,而不是故弄玄虚。
  • 忘记它是概率模型。同一个 Prompt 跑十次可能得到十个版本。凡是拿 AI 输出直接进生产链路的地方(代码、配置、数据),必须有校验、必须有测试兜底。信任要建立在验证之上,不是建立在”它看起来很对”之上。
  • 上下文污染。长对话里前面某轮的错误结论会被模型反复”引用”。发现跑偏就开新会话,别在泥潭里继续挣扎。

6. 一点思考:Prompt 会成为前端的必修课吗

两个多月用下来,我的答案是:会,而且比想象中来得快。

原因很直白:前端的日常工作本质是”把人的意图翻译成机器可执行的描述”——设计稿翻译成组件、PRD 翻译成代码。而 Prompt 工程干的也是同一件事,只是”机器”从浏览器换成了模型。我们这批人恰好站在翻译层的中间,Prompt 能力会直接决定我们驾驭模型的上限。

我的做法是把 Prompt 资产化:团队建了一个 Prompt 库仓库,按场景沉淀模板(组件生成、单测、review、抽取……),每个模板带一个真实用例和失败案例。代码我们会抽公共函数复用,Prompt 为什么不呢?

另一个观察是学习曲线比想象中平缓:真正上手只需要理解第 2 节那四个基本原则,剩下的提升都来自”多写、多跑、多改”。就像当年学 Git,难的不是命令本身,而是养成”每步都提交”的习惯。Prompt 也一样,难的不是技巧,是把”迭代打磨”变成肌肉记忆——别指望一次写对,指望第二次比第一次好。

写在最后

Prompt 工程听起来玄,拆开看就是老老实实的工程方法:定义角色、给足上下文、给示例、给格式约束、验证输出、迭代打磨。它没有编译器报错,但有”跑一遍看结果”这个终极校验——别信你的感觉,跑。

下一篇我打算换个视角:聊聊 AI 帮我”写代码”的体验——我 Copilot 已经付费用了两个多月,是时候交一份实战复盘了。如果你有自己打磨得顺手的 Prompt 模板,欢迎在评论区交流。