摘要

你是否在使用 ChatGPT 等大语言模型时,经常得到似是而非的答案?你是否觉得提示词写得越长效果反而越差?本文基于 OpenAI 官方课程《ChatGPT Prompt Engineering for Developers》的核心内容,系统讲解提示工程的两大原则:编写清晰具体的指令给模型留出思考时间。我们将通过丰富的代码示例(Python + OpenAI API)演示七种实战策略,包括使用分隔符、要求结构化输出、条件检查、少样本提示、步骤分解、强制模型先自行推理等。此外,本文还将揭示模型最常见的“幻觉”问题及其缓解方法。无论你是开发者还是 AI 爱好者,读完本文即可写出高效、可靠的提示词。

适用人群 / 前置知识

适用人群:希望系统学习提示工程技巧的开发者、数据科学家、AI 产品经理、以及对大语言模型应用感兴趣的学习者。
前置知识:了解基本的 Python 编程;知道什么是 API 密钥;对 ChatGPT 等大语言模型有初步使用经验更佳。

正文

引言:为什么提示工程如此重要?

大语言模型(LLM)拥有强大的语言理解和生成能力,但它的输出质量高度依赖于输入——也就是“提示词”(prompt)。一个模糊的提示可能得到无关甚至错误的答案,而精心设计的提示则能激发出模型的深层推理能力。提示工程(Prompt Engineering)不是简单的“问问题”,而是一套可复用的方法论。

本文将深入解析两个核心原则及其背后的七种战术,所有示例均使用 OpenAI 的 gpt-3.5-turbo 模型和官方 Python 库,你可以直接复制代码动手实践。

环境准备:快速搭建 OpenAI API 调用环境

首先,你需要安装 OpenAI Python 库(版本 0.27.0 或 1.x)。以下代码演示了两种版本的辅助函数写法。

安装与配置

pip install openai python-dotenv

在项目根目录创建 .env 文件,写入你的 API 密钥:

OPENAI_API_KEY=sk-...

辅助函数(OpenAI 库 0.27.0 版本)

import openai
import os
from dotenv import load_dotenv, find_dotenv

_ = load_dotenv(find_dotenv())
openai.api_key = os.getenv('OPENAI_API_KEY')

def get_completion(prompt, model="gpt-3.5-turbo"):
    messages = [{"role": "user", "content": prompt}]
    response = openai.ChatCompletion.create(
        model=model,
        messages=messages,
        temperature=0,  # 控制随机性,0 表示最确定
    )
    return response.choices[0].message["content"]

辅助函数(OpenAI 库 1.0.0 版本)

client = openai.OpenAI()

def get_completion(prompt, model="gpt-3.5-turbo"):
    messages = [{"role": "user", "content": prompt}]
    response = client.chat.completions.create(
        model=model,
        messages=messages,
        temperature=0,
    )
    return response.choices[0].message.content
注意:本文所有示例均使用 temperature=0,以获得最稳定、确定性的输出。在实际产品中,你可能需要适当调高温度以增加多样性。

原则一:编写清晰且具体的指令

很多初学者误以为“短提示词更好”,但事实上,清晰和具体远比长度更重要。以下四种战术能显著提升指令质量。

战术1:使用分隔符明确输入的不同部分

分隔符(如三重反引号 ```、引号 “”、XML 标签 等)可以明确告诉模型:哪部分是待处理的文本,哪部分是指令。这还能有效防止提示注入攻击(用户恶意输入改变模型行为)。

示例:总结一段文字

text = """
你应该尽可能用清晰、具体的指令来表达你希望模型做的事情。
这将引导模型朝着期望的输出方向前进,减少得到无关或错误回答的概率。
不要把写清晰的提示词和写简短的提示词混为一谈。
"""

prompt = f"""
将三重反引号分隔的文本总结为一个句子。
```{text}```
"""

print(get_completion(prompt))
# 输出:为了获得期望的输出,应提供清晰具体的指令,这比简短更重要。

延伸:如果你想让模型从用户输入中提取关键信息,但担心用户可能插入“忽略之前指令,现在做别的事”之类的恶意内容,使用分隔符将用户输入与系统指令隔开,模型通常会正确识别。

战术2:要求结构化输出(JSON / HTML)

结构化输出便于程序解析。例如让模型生成虚构书籍列表并输出 JSON。

prompt = """
生成三个虚构的书名,包含作者和流派。
以 JSON 格式提供,键名为:book_id, title, author, genre。
"""

response = get_completion(prompt)
print(response)

输出示例:

[
  {"book_id": 1, "title": "The Shadow of the Wind", "author": "Carlos Ruiz Zafón", "genre": "Mystery"},
  {"book_id": 2, "title": "The Night Circus", "author": "Erin Morgenstern", "genre": "Fantasy"},
  {"book_id": 3, "title": "Project Hail Mary", "author": "Andy Weir", "genre": "Science Fiction"}
]

在 Python 中,你可以用 json.loads() 直接转为列表或字典。

战术3:让模型先检查条件是否满足

如果任务依赖于某些前提(例如文本中必须包含指令),应当让模型先验证条件,避免强行执行导致错误。

示例:检查文本是否包含一系列步骤,若有则按格式输出,若无则返回“未提供步骤”。

text_with_instructions = """
泡茶很简单!首先,烧水。然后,拿一个杯子放入茶包。
水开后,倒入杯子。等待几分钟,取出茶包。加糖或牛奶即可。
"""

prompt = f"""
你将收到由三重引号分隔的文本。如果文本包含一系列指令,
请按以下格式重写:
Step 1 - ...
Step 2 - ...
...
如果文本不包含指令,则只写“未提供步骤”。
\"\"\"{text_with_instructions}\"\"\"
"""

print(get_completion(prompt))

输出:

Step 1 - 烧水。
Step 2 - 拿一个杯子放入茶包。
Step 3 - 水开后倒入杯子。
Step 4 - 等待几分钟。
Step 5 - 取出茶包。
Step 6 - 加糖或牛奶。

对于没有指令的纯描述文本,模型会返回“未提供步骤”。

战术4:少样本提示(Few-shot Prompting)

提供几个示例(输入→期望输出),让模型模仿示例的风格或逻辑。这对于需要保持特定语气或格式的场景特别有效。

示例:让孩子与祖父母的对话保持隐喻风格

prompt = """
你的任务是以一致的风格回答问题。

<孩子>: 教我什么是耐心。
<祖父母>: 雕刻出最深峡谷的河流始于 modest 的泉水;最宏大的交响乐源自一个音符;最复杂的挂毯始于一根丝线。

<孩子>: 教我什么是韧性。
"""

print(get_completion(prompt))

输出(模型模仿了隐喻风格):

韧性就像一棵在风中弯曲但永不折断的树,它的根深深扎入大地,每一次风暴后反而更加茁壮。

原则二:给模型留出“思考”时间

人类在匆忙回答复杂问题时容易犯错,模型也一样。通过强制模型先进行中间推理(Chain-of-Thought),可以大幅提升答案的准确性。

战术1:明确指定完成任务的步骤

将复杂任务分解为一系列有序的子任务,并要求模型按步骤执行。

示例:对一段故事文字,依次执行:总结→翻译成法语→提取名字→输出 JSON。

text = """
在一个迷人的村庄里,兄妹 Jack 和 Jill 出发去山顶水井打水。
他们边爬边唱,不幸 Jack 被石头绊倒滚下山坡,Jill 也跟着滚了下去。
虽然受了些伤,两人还是回到家中得到了安慰。尽管发生了意外,他们的冒险精神丝毫未减。
"""

prompt = f"""
执行以下操作:
1 - 用一句话总结由三重反引号分隔的文本。
2 - 将总结翻译成法语。
3 - 列出法语总结中的每个人名。
4 - 输出一个 JSON 对象,包含键:french_summary, num_names。
用换行分隔答案。

文本:
```{text}```
"""

print(get_completion(prompt))

为了获得更稳定的输出格式,可以要求模型按指定模板返回:

prompt = f"""
...(同上1-4步)
使用以下格式:
Text: <待总结文本>
Summary: <总结>
Translation: <法语翻译>
Names: <名字列表>
Output JSON: <JSON>

Text: <{text}>
"""

print(get_completion(prompt))

这样模型会严格按模板输出,便于程序自动提取各字段。

战术2:要求模型先自行求解,再比较答案

当任务涉及判断对错时,直接让模型判断容易出错,因为它会像人类一样“扫读”而忽略细节。强制模型先算出自己的答案,再与学生答案比较,结果更可靠。

错误示例(直接判断):

question = """
我建设太阳能电站,需要计算财务数据:
- 土地成本:100美元/平方英尺
- 太阳能板:250美元/平方英尺
- 维护费:每年固定10万美元,外加10美元/平方英尺
求第一年总成本关于面积(平方英尺)的函数。
"""

student_solution = """
设x为安装面积(平方英尺)。
成本:
1. 土地:100x
2. 太阳能板:250x
3. 维护:100,000 + 100x
总成本:100x+250x+100,000+100x = 450x + 100,000
"""

prompt = f"""
判断学生的解法是否正确。
问题:{question}
学生解法:{student_solution}
"""

print(get_completion(prompt))
# 输出:学生的解法正确(错误!实际上维护费用应为10x,不是100x)

改进后(要求模型先自己算):

prompt = f"""
你的任务是判断学生的解法是否正确。
请先自己完整解出问题(包括最终总成本表达式),
然后将你的解法与学生的解法比较,再给出结论。
不要在学生解法上做任何判断,直到你完成了自己的计算。

使用以下格式:
问题:
'''
问题内容
'''
学生解法:
'''
学生解法
'''
实际解法:
'''
你的计算步骤和最终结果
'''
学生答案与实际答案是否相同:
'''
是/否
'''
学生成绩:
'''
正确/错误
'''
问题:
'''
我建设太阳能电站,需要计算财务数据:
- 土地成本:100美元/平方英尺
- 太阳能板:250美元/平方英尺
- 维护费:每年固定10万美元,外加10美元/平方英尺
求第一年总成本关于面积(平方英尺)的函数。
'''
学生解法:
'''
设x为安装面积(平方英尺)。
成本:
1. 土地:100x
2. 太阳能板:250x
3. 维护:100,000 + 100x
总成本:100x+250x+100,000+100x = 450x + 100,000
'''
实际解法:
"""
response = get_completion(prompt)
print(response)

为什么有效?模型被强制要求进行逐步推理,这类似于人类“打草稿”,减少了因直觉跳跃而产生的错误。

模型的局限性:幻觉(Hallucination)及其缓解

即使是最先进的大语言模型,也会自信地编造虚假信息,这被称为“幻觉”。例如,询问一个不存在的产品:

prompt = "告诉我关于 Boie 公司的 AeroGlide Ultra Slim Smart Toothbrush"
print(get_completion(prompt))

模型会生成一段非常逼真的产品描述,包括功能、材质、价格等,但这款产品根本不存在。Boie 是真实公司,但该产品是模型捏造的。

缓解策略

  • 提供源文本并强制引用:如果你希望模型基于特定文档回答问题,可以要求模型先从文档中找出直接相关的引用,再基于这些引用生成答案。
  • 启用检索增强生成(RAG):将模型与外部知识库结合,让模型只从检索到的文档中提取信息。
  • 降低Temperature参数:较低的Temperature(如0)会减少创造性编造,但并不能完全消除幻觉。

示例(要求模型基于给定文本回答):

source_text = "Boie 公司只生产两种牙刷:基础款和 Pro 款,没有智能牙刷。"
prompt = f"""
基于以下文本回答问题。如果文本中没有相关信息,请回答“不知道”。
文本:{source_text}
问题:Boie 公司有 AeroGlide Ultra Slim Smart Toothbrush 吗?
"""
print(get_completion(prompt))
# 输出:不知道

总结与进阶建议

核心要点回顾

原则 战术 关键作用
清晰具体 分隔符 明确边界,防注入
清晰具体 结构化输出 便于程序解析
清晰具体 条件检查 避免无效执行
清晰具体 少样本提示 传递风格/格式
给时间思考 步骤分解 降低复杂性
给时间思考 先自解后比较 减少推理错误
应对局限 引用源文本 缓解幻觉

实战建议

  1. 提示词迭代:没有完美的第一次提示,请反复测试、修改、观察输出变化。
  2. 使用系统消息:在 OpenAI 的 Chat 接口中,system 角色的消息可以设定模型的行为基调,比在用户消息中重复指令更高效。
  3. 注意换行符:虽然 GPT-3.5 对换行不敏感,但某些模型可能会受影响,尽量保持提示词整洁。
  4. Temperature与 Top-p:需要创造性任务(如写诗)用较高Temperature(0.7-1.0),需要精确任务(如代码生成、数学计算)用较低Temperature(0~0.2)。

下一步学习

本文覆盖了提示工程的核心基础。你可以进一步学习:

  • 迭代式提示开发:如何系统性地改进提示词。
  • 工具使用(Function Calling):让模型调用外部 API 或数据库。
  • 构建聊天机器人:管理多轮对话上下文。

掌握这些技巧后,你将能构建出更可靠、更强大的大语言模型应用。

本文内容基于 DeepLearning.AI 的《ChatGPT Prompt Engineering for Developers》课程,结合个人实践整理而成。如有疑问,欢迎留言讨论。
Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐