LangChain 系列 - 快速上手 LangChain

LangChain 是一个用于开发基于大语言模型的应用的基础框架。本文介绍了 LangChain 的最基本概念和如何开始使用。

自从 ChatGPT 大受欢迎之后,大语言模型(LLM)成为了一个新的热点。大家都在思考如何将大语言模型集成到自己的应用场景中。最简单的集成方案是直接调用 OpenAI 的 API 来构建自己的系统。大语言模型不仅仅是 OpenAI 的,还有 LLaMA、Chatglm2、Falcon 等可选。同时,我们也希望将大语言模型和我们的业务数据结合起来使用。

LangChain 的诞生旨在解决 LLM 的工程化问题。LangChain 通过抽象 LLM 模型层、引入数据交互模块、记忆模块和回调模块等极大地简化了构建 LLM 工程的流程,统一了各种语言模型接口,构建好的应用可以很方便地切换不同的语言模型进行调试。

PS: 这个思路和我 2020 年开源的 Kashgari 框架的思路一样嘿嘿。

安装

1
2
3
pip install langchain
# 为了方便快速上手,我们先使用 OpenAI LLM
pip install openai

我们需要申请一个 OpenAI API key 来使用,申请网址,需要科学上网。

LangChain 三大模块

现在我们可以尝试构建自己的第一个应用。LangChain 提供了许多模块,可用于构建语言模型应用程序。这些模块可以作为独立模块在简单的应用程序中使用,也可以结合在一起用于更复杂的用例。

LangChain 中最核心的模块 LLMChain。LLMChain 由以下几个模块组成。

  • LLM: 语言模型是这里的核心推理引擎。为了使用 LangChain,需要了解不同类型的语言模型以及如何与它们一起工作。
  • Prompt Template:Prompt Templates 提供了对语言模型的指令。它控制语言模型的输出,因此了解如何构建提示和不同的提示策略至关重要。
  • Output Parser:这些将 LLM 的原始响应转换为更易处理的格式,使得在下游使用输出变得更加容易。

在这个入门指南中,我们将单独介绍这三个组件,然后介绍将它们结合起来的 LLMChain。理解这些概念将为能够使用和自定义 LangChain 应用程序奠定良好的基础。大多数 LangChain 应用程序允许配置 LLM 和/或使用的提示,因此了解如何利用这一点将成为构建 LLM 应用的核心。

LLM

LangChain 中有两类语言模型,分别是

  • LLMs: 这是一个以字符串作为输入并返回字符串的语言模型。
  • ChatModel:这是一个以 ChatMessage 数组作为输入并返回 ChatMessage 的语言模型。

ChatMessage 实际上是包含以下两个元素的对象。

  • content: 消息的内容,字符串形式。
  • role: 消息的来源角色。

下面几个 ChatMessage 子类是内置 role 角色信息的 Message 对象,方便我们快速使用。

  • HumanMessage: 一个来自用户的 ChatMessage 对象。
  • AIMessage: 一个来自 AI 的 ChatMessage 对象。
  • SystemMessage: 一个来自系统的 ChatMessage 对象。
  • FunctionMessage: 一个来自会函数调用的 ChatMessage 对象。

如果上面的四个预定义角色不符合任务需求,可以使用 ChatMessage 自定义角色。

LangChain 中的语言模型都有统一的推理接口。标准的推理接口都包含以下俩方法:

  • predict: 输入一个字符串,返回一个字符串。
  • predict_messages: 输入一个 ChatMessage 数组,返回一个 ChatMessage 对象。

首先我们实例化两个语言模型

1
2
3
4
5
from langchain.llms import OpenAI
from langchain.chat_models import ChatOpenAI

llm = OpenAI(openai_api_key=APIKEY)
chat_model = ChatOpenAI(openai_api_key=APIKEY)

注意其中 OpenAI 和 ChatOpenAI 还能传入很多基础参数,比如 model_name, temperaturemax_tokens 等。更多参数可以查阅 API 文档.

实例化语言模型后,我们先尝试 predict 接口,代码如下。可以看到返回均为字符串对象。

1
2
3
4
5
6
7
result1 = llm.predict("hi!")
print(result1, type(result1))
>>> "I'm here to help you with your queries. How can I help you today? <class 'str'>"

result2 = chat_model.predict("hi!")
print(result2, type(result2))
>>> "Hello! How can I assist you today? <class 'str'>"

现在我们尝试使用 predict_messages 接口,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
from langchain.schema import HumanMessage

text = "给游戏起一个好的名字,移动端幻想 RPG 游戏,二次元风格,中欧世界,"
messages = [HumanMessage(content=text)]

result1 = llm.predict_messages(messages)
print(result1, type(result1))
>>> content='暗黑气氛\n\n未来梦幻:影魅之渊' additional_kwargs={} example=False <class 'langchain.schema.messages.AIMessage'>

result2 = chat_model.predict_messages(messages)
print(result2, type(result2))
>>> content='幻世之旅' additional_kwargs={} example=False <class 'langchain.schema.messages.AIMessage'>

上面两个推理接口还能传入 temperature 等参数来调整本次的输出。调用函数时候传入参数将会覆盖初始化语言模型使用的参数。

Prompt Template

大多数 LLM 应用程序不会直接将用户输入传递给 LLM。通常,它们会将用户输入添加到一个更大的文本片段中,称为提示模板,该模板提供了有关当前具体任务的附加上下文。

在先前的示例中,我们传递给模型的文本包含了生成游戏的指令。对于我们的应用程序来说,如果用户只需要提供游戏的描述,而不必担心给模型提供指令,那会极大方便用户的使用。

PromptTemplates 正好可以解决这个问题!它们将从用户输入到完全格式化的提示的所有逻辑打包在一起。这可以非常简单地开始 - 例如,为了生成上述字符串,一个提示只需是:

1
2
3
4
5
from langchain.prompts import PromptTemplate

prompt = PromptTemplate.from_template("给游戏起一个好的名字, {game}")
print(prompt.format(game="移动端卡牌游戏,休闲轻松"))
>>> 给游戏起一个好的名字, 移动端卡牌游戏,休闲轻松

这时候你估计就要说,那我为什么不直接用字符串拼接来完成这一步骤。当然,字符串拼接完全可以做到这个 Prompt 构建过程,但是 PromptTemplate 提供了一些便利的方法,比如我可以一次只填写部分的变量。可以把多个 PromptTemplate 组合在一起构成一个 prompt 等。

下面就是用多个 PromptTemplate 组合出一个 prompt 的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from langchain.prompts.chat import (
ChatPromptTemplate,
SystemMessagePromptTemplate,
HumanMessagePromptTemplate,
)

template = "You are a helpful assistant that translates {input_language} to {output_language}."
system_message_prompt = SystemMessagePromptTemplate.from_template(template)
human_template = "{text}"
human_message_prompt = HumanMessagePromptTemplate.from_template(human_template)

chat_prompt = ChatPromptTemplate.from_messages([system_message_prompt, human_message_prompt])

chat_prompt.format_messages(input_language="English", output_language="French", text="I love programming.")

运行结果为

1
2
3
4
[
SystemMessage(content='You are a helpful assistant that translates English to French.', additional_kwargs={}),
HumanMessage(content='I love programming.', additional_kwargs={}, example=False)
]

Output Parser

OutputParser 用于将 LLM 的原始输出转换为可在下游应用使用的格式。常见的 OutputParser 用途有:

  • 将 LLM 的文本转换为结构化信息(例如 JSON)
  • 将 ChatMessage 转换为字符串
  • 将调用返回的消息以外的额外信息(例如 OpenAI 函数调用)转换为字符串。

我们定义一个最小自定义 OutputParser,代码如下:

1
2
3
4
5
6
7
8
9
10
from langchain.schema import BaseOutputParser

class CommaSeparatedListOutputParser(BaseOutputParser):
"""将 LLM 的输出使用逗号分隔为数组"""

def parse(self, text: str):
return text.strip().split(", ")

CommaSeparatedListOutputParser().parse("hi, bye")
# >> ['hi', 'bye']

LLMChain 组合

现在我们把前面的三大模块组合起来构造我们的 LLMChain。这个 chain 将会接受变量输入,将输入转换为 prompt,再把 prompt 传入 LLM 得到结果,并用 Parser 把结果解析成程序输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from langchain.chat_models import ChatOpenAI
from langchain.prompts.chat import (
ChatPromptTemplate,
SystemMessagePromptTemplate,
HumanMessagePromptTemplate,
)
from langchain.chains import LLMChain
from langchain.schema import BaseOutputParser

class CommaSeparatedListOutputParser(BaseOutputParser):
"""Parse the output of an LLM call to a comma-separated list."""


def parse(self, text: str):
"""Parse the output of an LLM call."""
return text.strip().split(", ")

# 实验发现英文 prompt 效果还是比中文好一些,所以系统模板尽量使用英文去写
template = """You are a helpful assistant who generates awesome game names in comma separated lists.
A user will pass description of his dream game, and you will generate a list of names in Chinese for the user.
ONLY return a comma separated list, and nothing more."""
system_message_prompt = SystemMessagePromptTemplate.from_template(template)
human_template = "{text}"
human_message_prompt = HumanMessagePromptTemplate.from_template(human_template)

chat_prompt = ChatPromptTemplate.from_messages([system_message_prompt, human_message_prompt])
chain = LLMChain(
llm=ChatOpenAI(openai_api_key=APIKEY),
prompt=chat_prompt,
output_parser=CommaSeparatedListOutputParser()
)
chain.run("移动端三消游戏,主打炫酷画面和潮玩")
# >> ['炫酷三消', '潮玩消消乐', '移动炫消', '画面狂炫', '酷玩三消']

常见问题解决

charset_normalizer has no attribute md__mypyc

1
AttributeError: partially initialized module 'charset_normalizer' has no attribute 'md__mypyc' (most likely due to a circular import)

如果遇到上面的报错,可以通过重新安装 charset-normalizer 特定版本,然后重启 Jupyter 来解决。

1
pip install --force-reinstall charset-normalizer==3.1.0

参考列表