第三章 提示工程
3.1 Prompt 基础技巧:与模型对话的艺术
承前:在第二章中,我们从 Transformer 架构出发,一路拆解了 LLM 的工作原理——注意力机制如何让模型理解上下文、预训练如何构建知识体系、微调如何将通用能力转化为专业能力。你现在知道了模型"是什么"以及"怎么训练的"。然而,一块训练好的模型就像刚入职第一天的高智商实习生——他知道很多,但还不知道你这次到底想要什么、该用什么格式交付、边界在哪里。学会和模型对话,让模型把"知道"变成"按需输出",正是本章——提示工程(Prompt Engineering)要解决的问题。
3.1.1 什么是 Prompt Engineering?
Prompt Engineering(提示工程)是设计和优化输入给大语言模型的文本指令,以引导模型产生期望输出的系统性方法论。
换个更直白的类比:Prompt 就是你给实习生下达的工作指令。
- 你说"帮我看下这个文档"——实习生一脸茫然:看什么?看格式还是看内容?要修改意见还是总结摘要?
- 你说"帮我把这份 10 页的需求文档总结成 3 条要点,每条不超过 30 字,重点突出用户痛点"——实习生立刻知道该做什么、做多少、怎么交付。
模型也一样。Prompt 越具体、越明确,模型输出的质量和可控性就越高。从"看下文档"到"3 条要点 × 30 字 × 突出痛点",这就是提示工程要解决的问题。
┌─────────────────────────────────────────────────────────┐
│ Prompt Engineering 全景图 │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ Zero-shot│ │ Few-shot │ │ Chain-of- │ │
│ │ Prompt │ │ Prompt │ │ Thought │ │
│ └────┬─────┘ └────┬─────┘ └──────┬───────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 角色设定 + 输出格式控制 │ │
│ │ (System Prompt + Structured Output) │ │
│ └─────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘上图展示了本节将覆盖的三大核心策略(Zero-shot、Few-shot、CoT)以及两个通用增强技巧(角色设定、输出格式控制)。掌握这些组合,你已经能应对日常工作 80% 的 Prompt 编写需求。
3.1.2 Zero-shot Prompt(零样本提示)
概念:不给模型任何示例,直接描述任务要求。模型完全依赖在预训练阶段学到的知识和能力来完成任务。
为什么叫"零样本":"零"指的是 Prompt 中没有提供任何示范样本——不是零知识,模型已经从海量训练语料中"学会"了大量常识和语言能力。
生活类比:就像你对一个有 10 年经验的会计说"帮我把这份资产负债表解读一下"——不需要给他看你以前是怎么解读的,他自己就知道该怎么做。
适用场景:通用问答、文本摘要、翻译、情感分析等常见、标准化的任务。
示例:
请将以下英文翻译成中文,保持专业术语的准确性:
"Artificial Intelligence is transforming the way we interact with technology,
enabling machines to learn from experience and perform human-like tasks."为什么有效:现代 LLM(如 GPT-4、Claude 3.5、DeepSeek-V3)在海量多语言语料上训练,已经内化了翻译能力。Zero-shot 之所以能工作,是因为模型在预训练时见过大量"英文-中文"对照文本——这本质上是一种"迁移学习":训练时见过的模式,在推理时可以直接复用。
Python 代码示例(逐行注释):
from openai import OpenAI
# 创建客户端实例,API Key 默认从环境变量 OPENAI_API_KEY 读取
client = OpenAI()
# 构造 Zero-shot Prompt:只有任务描述 + 待翻译文本,不提供任何示例
prompt = "请将以下英文翻译成中文,保持专业术语的准确性:\n\n" \
"Artificial Intelligence is transforming the way we interact with technology."
# 调用 Chat Completions 接口
response = client.chat.completions.create(
model="gpt-4o", # 使用 GPT-4o 模型
messages=[ # messages 是对话列表,每条消息有 role 和 content
{"role": "user", "content": prompt} # role=user 表示用户消息
],
temperature=0.0, # temperature=0 让输出更稳定可复现,翻译任务不需要"创造力"
)
# 从返回结果中取出模型生成的文本
result = response.choices[0].message.content
print(result)Zero-shot 的局限:
- 对复杂、冷门或高度专业化的任务效果不佳——模型在训练时见过的相关语料少,"迁移"不过来。
- 输出格式不稳定,有时需要多次尝试——因为 Prompt 没有规定格式,模型每次输出都可能不同。
- 无法保证推理步骤的正确性——Zero-shot 时模型倾向于直接给答案,中间推理被"跳过"了。
常见误区:很多人第一次用 LLM 失败时,第一反应是"模型不够强"。实际上大多数 Zero-shot 失败的原因是 Prompt 太模糊。在你升级到 Few-shot 或 CoT 之前,先尝试把任务描述写得更具体、更结构化,往往能收到奇效。
3.1.3 Few-shot Prompt(少样本提示)
概念:在 Prompt 中提供 2-5 个输入-输出示例,让模型通过"类比学习"理解任务模式。
生活类比:你对新实习生说"帮我把客户邮件分类"——他可能不知道怎么分。但如果你先给他看 3 封邮件并告诉他"这封是投诉、这封是咨询、这封是合作"——他立刻就懂了,后面就能照着分类。
核心原理:LLM 具有强大的 In-Context Learning(上下文学习) 能力——模型不需要更新参数、不需要重新训练,仅通过观察 Prompt 中的示例就能临时"学会"新任务。这是 LLM 区别于传统 NLP 模型最令人惊叹的特性之一:传统模型要学新任务必须重新训练或微调,而 LLM 只要"看几个例子"就行。
示例——情感分类:
请判断以下评论的情感倾向(正面/负面/中性)。
示例1:
评论: "这款手机拍照效果非常好,电池续航也很给力。"
情感: 正面
示例2:
评论: "用了两周就频繁死机,售后服务态度也很差。"
情感: 负面
示例3:
评论: "快递今天到了,还没拆开使用。"
情感: 中性
现在请分析以下评论:
评论: "性价比一般,但外观设计确实很漂亮。"
情感:注意最后一行"情感:"后故意留空——这是 Few-shot 的标准写法,让模型自动"补全"最后的输出。
Python 代码示例(逐行注释):
from openai import OpenAI
client = OpenAI()
# 构造 Few-shot Prompt:3 个示例 + 1 个待分类的真实输入
prompt = """请判断以下评论的情感倾向(正面/负面/中性)。
示例1:
评论: "这款手机拍照效果非常好,电池续航也很给力。"
情感: 正面
示例2:
评论: "用了两周就频繁死机,售后服务态度也很差。"
情感: 负面
示例3:
评论: "快递今天到了,还没拆开使用。"
情感: 中性
现在请分析以下评论:
评论: "性价比一般,但外观设计确实很漂亮。"
情感:"""
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
temperature=0.0, # 分类任务需要确定性输出,温度设为 0
max_tokens=10, # 只需要"正面/负面/中性"几个字,限制 token 节省成本
)
print(response.choices[0].message.content)Few-shot 设计要点:
| 要点 | 说明 | 反面案例 |
|---|---|---|
| 示例质量 | 示例必须准确无误,错误示例会严重误导模型 | 示例把正面评论标成"负面" |
| 示例多样性 | 覆盖各种边界情况,帮助模型泛化 | 3 个示例全是"正面" |
| 示例数量 | 通常 2-5 个即可,过多会消耗 token 且收益递减 | 给 20 个示例,token 消耗翻倍但准确率只涨 1% |
| 格式一致性 | 所有示例保持相同的输入-输出格式 | 第一个示例用"情感:",第二个用"Sentiment:" |
| 标签平衡 | 分类任务中确保各类别示例数量均衡 | 3 个示例中 2 个正面、1 个负面 |
常见误区:示例越多越好。实际上,研究表明 2-5 个高质量示例的效果往往优于 10 个普通示例。过多示例不仅浪费 token、增加延迟,还可能让模型"过拟合"到示例的特定表述上,反而在真实输入上表现变差。
3.1.4 Chain-of-Thought(思维链提示)
概念:引导模型在给出最终答案前,先展示中间推理步骤。这是 2022 年由 Google Research 提出的突破性技术,论文标题为 Chain-of-Thought Prompting Elicits Reasoning in Large Language Models。
核心思想:
传统 Prompt: 问题 ────────────────► 答案
Chain-of-Thought: 问题 ─► 步骤1 ─► 步骤2 ─► 步骤3 ─► 答案生活类比:你问一个数学好的学生"37 × 23 等于多少"——如果他直接说"851",你可能不信;但如果他说"37 × 20 = 740,37 × 3 = 111,740 + 111 = 851",你就信了。展示推理过程不仅让别人更容易验证,也让推理者自己不容易出错。CoT 就是让模型"展示算式",而不是直接报答案。
为什么 CoT 有效:
- 分解复杂性:将难题拆解为简单子问题,每一步只处理一个小问题,降低单步推理的难度。
- 减少幻觉:显式推理步骤让模型"思考"而非"猜测"——直接给答案时,模型往往是从概率分布中"采"一个词,而 CoT 让它有更多"中间 token"来积累正确推理。
- 可解释性:推理过程透明,便于验证和调试——出了错你能看到是哪一步算错了。
- 涌现能力:在足够大的模型(>100B 参数)上,CoT 能激发原本被"压制"的逻辑推理能力——这是大模型独有的现象,小模型上 CoT 反而可能让输出变差。
Zero-shot CoT 示例:
请逐步思考以下问题:
一个农场有鸡和兔子共35只,脚的总数为94只。请问鸡和兔子各有多少只?
请一步步推理,写出你的思考过程。关键就是最后一句"请一步步推理,写出你的思考过程"——这就是 Zero-shot CoT 的"魔法咒语"。
模型输出示例:
让我一步步思考:
1. 设鸡的数量为 x,兔子的数量为 y
2. 根据题意:x + y = 35(总数)
3. 鸡有2只脚,兔子有4只脚:2x + 4y = 94(总脚数)
4. 从方程1得:y = 35 - x
5. 代入方程2:2x + 4(35 - x) = 94
6. 化简:2x + 140 - 4x = 94
7. 继续:-2x + 140 = 94
8. 移项:-2x = 94 - 140 = -46
9. 解得:x = 23
所以,鸡有23只,兔子有12只。Few-shot CoT:结合示例和推理步骤,效果更好——既给了模型"怎么推理"的模板,又给了"推理到什么深度"的参照:
问题: 小明有5个苹果,给了小红2个,又买了3个,现在有几个?
推理: 开始有5个苹果 -> 给出2个后剩5-2=3个 -> 又买3个变成3+3=6个
答案: 6个
问题: 一个班级有40名学生,男生比女生多4人,问男生有多少人?
推理:Python 代码示例(逐行注释):
from openai import OpenAI
client = OpenAI()
# Zero-shot CoT 的核心:在 Prompt 末尾加上"请一步步推理"的引导语
prompt = """请逐步思考以下问题:
一个农场有鸡和兔子共35只,脚的总数为94只。请问鸡和兔子各有多少只?
请一步步推理,写出你的思考过程。"""
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
temperature=0.0, # 推理任务需要确定性,温度设为 0
max_tokens=500, # CoT 会生成较多中间步骤,需要给足 token 预算
)
print(response.choices[0].message.content)注意 max_tokens 设为 500——CoT 会产生大量中间推理文本,如果设太小(如 50)会导致推理被截断、答案丢失。
常见误区一:CoT 万能。实际上对于简单任务(如翻译、摘要),CoT 不仅没有帮助,还会让输出变得冗长啰嗦。CoT 的价值在于多步推理场景:数学题、逻辑题、复杂因果分析。
常见误区二:小模型也该用 CoT。研究表明,CoT 对参数量 < 60B 的模型几乎没有帮助,甚至可能让输出更差。如果你用的是小模型,优先考虑 Few-shot 而非 CoT。
3.1.5 角色设定(Role Prompting)
概念:通过 System Prompt 或 User Prompt 为模型设定特定角色,约束其行为、语气和专业领域。
生活类比:同一批实习生,一个被分配到法务部、一个被分配到市场部——他们的知识库是同一所学校教的,但"岗位设定"决定了他们回答问题时从什么角度切入、用什么语气说话、优先考虑什么。Role Prompting 就是在给模型"定岗位"。
角色设定的维度:
┌──────────────────────────────────────────────┐
│ 角色设定框架 │
├──────────────┬───────────────────────────────┤
│ 身份 (Who) │ 你是一位资深Python后端工程师 │
│ 领域 (Domain)│ 专注于高性能Web应用开发 │
│ 风格 (Style) │ 回答简洁、专业、以代码示例为主 │
│ 约束 (Rules) │ 不推荐已废弃的API,优先使用类型注解 │
│ 输出 (Output)│ 代码块使用```python标记 │
└──────────────┴───────────────────────────────┘为什么需要角色设定:
- 聚焦知识:模型的知识是"全科"的,但你的任务往往只需要"专科"。设定角色让模型从特定专业角度调用知识,避免答非所问。
- 统一语气:在多轮对话或批量任务中,角色设定能保证每次输出的语气和风格一致——这在构建客服机器人时尤为重要。
- 设定边界:通过约束规则,可以明确告诉模型"不要做什么"——比如"不要提供法律建议"。
完整示例:
你是一位拥有15年经验的Python代码审查专家。你的任务是审查用户提交的代码,
重点关注以下方面:
1. 代码安全漏洞(SQL注入、XSS等)
2. 性能瓶颈
3. 不符合PEP 8规范的代码风格
4. 潜在的逻辑错误
请以以下格式输出审查结果:
- 🔴 严重问题:(必须修复)
- 🟡 改进建议:(建议优化)
- 🟢 优点:(做得好的地方)
现在请审查以下代码:
[用户代码]Python 代码示例(逐行注释):
from openai import OpenAI
client = OpenAI()
# System Prompt 用于设定角色——这是它与 User Prompt 的关键区别
system_prompt = """你是一位拥有15年经验的Python代码审查专家。
你的任务是审查用户提交的代码,重点关注:
1. 安全漏洞(SQL注入、XSS等)
2. 性能瓶颈
3. PEP 8 规范
4. 潜在逻辑错误
输出格式:
- 🔴 严重问题:(必须修复)
- 🟡 改进建议:(建议优化)
- 🟢 优点:(做得好的地方)"""
# User Prompt 放实际的代码或任务输入
user_code = """
def get_user(name):
query = "SELECT * FROM users WHERE name='" + name + "'"
return db.execute(query)
"""
response = client.chat.completions.create(
model="gpt-4o",
messages=[
# 注意 role 的区别:system 设定角色,user 放任务
{"role": "system", "content": system_prompt}, # system 消息优先级最高
{"role": "user", "content": f"请审查以下代码:\n{user_code}"}
],
temperature=0.0, # 代码审查需要稳定输出,不要"创造性"
)
print(response.choices[0].message.content)
# 输出会指出 SQL 注入漏洞(字符串拼接 query),建议使用参数化查询上面这段代码本身就有 SQL 注入漏洞(字符串拼接 SQL),模型应该能识别出来。注意 messages 列表中有两条消息:system 设定角色,user 放任务——这是角色设定的标准写法。
常见误区一:角色设定越长越好。实际上,过于冗长的 System Prompt 会挤占上下文窗口、增加 token 成本,还可能让模型"注意力分散"——反而抓不住重点。好的角色设定应该精炼到 3-5 句话,覆盖身份、领域、风格、约束四个维度即可。
常见误区二:把所有东西都塞进 System Prompt。有些开发者把业务逻辑、示例、输出模板全塞进 System Prompt——这会让模型在多轮对话中"忘记"后面的内容。记住:System Prompt 放"角色定义和通用规则",User Prompt 放"具体任务和输入数据"。
3.1.6 输出格式控制
概念:在 Prompt 中明确指定输出格式,使模型返回结构化、可解析的数据。
生活类比:你对实习生说"帮我总结一下"——他可能给你写了一篇散文;但如果你说"帮我总结一下,用 Excel 表格,列名是'问题/影响/建议/优先级'"——他就只能按表格填。输出格式控制就是给模型"发一张表格模板"。
为什么需要格式控制:当 LLM 的输出要被程序读取、入库、传给下一个 API 时,格式不统一就是灾难——JSON 多一个逗号、Markdown 少一个竖线,都会让解析器崩溃。明确指定格式能把"自然语言输出"变成"可编程接口"。
常用格式控制技巧:
技巧 1:JSON 输出
请分析以下文本的情感,以JSON格式输出,包含以下字段:
- sentiment: "正面"/"负面"/"中性"
- confidence: 0-1之间的置信度
- keywords: 决定情感的关键词列表
文本: "这个产品的质量超出预期,客服回复也很快,非常满意!"
请严格按JSON格式输出,不要添加额外说明。最后一句"请严格按 JSON 格式输出,不要添加额外说明"非常关键——模型天生爱"说话",不强调的话它可能在 JSON 前后加一堆解释文字,导致 json.loads() 解析失败。
Python 代码示例(逐行注释):
import json
from openai import OpenAI
client = OpenAI()
prompt = """请分析以下文本的情感,以JSON格式输出,包含以下字段:
- sentiment: "正面"/"负面"/"中性"
- confidence: 0-1之间的置信度
- keywords: 决定情感的关键词列表
文本: "这个产品的质量超出预期,客服回复也很快,非常满意!"
请严格按JSON格式输出,不要添加额外说明。"""
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
temperature=0.0,
# 关键技巧:使用 response_format 强制输出合法 JSON
response_format={"type": "json_object"},
)
# 因为指定了 json_object,这里可以安全地解析
result = json.loads(response.choices[0].message.content)
print(result["sentiment"]) # 输出: 正面
print(result["confidence"]) # 输出: 0.95
print(result["keywords"]) # 输出: ['质量超出预期', '回复快', '非常满意']response_format={"type": "json_object"} 是 OpenAI 提供的"结构化输出"功能——它从底层保证模型输出一定是合法 JSON,不会有多余文字。如果你的模型 API 不支持这个参数,就要靠 Prompt 中的"请严格按 JSON 格式输出"来约束。
技巧 2:Markdown 表格
请对比Python、Go、Rust三门语言在以下维度的表现,以Markdown表格输出:
- 学习曲线
- 执行性能
- 并发支持
- 生态系统
- 适用场景Markdown 表格适合人类阅读的场景,如文档、报告、邮件。模型对 Markdown 语法掌握得很好,基本不会出错。
技巧 3:XML 标签约束
请分析以下代码,用XML格式输出分析结果:
<analysis>
<language>编程语言</language>
<purpose>代码用途</purpose>
<bugs>
<bug severity="high">严重bug描述</bug>
<bug severity="medium">中等bug描述</bug>
</bugs>
<suggestions>改进建议</suggestions>
</analysis>XML 标签的优势是结构嵌套清晰——适合需要层级关系的输出(如"bug 列表下有多个 bug,每个 bug 有 severity 属性")。Anthropic 的 Claude 模型对 XML 标签的遵循度尤其高。
常见误区一:只说"输出 JSON"但不给字段说明。模型不知道你要哪些字段,就会自己"编"字段名——结果每次输出的 JSON 结构都不一样。正确做法是列出每个字段名、类型和取值范围,像写 API 文档一样。
常见误区二:不处理模型"加料"的情况。即使你说了"只输出 JSON",模型有时还是会加"好的,以下是分析结果:"这样的前缀。在代码中应该做容错处理:用正则提取 JSON 部分,或使用支持
response_format的模型 API。
常见误区三:用自然语言描述格式不如直接给一个"空模板"。最高效的方式是直接给模型一个 JSON 骨架,让模型"填空"——这比用文字描述字段更精确、更省 token。