这门课,我们将学习如何使用RAG技术来构建一个内部员工问答系统。
Question:
公司内部有自己的业务流程和规范,也拥有针对员工的SOP手册。新员工入职培训时,会分享相关的信息。但是,这些信息分散于内部网和HR部门目录各处,有时不便查询;有时因为文档过于冗长,员工无法第一时间找到想要的内容;有时公司政策已更新,但是员工手头的文档还是旧版内容。
基于上述需求,我们将开发一套基于各种内部知识手册的 “Doc-QA” 系统。这个系统将充分利用LangChain框架,处理从员工手册中产生的各种问题。这个问答系统能够理解员工的问题,并基于最新的员工手册,给出精准的答案。
我们将分别从非结构化数据和结构化数据中提取知识,然后通过LLM,实现一个QA/Chat机器人,现在我们一般叫RAG,检索增强生成。
整个框架分为三个部分。
- 数据源(Data Sources):数据可以有很多种,包括PDF在内的非结构化的数据(Unstructured Data)、SQL在内的结构化的数据(Structured Data),以及Python、Java之类的代码(Code)。
- 大模型应用(Application,即LLM App):以大模型为逻辑引擎生成我们所需要的回答。
- 用例(Use-Cases):大模型生成的回答可以构建出QA/聊天机器人等系统。
整个流程分为五个部分:
加载文档Loading->文档拆分Splitting->文档嵌入Embedding->文档保存Storage->文档检索Retrieval->输出Output
其中文档嵌入和保存可以算成一步,就是对文档计算向量后保存在向量数据库内。
1. 加载文档
我们可以使用langchain.document_loaders来导入各种我们需要的包,例如解码PDF/Word/txt
from langchain.document_loaders import PyPDFLoader
from langchain.document_loaders import Docx2txtLoader
from langchain.document_loaders import TextLoader
然后我们可以通过加载器通过路径加载文件。
documents = []
for file in os.listdir(base_dir):
# 构建完整的文件路径
file_path = os.path.join(base_dir, file)
if file.endswith('.pdf'):
loader = PyPDFLoader(file_path)
documents.extend(loader.load())
elif file.endswith('.docx'):
loader = Docx2txtLoader(file_path)
documents.extend(loader.load())
elif file.endswith('.txt'):
loader = TextLoader(file_path)
documents.extend(loader.load())
2. 文档分割
Langchain里自带了文本分割器text_splitter
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=10)
chunked_documents = text_splitter.split_documents(documents)
chunk_size是指一个块的大小, chun_overlap是相邻两个块之间的重叠数。
3. 向量数据库存储
也是调库,你可以自己选择自己想要的模型,在Langchain上找对应的文档,一行代码,参数是文档,返回的是embeddings。这里使用OpenAIEmbeddings。
向量数据库同理,可以使用Chroma,这是一个简单的内存向量数据库,我们这里使用的是Qdrant。
重要的是掌握这套流程,而不是具体的代码。
from langchain.vectorstores import Qdrant
from langchain.embeddings import OpenAIEmbeddings
vectorstore = Qdrant.from_documents(
documents=chunked_documents, # 以分块的文档
embedding=OpenAIEmbeddings(), # 用OpenAI的Embedding Model做嵌入
location=":memory:", # in-memory 存储
collection_name="my_documents",) # 指定collection_name
4. 相关信息获取
现在我们的文档,不管原来是什么形式,是图片,是PDF,还是WORD,现在都是一条条的嵌入向量。于是我们就可以进行具体的查询操作了,一般是基于向量的距离或者相似度进行,例如欧氏距离和余弦相似度。
关心数量等大小差异时用欧氏距离,关心文本等语义差异时用余弦相似度。
Langchain的检索十分简单,我们上一步得到了向量数据库,直接把我们要查询的内容,丢入向量数据库就可以,然后向量数据库会返回给我们最相近的几条数据。
from langchain.chat_models import ChatOpenAI
from langchain.retrievers.multi_query import MultiQueryRetriever
from langchain.chains import RetrievalQA
llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0)
retriever_from_llm = MultiQueryRetriever.from_llm(retriever=vectorstore.as_retriever(), llm=llm)
# 实例化一个RetrievalQA链
qa_chain = RetrievalQA.from_chain_type(llm,retriever=retriever_from_llm)
这里的MultiQueryRetriever是多重查询的作用,根据用户输入的问题,让大模型生成多个询问,选中较多且相近的回答,使得回答更准确。
除此之外,还有上下文压缩,最大相似性,最大边际相似性等等。
5. 生成回答并展示
将用户输入的查询,丢入我们刚刚写好的链,即可得到回答
Flask代码
# 5. Output 问答系统的UI实现
from flask import Flask, request, render_template
app = Flask(__name__) # Flask APP
@app.route('/', methods=['GET', 'POST'])
def home():
if request.method == 'POST':
# 接收用户输入作为问题
question = request.form.get('question')
# RetrievalQA链 - 读入问题,生成答案
result = qa_chain({"query": question})
# 把大模型的回答结果返回网页进行渲染
return render_template('index.html', result=result)
return render_template('index.html')
if __name__ == "__main__":
app.run(host='0.0.0.0',debug=True,port=5000)