LangChain 在模型 I/O 各环节提供的便利功能
1 提示模板
2 语言模型
3 输出解析
LangChain 在模型 I/O 的各环节均提供了实用功能。在输入环节可创建模板,依实际需求动态选输入并调整;调用语言模型时,能通过通用接口实现,无论何种语言模型都可按同方式调用,增强灵活性与便利性;在输出环节还具备输出解析功能,可从模型输出提取所需信息,将大模型给出的非结构化文本转换为程序能处理的结构化数据。
提示模板学习
# 导入LangChain中的提示模板
from langchain.prompts import PromptTemplate
# 创建原始模板
template = """您是一位专业的鲜花店文案撰写员。\n
对于售价为 {price} 元的 {flower_name} ,您能提供一个吸引人的简短描述吗?
"""
# 根据原始模板创建LangChain提示模板
prompt = PromptTemplate.from_template(template)
# 打印LangChain提示模板的内容
print(prompt)
解释如下:
template = """您是一位专业的鲜花店文案撰写员。\n 对于售价为 {price} 元的 {flower_name} ,您能提供一个吸引人的简短描述吗? """
这里定义了一个字符串模板,它包含了一段特定的文本内容以及两个占位符{price}和{flower_name}。这个模板模拟了一个需求场景,即假设存在一位专业的鲜花店文案撰写员,需要针对特定价格({price})的某种鲜花({flower_name})来撰写一个吸引人的简短描述。
prompt = PromptTemplate.from_template(template)
这行代码使用之前定义的原始模板(template)来创建一个LangChain框架下的提示模板对象(prompt)。通过PromptTemplate.from_template()方法,LangChain会对传入的原始模板进行处理,识别其中的占位符等信息,以便后续可以方便地将具体的值填充到这些占位符中,从而生成完整的、符合特定场景需求的提示文本,用于与语言模型进行交互。
语言模型
- 大语言模型(LLM) ,也叫Text Model,这些模型将文本字符串作为输入,并返回文本字符串作为输出。Open AI的text-davinci-003、Facebook的LLaMA、ANTHROPIC的Claude,都是典型的LLM。
- 聊天模型(Chat Model),主要代表Open AI的ChatGPT系列模型。这些模型通常由语言模型支持,但它们的 API 更加结构化。具体来说,这些模型将聊天消息列表作为输入,并返回聊天消息。
- 文本嵌入模型(Embedding Model),这些模型将文本作为输入并返回浮点数列表,也就是Embedding。而文本嵌入模型如OpenAI的text-embedding-ada-002,我们之前已经见过了。文本嵌入模型负责把文档存入向量数据库,和我们这里探讨的提示工程关系不大。
# 导入聊天消息类模板
from langchain.prompts import (
ChatPromptTemplate,
SystemMessagePromptTemplate,
HumanMessagePromptTemplate,
)
# 模板的构建
template = "你是一位专业顾问,负责为专注于{product}的公司起名。"
system_message_prompt = SystemMessagePromptTemplate.from_template(template)
human_template = "公司主打产品是{product_detail}。"
human_message_prompt = HumanMessagePromptTemplate.from_template(human_template)
prompt_template = ChatPromptTemplate.from_messages(
[system_message_prompt, human_message_prompt]
)
# 格式化提示消息生成提示
prompt = prompt_template.format_prompt(
product="鲜花装饰", product_detail="创新的鲜花设计。"
).to_messages()
# 下面调用模型,把提示消息传入模型,生成结果
import os
# os.environ["OPENAI_API_KEY"] = '你的OpenAI API Key'
from langchain_openai import ChatOpenAI
chat = ChatOpenAI(
model=os.environ.get("LLM_MODELEND"),
)
result = chat(prompt)
print(result)
解释如下:
from langchain.prompts import ( ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate, )
这段代码从LangChain库中导入了几个与构建聊天消息提示模板相关的类:
ChatPromptTemplate:用于构建适合聊天场景的提示模板,它可以整合多种不同类型的消息提示模板来形成一个完整的聊天提示。SystemMessagePromptTemplate:专门用于创建系统消息类型的提示模板。在聊天场景中,系统消息通常用于设定一些背景、规则或角色等方面的信息。HumanMessagePromptTemplate:用于创建人类用户发出消息类型的提示模板,模拟人类向聊天模型提出问题或给出相关信息的场景。
template = "你是一位专业顾问,负责为专注于{product}的公司起名。" system_message_prompt = SystemMessagePromptTemplate.from_template(template)
这里首先定义了一个字符串模板,该模板设定了一个角色情境,即假设存在一个 “你” 是一位专业顾问,任务是为专注于特定产品(用占位符{product}表示)的公司起名。然后通过SystemMessagePromptTemplate.from_template()方法,将这个字符串模板转换为系统消息类型的提示模板(system_message_prompt),以便在后续的聊天提示中作为系统设定的背景信息。
human_template = "公司主打产品是{product_detail}。" human_message_prompt = HumanMessagePromptTemplate.from_template(human_template)
接着又定义了另一个字符串模板,用于描述公司的主打产品情况(用占位符{product_detail}表示),并通过HumanMessagePromptTemplate.from_template()方法将其转换为人类消息类型的提示模板(human_message_prompt),模拟人类向聊天模型提供关于公司产品的具体信息。
prompt_template = ChatPromptTemplate.from_messages( [system_message_prompt, human_message_prompt] )
最后,使用ChatPromptTemplate.from_messages()方法,将前面创建的系统消息提示模板和人类消息提示模板组合在一起,形成一个完整的适合聊天场景的提示模板(prompt_template)。这个提示模板整合了系统设定的角色任务(为专注于特定产品的公司起名)以及人类提供的公司产品具体信息(公司主打产品是某某)。
prompt = prompt_template.format_prompt( product="鲜花装饰", product_detail="创新的鲜花设计。" ).to_messages()
这一步通过调用prompt_template的format_prompt()方法,将具体的值(product="鲜花装饰"和product_detail="创新的鲜花设计。")填充到提示模板中的占位符里,然后再通过.to_messages()方法将其转换为适合传入聊天模型的消息格式。这样就生成了一个具体的、包含了实际应用场景信息的聊天提示消息(prompt)。
result = chat(prompt) print(result)
将前面生成的聊天提示消息(prompt)传入创建好的ChatOpenAI实例(chat)中,调用聊天模型进行处理,然后将模型返回的结果打印出来(result),这样就可以看到根据所提供的公司产品信息以及起名任务要求,聊天模型给出的关于公司起名的建议结果。
使用LangChain和提示模板的好处是:
- 代码的可读性:使用模板的话,提示文本更易于阅读和理解,特别是对于复杂的提示或多变量的情况。
- 可复用性:模板可以在多个地方被复用,让你的代码更简洁,不需要在每个需要生成提示的地方重新构造提示字符串。
- 维护:如果你在后续需要修改提示,使用模板的话,只需要修改模板就可以了,而不需要在代码中查找所有使用到该提示的地方进行修改。
- 变量处理:如果你的提示中涉及到多个变量,模板可以自动处理变量的插入,不需要手动拼接字符串。
- 参数化:模板可以根据不同的参数生成不同的提示,这对于个性化生成文本非常有用。
输出解析
-
背景介绍
- LangChain 是一个用于开发语言模型应用程序的强大框架。在与语言模型交互时,模型的输出通常是文本形式,可能包含复杂的信息,如多个实体、关系、意图等。LangChain 提供了一些功能来帮助解析这些输出,使得开发者能够更好地利用模型的结果。
-
解析工具和方法
-
Output Parsers(输出解析器)
-
定义和作用:Output Parsers 是 LangChain 中专门用于解析模型输出的组件。它们可以将模型生成的文本转换为更结构化的格式,例如将一段包含多个命名建议的文本转换为一个列表,每个元素是一个命名建议及其解释的字典。
-
-
Schema - based Parsing(基于模式的解析)
-
定义和作用:这种方法允许开发者定义一个期望的输出模式(Schema),例如一个包含特定字段的字典或一个特定类的实例。LangChain 可以根据这个模式来解析模型输出,确保输出符合预期的结构。
-
-
Chain - based Parsing(基于链的解析)
-
定义和作用:在一些复杂的场景下,可能需要多个步骤来解析模型输出。LangChain 的 Chain - based Parsing 允许将多个解析操作组合成一个链。例如,首先可能需要提取文本中的关键部分,然后对提取的部分进行进一步的格式化或分类。
-
# 导入OpenAI Key
import os
os.environ["OPENAI_API_KEY"] = '你的OpenAI API Key'
# 导入LangChain中的提示模板
from langchain.prompts import PromptTemplate
# 创建原始提示模板
prompt_template = """您是一位专业的鲜花店文案撰写员。
对于售价为 {price} 元的 {flower_name} ,您能提供一个吸引人的简短描述吗?
{format_instructions}"""
# 通过LangChain调用模型
from langchain_openai import OpenAI
# 创建模型实例
model = OpenAI(model_name='gpt-3.5-turbo-instruct')
# 导入结构化输出解析器和ResponseSchema
from langchain.output_parsers import StructuredOutputParser, ResponseSchema
# 定义我们想要接收的响应模式
response_schemas = [
ResponseSchema(name="description", description="鲜花的描述文案"),
ResponseSchema(name="reason", description="问什么要这样写这个文案")
]
# 创建输出解析器
output_parser = StructuredOutputParser.from_response_schemas(response_schemas)
# 获取格式指示
format_instructions = output_parser.get_format_instructions()
# 根据原始模板创建提示,同时在提示中加入输出解析器的说明
prompt = PromptTemplate.from_template(prompt_template,
partial_variables={"format_instructions": format_instructions})
# 数据准备
flowers = ["玫瑰", "百合", "康乃馨"]
prices = ["50", "30", "20"]
# 创建一个空的DataFrame用于存储结果
import pandas as pd
df = pd.DataFrame(columns=["flower", "price", "description", "reason"]) # 先声明列名
for flower, price in zip(flowers, prices):
# 根据提示准备模型的输入
input = prompt.format(flower_name=flower, price=price)
# 获取模型的输出
output = model.invoke(input)
# 解析模型的输出(这是一个字典结构)
parsed_output = output_parser.parse(output)
# 在解析后的输出中添加“flower”和“price”
parsed_output['flower'] = flower
parsed_output['price'] = price
# 将解析后的输出添加到DataFrame中
df.loc[len(df)] = parsed_output
# 打印字典
print(df.to_dict(orient='records'))
# 保存DataFrame到CSV文件
df.to_csv("flowers_with_descriptions.csv", index=False)
关键部分解释:
from langchain.output_parsers import StructuredOutputParser, ResponseSchema
这两行代码从LangChain库中导入了两个重要的类,用于处理模型输出的结构化解析。
StructuredOutputParser:这个类是用于将模型输出按照特定的结构进行解析的工具。它可以根据我们预先定义的期望输出结构,把模型生成的文本内容转换为更易于处理和理解的结构化数据形式,比如字典或列表等。ResponseSchema:用于定义我们期望从模型输出中获取的具体信息的结构模式。通过指定不同的ResponseSchema实例,我们可以明确告诉解析器我们希望模型输出包含哪些字段,以及每个字段所代表的含义。
response_schemas = [ ResponseSchema(name="description", description="鲜花的描述文案"), ResponseSchema(name="reason", description="问什么要这样写这个文案") ]
在这里,我们定义了一个列表response_schemas,其中包含了两个ResponseSchema实例。
-
第一个
ResponseSchema实例:name="description":指定了这个字段在结构化输出中的名称为 “description”。description="鲜花的描述文案":说明了这个字段所期望获取的内容是关于鲜花的描述文案,也就是希望模型生成的针对特定鲜花的吸引人的简短描述内容。
-
第二个
ResponseSchema实例:name="reason":设定该字段名称为 “reason”。description="为什么要这样写这个文案":表示这个字段要获取的是为什么要按照前面生成的描述文案那样去写的原因解释,即模型对于生成特定鲜花描述文案的思路或依据。
output_parser = StructuredOutputParser.from_response_schemas(response_schemas)
这行代码使用前面定义的response_schemas列表来创建一个StructuredOutputParser实例,名为output_parser。这个实例将会根据我们设定的response_schemas中的结构模式,对模型输出进行解析,将其转换为符合我们期望的结构化数据。
format_instructions = output_parser.get_format_instructions()
通过调用output_parser的get_format_instructions()方法,我们获取到了一系列格式指示信息,存储在format_instructions变量中。这些格式指示是专门为了让模型知道我们期望它按照怎样的结构化格式来输出内容而生成的。模型在生成文本时,看到这些格式指示,就会尽量按照对应的结构化方式来组织输出的内容,以便后续能够顺利地被我们的输出解析器解析。
prompt = PromptTemplate.from_template(prompt_template, partial_variables={"format_instructions": format_instructions})
最后,这行代码是对之前创建的提示模板prompt_template进行进一步的完善。我们使用PromptTemplate.from_template()方法,并且通过partial_variables参数将刚才获取到的format_instructions添加到提示模板中。这样,当我们后续使用这个提示模板向模型提供输入时,模型不仅能知道要完成的任务(针对特定鲜花和价格撰写描述文案),还能明确我们期望它按照怎样的结构化格式来输出结果,从而更好地满足我们对模型输出进行结构化处理的需求。
思考题
- 请你用自己的理解,简述LangChain调用大语言模型来做应用开发的优势。
- 加入了partial_variables,也就是输出解析器指定的format_instructions之后的提示,为什么能够让模型生成结构化的输出?你可以打印出这个提示,一探究竟。
- 使用输出解析器后,调用模型时有没有可能仍然得不到所希望的输出?也就是说,模型有没有可能仍然返回格式不够完美的输出?
回答:
-
LangChain 调用大语言模型来做应用开发的优势:
- 集成多种语言模型:LangChain 可以方便地集成多种不同的大语言模型,如 OpenAI 的 GPT 系列、BERT 等。这使得开发者可以根据项目需求灵活选择最适合的模型,而不需要从头开始开发与模型交互的接口。
- 模块化和可扩展性:它具有模块化的架构,提供了如提示模板(Prompt Templates)、链(Chains)、代理(Agents)等组件。这些组件可以被独立开发和使用,并且可以通过组合这些模块来构建复杂的应用程序,具有很强的可扩展性。
- 处理复杂工作流:能够处理包含多个步骤的复杂工作流。例如,在一个问答系统中,可以将问题分解、调用语言模型生成答案、对答案进行后处理等多个步骤通过链(Chains)来串联起来,简化开发过程。
- 输出解析和格式化:LangChain 提供了输出解析器(Output Parsers),可以将语言模型的输出转换为结构化的数据。这对于需要特定格式输出的应用场景(如从自然语言生成 SQL 查询语句等)非常有用。
- 工具集成和代理功能:可以集成外部工具,并且通过代理(Agents)功能来决定何时调用语言模型、何时调用外部工具,从而实现更智能的决策和操作。
-
加入了 partial_variables(输出解析器指定的 format_instructions 之后的提示)能够让模型生成结构化的输出的原因:
- 引导模型的输出格式:当在提示模板中加入由输出解析器指定的 format_instructions 作为 partial_variables 时,实际上是在给模型一个明确的输出格式要求。模型在生成内容时会尽量遵循这些格式要求。
- 结构化提示影响生成内容:模型在训练过程中学习了如何根据输入的提示来生成内容。当提示中包含了结构化的要求时,模型会利用其在训练中学到的知识和模式,尝试生成符合这种结构化要求的内容。例如,如果 format_instructions 要求输出是一个包含特定字段的字典形式,模型会按照这种字典形式的逻辑来组织其生成的内容。
- 减少模糊性:这种方式减少了模型输出的模糊性。如果没有明确的格式指示,模型可能会以任意的、难以处理的格式生成内容。而通过提供 format_instructions,模型的输出变得更加可预测和易于处理。
-
使用输出解析器后,调用模型时有没有可能仍然得不到所希望的输出?
- 模型本身的不确定性:尽管有输出解析器和相应的格式指示,但大语言模型本身具有一定的不确定性。模型的输出是基于其训练数据和训练算法生成的概率分布,即使有格式要求,也不能完全保证每次都能生成完全符合期望的输出。
- 复杂任务和语义理解:对于一些复杂的任务或语义模糊的情况,模型可能会误解输入的要求或者无法按照期望的格式生成内容。例如,当任务涉及到高度专业或特定领域的知识,而模型在该领域的训练数据有限时,可能会出现输出不符合要求的情况。
- 格式的灵活性和容错性:输出解析器本身可能具有一定的容错性,但如果模型输出的格式与要求相差过大,可能无法正确解析。而且在某些情况下,模型可能会生成一些在语法或格式上看似符合要求,但在语义上不符合逻辑的内容,这也可能导致得不到理想的输出。