什么是Model I/O
对于LangChain中被称为LangChain六大核心组件之一的Model I/O这个基础的概念,在这里引用小册中的概念: 我们可以把对模型的使用过程拆解成三块,分别是输入提示 (对应图中的Format)、调用模型 (对应图中的Predict)和输出解析 (对应图中的Parse)。这三块形成了一个整体,因此在LangChain中这个过程被统称为 Model I/O (Input/Output)。 如图所示,这个过程主要分为三个部分,分别是输入提示,调用模型,以及输出解析,下面将就我近期实践浅谈一下对各部分的理解。
输入提示
在输入提示(Format)部分,我们需要为LangChain提供一个模板提示语句 ,将我们输入信息的基本骨架固定,并将输入信息中需要根据实际情况进行改变的提示信息以参数的形式嵌入在我们定义的模板中,在我们需要向大模型提供相应的输入信息时,为模板传入指定的参数,便能够直接将我们的输入参数与模板结合,生成对应的提示输入语句给大模型。通过这种方式生成输入提示信息的一个简单例子如下:
python
# 首先导入langchain.prompts模块中的PromptTemplate类
from langchain.prompts import PromptTemplate
# 定义提示模板语句
template = "你是一位{role},对于{content},你能谈谈自己的看法吗?"
# 创建PromptTemplate对象
prompt = PromptTemplate.from_template(template)
# 格式化提示消息生成提示
Prompt = prompt.format(role="AI顾问", content="人工智能")
# 打印提示消息
print(Prompt)
上面代码的执行结果如下:
可以看到,生成的语句骨架确实与我们给定的模板相同,并且在变量部分也确实根据我们输入的参数进行了填充,至此,我们已经成功通过提示模板生成了我们想要的输入提示信息。接下来,这个输入提示将会作为调用模型部分的输入传给大模型。
调用模型
在这个部分,我们主要通过调用语言大模型,对输入提示模块生成的输入提示进行处理,让语言大模型为我们生成对应的输出。在调用大模型之前,首先要先了解了解LangChain支持的模型有哪几种,它们分别是大语言模型(LLM) 、聊天模型(Chat Model) 、文本嵌入模型(Embedding Model)。其中,LLM会为我们的输入返回文本字符串作为输出,Chat模型会为我们的输入返回对应的聊天消息,文本嵌入模型则会对我们的输入进行处理并返回浮点数,由于我们需要的是大模型的文本回复,显然,这里主要使用到的会是LLM和Chat模型。
这里我们以Chat模型为例,简单介绍一下大语言模型的调用(LLM类似,可自行实践):
python
# 首先导入langchain.prompts模块中的PromptTemplate类
from langchain.prompts import PromptTemplate
# 定义提示模板语句
template = "你是一位{role},对于{content},你能谈谈自己的看法吗?"
# 创建PromptTemplate对象
prompt = PromptTemplate.from_template(template)
# 格式化提示消息生成提示
Prompt = prompt.format(role="AI顾问", content="人工智能")
# 打印提示消息
print(Prompt)
# 导入os模块,用于加载环境变量
import os
# 导入Chat模型
from langchain_openai import ChatOpenAI
# 初始化Chat模型
chat = ChatOpenAI(
model=os.environ.get("LLM_MODELEND"),
)
# 调用Chat模型,将上文生成的Prompt作为输入传入,并返回结果
result = chat.invoke(Prompt)
# 打印模型返回结果
print(result.content)
模型的输出结果如下,由于输出篇幅过长,这里只展示一部分:
可以看到,Chat模型是可以正常根据我们的输入提示进行分析并回答我们提出的问题的,但是显然,Chat的回答格式是它自定义的,如果我们多次调用,可能输出的格式是截然不同的,在某些情况下不方便我们的保存使用,如果要对Chat的输出进行格式化,就需要用到我们的输出解析了。
输出解析
在LangChain的输出解析部分,我们可以对大语言模型的输出进行处理,使其输出结构化的聊天信息,这样就极大地方便了我们的对数据的保存,下面将通过一个简单的例子介绍输出解析的常规用法:
python
# 首先导入langchain.prompts模块中的PromptTemplate类
from langchain.prompts import PromptTemplate
# 导入输出解析器模块和输出格式模块
from langchain.output_parsers import StructuredOutputParser, ResponseSchema
# 定义提示模板语句
template = "你是一位{role},对于{content},你能谈谈自己的看法吗?{output_instructions}"
# 创建PromptTemplate对象
prompt = PromptTemplate.from_template(template)
# 定义我们想要Chat为我们输出的的回复格式
response_schemas=[
ResponseSchema(name="view",description="输出的观点"),
ResponseSchema(name="reason",description="这样理解的原因")
]
# 创建输出解析器对象,将我们的回复格式传入,方便我们后面的到到解析后的回复
parser = StructuredOutputParser.from_response_schemas(response_schemas)
# 获得解析器为我们生成的回复提示,通过这个提示,我们就可以通过解析器将Chat的回复解析为我们想要的格式
output_instructions = parser.get_format_instructions()
# 格式化提示消息生成提示
Prompt = prompt.format(role="AI顾问", content="人工智能",output_instructions=output_instructions)
# 打印Prompt,与前面的Prompt不同,这里附带了我们的输出格式提示
print(Prompt)
# 导入os模块用于加载环境变量
import os
# 导入ChatOpenAI类用于调用Chat模型
from langchain_openai import ChatOpenAI
# 初始化Chat模型
chat = ChatOpenAI(
model=os.environ.get("LLM_MODELEND"),
)
# 调用Chat生成回复
result = chat.invoke(Prompt)
# 通过parser解析器,将Chat的回复解析为我们想要的格式并输出
print(parser.parse(result.content))
在上面的代码中,我们就使用到了输出解析器,为我们生成了格式化输出的输入提示信息,并在输入提示模板中以变量的形式进行嵌入: 可以看到,相较于前面两部分的代码中,这次我们的输入提示模板中多了输出提示这个变量,这就使得我们可以将输出解析器生成的格式化输出的输入提示语句 进行嵌入,在调用Chat模型时将其一起传入: 以上是以这种模板作为输入提示模板时我们为Chat模型所提供的输入信息,可以看到相较于前面两部分的代码,多出了用红色部分标注出来的信息,通过翻译我们也可以知道正是这部分对Chat模型的输出进行了限制,以达到让Chat为我们输出我们想要的输出格式。
由于这部分提示信息有一定的格式,看起来,我们并不能像前面一样可以直接由我们自行给出,但是好在输出解析器为我们提供了可以直接生成这部分提示的函数,我们只需要定义我们想要的输出格式,并且将我们想要的输入格式作为参数传给输出解析器,输出解析器就可以直接为我们生成这部分的提示信息: 上面代码中,我们通过ResponseSchema类定义了我们想要的输出格式以及描述信息,将其作为参数在parser初始化时传入,后续就可以通过parser的get_format_instructions()函数获取到parser为我们生成的提示信息。
我们已经完成了输入提示模板所有参数的补全并且已经对Chat的输出进行了规范,接下来只需要直接运行我们的代码,就可以得到Chat给我们输出的格式化消息,如下(示例代码已在这部分的开头给出,不缺少库和环境变量正确的情况下可以直接CV到环境中Run): 可以看到,这次Chat的输出是以json格式进行输出,并且也将自己的输出分成了view和reason两部分,这正是我们在代码中为其指定的规范化输出。
至此,模型解析的基本使用也基本介绍完毕,当然,输出解析器并非只有一种,也可以查看LangChain帮助文档自行了解并尝试其他几种输出解析器,同时,上文提到的如何将Chat的回复消息作为文件进行保存与输出解析的知识关系不大,在这里也不做赘述,可自行了解。
总结
上文按照输入提示->模型调用->输出解析 的顺序并举了一些的例子对Model I/O做了简单的介绍,如果帮你解决了学习过程中遇到的一部分问题,万分荣幸。
接下来关于输入提示->模型调用->输出解析这个顺序不知道你有没有过这样的疑惑:
在我们讲到上述例子的过程中,可以发现,在最终的代码里,我们大体上确实是通过输入提示->模型调用->输出解析 向Chat模型提出问题并得到我们想要的结果,但是在代码的实现过程中,我们其实是在给出输入提示模板的时候,就已经开始考虑到模型解析器了,并且也确实在生成真正的输入提示之前就对parser解析器进行了实例化并调用get_format_instructions()函数获取了parser为我们生成的格式化输出的提示语句并将其嵌入到输入提示模板中,之后才将输入提示传给Chat模型,然后才是parser对Chat的回复消息进行了解析。这样看起来好像顺序就成为了输出解析器生成格式化输出的输入提示->输入提示->模型调用->输出解析 ,这种顺序好像与我们开篇提到的顺序并不相同,看起来更像是输出解析对整个过程进行了环绕。但是实际上,流程确实是按照输入提示->模型调用->输出解析 的顺序来进行的,之所以会出现parser环绕整个过程的情况实际上是因为LangChain的开发者为了方便我们生成提示信息才这样设计的,上文有提到过,能使得Chat进行格式化输出并让输出可以被parser正常解析的输入提示语句我们并不容易直接书写,所以就需要一个函数来为我们自动生成,而又要确保这部分提示信息给到Chat模型之后,输出又正好是parser可以进行解析的,所以就需要这个信息是由parser自行生成的(可以理解正常情况下为自己给出的任务提交的结果自己是一定可以理解的)。正是因为如此,parser才在前后都有出现,但是parser在生成提示信息的时候并未使用输出解析的功能,所以正确的顺序依然是输入提示->模型调用->输出解析 ,输出解析器 和输出解析是两个概念,希望大家在学习时不要混淆。
其他
在上面给出的代码都可以直接CV运行,如果报错请检查是否缺少模块以及环境变量是否配置正确,关于环境变量的配置,可以查看我的本地运行环境配置教学。