1. 前言

近期,DeepLearning.AI推出了时长一个小时的“ChatGPT Prompt Engineering for Developers”课程,介绍了如何开发基于ChatGPT的应用程序,由来自OpenAI的Isa Fulford和人工智能领域专家吴恩达(Andrew Ng)主讲。作为一个短时课程,这门课并没有讲解ChatGPT和提示词(prompts)的原理,也没有讲解编程开发相关的知识。它的定位十分准确,正如其名——面向开发者的ChatGPT提示词工程,主要讨论的是如何设计更好的提示词,帮助ChatGPT理解开发者和用户的意图,从而给出更符合期望的回答。

所谓提示词工程,其实恰恰对应着人类社会中的“提问的艺术”。为什么会有好问题和坏问题之分?为什么提正确的问题非常重要?在提问时,你的问题描述是否清晰具体,而不是具有二义性甚至多义性?你是否给出了相关背景知识和上下文,以及你已经做出过的思考和努力?你是否描述了期待听到的回答的大概方向、内容和形式?当然了,人与人的沟通通常不需要这么复杂,生活中绝大部分问题也没必要如此较真。然而,对于严肃的问题,尤其是在探求科学与真理的道路上,严肃的思考和提问是必要的。

好的问题,反映了提问者对问题内涵的深入思考和清晰认知,能够准确表述提问者对未知之地的想象,即使他可能不知道如何到达那里,也不知道那里具体有什么。

言归正传。虽然这是一个面向开发者的课程,但是其中的许多技巧也可以用在我们的日常工作中,帮助我们提高效率。作为一个实用主义者,我并不过分关注网络上关于LLM的是是非非和争吵。无论是黑猫还是白猫,只要它能够帮助我们节约时间、提高生产力就好。本文是该课程的笔记,方便日后参考。

在课程的开头部分,吴恩达介绍了两类LLM,一种是Base LLM,基于文本训练数据预测下一个词;另一种是Instruction Tuned LLM,可以根据指令给出回答,其中用到了基于人类反馈的强化学习(Reinforcement Learning with Human Feedback,简称RLHF)。后者基于前者,相比较而言也更能给出符合人类期望的回答,目前我们看到的商用LLM通常是后者。

2. 指导原则

总体而言,与ChatGPT交互的指导原则有两个:

  1. 编写清晰详细的指令。
  2. 给模型时间思考。

本课程中使用了OpenAI提供的Python库来与ChatGPT交互。相关配置代码如下,可供我们自己开发程序时参考:


# pip install openai
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, # this is the degree of randomness of the model's output
    )
    return response.choices[0].message["content"]

另外,LLM面临着幻觉(hallucination)问题,给出的可能是看上去很正确、但实际上错误的答案。为了减少幻觉,我们可以让它先寻找相关信息,然后根据相关信息作答。

2.1 原则1:编写清晰详细的指令

具体来说,指令编写策略有四个:

一、使用分隔符(delimiters)来将提问内容明确划分出来。常见的分隔符有triple quotes、triple backticks、triple dashes、angle brackets和XML tags。

text = f"""
You should express what you want a model to do by \
providing instructions that are as clear and \
specific as you can possibly make them. \
This will guide the model towards the desired output, \
and reduce the chances of receiving irrelevant \
or incorrect responses. Don't confuse writing a \
clear prompt with writing a short prompt. \
In many cases, longer prompts provide more clarity \
and context for the model, which can lead to \
more detailed and relevant outputs.
"""
prompt = f"""
Summarize the text delimited by triple backticks \
into a single sentence.
```{text}```
"""
response = get_completion(prompt)
print(response)

二、要求给出结构化的输出。例如,我们可以要求ChatGPT将结果输出为HTML或JSON。

prompt = f"""
Generate a list of three made-up book titles along \
with their authors and genres.
Provide them in JSON format with the following keys:
book_id, title, author, genre.
"""

三、要求ChatGPT检查对于给定任务来说,条件是否满足。

text_1 = f"""
Making a cup of tea is easy! First, you need to get some \
water boiling. While that's happening, \
grab a cup and put a tea bag in it. Once the water is \
hot enough, just pour it over the tea bag. \
Let it sit for a bit so the tea can steep. After a \
few minutes, take out the tea bag. If you \
like, you can add some sugar or milk to taste. \
And that's it! You've got yourself a delicious \
cup of tea to enjoy.
"""
prompt = f"""
You will be provided with text delimited by triple quotes.
If it contains a sequence of instructions, \
re-write those instructions in the following format:

Step 1 - ...
Step 2 - …
Step N - …

If the text does not contain a sequence of instructions, \
then simply write \"No steps provided.\"

\"\"\"{text_1}\"\"\"
"""

四、少样本提示(few-shot prompting)。其实很简单,给出一些成功的例子,让模型参考这些例子去执行任务。

prompt = f"""
Your task is to answer in a consistent style.

<child>: Teach me about patience.

<grandparent>: The river that carves the deepest \
valley flows from a modest spring; the \
grandest symphony originates from a single note; \
the most intricate tapestry begins with a solitary thread.

<child>: Teach me about resilience.
"""

2.2 原则2:给模型时间思考

具体来说,指令编写策略有两个:

一、给出完成任务的步骤。例如,我们可以告诉模型,第一步要做什么,第二步要做什么……

text = f"""
In a charming village, siblings Jack and Jill set out on \
a quest to fetch water from a hilltop \
well. As they climbed, singing joyfully, misfortune \
struck—Jack tripped on a stone and tumbled \
down the hill, with Jill following suit. \
Though slightly battered, the pair returned home to \
comforting embraces. Despite the mishap, \
their adventurous spirits remained undimmed, and they \
continued exploring with delight.
"""
# example 1
prompt_1 = f"""
Perform the following actions:
1 - Summarize the following text delimited by triple \
backticks with 1 sentence.
2 - Translate the summary into French.
3 - List each name in the French summary.
4 - Output a json object that contains the following \
keys: french_summary, num_names.

Separate your answers with line breaks.

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

二、命令模型在给出判断或总结前,先得出自己的答案。

prompt = f"""
Your task is to determine if the student's solution \
is correct or not.
To solve the problem do the following:
- First, work out your own solution to the problem.
- Then compare your solution to the student's solution \
and evaluate if the student's solution is correct or not.
Don't decide if the student's solution is correct until
you have done the problem yourself.

Use the following format:
Question:
```
question here
```
Student's solution:
```
student's solution here
```
Actual solution:
```
steps to work out the solution and your solution here
```
Is the student's solution the same as actual solution \
just calculated:
```
yes or no
```
Student grade:
```
correct or incorrect
```

Question:
```
I'm building a solar power installation and I need help \
working out the financials.
- Land costs $100 / square foot
- I can buy solar panels for $250 / square foot
- I negotiated a contract for maintenance that will cost \
me a flat $100k per year, and an additional $10 / square \
foot
What is the total cost for the first year of operations \
as a function of the number of square feet.
```
Student's solution:
```
Let x be the size of the installation in square feet.
Costs:
1. Land cost: 100x
2. Solar panel cost: 250x
3. Maintenance cost: 100,000 + 100x
Total cost: 100x + 250x + 100,000 + 100x = 450x + 100,000
```
Actual solution:
"""

3. 迭代式开发

和传统的开发工作一样,我们不能期待一下子就从ChatGPT获得完美的答案,而是需要不断迭代和优化。整体的提示词设计流程如下:

  1. 确保提示词是清晰、详细的。
  2. 分析答案为什么不符合预期。
  3. 根据分析结果优化提示词。
  4. 重复上述过程。

本节使用的案例如下:

fact_sheet_chair = """
OVERVIEW
- Part of a beautiful family of mid-century inspired office furniture,
including filing cabinets, desks, bookcases, meeting tables, and more.
- Several options of shell color and base finishes.
- Available with plastic back and front upholstery (SWC-100)
or full upholstery (SWC-110) in 10 fabric and 6 leather options.
- Base finish options are: stainless steel, matte black,
gloss white, or chrome.
- Chair is available with or without armrests.
- Suitable for home or business settings.
- Qualified for contract use.

CONSTRUCTION
- 5-wheel plastic coated aluminum base.
- Pneumatic chair adjust for easy raise/lower action.

DIMENSIONS
- WIDTH 53 CM | 20.87”
- DEPTH 51 CM | 20.08”
- HEIGHT 80 CM | 31.50”
- SEAT HEIGHT 44 CM | 17.32”
- SEAT DEPTH 41 CM | 16.14”

OPTIONS
- Soft or hard-floor caster options.
- Two choices of seat foam densities:
 medium (1.8 lb/ft3) or high (2.8 lb/ft3)
- Armless or 8 position PU armrests

MATERIALS
SHELL BASE GLIDER
- Cast Aluminum with modified nylon PA6/PA66 coating.
- Shell thickness: 10 mm.
SEAT
- HD36 foam

COUNTRY OF ORIGIN
- Italy
"""

基本的提示词如下:

prompt = f"""
Your task is to help a marketing team create a
description for a retail website of a product based
on a technical fact sheet.

Write a product description based on the information
provided in the technical specifications delimited by
triple backticks.

Technical specifications: ```{fact_sheet_chair}```
"""
response = get_completion(prompt)
print(response)

下面是在上述案例上不断迭代改进的三个例子。

3.1 需求1:限制答案的长度

我们可以要求ChatGPT限制答案的长度:

prompt = f"""
Your task is to help a marketing team create a
description for a retail website of a product based
on a technical fact sheet.

Write a product description based on the information
provided in the technical specifications delimited by
triple backticks.

Use at most 50 words.

Technical specifications: ```{fact_sheet_chair}```
"""

3.2 需求2:关注重点细节

我们可以让ChatGPT面向目标人群(如retailers)生成答案:

prompt = f"""
Your task is to help a marketing team create a
description for a retail website of a product based
on a technical fact sheet.

Write a product description based on the information
provided in the technical specifications delimited by
triple backticks.

The description is intended for furniture retailers,
so should be technical in nature and focus on the
materials the product is constructed from.

At the end of the description, include every 7-character
Product ID in the technical specification.

Use at most 50 words.

Technical specifications: ```{fact_sheet_chair}```
"""

3.3 需求3:将结果以表格形式输出

格式化的输出能够帮助我们更好地查看结果:

prompt = f"""
Your task is to help a marketing team create a
description for a retail website of a product based
on a technical fact sheet.

Write a product description based on the information
provided in the technical specifications delimited by
triple backticks.

The description is intended for furniture retailers,
so should be technical in nature and focus on the
materials the product is constructed from.

At the end of the description, include every 7-character
Product ID in the technical specification.

After the description, include a table that gives the
product's dimensions. The table should have two columns.
In the first column include the name of the dimension.
In the second column include the measurements in inches only.

Give the table the title 'Product Dimensions'.

Format everything as HTML that can be used in a website.
Place the description in a <div> element.

Technical specifications: ```{fact_sheet_chair}```
"""

我们可以调用IPython的库渲染输出的HTML:

from IPython.display import display, HTML
display(HTML(response))

4. 总结归纳(Summarizing)

ChatGPT是一个做总结的好手。对于“太长不看”(TL;DR)的内容,我们完全可以请ChatGPT帮助总结。关键点依然在于,明确提出你的需求。

本节的案例如下:

prod_review = """
Got this panda plush toy for my daughter's birthday, \
who loves it and takes it everywhere. It's soft and \
super cute, and its face has a friendly look. It's \
a bit small for what I paid 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 her.
"""

4.1 技巧1:限制总结的长度

我们可以使用类似下面这样的提示词来限制总结内容的长度:

prompt = f"""
Your task is to generate a short summary of a product \
review from an ecommerce site.

Summarize the review below, delimited by triple
backticks, in at most 30 words.

Review: ```{prod_review}```
"""

4.2 技巧2:给出总结的关注点

我们可以使用类似下面这样的提示词来让ChatGPT关注产品运输方面的内容:

prompt = f"""
Your task is to generate a short summary of a product \
review from an ecommerce site to give feedback to the \
Shipping deparmtment.

Summarize the review below, delimited by triple
backticks, in at most 30 words, and focusing on any aspects \
that mention shipping and delivery of the product.

Review: ```{prod_review}```
"""

4.3 技巧3:尝试提取,而非总结

有时候,我们希望仅获得某个方面的信息,这时就可以告诉ChatGPT去提取这些信息(总结则可能会包含不相关的信息):

prompt = f"""
Your task is to extract relevant information from \
a product review from an ecommerce site to give \
feedback to the Shipping department.

From the review below, delimited by triple quotes \
extract the information relevant to shipping and \
delivery. Limit to 30 words.

Review: ```{prod_review}```
"""

5. 推理(Inferring)

ChatGPT也可以基于文本进行推理,这个功能对于人文学科尤其有价值。例如,在传播学领域,我们可以借助ChatGPT去分析文本的情感、话题等。

5.1 技巧1:情感分析

我们可以简单地让ChatGPT分析一段文本表达的情感:

prompt = f"""
What is the sentiment of the following product review,
which is delimited with triple backticks?

Review text: '''{lamp_review}'''
"""

还可以让它直接给出二元评价:

prompt = f"""
What is the sentiment of the following product review,
which is delimited with triple backticks?

Give your answer as a single word, either "positive" \
or "negative".

Review text: '''{lamp_review}'''
"""

ChatGPT还能给出准确的词语来描述文本表达的情绪:

prompt = f"""
Identify a list of emotions that the writer of the \
following review is expressing. Include no more than \
five items in the list. Format your answer as a list of \
lower-case words separated by commas.

Review text: '''{lamp_review}'''
"""

我们还可以让ChatGPT识别某类情绪:

prompt = f"""
Is the writer of the following review expressing anger?\
The review is delimited with triple backticks. \
Give your answer as either yes or no.

Review text: '''{lamp_review}'''
"""

5.2 技巧2:命名实体识别

命名实体识别(Named Entity Recognition)是自然语言处理中的一个细分研究领域。现在,ChatGPT也能够很好地完成这个任务:

prompt = f"""
Identify the following items from the review text:
- Item purchased by reviewer
- Company that made the item

The review is delimited with triple backticks. \
Format your response as a JSON object with \
"Item" and "Brand" as the keys.
If the information isn't present, use "unknown" \
as the value.
Make your response as short as possible.

Review text: '''{lamp_review}'''
"""

我们甚至可以让它一次性完成多个分析。人生如戏,全凭想象力 :-)

prompt = f"""
Identify the following items from the review text:
- Sentiment (positive or negative)
- Is the reviewer expressing anger? (true or false)
- Item purchased by reviewer
- Company that made the item

The review is delimited with triple backticks. \
Format your response as a JSON object with \
"Sentiment", "Anger", "Item" and "Brand" as the keys.
If the information isn't present, use "unknown" \
as the value.
Make your response as short as possible.
Format the Anger value as a boolean.

Review text: '''{lamp_review}'''
"""

5.3 技巧3:话题推断

最后,我们可以让ChatGPT帮助推断文本包含的话题(其他技术,如结构主题模型STM,也能够实现类似的功能)。如前所述,这在社会学、传播学研究中非常有用。

prompt = f"""
Determine five topics that are being discussed in the \
following text, which is delimited by triple backticks.

Make each item one or two words long.

Format your response as a list of items separated by commas.

Text sample: '''{story}'''
"""

它还可以判断某个话题是否出现在给定文本中:

prompt = f"""
Determine whether each item in the following list of \
topics is a topic in the text below, which
is delimited with triple backticks.

Give your answer as list with 0 or 1 for each topic.\

List of topics: {", ".join(topic_list)}

Text sample: '''{story}'''
"""

6. 转换(Transforming)

“转换”有多种形式,比如,从一种格式转换为另一种格式,或者从一种语言转换到另一种语言(翻译)。

6.1 技巧1:自然语言翻译

翻译句子:

prompt = f"""
Translate the following English text to Spanish: \
```Hi, I would like to order a blender```
"""

辨别语言:

prompt = f"""
Tell me which language this is:
```Combien coûte le lampadaire?```
"""

多语言翻译:

prompt = f"""
Translate the following  text to French and Spanish
and English pirate: \
```I want to order a basketball```
"""

翻译为不同风格的语言:

prompt = f"""
Translate the following text to Spanish in both the \
formal and informal forms:
'Would you like to order a pillow?'
"""

ChatGPT作为万能语言翻译器:

user_messages = [
  "La performance du système est plus lente que d'habitude.",  # System performance is slower than normal
  "Mi monitor tiene píxeles que no se iluminan.",              # My monitor has pixels that are not lighting
  "Il mio mouse non funziona",                                 # My mouse is not working
  "Mój klawisz Ctrl jest zepsuty",                             # My keyboard has a broken control key
  "我的屏幕在闪烁"                                               # My screen is flashing
]

for issue in user_messages:
    prompt = f"Tell me what language this is: ```{issue}```"
    lang = get_completion(prompt)
    print(f"Original message ({lang}): {issue}")

    prompt = f"""
    Translate the following  text to English \
    and Korean: ```{issue}```
    """
    response = get_completion(prompt)
    print(response, "\n")

6.2 技巧2:语气转换

例如,从俚语转为一封标准的商业信件:

prompt = f"""
Translate the following from slang to a business letter:
'Dude, This is Joe, check out this spec on this standing lamp.'
"""

结果还是挺有意思的:

Dear Sir/Madam,

I am writing to bring to your attention a standing lamp that I believe may be of interest to you. Please find attached the specifications for your review.

Thank you for your time and consideration.

Sincerely,

Joe

6.3 技巧3:格式转换

这个功能更常用了。例如,从JSON转换为HTML:

data_json = { "resturant employees" :[
    {"name":"Shyam", "email":"shyamjaiswal@gmail.com"},
    {"name":"Bob", "email":"bob32@gmail.com"},
    {"name":"Jai", "email":"jai87@gmail.com"}
]}

prompt = f"""
Translate the following python dictionary from JSON to an HTML \
table with column headers and title: {data_json}
"""

6.4 技巧4:拼写和语法检查

这个功能也很实用!我们在写各种材料时,都可以请ChatGPT帮忙审阅一下。

prompt = f"""Proofread and correct the following text
and rewrite the corrected version. If you don't find
and errors, just say "No errors found". Don't use
any punctuation around the text:
```{t}```"""

7. 扩展(Expanding)

所谓扩展,就是让ChatGPT根据提示主动生成一些文本。

例如,我们可以让ChatGPT针对客户的评价自动生成回复邮件:

prompt = f"""
You are a customer service AI assistant.
Your task is to send an email reply to a valued customer.
Given the customer email delimited by ```, \
Generate a reply to thank the customer for their review.
If the sentiment is positive or neutral, thank them for \
their review.
If the sentiment is negative, apologize and suggest that \
they can reach out to customer service.
Make sure to use specific details from the review.
Write in a concise and professional tone.
Sign the email as `AI customer agent`.
Customer review: ```{review}```
Review sentiment: {sentiment}
"""

其中,情感分析结果来自前面讲过的推理部分。我们还可以强调,邮件要包含客户提到的细节:

prompt = f"""
You are a customer service AI assistant.
Your task is to send an email reply to a valued customer.
Given the customer email delimited by ```, \
Generate a reply to thank the customer for their review.
If the sentiment is positive or neutral, thank them for \
their review.
If the sentiment is negative, apologize and suggest that \
they can reach out to customer service.
Make sure to use specific details from the review.
Write in a concise and professional tone.
Sign the email as `AI customer agent`.
Customer review: ```{review}```
Review sentiment: {sentiment}
"""

这里,老师们介绍了模型传入的temperature参数的作用——值越大,灵活程度和随机性越高:

image-20230504141253972

8. 聊天机器人(Chatbot)

前面我们提到的各种应用场景都是一次交互,聊天机器人则是多次交互。区别就是,在实现聊天机器人时,我们需要在每一次的请求中把历史聊天记录作为上下文传递给ChatGPT。

对此,我们封装一个新的请求函数:

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

在上面函数的基础上,我们再实现一个具有上下文收集功能的请求函数,自动请求并收集上下文:

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)

最终,该课程实现了一个Chatbot原型:

import panel as pn  # GUI
pn.extension()

panels = [] # collect display

context = [ {'role':'system', 'content':"""
You are OrderBot, an automated service to collect orders for a pizza restaurant. \
You first greet the customer, then collects the order, \
and then asks if it's a pickup or delivery. \
You wait to collect the entire order, then summarize it and check for a final \
time if the customer wants to add anything else. \
If it's a delivery, you ask for an address. \
Finally you collect the payment.\
Make sure to clarify all options, extras and sizes to uniquely \
identify the item from the menu.\
You respond in a short, very conversational friendly style. \
The menu includes \
pepperoni pizza  12.95, 10.00, 7.00 \
cheese pizza   10.95, 9.25, 6.50 \
eggplant pizza   11.95, 9.75, 6.75 \
fries 4.50, 3.50 \
greek salad 7.25 \
Toppings: \
extra cheese 2.00, \
mushrooms 1.50 \
sausage 3.00 \
canadian bacon 3.50 \
AI sauce 1.50 \
peppers 1.00 \
Drinks: \
coke 3.00, 2.00, 1.00 \
sprite 3.00, 2.00, 1.00 \
bottled water 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

上下文涉及不同的角色。其中,system项是最初的提示词,user项是用户的历史消息,assistant项是ChatGPT的历史消息。

9. 总结与思考

在这节课中,我们学习了ChatGPT提示词编写的指导原则和迭代式开发模式,还了解了ChatGPT总结归纳、推理、转换、扩展等实用功能,最后掌握了基于ChatGPT的聊天机器人的实现思路。

我认同吴恩达教授的观点,没有完美的提示词,只有适合具体场景的好提示词。希望本节课的知识能够帮助我们更好地利用ChatGPT,从日常琐碎事务中解脱出来,并探索更多的可能性。