本篇文章获得同事刘工的授权刊登。原文发表于2023年7月13日。
介绍本地知识库的概念和用途
在现代信息时代,我们面临着海量的数据和信息,如何有效地管理和利用这些信息成为一项重要的任务。本地知识库是一种基于本地存储的知识管理系统,旨在帮助用户收集、组织和检索大量的知识和信息。它允许用户在本地环境中构建和管理自己的知识资源,以便更高效地进行信息处理和决策。
本地知识库通常采用数据库、索引和搜索技术,以构建一个结构化的存储系统,使用户可以快速地访问和查询所需的信息。
引出使用GPT-3.5、LangChain和Milvus构建本地知识库的动机
当面临一个知识类问题时,我们往往需要利用自己获取到的信息加以总结,对海量信息中包含的要点进行快速地查询和了解,而现在出现的GPT-3.5技术则能够使得用户向语言模型提问并得到一个回答。LangChain则是对大语言模型技术所用到的一些功能进行了统一的封装,这使得我们可以利用本地的知识资源,以获得我们需要的信息,Milvus则是一个可以存储这种类型数据的向量数据库。
ChatGPT 是由 OpenAI 开发的一种高级语言模型,可以根据给定的提示生成类似人类语言的文本,从而实现对话、文本摘要和问答等多种功能。
出于演示的目的,我们将专注于OpenAI的"gpt-3.5-turbo-16k"模型,因为它目前价格合适,速度较快,回答比较准确。
如果想深入了解chatgpt相关信息,请参考下面链接:(请使用VPN)
1 . https://platform.openai.com/docs/api-referencehttps://platform.openai.com/docs/api-reference
LangChain 是一个库(以 Python、JavaScript 或 TypeScript 提供),提供了一组用于处理语言模型、文本嵌入和文本处理任务的工具和实用程序。 它通过组合语言模型、向量存储和文档加载器等各种组件来简化创建聊天机器人、处理文档检索和执行问答操作等任务。
我们将专注于创建一个问答聊天机器人,其中包含上面图中所展示的绿色的部分。
如果想深入了解LangChain相关信息,请参考下面链接:
Get started | ️ LangchainGet started with LangChainhttps://python.langchain.com/docs/get_started
Milvus 是一个数据库,用于存储、索引和管理由深度神经网络和其他机器学习 (ML) 模型生成的大量嵌入向量。
跟faiss不同,Milvus需要在本地安装相关服务。安装文档如下:
Install Milvus Standalone with Docker Compose (CPU) Milvus documentationLearn how to install Milvus stanalone with Docker Compose v2.3.x.https://milvus.io/docs/install_standalone-docker.md在安装Milvus之前,需要先安装docker,安装文档如下:
Install Docker Engine on CentOS | Docker DocsLearn how to install Docker Engine on CentOS. These instructions cover the different installation methods, how to uninstall, and next steps.https://docs.docker.com/engine/install/centos/
安装完成之后当使用SDK连接Milvus时,其默认端口是:19530
相关文档如下:
Manage Milvus Connections Milvus documentationLearn how to connect to a Milvus server v2.3.x.https://milvus.io/docs/manage_connection.md
Document loaders是langchain中的一个组件,它的功能是从文件中读取数据,比如PDF,csv,url,txt等。经过loaders加载后的数据Document主要由两部分组成,即page_content和metadata。metadata中存储了文件名,第几页等基础信息,page_content中存储了该页的内容。
其基础用法如下:、
from langchain.document_loaders import PyPDFLoader
loader = PyPDFLoader(r"loRA _refer.pdf")
print(loader.load())
代码中所展示的文档可以从这里下载:https://arxiv.org/pdf/2106.09685.pdfhttps://arxiv.org/pdf/2106.09685.pdf
代码解释:
PyPDFLoader是document_loaders 中加载pdf的组件,这段代码将pdf加载为一个loader对象,并打印了其中内容,可以看出打印为一个列表,这个列表中存放了一个Document对象。
除了PyPDFLoader,langchain还提供了CSVLoader,HTMLLoader,JSONLoader,MarkdownLoader,File Directory,ExcelLoader,Microsoft Word,Microsoft PowerPoint,GitHub,EPub,Images,WebBaseLoader,URL等多种加载器,具体可查看其document_loaders文档:
Document loaders | ️ LangchainHead to Integrations for documentation on built-in document loader integrations with 3rd-party tools.https://python.langchain.com/docs/modules/data_connection/document_loaders/
WebBaseLoaderr可以从网页中加载文本内容,其基本用法如下:
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import Milvus
from langchain.document_loaders import WebBaseLoader
from langchain.text_splitter import CharacterTextSplitter
loader = WebBaseLoader([
"https://milvus.io/docs/overview.md",
])
docs = loader.load()
因为在现在的技术条件下,chatgpt或其他的大语言模型均有受到文本长度的限制,所以对于一个大型文件或者很多个大型文件时,若将全部文本一次性发送给chatgpt,则模型往往会报错token超出。在这种情况下,我们则会先将长文档拆分为可以放入模型上下文窗口的较小块。在langchain中内置了很多函数,使我们可以直接进行这个操作。
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import Milvus
from langchain.document_loaders import WebBaseLoader
from langchain.text_splitter import CharacterTextSplitter
loader = WebBaseLoader([
"https://milvus.io/docs/overview.md",
])
docs = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=1024, chunk_overlap=20)
docs = text_splitter.split_documents(docs)
代码解释:
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
: 这行代码创建一个名为text_splitter
的对象,它是CharacterTextSplitter
类的一个实例。构造函数的参数如下所示:
chunk_size=1000
: 这是指定拆分文本片段的大小的参数。在这里,设置为1000,表示每个片段的字符数为1000。
chunk_overlap=0
: 这是指定片段之间重叠部分的大小的参数。在这里,设置为0,表示片段之间没有重叠。设置重叠部分有可能会在某些长句子面临被切开情况会很有用,或者上下两句联系较为紧密时起作用。可以根据实际情况来写入,默认可以设置为0.
docs = text_splitter.split_documents(documents)
: 这行代码使用之前创建的text_splitter
对象调用split_documents()
方法来将加载的文档拆分成较小的文本片段。拆分后的文本片段将存储在名为docs
的变量中。split_documents()
方法的参数documents
是之前加载的文档。
embedding (嵌入)这个动作的目的是创建一段文本的矢量表示形式。矢量是一个数学领域的概念,若对这部分不熟悉请学习线性代数这门课程。
矢量在数学上一般以一个有序数组来表示,相关概念见下文:
线性代数基础 | 向量的运算与向量的线性组合 - 知乎向量及其基本运算“ 开局一个向量,构造出新的向量。 ”向量的形式化定义 n个有序数a_1,a_2,\cdots,a_n所组成的数组称为n维向量,这n个数称为该向量的n个分量,第i个数a_i称为第i个分量。 n维向量可写成一行或一列…https://zhuanlan.zhihu.com/p/339974158
文本向量化之后,就可以执行诸如语义搜索之类的操作,在其中我们可以寻找向量空间中最相似的文本片段。
当我们需要查找向量空间的最相似的文本片段时,就必须引入另一个工具,即向量数据库。
存储和搜索非结构化数据的最常见方法之一是将数据embedding并存储生成的embedding向量。然后在查询时检索与查询内容"最相似"的embedding向量。Vector stores(矢量存储)负责存储embedding数据并为您执行矢量搜索。(请注意,在数学中矢量与向量同义)。
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores import Milvus
from langchain.document_loaders import WebBaseLoader
from langchain.text_splitter import CharacterTextSplitter
loader = WebBaseLoader([
"https://milvus.io/docs/overview.md",
])
docs = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=1024, chunk_overlap=20)
docs = text_splitter.split_documents(docs)
embeddings = OpenAIEmbeddings()
vector_db = Milvus.from_documents(
docs,
embeddings,
connection_args={"host": "127.0.0.1", "port": "19530"},
collection_name="milvus_docs",
)
从文档中生成向量,并在Milvus数据库中创建一个名为"milvus_docs"的集合,将对应的向量数据保存到该集合中。
from langchain.vectorstores import Milvus
from langchain.chat_models import ChatOpenAI
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.chains.retrieval_qa.base import RetrievalQA
from typing import Any
from langchain.memory import ConversationBufferMemory
from langchain import PromptTemplate, FAISS
embeddings = OpenAIEmbeddings()
def get_vector_chain() -> Any:
llm = ChatOpenAI(temperature=0,model_name="gpt-3.5-turbo-16k")
template = """
Use the following context (delimited by ) and the chat history (delimited by ) to answer the question:
------
{context}
------
{history}
------
{question}
Answer in the language in which the question was asked:
"""
prompt = PromptTemplate(
input_variables=["history", "context", "question"],
template=template,
)
vector_db = Milvus(
embedding_function=embeddings,
connection_args={"host": "127.0.0.1", "port": "19530"},
collection_name="milvus_docs",
)
chain = RetrievalQA.from_chain_type(
llm,
retriever=vector_db.as_retriever(search_type="similarity", search_kwargs={"k": 2}),
chain_type="stuff",
chain_type_kwargs={
"prompt": prompt,
"memory": ConversationBufferMemory(
memory_key="history",
input_key="question"),
},
)
return chain
chain = get_vector_chain()
answer = chain.run("What is milvus?")
print(answer)
(33行)从Milvus中加载集合名为"milvus_docs"的相关数据进来。请注意这里没有加载文档,没有使用from_documents方法,而在前面的加载中使用了vector_db = Milvus.from_documents( docs, embeddings, connection_args={"host": "127.0.0.1", "port": "19530"}, collection_name="milvus_docs", ),这里是读取和存储的核心,请留意一下。
(40行)搜索前两个最相关的数据发送给RetrievalQA,这里的search_kwargs={"k": 2}是指前两个最相关的。