Prompt Engineering 系统指南

从任务定义、消息分层、few-shot、结构化输出到工具调用与评测回归,建立一套可复用的提示词工程方法。

难度

进阶

阅读时长

约 120 分钟

更新日期

2026/03/24

主题

Prompting / Few-shot / Chain-of-Thought / 结构化输出

先修知识

Transformer 注意力机制入门基础 API 调用常识JSON 与 Markdown 基础

学习目标

读完这篇教程后,你应该能:

  1. 理解 Prompt Engineering 不是“写魔法咒语”,而是任务接口设计。
  2. 区分 system、user、工具上下文、few-shot 示例分别在控制什么。
  3. 为结构化输出、检索问答、工具调用设计更稳的提示模板。
  4. 用评测集和失败样本,而不是凭感觉,持续迭代提示词。

如果你已经读过 Chain-of-Thought Prompting,这篇文章会把“提示词能激发什么能力”进一步落到产品和工程实践上。

为什么 Prompt Engineering 仍然是应用开发的第一现场

很多人把提示词工程理解成“临时技巧”,仿佛只要模型足够强,Prompt 就不重要了。现实恰好相反:

  • 同一个模型,提示设计不同,效果差距可能非常大。
  • 很多应用问题不是模型不会,而是任务定义不清。
  • 在微调、RAG、Agent 编排之前,Prompt 往往是成本最低的控制手段。

所以 Prompt Engineering 真正解决的不是“让模型变聪明”,而是:

  • 明确任务边界
  • 稳定输出格式
  • 减少误解和幻觉
  • 让模型行为更接近产品目标

把它看成“模型接口设计”,比把它看成“玄学调词”更接近真实工作。

第一步:先把任务拆清楚,再写提示词

一个好 Prompt 的前提不是文案华丽,而是任务定义足够明确。动手写之前,建议先回答四个问题:

  1. 用户真正要完成什么任务?
  2. 模型需要哪些输入信息?
  3. 输出必须满足什么格式或约束?
  4. 什么时候模型应该拒答、追问或降级?

如果这四件事你自己都说不清,那么写出来的提示词通常也会含糊不稳。

例如,“请总结这篇文档”看起来简单,但其实至少要明确:

  • 总结给谁看
  • 需要多长
  • 保留哪些重点
  • 是否要求列风险项
  • 是否要输出成固定模板

Prompt 工程的第一步,从来不是写句子,而是建任务规格。

第二步:理解消息分层在控制什么

现代 LLM 应用里,常见输入通常会分成几层:

  • system:角色、原则、输出边界
  • user:当前任务请求
  • context:附加材料,如 RAG 检索片段、工具结果、业务状态
  • examples:few-shot 示例

一个很常见的问题是把所有要求都堆进 user message,最后导致:

  • 任务信息和规则混在一起
  • 多轮对话后规则逐渐漂移
  • 模板越来越长却不稳定

更好的做法是分层管理:

  • system 负责稳定规则
  • user 负责当前具体请求
  • context 负责证据
  • examples 负责演示“理想答案长什么样”

这会让 Prompt 更容易维护,也更容易做 A/B 测试。

如果把这四层画开,会更容易理解为什么“所有信息都塞进 user message”通常会越写越乱:

Prompt 的消息分层结构图 展示 system、user、context 和 examples 四层在提示工程中的不同职责,以及它们共同作用于模型输出的方式。 Prompt 工程更像分层接口设计,而不是把所有要求混成一大段文本
  <g>
    <rect x="84" y="86" width="390" height="60" rx="18" fill="#ede9fe" stroke="#b7a8ea" />
    <text x="114" y="122" font-size="20" font-weight="700">System</text>
    <text x="212" y="122" font-size="13" fill="#4b5563">角色、原则、拒答边界、输出约束</text>
  </g>

  <g>
    <rect x="84" y="160" width="390" height="60" rx="18" fill="#e8f1ff" stroke="#98b7e1" />
    <text x="114" y="196" font-size="20" font-weight="700">User</text>
    <text x="212" y="196" font-size="13" fill="#4b5563">当前任务、目标、用户语气与具体需求</text>
  </g>

  <g>
    <rect x="84" y="234" width="390" height="60" rx="18" fill="#eef6e8" stroke="#a8c48e" />
    <text x="114" y="270" font-size="20" font-weight="700">Context</text>
    <text x="212" y="270" font-size="13" fill="#4b5563">RAG 证据、工具结果、业务状态、历史信息</text>
  </g>

  <g>
    <rect x="84" y="308" width="390" height="60" rx="18" fill="#fff4dc" stroke="#e2c36f" />
    <text x="114" y="344" font-size="20" font-weight="700">Examples</text>
    <text x="212" y="344" font-size="13" fill="#4b5563">few-shot 示例、风格样板、边界案例</text>
  </g>

  <g>
    <rect x="612" y="122" width="250" height="172" rx="24" fill="#f9fafb" stroke="#d7e2f1" />
    <text x="737" y="160" text-anchor="middle" font-size="21" font-weight="700">模型输出</text>
    <text x="737" y="192" text-anchor="middle" font-size="14" fill="#4b5563">回答内容</text>
    <text x="737" y="214" text-anchor="middle" font-size="14" fill="#4b5563">结构化格式</text>
    <text x="737" y="236" text-anchor="middle" font-size="14" fill="#4b5563">是否调工具</text>
    <text x="737" y="258" text-anchor="middle" font-size="14" fill="#4b5563">是否追问 / 拒答</text>
  </g>

  <line x1="474" y1="116" x2="612" y2="158" stroke="#5b6b7f" stroke-width="3" marker-end="url(#prompt-stack-arrow)" />
  <line x1="474" y1="190" x2="612" y2="190" stroke="#5b6b7f" stroke-width="3" marker-end="url(#prompt-stack-arrow)" />
  <line x1="474" y1="264" x2="612" y2="224" stroke="#5b6b7f" stroke-width="3" marker-end="url(#prompt-stack-arrow)" />
  <line x1="474" y1="338" x2="612" y2="258" stroke="#5b6b7f" stroke-width="3" marker-end="url(#prompt-stack-arrow)" />
</g>
system 定规则,user 给当前任务,context 提供证据,examples 演示合格答案。分层清楚之后,Prompt 才更容易维护和回归测试。

第三步:先写“最小清晰提示”,不要一开始就堆很多技巧

一个稳健的起点通常只需要包含四类信息:

  1. 角色
  2. 任务
  3. 约束
  4. 输出格式

例如:

你是一个中文技术助手。
任务:解释给定术语。
约束:
1. 只输出与问题直接相关的内容
2. 不确定时明确说明
3. 不编造论文或实验结果
输出格式:
- 定义
- 工作原理
- 应用场景
- 常见误区

这个版本看起来不复杂,但已经足够清晰。很多时候,模型失败并不是因为少了某个“高级技巧”,而是任务描述本身模糊。

第四步:few-shot 的价值不是“举例更多”,而是“给出判断标准”

few-shot 最大的价值,不在于给模型更多文本,而在于告诉它:

  • 什么样的输入属于这类任务
  • 什么样的输出才算合格
  • 风格、粒度、边界应该如何保持一致

一个典型 few-shot 模板会包含:

  • 1 到 3 个代表性输入
  • 对应的理想输出
  • 尽量覆盖真实应用中的关键难点

few-shot 设计时要特别避免两种情况:

  1. 例子太完美、太单一,模型只会模仿表面格式。
  2. 例子太多、太长,反而把关键任务指令淹没。

few-shot 更像是“任务说明书中的示例页”,而不是“往上下文里堆越多越好”。

第五步:Chain-of-Thought 要谨慎使用,而不是逢题必上

Chain-of-Thought Prompting 让很多人第一次意识到,提示词可以影响模型的推理展开方式。但在产品实践里,CoT 不是无脑通用增强器。

它更适合:

  • 数学或逻辑步骤明显的任务
  • 需要分阶段分析的复杂判断
  • 希望模型先做计划、再给答案的场景

它不一定适合:

  • 追求极低延迟的简单问答
  • 高度格式化的抽取任务
  • 对“输出必须简洁”要求很高的场景

一个更实用的思路是:

  • 当任务需要推理结构时,用分步骤提示或计划模板。
  • 当任务主要是抽取、改写、分类时,优先用明确 schema,而不是强行要求详细推理。

提示词工程真正重要的是“任务匹配”,不是把所有流行技巧叠满。

第六步:结构化输出是应用落地的关键能力

如果你的模型输出最终还要进数据库、前端组件、工作流引擎或工具调用链,那么“能不能稳定结构化输出”通常比“语言多优雅”更重要。

结构化输出 prompt 至少要明确:

  • 目标 schema 是什么
  • 字段含义是什么
  • 缺失信息如何表示
  • 不允许输出哪些额外文字

一个简单例子:

请根据用户问题生成 JSON,不要输出额外解释。

Schema:
{
  "task_type": "faq | retrieval | escalation",
  "priority": "low | medium | high",
  "requires_human": true | false,
  "reason": "string"
}

如果你没有把 schema 说清楚,模型往往会“自作聪明”地多加说明文字,导致后续解析失败。

一个更稳的结构化输出示例

可以把字段说明和边界进一步写细一点:

{
  "system": "你是客服分流助手。请严格输出 JSON。",
  "user": "我的订单已经扣款但后台还是显示未支付,怎么办?",
  "instructions": [
    "只能输出合法 JSON",
    "task_type 只能是 faq、retrieval、escalation 之一",
    "当涉及人工核实或资金风险时,requires_human 必须为 true",
    "reason 用一句中文说明判断依据"
  ]
}

这类 Prompt 的重点不是“更长”,而是“可执行性更强”。当你希望模型接入后续工作流时,清晰约束远比风格化措辞重要。

第七步:RAG 场景里的 Prompt 要强调证据,而不是流畅度

进入 RAG 场景后,Prompt 的重点会发生变化。此时你最该强调的是:

  • 只基于给定材料回答
  • 若证据不足,要明确承认
  • 优先引用资料,而不是自由发挥
  • 多段材料冲突时要指出冲突来源

这也是为什么 RAG Prompt 和普通聊天 Prompt 很不一样。普通聊天更强调自然性,RAG 更强调 groundedness,也就是“答案是否真正扎根于证据”。

如果你还没有把 RAG 链路本身搭起来,可以先看 RAG 系统搭建实战

第八步:工具调用 Prompt 要把“何时调用”和“如何调用”说清楚

当模型要接数据库、搜索、代码执行或企业系统 API 时,Prompt 又会多一个重点:工具使用规则。

一个好的工具调用提示至少要明确:

  • 什么情况下可以调用工具
  • 什么情况下不该调用
  • 调用前需要哪些参数
  • 调用失败后如何降级或重试

例如,不要只写“你可以使用搜索工具”,而要写得更明确:

  • 当问题依赖最新信息时先搜索
  • 当用户只问定义时不必搜索
  • 如果搜索结果不足,不要编造答案

Prompt Engineering 在这一步已经非常接近“工作流设计”,而不只是文本写作。

长上下文场景里,信息顺序会影响结果

Lost in the Middle 给应用开发很重要的一个提醒是:

不是把更多材料塞进上下文,模型就会更会用。

因此在设计长 Prompt 时,建议特别注意:

  • 最关键的信息尽量前置
  • 相关信息尽量聚合,而不是分散插入
  • few-shot 示例不要淹没真实任务
  • 长规则不要和检索材料混成一团

Prompt 长度管理,本质上也是上下文设计问题。

提示词应该怎么评测,而不是靠“手感不错”

Prompt 调整最常见的陷阱,是每次改完只问一句“感觉是不是更好了”。这会让团队永远陷在主观争论里。

更稳的方法是建立小型评测集,并给每类问题打标签,例如:

  • 标准样本
  • 改写样本
  • 长上下文样本
  • 风险样本
  • 结构化输出样本

然后观察改 Prompt 后哪些维度变好了、哪些维度变差了。这样你才知道:

  • 是格式更稳了
  • 还是正确性更高了
  • 还是只是回答变长了,看起来更像“认真回答”

想把评测方法再往前推进,可以继续看 LLM 评测方法论

一个实用的 Prompt 迭代循环

你可以把提示词优化做成一个非常清晰的闭环:

  1. 收集失败样本
  2. 给失败类型分类
  3. 针对某一类失败修改 Prompt
  4. 用固定评测集回归
  5. 检查是否引入副作用

比如:

  • 为了让 JSON 更稳,加入更严格 schema
  • 结果却让自由问答能力变差

这种副作用如果不通过回归集观察,很容易在上线后才暴露出来。

把这件事做成显式闭环,Prompt 迭代才不会退化成“拍脑袋换措辞”:

Prompt 迭代与回归闭环图 从线上失败样本出发,分类问题、定向修改 Prompt、跑固定评测集、检查副作用,再决定是否上线,形成可持续迭代闭环。 一个成熟的 Prompt 团队,优化的不是句子本身,而是“失败 -> 归因 -> 回归”的闭环
  <g>
    <rect x="34" y="114" width="160" height="74" rx="18" fill="#e8f1ff" stroke="#98b7e1" />
    <text x="114" y="145" text-anchor="middle" font-size="19" font-weight="700">失败样本</text>
    <text x="114" y="168" text-anchor="middle" font-size="13" fill="#4b5563">线上 bad case / 人工标注</text>
  </g>
  <g>
    <rect x="224" y="114" width="160" height="74" rx="18" fill="#eef6e8" stroke="#a8c48e" />
    <text x="304" y="145" text-anchor="middle" font-size="19" font-weight="700">失败分类</text>
    <text x="304" y="168" text-anchor="middle" font-size="13" fill="#4b5563">格式错 / 幻觉 / 边界错</text>
  </g>
  <g>
    <rect x="414" y="114" width="160" height="74" rx="18" fill="#fff4dc" stroke="#e2c36f" />
    <text x="494" y="145" text-anchor="middle" font-size="19" font-weight="700">Prompt 改动</text>
    <text x="494" y="168" text-anchor="middle" font-size="13" fill="#4b5563">只修一类问题,避免混改</text>
  </g>
  <g>
    <rect x="604" y="114" width="160" height="74" rx="18" fill="#fce7ef" stroke="#e2a8bd" />
    <text x="684" y="145" text-anchor="middle" font-size="19" font-weight="700">固定评测集</text>
    <text x="684" y="168" text-anchor="middle" font-size="13" fill="#4b5563">标准样本 + 风险样本</text>
  </g>
  <g>
    <rect x="794" y="114" width="152" height="74" rx="18" fill="#dff4f0" stroke="#8dc7bd" />
    <text x="870" y="145" text-anchor="middle" font-size="19" font-weight="700">上线判断</text>
    <text x="870" y="168" text-anchor="middle" font-size="13" fill="#4b5563">收益是否大于副作用</text>
  </g>

  <line x1="194" y1="151" x2="224" y2="151" stroke="#5b6b7f" stroke-width="3" marker-end="url(#prompt-loop-arrow)" />
  <line x1="384" y1="151" x2="414" y2="151" stroke="#5b6b7f" stroke-width="3" marker-end="url(#prompt-loop-arrow)" />
  <line x1="574" y1="151" x2="604" y2="151" stroke="#5b6b7f" stroke-width="3" marker-end="url(#prompt-loop-arrow)" />
  <line x1="764" y1="151" x2="794" y2="151" stroke="#5b6b7f" stroke-width="3" marker-end="url(#prompt-loop-arrow)" />
  <path d="M 870 188 C 870 264, 174 264, 114 188" fill="none" stroke="#5b6b7f" stroke-width="3" marker-end="url(#prompt-loop-arrow)" />
  <text x="492" y="298" text-anchor="middle" font-size="15" fill="#4b5563">如果出现副作用,就回到失败分类,而不是继续“叠规则碰运气”</text>
</g>
高质量 Prompt 迭代通常只改一类问题、跑一套固定回归集、明确观察副作用。这样才能分清“更稳了”还是“只是看起来更认真了”。

常见误区

1. 把 Prompt Engineering 当成“试词游戏”

真正有效的提示词工程依赖任务定义、失败样本和评测闭环,而不是随机换几句话碰运气。

2. 一次性塞太多规则

规则过多、互相冲突时,模型反而更难稳定遵守。

3. 例子很多,但没有代表性

few-shot 不是越多越好,关键是能不能代表真实任务中的难点。

4. 不区分模型能力问题和提示词问题

有些错误来自模型本身不会做,有些错误只是 prompt 没说明白。两者优化路径完全不同。

练习与思考题

  1. 为什么说 Prompt Engineering 的第一步是任务规格设计,而不是语言润色?
  2. 在什么情况下,few-shot 比增加更多规则更有效?
  3. 为什么结构化输出场景里,schema 约束通常比“写得礼貌”更重要?
  4. 如果一个 Prompt 在标准样本上更好了,却在边界样本上更差了,你应该如何判断是否上线?

延伸阅读

相关阅读

从相近主题继续深入,建立连续学习链路。