很多 Prompt 调优建议会说“加 Few-shot”“降低温度”“做 A/B 测试”,但没有回答一个更实际的问题:面对一条效果不好的 Prompt,下一步具体该改什么,怎样知道改对了?
答案不是继续润色文字,而是把 Prompt 当成一段可测试的程序:先定义输入输出契约,用真实失败样本建立评估集,再针对某一种失败做单变量实验。只有离线结果稳定改善,才值得进入线上 A/B。
调优前先判断:问题真的在 Prompt 吗?
以下问题改 Prompt 通常没有用,应该先修上游或下游:
| 现象 | 更可能的原因 | 应优先处理 |
|---|---|---|
| 回答依据错误或缺失 | 检索没有召回正确文档 | RAG 查询、分片、召回和重排序 |
| JSON 经常解析失败 | 没有结构化输出能力或解析器太脆弱 | JSON Schema / Structured Outputs / 解析与重试 |
| 工具调用失败 | 参数校验、权限或工具实现有问题 | Tool schema、服务端校验、超时与重试 |
| 同一输入每次差异很大 | 模型采样参数或上下文不稳定 | 固定模型版本、温度、上下文和工具结果 |
| 任务本身需要专业知识 | 模型或知识源能力不足 | 补充知识、换模型或人工兜底 |
Prompt 适合解决的是:任务目标不清楚、规则有歧义、输出遗漏、格式不符合契约、面对边界情况时决策不一致。
用一个具体任务定义“好结果”
假设要从客服消息中抽取退款工单,返回给后端系统。不要从“请你成为专业客服助手”开始,而要先写清楚输出契约:
{
"intent": "refund | other",
"order_id": "string | null",
"reason": "quality | delivery | duplicate_charge | other | null",
"needs_human": true,
"reply": "string"
}
再定义每个字段如何判定:订单号不明确时填 null;用户只是在咨询政策时 intent 为 other;出现威胁、欺诈或无法判断的信息时 needs_human 为 true。这份契约比“写得更详细的 Prompt”更重要,因为它同时决定了 Prompt、评估脚本和后端处理逻辑。
第一步:建立可复现的基线
先保留当前 Prompt 为 v0,固定模型版本、温度、最大输出长度、工具列表和 RAG 配置。准备 30 到 50 条脱敏的真实输入,不能只挑正常案例,应至少包含:
- 明确的正常请求;
- 信息缺失、错别字和口语化表达;
- 多个意图混在一条消息中;
- 容易误判的反例,例如“我想了解退款规则”;
- 需要人工处理的高风险案例。
每条样本记录期望字段、关键事实和允许的回答范围。先跑一次 v0,把结果按失败类型归档,而不是简单记为“回答不好”。
| 样本 | 期望 | 实际结果 | 失败标签 |
|---|---|---|---|
| “订单 A123 重复扣款了” | duplicate_charge |
other |
分类错误 |
| “商品有破损,订单号找不到了” | order_id: null |
编造订单号 | 事实编造 |
| “退款多久到账?” | intent: other |
创建退款工单 | 意图边界错误 |
| “帮我尽快处理” | 合法 JSON | 缺少 needs_human |
格式/字段遗漏 |
这张失败表就是下一轮调优的待办列表。
第二步:把失败原因映射为动作
| 失败类型 | 先检查什么 | 常见的 Prompt 改动 |
|---|---|---|
| 漏字段、字段类型错误 | 输出契约是否明确 | 写出完整 Schema、字段含义和 null 规则 |
| 规则误解、意图混淆 | 规则是否可判定 | 改成决策条件与优先级,而非抽象要求 |
| 边界案例误判 | 是否缺少代表性反例 | 增加 1-3 个最有区分度的 Few-shot 示例 |
| 编造上下文没有的信息 | 是否说明信息来源边界 | 明确“仅使用输入内容;未知则填 null/转人工” |
| 回答啰嗦或语气不当 | 输出目标是否具体 | 指定受众、长度、禁用内容和输出模板 |
不要一次同时加入角色设定、十个示例、几十条规则和新的检索策略。那样即使结果改善,也不知道真正有效的因素是什么,更难排查回归。
第三步:做单变量 Prompt 实验
假设基线显示最多的问题是:模型在没有订单号时会编造订单号。先只改这一件事。
v0:
从用户消息中抽取退款工单,按 JSON 返回。
v1,只新增事实边界:
从用户消息中抽取退款工单,按指定 JSON 返回。
事实边界:只使用用户消息中明确出现的信息。
- 未出现订单号时,order_id 必须为 null。
- 不得猜测或补全订单号、商品或退款原因。
运行同一份评估集,重点看“编造订单号”是否下降,同时检查是否引入了新问题,例如把合法订单号也误判为 null。如果 v1 有改善,再以 v1 为基线处理下一个最常见失败。
何时使用 Few-shot
Few-shot 不是默认配料。它适合规则难以用一句话定义、但输入输出模式很稳定的情况,例如区分“申请退款”和“咨询退款政策”。示例要覆盖最容易混淆的边界,而不是重复展示简单正确样本。
示例:
用户:退款多久能到账?
输出:{"intent":"other", "order_id":null, ...}
用户:订单 A123 重复扣款,请退款。
输出:{"intent":"refund", "order_id":"A123", ...}
示例会占用上下文并增加维护成本。新增示例后,必须把它们之外的样本作为测试集,避免只是在“背题”。
第四步:让评估替代感觉
评估至少要同时看质量、可靠性和成本。对于上面的结构化任务,可定义:
字段准确率 = 正确字段数 / 应评字段数
格式合规率 = 能通过 JSON Schema 校验的输出数 / 总输出数
编造率 = 含输入中不存在的关键事实的输出数 / 总输出数
人工转交准确率 = needs_human 判断正确数 / 总样本数
成本 = 输入 token + 输出 token + 工具调用成本
延迟 = P50 / P95 端到端耗时
将评估集分为两部分:开发集用于迭代,保留集只在候选版本确定后评估。否则反复针对同一批样本调 Prompt,会高估真实收益。
一份最小版本记录就足够开始:
| 版本 | 唯一改动 | 字段准确率 | 格式合规率 | 编造率 | P95 延迟 | 结论 |
|---|---|---|---|---|---|---|
| v0 | 基线 | 82% | 91% | 12% | 1.2s | 当前线上版本 |
| v1 | 增加未知值规则 | 86% | 93% | 3% | 1.2s | 保留 |
| v2 | 增加两个边界示例 | 89% | 95% | 2% | 1.4s | 进入灰度 |
数字应来自实际数据。示例表只展示记录格式,不应被当作目标承诺。
离线对比通过后,才做线上 A/B
离线评估证明“在已知样本上更好”,不能保证线上更好。上线时将用户或会话稳定地分配到对照组 v0 和实验组 v2,避免同一个会话在中途切换 Prompt。
线上指标必须绑定业务结果,例如工单一次完成率、人工介入率、用户再次追问率、投诉率,以及成本和延迟。不要只比较模型自评分。
开始前写下放量和回滚条件,例如:
灰度比例:10%
放量条件:任务完成率不低于 v0,人工介入率下降至少 5%,P95 延迟增加不超过 10%
回滚条件:高风险错误率上升、投诉率异常,或任何安全规则被绕过
观察窗口:覆盖至少一个完整业务周期,并达到预先计算的样本量
如果样本量很小或高风险错误不能接受,不应为了“做 A/B”而放量。可以扩大离线评估、采用人工复核,或只在低风险流量中验证。
一次完整调优的工作清单
1. 写出输入、输出 Schema、未知值和人工转交规则。
2. 固定模型与运行参数,保存当前 Prompt 作为 v0。
3. 收集真实样本,标注期望结果和失败标签。
4. 跑 v0,按失败类型排序,选影响最大的一个问题。
5. 只针对这一个问题修改 Prompt,形成 v1。
6. 在开发集和保留集上比较 v0/v1 的质量、成本与延迟。
7. 记录唯一改动和结果;无改善就回退并换假设。
8. 离线稳定胜出后,小流量 A/B,并按预先定义的门槛放量或回滚。
9. 把线上新失败样本持续补入评估集。
总结
Prompt 调优的交付物不是一段“更聪明”的提示词,而是一套可复现的任务契约、评估集、版本记录和上线判定标准。先定位失败,再做单变量实验,最后让真实指标决定是否上线。