如何使用LangChain、RStudio和足够的Python生成人工智能

译文
人工智能
LangChain是最热门的大型语言模型和生成式人工智能平台之一,但它通常只适用于Python和JavaScript用户。人们需要了解R用户如何绕过这个问题的方法。

译者 | 李睿

审校 | 重楼

LangChain是当今最热门的开发平台之一,用于创建使用生成式人工智能的应用程序,但它只适用于Python和JavaScript。对于想要使用LangChain的R程序员,该怎么办?

幸运的是,可以使用非常基本的Python代码在LangChain中做很多有用的事情。而且,多亏有了reticulate R包,R和RStudio用户可以在他们熟悉的环境中编写和运行Python,包括在Python和R之间来回传递对象和数据。

在这个LangChain教程中,将展示如何使用Python和R来访问LangChain和OpenAI API。这将允许使用大型语言模型(LLM)来查询ggplot2的300页PDF文档。第一个示例查询:“如何在图形的x轴上旋转文本?”

以下是介绍这个过程的每个步骤:

(1)如果还没有运行,需要将系统设置为运行Python和reticulate。

(2)将ggplot2 PDF文档文件导入为纯文本的LangChain对象。

(3)将文本分割成可以被大型语言模型读取的更小的部分,因为这些模型对一次读取的数量有限制。而长达300页的文本将超出OpenAI公司的限制。

(4)使用LLM为每个文本块创建“嵌入”,并将它们全部保存在数据库中。嵌入是一串数字,表示多维空间中文本的语义。

(5)为用户的问题创建一个嵌入,然后将问题嵌入与文本中的所有现有问题进行比较。查找并检索最相关的文本片段。

(6)只将那些相关的文本部分输入到像GPT-3.5这样的LLM中,并要求它生成答案。

如果用户要遵循示例并使用OpenAI API,则需要一个API密钥。可以在platform.openai.com注册。如果愿意使用另一个模型,LangChain有组件可以为许多LLM构建链,而不仅仅是OpenAI公司的组件,因此用户不会被某个LLM提供商锁定。

LangChain拥有可以轻松处理这些步骤的组件,特别是如果企业对其默认值感到满意的话。这就是为什么它变得如此受欢迎的原因。

以下开始逐步实施:

步骤1:设置系统在RStudio中运行Python

如果用户已经运行了Python和reticulate,则可以跳到下一步。否则,确保在系统上有最新版本的Python。有很多方法可以安装Python,但只需要从python.org网站中下载就可以。然后,按照通常的方式使用install.packages("reticulate")安装reticulate R包。

如果已经安装了Python,但是reticulate找不到它,可以使用命令use_Python(“/path/to/your/Python”)。

library(reticulate)
virtualenv_create(envname = "langchain_env", packages = c( "langchain", "openai", "pypdf", "bs4", "python-dotenv", "chromadb", "tiktoken")) # Only do this once

注意,用户可以随意命名其环境。如果需要在创建环境后安装软件包,使用py_install(),如下所示:

py_install(packages = c( "langchain", "openai", "pypdf", "bs4", "python-dotenv", "chromadb", "tiktoken"), envname = "langchain_env")

与在R中一样,用户应该只需要安装一次软件包,而不是每次需要使用环境时都安装软件包。另外,不要忘记激活虚拟环境:

use_virtualenv("langchain_env")

每次回到项目时,在开始运行Python代码之前,都要这样做。

用户可以测试其Python环境是否正在使用

reticulate::py_run_string('
print("Hello, world!") 
')

如果用户喜欢的话,可以采用Python变量设置OpenAI API密钥。因为已经在一个R环境变量中有了它,通常使用R来设置OpenAI API密钥。用户可以使用reticulate的r_to_py()函数将任何R变量保存为python友好的格式,包括环境变量:

api_key_for_py <- r_to_py(Sys.getenv("OPENAI_API_KEY"))

它接受OPENAI_API_KEY环境变量,确保它是Python友好的,并将其存储在一个新变量中:api_key_for_py(同样,可以采用任何名称)。

最后,准备好编写代码!

步骤2:下载并导入PDF文件

将在主项目目录下创建一个新的docs子目录,并使用R在那里下载文件。

# Create the directory if it doesn't exist
if(!(dir.exists("docs"))) {
 dir.create("docs")
}

# Download the file
download.file("https://cran.r-project.org/web/packages/ggplot2/ggplot2.pdf", destfile = "docs/ggplot2.pdf", mode = "wb")

接下来是Python代码,将该文件导入为包含内容和元数据的LangChain文档对象。将为此创建一个名为prep_docs.py的新Python脚本文件。可以像上面那样使用py_run_string()函数在R脚本中继续运行Python代码。然而,如果用户正在处理一个更大的任务,那么就太理想,因为将会在诸如代码完成之类的事项上面临失败。

Python新手的关键点:不要将脚本文件的名称与将要加载的Python模块的名称相同!换句话说,虽然该文件不必命名为prep_docs.py,但如果要导入langchain包,就不要将其命名为langchain.py !它们会发生冲突。这在R中并不是问题。

以下是新的prep_docs.py文件的第一部分:

If running from RStudio, remember to first run in R:
# library(reticulate)
# use_virtualenv("the_virtual_environment_you_set_up")
# api_key_py <- r_to_py(Sys.getenv("OPENAI_API_KEY"))
from langchain.document_loaders import PyPDFLoader
my_loader = PyPDFLoader('docs/ggplot2.pdf')
# print(type (my_loader))
all_pages = my_loader.load()
# print(type(all_pages)) 
print( len(all_pages) )

这段代码首先导入PDF文档加载器PyPDFLoader。接下来,它创建PDF加载器类的一个实例。然后,它运行加载器及其load方法,将结果存储在一个名为all_pages的变量中。该对象是一个Python列表。

在这里包含了一些注释行,如果想看到它们,它们将打印对象类型。最后一行打印列表的长度,在本例中是304。

可以点击RStudio中的source按钮来运行一个完整的Python脚本。或者,突出显示一些代码行并只运行它们,就像使用R脚本一样。Python代码在运行时看起来与R代码略有不同,因为它在R控制台中打开一个Python交互式REPL会话。用户将被指示输入exit或quit(没有括号)以退出并在完成后返回常规R控制台。

用户可以使用reticulate的py对象在R中检查all_pages Python对象。下面的R代码将Python all_pages对象存储到一个名为all_pages_in_r的R变量中(用户可以随意调用它)。然后,可以像处理任何其他R对象一样处理该对象。在本例中,它是一个列表。

all_pages_in_r <- py$all_pages
# Examples:
all_pages_in_r[[1]]$metadata # See metadata in the first item
nchar(all_pages_in_r[[100]]$page_content) # Count number of characters in the 100th item

LangChain集成

如果用户还没有最喜欢的将PDF转换为可读文本的方法,那么LangChain的PyPDFLoader可以方便地用于其他非人工智能项目。而且,LangChain还有100多种其他文件加载器,包括PowerPoint、Word、网页、YouTube、epub、Evernote和Notion等格式。可以在LangChain集成中心中看到一些文件格式和集成文档加载器。

步骤3:将文档拆分为多个部分

LangChain有几个转换器可以将文档分解成块,包括按字符、标记和标记头进行拆分。一个推荐的默认值是RecursiveCharacterTextSplitter,它将“递归地尝试按不同的字符进行拆分,以找到一个有效的字符”。另一个流行的选项是CharacterTextSplitter,它的设计目的是让用户设置其参数。

用户可以设置该拆分器的最大文本块大小,是按字符计数还是按LLM令牌计数(令牌通常是1到4个字符),以及文本块应该重叠多少。在开始使用LangChain之前,从未考虑过文本块重叠的必要性,但它是有意义的,除非用户可以通过逻辑块(如用标题分隔的章节或节)来分隔。否则,文本可能会在句子中间被拆分,一个重要的信息可能会被分成两个部分,其中任何一个都没有明确的完整含义。

用户还可以选择希望拆分器在分割文本时优先考虑哪些分隔符。CharacterTextSplitter的默认值是首先拆分为两个新行(\n\n),然后再拆分一个新行、一个空格,最后完全不使用分隔符。

下面的代码通过使用Python内部的reticulate的R对象,从R api_key_for_py变量导入OpenAI API密钥。它还加载openai Python包和LangChain的递归字符分割器,创建一个RecursiveCharacterTextSplitter类的实例,并在all_pages块上运行该实例的split_documents()方法。

import openai
openai.api_key = r.api_key_for_py 
from langchain.text_splitter import RecursiveCharacterTextSplitter
my_doc_splitter_recursive = RecursiveCharacterTextSplitter()
my_split_docs = my_doc_splitter_recursive.split_documents(all_pages)

同样,用户可以用R代码将这些结果发送给R,例如:

My_split_docs <- py$ My_split_docs

是否想知道块中的最大字符数是多少?可以用R中的一个自定义函数来检查这个列表:

get_characters <- function(the_chunk) {
x <- nchar(the_chunk$page_content)
return(x)
}

purrr::map_int(my_split_docs, get_characters) |>
max()

这将生成3,985个字符,因此看起来默认的块最大值是4,000个字符。

如果想要更小的文本块,首先尝试CharacterTextSplitter并人工地将chunk_size设置为小于4,000,例如

chunk_size = 1000
chunk_overlap = 150
from langchain.text_splitter import CharacterTextSplitter
c_splitter = CharacterTextSplitter(chunk_size=chunk_size, chunk_overlap=chunk_overlap, separator=" ")
c_split_docs = c_splitter.split_documents(all_pages)
print(len(c_split_docs)) # To see in Python how many chunks there are now

可以在R和Python中检查结果:

c_split_docs <- py$c_split_docs
length(c_split_docs)

该代码生成695个块,最大为1000。

成本是多少?

在进一步讨论之前,如果想知道为所有这些块生成嵌入是否会非常昂贵。将从默认的306项递归拆分开始。可以计算R对象上这些块中的字符总数:

purrr::map_int(my_split_docs, get_characters) |>
 sum()

答案是513506。保守地估计每个令牌有两个字符,其结果大约是20万个。

如果想更准确,TheOpenAIR R包有一个count_tokens()函数(确保安装该函数和purrr以使用下面的R代码):

purrr::map_int(my_split_docs, ~ TheOpenAIR::count_tokens(.x$page_content)) |>
sum ()

该代码显示了126,343个令牌。

那要花费多少成本?OpenAI用于生成嵌入的模型是ada-2。现在ada-2的1000个令牌价格为0.0001美元,126,000代币的价格约为1.3美分。这些费用在预算之内。

步骤4:生成嵌入

LangChain有预制的组件,可以从文本块创建嵌入并存储它们。对于存储,将使用LangChain中最简单的选项之一:Chroma,这是一个可以在本地使用的开源嵌入数据库。

首先,将用R代码为docs目录创建一个子目录,因为建议在Chroma目录中除了数据库之外什么都不要。这是R代码:

if(!dir.exists("docs/chroma_db")) {
 dir.create("docs/chromaba_db")
}

下面是使用LangChain的OpenAIEmbeddings生成嵌入的一些Python代码。这目前默认为OpenAI的ada-2模型,因此不需要指定它。LangChain通过其嵌入类支持许多其他LLM,包括Hugging Face Hub、Cohere、Llama cpp和Spacy。

下面的Python代码稍微修改了一下DeepLearning.AI的LangChain与其数据聊天在线教程。

from langchain.embeddings.openai import OpenAIEmbeddings
embed_object = OpenAIEmbeddings()

from langchain.vectorstores import Chroma
chroma_store_directory = "docs/chroma_db"

vectordb = Chroma.from_documents(
 documents=my_split_docs,
 embedding=embed_object,
 persist_directory=chroma_store_directory
)

# Check how many embeddings were created
print(vectordb._collection.count())

注意_collection.count()中的下划线!

可以看到有306个嵌入,与ggplot2文本块的数量相同。

Python新手的另一个注意事项:缩进在Python中很重要。确保非缩进行之前没有空格,并且缩进行都使用相同数量的缩进空格。

在这个系统上,这段代码似乎将数据保存到了磁盘上。但是,教程指出用户应该运行以下Python代码来保存嵌入以供以后使用。这样做的原因是不想在文档更改之前重新生成嵌入。

vectordb.persist()

现在,已经完成了为查询准备文档的工作。将创建qanda.py这个新文件,来使用创建的矢量嵌入。

步骤5:嵌入用户查询和查找文档块

现在是时候提出一个问题,为该问题生成嵌入,并根据块的嵌入检索与该问题最相关的文档。

由于vectordb对象的内置方法,LangChain提供了在一行代码中完成所有这些工作的几种方法。它的similarity_search()方法直接计算向量相似度,并返回最相似的文本块。

不过,还有其他几种方法可以做到这一点,包括max_marginal_relevance e_search()。这背后的想法是,不一定想要三个几乎相同的文本块。如果文本中有一点多样性,以获得额外的有用信息,也许最终会得到一个更丰富的回答。因此,max_marginal_relevance e_search()检索的相关文本比实际计划传递给LLM以获取答案的文本多一些(用户决定多出多少)。然后,结合一定程度的多样性,它选择最后的文本片段。

用户可以指定希望similarity_search()返回多少相关文本块及其k参数。对于max_marginal_relevance(),用户指定最初应该使用fetch_k检索多少块,以及希望LLM查找其使用k的答案的最终文本片段。

如果文档没有更改,不想运行文档准备文件,将首先在新的qanda.py文件中加载必要的包和环境变量(即OpenAI API密钥),就像在使用doc_prepare .py之前所做的那样。然后,将加载chromadb矢量数据库:

# If running from RStudio, remember to first run in R:
# library(reticulate)
# use_virtualenv("the_virtual_environment_you_set_up")
# api_key_py <- r_to_py(Sys.getenv("OPENAI_API_KEY"))

import openai
openai.api_key = r.api_key_for_py 
from langchain.embeddings.openai import OpenAIEmbeddings
embed_object = OpenAIEmbeddings()

from langchain.vectorstores import Chroma
chroma_store_directory = "docs/chroma_db"
vectordb = Chroma(persist_directory=chroma_store_directory, 
 embedding_functinotallow=embed_object)

接下来,将硬编码一个问题并检索相关文档。需要注意,可以用一行代码检索文档:

my_question = "How do you rotate text on the x-axis of a graph?"
# For straightforward similarity searching
sim_docs = vectordb.similarity_search(my_question)

# For maximum marginal relevance search retrieving 5 possible chunks and choosing 3 finalists:
mm_docs = vectordb.max_marginal_relevance_search(my_question, k = 3, fetch_k = 5)

如果想查看检索到的文档片段,可以在Python中打印它们,如下所示:

for doc in mm_docs:
 print(doc.page_content)

for doc in sim_docs:
 print(doc.page_content)

注意缩进是for循环的一部分。

还可以使用以下命令查看它们的元数据:

for doc in mm_docs:
 print(doc.metadata)

for docs in sim_docs:
 print(docs.metadata)

与其他对象一样,也可以在R中查看这些对象:

mm_relevant <- py$mm_docs
sim_relevant <- py$sim_docs

不确定为什么当请求三个文档时,模型有时会返回四个文档,但这应该不是问题,除非LLM在遍历文本以生成响应时有太多的令牌。

步骤6:生成答案

现在是时候让GPT-3.5这样的LLM根据相关文档生成对用户问题的书面回复了。可以使用LangChain的RetrievalQA功能来实现这一点。

建议首先尝试LangChain的默认模板,这很容易实现,通常适用于原型制作或用户自己使用:

# Set up the LLM you want to use, in this example OpenAI's gpt-3.5-turbo
from langchain.chat_models import ChatOpenAI
the_llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0)

# Create a chain using the RetrievalQA component
from langchain.chains import RetrievalQA
qa_chain = RetrievalQA.from_chain_type(the_llm,retriever=vectordb.as_retriever())

# Run the chain on the question, and print the result
print(qa_chain.run(my_question))

LLM给出了以下回复:

To rotate text on the x-axis of a graph, you can use the `theme()` function in ggplot2. Specifically, you can use the `axis.text.x` argument to modify the appearance of the x-axis text. Here is an example:

```R
library(ggplot2)

# Create a basic scatter plot
p <- ggplot(mtcars, aes(x = mpg, y = wt)) +
 geom_point()

# Rotate x-axis text by 45 degrees
p + theme(axis.text.x = element_text(angle = 45, hjust = 1))
```

In this example, the `angle` argument is set to 45, which rotates the x-axis text by 45 degrees. The `hjust` argument is set to 1, which aligns the text to the right. You can adjust the angle and alignment values to achieve the desired rotation and alignment of the x-axis text.

看起来正确的!

现在链已经设置好了,可以用一个R脚本用一个命令在其他问题上运行它:

py_run_string('
print(qa_chain.run("How can I make a bar chart where the bars are steel blue?"))
')

以下是它们的回应:

```R
library(ggplot2)

# Create a bar chart with steel blue bars
p <- ggplot(mtcars, aes(factor(cyl)))
p + geom_bar(fill = "steelblue")
```

In this example, we use the `fill` aesthetic to specify the color of the bars as "steelblue". You can adjust the color to your preference by changing the color name or using a hexadecimal color code.

这是一个比在ChatGPT 3.5中有时收到的相同问题可获得更好的答案。有时它发回的代码实际上并不起作用。

用户可能还想确认答案不是从一般的ChatGPT知识库中提取的,而是真正来自其上传的文档。为了找到答案,可以问一些与ggplot2完全无关的问题,这些问题不会出现在文档中:

py_run_string('
print(qa_chain.run("What is the capital of Australia?"))
')

户应该这样回复:

I don't know.

如果正在创建一个应用程序以供更广泛的使用,那么“我不知道”可能有点简洁。如果用户想定制默认模板,可以查看LangChain文档。如果用户正在为不止自己或一个小团队创建应用程序,那么个性化响应是有意义的。

模板调整是LangChain可能感觉过于复杂的一个领域,它可能需要多行代码来实现对模板的小更改。然而,使用任何固执己见的框架都是有风险的,这取决于每个开发人员来决定项目的总体收益是否值得这样的成本。虽然它非常受欢迎,但并不是每个人都是LangChain的忠实用户。

还可以用LangChain做什么?

到目前为止,对应用程序最简单的添加是包含更多文档。LangChain有一个DirectoryLoader来简化这个过程。如果用户正在跨多个文档进行搜索,可能希望知道哪些文档用于生成响应。可以给RetrievalQA添加return_source_documents=True参数,如下所示:

qa_chain = RetrievalQA.from_chain_type(the_llm,retriever=vectordb.as_retriever(), return_source_documents=True) 
my_result = qa_chain({"query": my_question})
print(my_result['result'])

该代码最初只对单个用户在本地运行有用,但它可以成为使用Streamlit或Shiny for Python等框架的交互式Web应用程序的逻辑基础。或者,将Python和R结合起来,将LLM的最终答案发送回R,并使用Shiny R Web框架创建一个应用程序(尽管发现同时使用Python和R部署Shiny应用程序有点复杂)

还要注意的是,这个应用程序在技术上并不是一个“聊天机器人”,因为它不会记住用户之前的问题。所以,不能有一个“对话”,例如“如何改变图表标题文字的大小?”,然后是“图例呢?”用户需要把每个新单词拼出来。

但是,可以向应用程序添加内存,使用LangChain的ConversationBufferMemory将其转换为聊天机器人。

其他资源

要了解更多关于LangChain的信息,除了LangChain文档之外,还有一个LangChain Discord服务器,其中有一个人工智能聊天机器人kapa。它可以查询文档。

原文标题:Generative AI with LangChain, RStudio, and just enough Python,作者:Sharon Machlis


责任编辑:华轩 来源: 51CTO
相关推荐

2023-10-12 10:14:54

2024-03-25 19:47:00

AIGC数据治理

2024-02-28 08:00:00

人工智能Slackbot

2023-11-07 10:20:22

人工智能AI

2023-05-05 14:02:59

人工智能聊天机器人

2024-02-21 10:42:08

人工智能机器学习企业技术堆栈

2023-05-06 10:57:06

人工智能OpenAI

2023-08-02 18:26:31

2024-03-01 10:00:09

2023-08-15 11:13:17

人工智能医疗保健

2024-01-25 11:44:14

人工智能生成式人工智能

2023-08-25 18:33:56

人工智能神经网络

2022-11-30 13:28:53

人工智能AI

2024-04-18 16:12:10

2022-07-29 15:47:25

人工智能AI

2023-08-29 11:36:49

2021-09-24 08:00:00

人工智能ITAV

2021-11-11 14:55:35

人工智能AI

2021-09-22 14:47:10

人工智能IT AV

2023-07-17 13:41:26

人工智能测试数据
点赞
收藏

51CTO技术栈公众号