用GPT-4和ChromaDB向你的文本文件对话:一步一步的教程(LangChain 🦜🔗,ChromaDB,OpenAI嵌入,Web Scraping)。
在这篇文章中使用的笔记本可以在我的GitHub上找到(https://github.com/rubentak/Langchain/blob/main/notebooks/Langchain_doc_chroma.ipynb)。
在使用类似GPT-4或Google的PaLM 2这样的大型语言模型(LLMs)时,您经常会处理大量非结构化文本数据。结构化数据可以存储在SQL数据库中,但对于非结构化数据来说更加困难。例如,当您有大量PDF文件包含某个特定主题的信息时,为了以最高效的方式检索所需数据,最好以不同的方式存储这些信息。解决这个问题的方法是:向量数据库。
在这篇文章中,我将:
•解释什么是向量数据库•解释什么是ChromaDB•网络爬取LangChain文档•将LangChain文档存储在本地的Chroma DB向量数据库中•创建一个检索器来检索所需的信息•使用GPT-4创建一个问答聊天机器人•展示如何在本地删除和重新打开向量数据库以节省空间•可视化您的向量数据库(非常酷,一直读到最后!)
如果您对LangChain不熟悉,请先看一下我之前写的关于LangChain是什么以及如何给您的LLM互联网访问权限的文章:
通过LangChain给OpenAI模型提供互联网访问权限🦜🔗将AI推向新的高度[1]
如果您对使用LLM的力量与SQL或CSV格式与结构化数据库进行交流也感兴趣,我还建议阅读这篇文章:
使用LangChain 🦜🔗用GPT模型与数据库交流(CSV) 在这篇简短的文章中,我将向您展示如何使用大型语言模型(LLM)来提问关于您的数据的问题...[2]
让我们从讨论什么是向量数据库以及为什么它们在处理复杂数据方面如此出色开始。
我听到您在想:向量实际上是什么?
一个向量(或嵌入)是一个数字数组。单单这一点就令人兴奋,但更令人兴奋的是,这些数组可以表示更复杂的数据,如文本、图像、音频甚至视频。就文本而言,这些表示被设计为捕捉词语之间的语义和句法关系,使算法能够更有效地理解和处理语言。
具体而言,词嵌入是根据词语在大量文本语料中的上下文编码词语的意义的稠密向量表示。简单地说,它们把词语映射到高维空间中的数值向量,其中相似的词语彼此更接近。这是在一个向量数据库中完成的。
创建这些嵌入是通过一个嵌入模型完成的。可以使用多个嵌入模型。在本文中,我将使用OpenAI-embeddings模型
text-embedding-ada-002[3]*。
嵌入可以通过以下方式进行可视化:
https://www.glean.com/blog/unlocking-the-power-of-vector-search-in-enterprise
然后,这些嵌入将被放入一个向量数据库中,如下所示:
阅读下文,了解如何可视化自己的向量数据库
在下面的使用案例中,我们将创建一个能够从此数据库中检索信息的GPT聊天机器人。向聊天机器人提问的问题也将被嵌入,并基于相似性搜索,检索器将返回带有数据的嵌入信息以回答问题。之后,LLM将返回一个连贯且结构良好的答案。
我将使用的具体向量数据库是ChromaDB向量数据库。
Chroma网站[4]:
Chroma是一个用于构建带有嵌入式的人工智能应用程序的数据库。它内置了一切您需要开始使用的内容,并在您的计算机上运行。ChromaDB
Chroma 是一个在 GitHub 上的开源项目(链接:https://github.com/chroma-core/chroma)。这个开源数据库专门为人工智能应用程序设计。该代码库当前仅有6.4K个星,看起来并不多。然而,在2023年4月初,Chroma 公司宣布他们获得了1800万美元的种子轮融资(链接:https://siliconangle.com/2023/04/06/chroma-bags-18m-speed-ai-models-embedding-database/),这显示出投资者对该服务的巨大潜力。因为它是免费的、本地化的、非常易于使用,而且还有内存模式以进一步提速,我建议使用它。其他嵌入式数据库包括 Pinecone(链接:https://www.pinecone.io/)、Milvus(链接:https://milvus.io/)或 Weaviate(链接:https://weaviate.io/)。
既然我们已经讨论了理论,让我们开始实际操作吧。我已经撰写了关于 LangChain 的一些文章,但是在这篇文章中,我将把它提升到另一个层次:我将创建自己的 LangChain 问答聊天机器人,以便我可以向它提问关于 LangChain 的问题,它可以解释其工作原理!
本笔记的目标如下所示的示意图:
https://docs.langchain.com/docs/use-cases/qa-docs
我使用的笔记本可以在我的GitHub[5]上找到。
要创建一个LangChain聊天机器人并在文档中提问,首先我需要对LangChain网站[6]进行网页抓取,因为该网站是LangChain工作原理的文档。
LangChain网站[7]
#导入库
from bs4 import BeautifulSoup
import requests
import re
#%%
#从网站URL获取文本数据的函数
def get_data(url):
r = requests.get(url)
return r.text
get_data('https://python.langchain.com/en/latest/index.html')
输出是主页HTML的文本文件。
在这个主页上,所有指向其他页面的超链接都列在侧边栏菜单中(见上方图片)。为了获取LangChain文档中所有页面的URL链接,我编写了以下函数:
#获取网站链接
def get_links(website_link):
html_data = get_data(website_link)
soup = BeautifulSoup(html_data, "html.parser")
list_links = []
for link in soup.find_all("a", href=True):
list_links.append(link["href"])
return list_links
sub_links = get_links('https://python.langchain.com/en/latest/index.html')
sub_links
这将返回主页上的所有子链接。
'./additional_resources/deploy_llms.html',
'./additional_resources/tracing.html',
'./additional_resources/model_laboratory.html',
'https://discord.gg/6adMQxSpJS',
'./additional_resources/youtube.html',
等。
在统计了子链接数量后,我发现这个网站有677个页面。真的很多!
由于我现在只有子路径,我需要添加基路径以创建可用的URL。
#给所有链接添加基路径
def add_base_path(website_link, list_links):
list_links_with_base_path = []
for link in list_links:
if not link.startswith('/'):
link_with_base_path = website_link + link
list_links_with_base_path.append(link_with_base_path)
#如果链接以 'https://' 开头,则不添加基路径
elif link.startswith('http://'):
list_links_with_base_path.append(link)
elif link.startswith('.'):
link_with_base_path = website_link + link.split('/')[-1]
list_links_with_base_path.append(link_with_base_path)
return list_links_with_base_path
link_list = add_base_path('https://python.langchain.com/en/latest/', sub_links)
link_list_print = print(link_list)
现在我已经获得了LangChain网站的所有URL,是时候开始一些爬取操作了!
下面的函数用于从给定的链接下载HTML内容。然后提取纯文本内容,清理并创建独特的文件。如果需要,它会创建一个文件夹以存储输出文件。最后,它将清理后的文本内容保存到指定文件夹中具有唯一名称的各个文本文件中。
在LangChain网站的情况下,文本文件开头包含大约835行的侧边栏菜单。为了去除这些不必要的信息,该函数使用数组切片来排除这些行([835:])。
每个文件的名称由索引号和文本的前三个单词组成,这大致代表了每个页面的标题。
通过按照以下步骤进行,该函数确保下载的HTML内容被转换为清理后的文本文件,具有适当的名称,并存储在指定的文件夹中。
def save_content(link_list):
for i, link in enumerate(link_list):
html_data = get_data(link)
soup = BeautifulSoup(html_data, "html.parser")
text = soup.get_text()
# 删除前835行
lines = text.splitlines()
cleaned_text = "\n".join(lines[835:])
# 获取清理后文本中的前3个单词
words = cleaned_text.split()[:3]
file_name_prefix = "_".join(words)
# 用下划线替换特殊字符和空格
file_name_prefix = re.sub(r"[^a-zA-Z0-9]+", "_", file_name_prefix)
# 获取当前工作目录
current_dir = os.getcwd()
# 返回到上一级,即父目录
parent_dir = os.path.dirname(current_dir)
# 设置数据文件夹的路径
data_folder = os.path.join(parent_dir, "data/langchain_doc")
# 如果数据文件夹不存在,则创建
if not os.path.exists(data_folder):
os.makedirs(data_folder)
# 设置输出文件的路径
output_file = os.path.join(data_folder, f"{i}_{file_name_prefix}.txt")
# 将清理后的内容保存到输出文件
with open(output_file, "w") as f:
f.write(cleaned_text)
# 运行函数:
# 将链接的内容保存到txt文件中
save_content(link_list)
这个过程可能需要几分钟,因为需要抓取677个页面。
接下来我们来创建聊天机器人。
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chat_models import ChatOpenAI
import os
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
from langchain.llms import OpenAI
from langchain.chains import RetrievalQA
from langchain.document_loaders import DirectoryLoader
导入库之后,我们需要创建并设置一个新的OpenAI API密钥。如果您没有OpenAI API密钥,您可以在此处创建一个链接[8]。以以下方式导入此密钥:
#创建一个新的OpenAI API密钥
os.environ["OPENAI_API_KEY"] = "sk-..."
计算已爬取的文件数量:
#打印目录中的txt文件数量
loader = DirectoryLoader('your/file/path', glob="./*.txt")
doc = loader.load()
len(doc)
有600多个带有文本的文件。这些文件太大了。如果我们希望聊天机器人能够正确回答问题,建议将所有文本文件拆分成块。这样,稍后创建的检索器将只返回需要回答问题的信息片段。
我使用了langchain.text_splitter库中的RecursiveCharacterTextSplitter模块:
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200) texts = text_splitter.split_documents(doc)
len(texts)
现在所有的文档都被划分成了5576个不同的文本块。
现在我们将文本块储存在本地的Chroma向量数据库中。提供persist_directory参数将嵌入存储到磁盘中。
persist_directory = 'db'
embedding = OpenAIEmbeddings()
vectordb = Chroma.from_documents(documents=texts, embedding=embedding, persist_directory=persist_directory)
vectordb.persist() vectordb = None
运行该代码后,机器将返回以下内容:
“使用嵌入的DuckDB进行持久化:数据将存储在:db”
现在我们可以从磁盘加载持久化数据库,并像平常一样使用它:
vectordb = Chroma(persist_directory=persist_directory, embedding_function=embedding)
为了再次从数据库中获取数据,我们需要创建一个检索器。这个检索器将返回与所提问题相关的所有文档(或块):
retriever = vectordb.as_retriever()
docs = retriever.get_relevant_documents("初学者该如何入门?")
docs
返回结果是4个文本块及其对应的文件路径。这可能是一些过多的块,所以通过运行以下代码进行限制:
retriever = vectordb.as_retriever(search_kwargs={"k": 2})
检索器将为给定的查询检索出两个最相似的向量。
通过运行:
retriever.search_type
可以看到这个检索器的搜索类型是“相似度”。
链将按照以前的文章中创建的方式创建。
qa_chain = RetrievalQA.from_chain_type(llm=OpenAI(temperature=0, model_name='gpt-4'), chain_type="stuff", retriever=retriever, return_source_documents=True, verbose=True)
如果你想了解不同的LangChain类型,请查看以下文章:
LangChain 🦜🔗:使用不同的LangChain链来为The Office US编写新的剧集. 在本文中,我将向您展示如何使用LangChain中的不同链条[9]
为了看到我们的聊天机器人返回答案的方式,我们可以创建以下函数:
#引用来源
def process_llm_response(llm_response):
print(llm_response['result'])
print('\n\n来源:')
for source in llm_response["source_documents"]:
print(source.metadata['source'])
为了测试这是否有效,让我们尝试一个例子:
query = "快速入门指南的步骤是什么?"
llm_response = qa_chain(query)
process_llm_response(llm_response)
返回:
进入新的检索QA链...
完成链条。获取入门指南、模块、用例、参考文档、生态系统和 其他资源。来源:... /674_欢迎来到LangChain.txt ... /0_欢迎来到LangChain.txt
答案是正确的,并且已经引用了答案的来源。现在我们可以开始使用这个数据库来探索LangChain的无限可能性了!
另外,我们还可以检查一下在我们使用的模型中所使用的提示模板是什么。这会很有用,因为它能展示LLM在回答问题时的行为方式。
print(qa_chain.combine_documents_chain.llm_chain.prompt.template)
返回:
请使用以下上下文来回答最后的问题。如果你不知道答案,只需要说你不知道,不要试图编造一个答案。
{context}
问题:{question} 有用的回答:
为了节省本地机器的空间,建议在使用完数据库后删除它。以下代码将数据库压缩为一个zip文件并删除集合和目录:
!zip -r db.zip ./db
vectordb.delete_collection() vectordb.persist()
!rm -rf db/
当你想要再次开始使用数据库时,可以解压缩zip文件并继续操作:
!unzip db.zip
太棒了!现在你知道如何使用向量数据库来处理大量文本数据了。如果我这么说不过分的话,真的很酷,而且并不难。但是,我们还没有完成。我还想向你展示一件事,那就是将你的向量数据库可视化到 3D 中。我偶然发现了一个 GitHub 仓库,由 Spruce Campbell[10] 制作了一个包,其描述如下:
作为 Chroma[11] 向量数据库的一部分,用于可视化向量嵌入集合的包。 使用 Flask[12]、Vite[13] 和 react-three-fiber[14] 在 Web 浏览器中托管数据的实时 3D 视图,应该能在 10k+ 文档的情况下有良好的性能。
通过运行以下两行代码:
from chromaviz import visualize_collection
visualize_collection(vectordb._collection) 我得到了这个美丽的可视化信息:
每个点代表一个嵌入。接近且颜色相同的点之间存在一些相似之处。玩弄这个可视化图表很有趣,也能对本地机器内部的情况有一定的了解。
在下面的图片中,您可以看到每个嵌入所提供的信息。
使用LangChain、向量数据库和LLMs可以实现更多功能,我建议您进行尝试。我希望这篇文章能帮助大家理解如何在Python中与不同的数据源进行交互。感谢您的阅读,如果您喜欢这篇文章,请点赞👏,并留下您的反馈或想分享的想法!
本文原作者Rubentak,由山行翻译整理自:https://medium.com/@rubentak/unleashing-the-power-of-intelligent-chatbots-with-gpt-4-and-vector-databases-a-step-by-step-8027e2ce9e78,感兴趣的请点赞、关注、收藏,后续会继续分享AI应用相关的优秀文章。
[1]
通过LangChain给OpenAI模型提供互联网访问权限🦜🔗将AI推向新的高度: https://medium.com/@rubentak/give-openai-models-with-internet-access-using-langchain-7d5849f33e03
[2]
使用LangChain 🦜🔗用GPT模型与数据库交流(CSV) 在这篇简短的文章中,我将向您展示如何使用大型语言模型(LLM)来提问关于您的数据的问题...: https://medium.com/@rubentak/talk-to-your-data-base-with-gpt-models-using-langchain-csv-19e2b32aa729
[3]
text-embedding-ada-002
: https://platform.openai.com/docs/guides/embeddings/what-are-embeddings
[4]
Chroma网站: https://docs.trychroma.com/getting-started#:~:text=Chroma%20is%20a%20database%20for,hosted%20version%20is%20coming%20soon!
[5]
GitHub: https://github.com/rubentak/Langchain/blob/main/notebooks/Langchain_doc_chroma.ipynb
[6]
LangChain网站: https://python.langchain.com/en/latest/index.html
[7]
LangChain网站: https://python.langchain.com/docs/get_started/introduction.html
[8]
链接: https://platform.openai.com/account/api-keys
[9]
LangChain 🦜🔗:使用不同的LangChain链来为The Office US编写新的剧集. 在本文中,我将向您展示如何使用LangChain中的不同链条: https://medium.com/@rubentak/langchain-using-different-langchain-chains-to-write-a-new-episode-for-the-office-us-7c45d869d895
[10]
Spruce Campbell: https://github.com/mtybadger
[11]
Chroma: https://trychroma.com/
[12]
Flask: https://flask.palletsprojects.com/en/2.3.x/
[13]
Vite: https://vitejs.dev/
[14]
react-three-fiber: https://github.com/pmndrs/react-three-fiber