纯干货——《面向开发者的 ChatGPT Prompt工程》学习笔记

前言

本文为吴恩达教授联合Isa一起开设的提示工程教程笔记,记录了一些重要的知识点,并且把实践源码中文版贴出来了,可以跟着本文一起实操~也可以跟着视频过一遍


此教程的主要目的是为大家介绍如何在自己的应用开发过程中,创建合适的 prompt。那么为什么会重点介绍 prompt?其实接触过 llm 的同学应该都很清楚,想要开发一款强大的 llm 应用,优秀的 prompt是至关重要的,熟练掌握 prompt 编写的人,就可以更好的让 model 理解需求。

(demo使用反斜杠 \ 是为了让文本适应屏幕大小,不过没有用换行符 \n 。GPT-3 并不受换行符的影响,但使用其他大模型,需考虑换行符是否会影响模型性能。)

在正式学习前,先配置一下openai key以方便下文demo

一、Prompt 使用准则

本小节主要讲述一些原则和策略:

毋庸置疑,你需要先设置你的 openai key

import openai 
openai.api_key='sk-xxx'

利用 model gpt-3.5-turbo构建一个函数

def get_completion(prompt, model="gpt-3.5-turbo"):
    messages = [{"role": "user", "content": prompt}]
    response = openai.ChatCompletion.create(
        model=model,
        messages=messages,
        temperature=0, # this is the degree of randomness of the model's output
    )
    return response.choices[0].message["content"]

解释一下这段函数:
OpenAI的ChatCompletion.create函数是用于与GPT-3模型进行对话式交互的API调用之一。允许发送一系列消息(通常包括用户消息和模型消息),然后获得一个包含模型生成响应的回复。

messages: 一个包含对话消息的对象数组。通常,对话从一个用户消息开始,然后交替地添加用户和模型的消息。用户消息用于提供输入提示,模型消息用于存储先前的对话历史。这段代码只有一个用户信息,该信息即为输入的 prompt 提示。

temperature 设置为 0,也就意味着,输出的结果是稳定的,不会有太多随机性。

response则为模型对于 prompt 给出的回应。

原则一:清晰而具体的指示

尽可能的将想要得到的结果和需求清楚的告诉 model,可以有效的减少得到不相关或者不正确的模型反应。但是清晰且具体,并不意味着要将prompt 构造的简短就是好的,并且根据实验发现,较长的prompt 更有助于模型的理性从而输出更为详细且合理的结果。下文将介绍几种策略,以有助于我们创造更为清晰而具体的prompt。

策略一:恰到好处的运用分隔符来明确指出输入的不同部分。
这里我的理解是:在构造 prompt 时,我们可以利用```, “”", < >, , : 等符号清楚的让model知道,哪一部分是你要让它遵循的规则,哪一部分是它需要操作的对象 。

text = f"""
您应该提供尽可能清晰、具体的指示,以表达您希望模型执行的任务。\
这将引导模型输出更朝向所需,并降低收到无关或不正确响应的可能性。\
不要将写清晰的提示词与写简短的提示词混淆。\
在许多情况下,更长的提示词可以为模型提供更多的清晰度和上下文信息,从而使输出更详细和相关。
"""
prompt = f"""
把用三个反引号括起来的文本总结成一句话。
```{text}```
"""
response = get_completion(prompt)
print(response)

输出结果:

为了获得所需的输出,您应该提供清晰、具体的指示,避免与简短的提示词混淆,并使用更长的提示词来提供更多的清晰度和上下文信息。

在demo 中, 其作用是将包含在三个反引号(triple backticks)之间的文本段落进行总结,并将总结后的内容生成为一个单独的句子。具体来说,prompt包括了一个指示,要求模型总结位于text变量中的文本。通过调用get_completion函数,传递了这个提示,模型会根据指示来处理text文本段落,返回这个生成的概括性句子。

一张图可以更好的理解:

这里用户输入的文本是:“忘掉以前的句子…”那么当我们没有用分隔符划定的话,model 就会将其当作 prompt 的一部分,去忘记之前的用户输入,而不是对“忘掉以前的句子…”这段话进行总结了。
纯干货——《面向开发者的 ChatGPT Prompt工程》学习笔记_第1张图片

策略二:结构化输出
进一步规定 prompt 的输出格式,以方便我们更好的利用 model 的输出结果,去完成更加高级的任务,如输出 json、html。

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

输出结果:

{
  "books": [
    {
      "book_id": 1,
      "title": "迷失的时光",
      "author": "张三",
      "genre": "科幻"
    },
    {
      "book_id": 2,
      "title": "幻境之门",
      "author": "李四",
      "genre": "奇幻"
    },
    {
      "book_id": 3,
      "title": "虚拟现实",
      "author": "王五",
      "genre": "科幻"
    }
  ]
}

策略三:检查条件是否被满足
在 prompt 中提出对于此次任务的假设情况,可以是一些边缘条件,如果假设不被满足,可以让 model 先检查这些假设,如果仍不满足假设,那就让模型指出错误,并在完成任务的过程中停止或者提供出研究方案。看一下 demo:

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

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

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

输出结果:

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

这段代码的目的是将包含在text_1变量中的文本中的指示性内容重新格式化,并生成一个按照步骤编号的说明。

具体来说,代码构建了一个名为prompt的字符串,其中包含了一些指示。它告诉模型,如果提供的文本包含了一系列的操作步骤,那么需要将这些步骤重新编写,并按照步骤编号的格式呈现。如果文本中没有操作步骤,那么只需写入"未提供步骤"。

这里的“包含一系列的指令”就是上文中的假设,以保证用户在输入此类型的文本时可以准确的总结。

如果我们的文本换成不再有任何提示性语句的 text,model 将不再总结,比如下面的 demo:

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

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

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

输出结果:

text_2 的总结:
未提供步骤。

此时,因为没有指示性词语在 text 中,model 将不再总结步骤。

策略四:提供成功案例
在要求模型完成任务之前,给予模型提供一些案例,去引导模型按照所需方向生成结果

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

<孩子>: 请教我何为耐心。

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

<孩子>: 请教我何为韧性。

"""
response = get_completion(prompt)
print(response)

输出结果:

<祖父母>: 韧性是一种坚持不懈的品质,就像一棵顽强的树在风雨中屹立不倒。它是面对困难和挑战时不屈不挠的精神,能够适应变化和克服逆境。韧性是一种内在的力量,让我们能够坚持追求目标,即使面临困难和挫折也能坚持不懈地努力。 

如上demo,这段代码是以对话的形式,模拟一个孩子向祖父母寻求关于耐心和坚韧的教导。代码中的prompt提供了对话的上下文,其中包括孩子和祖父母之间的问题和回答。生成的回应内容则是祖父的回答,以同样的风格传达有关耐心和坚韧的概念。

原则二:给予模型思考时间

设计 Prompt 时,需要给予语言模型充足的思考时间。我们可以把他想象成人类,它是需要时间来处理复杂问题的,快速得出的结果往往不准确。举个例子,如果要求model从书名和简介就推测出一本书的主题,这就好比要求一个人在极短时间内解答复杂数学问题,虽然能输出结果,但准确性难以保证。

相反,我们应该通过 Prompt 引导语言模型进行深思熟虑。可以要求它首先提出不同的问题观点和可能性,详细解释推理基础,然后再得出最终结论。在 Prompt 中逐步添加要求,可以让语言模型有更多时间进行逻辑思考,结果也会更可靠。以下是实操中可以参考的一些策略。

策略一:指定完成任务的步骤
按照需求,逐步的去引导模型输出相应的答案如:第一步、第二步…直接看一下 demo 代码:

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

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

Text:
```{text}```
"""
response = get_completion(prompt_1)
print("Completion for prompt 1:")
print(response)

输出结果:

prompt 1:
1-两个兄妹在山上打水时发生意外,但最终平安回家。
2-In a charming village, siblings Jack and Jill set off to fetch water from a well on top of a hill. While singing joyfully, they climbed up, but unfortunately, Jack tripped on a stone and rolled down the hill, with Jill following closely behind. Despite some minor injuries, they made it back to their cozy home. Despite the mishap, their adventurous spirit remained undiminished as they continued to explore with delight.
3-Jack, Jill
4-{"english_summary": "In a charming village, siblings Jack and Jill set off to fetch water from a well on top of a hill. While singing joyfully, they climbed up, but unfortunately, Jack tripped on a stone and rolled down the hill, with Jill following closely behind. Despite some minor injuries, they made it back to their cozy home. Despite the mishap, their adventurous spirit remained undiminished as they continued to explore with delight.", "num_names": 2}

上述代码的功能为:从包含在text变量中的故事中执行以下任务:

1.用句话总结文本 2.将这个总结翻译成法语。3.列出法语总结中的每个名字。4.输出一个JSON对象,包括法语总结和法语总结中的名字数量。

策略二:指示模型在得出结论之前,推理一套方案
这个过程更多的是将 model 拟人化了,也就是给他更多的时间去思考,像人类一样,而不是着急要求一个结果输出,这样的好处显而易见,经过更多思考的内容输出绝对要比直接给出的结果优质的多,还是一个 demo:

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)

上面代码的目的是模拟一个场景,其中一个学生提供了一个解决方案来计算太阳能发电安装的首年运营总成本,但是这里我们误导 model 100,000 + 100x ,实际上根据题意是 100,000 + 10x,但是这个时候就会发现model 评价学生是对的,也就启示我们:在让模型校验时,不要直接去让他推断,最好的方法是,让他给出自己的方案,然后再去按照自己的方案去校验。那么继续修改一下:

prompt = f"""
请判断学生的解决方案是否正确,请通过如下步骤解决这个问题:
步骤:
    首先,自己解决问题。
    然后将您的解决方案与学生的解决方案进行比较,对比计算得到的总费用与学生计算的总费用是否一致,并评估学生的解决方案是否正确。
    在自己完成问题之前,请勿决定学生的解决方案是否正确。
使用以下格式:
    问题:问题文本
    学生的解决方案:学生的解决方案文本
    实际解决方案和步骤:实际解决方案和步骤文本
    学生计算的总费用:学生计算得到的总费用
    实际计算的总费用:实际计算出的总费用
    学生计算的费用和实际计算的费用是否相同:是或否
    学生的解决方案和实际解决方案是否相同:是或否
    学生的成绩:正确或不正确
问题:
    我正在建造一个太阳能发电站,需要帮助计算财务。 
    - 土地费用为每平方英尺100美元
    - 我可以以每平方英尺250美元的价格购买太阳能电池板
    - 我已经谈判好了维护合同,每年需要支付固定的10万美元,并额外支付每平方英尺10美元;
    作为平方英尺数的函数,首年运营的总费用是多少。
学生的解决方案:
    设x为发电站的大小,单位为平方英尺。
    费用:
    1. 土地费用:100x美元
    2. 太阳能电池板费用:250x美元
    3. 维护费用:100,000+100x=10万美元+10x美元
    总费用:100x美元+250x美元+10万美元+100x美元=450x+10万美元
实际解决方案和步骤:
"""
response = get_completion(prompt)
print(response)

输出结果:

实际解决方案和步骤:
    1. 土地费用:每平方英尺100美元,所以总费用为100x美元。
    2. 太阳能电池板费用:每平方英尺250美元,所以总费用为250x美元。
    3. 维护费用:固定费用为10万美元,额外费用为每平方英尺10美元,所以总费用为10万美元+10x美元。
    4. 总费用:将上述三项费用相加,得到总费用为100x美元+250x美元+10万美元+10x美元=360x+10万美元。

学生计算的总费用:450x+10万美元
实际计算的总费用:360x+10万美元
学生计算的费用和实际计算的费用是否相同:否
学生的解决方案和实际解决方案是否相同:否
学生的成绩:不正确

第一版本和第二版本的差距就在于,先让模型生成了自己的方案,然后再去评估,“correct"或"incorrect”。这个时候的答案就是正确的了,由此可见引导后评估是一个优秀的方案。

幻觉
model对自己的知识边界并不够了解,也就是它有一些“自大”,即使不懂也要装懂,说的专业,即幻觉。这是 model 一个很大的缺陷,如下一个示例:

prompt = f"""
华为公司生产的GT Watch运动手表的相关信息
"""
response = get_completion(prompt)
print(response)

其实华为公司没有这款手表,但model还是给出了看起来十分正规的一套答案:

华为公司生产的GT Watch运动手表是一款智能手表,具有多种功能和特点。以下是相关信息:

1. 设计和外观:GT Watch采用圆形表盘设计,具有精致的外观和高质量的材料制造。它有多种颜色和表带选择,可以根据个人喜好进行定制。

2. 显示屏:GT Watch配备了1.39英寸的AMOLED显示屏,具有高清分辨率和良好的可视性。用户可以通过触摸屏幕进行操作和导航。

3. 运动追踪:GT Watch具有全天候的运动追踪功能,可以监测用户的步数、跑步距离、卡路里消耗和心率等数据。它还支持多种运动模式,如跑步、骑行、游泳等。

4. 健康监测:GT Watch可以监测用户的心率、血氧饱和度和睡眠质量等健康指标。它还提供健康建议和提醒,帮助用户保持良好的健康状态。

5. 通知和连接:GT Watch可以与用户的手机进行连接,通过蓝牙技术实现通知推送和电话提醒。用户可以在手表上查看短信、电话和社交媒体通知,无需拿出手机。

6. 长续航时间:GT Watch具有较长的续航时间,一次充电可以使用数天。它还支持快速充电技术,可以在短时间内充满电。

7. 其他功能:GT Watch还具有其他功能,如天气预报、闹钟、计时器、计步器等。它还支持NFC支付和音乐控制等便利功能。

总体而言,华为GT Watch是一款功能强大、外观精致的智能运动手表,适合那些注重健康和运动的用户使用。

如果我们不知道华为确实没有这款手表,已经被他骗了吧…毕竟上述内容如此的正规且标准。

那这种现象有没有好的办法可以去规避呢?目前这一现象属于大模型自带的一个缺陷,虽然相关公司在积极改善,但还是没有完美解决这一问题,所以在技术得以进一步改进之前,我们只可以通过合理的prompt尽可能减少幻觉发生。举个例子,可以先让LLM直接引用文本中的原句,然后再进行解答。这可以有效定位信息来源,降低生成虚假内容。

二、工程迭代

其实无论多么优秀的工程师在开发过程中也很难做到一次就将 prompt 创造的足够完美,一个完美的 prompt 是要经历过 n 次迭代的。所以,往往我们需要关注的是在这个迭代创造的过程中,我们所总结出来的思考框架。本节将重点讲述这个思考过程的一些核心思想。

在机器学习开发中,经常用的思想是如下图所示的一个循环过程,利用 idea 去写代码或收集数据,然后推断出一些结果并分析,最后利用这些分析结果优化甚至改变 idea。创造 prompt 是一个很相似的过程,提出一个尽可能符合第一节的 prompt,得到输出结果并分析,优化或者修改 prompt ,不断重复此过程。

纯干货——《面向开发者的 ChatGPT Prompt工程》学习笔记_第2张图片

本节的内容更偏向于实操性,课程里最初提供的基本源码如下,你可以借助这个实例去感受model在prompt不断完善时,所输出的结果的差异性。首先demo提供了一份椅子的说明书,说明结构、材料、尺寸等属性:

# 示例:产品说明书
fact_sheet_chair = """
概述
    美丽的中世纪风格办公家具系列的一部分,包括文件柜、办公桌、书柜、会议桌等。
    多种外壳颜色和底座涂层可选。
    可选塑料前后靠背装饰(SWC-100)或10种面料和6种皮革的全面装饰(SWC-110)。
    底座涂层选项为:不锈钢、哑光黑色、光泽白色或铬。
    椅子可带或不带扶手。
    适用于家庭或商业场所。
    符合合同使用资格。
结构
    五个轮子的塑料涂层铝底座。
    气动椅子调节,方便升降。
尺寸
    宽度53厘米|20.87英寸
    深度51厘米|20.08英寸
    高度80厘米|31.50英寸
    座椅高度44厘米|17.32英寸
    座椅深度41厘米|16.14英寸
选项
    软地板或硬地板滚轮选项。
    两种座椅泡沫密度可选:中等(1.8磅/立方英尺)或高(2.8磅/立方英尺)。
    无扶手或8个位置PU扶手。
材料
外壳底座滑动件
    改性尼龙PA6/PA66涂层的铸铝。
    外壳厚度:10毫米。
    座椅
    HD36泡沫
原产国
    意大利
"""

然后根据这段说明书帮助一个市场营销团队基于技术规格表的信息,为一款产品创建一个零售网站的产品描述,第一版本的prompt如下:

# Prompt :基于说明书创建营销描述
prompt = f"""
您的任务是帮助营销团队基于技术说明书创建一个产品的营销描述。
根据```标记的技术说明书中提供的信息,编写一个产品描述。
技术说明: ```{fact_sheet_chair}```
"""
response = get_completion(prompt)
print(response)

输出结果:

产品描述:

我们自豪地推出美丽的中世纪风格办公家具系列,其中包括文件柜、办公桌、书柜、会议桌等。我们的产品采用多种外壳颜色和底座涂层,以满足您的个性化需求。您可以选择塑料前后靠背装饰(SWC-100)或10种面料和6种皮革的全面装饰(SWC-110),以使您的办公室更加舒适和时尚。

我们的底座涂层选项包括不锈钢、哑光黑色、光泽白色或铬,以满足您的不同需求。椅子可带或不带扶手,适用于家庭或商业场所。我们的产品符合合同使用资格,为您提供更加可靠的保障。

我们的产品采用五个轮子的塑料涂层铝底座,气动椅子调节,方便升降。尺寸为宽度53厘米|20.87英寸,深度51厘米|20.08英寸,高度80厘米|31.50英寸,座椅高度44厘米|17.32英寸,座椅深度41厘米|16.14英寸,为您提供舒适的使用体验。

我们的产品还提供软地板或硬地板滚轮选项,两种座椅泡沫密度可选:中等(1.8/立方英尺)或高(2.8/立方英尺),以及无扶手或8个位置PU扶手,以满足您的不同需求。

我们的产品采用改性尼龙PA6/PA66涂层的铸铝外壳底座滑动件,外壳厚度为10毫米,座椅采用HD36泡沫,为您提供更加舒适的使用体验。我们的产品原产国为意大利,为您提供更加优质的品质保证。

优化一:缩短文案
目前觉得生成的文案内容有点偏长,不适合作为广告语,在这里,将prompt中添加“使用最多50个词。”看一下结果:

中世纪风格办公家具,涵盖文件柜、办公桌、书柜、会议桌等。多色选,底座涂层有不锈钢、黑色、白色、铬。适用家庭与商业,符合要求,意大利制造。

这段文字长度是55,和要求还是比较接近的,但是我们也可以发现,LLM模型生成的输出长有时并不能精确符合我们的要求,有一定的偏差,原因是因为LLM在计算和判断文本长度时依赖于分词器,但分词器在字符统计这一领域并不具备完美精度。

所以我们可以多次调试完成缩短句子这一需求,找到一个精度相对较高的方式,比如说限制句子数,单词数或者是字符数等,每一种都可以有效缩短总结文本的长度。

优化二:优化文本风格
在生成内容的专业度上也可以有所调整,比如让其生成的内容更加专业化,更加能够展示产品的实现细节,如增加描述产品的部件化学成分,物理构造等技术细节。比如将prompt进一步调整:

prompt = f"""
您的任务是帮助营销团队基于技术说明书创建一个产品的零售网站描述。

根据```标记的技术说明书中提供的信息,编写一个产品描述。

该描述面向家具零售商,因此应具有技术性质,并侧重于产品的材料构造。

使用最多50个单词。

技术规格: ```{fact_sheet_chair}```
"""
response = get_completion(prompt)
print(response)

输出结果:

这款中世纪风格办公家具系列包括文件柜、办公桌、书柜和会议桌等,适用于家庭或商业场所。可选多种外壳颜色和底座涂层,底座涂层选项为不锈钢、哑光黑色、光泽白色或铬。椅子可带或不带扶手,可选软地板或硬地板滚轮,两种座椅泡沫密度可选。外壳底座滑动件采用改性尼龙PA6/PA66涂层的铸铝,座椅采用HD36泡沫。原产国为意大利。

我们可以看到输出的内容又进一步被完善。最后,我们甚至可以尝试让其生成html代码以便清晰地展示内容,这里就不再继续展示效果了,和上述过程相差甚微。

理解本小节的关键,就是去体会这个优化的过程。这里也提供了一个总结方法,就是利用第一小节的知识,将它们熟记于心,然后不断在这个基础上迭代优化,最好不要一次性的塞给模型,因为我们不知道在这个生成过程中每一步模型的表现如何,也就没办法精准定位 ,我们应该从model生成的内容去不断迭代,才更有可能创造处最理想的prompt。而对于更复杂的应用,可以在多个样本上进行迭代训练,评估 Prompt 的平均表现。在能够生成较为满意的结果之后,在转移到多样本集上进一步评估,这样可以更省计算资源。

三、文本总结

我们都知道现在是一个信息爆炸的时代,我们不得不接触大量的文本内容,并试图在这些文本提炼出真正有用的内容,这无疑是十分耗时耗力的,那么总结文本这一功能就是十分有必要的,利用LLM总结文本就可以节省时间,提高效率,以及精准获取信息。下文将介绍两种类型的文本:

单一文本总结:
这里提供了一段某shopping网站的商品评价,让其进行摘要,内容如下:

prod_review = """
这个熊猫公仔是我给女儿的生日礼物,她很喜欢,去哪都带着。
公仔很软,超级可爱,面部表情也很和善。但是相比于价钱来说,
它有点小,我感觉在别的地方用同样的价钱能买到更大的。
快递比预期提前了一天到货,所以在送给女儿之前,我自己玩了会。
"""
prompt = f"""
您的任务是从购物网站上生成一个产品评论的简短摘要。
请对三个反引号之间的评论文本进行概括,最多30个字。
评论: ```{prod_review}```
"""
response = get_completion(prompt)
print(response)

输出结果:

可爱软熊猫公仔,送给女儿生日礼物,但有点小,价钱稍贵。快递提前一天到货。

32字,效果还不错。在这个基础上进一步增强我们的prompt,强调一个特定的视角,因为不同视角往往关注的重点也有所不同,比如说,物流部门专注于快递,用户关注价格和质量,那以物流部门视角我们可以进一步修改prompt:

prompt = f"""
您的任务是从电子商务网站上生成一个产品评论的简短摘要。
请对三个反引号之间的评论文本进行概括,最多30个字,并且侧重在快递服务上。
评论: ```{prod_review}```
"""
response = get_completion(prompt)
print(response)

输出结果:

快递提前到货,公仔可爱但有点小。

可以看到确实侧重点偏向了物流方面。那我们再修改至产品的价格和质量这一侧:

prompt = f"""
您的任务是从电子商务网站上生成一个产品评论的简短摘要。
请对三个反引号之间的评论文本进行概括,最多30个词汇,并且侧重在产品价格和质量上。
评论: ```{prod_review}```
"""
response = get_completion(prompt)
print(response)

输出结果:

可爱的熊猫公仔,质量好但有点小,价格稍高。快递提前一天到货。

虽然我们通过添加关键角度侧重的 Prompt ,确实让文本摘要更侧重于某一特定方面,然而,我们可以发现,在结果中也会保留一些其他信息,比如偏重价格与质量角度的概括中仍保留了“快递提前到货”的信息。如果我们只想要提取某一角度的信息,并过滤掉其他所有信息,则可以要求 LLM 进行 文本提取而非概括,如下所示:

prompt = f"""
您的任务是从电子商务网站上的产品评论中提取相关信息。
请从以下三个反引号之间的评论文本中提取产品运输相关的信息,最多30个词汇。
评论: ```{prod_review}```
"""
response = get_completion(prompt)
print(response)

输出结果:

产品运输相关的信息:快递提前一天到货。

“提取”使模型的结果更为准确,不再涵盖其他无关的点。

多文本总结:
其实在我们实际应用中,大多数还是面向综合性文本,还是上述的shopping网站评论整理,这些评论往往是多条的,所以我们往往要进行的也是批量评估,下面的demo将多条用户评价集合在一个列表中,并利用 for 循环和文本概括提示词,将评价概括至小于 20 个词以下,并按顺序打印。

review_1 = prod_review

# 一盏落地灯的评论
review_2 = """
我需要一盏漂亮的卧室灯,这款灯不仅具备额外的储物功能,价格也并不算太高。
收货速度非常快,仅用了两天的时间就送到了。
不过,在运输过程中,灯的拉线出了问题,幸好,公司很乐意寄送了一根全新的灯线。
新的灯线也很快就送到手了,只用了几天的时间。装配非常容易。
然而,之后我发现有一个零件丢失了,于是我联系了客服,他们迅速地给我寄来了缺失的零件!
对我来说,这是一家非常关心客户和产品的优秀公司。
"""

# 一把电动牙刷的评论
review_3 = """
我的牙医推荐使用电动牙刷,所以买了这款。
目前为止,电池续航表现相当不错。
初次充电后,我在第一周一直将充电器插着,为的是对电池进行条件养护。
过去的3周里,我每天早晚都使用它刷牙,但电池依然维持着原来的充电状态。
不过,牙刷头太小了。我见过比这个牙刷头还大的婴儿牙刷。
我希望牙刷头更大一些,带有不同长度的刷毛,
这样可以更好地清洁牙齿间的空隙,但这款牙刷做不到。
总的来说,如果能以50美元左右的价格购买到这款牙刷,那是一个不错的交易。
制造商的替换刷头相当贵,但可以购买价格更为合理的通用刷头。
这款牙刷让我感觉就像每天都去了一次牙院,感觉刷的非常干净!
"""

# 一台搅拌机的评论
review_4 = """
在11月份期间,这个17件套装还在季节性促销中,售价约为49美元,打了五折左右。
可是由于某种原因,到了12月的第二周,所有的价格都上涨了,
同样的套装价格涨到了70-89美元不等。而11件套装的价格也从之前的29美元上涨了约10美元。
看起来还算不错,但是如果你仔细看底座,刀片锁定的部分看起来没有前几年版本的那么漂亮。
然而,我打算非常小心地使用它
(例如,我会先在搅拌机中研磨豆类、冰块、大米等坚硬的食物,然后再将它们研磨成所需的粒度,
接着切换到打蛋器刀片以获得更细的面粉,如果我需要制作更细腻/少果肉的食物)。
在制作冰沙时,我会将要使用的水果和蔬菜切成细小块并冷冻
(如果使用菠菜,我会先轻微煮熟菠菜,然后冷冻,直到使用时准备食用。
如果要制作冰糕,我会使用一个小到中号的食物加工器),这样就可以避免添加过多的冰块。
大约一年后,电机开始发出奇怪的声音。我打电话给客户服务,但保修期已经过期了,
所以我只好购买了另一台。值得注意的是,这类产品的整体质量在过去几年里有所下降
,所以他们在一定程度上依靠品牌认知和消费者忠诚来维持销售。在大约两天内,我收到了新的搅拌机。
"""

reviews = [review_1, review_2, review_3, review_4]

然后利用for循环处理文案

for i in range(len(reviews)):
    prompt = f"""
    你的任务是从电子商务网站上的产品评论中提取相关信息。
    请对三个反引号之间的评论文本进行概括,最多20个词汇。
    评论文本: ```{reviews[i]}```
    """
    response = get_completion(prompt)
    print(f"评论{i+1}: ", response, "\n")

输出结果:

评论1:  熊猫公仔是生日礼物,女儿喜欢,软可爱,面部表情和善。价钱有点小,快递提前一天到货。 

评论2:  漂亮卧室灯,储物功能,快速送达,灯线问题,快速解决,容易装配,关心客户和产品。 

评论3:  这款电动牙刷电池续航好,但牙刷头太小,价格合理,清洁效果好。 

评论4:  该评论提到了一个17件套装的产品,在11月份有折扣销售,但在12月份价格上涨。评论者提到了产品的外观和使用方法,并提到了产品质量下降的问题。最后,评论者提到他们购买了另一台搅拌机。  

四、模型推理

推断情感:
依旧是以购物网站为背景,可以考虑一下,我们现实中可能会经常遇到这个场景:我们需要了解用户对于我们商品的态度是积极还是消极的,以此作为基本的数据支持去适当的调整产品构成。以一个台灯评论为例:

lamp_review = """
我买了一盏漂亮的卧室灯,这款灯具有多色调节功能,价格也不贵。\
我很快就收到了它。在运输过程中,虽然灯绳断了,但是公司爽快的寄送了一个新的。\
几天后就收到了。这款灯很容易组装。我发现少了一个零件,于是联系了他们的客服,他们很快就给我寄来了缺失的零件!\
在我看来,Lumina 是一家非常关心顾客和产品的优秀公司!
"""

然后,可以直接的去设置prompt了解这段文字的情感态度积极与否:

prompt = f"""
以下用三个反引号分隔的产品评论的情感是什么?

评论文本: ```{lamp_review}```
"""
response = get_completion(prompt)
print(response)

输出结果:

情感是积极的。

接下来,我们使用刚才的评论,但改为用一个新的 Prompt 。这一次不仅让模型能够识别出评论作者所表达的情感,还要将这些情感用不超过五个词语的关键字总结。prompt如下:

# 中文
prompt = f"""
识别以下评论的作者表达的情感。用不超过五个词语总结。将答案格式化为以逗号分隔的单词列表。
评论文本: ```{lamp_review}```
"""
response = get_completion(prompt)
print(response)

输出结果:

满意,感激,赞赏,信任,满足

上述例子其实我们可以发现LLM在文本中提取情感这一能力还是蛮强大的。

识别负面评价:
对于商家来说,准确识别出客户的负面评价往往比识别积极评价更有意义,每一个差评辩证的去看其实都是一个改进提升的机会,也是一个提升公司口碑的机会。

# 中文
prompt = f"""
以下评论的作者是否表达了愤怒?评论用三个反引号分隔。给出是或否的答案。
评论文本: ```{lamp_review}```
"""
response = get_completion(prompt)
print(response)

输出结果为:否

其实到这里我们就可以发现,曾经需要多次引用model才能完成的任务如今利用prompt就能轻松整合,这看起来要比在入门篇一中的demo示例更为简单的多。

提取信息:
在文本总结过程中,除了上述的情感分析我们还经常要对评论文本进行一系列的信息提取,比如在这里除了了解到用户的反馈态度之外,我还想要了解到上述评论中用户购买的产品是什么?产品来自于哪个公司?并且为了方便查看,我们也可以进一步去规定输出格式为json,如下所示:

# 中文
prompt = f"""
从评论文本中识别以下项目:
- 情绪(正面或负面)
- 评论者购买的物品
- 制造该物品的公司

评论用三个反引号分隔。将你的响应格式化为 JSON 对象,以 “情感倾向”、“是否生气”、“物品类型” 和 “品牌” 作为键。
如果信息不存在,请使用 “未知” 作为值。
回应尽可能简短。

评论文本: ```{lamp_review}```
"""
response = get_completion(prompt)
print(response)

输出结果:

{
  "情感倾向": "正面",
  "物品类型": "卧室灯",
  "品牌": "Lumina"
}

推断主题:
除了上述的示例,在文本总结过程中我们还经常利用llm进行主题推断,比如说快速了解到一篇文字的主旨是什么。比如下面的demo中就提供给llm一篇虚构的报纸内容让其总结:

# 中文
story = """
在政府最近进行的一项调查中,要求公共部门的员工对他们所在部门的满意度进行评分。
调查结果显示,NASA 是最受欢迎的部门,满意度为 95%。

一位 NASA 员工 John Smith 对这一发现发表了评论,他表示:
“我对 NASA 排名第一并不感到惊讶。这是一个与了不起的人们共事的好地方。我为成为这样一个创新组织的一员感到自豪。”

NASA 的管理团队也对这一结果表示欢迎,主管 Tom Johnson 表示:
“我们很高兴听到我们的员工对 NASA 的工作感到满意。
我们拥有一支才华横溢、忠诚敬业的团队,他们为实现我们的目标不懈努力,看到他们的辛勤工作得到回报是太棒了。”

调查还显示,社会保障管理局的满意度最低,只有 45%的员工表示他们对工作满意。
政府承诺解决调查中员工提出的问题,并努力提高所有部门的工作满意度。
"""

输出结果:

['NASA', '满意度', '员工', '调查', '部门']

我们也可以“反向操作”,比如直接给llm一些关键字,让llm判断主题是否符合,将prompt修改成下述格式:

# 中文
prompt = f"""
判断主题列表中的每一项是否是给定文本中的一个话题,

以列表的形式给出答案,每个元素是一个Json对象,键为对应主题,值为对应的 0 或 1。

主题列表:当地政府、AI、员工满意度、NASA

给定文本: ```{story}```
"""
response = get_completion(prompt)
print(response)

输出结果:

[
  {"当地政府": 1},
  {"AI": 0},
  {"员工满意度": 1},
  {"NASA": 1}
]

可以看到llm精准的给出了这段文字相关的词语是哪些,这个过程就是目前很出名的“零样本”学习,我们无须在提供大量标签数据去训练模型,就直接可以通过prompt判断出关键词。扩展一下,我们也可以通过这类prompt,实现一个新闻提醒的功能,只要文章内容出现了设定关键字的内容,就让系统输出相应的提醒:

response = get_completion(prompt)

result_lst = eval(response)##解析为list
topic_dict = {list(i.keys())[0] : list(i.values())[0] for i in result_lst}
print(topic_dict)
if topic_dict['NASA'] == 1:
    print("提醒: 关于NASA的新消息")

五、文本翻译

相信使用过一段时间的gpt之后的你已经发现llm具有强大的翻译能力,支持很多种语言,英语、法语、西班牙语…不仅如此,还支持拼写纠正、语法调整、格式转换等各类文本转换的任务,用起来很方便,本章节将介绍如何通过api在本地实现上述的一系列功能(课程里Isa是循序渐进的介绍了几种功能实现:文本翻译、语气及风格调整、文件格式转换、拼写和语法的纠正,最后整合到一起)为使文章更加精简,这里我只记录了最后整合的过程。在这个demo中我们实现了文本的翻译、拼写纠正、语气调整和格式转换。

text = f"""
Got this for my daughter for her birthday cuz she keeps taking \
mine from my room.  Yes, adults also like pandas too.  She takes \
it everywhere with her, and it's super soft and cute.  One of the \
ears is a bit lower than the other, and I don't think that was \
designed to be asymmetrical. It's a bit small for what I paid for it \
though. I think there might be other options that are bigger for \
the same price.  It arrived a day earlier than expected, so I got \
to play with it myself before I gave it to my daughter.
"""

prompt = f"""
针对以下三个反引号之间的英文评论文本,
首先进行拼写及语法纠错,
然后将其转化成中文,
再将其转化成优质淘宝评论的风格,从各种角度出发,分别说明产品的优点与缺点,并进行总结。
润色一下描述,使评论更具有吸引力。
输出结果格式为:
【优点】xxx
【缺点】xxx
【总结】xxx
注意,只需填写xxx部分,并分段输出。
将结果输出成Markdown格式。
```{text}```
"""
response = get_completion(prompt)

输出结果:

【优点】
- 这个毛绒玩具是我给女儿生日买的,因为她总是从我的房间拿走我的玩具。
- 是的,成年人也喜欢熊猫。她随身携带它,它非常柔软和可爱。
- 其中一只耳朵比另一只低一点,我不认为这是设计上的不对称。
- 它的尺寸有点小,与我支付的价格不太相符。

【缺点】
- 我认为可能有其他同样价格但更大的选择。
- 它比预期提前了一天送到,所以我在给女儿之前自己玩了一下。

【总结】
这个熊猫毛绒玩具非常适合孩子,柔软可爱。然而,尺寸有点小,价格可能不太合理。如果你在淘宝上购买,可能会有更多选择。

六、文本扩展

文本扩展就是将一段较为简短的文字扩展成更加丰富的长文,极大程度上的方便了我们去生成一份日报/月报或者是各类电子邮件,在这节中我们在使用api实现任务的时候会额外的使用temprature这个参数,去扩展生成内容的多样性,temperature 的值越大,语言模型输出的多样性越大;temperature 的值越小,输出越稳定,举个例子,在某一文本中,语言模型认为“比萨”是接下来最可能的词,其次是“寿司”和“塔可”。若 temperature 为0,则每次都会生成“比萨”;而当 temperature 越接近 1 时,生成结果是“寿司”或“塔可”的可能性越大,使文本更加多样。

纯干货——《面向开发者的 ChatGPT Prompt工程》学习笔记_第3张图片

所以说,当我们需要更加可靠的文本时,通常都将temperature设置为0,但如果想要文本更加具有发散性和创造性则经常设置更高的temperature系数,接下里的demo将分别将temperature设置为0和一个较高的值,我们可以一起看一下输出结果的差异。一个任务为为客户发送电子邮件回复,code如下:

def get_completion(prompt, model="gpt-3.5-turbo"):
    messages = [{"role": "user", "content": prompt}]
    response = openai.ChatCompletion.create(
        model=model,
        messages=messages,
        temperature=0.7, # this is the degree of randomness of the model's output
    )
    return response.choices[0].message["content"]
sentiment = "negative"
review = f"""
他们在11月份的季节性销售期间以约49美元的价格出售17件套装,折扣约为一半。\
但由于某些原因(可能是价格欺诈),到了12月第二周,同样的套装价格全都涨到了70美元到89美元不等。\
11件套装的价格也上涨了大约10美元左右。\
虽然外观看起来还可以,但基座上锁定刀片的部分看起来不如几年前的早期版本那么好。\
不过我打算非常温柔地使用它,例如,\
我会先在搅拌机中将像豆子、冰、米饭等硬物研磨,然后再制成所需的份量,\
切换到打蛋器制作更细的面粉,或者在制作冰沙时先使用交叉切割刀片,然后使用平面刀片制作更细/不粘的效果。\
制作冰沙时,特别提示:\
将水果和蔬菜切碎并冷冻(如果使用菠菜,则轻轻煮软菠菜,然后冷冻直到使用;\
如果制作果酱,则使用小到中号的食品处理器),这样可以避免在制作冰沙时添加太多冰块。\
大约一年后,电机发出奇怪的噪音,我打电话给客服,但保修已经过期了,所以我不得不再买一个。\
总的来说,这些产品的总体质量已经下降,因此它们依靠品牌认可和消费者忠诚度来维持销售。\
货物在两天内到达。
"""
prompt = f"""
你是一名客户服务的AI助手。
你的任务是给一位重要的客户发送邮件回复。
根据通过“```”分隔的客户电子邮件生成回复,以感谢客户的评价。
如果情感是积极的或中性的,感谢他们的评价。
如果情感是消极的,道歉并建议他们联系客户服务。
请确保使用评论中的具体细节。
以简明和专业的语气写信。
以“AI客户代理”的名义签署电子邮件。
客户评价:```{review}```
评论情感:{sentiment}
"""
response = get_completion(prompt)
print(response)

当temperature=0.7的时候 两次结果是较大差距的:temperature为0时,每次使用同样的 Prompt,得到的结果总是一致的。

纯干货——《面向开发者的 ChatGPT Prompt工程》学习笔记_第4张图片

七、聊天机器人

或许在实现上述demo,你会觉得这个过程很简单,甚至没什么好讲的,但其实在短短几分钟内,我们甚至就可以利用prompt建立多个用于文本推理的系统,而这是以前需要机器学习领域的专家经过数天甚至数周时间才能完成的任务。在正式开始实现之前先介绍一些前缀知识,聊天机器人和上文中的单轮对话的差距就是message 这个list的设置,当实现一个机器人的时候,message就不能在只包含一个user的信息了,需要将上下文中的所有信息都收集起来,以确保对话的连续性,让model每次回复时都有上几轮的思维条件。这里我们以一个订餐机器人为例来介绍整个过程。

首先,我们新建一个函数,它和文章最初提到的get_completion功能是一样的,略有区别的就是message的设置:

import openai

# 下文第一个函数即tool工具包中的同名函数,此处展示出来以便对比
def get_completion(prompt, model="gpt-3.5-turbo"):
    messages = [{"role": "user", "content": prompt}]
    response = openai.ChatCompletion.create(
        model=model,
        messages=messages,
        temperature=0, 
    )
    return response.choices[0].message["content"]

def get_completion_from_messages(messages, model="gpt-3.5-turbo", temperature=0):
    response = openai.ChatCompletion.create(
        model=model,
        messages=messages,
        temperature=temperature, # 控制模型输出的随机程度
    )
    return response.choices[0].message["content"]

get_completion_from_messages这个函数将message设置为参数,制作机器人的时候role可以用到:system、user、assistant这三种,我们可以在system中设置机器人的身份,比如:{‘role’:‘system’, ‘content’:‘你是个聪明的聊天机器人。’},user则代表我们用户的输入比如:{‘role’:‘user’, ‘content’:‘Hi, 我是Isa’},assistant则是机器人本身的回复比如:{‘role’:‘assistant’, ‘content’: “Hi Isa! 很高兴认识你。今天有什么可以帮到你的吗?”}。

在了解了message之后我们可以很清楚的了解,其实我们这里需要实现的任务就是在设置完初始语境后去收集用户的信息、保存模型回复的信息即可。实现参数如下:

def collect_messages(_):
    prompt = inp.value_input
    inp.value = ''
    context.append({'role':'user', 'content':f"{prompt}"})
    response = get_completion_from_messages(context) 
    context.append({'role':'assistant', 'content':f"{response}"})
    panels.append(
        pn.Row('User:', pn.pane.Markdown(prompt, width=600)))
    panels.append(
        pn.Row('Assistant:', pn.pane.Markdown(response, width=600, style={'background-color': '#F6F6F6'})))
    return pn.Column(*panels)

课程里用的是panel这个库去构建页面,以获取用户的信息,这里大概讲一下这个库相关的使用:

使用之前pip一下,

pip install panel

pn.Row 用于创建行,包含了用户或助手的名称和消息内容

pn.pane.Markdown 用于将消息内容以Markdown格式呈现

inp 是一个由Panel库提供的文本输入框(下面的代码有定义)

收集信息后的函数就可以用来panel展示了

pn.extension()
panels = [] # collect display 
context = [{'role':'system', 'content':"""
你是订餐机器人,为披萨餐厅自动收集订单信息。
你要首先问候顾客。然后等待用户回复收集订单信息。收集完信息需确认顾客是否还需要添加其他内容。
最后需要询问是否自取或外送,如果是外送,你要询问地址。
最后告诉顾客订单总金额,并送上祝福。

请确保明确所有选项、附加项和尺寸,以便从菜单中识别出该项唯一的内容。
你的回应应该以简短、非常随意和友好的风格呈现。

菜单包括:

菜品:
意式辣香肠披萨(大、中、小) 12.95、10.00、7.00
芝士披萨(大、中、小) 10.95、9.25、6.50
茄子披萨(大、中、小) 11.95、9.75、6.75
薯条(大、小) 4.50、3.50
希腊沙拉 7.25

配料:
奶酪 2.00
蘑菇 1.50
香肠 3.00
加拿大熏肉 3.50
AI酱 1.50
辣椒 1.00

饮料:
可乐(大、中、小) 3.00、2.00、1.00
雪碧(大、中、小) 3.00、2.00、1.00
瓶装水 5.00
"""} ]  # accumulate messages

inp = pn.widgets.TextInput(value="Hi", placeholder='Enter text here…')
button_conversation = pn.widgets.Button(name="Chat!")
interactive_conversation = pn.bind(collect_messages, button_conversation)
dashboard = pn.Column(
    inp,
    pn.Row(button_conversation),
    pn.panel(interactive_conversation, loading_indicator=True, height=300),
)
dashboard.show()

把本节的三段代码组合到一起,就可以看到:
纯干货——《面向开发者的 ChatGPT Prompt工程》学习笔记_第5张图片

至此,我们就已经成功的实现了一个聊天机器人~有没有一种豁然开朗的感觉呢?

八、总结

课程到这里就完结了,原视频大概1h多点,所以干货还是很多的~本文覆盖了所有的知识点以及绝大部份的demo(有一些demo比较相似就没有整理出来)。相信你也和我一样,学完这个课程,并在最后实现点餐机器人之后就会发现,原来我们离ai是那么近,我们无须再去收集数据/训练数据就可以轻松实现很多有趣的小功能。最后在回顾一下这里涉及到的核心知识点:prompt的编写核心是:编写清晰具体的指令;给予模型一些思考时间。LLM在工程迭代、文本总结、模型推理、文本翻译、文本扩展以及聊天机器人等功能有着优秀的表现。

你可能感兴趣的:(AI,chatgpt,prompt,学习,人工智能)