一架梯子,一头程序猿,仰望星空!
面向开发者的 Prompt Engineering(吴恩达) > 内容正文

Prompt 设计原则


本章的主要内容为编写 Prompt 的原则,在本章中,将给出两个编写 Prompt 的原则与一些相关的策略,你将学习如何基于这两个原则,编写有效的 Prompt,从而有效的指挥 LLM干活。

提示:虽然教程使用的是python语言描述如何调用OpenAI接口,如果你不熟悉Python没关系,学习prompt即可,OpenAI API支持restful api接口,其他大模型的prompt的写法也类似。

1.环境配置

本教程使用 OpenAI 所开放的 ChatGPT API,因此你需要首先拥有一个 ChatGPT 的 API_KEY(也可以直接访问官方网址在线测试),然后需要安装 openai 的第三方库

首先需要安装所需第三方库:
openai:

pip install openai

dotenv:

pip install -U python-dotenv

将自己的 API-KEY 导入系统环境变量

!export OPENAI_API_KEY='api-key'

加载配置的代码

import openai
import os
from dotenv import load_dotenv, find_dotenv
# 导入第三方库

_ = load_dotenv(find_dotenv())
# 读取系统中的环境变量

openai.api_key  = os.getenv('OPENAI_API_KEY')
# 设置 API_KEY

我们将在后续课程中深入探究 OpenAI 提供的 ChatCompletion API 的使用方法,在此处,我们先将它封装成一个函数,你无需知道其内部机理,仅需知道调用该函数输入 Prompt 其将会给出对应的 Completion 即可。

# 一个封装 OpenAI 接口的函数,参数为 Prompt,返回对应结果
def get_completion(prompt, model="gpt-3.5-turbo"):
    '''
    prompt: 对应的提示
    model: 调用的模型,默认为 gpt-3.5-turbo(ChatGPT),有内测资格的用户可以选择 gpt-4
    '''
    messages = [{"role": "user", "content": prompt}]
    response = openai.ChatCompletion.create(
        model=model,
        messages=messages,
        temperature=0, # 模型输出的温度系数,控制输出的随机程度
    )
    # 调用 OpenAI 的 ChatCompletion 接口
    return response.choices[0].message["content"]

2.两个基本原则

2.1.原则一:编写清晰、具体的指令

你应该通过提供尽可能清晰和具体的指令来表达您希望模型执行的操作。这将引导模型给出正确的输出,并减少你得到无关或不正确响应的可能。编写清晰的指令不意味着简短的指令,因为在许多情况下,更长的提示实际上更清晰且提供了更多上下文,这实际上可能导致更详细更相关的输出。

2.1.1.策略一

使用分隔符清晰地表示输入的不同部分,分隔符可以是:```,””,<>,<tag>,<\tag>等

你可以使用任何明显的标点符号将特定的文本部分与提示的其余部分分开。这可以是任何可以使模型明确知道这是一个单独部分的标记。使用分隔符是一种可以避免提示注入的有用技术。提示注入是指如果用户将某些输入添加到提示中,则可能会向模型提供与您想要执行的操作相冲突的指令,从而使其遵循冲突的指令而不是执行您想要的操作。即,输入里面可能包含其他指令,会覆盖掉你的指令。对此,使用分隔符是一个不错的策略。

以下是一个例子,我们给出一段话并要求 GPT 进行总结,在该示例中我们使用 `` 来作为分隔符

# 需要总结的文本
text = f"""
你应该提供尽可能清晰、具体的指示,以表达你希望模型执行的任务。\
这将引导模型朝向所需的输出,并降低收到无关或不正确响应的可能性。\
不要将写清晰的提示与写简短的提示混淆。\
在许多情况下,更长的提示可以为模型提供更多的清晰度和上下文信息,从而导致更详细和相关的输出。
"""
# 需要提交给AI的prompt,这里嵌入了前面的text变量
prompt = f"""
把用2个反引号括起来的文本总结成一句话。
``{text}``
"""
# 指令内容,使用 `` 来分隔指令和待总结的内容,调用AI获取结果
response = get_completion(prompt)
print(response)

提供清晰具体的指示,避免无关或不正确响应,不要混淆写清晰和写简短,更长的提示可以提供更多清晰度和上下文信息,导致更详细和相关的输出。

2.1.2.策略二

要求一个结构化的输出,可以是 Json、HTML 等格式

第二个策略是要求生成一个结构化的输出,这可以使模型的输出更容易被我们解析,例如,你可以在 Python 中将其读入字典或列表中。。

在以下示例中,我们要求 GPT 生成三本书的标题、作者和类别,并要求 GPT 以 Json 的格式返回给我们,为便于解析,我们指定了 Json 的键。

prompt = f"""
请生成包括书名、作者和类别的三本虚构书籍清单,\
并以 JSON 格式提供,其中包含以下键:book_id、title、author、genre。
"""
response = get_completion(prompt)
print(response)

返回结果

[
  {
    "book_id": 1,
    "title": "The Lost City of Zorath",
    "author": "Aria Blackwood",
    "genre": "Fantasy"
  },
  {
    "book_id": 2,
    "title": "The Last Survivors",
    "author": "Ethan Stone",
    "genre": "Science Fiction"
  },
  {
    "book_id": 3,
    "title": "The Secret Life of Bees",
    "author": "Lila Rose",
    "genre": "Romance"
  }
]

2.1.3.策略三

要求模型检查是否满足条件

如果任务做出的假设不一定满足,我们可以告诉模型先检查这些假设,如果不满足,指示并停止执行。你还可以考虑潜在的边缘情况以及模型应该如何处理它们,以避免意外的错误或结果。

在如下示例中,我们将分别给模型两段文本,分别是制作茶的步骤以及一段没有明确步骤的文本。我们将要求模型判断其是否包含一系列指令,如果包含则按照给定格式重新编写指令,不包含则回答未提供步骤。

# 有步骤的文本
text_1 = f"""
泡一杯茶很容易。首先,需要把水烧开。\
在等待期间,拿一个杯子并把茶包放进去。\
一旦水足够热,就把它倒在茶包上。\
等待一会儿,让茶叶浸泡。几分钟后,取出茶包。\
如果你愿意,可以加一些糖或牛奶调味。\
就这样,你可以享受一杯美味的茶了。
"""
# 发给ai的prompt,嵌入前面text_1变量内容
prompt = f"""
您将获得由三个引号括起来的文本。\
如果它包含一系列的指令,则需要按照以下格式重新编写这些指令:

第一步 - ...
第二步 - …
…
第N步 - …

如果文本中不包含一系列的指令,则直接写“未提供步骤”。"
\"\"\"{text_1}\"\"\"
"""
response = get_completion(prompt)
print("Text 1 的总结:")
print(response)

返回结果

Text 1 的总结:
第一步 - 把水烧开。
第二步 - 拿一个杯子并把茶包放进去。
第三步 - 把烧开的水倒在茶包上。
第四步 - 等待几分钟,让茶叶浸泡。
第五步 - 取出茶包。
第六步 - 如果你愿意,可以加一些糖或牛奶调味。
第七步 - 就这样,你可以享受一杯美味的茶了。

无步骤的文本

text_2 = f"""
今天阳光明媚,鸟儿在歌唱。\
这是一个去公园散步的美好日子。\
鲜花盛开,树枝在微风中轻轻摇曳。\
人们外出享受着这美好的天气,有些人在野餐,有些人在玩游戏或者在草地上放松。\
这是一个完美的日子,可以在户外度过并欣赏大自然的美景。
"""
prompt = f"""
您将获得由三个引号括起来的文本。\
如果它包含一系列的指令,则需要按照以下格式重新编写这些指令:

第一步 - ...
第二步 - …
…
第N步 - …

如果文本中不包含一系列的指令,则直接写“未提供步骤”。"
\"\"\"{text_2}\"\"\"
"""
response = get_completion(prompt)
print("Text 2 的总结:")
print(response)

返回结果

Text 2 的总结:
未提供步骤。

2.1.4.策略四

提供少量示例

即在要求模型执行实际任务之前,提供给它少量成功执行任务的示例。

例如,在以下的示例中,我们告诉模型其任务是以一致的风格回答问题,并先给它一个孩子和一个祖父之间的对话的例子。孩子说,“教我耐心”,祖父用这些隐喻回答。因此,由于我们已经告诉模型要以一致的语气回答,现在我们说“教我韧性”,由于模型已经有了这个少样本示例,它将以类似的语气回答下一个任务。

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

<孩子>: 教我耐心。

<祖父母>: 挖出最深峡谷的河流源于一处不起眼的泉眼;最宏伟的交响乐从单一的音符开始;最复杂的挂毯以一根孤独的线开始编织。

<孩子>: 教我韧性。
"""
response = get_completion(prompt)
print(response)

返回

<祖父母>: 韧性就像是一棵树,它需要经历风吹雨打、寒冬酷暑,才能成长得更加坚强。在生活中,我们也需要经历各种挫折和困难,才能锻炼出韧性。记住,不要轻易放弃,坚持下去,你会发现自己变得更加坚强。

2.2.原则二:给模型时间去思考

如果模型匆忙地得出了错误的结论,您应该尝试重新构思查询,请求模型在提供最终答案之前进行一系列相关的推理。换句话说,如果您给模型一个在短时间或用少量文字无法完成的任务,它可能会猜测错误。这种情况对人来说也是一样的。如果您让某人在没有时间计算出答案的情况下完成复杂的数学问题,他们也可能会犯错误。因此,在这些情况下,您可以指示模型花更多时间思考问题,这意味着它在任务上花费了更多的计算资源。

2.2.1.策略一

指定完成任务所需的步骤

接下来我们将通过给定一个复杂任务,给出完成该任务的一系列步骤,来展示这一策略的效果

首先我们描述了杰克和吉尔的故事,并给出一个指令。该指令是执行以下操作。首先,用一句话概括三个双引号限定的文本。第二,将摘要翻译成法语。第三,在法语摘要中列出每个名称。第四,输出包含以下键的 JSON 对象:法语摘要和名称数。然后我们要用换行符分隔答案。

text = f"""
在一个迷人的村庄里,兄妹杰克和吉尔出发去一个山顶井里打水。\
他们一边唱着欢乐的歌,一边往上爬,\
然而不幸降临——杰克绊了一块石头,从山上滚了下来,吉尔紧随其后。\
虽然略有些摔伤,但他们还是回到了温馨的家中。\
尽管出了这样的意外,他们的冒险精神依然没有减弱,继续充满愉悦地探索。
"""
# example 1
prompt_1 = f"""
执行以下操作:
1-用一句话概括下面用三个双引号括起来的文本。
2-将摘要翻译成法语。
3-在法语摘要中列出每个人名。
4-输出一个 JSON 对象,其中包含以下键:French_summary,num_names。

请用换行符分隔您的答案。

Text:
\"\"\"{text}\"\"\"
"""
response = get_completion(prompt_1)
print("prompt 1:")
print(response)

返回结果

prompt 1:
1-兄妹在山顶井里打水时发生意外,但仍然保持冒险精神。
2-Dans un charmant village, les frère et sœur Jack et Jill partent chercher de l'eau dans un puits au sommet de la montagne. Malheureusement, Jack trébuche sur une pierre et tombe de la montagne, suivi de près par Jill. Bien qu'ils soient légèrement blessés, ils retournent chez eux chaleureusement. Malgré cet accident, leur esprit d'aventure ne diminue pas et ils continuent à explorer joyeusement.
3-Jack, Jill
4-{
   "French_summary": "Dans un charmant village, les frère et sœur Jack et Jill partent chercher de l'eau dans un puits au sommet de la montagne. Malheureusement, Jack trébuche sur une pierre et tombe de la montagne, suivi de près par Jill. Bien qu'ils soient légèrement blessés, ils retournent chez eux chaleureusement. Malgré cet accident, leur esprit d'aventure ne diminue pas et ils continuent à explorer joyeusement.",
   "num_names": 2
}

上述输出仍然存在一定问题,例如,键“姓名”会被替换为法语,因此,我们给出一个更好的 Prompt,该 Prompt 指定了输出的格式

prompt_2 = f"""
1-用一句话概括下面用<>括起来的文本。
2-将摘要翻译成英语。
3-在英语摘要中列出每个名称。
4-输出一个 JSON 对象,其中包含以下键:English_summary,num_names。

请使用以下格式:
文本:<要总结的文本>
摘要:<摘要>
翻译:<摘要的翻译>
名称:<英语摘要中的名称列表>
输出 JSON:<带有 English_summary 和 num_names 的 JSON>

Text: <{text}>
"""
response = get_completion(prompt_2)
print("\nprompt 2:")
print(response)

返回结果

prompt 2:
摘要:兄妹杰克和吉尔在迷人的村庄里冒险,不幸摔伤后回到家中,但仍然充满冒险精神。
翻译:In a charming village, siblings Jack and Jill set out to fetch water from a mountaintop well. While climbing and singing, Jack trips on a stone and tumbles down the mountain, with Jill following closely behind. Despite some bruises, they make it back home safely. Their adventurous spirit remains undiminished as they continue to explore with joy.
名称:Jack,Jill
输出 JSON:{"English_summary": "In a charming village, siblings Jack and Jill set out to fetch water from a mountaintop well. While climbing and singing, Jack trips on a stone and tumbles down the mountain, with Jill following closely behind. Despite some bruises, they make it back home safely. Their adventurous spirit remains undiminished as they continue to explore with joy.", "num_names": 2}

2.2.2.策略二

指导模型在下结论之前找出一个自己的解法

有时候,在明确指导模型在做决策之前要思考解决方案时,我们会得到更好的结果。

接下来我们会给出一个问题和一个学生的解答,要求模型判断解答是否正确

prompt = f"""
判断学生的解决方案是否正确。

问题:
我正在建造一个太阳能发电站,需要帮助计算财务。

    土地费用为 100美元/平方英尺
    我可以以 250美元/平方英尺的价格购买太阳能电池板
    我已经谈判好了维护合同,每年需要支付固定的10万美元,并额外支付每平方英尺10美元
    作为平方英尺数的函数,首年运营的总费用是多少。

学生的解决方案:
设x为发电站的大小,单位为平方英尺。
费用:

    土地费用:100x
    太阳能电池板费用:250x
    维护费用:100,000美元+100x
    总费用:100x+250x+100,000美元+100x=450x+100,000美元
"""
response = get_completion(prompt)
print(response)

返回

学生的解决方案是正确的。

但是注意,学生的解决方案实际上是错误的。

我们可以通过指导模型先自行找出一个解法来解决这个问题。

在接下来这个 Prompt 中,我们要求模型先自行解决这个问题,再根据自己的解法与学生的解法进行对比,从而判断学生的解法是否正确。同时,我们给定了输出的格式要求。通过明确步骤,让模型有更多时间思考,有时可以获得更准确的结果。在这个例子中,学生的答案是错误的,但如果我们没有先让模型自己计算,那么可能会被误导以为学生是正确的。

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)

返回结果

正确的解决方案和步骤:
    1. 计算土地费用:100美元/平方英尺 * x平方英尺 = 100x美元
    2. 计算太阳能电池板费用:250美元/平方英尺 * x平方英尺 = 250x美元
    3. 计算维护费用:10万美元 + 10美元/平方英尺 * x平方英尺 = 10万美元 + 10x美元
    4. 计算总费用:100x美元 + 250x美元 + 10万美元 + 10x美元 = 360x + 10万美元

学生的解决方案和实际解决方案是否相同:否

学生的成绩:不正确

3.局限性

虚假知识:模型偶尔会生成一些看似真实实则编造的知识

如果模型在训练过程中接触了大量的知识,它并没有完全记住所见的信息,因此它并不很清楚自己知识的边界。这意味着它可能会尝试回答有关晦涩主题的问题,并编造听起来合理但实际上并不正确的答案。我们称这些编造的想法为幻觉。


关联主题