项目及实现框架
项目名称:“易速鲜花”内部员工知识库问答系统。
项目介绍:大型鲜花销售平台,有自己的业务流程和规范,也有针对员工的SOP手册。但信息比较分散有时不便查询;有时因文档过于冗长员工难以定位自己想要的内容;有时公司的政策已更新但员工手册还是旧的内容。
基于上述,开发基于各种内部知识手册的 “Doc-QA” 系统。
开发框架:以下是知识库文档系统的整体框架。
组成部分
- 数据源:非结构化数据(如pdf)、结构化数据(如SQL),代码(如Python、Java)。该系统中针对的是非结构化数据。
- 大模型应用:以大模型为逻辑引擎,生成所需要的回答。
- 用例:大模型生成的回答可以构建 “Doc-QA” 系统。
核心实现机制:下图的数据处理管道。
具体流程:
- Loading:文档加载器, Document->LangChain可读取的形式
- Splitting:文本分割器,Document->指定大小的分割(文档块/文档片)
- Storage:文档块以嵌入(Embedding)形式存储到Vector DB,形成一个个嵌入片
- Retrieval:App从Vector DB检索分割后的文档(比较余弦相似度等)
- Output:把Prompt(问题和相似的嵌入片)传递给LLM,生成答案。
数据的准备和载入Document Loading
“易速鲜花”的内部资料包括pdf、word和txt格式的各种文件(框架中的数据源)。
使用LangChain中的document_loaders可以用来加载各种格式的文本文件(项目中的OneFlower目录中)。
这一阶段,加载文本,存储在一个列表documents中。
- OpenAI的Embedding模型为文档做嵌入
- 调用OpenAI的GPT模型来生成问答系统中的回答
后续会采用通义千问的模型
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())
文本的分割Splitting
加载的文本->更小的块,以便嵌入和向量存储。
使用LangChain中的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=200:文档->一个个200字符左右的文档块
chunk_overlap=10:相邻chunk之间有10个字符的重复,避免在分割点处丢失重要的上下文信息。
向量数据库存储Storage
分割的文本->嵌入的形式,存储在Vector DB中。
OpenAIEmbeddings生成嵌入,使用Qdrant向量数据库(开源)来存储嵌入。
文本的嵌入
词嵌入(Word Embedding) ,文字或数字->一系列数字,数字并不是随机的,而是捕获了这个词的含义和它在文本中的上下文。语义相似/相关->数字空间接近,在许多nlp(文本分类、机器翻译、情感分析)中非常有用。
向量数据库(矢量数据库/向量搜索引擎):存储和搜索向量形式的数据的数据库。非结构化数据->高维度的向量。
传统的关系型数据库在查询和存储高维度的向量数据存在效率和性能问题。
在实践中使用通义的Embedding来做嵌入
# 3.Store 将分割嵌入并存储在矢量数据库Qdrant中
from langchain_community.vectorstores import Qdrant
from langchain_community.embeddings import DashScopeEmbeddings
embeddings = DashScopeEmbeddings(
model="text-embedding-v1",
dashscope_api_key='阿里的dashscope api key'
)
vectorstore = Qdrant.from_documents(
documents=chunked_documents, # 以分块的文档
embedding=embeddings, # 用阿里text-embedding-v1的Embedding Model做嵌入
location=":memory:", # in-memory 存储
collection_name="my_documents", # 指定collection_name
)
至此,易速鲜花的所有内部文档,都以“文档块嵌入片”的格式被存储在Vector DB中了。只需要查询这个向量数据库,就可以找到相关的信息了。
相关信息的获取Retrieval
问题->向量,和向量数据库中的各个向量比较,提取最接近的信息。
向量比较通常基于向量的距离或相似度。高维空间,向量的距离/相似度计算方法有欧式距离和余弦相似度。
欧氏距离:各个维度差的平方和的平方根。
余弦相似度:很多情况,更关心方向而不是大小。文本处理,文本长,可能向量大,但方向更能反映语义。范围-1~1,越接近1,向量的方向越相似。
关心数量等大小差异时用欧式距离,关心文本等语义差异时用余弦相似度。
欧氏距离,例如在物品推荐系统中,用户购买量->偏好强度;数据集中各个向量的大小相似,且数据分布均匀时。
余弦相似度,常用于处理文本数据或其他高维稀疏矩阵。例如信息检索和文本分类等任务,文本数据->高维的词向量,词向量的方向更能反映语义相似性。由于,该项目处理的是文本数据,目标是建立一个QA系统,需要从语义上理解和比较问题可能的答案,因此使用余弦相似度更合适。
RetrievalQA链两个重要组成成分:
- llm:回答问题
- retrieval:根据问题检索相关的文档,找到“嵌入片”。“嵌入片”->“文档块”->知识 + 问题 传递给大模型,这对于私营企业内部知识的QA系统非常费用(llm无法拥有私营企业内部的知识)。
# 4. Retrieval 准备模型和Retrieval链
import logging # 导入Logging工具
from langchain_community.chat_models.tongyi import ChatTongyi # 导入Tongyi大模型
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)
# 实例化一个大模型工具 - Tongyi
os.environ["DASHSCOPE_API_KEY"] = '阿里的dashscope api key'
llm = ChatTongyi()
# 实例化一个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)
生成回答并展示
问答系统的主要UI交互部分,创建一个Flask应用(Flask包)接收用户的问题,并生成相应的答案,最后通过index.html对答案进行渲染和呈现。
在这个步骤中,使用了之前创建的RetrievalQA链来获取相关的文档和生成答案,最后将答案返回给用户,并显示在网页上。
此处需要把相关的static资源和index.html的templates放入项目结构中。
# 5. Output 问答系统的UI实现
from flask import Flask, request, render_template
app = Flask(__name__) # Flask APP
# 将url路径映射到函数
@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)
运行结果
访问127.0.0.1:5000就可以看到如下页面了