引言

在自然语言处理 (NLP) 领域,LangChain 是一个功能强大的 Python 库,它赋予开发人员和研究人员创建、实验和分析语言模型和智能代理的能力。它提供了丰富的功能,可以从构建自定义模型到高效处理文本数据。本篇综合指南将深入介绍 LangChain 的重要组件,并演示如何在 Python 中发挥其强大的功能。

安装设置

在开始本指南之前,您需要创建一个新的文件夹,并使用 pip 安装 LangChain 和 OpenAI 库:

plaintext
1
pip3 install langchain openai

代理

在 LangChain 中,** 代理 (Agent)** 是一种可以理解和生成文本的实体。这些代理可以配置特定的行为和数据源,并经过训练以执行各种与语言相关的任务,使其成为广泛应用于各种应用的多功能工具。

创建 LangChain 代理

代理可以配置使用 “工具” 来收集所需的数据并生成良好的响应。以下是一个示例,它使用 Serp API (互联网搜索 API) 来搜索与问题或输入相关的信息,并使用 llm-math 工具执行数学运算,例如单位转换或查找两个值之间的百分比变化:

python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from langchain.agents import load_tools
from langchain.agents import initialize_agent
from langchain.agents import AgentType
from langchain.llms import OpenAI
import os

os.environ["OPENAI_API_KEY"] = "YOUR_OPENAI_API_KEY"
os.environ["SERPAPI_API_KEY"] = "YOUR_SERP_API_KEY" # 在这里获取您的Serp API密钥:https://serpapi.com/

OpenAI.api_key = "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"
llm = OpenAI(model="gpt-3.5-turbo", temperature=0)
tools = load_tools(["serpapi", "llm-math"], llm=llm)
agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True)
agent.run("风力涡轮机在2022年全球产生了多少能量?")

在上面的代码中,我们首先进行了基本的导入和初始化 LLM (llm = OpenAI (model=”gpt-3.5-turbo”, temperature=0)), 然后使用 tools = load_tools ([“serpapi”, “llm-math”], llm=llm) 加载代理工作所需的工具。接下来,我们使用 initialize_agent 函数创建代理,给定指定的工具,并分配 ZERO_SHOT_REACT_DESCRIPTION 描述,这意味着它不会记得先前的问题。

代理测试示例 1

让我们使用以下输入对该代理进行测试:

plaintext
1
"风力涡轮机在2022年全球产生了多少能量?"

如下图所示:

测试代理

如上所示,它使用以下逻辑:

  • 使用 Serp 互联网搜索 API 搜索 “2022 年全球风力涡轮机能源产量”
  • 分析最佳结果
  • 获取任何相关的数字
  • 使用 llm-math 工具将 906 吉瓦转换为焦耳,因为我们询问的是能量,而不是功率

代理测试示例 2

LangChain 代理不限于搜索互联网。我们可以将几乎任何数据源 (包括我们自己的数据源) 连接到 LangChain 代理,并向其提问有关数据的问题。让我们尝试使用训练过的基于 CSV 数据集的代理。

从 Kaggle 上下载 Netflix 电影和电视节目数据集 (由 SHIVAM BANSAL 提供), 将其移动到您的目录中。现在将以下代码添加到一个新的 Python 文件中:

python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from langchain.llms import OpenAI
from langchain.chat_models import ChatOpenAI
from langchain.agents.agent_types import AgentType
from langchain.agents import create_csv_agent
import os

os.environ["OPENAI_API_KEY"] = "YOUR_OPENAI_API_KEY"

agent = create_csv_agent(
OpenAI(temperature=0),
"netflix_titles.csv",
verbose=True,
agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
)

agent.run("Christian Bale出演了多少部电影?")

这段代码调用 create_csv_agent 函数,并使用 netflix_titles.csv 数据集。下图展示了我们的测试结果。

正如上面所示,它的逻辑是在 “cast” 列中查找所有出现的 “Christian Bale”。

我们还可以使用 Pandas 的 Dataframe 代理,代码如下:

python
1
2
3
4
5
6
7
8
9
10
11
12
13
from langchain.agents import create_pandas_dataframe_agent
from langchain.chat_models import ChatOpenAI
from langchain.agents.agent_types import AgentType
from langchain.llms import OpenAI
import pandas as pd
import os

os.environ["OPENAI_API_KEY"] = "YOUR_OPENAI_KEY"
df = pd.read_csv("netflix_titles.csv")

agent = create_pandas_dataframe_agent(OpenAI(temperature=0), df, verbose=True)

agent.run("什么年份发布了最多的喜剧电影?")

如果运行此代码,将会得到类似下面的结果。

这只是一些示例。我们可以使用 LangChain 与几乎任何 API 或数据集进行集成。

模型

LangChain 中有三种类型的模型:LLM、聊天模型和文本嵌入模型。让我们通过一些示例来探索每种模型的功能。

语言模型

LangChain 提供了一种使用语言模型在 Python 中根据文本输入生成文本输出的方法。它没有聊天模型那么复杂,最适合用于简单的输入 - 输出语言任务。以下是使用 OpenAI 的示例:

python
1
2
3
4
5
6
from langchain.llms import OpenAI
import os
os.environ["OPENAI_API_KEY"] = "YOUR_OPENAI_API_KEY"

llm = OpenAI(model="gpt-3.5-turbo", temperature=0.9)
print(llm("为Matt Nikonorov想一个说唱艺名"))

如上所示,它使用 gpt-3.5-turbo 模型根据提供的输入 (“为 Matt Nikonorov 想一个说唱艺名”) 生成输出。在这个例子中,我将温度设置为 0.9, 以使 LLM 更具创造力。它给出的答案是”MC MegaMatt”。我会给它 9 分 (满分 10 分)。

聊天模型

LLM 模型生成说唱名字很有趣,但如果我们想要更复杂的答案和对话,就需要使用聊天模型,它比语言模型在技术上有所不同。用 LangChain 文档的话来说:

聊天模型是对语言模型的一种变体。虽然聊天模型在内部使用语言模型,但它们使用的接口有所不同。它们不是使用 “输入文本,输出文本” 的 API, 而是使用 “聊天消息” 作为输入和输出的接口。

以下是一个简单的 Python 聊天模型脚本:

python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from langchain.llms import OpenAI
from langchain.chat_models import ChatOpenAI
from langchain.schema import (
AIMessage,
HumanMessage,
SystemMessage
)
import os
os.environ["OPENAI_API_KEY"] = "YOUR_OPENAI_API_KEY"

chat = ChatOpenAI()
messages = [
SystemMessage(content="你是一个友好、非正式的助手"),
HumanMessage(content="说服我Djokovic比Federer更好")
]
print(chat(messages))

如上所示,代码首先发送了一个 SystemMessage, 告诉聊天机器人要友好和非正式,然后发送了一个 HumanMessage, 告诉聊天机器人说服我们 Djokovic 比 Federer 更好。

如果运行这个聊天机器人模型,你会看到类似下面的结果。

嵌入模型

** 嵌入 (Embeddings)** 是一种将文本块中的单词和数字转换为向量的方法,这些向量可以与其他单词或数字相关联。这听起来有点抽象,让我们来看一个示例:

python
1
2
3
4
5
from langchain.embeddings import OpenAIEmbeddings

embeddings_model = OpenAIEmbeddings()
embedded_query = embeddings_model.embed_query("谁创造了万维网?")
embedded_query[:5]

这将返回一个浮点数列表:[0.022762885317206383, -0.01276398915797472, 0.004815981723368168, -0.009435392916202545, 0.010824492201209068]。这就是嵌入的样子。

嵌入模型的应用案例

如果我们想要训练一个聊天机器人或 LLM 来回答与数据或特定文本样本相关的问题,我们需要使用嵌入。让我们创建一个简单的 CSV 文件 (embs.csv), 其中包含一个包含三个信息的 “text” 列:

plaintext
1
2
3
4
text
"Robert Wadlow是有史以来最高的人"
"布尔嘉利发起是最高的摩天大楼"
"玫瑰是红色的"

现在,下面的脚本将根据问题 “有史以来最高的人是谁?” 从 CSV 文件中找到正确的答案,并将其格式化为 JSON 对象:

python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from langchain.embeddings import OpenAIEmbeddings
from openai.embeddings_utils import cosine_similarity
import os
import pandas as pd

os.environ["OPENAI_API_KEY"] = "YOUR_OPENAI_KEY"
embeddings_model = OpenAIEmbeddings()

df = pd.read_csv("embs.csv")

# 对每个信息生成嵌入
emb1 = embeddings_model.embed_query(df["text"][0])
emb2 = embeddings_model.embed_query(df["text"][1])
emb3 = embeddings_model.embed_query(df["text"][2])
emb_list = [emb1, emb2, emb3]
df["embedding"] = emb_list

embedded_question = embeddings_model.embed_query("有史以来最高的人是谁?") # 生成问题的嵌入
df["similarity"] = df.embedding.apply(lambda x: cosine_similarity(x, embedded_question)) # 找到每个数据片段与问题的相关性
df.to_csv("embs.csv")
df2 = df.sort_values("similarity", ascending=False) # 根据与问题的关联性对信息片段进行排序
print(df2["text"][0])

如果运行此代码,将会得到”Robert Wadlow 是有史以来最高的人” 作为输出。这段代码通过获取每个信息片段的嵌入并找到与问题” 有史以来最高的人是谁?” 嵌入最相关的那个来找到正确的答案。嵌入的威力!

分块

LangChain 模型无法一次处理大量文本并生成响应。这就是分块和文本分割的作用。让我们看看两种将文本数据分割为块的简单方法。

按字符分块

为了避免分块时出现突然的中断,我们可以通过在每个换行符或双换行符处分割文本来按段落拆分文本:

python
1
2
3
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(separators=["\n\n", "\n"], chunk_size=2000, chunk_overlap=250)
texts = text_splitter.split_text(your_text)

递归分割块

如果我们想要严格按字符长度拆分文本,可以使用 RecursiveCharacterTextSplitter:

python
1
2
3
4
5
6
7
8
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=2000,
chunk_overlap=250,
length_function=len,
add_start_index=True,
)
texts = text_splitter.create_documents([your_text])

块大小和重叠

在查看上面的示例时,您可能想知道块大小和重叠参数的确切含义以及对性能的影响。可以通过以下两点来解释:

  • 块大小决定了每个块中的字符数量。块大小越大,块中的数据越多,LangChain 处理块的时间和生成输出的时间就越长,反之亦然。

  • 块重叠是用于在块之间共享信息的机制,以便它们共享一些上下文。块重叠越高,块之间的冗余就越多;块重叠越低,块之间共享的上下文就越少。通常,良好的块重叠率在块大小的 10% 到 20% 之间,尽管理想的块重叠率因不同的文本类型和用例而异。

链基本上是将多个 LLM 功能链接在一起,以执行无法通过简单的 LLM“输入 -> 输出” 方式完成的更复杂任务。让我们看一个有趣的示例:

python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
import os
os.environ["OPENAI_API_KEY"] = "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"

llm = OpenAI(temperature=0.9)
prompt = PromptTemplate(
input_variables=["media", "topic"],
template="为{media}关于{topic}制作一个好标题",
)
chain = LLMChain(llm=llm, prompt=prompt)
print(chain.run({
'media': "恐怖电影",
'topic': "数学"
}))

这段代码接受两个变量作为其提示,并根据创造性的答案 (temperature=0.9) 进行格式化。在这个例子中,我们要求它为一部关于数学的恐怖电影想一个好标题。运行此代码后的输出是”The Calculating Curse”, 但这并不能完全展示链的强大之处。

让我们看一个更实际的例子:

python
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 import PromptTemplate
from typing import Optional

from langchain.chains.openai_functions import (
create_openai_fn_chain,
create_structured_output_chain,
)
import os

os.environ["OPENAI_API_KEY"] = "YOUR_KEY"

llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.1)
template = """按照给定的格式从以下输入中提取信息:{input}。确保以正确的格式回答"""
prompt = PromptTemplate(template=template, input_variables=["input"])

json_schema = {
"type": "object",
"properties": {
"name": {"title": "Name", "description": "The artist's name", "type": "string"},
"genre": {"title": "Genre", "description": "The artist's music genre", "type": "string"},
"debut": {"title": "Debut", "description": "The artist's debut album", "type": "string"},
"debut_year": {"title": "Debut_year", "description":
```python
"Year of artist's debut album", "type": "integer"}
},
"required": ["name", "genre", "debut", "debut_year"],
}

chain = create_structured_output_chain(json_schema, llm, prompt, verbose=False)
f = open("Nas.txt", "r")
artist_info = str(f.read())
print(chain.run(artist_info))

这段代码读取一篇关于 Nas (嘻哈艺术家) 的短传记,并从文本中提取以下值,并将它们格式化为 JSON 对象:

  • 艺术家的名字
  • 艺术家的音乐风格
  • 艺术家的首张专辑
  • 艺术家的首张专辑发行年份

在提示中,我们还指定 “确保以正确的格式回答”, 以便始终以 JSON 格式获取输出。这是该代码的输出:

json
1
{'name': 'Nas', 'genre': 'Hip Hop', 'debut': 'Illmatic', 'debut_year': 1994}

通过向 create_structured_output_chain 函数提供 JSON 模式,我们使链将其输出放入 JSON 格式中。

超越 OpenAI

即使我一直在使用 OpenAI 模型作为 LangChain 各种功能的示例,但 LangChain 并不限于 OpenAI 模型。我们可以将 LangChain 与许多其他 LLM 和 AI 服务集成 (这是一个完整的可集成的 LLM 列表)。

例如,我们可以将 LangChain 与 Cohere 一起使用。在安装 Cohere (pip3 install cohere) 后,我们可以使用 LangChain 和 Cohere 编写一个简单的 “问题 -> 答案” 的代码,如下所示:

python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from langchain.llms import Cohere
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain

template = """问题:{question}

答案:让我们一步一步地思考。"""
prompt = PromptTemplate(template=template, input_variables=["question"])
llm = Cohere(cohere_api_key="YOUR_COHERE_KEY")
llm_chain = LLMChain(prompt=prompt, llm=llm)

question = "Novak Djokovic是在哪一天出生的?"

print(llm_chain.run(question))

上面的代码输出如下:

plaintext
1
2
3
The answer is Novak Djokovic was born on May 22, 1987.

Novak Djokovic is a Serbian tennis player.

结论

在本指南中,您已经了解了 LangChain 的不同方面和功能。掌握了这些知识,您现在可以利用 LangChain 在 NLP 领域进行研究、开发或爱好项目。无论是研究人员、开发人员还是爱好者,都可以利用 LangChain 的能力。祝您在 Python 中使用 LangChain 进行愉快的编码和实验!