[Survey]PromptEngineering

1. 什么是PromptEngineering

所谓prompt工程,就是让LLM模型清晰准确的知道你要干什么,引导模型充分的发掘它的能力上限,类比到人与人之间,就是提升沟通的效率。OpenAI提出了prompt工程的6条原则[1],在这篇文档里,按照个人的理解,将这部分按照适用场景分成用户和开发者两个部分,一共6条(与openai的6条顺序有所不同):

用户策略:

  • Write clear instructions(写出清晰的指令)
  • Provide reference text(提供参考文本)
  • Give the model time to “think”(给模型时间“思考”)

开发者策略:

  • Split complex tasks into simpler subtasks(将复杂的任务拆分为更简单的子任务)
  • Use external tools(使用外部工具)
  • Test changes systematically(系统地测试变更)

这几个原则概括了一个好的prompt应该是什么样子的。实际应用中,prompt工程能否有效,很大程度上依赖LLM的指令追随能力,要不然就变成 了,对着聋子打鼓–充耳不闻,说了也白说……

2. 理论派

详细说一下这6条原则各自包含的内容。

2.1 清晰的指令

这是最重要的一个原则,包括下面6条内容:

  • 把话说详细,在查询中包含详细信息以获得更相关的答案(不要让模型猜,详细且准确的表述出你的需求),举例如下:

    • bad:如何在Excel中添加数字?

    • good:如何在Excel中添加一行花费金额?我想自动为整张表的行执行此操作,所有总计都在右侧名为“总计”的列中结束

  • 让模型充当某个角色(角色和要回答的问题要适配,例如数学老师和装修,这种不搭配的角色会带来更差的结果),这是一个广为人知的策略,从生成的角度来说,模型预设某个角色,模型会更容易使用该领域的知识,有助于问题的解决。在同一个生成参数下,使用34B模型进行生成。

    • 测试1:

      • Q:你是一个专业,聪明,经验丰富的数学老师。100+180=?
      • A:答案是280。
    • 测试2:

      • Q:你是一个专业,聪明,经验丰富的汽车司机。100+180=?

      • A:答案是200。

  • 使用分隔符清楚地指示输入的不同部分。这样可以让模型准确的知道什么地方是什么内容,举例如下:

    • Q =下面用户将为你提供三重引号的文本(‘’’文本内容’’’)。用一个带有’摘要:’前缀的一句话总结这段文本。
  • 指定完成任务所需的步骤(优化COT,让模型逐步生成,任务如果可以拆分,那最好进行拆分成更简单的单步任务),这是一个被广泛应用的策略,个人一直认为Agent目前的实现思路,本质上就是这个策略的深挖。

    1
    2
    3
    使用以下分步说明响应用户输入。  
    第1步-用户将为您提供三重引号的文本。用一个带有“摘要:”前缀的句子总结这段文本。 
    第2步-将第1步中的摘要翻译成西班牙语,前缀为“翻译:”
  • 提供例子, oneshot 或者 fewshot

    • Q=按照下面的给出的示例风格来写’冬天’的文章:’落霞与孤鹜齐飞
  • 指定所输出长度(在中文场景中,尽量使用 一句话,两个段落 这种说法,而不要使用100个字这种表述,这种只能给出个大概,模型无法准确的生成),举例如下

    1
    2
    使用一句话总结下面给出的文本内容。
    “文本插入这里”

2.2 提供参考上下文

  • 模型使用参考文本来回答。

    例如RAG方案,或手动提供外部文档进行回答,这样会减少模型的幻觉,与此同时,对模型的文本检索能力提出了要求。

    1
    2
    3
    4
    5
    使用提供的由三重引号引起来的文章来回答问题。如果在文章中找不到答案,请写“我找不到答案”。 
    """<在此插入文档>"""
    """<在此插入文档>"""
    问题:<在此插入问题>
    回答:
  • 生成引文的格式

    要求模型通过引用所提供文档中的段落来为其答案添加引用。可以提高正确性,增加可验证性,并能够减少幻觉。

    1
    2
    3
    你将得到一份由三重引号和一个问题分隔的文件。你的任务是只用提供的文件回答问题,并引用用于回答问题的文件的段落。如果文件不包含回答这个问题所需的信息,那么只需写:“信息不足”。如果提供了问题的答案,则必须用引文注释。使用以下格式引用相关段落({“引文”})
    """<在此处插入文档>"""
    问题:<在此处插入问题>

2.3 让模型思考(think step by step)

这是一个经常被提到的策略,是从COT的角度提出的优化策略,逐步推导,给出答案。

  • 与其直接让模型判断,不如让模型得出结论之前找出自己的解决方案

    比如扔个数学题给大模型,直接让LLM判断对错与否,会发现结果很随机。但是如果先让LLM自己做一遍,再去判断对与不对,结果就会提升 非常多了。

    假如是一个判断学生回答的题目是否正确的场景,prompt可以这样设计:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    PROMPT='''首先找出你自己的解决方案。然后将你的解决方案与学生的解决方案进行比较,评估学生的解决方案是否正确。在你自己做这道题之前,不要决定学生的解决方案是否正确。
    问题陈述:我正在建造一个太阳能装置,我需要帮助解决财务问题。
    -土地成本100美元/平方英尺
    -我可以以250美元/平方英尺的价格购买太阳能电池板
    -我协商了一份维护合同,每年将花费我10万美元,另外10美元/平方英尺
    作为平方英尺数的函数,第一年运营的总成本是多少。

    学生解:设x为装置的大小,单位为平方英尺。
    1.土地成本:100倍
    2.太阳能电池板成本:250x
    3、维护成本:10万+100x
    总成本:100x+250x+100,000+100x=450x+100,000
    '''

    ASSISTANT(GPT4)='''我的解:设装置的面积为y平方英尺。
    1. 土地成本:100y
    2. 太阳能电池板成本:250y
    3. 维护成本:100,000+10y
    总成本 = 100y + 250y + 100,000 + 10y = 360y + 100,000

    比较学生的解与我的解,发现学生在计算维护成本时,将每平方英尺的费用误写为100美元,实际上应该是10美元,所以学生的解答存在错误。'''

在gpt3.5和aquila34b上使用该prompt进行测试 ,基本以失败告终,gpt4可以成功生成

  • 使用内心独白或一系列查询来隐藏模型的推理过程(教育场景?)

    模型在回答特定问题之前,详细推理问题有时很重要。对于某些场景,模型用来得出最终答案的推理过程不适合与用户共享。例如,在教育辅导相关的场景中,我们可能希望鼓励学生自己找出答案,但是模型关于学生解决方案的推理过程可能会向学生揭示答案,所以这种情况下,模型的推导过程要对用户隐藏。

    内心独白是一种可以用来缓解这种情况的策略。内心独白的想法是:指示模型将应对用户隐藏的部分输出放入结构化的特定格式中,这可以是这部分的解析变得容易。然后在将输出呈现给用户之前,只有一部分输出是可见的,对用户隐藏的部分对外不可见。比如下面的查询,告诉模型如何逐步完成这次查询

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    PROMPT='''
    按照以下步骤回答用户查询。

    第一步-首先找出你自己的解决方案。不要依赖学生的解决方案,因为它可能是不正确的。将这一步的所有工作都用三重引号括起来 (""").

    第2步-将您的解决方案与学生的解决方案进行比较,并评估学生的解决方案是否正确。将此步骤的所有工作用三重引号括起来 (""").

    第3步-如果学生犯了错误,确定你可以在不泄露答案的情况下给学生什么提示。将这一步的所有工作用三重引号括起来 (""").

    第4步-如果学生犯了错误,请向学生提供上一步的提示(三重引号之外)。而不是写“步骤4-…”写“提示:”。

    问题陈述:<插入问题陈述>

    学生解决方案:<插入学生解决方案>'''

    当然也可以将上面的四步拆分成4个调用来完成,前面几次对用户隐藏让用户无法感知,这两个具体哪个表现更好,直觉上分成四次调用可能更准确[7]

  • 询问模型在之前的过程中是否遗漏了任何内容(多轮对话后续追问)

    长文本问答中常用。比如我们给了一个文档,要让大模型模型来列出与一个特定问题相关的信息。如果源文档很大,模型通常会过早停止或者无法列出所有相关信息。在这种情况下,通过使用后续的prompt让模型查找之前错过的任何相关信息,通常可以获得更好的性能。例如下面这种查询场景

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    SYSTEM='''您将获得一份由三重引号分隔的文档。您的任务是选择与以下问题相关的摘录:“人工智能历史上发生了哪些重大范式转变。”

    确保摘录包含解释它们所需的所有相关上下文——换句话说,不要提取缺少重要上下文的小片段。提供JSON格式的输出,如下所示:

    [{“摘录”:“……”},

    {“摘录”:“……”}]
    '''
    USER="""<insert document here>"""

    ASST="""[{"摘录":"模型在这里写摘录"},

    {"摘录":"模型在这里写另一个摘录"}]"""

    USER="""还有更多相关的摘录吗?注意不要重复摘录。还要确保摘录包含解释它们所需的所有相关上下文——换句话说,不要摘录缺少重要上下文的小片段。
    """

2.4 将复杂任务拆分为更简单的子任务

  • [开发者策略] 使用意图分类来识别与用户查询最相关的指令,这条策略直接说不好理解。

    举个例子:在客服场景下,直接问模型“我连不上网了”模型需要从查找整个知识库,去匹配连不上网了该怎么办。将这个任务进行拆解,第一步根据用户的问题,来判断这个问题对应的是预先设定好的哪个类别的服务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    SYSTEM='''您将获得客户服务查询。将每个查询分类为主要类别和次要类别。以json格式提供带有键的输出:主要和次要。

    主要类别:计费、技术支持、账户管理或一般查询。

    计费二级类别:
    -取消订阅或升级

    技术支持二级类别:
    -故障排除

    账户管理二级类别:
    -密码重置

    一般查询二级类别:
    -产品信息
    -和人类说话'''

    USER='''我连不上网络怎么办'''

    这一步会对输入的问题进行意图判断,假设认为用户要找”技术支持/故障排除”,接下来根据第一步得到的结果找到该类别下的内容,将该内容作为上下文,用来回答用户的问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    SYSTEM='''您将获得需要在技术支持上下文中进行故障排除的客户服务查询。通过以下方式帮助用户:

    -请他们检查所有进出路由器的电缆是否已连接。请注意,随着时间的推移,电缆松动是很常见的。
    -如果所有电缆都已连接并且问题仍然存在,请询问他们使用的路由器型号
    -现在您将建议他们如何重新启动设备:
    --如果型号是MTD-327J,建议他们按下红色按钮并按住5秒钟,然后等待5分钟再测试连接。
    -如果客户的问题在重新启动设备并等待5分钟后仍然存在,请通过输出{“请求IT支持”}将他们连接到IT支持。
    -如果用户开始问与此主题无关的问题,请确认他们是否愿意结束当前关于故障排除的聊天并根据以下方案对他们的请求进行分类:

    <技术支持/故障排除>'''

    USER=我连不上网络怎么办

    通过将任务拆解,可以有效提升准确率并减少上下文的长度。

  • [开发者策略] 长文本对话,总结或者过滤之前的对话或者使用RAG方案

    因为模型具有固定的上下文长度,因此用户和助手之间的对话无法无限期地继续,一般的解决办法是:

    • 总结对话中的历史记录。一旦输入的大小达到预定的阈值长度,这可能会触发总结部分对话的查询,并且先前对话的摘要可以作为系统消息的一部分包括在内;

    • 或者,可以在整个对话过程中在后台异步总结之前的对话;

    • 或者,把过去的所有聊天记录存成向量库,后续跟用户对话的时候动态查询embedding[5],这里设计要如何进行特征压缩和信息嵌入;

  • [开发者策略] 分段总结长文档并递归构建完整摘要

    让大模型总结一本书,肯定是超Token上限了,所以可以使用一系列查询来总结文档的每个部分。章节摘要可以连接和总结,生成摘要的摘要。这个过程可以递归地进行,直到总结整个文档。openai对实现方案进行了详细的阐述[6]。

    Our model works by first summarizing small sections of a book, then summarizing those summaries into a higher-level summary, and so on.

2.5 使用外部工具

对于一些数学,查天气之类的问题,外部的工具可以获得稳定且正确的结果。LLM擅长的是逻辑推理,擅长写公式或者function但是并不是擅长具体执行。那最优的办法是,让LLM来告知该怎么做,拿到代码或者执行语句之后使用专业的软件去执行,只让大模型做一个答案组装的工作就够了。

  • RAG技术的使用,将信息检索的工作外放给专门的向量模型来执行(不在这里赘述)
  • 使用代码执行来进行更准确的计算或调用外部API
  • [开发者策略]给模型提供特定的功能[9]

2.6 [开发者策略]系统的测试变更[10]

一条新指令或一个新设计是让你的系统变得更好还是更坏。看几个例子可能会暗示哪个更好,但在样本量较小的情况下,很难区分真正的改进还是随机的运气。也许这种变化有助于某些输入的性能,但会损害其他输入的性能[10]。那么如何评估呢?

3. 实践派

3.1 生成引文格式

在基于RAG的请求中,让模型能够数据出带引文编号的格式。这个考验的是prompt编写能力和模型的指令追随能力,此外为了提升模型的生产准确性,会使用fewshot。在论文[2]中给出了好用的prompt模板。

  • Instruction for VANILLA.
1
Instruction: Write an accurate, engaging, and concise answer for the given question using only the provided search results (some of which might be irrelevant) and cite them properly. Use an unbiased and journalistic tone. Always cite for any factual claim. When citing several search results, use [1][2][3]. Cite at least one document and at most three documents in each sentence. If multiple documents support the sentence, only cite a minimum sufficient subset of the documents.
  • Short instruction for VANILLA.
1
Instruction: Write a high-quality answer for the given question using only the provided search results and cite them properly using [1][2][3].
  • VANILLA基础上扩展出中文模板

    在下面的模板中可以看到,坚定的贯穿了理论中的各种prompt策略:准确的表达,给出例子。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    你是一个专业,聪明的汽车助手。使用提供的参考内容(其中一些可能无关,参考内容在两个'==='之间)编写用户给定问题的准确、简洁的答案,并确保正确引用。要使用无偏见的新闻语气。对任何事实主张都应提供引用。在引用多个搜索结果时,使用[1][2][3]。在每个句子中,至少引用1个文档,最多引用3个文档。如果多个文档支持这个句子,只引用最少的足够的文档子集。如果连续的句子都引用同一个文档,那么只在最后1个句子处引用文档。如果所有的参考内容都与给定的问题不相关,那么不依赖参考内容进行回答。

    按照下面给出的两个例子进行生成

    示例1开始
    参考内容如下:
    ===
    参考内容[0] (标题:提示信息;摘要:排放控制系统因发动机型号不同会有差异。;作者:无):排放控制系统因发动机型号不同会有差异,具体配备请以实车为准。禁止对发动机或排放控制系统的任何部件进行改装。
    参考内容[1] (标题:无;摘要:涡轮增压和自然吸气发动机的动力表现差异,以及昂科威和自由光在外观和尺寸上的差异。;作者:无):涡轮PK自吸 有人觉得带“T”的发动机动力表现够强劲,再加上不大的排气量也能保证较低的油耗;又有人觉得涡轮增压发动机或多或少会在低转速时遇到动力迟滞,不如自然吸气发动机来的平顺。
    ===
    参考内容全部结束。

    用户给定的问题:排放控制系统是否会因为发动机型号不同而有差异?各有什么优势?
    回答:排放控制系统会因发动机型号不同而有差异[0]。例如,涡轮增压发动机动力表现强劲,排气量小可以保证较低的油耗;但可能在低转速时遇到动力迟滞。相比之下,自然吸气发动机运行更平顺[1]。具体的设备配置需要以实车为准[0]。另外,对发动机或排放控制系统的任何部件进行改装是被禁止的[0]。
    示例1结束

    示例2开始
    参考内容如下:
    ===
    参考内容[0] (标题:提示信息;摘要:排放控制系统因发动机型号不同会有差异。;作者:无):排放控制系统因发动机型号不同会有差异,具体配备请以实车为准。禁止对发动机或排放控制系统的任何部件进行改装。
    参考内容[1] (标题:无;摘要:涡轮增压和自然吸气发动机的动力表现差异。;作者:无):涡轮PK自吸 有人觉得带“T”的发动机动力表现够强劲,再加上不大的排气量也能保证较低的油耗;又有人觉得涡轮增压发动机或多或少会在低转速时遇到动力迟滞
    ===
    参考内容全部结束。

    燃油车有什么优势
    回答:燃油车可能的优势包括更广泛的加油站网络、更长的驾驶距离(在需要加油/充电前)和更短的“加油”时间。然而,具体的优势可能取决于特定的车型和驾驶条件。
    示例2结束

    参考内容如下:
    ===
    {refers}
    ===
    参考内容全部结束。

    用户给定的问题:{question}
    回答:
    """

3.2 使用LLM来作为evaluator

在很多场景下会使用GPT模型作为evaluator,来对比来对于同一个问题的两个回答AB的优劣,在这种AB对比的场景下,为了消除位置偏见,在[3]给出了一个模板,用来解决这种情况。对于一条数据,会进行两个验证,分别是A在前和B在前,使用GPT模型对A,B进行两次打分,取两次打分的均值。prompt的设计可以参考下面代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def gen_prompt(ques, ans1, ans2):
    sys_prompt = 'You are a helpful and precise assistant for checking the quality of the answer.'
    prompt_template = "[Question]\n{question}\n\n[The Start of Assistant 1's Answer]\n{answer_1}\n[The End of Assistant 1's Answer]\n\n[The Start of Assistant 2's Answer]\n{answer_2}\n[The End of Assistant 2's Answer]\n\n[System]\n{prompt}\n"
    default_prompt =  """We would like to request your feedback on the performance of two AI assistants in response to the user question displayed above.
    Please rate the helpfulness, relevance, accuracy, level of details of their responses.

    Each assistant receives an overall score on a scale of 1 to 10, where a higher score indicates better overall performance.
    Please first provide a comprehensive explanation of your evaluation, avoiding any potential bias and ensuring that the order in which the responses were presented does not affect your judgment.
    Then, output two lines indicating the scores for Assistant 1 and 2, respectively.

    Output with the following format:
    Evaluation evidence: <your evluation explanation here>
    Score of the Assistant 1: <score>
    Score of the Assistant 2: <score>"""
    return sys_prompt, prompt_template.format(question=ques, answer_1=ans1, answer_2=ans2, prompt=default_prompt)

这个模板可以扩展成只包含一个答案的评估,代码如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def gen_prompt(ques, ans1):
    sys_prompt = "You are a helpful and precise assistant for checking the quality of the answer."
    prompt_template = "[Question]\n{question}\n\n[The Start of Assistant  Answer]\n{answer_1}\n[The End of Assistant  Answer]\n\n[System]\n{prompt}\n"

    default_prompt = """We would like to request your feedback on the performance of two AI assistants in response to the user question displayed above.
    Please rate the helpfulness, relevance, accuracy, level of details of their responses.

    The assistant receives an overall score on a scale of 1 to 10, where a higher score indicates better overall performance.
    Please first provide a comprehensive explanation of your evaluation, avoiding any potential bias and ensuring that the order in which the responses were presented does not affect your judgment.
    Then, output one line indicating the score for Assistant's response.

    Output with the following format:
    Evaluation evidence:
    Score of the Assistant : """
    return sys_prompt, prompt_template.format(
        question=ques, answer_1=ans1, prompt=default_prompt
    )

3.3 query改写

这个任务比较简单,要标明改写的用于,例如是用来做搜索,还是用来做主题生成。列举一个rag-fusion中来进行query扩充的prompt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
rag_fusion_query_rewrite_prompt = """你是一个聪明的智能助手,可以根据单个输入查询语句生成多个与它相关的搜索查询语句。下面根据用户的问题生成3个用于查询的相关问题。要求逐行显示。

约定生成的回答格式为:
1. answer1
2. answer2
...
N. answerN

用户的问题为:{question}
生成回答:
"""

## from https://github.com/Raudaschl/rag-fusion/blob/master/main.py#L16
rag_fusion_prompt_en="""You are a helpful assistant that generates multiple search queries based on a single input query.

Generate multiple search queries related to: {question}
OUTPUT (4 querys):
"""

3.4 …

参考文献

  1. OpenAI PromptEngineering:https://platform.openai.com/docs/guides/prompt-engineering
  2. OpenAI的官方Prompt工程指南详解https://mp.weixin.qq.com/s/jOU2qT5o88tuZC1p6vLkJw
  3. ALCE: https://arxiv.org/pdf/2305.14627.pdf
  4. FairEval:https://github.com/i-Eval/FairEval
  5. https://platform.openai.com/docs/guides/prompt-engineering/tactic-use-embeddings-based-search-to-implement-efficient-knowledge-retrieval
  6. https://openai.com/research/summarizing-books
  7. https://platform.openai.com/docs/guides/prompt-engineering/tactic-use-inner-monologue-or-a-sequence-of-queries-to-hide-the-model-s-reasoning-process
  8. https://www.promptingguide.ai/zh/techniques
  9. https://cookbook.openai.com/examples/how_to_call_functions_with_chat_models
  10. https://platform.openai.com/docs/guides/prompt-engineering/strategy-test-changes-systematically
赏杯咖啡!