提示工程
第 20 课:提示工程
上一课我们学会了怎么「证明」一个模型变好了——用评测把分数拆成构念、污染、判官与噪声。这一课回到最日常、也最像玄学的动作:把话说给模型听。同一个模型、同一个问题,只是换个问法,结果为什么能差一大截?提示里给不给例子(zero-shot 零样本 / few-shot 少样本,即给 0 个还是几个示例)、要不要让它先一步步想(Chain-of-Thought 思维链)、输出怎么卡成 JSON(用键值对表示数据的文本格式)、调不好时该改提示还是改检索与微调、一个 Agent 的 prompt(提示词)到底由哪几块拼成——这一课把「提示工程」从手感变成可算的动作,也为下一课《从 Prompt 到 Agent》把最基础的接口讲清楚。
② 用 TriviaQA 的三个读数(64.3 / 68.0 / 71.2)算清 zero-shot→few-shot 的收益与 token 成本(给 5 个例子,prompt 膨胀 8.5 倍),并用 Binomial(40, 0.569) 算出「多条理由投票」把正确率抬到 76.56%
③ 用 8000 token 的窗口把 Agent 的 prompt 拆成 system prompt(系统提示)+ 角色 + context(上下文),并算出历史涨到 6000 token 时超支 400 token
先澄清一个直觉误区:模型不是「听懂了你的意思」,而是「在你给的条件上,接着往下续写」。
因为它每一步输出,都是从一张候选表里挑一个 token——而这张候选表(每个候选的分数叫 logits(模型给每个候选 token 打出的原始分数))完全由你这段 prompt 决定。prompt 里多一个词、少一个标点,候选表就会挪一挪。
就像同一份外卖备注:写「不要辣」和写「少放辣椒、小孩吃」,厨师出餐很可能就是两道菜——你改的是备注,不是厨房。回到术语:能左右模型输出的这段输入,就是 prompt。所以下面用三笔账,把「换个问法」到底动了什么算出来。
| 项 | 直接问 | 加一句限定后 |
|---|---|---|
| 候选 A 的 logits | 2.1 | 1.9 |
| 候选 B 的 logits | 2.0 | 2.2 |
| A 的概率(softmax) | 0.525 | 0.4256 |
| B 的概率(softmax) | 0.475 | 0.5744 |
| 项 | 数值 | 来源 / 口径 |
|---|---|---|
| 示意基准正确率 | 70% | 示意值 |
| 释义后最坏上浮 | 70 × (1 + 10%) = 77% | 波动上限 10%(arXiv:2503.00137) |
| 释义后最坏下探 | 70 × (1 − 10%) = 63% | 同上 |
| 同一模型、同一批题的跨度 | 77 − 63 = 14 分 | 算术 |
| 情形 | 题数 | 答案改变的题数 | 占比 |
|---|---|---|---|
| 直接问 vs 换个说法 | 200 | 28 | 28 ÷ 200 = 14% |
prompt 不是「说明」,而是模型这次作答的条件:你写下的每个词都会挪动候选表的 logits。所以「换个问法结果差这么多」不是模型不稳定,而是条件变了。提示工程的第一条纪律由此而来——把提示当成代码来对待:写清楚、固定住、可复现,而不是每次随口一说。
上一张卡片看清了 prompt 是「条件」。这一张问:条件里放几个例子,为什么效果差这么多?
因为模型读这几个例子时,参数一个都没变——它只是把例子当成了本轮 context(模型这一次能看到的全部输入)的一部分,在续写时照着格式与规律往下接。就像你临时给同事看三张填好的报销单:他没参加过培训、也没改自己的习惯,却照着样例把第四张填对了——他「会」的是当场照猫画虎,不是学会了新技能。这类「不改参数、只靠上下文里的例子当场学会」的能力,就叫 in-context learning。所以下面用 GPT-3 论文的三个真实读数,把「给例子」的收益和代价一起算。
更自然的做法是一个例子都不给(zero-shot)——模型只能靠指令本身去猜格式;GPT-3 在 TriviaQA 上从 zero-shot 的 64.3% 抬到 few-shot 的 71.2%,这 6.9 分就是「当场照抄示例格式」换来的。
| 设置 | 例子数 | 准确率 | 相对前档 |
|---|---|---|---|
| zero-shot(不用示例) | 0 | 64.3% | — |
| one-shot(1 个示例) | 1 | 68.0% | 68.0 − 64.3 = +3.7 |
| few-shot(若干示例) | K | 71.2% | 71.2 − 68.0 = +3.2 |
| 组成 | token 数 |
|---|---|
| 系统说明 + 问题 | 40 |
| 每个示例 | 60 |
| K = 5 时 prompt = 40 + 5 × 60 | 340 |
| 相对不给例子(40) | 340 ÷ 40 = 8.5 倍 |
| K(示例数) | prompt token = 40 + 60K | 相对零样本 |
|---|---|---|
| 1 | 100 | 2.5 倍 |
| 5 | 340 | 8.5 倍 |
| 32 | 1960 | 49.0 倍 |
zero-shot / one-shot / few-shot 的区别只是 context 里放几个例子,模型权重自始至终不变。所以 few-shot 的收益来自「当场照抄格式与规律」,不是「训练」。代价是每个请求都要重读一遍例子(token 线性增长)。一句话:例子是放进 context 的,不是写进权重的——这条界线,正是后面「检索 vs 微调」选型的起点。
上一张卡片给的是「例子」。这一张问:对同样的题目,为什么让它先写推理步骤、再给答案,正确率就上去了?
因为模型每多吐一个 token(它输出的最小文本单位)就多做一步计算——直接答只能一口气算完,先想再答等于把计算量摊开。就像让心算和列竖式比:题目不会变,但列了竖式就有草稿可对齐、可回看。这种做法叫 Chain-of-Thought。要特别划清一条线:CoT 是「推理时」的技巧——你改的是提示,模型权重一点没动;而「把推理能力直接训进权重」是另一条路,那是第 12 课(推理模型与 RLVR(用可验证的奖励把推理写进权重))的主题。所以下面把 CoT 的收益、代价,以及它的升级版 Self-Consistency 一起算。
更自然的做法是直接要答案——PaLM 540B 在 GSM8K 上直接答只有 17.9%,换成带步骤的示例后跳到 56.9%,这 39 分的差距全来自「把中间步骤也吐成 token」。
| 提示方式 | PaLM 540B 解得率 |
|---|---|
| 标准提示(直接给答案) | 17.9% |
| 思维链提示(带步骤的示例) | 56.9% |
| 输出方式 | 大致 token 数 | 说明 |
|---|---|---|
| 直接给答案 | 5 | 只有一个结果 |
| 带步骤推理 | 120 | 几行推导 + 结论 |
| 倍数 | 120 ÷ 5 = 24 倍 | 时间与费用随之上升 |
| 项 | 数值 |
|---|---|
| 采样条数 n | 40 |
| 单条正确率 p(沿用 56.9%) | 0.569 |
| 40 条里正确票的期望 | 40 × 0.569 = 22.76 票 |
| 多数票落在正确项的概率 | ≈ 76.56% |
思维链把「一口气算」摊成多步,Self-Consistency 再把「一条链」变成「多条链投票」。两者的共同点是都在推理时发生,权重不变——这和第 12 课「把推理能力训进权重」是两条不同的路。记住这条界线:提示层面的技巧,改的是 context;训练层面的能力,改的是参数。
上一张卡片的思维链会吐出一大段自然语言。这一张问:当下游是一个程序时,怎么保证它收到的一定是能解析的结构?
因为模型的输出是逐 token 采样出来的,每个位置都可能冒出任何字符——哪怕提示里写了「只输出 JSON」(JSON(用键值对表示数据的文本格式)),它也可能多一句「好的,这是结果:」,或漏掉一个括号,让整段解析失败。就像你要求实习生「只填表格」,他仍可能交回一张边角写了备注的纸——格式约束不能只靠口头叮嘱。所以行业给了两级做法:先是 JSON mode,再是 Structured Outputs。下面把「靠解析兜底」和「结构约束」的代价算出来。
更自然的做法是在提示里补一句「请只输出 JSON」——但生成是逐 token 采样的,模型仍可能多一句前缀或漏一个括号:示意算例里 15% 的输出格式不对,等于每次成功要多打约 1.176 次请求。
| 项 | 数值 |
|---|---|
| 单次调用解析失败率(示意) | 15% |
| 成功率 | 1 − 0.15 = 85% |
| 每次成功平均需要的调用次数 | 1 ÷ 0.85 = 1.176 次 |
| 1000 次成功实际要调用 | 1000 ÷ 0.85 ≈ 1176 次 |
| 项 | 自由文本 + 正则 | 结构化输出 |
|---|---|---|
| 格式合法率 | 85%(示意) | 接近 100% |
| 要不要写正则兜底 | 要 | 不用(按 Schema 反序列化) |
| 缺字段风险 | 有 | 由 Schema 的必填字段声明挡住 |
| 项 | 数值 |
|---|---|
| 用结构化输出后解析失败率 | ≈ 0 |
| 每次成功调用次数 | 1.0 次 |
| 比 Step 1 的 1.176 次省下 | (1.176 − 1.0) ÷ 1.176 ≈ 15% |
| 1000 次成功省下的调用 | 约 176 次 |
让输出「可被程序接住」有三层,缺一层都不稳:① 说清要什么结构(提示里给 Schema)→ ② 用结构约束解码(结构化输出,把非法 token 从候选里挡掉)→ ③ 关掉随机性(temperature = 0)。这三层做完,「解析输出」才从「每次写正则兜底」变成「直接反序列化」。这也是第 22 课「工具调用」的地基:要调用工具,先得稳定拿到工具名和参数。
上一张卡片让输出稳定了。这一张处理最现实的岔路:提示改到怀疑人生,是不是该换武器了?(承第 17 课 Card 6:那里从"选哪条路"入场,这里从"提示的边界"入场——两处侧重不同。)
因为三条路的病根不同:提示只能改 context(这次输入里有什么),改不了模型没见过的事实;检索能把外部文档塞进 context,却改不了模型「不擅长某动作」;微调才动参数,能把风格与格式固化进权重。就像孩子考试:题目没见过 → 给他翻书(检索);粗心算错 → 多练题型(微调);只是没看清题干 → 重读一遍题干(改提示)——用错药都白费。判断标准很简单:「不知道」找检索,「不熟练」才考虑微调,「没说清」先好好写提示。所以下面用成本把三条路摆到同一口径上。
更自然的反应是「调不好就继续改提示」——但如果病根是模型没见过某条事实,改多少次措辞都补不进这条知识;这时该把资料检索进来(RAG),而不是在提示上继续打磨。
| 组成 | token 数 |
|---|---|
| 系统说明 + 问题 | 40 |
| 5 个示例 | 5 × 60 = 300 |
| 合计 prompt | 340 |
| 组成 | token 数 |
|---|---|
| 系统说明 + 问题 | 40 |
| 检索回来的资料(k = 5 段,每段约 500) | 5 × 500 = 2500 |
| 合计 prompt | 2540 |
| 是 few-shot 的 | 2540 ÷ 340 ≈ 7.5 倍 |
| 项 | 数值 |
|---|---|
| 微调后每次请求(不再塞示例) | 40 |
| 相对 few-shot 的 340 | (340 − 40) ÷ 340 ≈ 88.2% |
| 月请求 100 万次时省下的 token | (340 − 40) × 1,000,000 = 3 亿 |
选型按「病根」走,而不是按手感:① 没说清 → 改提示(最便宜,先穷尽);② 缺知识/缺事实 → 上检索(把文档塞进 context,后续有专门一课讲 RAG 工程化);③ 不擅长固定动作 → 才考虑微调(动权重、要数据)。一条判据贯穿:能被「说清楚」的绝不先上微调,能靠「检索到」的绝不先上微调——因为改动成本差一个量级,越贵越难回滚。
上一张卡片给定了一条选型线。这一张把整课的零件装起来:一个 Agent 的 prompt 到底由哪几块组成?
因为Agent 不会被只喂一句问题,它每一轮都要把「我是谁、有什么工具、记得什么、刚才发生了什么、现在要干嘛」一起递进去——这些拼起来就是它这一轮的 context。其中固定不变的那部分叫 system prompt,会变的(历史对话、检索结果、工具返回值)再逐轮拼上。就像给新员工的工牌背面:先印死「你是谁、能开哪些门」,每天再贴上当天任务便签——工牌是常量,便签是变量。把这层想清楚,「prompt 工程」就升级成了上下文组装。所以下面用 token 预算把这四块算一遍,看看什么时候会撑爆窗口。
| 组成 | token 数(示意) |
|---|---|
| system prompt(角色 + 规则) | 400 |
| 工具说明(名称、参数、用途) | 800 |
| 长期记忆 / 用户画像 | 1000 |
| 历史对话 | 3000 |
| 本轮问题 | 200 |
| 合计 | 5400 |
| 项 | 数值 |
|---|---|
| 模型上下文窗口 | 8000 |
| 已占(输入) | 5400 |
| 留给输出 | 8000 − 5400 = 2600 |
| 项 | 历史 3000 时 | 历史 6000 时 |
|---|---|---|
| 输入合计 | 5400 | 8400 |
| 窗口 8000 下是否超支 | 否 | 超 8400 − 8000 = 400 |
Agent 的 prompt = system prompt(常量)+ 角色 + 动态 context(历史 / 记忆 / 检索 / 工具返回),而它天生受 token 预算约束。所以「会写 prompt」在 Agent 里变成了「会做上下文组装」:谁的优先级高、什么时候压缩、什么时候丢弃,都是可算的工程决策。带着这套组装方式,下一课《从 Prompt 到 Agent》就能进入真正的循环:让模型不只是回答一次,而是一边行动、一边把结果拼回 context。