3.3 聊天提示模板
提示模板是在使用语言模型时用于生成输入文本的结构化模板,通常用于指导模型生成符合特定格式要求的文本。在 LangChain 中,提示模板是一个灵活的工具,可以用于各种场景,包括聊天对话、问题回答、任务指导等。提示模板的主要特征和组成部分如下所示。
- 模板字符串(Template String):这是提示模板的核心,它定义了生成的文本的基本结构。模板字符串中通常包含占位符或格式化标记,用于插入变量值或执行特定的文本格式化操作。
- 输入变量(Input Variables):这些是在模板字符串中使用的占位符,用于表示输入的变量。在生成文本时,输入变量将被替换为实际的数值或文本。例如,一个聊天对话的提示模板可能包含名为“user_message”的输入变量,用于表示用户的输入消息。
- 部分变量(Partial Variables):这些是模板的一部分,在生成文本时可以被预先填充为特定的值。部分变量通常用于那些在模板使用之前就已经确定了值的情况。例如,如果模板中需要包含当前日期,那么可以使用部分变量将日期预先填充进模板中。
- 模板方法(Template Methods):这些是一些用于对模板执行操作的方法。例如,格式化模板字符串、执行部分填充、生成最终的文本等。模板方法使得可以在模板的基础上执行各种操作,以满足具体的需求。
- 格式化(Formatting):格式化是将模板字符串中的占位符替换为实际数值或文本的过程。这一过程通常由模板方法或模板引擎自动完成,但有时也可以手动执行。
提示模板在自然语言生成任务中具有广泛的应用,可以帮助开发者快速构建符合要求的文本生成系统,并且使得文本生成过程更加灵活和可控。
3.3.1 BaseChatPromptTemplate介绍
在LangChain中,langchain_core.prompts.chat.BaseChatPromptTemplate 是一个基类,用于创建和管理聊天提示模板。类BaseChatPromptTemplate提供了构建聊天机器人对话系统的基础结构和方法,可以被继承和扩展以满足不同的应用场景。BaseChatPromptTemplate可以被子类化以创建不同类型的聊天模型提示,其主要成员方法如下所示。
- abatch 和 abatch_as_completed:这些方法用于并行处理多个输入,适用于提高聊天系统的吞吐量。
- aformat 和 aformat_messages:这些方法用于格式化聊天模板和消息,将变量插入到模板中以生成最终的聊天消息。
- aformat_prompt 和 invoke:这些方法用于创建提示值和调用聊天模型,生成基于模板的聊天回复。
- assign:允许为这个可运行对象分配新的字段,返回一个新的可运行对象。
- astream 和 astream_events:这些方法用于生成关于聊天模型执行进度的实时信息流。
- partial:返回提示模板的部分实例,这在不需要完整输入时非常有用。
- save:将提示模板保存到文件中,便于后续使用或分享。
3.3.2 少量示例提示模板
在LangChain中,FewShotChatMessagePromptTemplate 是用于在聊天模型中执行少量示例提示的模板。类FewShotChatMessagePromptTemplate允许创建包含前缀消息、示例消息和后缀消息的聊天提示模板,这些消息可以用于构建包含中间示例的对话,例如展示一系列的问答对。
FewShotChatMessagePromptTemplate的主要特点如下所示。
- 少数示例支持:能够根据输入动态选择示例,或者从固定列表中生成示例。
- 灵活的结构:生成的提示模板具有高度的结构化,包括系统消息、人类消息和 AI 消息。
- 示例格式化:可以使用 example_prompt 对每个示例进行格式化,以便将其插入到聊天提示中。
- 元数据和追踪:支持使用元数据和标签来追踪和记录聊天提示的详细信息。
FewShotChatMessagePromptTemplate包含的主要成员方法如下所示。
- __init__:这是FewShotChatMessagePromptTemplate的构造方法,用于初始化 FewShotChatMessagePromptTemplate 实例。它接受参数,如示例数据、用于格式化每个示例的提示模板等。
- format:将输入变量格式化为字符串,使用提供的示例和 example_prompt 来构建最终的聊天提示字符串。
- format_messages:与 format 类似,但返回的是一个消息列表,每个消息都是格式化后的聊天消息的一部分。
- aformat_prompt:异步方法,用于创建 PromptValue 对象,这可以用于进一步的处理或传递给语言模型。
- abatch:异步方法,使用 asyncio.gather 并行处理一批输入,适用于 I/O 绑定的任务。
- abatch_as_completed:异步方法,异步地并行处理一批输入,并在每个结果完成时生成它们。
- assign:允许为这个可运行对象分配新的字段,并返回一个新的可运行对象。
- save:将提示模板保存到文件中,便于后续使用或分享。
- json:生成模型的 JSON 表示,包括包含和排除参数的选项。
- to_json:序列化可运行对象到 JSON。
- pretty_print:提供一个人类可读的表示形式。
- schema 和 schema_json:生成模型的 JSON 模式定义。
例如下面是一个使用 FewShotChatMessagePromptTemplate 的例子,实现了一个少量示例提示的模板。
实例3-10:实现了一个少量示例提示的模板(源码路径:codes\3\PromptTemplate01.py)
实例文件PromptTemplate01.py的具体实现代码如下所示。
from langchain.prompts import ChatPromptTemplate, FewShotChatMessagePromptTemplate
# 定义示例列表
examples = [
{"input": "猫的叫声是什么?", "output": "喵喵"},
{"input": "狗的叫声是什么?", "output": "汪汪"},
]
# 定义每个示例的模板
example_prompt = ChatPromptTemplate.from_messages(
[
("human", "{input}"),
("ai", "{output}"),
]
)
# 创建 FewShotChatMessagePromptTemplate 对象
few_shot_prompt = FewShotChatMessagePromptTemplate(
example_prompt=example_prompt,
examples=examples,
)
# 格式化输入值并生成最终的提示文本
formatted_prompt = few_shot_prompt.format(input="猫的叫声是什么?")
print(formatted_prompt)
在上述代码中,创建了一个 FewShotChatMessagePromptTemplate 对象,其中包含了一些关于猫和狗的示例。然后,它使用输入值 "猫的叫声是什么?" 格式化了最终的提示文本,并打印出来。执行后会输出:
Human: 猫的叫声是什么?
AI: 喵喵
Human: 狗的叫声是什么?
AI: 汪汪
再看下面的例子,使用了一个包含多个问题和答案的样本集合。每个样本都是一个字典,包含了一个问题和相应的答案。
实例3-1:创建一个基于少量样本的对话提示模板(源码路径:codes\3\PromptTemplate02.py)
本实例使用 FewShotPromptTemplate 创建了一个基于少量样本的对话提示模板,实例文件PromptTemplate02.py的具体实现代码如下所示。
from langchain.prompts.prompt import PromptTemplate
from langchain.prompts.few_shot import FewShotPromptTemplate
# 定义 few-shot 示例
examples = [
{
"question": "马云的公司是什么?",
"answer": "马云的公司是阿里巴巴。",
},
{
"question": "成龙的代表作是哪部电影?",
"answer": "成龙的代表作是《警察故事》。",
},
{
"question": "李嘉诚是哪个国家的企业家?",
"answer": "李嘉诚是中国的企业家。",
},
]
# 定义格式化器
example_prompt = PromptTemplate(
input_variables=["question", "answer"], template="问题:{question}\n答案:{answer}"
)
# 创建 FewShotPromptTemplate 对象
prompt = FewShotPromptTemplate(
examples=examples,
example_prompt=example_prompt,
suffix="问题:{input}",
input_variables=["input"],
)
# 使用输入来格式化最终的提示文本
print(prompt.format(input="成龙的代表作是哪部电影?"))
上述代码的实现流程如下所示:
- 首先,创建了样本集合,包含了一些常见问题和它们的答案,例如马云的公司是什么,成龙的代表作是哪部电影等等。
- 接下来,创建了一个 PromptTemplate 对象,用于将每个样本格式化成一个问题和答案的字符串。然后,我们使用这个样本集合和格式化器创建了一个 FewShotPromptTemplate 对象。
- 最后,使用这个 FewShotPromptTemplate 对象来格式化一个特定的输入,并输出相应的问题和答案。在这个例子中,我们使用了问题“成龙的代表作是哪部电影?”作为输入,程序输出了相应的答案。执行后会输出:
问题:马云的公司是什么?
答案:马云的公司是阿里巴巴。
问题:成龙的代表作是哪部电影?
答案:成龙的代表作是《警察故事》。
问题:李嘉诚是哪个国家的企业家?
答案:李嘉诚是中国的企业家。
问题:成龙的代表作是哪部电影?
3.3.3 部分填充模板
部分提示模板(Partial prompt templates)是一种在创建提示模板时,可以传入部分变量值的技术。这种技术使得在提示模板中只需要传入部分变量值,而不是所有变量值,从而提供了更灵活的使用方式。
LangChain 支持两种部分提示模板的创建方式,一种是通过字符串值进行部分格式化,另一种是通过返回字符串值的函数进行部分格式化。
1. 使用字符串值进行部分格式化
这种方式的常见用法是当获取到某些变量值,但还没有获取到全部变量值时。假设有一个提示模板,需要两个变量 foo 和 baz。如果早先获取到了 foo 的值,但稍后才获取到 baz 的值,那么等待直到两个变量都获取到再传入模板可能会有些麻烦。相反,可以使用 foo 的值部分化提示模板,然后继续传递部分化的提示模板,只需传入剩余的变量即可。例如下面的演示代码:
from langchain.prompts import PromptTemplate
# 创建一个完整的提示模板
prompt = PromptTemplate.from_template("{foo}{bar}")
# 使用 foo 的值部分化提示模板
partial_prompt = prompt.partial(foo="foo")
# 格式化部分化的提示模板并传入剩余的变量
print(partial_prompt.format(bar="baz"))
输出结果为:
foobaz
也可以直接初始化提示模板时就传入部分化的变量,例如下面的演示代码:
prompt = PromptTemplate(
template="{foo}{bar}", input_variables=["bar"], partial_variables={"foo": "foo"}
)
print(prompt.format(bar="baz"))
输出结果为:
foobaz
2. 使用函数进行部分格式化
另一种常见的用例是使用函数进行部分格式化。在某些情况下,可能会希望在创建提示模板时,某些变量的值是由特定的方法或函数动态获取的,而不是由用户手动输入或者固定的值。一个典型的例子是日期或时间。假设有一个提示模板,总是希望其中包含当前日期。不能直接在模板中硬编码日期,而将它与其他输入变量一起传递有点麻烦。在这种情况下,使用函数进行部分格式化非常方便,这个函数总是返回当前日期。例如下面的演示代码:
from datetime import datetime
# 获取当前日期时间的函数
def _get_datetime():
now = datetime.now()
return now.strftime("%m/%d/%Y, %H:%M:%S")
# 创建提示模板
prompt = PromptTemplate(
template="Tell me a {adjective} joke about the day {date}",
input_variables=["adjective", "date"],
)
# 使用函数对日期进行部分化
partial_prompt = prompt.partial(date=_get_datetime)
# 格式化部分化的提示模板并传入其他变量
print(partial_prompt.format(adjective="funny"))
输出结果为:
Tell me a funny joke about the day 8/27/2024, 10:45:22
在这种工作流程中,直接初始化提示模板时传入部分化的变量更加合理。例如下面的演示代码:
prompt = PromptTemplate(
template="Tell me a {adjective} joke about the day {date}",
input_variables=["adjective"],
partial_variables={"date": _get_datetime},
)
print(prompt.format(adjective="funny"))
输出结果为:
Tell me a funny joke about the day 8/27/2024, 10:45:36
部分提示模板提供了一种灵活的方式,使得你可以在创建提示模板时只传入部分变量值,从而更好地适应不同的使用场景。
下面是一个使用部分提示模板(Partial prompt templates)的例子,假设我们正在为一个在线教育平台开发一个智能问答系统,该系统可以回答有关课程内容、作业帮助和学习资源的问题。我们想要创建一个提示模板,该模板能够根据用户的具体问题动态生成回答。
实例3-1:简易智能问答系统(源码路径:codes\3\PromptTemplate03.py)
实例文件PromptTemplate03.py的具体实现代码如下所示。
from langchain.prompts import PromptTemplate
# 完整的提示模板
full_prompt_template = PromptTemplate.from_template(
"""您的问题: {user_question}
系统回答: {system_answer}
推荐阅读: {resource_link}"""
)
# 部分提示模板,预先填充系统回答
partial_prompt_with_system_answer = full_prompt_template.partial(
system_answer="这个问题的答案可以在课程讲义的第三章找到。",
resource_link="https://www.example.com/chapter3"
)
# 另一个部分提示模板,针对作业帮助的问题
partial_prompt_for_homework_help = full_prompt_template.partial(
system_answer="关于作业的帮助,您可以查看附加的习题解答文档。",
resource_link="https://www.example.com/homework-help"
)
# 假设我们收到了用户的以下问题
user_questions = [
"课程讲义第三章的内容是什么?",
"我需要一些关于作业的帮助,有解答文档吗?",
"有没有好的学习资源推荐?"
]
# 为每个问题生成提示
for question in user_questions:
if "课程讲义" in question:
prompt = partial_prompt_with_system_answer.format(user_question=question)
elif "作业帮助" in question:
prompt = partial_prompt_for_homework_help.format(user_question=question)
else:
# 对于其他问题,我们可以提供一个通用的回答和资源链接
prompt = full_prompt_template.format(
user_question=question,
system_answer="对不起,我们没有找到您问题的答案。",
resource_link="https://www.example.com/resources"
)
print(prompt)
上述代码的实现流程如下所示:
- 首先,定义了一个完整的提示模板 full_prompt_template,它包含用户问题、系统回答和推荐阅读三个部分。
- 接着,创建了两个部分提示模板 partial_prompt_with_system_answer 和 partial_prompt_for_homework_help,它们分别针对特定类型的问题预先填充了系统回答和资源链接。
- 然后,定义了一个用户问题列表 user_questions,包含三种不同类型的问题。
- 最后,通过一个循环,根据用户问题的内容选择合适的部分提示模板,并填充剩余的信息(用户问题),然后打印输出完整的提示。执行后会输出:
您的问题: 课程讲义第三章的内容是什么?
系统回答: 这个问题的答案可以在课程讲义的第三章找到。
推荐阅读: https://www.example.com/chapter3
您的问题: 我需要一些关于作业的帮助,有解答文档吗?
系统回答: 对不起,我们没有找到您问题的答案。
推荐阅读: https://www.example.com/resources
您的问题: 有没有好的学习资源推荐?
系统回答: 对不起,我们没有找到您问题的答案。
推荐阅读: https://www.example.com/resources
在这个例子中,format 方法用于将占位符替换为实际的值。例如,{user_question} 被替换为用户的具体问题,{system_answer} 被替换为预先定义好的系统回答,{resource_link} 被替换为相关的资源链接。
如果在用户问题中包含 "课程讲义",那么使用 partial_prompt_with_system_answer 模板;如果包含 "作业帮助",则使用 partial_prompt_for_homework_help 模板。对于其他问题,使用完整的提示模板 full_prompt_template 并提供一个通用的回答和资源链接。
这种方法允许系统灵活地处理不同类型的用户问题,并提供个性化的回复,从而提高用户体验。同时,通过部分化提示模板,可以减少代码重复,并简化模板管理。
3.3.4 模板的组合(Composition)
模板的组合(Composition)是指将多个模板结合在一起,以创建更复杂、更完整的提示模板。在LangChain中,可以使用不同的方法来实现模板的组合,包括字符串模板的组合和聊天模板的组合。
在LangChain中,可以使用PipelinePromptTemplate实现模板的组合(Composition)。PipelinePromptTemplate是一个抽象提示模板,用于组合多个子模板以创建更复杂的提示结构。PipelinePromptTemplate 提供了一种方便的方式来定义模板的组合,并允许在组合的过程中传递变量。
类PipelinePromptTemplate主要用于组合多个子模板,并提供了 format() 方法来格式化最终的提示模板。这个类并没有额外的成员方法,因为它的主要功能是组合和格式化子模板。类PipelinePromptTemplate的主要成员方法如下所示:
- __init__():类的构造函数,用于初始化 PipelinePromptTemplate 对象。需要传入最终的提示模板(final_prompt)以及一组子模板(pipeline_prompts)。
- format():用于格式化 PipelinePromptTemplate。接受一组变量值作为参数,并使用这些值来填充子模板中的变量,然后返回最终的提示字符串。
1. 字符串模板的组合
在LangChain中,可以使用类PromptTemplate来创建字符串模板。这个类允许定义模板字符串,其中可以包含变量,并使用.format()方法将变量填充到模板中。在组合时,使用加号(+)操作符将多个字符串模板连接在一起来创建一个完整的提示模板。例如下面是一个使用字符串模板组合的例子,创建了一个 PipelinePromptTemplate 对象,其中包含多个子模板,并演示如何使用它们来格式化最终的提示字符串。
实例3-1:实现字符串模板的组合(源码路径:codes\3\PromptTemplate04.py)
实例文件PromptTemplate04.py的具体实现代码如下所示。
from langchain.prompts import PromptTemplate
from langchain.prompts.pipeline import PipelinePromptTemplate
# 创建子模板
introduction_template = "欢迎您来到我们的网站!"
introduction_prompt = PromptTemplate.from_template(introduction_template)
question_template = "请问有什么可以帮助您的?"
question_prompt = PromptTemplate.from_template(question_template)
response_template = "感谢您的访问,祝您生活愉快!"
response_prompt = PromptTemplate.from_template(response_template)
# 创建 PipelinePromptTemplate
pipeline_prompts = [
("introduction", introduction_prompt),
("question", question_prompt),
("response", response_prompt)
]
final_template = "这是最终的提示模板。"
final_prompt = PromptTemplate.from_template(final_template)
pipeline_prompt = PipelinePromptTemplate(final_prompt=final_prompt, pipeline_prompts=pipeline_prompts)
# 格式化并输出组合后的模板
formatted_prompt = pipeline_prompt.format()
print(formatted_prompt)
在上述代码中,创建了三个单独的字符串模板(introduction_template、question_template 和 response_template),然后将它们作为子模板传递给 PipelinePromptTemplate。最终的模板 final_template 也是一个字符串模板。执行后会输出:
这是最终的提示模板。
2. 聊天模板的组合
在LangChain中,可以使用类ChatPromptTemplate创建聊天模板,这个类允许创建由多个消息组成的对话。ChatPromptTemplate可以将多个消息组合在一起来创建一个聊天模板,每个消息可以是系统消息、人类消息或AI消息,它们按照特定的顺序组合在一起。假设要创建一个简单的对话,其中包含一条系统消息、一条人类消息和一条AI消息,然后将它们组合在一起。下面的代码将创建一个包含系统消息、人类消息和AI消息的对话,并将它们组合在一起以创建一个聊天模板。然后,它将格式化并打印出这个聊天模板。
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
from langchain.prompts.chat import ChatPromptTemplate
# 创建系统消息、人类消息和AI消息
system_msg = SystemMessage(content="系统消息:欢迎来到聊天室!")
human_msg = HumanMessage(content="我:你好,大家!")
ai_msg = AIMessage(content="AI:大家好,我是聊天室的机器人。")
# 使用ChatPromptTemplate创建聊天模板,并将消息组合在一起
chat_template = ChatPromptTemplate(messages=[system_msg, human_msg, ai_msg])
# 格式化并打印聊天模板
print(chat_template.format_messages())
执行后会输出:
[SystemMessage(content='系统消息:欢迎来到聊天室!'), HumanMessage(content='我:你好,大家!'), AIMessage(content='AI:大家好,我是聊天室的机器人。')]