青训营学习5:模型 I/O 相关知识
主要是学习 LangChain 的六大核心组件。首先是位于LangChain框架最底层的 模型(Model) 。模型是基于语言模型构建应用的核心元素,因为 LangChain 应用开发的实质,就是以 LangChain 作为框架,通过 API 调用大型语言模型(LLM)来解决具体问题。
可以理解为整个 LangChain 框架的逻辑都是由 LLM 驱动的。没有模型,LangChain 也就失去了存在的意义。通过学习课程以及尝试构建一个能够自动生成鲜花文案的应用程序,对 输入提示、调用模型、解析输出 有了更深入的认识。
1、模型 I/O
使用模型的过程可以拆解为三个部分:分别是输入提示(下图中的Format)、调用模型(下图中的Predict)和输出解析(下图中的Parse)。
- 输入提示(Prompt) :即提示模板,是准备并输入给模型的提示信息,我们可以创建LangChain模板,根据需求、特定的任务和应用调整输入。
- 调用模型(Model Invocation) :即语言模型,通过 API 或 SDK 调用模型进行预测或生成,提高了灵活性和便利性。。
- 输出解析(Output Parsing) :对模型输出的结果进行解析和处理。可以把大模型给回的非结构化文本,转换成程序可以处理的结构化数据。
这三个部分构成了一个完整的流程,在 LangChain 中被统称为 模型 I/O(Model Input/Output) 。
1.1 输入提示(Prompt Templates)
使用模型的第一步是将提示信息输入到模型中。LangChain 提供了 提示模板(Prompt Templates) ,允许我们根据实际需求动态地选择不同的输入,并针对特定的任务和应用调整输入。
提示模板的优势:
- 可读性:模板使提示文本更易于阅读和理解,特别是对于复杂的提示或涉及多个变量的情况。
- 可重用性:模板可以在多个地方重复使用,避免在每个需要生成提示的地方重新构造提示字符串。
- 维护性:如果需要修改提示,只需修改模板,无需在代码中查找所有使用该提示的地方。
- 变量处理:模板可以自动处理变量的插入,避免手动拼接字符串的繁琐。
- 参数化:模板可以根据不同的参数生成不同的提示,有助于个性化生成文本。
学习的示例:
比如为销售的每种鲜花生成一段简介文案,那么每当员工或顾客想了解某种鲜花时,调用该模板就会生成适合的文字。
提示模板的生成方法:
from langchain.prompts import PromptTemplate
template = """您是一位专业的鲜花店文案撰写员。\n
对于售价为 {price} 元的 {flower_name} ,您能提供一个吸引人的简短描述吗?
"""
# 根据原始模板创建LangChain提示模板
prompt = PromptTemplate.from_template(template)
print(prompt)
PromptTemplate的from_template方法就是将一个原始的模板字符串转化为一个更丰富、更方便操作的PromptTemplate对象,这个对象就是LangChain中的提示模板。
提示模板的具体内容:
input_variables=['flower_name', 'price']
output_parser=None partial_variables={}
template='/\n您是一位专业的鲜花店文案撰写员。
\n对于售价为 {price} 元的 {flower_name} ,您能提供一个吸引人的简短描述吗?\n'
template_format='f-string'
validate_template=True
在示例中,我们创建了一个包含 {flower_name} 和 {price} 两个变量的提示模板。这些变量在实际使用时会被替换为具体的值,从而生成定制化的提示信息。
1.2 调用模型(Language Models)
LangChain 支持三大类模型:
- 大语言模型(LLMs) :将文本字符串作为输入,返回文本字符串作为输出。例如,OpenAI 的
text-davinci-003、Facebook 的 LLaMA、Anthropic 的 Claude 等。 - 聊天模型(Chat Models) :以结构化的方式接受一系列聊天消息作为输入,返回聊天消息作为输出,主要代表 OpenAI 的 ChatGPT 系列模型。
- 文本嵌入模型(Embedding Models) :将文本作为输入,返回浮点数列表(即嵌入向量)。例如,OpenAI 的
text-embedding-ada-002。
在调用模型时,LangChain 提供了统一的接口,使得无论使用哪种语言模型,都可以通过相同的方式进行调用,提高了灵活性和便利性。
学习的示例:
使用之前创建的提示模板,调用语言模型来生成鲜花的文案,并且返回文案的结果。
import os
os.environ["OPENAI_API_KEY"] = 'Open AI API Key'
from langchain_openai import OpenAI
# 创建模型实例
model = OpenAI(model_name='gpt-3.5-turbo-instruct')
input = prompt.format(flower_name=["玫瑰"], price='50')
output = model.invoke(input)
print(output)
其中 input 这行代码的作用是将模板实例化,将 {flower_name} 替换为 "玫瑰",{price} 替换为 '50',形成了具体的提示。接收到这个输入,调用模型之后可以得到输出,而且复用提示模板可以同时生成多个鲜花的文案。
深入思考的问题:
直接使用 OpenAI 的 API 也可以实现类似的功能,那么使用 LangChain 的意义何在?
- 模板化管理:LangChain 的提示模板使得我们只需定义一次模板,就可以在不同的地方复用,减少了代码冗余。
- 易于维护:修改模板或切换模型时,无需修改提示相关的代码,增加了代码的可维护性。
- 功能集成:LangChain 的提示模板集成了
output_parser、template_format等功能,提供了更强大的特性。 - 模型无关性:使用 LangChain,可以方便地在不同模型之间切换,而无需改变调用逻辑。
这类似于机器学习中的框架,如 PyTorch 和 TensorFlow,让模型的训练和调用更加规范和可复用。
1.3 输出解析(Output Parsing)
在开发应用时,我们通常需要从模型的输出中提取结构化的信息,以便程序进一步处理。LangChain 提供的 输出解析器(Output Parsers) ,使我们能够更容易地将模型的输出转换为结构化的数据格式。
为何需要输出解析器?
- 非结构化输出:模型返回的文本往往是非结构化的,自然语言的表达。
- 数据处理需求:程序通常需要结构化的数据,如字典、JSON 等,来进行数据处理或存储。
学习的示例:
我们希望模型返回以下两个字段:
description:鲜花的描述文案。reason:撰写该文案的原因,解释一下为何要这样写这样的文案。
直接让模型生成文本,可能会得到混杂在一起的自然语言描述,而不是我们需要的结构化数据。为了解决这个问题,我们可以使用输出解析器来规范模型的输出格式。
import os
os.environ["OPENAI_API_KEY"] = 'OpenAI API Key'
from langchain.prompts import PromptTemplate
prompt_template = """您是一位专业的鲜花店文案撰写员。
对于售价为 {price} 元的 {flower_name} ,您能提供一个吸引人的简短描述吗?
{format_instructions}"""
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)
parsed_output['flower'] = flower
parsed_output['price'] = price
df.loc[len(df)] = parsed_output
print(df.to_dict(orient='records'))
df.to_csv("flowers_with_descriptions.csv", index=False)
在实现的代码中:
- 定义了输出的结构
response_schemas,包含description和reason两个字段。 - 创建了一个输出解析器
output_parser,用于解析模型的输出。 - 获取了输出格式说明
format_instructions,并将其整合到提示模板中。 - 使用新的模板生成模型的输入,得到符合我们预期结构的输出。
- 通过
output_parser.parse(output)方法,将模型的输出解析为 Python 字典。
解析后的输出是一个包含 description 和 reason 的字典,便于程序进一步处理。可以将这些数据整合到 pandas 的 DataFrame 中,或者保存为 CSV 文件,实现数据的结构化存储和处理。
2、总结、知识积累
通过学习开发了一个能够自动生成鲜花文案的应用程序,完成了模型 I/O 的全过程:
- 输入提示(Prompt):使用提示模板,动态生成输入。是准备并输入给模型的提示信息。
- 调用模型(Model Invocation):通过 LangChain 的统一接口调用语言模型生成结果,例如通过 API 或 SDK 调用模型进行预测或生成。
- 输出解析(Output Parsing):使用输出解析器,将模型的输出转换为结构化数据。可以把大模型给回的非结构化文本,转换成程序可以处理的结构化数据。
这种方式不仅提高了代码的可读性、可维护性和可重用性,还使我们能够更高效地开发基于语言模型的应用。
LangChain框架的好处:
- 模板管理:大型项目中可能会有许多不同的提示模板,使用 LangChain 可以帮助更好地管理这些模板。
- 变量提取和检查:LangChain 可以自动提取模板中的变量并进行检查。
- 模型切换:如果想尝试使用不同的模型,只需要更改模型的名称就可以。
- 输出解析:LangChain的提示模板可以嵌入对输出格式的定义,以便在后续处理过程中比较方便地处理已经被格式化了的输出。
3、个人思考
- LangChain 的优势:LangChain 将提示工程、模型调用和输出解析有机地结合在一起,提供了一个高效的框架,极大地简化了开发流程。
- 结构化数据的重要性:在实际应用中,结构化的数据比纯文本更易于处理和分析。使用输出解析器,可以直接获得可用于程序处理的数据格式。
- 模型的可替换性:通过 LangChain,我们的代码与具体的模型实现解耦,方便地切换不同的模型,以满足不同的需求或利用更好的模型性能。
对于课程的思考题:
- 在上面的示例中,format_instructions,也就是输出格式是怎样用output_parser构建出来的,又是怎样传递到提示模板中的?
思考:
format_instructions是通过output_parser.get_format_instructions()方法生成的。这个方法会根据我们之前定义的response_schemas,自动生成一段说明,指导模型按照指定的格式输出。然后,我们在创建提示模板时,通过partial_variables参数将format_instructions传递给模板中的{format_instructions}占位符。这使得提示模板在实际使用时,会包含对输出格式的明确要求,引导模型生成符合预期的结构化输出。
- 加入了partial_variables,也就是输出解析器指定的format_instructions之后的提示,为什么能够让模型生成结构化的输出?你可以打印出这个提示,一探究竟。
思考:通过在提示中添加
format_instructions,我们明确地告诉模型期望的输出格式和结构。这种明确的指令能够帮助模型理解我们的需求,使其更有可能返回符合预期的结构化数据。可以打印出最终生成的提示,来看一看模型到底接收到了什么指令。例如:
你的任务是为一种鲜花生成一段介绍文案。 花名:玫瑰 价格:50元 请按照以下格式返回: {format_instructions}其中,
{format_instructions}会被替换为由output_parser生成的具体格式说明,比如要求以 JSON 格式返回包含description和reason两个字段的内容。
- 使用输出解析器后,调用模型时有没有可能仍然得不到所希望的输出?也就是说,模型有没有可能仍然返回格式不够完美的输出?
思考:即使使用了输出解析器,并在提示中加入了
format_instructions,也不能完全保证模型的输出总是符合预期的格式。由于大型语言模型的生成具有随机性,或者对指令的理解可能不够准确,模型可能会返回格式不完全正确的输出。这种情况下,输出解析器可能会抛出解析错误,或者返回不完整的数据。 为了应对这种情况,我们可以:
- 增加提示的清晰度:在提示中进一步强调输出格式的重要性,或者提供示例。
- 异常处理:在代码中对解析过程添加异常处理,捕获解析错误并进行相应的处理,例如重试或返回默认值。
- 多次尝试:在必要时,使用循环或多次调用模型,直到获得符合要求的输出。
有关应用拓展:
- 多语言支持:通过调整提示模板和输出解析器,可以方便地支持多语言的文本生成和处理。
- 复杂任务:对于更加复杂的任务,如问答系统、对话代理等,LangChain 的模块化设计依然适用,能够帮助我们快速构建原型。
- 与其他工具集成:LangChain 可以与其他数据处理库(如 Pandas、NumPy)以及数据库、API 等集成,构建更加完整的应用程序。