1. 模型(Model)组件在 LangChain 中的作用
模型(Model),位于 LangChain 框架的最底层,是基于语言模型构建的应用的核心元素,因为 LangChain 框架的作用就是让开发者通过调用 API 来使用大模型解决实际问题。可以说,整个 LangChain 框架的逻辑都是由 LLM 这个发动机来驱动的。没有模型,LangChain 这个框架也就失去了它存在的意义。
2. Model I/O
我们可以把对模型的使用过程拆解成三部分,分别是输入提示(对应上图中的Format)、调用模型(对应上图中的Predict)和输出解析(对应上图中的Parse)。这三部分形成了一个整体,在 LangChain 中这个过程被统称为 Model I/O(Input/Output)。
LangChain 作为一个强大的工具集,在 Model I/O 的每个环节都为开发者提供了一套完整的解决方案,以简化与语言模型的交互流程。
-
动态提示模板:使用模型的第一个环节是把提示信息输入到模型中,LangChain 的提示模板功能允许开发者根据具体任务的需求,动态地构建和调整输入提示。这意味着,无论是处理文本生成、翻译还是其他 NLP 任务,你都可以轻松地定制输入,以激发语言模型的最佳表现。通过这种方式,LangChain 使得提示的创建变得更加灵活和高效。
-
通用语言模型接口:LangChain 允许你通过通用接口以统一的方式调用不同的语言模型。这意味着无论你要使用哪种语言模型,都可以通过同一种方式进行调用,这种设计不仅提高了开发效率,还增加了应用的灵活性,让开发者能够专注于应用逻辑而非模型细节。
-
输出解析:LangChain 在模型输出步骤提供了强大的输出解析器,能够帮助开发者从模型生成的文本中提取关键信息。通过输出解析器,你可以精确地从模型的输出中获取需要的信息,而不需要处理冗余或不相关的数据,这一功能对于构建高效的数据处理流程至关重要。
3. 动态提示模板
什么是提示模版?首先我们可以回想一下自己使用大模型进行问答的过程,我们先给大模型输入一段描述,然后模型处理这段描述再给予我们回复,我们这里的描述并不是模版,而是一个具体的 prompt。所谓提示模版,就类似于编程中的函数,可以通过输入不同的参数来获得不同的输出,函数模版的存在让程序处理数据更加灵活,而提示模版之于AI应用的开发也是如此。
在使用大模型的过程中,我们很明显能感受到不同的 prompt 对模型输出的影响是很大的,一般来说,更加精确/规范的描述能获得更加准确的结果,这也是提示工程(Prompt Engineering)致力于做的事——提示工程专门研究对大语言模型的提示构建以期待更好地激发模型的潜力。
我们使用大模型的场景千差万别,因此肯定不存在那么一两个神奇的模板,能适配所有情况并取得令人满意的效果,那么如何设计或者说想出一个好的 prompt 让我们的任务事半功倍确实还是一个技术活。这其中的具体原则,不外乎吴恩达老师在他的提示工程课程中所说的:
-
给予模型清晰明确的指示
-
让模型慢慢地思考
虽然看起来很简单,但是实际做起来还是很伤脑筋的。我们下面先从一个简单的程序开始,感受一下 LangChain 提示模版的设计和使用。
import os
from langchain.prompts import PromptTemplate
# 创建原始模板
template = """您是一位专业的鲜花店文案撰写员。\n
对于售价为 {price} 元的 {flower_name} ,您能提供一个吸引人的简短描述吗?
"""
# 根据原始模板创建LangChain提示模板
prompt = PromptTemplate.from_template(template)
# 打印LangChain提示模板的内容
print(prompt)
这里我们通过 PromptTemplate.from_template 调用了 PromptTemplate 类的 from_template 类方法。这个类方法的作用是根据给定的模板字符串创建一个 PromptTemplate 对象,这个对象就是 LangChain 中的提示模板。从代码中可以看出,所谓提示模版,就是将 prompt 中的变量用占位符替代,然后在实际使用模板生成提示时用具体的值替换占位符。下面是我们创建的模版中包含的内容:
input_variables=['flower_name', 'price']
template='您是一位专业的鲜花店文案撰写员。\n\n对于售价为 {price} 元的 {flower_name} ,
您能提供一个吸引人的简短描述吗?\n'
可以看到这个对象中包括输入的变量(在这个例子中就是 flower_name 和 price)、输出解析器(这个例子中没有指定)、模板的格式(这个例子中为'f-string')、是否验证模板(这个例子中设置为 True)。除了这种模版创建方式,LangChain 还提供了多个类和函数,为各种应用场景设计了很多内置模板,使构建和使用提示变得容易
4. 通用语言模型接口
通用语言模型接口是 LangChain 的一个显著优势,它允许开发者以统一的方式调用不同的语言模型,无论你选择的是 GPT-3、BERT 还是其他任何模型,LangChain 都能提供一个标准化的调用方法,让开发者能够专注于应用逻辑而非模型细节。
LangChain 中支持的模型有三大类。
- 大语言模型(LLM) ,也叫 Text Model,这些模型将文本字符串作为输入,并返回文本字符串作为输出。OpenAI 的
text-davinci-003、Facebook 的LLaMA、ANTHROPIC 的Claude,都是典型的 LLM。 - 聊天模型(Chat Model),主要代表模型是 OpenAI 的
ChatGPT系列。这些模型将聊天消息列表作为输入,并返回聊天消息。 - 文本嵌入模型(Embedding Model),这些模型将文本作为输入并返回浮点数列表,也就是Embedding。
总体来说,大语言模型适用于广泛的文本生成和理解任务,聊天模型专注于模拟人类对话和交互,而文本嵌入模型则用于将文本转换为向量,以便于在各种数据匹配和检索任务中使用。
下面我们通过一个 for 循环复用上文的提示模板,让模型为我们生成多个鲜花的文案:
from langchain.prompts import PromptTemplate # 导入LangChain中的提示模板
# 创建原始模板
template = """您是一位专业的鲜花店文案撰写员。\n
对于售价为 {price} 元的 {flower_name} ,您能提供一个吸引人的简短描述吗?
"""
# 根据原始模板创建LangChain提示模板
prompt = PromptTemplate.from_template(template)
import os
# 导入LangChain中的OpenAI模型接口
from langchain_openai import OpenAI, ChatOpenAI
# 创建模型实例
model = ChatOpenAI(model=os.environ.get("LLM_MODELEND"))
# 多种花的列表
flowers = ["玫瑰", "百合", "康乃馨"]
prices = ["50", "30", "20"]
# 生成多种花的文案
for flower, price in zip(flowers, prices):
# 使用提示模板生成输入
input_prompt = prompt.format(flower_name=flower, price=price)
# 得到模型的输出
output = model.invoke(input_prompt)
# 打印输出内容
print(output.content)
模型输出如下:
我们对比一下使用 OpenAI 官方接口实现上面的功能:
import openai # 导入OpenAI
openai.api_key = 'Your-OpenAI-API-Key' # API Key
prompt_text = "您是一位专业的鲜花店文案撰写员。对于售价为{}元的{},您能提供一个吸引人的简短描述吗?" # 设置提示
flowers = ["玫瑰", "百合", "康乃馨"]
prices = ["50", "30", "20"]
# 循环调用Text模型的Completion方法,生成文案
for flower, price in zip(flowers, prices):
prompt = prompt_text.format(price, flower)
response = openai.completions.create(
engine="gpt-3.5-turbo-instruct",
prompt=prompt,
max_tokens=100
)
print(response.choices[0].text.strip()) # 输出文案
其实也很简洁,那 LangChain 存在的意义是什么呢?其实深入思考一下就会发现 LangChain 的优势所在。对比单纯使用 f-string 来格式化文本生成 prompt,LangChain 通过定义模版的做法更加简洁,也更容易维护,并且 LangChain 在提示模板中,还整合了output_parser、template_format 以及是否需要 validate_template 等功能。更重要的是,使用 LangChain 提示模板,我们可以很方便地把程序切换到不同的模型,而不需要修改任何提示相关的代码。这跟深度学习中的 PyTorch 和 TensorFlow 框架如出一辙——模型可以自由选择、自主训练,而调用模型的框架往往是有章法、而且可复用的。
总的来说,使用 LangChain 和提示模版的好处是:
- 增强可读性:模板使得提示文本的阅读和理解变得更加直观,尤其适用于处理复杂的提示或涉及多个变量的情况。这种清晰性有助于其他开发者更快地理解代码逻辑。
- 提高可复用性:通过模板,你可以在多个场景中重用相同的提示逻辑,从而避免重复编写提示字符串。这不仅使代码更加简洁,也减少了代码冗余。
- 简化维护:当需要对提示进行修改时,模板提供了集中管理的优势。你只需在模板中进行更改,而无需在代码库中搜索和更新所有使用该提示的地方。
- 自动化变量处理:模板能够自动处理提示中的变量插入,不需要手动拼接字符串。这不仅减少了出错的可能性,也提高了代码的安全性和可靠性。
- 支持参数化:模板允许根据不同的参数生成定制化的提示,这对于创建个性化的文本内容至关重要。参数化使得模板更加灵活,能够适应各种不同的使用场景。
5. 输出解析
LangChain 提供的解析模型输出的功能,能够帮助开发者从模型生成的文本中提取关键信息。输出解析器不仅能够去除冗余数据,还能将非结构化文本转换成结构化数据,从而便于程序进一步处理和分析,这将大大加快基于语言模型进行应用开发的效率。
输出解析之所以重要,与大模型输出的不确定性是分不开的。因为大模型的输出本质上是通过 token 的概率预测来生成的,而只要是概率就存在不确定性。因此就算是完全一致的输入,模型也可能返回不同的回复,更不用说意义相近的 promt 了。
尽管这种不确定性让模型更具创造性,但却给我们应用的开发带来了困难。因为在应用开发过程中,我们不仅仅需要文字,更多情况下我们需要的是程序能够直接处理的、结构化的数据。所以,如何从大模型的输出中提取出我们需要的数据和信息对我们的开发至关重要。(至于如何让大模型尽可能生成包含我们需要信息的输出这就是提示工程需要考虑的事了)。
下面,我们还是以一个简单的例子来体会 LangChain 中输出解析器的作用。
import os
# 导入LangChain中的提示模板
from langchain.prompts import PromptTemplate
# 创建提示模板
prompt_template = """您是一位专业的鲜花店文案撰写员。
对于售价为 {price} 元的 {flower_name} ,您能提供一个吸引人的简短描述吗?
{format_instructions}"""
# 通过LangChain调用模型
from langchain_openai import OpenAI, ChatOpenAI
# 创建模型实例
model = ChatOpenAI(model=os.environ.get("LLM_MODELEND"))
# 导入结构化输出解析器和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}
)
print('\n',prompt,'\n')
# 数据准备
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.content)
# 在解析后的输出中添加“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)
在这个程序中,我们利用了 LangChain 库来生成鲜花的描述性文案,并将结果存储在一个 Pandas DataFrame 中,最后将 DataFrame 保存为 CSV 文件。
在这段代码中,我们首先定义了希望从模型中获取的输出格式:包括鲜花的描述("description")和撰写理由("reason")。我们创建了一个包含这些期望输出的 response_schemas 列表,其中包含两个 ResponseSchema 对象,分别对应这两部分的输出,并根据这个列表,使用StructuredOutputParser.from_response_schemas 方法创建了一个输出解析器。然后我们通过输出解析器对象的 get_format_instructions() 方法获取输出的格式说明(format_instructions),再根据原始的字符串模板和输出解析器格式说明创建新的提示模板。通过添加输出格式说明到提示模版中,模型的输出结构将尽最大可能遵循我们的指示,以便于输出解析器进行解析。
重构后的提示模版如下:
"""您是一位专业的鲜花店文案撰写员。
对于售价为 {price} 元的 {flower_name} ,您能提供一个吸引人的简短描述吗?
The output should be a markdown code snippet formatted in the following schema, including the leading and trailing "```json" and "```":
```json
{{
\t"description": string // 鲜花的描述文案
\t"reason": string // 问什么要这样写这个文案
}}
可以看到,
format_instructions其实就是对模型输出格式的一段补充,这里我们设置的响应模式要求模型返回一个 json 格式的输出,包含鲜花描述文案和撰写理由。
在得到模型回复之后,我们还需要用 output_parser.parse(output) 把模型输出的文案解析成之前定义好的数据格式,也就是一个 Python 字典,这个字典中包含了 description 和 reason 这两个字段的值。
最后,把所有信息整合到一个 pandas DataFrame 对象中并保存成 CSV 文件,此时数据不再是模糊的、无结构的文本,而是结构清晰的有格式的数据,这样我们在程序中就能很方便地处理这些数据了~
6. 总结
总的来说,使用 LangChain 框架有以下几个好处:
- 模板管理:通过 LangChain,项目中的众多提示模板能够得到有效管理,这有助于维护代码的整洁和组织性,从而提高项目的可维护性。
- 变量处理:LangChain 具备自动提取和检查模板变量的功能,这可以防止因遗漏变量而引发的错误,确保了模板的正确填充和使用。
- 灵活的模型切换:在需要尝试不同模型时,LangChain允许我们通过简单更改模型名称来实现而无需对代码进行大规模修改,这大大提升了开发效率和灵活性。
- 输出解析:LangChain 的提示模板支持嵌入输出格式的定义,让我们在处理模型输出时,可以更加方便地解析和利用已经格式化的数据,简化了后续的处理流程。
通过这些功能,LangChain 不仅提升了开发效率,还确保了代码的质量和项目的可扩展性。