项目名称:“易速鲜花”内部员工知识库问答系统
项目介绍:传统的大语言模型,虽然有很强的推理能力和知识,但通常无法获取内部信息。“易速鲜花”,有很多员工手册、sop和规则等内容,大模型通常无法直接访问,使用微调等手段往往效率不高,此时我们通常采用RAG(Retrieval Augmented LLM)的技术来实现访问内部信息。然后本笔记简要简述一下问答系统每个模块的内容。
架构图
上图是构建langchain的架构图,整个架构分为三个部分 1.数据源,有pdf这种非结构化数据,代码(python)和sql、execel这种结构化数据 2.大模型的应用开发,以大模型为逻辑引擎,生成所需答案 3.使用例子,例如问答或者聊天机器人
实现机制
如上图所示,实现上面的私有化问答的功能需要5个部分,第一部分是数据的加载,第二部分是数据的切割,第三部分是数据向量存储,第四部分是检索,第五部分是结合大模型进行输出。
数据加载
对于“易速鲜花”项目的资料,包括pdf、word和txt,如果使用传统的载入会有点复杂。但langchain能很方便加载doc、pdf和txt数据。下面是示例代码
import os
os.environ["OPENAI_API_KEY"] = '你的Open AI API Key'
# 1.Load 导入Document Loaders
from langchain.document_loaders import PyPDFLoader
from langchain.document_loaders import Docx2txtLoader
from langchain.document_loaders import TextLoader
# 加载Documents
base_dir = '.\OneFlower' # 文档的存放目录
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())
文本分割
将我的非结构化文本,切割成chunk,并用openai的Embedding模型来向量化chunk并存入到向量存储库中,我们使用LangChian的RecursiveCharacterTextSplitter
# 2.Split 将Documents切分成块以便后续进行嵌入和向量存储
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=10)
chunked_documents = text_splitter.split_documents(documents)
其中chunk_size表示截断大小,chunk_overlap表示每个chunk之间重叠的字数
向量存储库
为了方便计算机理解文本、图像、音频等内容,通常会使用embedding模型。目的是为了将这些内容转成高维的向量,这些向量的目的就是为了抽象内容,例如通过某种词嵌入技术,我们可能会得到: "国王" -> [1.2, 0.5, 3.1, ...] "皇帝" -> [1.3, 0.6, 2.9, ...] "苹果" -> [0.9, -1.2, 0.3, ...]
从这些向量中,我们可以看到“国王”和“皇帝”这两个词的向量在某种程度上是相似的,而与“苹果”这个词相比,它们的向量则相差很大,因为这两个概念在语义上是不同的。
随着大模型的兴起,向量数据库也应运而生,相较于传统的关系型数据库,存储和查询高纬的向量数据时,往往面临着效率和性能的问题。因此,向量数据库被设计出来解决这一问题,来更好地处理这些高维数据。常用的向量数据库有Pinecone,Chroma和Qdrant 代码实现如下
# 3.Store 将分割嵌入并存储在矢量数据库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
相关信息的获取(检索)
为了根据我们的query来查询向量存储库的文本内容,通常会考虑到向量之间的距离或者相似度计算,就是欧氏距离和余弦相似度。 欧式距离:表示空间中任意两点之间的直线距离,在高维空间中,两个向量的欧氏距离就是各个对应维度差的平方和的平方根。
余弦相似度:- 在很多情况下,我们更关心向量的方向而不是它的大小。例如在文本处理中,一个词的向量可能会因为文本长度的不同,而在大小上有很大的差距,但方向更能反映其语义。余弦相似度就是度量向量之间方向的相似性,它的值范围在-1到1之间,值越接近1,表示两个向量的方向越相似。
欧氏距离和余弦相似度的选择
关心数量等大小差异时用欧氏距离,关心文本等语义差异时用余弦相似度
欧式距离是绝对距离,能很好地反映向量的差异,例如在物品推荐系统中,用户的购买量可能反映他们的偏好强度,此时可以考虑使用欧氏距离。同样,在数据集中各个向量的大小相似,且数据分布大致均匀时,使用欧氏距离也比较适合。
余弦相似度度量的是方向的相似性,它更关心的是两个向量的角度差异,而不是它们的大小差异。在处理文本数据或者其他高维稀疏数据的时候,余弦相似度特别有用。
检索代码如下
# 4. Retrieval 准备模型和Retrieval链
import logging # 导入Logging工具
from langchain.chat_models import ChatOpenAI # ChatOpenAI模型
from langchain.retrievers.multi_query import MultiQueryRetriever # MultiQueryRetriever工具
from langchain.chains import RetrievalQA # RetrievalQA链
# 设置Logging
logging.basicConfig()
logging.getLogger('langchain.retrievers.multi_query').setLevel(logging.INFO)
# 实例化一个大模型工具 - OpenAI的GPT-3.5
llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0)
# 实例化一个MultiQueryRetriever
retriever_from_llm = MultiQueryRetriever.from_llm(retriever=vectorstore.as_retriever(), llm=llm)
# 实例化一个RetrievalQA链
qa_chain = RetrievalQA.from_chain_type(llm,retriever=retriever_from_llm)
生成回答展示
这里使用的是Flask进行前后端交互,通过前端的form表单,获取question的id传入问题,后端就是调用qa_chain来获取前端的问题,最后后端将result传到前端渲染
#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)
#前端
<body>
<div class="container">
<div class="header">
<h1>易速鲜花内部问答系统</h1>
<img src="{{ url_for('static', filename='flower.png') }}" alt="flower logo" width="200">
</div>
<form method="POST">
<label for="question">Enter your question:</label><br>
<input type="text" id="question" name="question"><br>
<input type="submit" value="Submit">
</form>
{% if result is defined %}
<h2>Answer</h2>
<p>{{ result.result }}</p>
{% endif %}
</div>
</body>