02_提示词工程应用实践

90 阅读10分钟

什么是提示词模板?

提示词模板本质上跟平时大家使用的邮件模板、短信模板没什么区别,就是一个字符串模板,模板可以包含一组模板参数,通过模板参数值可以替换模板对应的参数。

一个提示词模板可以包含下面内容:

  • 发给大语言模型(LLM)的指令
  • 一组问答示例,以提醒AI以什么格式返回请求
  • 发给语言模型的问题

语言模型以文本作为输入 - 这个文本通常被称为提示词(prompt)。在开发过程中,对于提示词通常不能直接硬编码,不利于提示词管理,而是通过提示词模板进行维护,类似开发过程中遇到的短信模板、邮件模板等等。

提示词工程示意图

创建提示词模板(prompt template)

可以使用 PromptTemplate 类创建简单的提示词。提示词模板可以内嵌任意数量的模板参数,然后通过参数值格式化模板内容。

from langchain.prompts import PromptTemplate

# 定义一个提示模板,包含adjective和content两个模板变量,模板变量使用{}包括起来
prompt_template = PromptTemplate.from_template(
    "给我讲一个关于{content}的{adjective}笑话。"
)

# 通过模板参数格式化提示模板
result = prompt_template.format(adjective="冷", content="猴子")
print(result)

输出结果:

给我讲一个关于猴子的冷笑话。

聊天消息提示词模板(chat prompt template)

聊天模型(Chat Model)以聊天消息列表作为输入,这个聊天消息列表的消息内容也可以通过提示词模板进行管理。这些聊天消息与原始字符串不同,因为每个消息都与"角色(role)"相关联。

例如,在OpenAI的Chat Completion API中,Openai的聊天模型,给不同的聊天消息定义了三种角色类型分别是助手(assistant)、人类(human)或系统(system)角色:

  • 助手(Assistant) 消息指的是当前消息是AI回答的内容
  • 人类(user)消息指的是你发给AI的内容
  • 系统(system)消息通常是用来给AI身份进行描述

创建聊天消息模板例子

from langchain_core.prompts import ChatPromptTemplate

# 通过一个消息数组创建聊天消息模板
# 数组每一个元素代表一条消息,每个消息元组,第一个元素代表消息角色(也成为消息类型),第二个元素代表消息内容。
# 消息角色:system代表系统消息、human代表人类消息,ai代表LLM返回的消息内容
# 下面消息定义了2个模板参数name和user_input
chat_template = ChatPromptTemplate.from_messages(
    [
        ("system", "你是一位人工智能助手,你的名字是{name}。"),
        ("human", "你好"),
        ("ai", "我很好,谢谢!"),
        ("human", "{user_input}"),
    ]
)

# 通过模板参数格式化模板内容
messages = chat_template.format_messages(name="Bob", user_input="你的名字叫什么?")
print(messages)

输出结果会包含格式化后的聊天消息列表,每条消息包含角色和内容。

通常我们不会直接使用format_messages函数格式化提示模板(prompt templae)内容,而是交给Langchain框架自动处理。

MessagesPlaceholder

这个提示模板负责在特定位置添加消息列表。在上面的 ChatPromptTemplate 中,我们看到了如何格式化两条消息,每条消息都是一个字符串。但是,如果我们希望用户传入一个消息列表,我们将其插入到特定位置,该怎么办?这就是您使用 MessagesPlaceholder 的方式。

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

chat_template = ChatPromptTemplate.from_messages(
    [
        ("system", "你是一位人工智能助手,你的名字是Bob。"),
        MessagesPlaceholder(variable_name="history"),  # 这里可以插入多条历史消息
        ("human", "{user_input}"),
    ]
)

# 历史消息
history_messages = [
    {"role": "human", "content": "你好"},
    {"role": "ai", "content": "你好!我是Bob,有什么我能帮助你的吗?"}
]

# 使用模板生成消息
messages = chat_template.format_messages(history=history_messages, user_input="请告诉我今天的天气")
print(messages)

这将生成多条消息:一条系统消息,然后是我们传入的历史消息(一条human消息和一条ai消息),最后是一条新的human消息。如果我们传入了5条历史消息,那么总共会生成7条消息(系统消息加上传入的5条消息,再加上新的human消息)。这对于将一系列消息插入到特定位置非常有用。

另一种实现相同效果的替代方法是不直接使用 MessagesPlaceholder 类,而是:

from langchain_core.messages import HumanMessage, AIMessage, SystemMessage

messages = [
    SystemMessage(content="你是一位人工智能助手,你的名字是Bob。"),
    HumanMessage(content="你好"),
    AIMessage(content="你好!我是Bob,有什么我能帮助你的吗?"),
    HumanMessage(content="请告诉我今天的天气")
]

提示词追加示例(Few-shot prompt templates)

提示词中包含交互样本的作用是为了帮助模型更好地理解用户的意图,从而更好地回答问题或执行任务。小样本提示模板是指使用一组少量的示例来指导模型处理新的输入。这些示例可以用来训练模型,以便模型可以更好地理解和回答类似的问题。

使用示例集

创建示例集

下面定义一个examples示例数组,里面包含一组问答样例。

from langchain.prompts.few_shot import FewShotPromptTemplate
from langchain.prompts.prompt import PromptTemplate

examples = [
    {
        "question": "谁的寿命更长,穆罕默德·阿里还是艾伦·图灵?",
        "answer": 
        """
        这里需要跟进问题吗:是的。
        
        跟进:穆罕默德·阿里去世时多大?
        
        中间答案:穆罕默德·阿里去世时74岁。
        
        跟进:艾伦·图灵去世时多大?
        
        中间答案:艾伦·图灵去世时41岁。
        
        所以最终答案是:穆罕默德·阿里
        """
    },
    {
        "question": "craigslist的创始人是什么时候出生的?",
        "answer":
        """
        这里需要跟进问题吗:是的。
        
        跟进:craigslist的创始人是谁?
        
        中间答案:craigslist由Craig Newmark创立。
        
        跟进:Craig Newmark是什么时候出生的?
        
        中间答案:Craig Newmark于1952年12月6日出生。
        
        所以最终答案是:1952年12月6日
        """
    },
    {
        "question": "乔治·华盛顿的祖父母中的母亲是谁?",
        "answer":
        """
        这里需要跟进问题吗:是的。
        
        跟进:乔治·华盛顿的母亲是谁?
        
        中间答案:乔治·华盛顿的母亲是Mary Ball Washington。
        
        跟进:Mary Ball Washington的父亲是谁?
        
        中间答案:Mary Ball Washington的父亲是Joseph Ball。
        
        所以最终答案是:Joseph Ball
        """
    },
    {
        "question": "《大白鲨》和《皇家赌场》的导演都来自同一个国家吗?",
        "answer":
        """
        这里需要跟进问题吗:是的。
        
        跟进:《大白鲨》的导演是谁?
        
        中间答案:《大白鲨》的导演是Steven Spielberg。
        
        跟进:Steven Spielberg来自哪里?
        
        中间答案:美国。
        
        跟进:《皇家赌场》的导演是谁?
        
        中间答案:《皇家赌场》的导演是Martin Campbell。
        
        跟进:Martin Campbell来自哪里?
        
        中间答案:新西兰。
        
        所以最终答案是:不是
        """
    }
]

创建小样本示例的格式化程序

通过PromptTemplate对象,简单的在提示词模板中插入样例。

# 定义如何格式化示例
example_prompt = PromptTemplate(
    input_variables=["question", "answer"],
    template="问题: {question}\n{answer}"
)

# 打印一个格式化的示例
print(example_prompt.format(**examples[0]))

返回:

问题: 谁的寿命更长,穆罕默德·阿里还是艾伦·图灵?

        这里需要跟进问题吗:是的。
        
        跟进:穆罕默德·阿里去世时多大?
        
        中间答案:穆罕默德·阿里去世时74岁。
        
        跟进:艾伦·图灵去世时多大?
        
        中间答案:艾伦·图灵去世时41岁。
        
        所以最终答案是:穆罕默德·阿里

将示例和格式化程序提供给FewShotPromptTemplate

通过FewShotPromptTemplate对象,批量插入示例内容。

# 定义少量示例的提示模板
few_shot_prompt = FewShotPromptTemplate(
    examples=examples,
    example_prompt=example_prompt,
    prefix="以下是一些问题和详细的回答示例:\n\n",
    suffix="\n\n问题: {input}\n",
    input_variables=["input"],
    example_separator="\n\n"
)

# 使用模板生成提示
print(few_shot_prompt.format(input="长城有多长?"))

返回:

以下是一些问题和详细的回答示例:

问题: 谁的寿命更长,穆罕默德·阿里还是艾伦·图灵?

        这里需要跟进问题吗:是的。
        
        跟进:穆罕默德·阿里去世时多大?
        
        中间答案:穆罕默德·阿里去世时74岁。
        
        跟进:艾伦·图灵去世时多大?
        
        中间答案:艾伦·图灵去世时41岁。
        
        所以最终答案是:穆罕默德·阿里

问题: craigslist的创始人是什么时候出生的?

        这里需要跟进问题吗:是的。
        
        跟进:craigslist的创始人是谁?
        
        中间答案:craigslist由Craig Newmark创立。
        
        跟进:Craig Newmark是什么时候出生的?
        
        中间答案:Craig Newmark于1952年12月6日出生。
        
        所以最终答案是:1952年12月6日

问题: 乔治·华盛顿的祖父母中的母亲是谁?

        这里需要跟进问题吗:是的。
        
        跟进:乔治·华盛顿的母亲是谁?
        
        中间答案:乔治·华盛顿的母亲是Mary Ball Washington。
        
        跟进:Mary Ball Washington的父亲是谁?
        
        中间答案:Mary Ball Washington的父亲是Joseph Ball。
        
        所以最终答案是:Joseph Ball

问题: 《大白鲨》和《皇家赌场》的导演都来自同一个国家吗?

        这里需要跟进问题吗:是的。
        
        跟进:《大白鲨》的导演是谁?
        
        中间答案:《大白鲨》的导演是Steven Spielberg。
        
        跟进:Steven Spielberg来自哪里?
        
        中间答案:美国。
        
        跟进:《皇家赌场》的导演是谁?
        
        中间答案:《皇家赌场》的导演是Martin Campbell。
        
        跟进:Martin Campbell来自哪里?
        
        中间答案:新西兰。
        
        所以最终答案是:不是

问题: 长城有多长?

使用示例选择器

将示例提供给ExampleSelector

这里重用前一部分中的示例集和提示词模板(prompt template)。但是,不会将示例直接提供给FewShotPromptTemplate对象,把全部示例插入到提示词中,而是将它们提供给一个ExampleSelector对象,插入部分示例。

这里我们使用SemanticSimilarityExampleSelector类。该类根据与输入的相似性选择小样本示例。它使用嵌入模型计算输入和小样本示例之间的相似性,然后使用向量数据库执行相似搜索,获取跟输入相似的示例。

提示:这里涉及向量计算、向量数据库,在AI领域这两个主要用于数据相似度搜索,例如:查询相似文章内容、相似的图片、视频等等,这里先简单了解下就行。

from langchain.prompts.example_selector import SemanticSimilarityExampleSelector
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings

example_selector = SemanticSimilarityExampleSelector.from_examples(
    # 这是可供选择的示例列表。
    examples,
    # 这是用于生成嵌入的嵌入类,该嵌入用于衡量语义相似性。
    OpenAIEmbeddings(),
    # 这是用于存储嵌入和执行相似性搜索的VectorStore类。
    Chroma,
    # 这是要生成的示例数。
    k=1
)

# 选择与输入最相似的示例。
question = "乔治·华盛顿的父亲是谁?"
selected_examples = example_selector.select_examples({"question": question})
print(f"最相似的示例:{question}")
for example in selected_examples:
    print("\n")
    for k, v in example.items():
        print(f"{k}{v}")

这里匹配了跟问题相似的例子,下面是返回示例:

最相似的示例:乔治·华盛顿的父亲是谁?

question:乔治·华盛顿的祖父母中的母亲是谁?
answer:
        这里需要跟进问题吗:是的。
        
        跟进:乔治·华盛顿的母亲是谁?
        
        中间答案:乔治·华盛顿的母亲是Mary Ball Washington。
        
        跟进:Mary Ball Washington的父亲是谁?
        
        中间答案:Mary Ball Washington的父亲是Joseph Ball。
        
        所以最终答案是:Joseph Ball

将示例选择器提供给FewShotPromptTemplate

最后,创建一个FewShotPromptTemplate对象。根据前面的example_selector示例选择器,选择一个跟问题相似的例子。

# 定义少量示例的提示模板,但这次使用示例选择器
few_shot_prompt_with_selector = FewShotPromptTemplate(
    example_selector=example_selector,  # 使用示例选择器而不是示例列表
    example_prompt=example_prompt,
    prefix="以下是一些问题和详细的回答示例:\n\n",
    suffix="\n\n问题: {input}\n",
    input_variables=["input"],
    example_separator="\n\n"
)

# 使用模板生成提示
print(few_shot_prompt_with_selector.format(input="乔治·华盛顿的父亲是谁?"))

返回:

以下是一些问题和详细的回答示例:

问题: 乔治·华盛顿的祖父母中的母亲是谁?

        这里需要跟进问题吗:是的。
        
        跟进:乔治·华盛顿的母亲是谁?
        
        中间答案:乔治·华盛顿的母亲是Mary Ball Washington。
        
        跟进:Mary Ball Washington的父亲是谁?
        
        中间答案:Mary Ball Washington的父亲是Joseph Ball。
        
        所以最终答案是:Joseph Ball

问题: 乔治·华盛顿的父亲是谁?