LangChain 实战03 - 知识库智能问答系统

294 阅读6分钟

我正在参加「豆包MarsCode AI练中学体验活动」详情请看:掘金小册上线 AI练中学功能 | 你的 AI 编程助教喊你免费领小册啦!

前言

“易速鲜花”内部员工知识库问答系统作为一个大型在线鲜花销售平台,拥有自己的业务流程和规范,并配备了针对员工的SOP手册。新员工入职培训时,会分享相关信息。然而,这些信息分散在内部网和HR部门的各个目录中,有时难以查询;有时文档过于冗长,员工无法迅速找到所需内容;有时公司政策已更新,但员工手中的文档仍是旧版。

基于以上需求,我们将开发一套基于各种内部知识手册的“Doc-QA”系统。该系统将充分利用LangChain框架,处理从员工手册中产生的各类问题。这个问答系统能够理解员工的问题,并基于最新的员工手册,提供精准的答案。

系统架构

image.png

整个框架分为以下三个部分。

  • 数据源(Data Sources):数据类型多样,包括PDF等非结构化数据(Unstructured Data)、SQL等结构化数据(Structured Data),以及Python、Java等代码(Code)。在此示例中,我们重点关注非结构化数据的处理。
  • 大模型应用(Application,即LLM App):以大模型为逻辑引擎,生成所需的回答。
  • 用例(Use-Cases):大模型生成的回答可以构建出QA/聊天机器人等系统。

核心实现机制:  该项目的核心实现机制是下图所示的数据处理管道(Pipeline)。 image.png

具体流程分为以下5步。

  1. Loading:文档加载器将以LangChain能够读取的形式 加载 Documents。
  2. Splitting:文本分割器将Documents 切分 为指定大小的片段,我们称之为“文档块”或“文档片”。
  3. Storage:将上一步中分割好的“文档块”以“嵌入”(Embedding)的形式 存储 到向量数据库(Vector DB)中,形成一个个的“嵌入片”。
  4. Retrieval:应用程序从存储中 检索 分割后的文档(例如通过比较余弦相似度,找到与输入问题类似的嵌入片)。
  5. 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,表示两个向量的方向越相似。

这两种方法都被广泛应用于各种机器学习和人工智能任务中,选择哪一种方法取决于具体的应用场景。

image.png

  • 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)