我正在参加「豆包MarsCode AI练中学体验活动」详情请看:掘金小册上线 AI练中学功能 | 你的 AI 编程助教喊你免费领小册啦!
前言
“易速鲜花”内部员工知识库问答系统作为一个大型在线鲜花销售平台,拥有自己的业务流程和规范,并配备了针对员工的SOP手册。新员工入职培训时,会分享相关信息。然而,这些信息分散在内部网和HR部门的各个目录中,有时难以查询;有时文档过于冗长,员工无法迅速找到所需内容;有时公司政策已更新,但员工手中的文档仍是旧版。
基于以上需求,我们将开发一套基于各种内部知识手册的“Doc-QA”系统。该系统将充分利用LangChain框架,处理从员工手册中产生的各类问题。这个问答系统能够理解员工的问题,并基于最新的员工手册,提供精准的答案。
系统架构
整个框架分为以下三个部分。
- 数据源(Data Sources):数据类型多样,包括PDF等非结构化数据(Unstructured Data)、SQL等结构化数据(Structured Data),以及Python、Java等代码(Code)。在此示例中,我们重点关注非结构化数据的处理。
- 大模型应用(Application,即LLM App):以大模型为逻辑引擎,生成所需的回答。
- 用例(Use-Cases):大模型生成的回答可以构建出QA/聊天机器人等系统。
核心实现机制: 该项目的核心实现机制是下图所示的数据处理管道(Pipeline)。
具体流程分为以下5步。
- Loading:文档加载器将以LangChain能够读取的形式 加载 Documents。
- Splitting:文本分割器将Documents 切分 为指定大小的片段,我们称之为“文档块”或“文档片”。
- Storage:将上一步中分割好的“文档块”以“嵌入”(Embedding)的形式 存储 到向量数据库(Vector DB)中,形成一个个的“嵌入片”。
- Retrieval:应用程序从存储中 检索 分割后的文档(例如通过比较余弦相似度,找到与输入问题类似的嵌入片)。
- Output:将问题和相似的嵌入片传递给语言模型(LLM),使用包含问题和检索到的分割的提示 生成答案。
开发流程
1.准备和加载数据
# 1.Load 导入Document Loaders
import os
from langchain_community.document_loaders import PyPDFLoader
from langchain_community.document_loaders import Docx2txtLoader
from langchain_community.document_loaders import TextLoader
from typing import Dict, List, Any
# 用OpenAI的Embedding模型为文档做嵌入
from langchain.embeddings.base import Embeddings
#调用OpenAI的GPT模型来生成问答系统中的回答
from langchain.pydantic_v1 import BaseModel
from volcenginesdkarkruntime import Ark
# os.environ["OPENAI_API_KEY"] = '你的OpenAI API Key'
# os.environ["OPENAI_BASE_URL"] = 'OpenAI 的 API URL'
# 从 pdf、word 和 txt 文件中加载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())
2. 文本分割
# 2.Split 将Documents切分成200字符左右的块以便后续进行嵌入和向量存储
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=10)
chunked_documents = text_splitter.split_documents(documents)
3. 向量数据库存储
将这些分割后的文本转换成嵌入的形式,并将其存储在一个向量数据库中
- OpenAIEmbeddings 来生成嵌入
- 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
词嵌入(Word Embedding)
自然语言处理和机器学习中的一个概念,它将文字或词语转换为一系列数字,通常是一个向量。简单地说,词嵌入就是一个为每个词分配的数字列表。
举例:
“电脑” -> [1.2, 0.5, 3.1, …]
“笔记本” -> [1.3, 0.6, 2.9, …]
“汽车” -> [0.9, -1.2, 0.3, …]
从这些向量中,我们可以观察到“电脑”和“笔记本”这两个词的向量在某种程度上是相似的,而与“汽车”这个词相比,它们的向量则相差很大,因为这两个概念在语义上是不同的。
词嵌入的优点在于,它提供了一种将文本数据转化为计算机可以理解和处理的形式,同时保留了词语之间的语义关系。这在许多自然语言处理任务中都是非常有用的,比如文本分类、机器翻译和情感分析等。
4. 信息获取
当内部文档存储到向量数据库之后,我们需要根据问题和任务来提取最相关的信息。此时,信息提取的基本方式是将问题也转换为向量,然后与向量数据库中的各个向量进行比较,提取最接近的信息。
向量之间的比较通常基于向量的距离或相似度。在高维空间中,常用的向量距离或相似度计算方法有欧氏距离和余弦相似度。
-
关心数量等大小差异时用欧氏距离,关心文本等语义差异时用余弦相似度。
-
欧氏距离:这是最直接的距离度量方式,类似于在二维平面上测量两点之间的直线距离。在高维空间中,两个向量的欧氏距离就是各个对应维度差的平方和的平方根。
-
余弦相似度:在很多情况下,我们更关心向量的方向而不是它的大小。例如在文本处理中,一个词的向量可能会因为文本长度的不同而在大小上有很大的差距,但方向更能反映其语义。余弦相似度就是度量向量之间方向的相似性,它的值范围在-1到1之间,值越接近1,表示两个向量的方向越相似。
这两种方法都被广泛应用于各种机器学习和人工智能任务中,选择哪一种方法取决于具体的应用场景。
- retriever(vectorstore.as_retriever())负责根据问题检索相关的文档,找到具体的“嵌入片”。这些“嵌入片”对应的“文档块”就会作为知识信息,和问题一起传递进入大模型。本地文档中检索而得的知识很重要,因为 从互联网信息中训练而来的大模型不可能拥有“易速鲜花”作为一个私营企业的内部知识。
# 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)
5.生成回答
创建一个 Flask 应用(需要安装Flask包)来接收用户的问题,并生成相应的答案,最后通过 index.html 对答案进行渲染和呈现。
在这个步骤中,我们使用了之前创建的 RetrievalQA 链来获取相关的文档和生成答案。然后,将这些信息返回给用户,显示在网页上。
# 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)