02 文档Q_A系统实战 | 青训营X豆包MarsCode 技术训练营

178 阅读6分钟

项目及整体框架

项目名称:易速鲜花员工问答系统

项目背景:

易速鲜花”作为一个大型在线鲜花销售平台,有自己的业务流程和规范,也拥有针对员工的SOP手册。新员工入职培训时,会分享相关的信息。但是,这些信息分散于内部网和HR部门目录各处,有时不便查询;有时因为文档过于冗长,员工无法第一时间找到想要的内容;有时公司政策已更新,但是员工手头的文档还是旧版内容。

构建工具:

python HTML CSS JS LangChain框架 公司pdf等文件

开发流程与架构

image.png

共分三个部分:整个框架分为这样三个部分。大致为:Loading->Splitting->Storage->Retrieval->Output//

数据源(Data Sources):数据可以有很多种,包括PDF在内的非结构化的数据(Unstructured Data)、SQL在内的结构化的数据(Structured Data),以及Python、Java之类的代码(Code)。在这个示例中,我们聚焦于对非结构化数据的处理
大模型应用(Application,即LLM App):以大模型为逻辑引擎,生成我们所需要的回答。- 用例(Use-Cases):大模型生成的回答可以构建出QA/聊天机器人等系统。

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

具体实现

1.文件载入

首先用LangChain中的document_loaders来加载各种格式的文本文件。(这些文件我把它放在OneFlower这个目录中了,如果你创建自己的文件夹,就要调整一下代码中的目录。) 在这一步中,我们从 pdf、word 和 txt 文件中加载文本,然后将这些文本存储在一个列表中。

代码实现:

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

2.文本分割

接下来需要将加载的文本分割成更小的块,以便进行嵌入和向量存储。这个步骤中,我们使用 LangChain中的RecursiveCharacterTextSplitter 来分割文本。

代码实现:

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 这个向量数据库来存储嵌入。

代码实现:

from langchain.vectorstores import Qdrant
from langchain.embeddings import OpenAIEmbeddings
vectorstore = Qdrant.from_documents(
    documents=chunked_documents,
    embedding=OpenAIEmbeddings(), 
    location=":memory:",  
    collection_name="my_documents",) 

4.信息的获取

来到比较重要的地方了,涉及到原理部分的东西,前面似乎只是应用,因为LangChain本来就是就是构建工具,而更重要的其实是原理的部分!

信息提取与向量数据库

  • 信息提取:当我们把文档内容存储到向量数据库中后,想要从中找到相关信息时,需要将问题也转换成向量。
  • 向量比较:然后,我们会将这个问题的向量与数据库中其他向量进行比较,找出最相关的信息。

向量距离与相似度

  • 向量之间的比较:比较向量的方式主要有两种:距离相似度
1. 欧氏距离
  • 定义:就像在平面上测量两点之间的直线距离。在高维空间中,欧氏距离是两个向量在各个维度差的平方和的平方根。
  • 用途:这是最简单和直接的测量方式,适合于那些我们关注实际距离的场景。
2. 余弦相似度
  • 定义:更关注向量的方向,而不是它们的大小。即使两个向量的长度不同,只要它们的方向相似,余弦相似度也会很高。
  • 值范围:它的值在-1到1之间,越接近1,表示两个向量的方向越相似。
  • 用途:适合于文本处理等需要关注语义的场景,比如判断两个文本的相似度。

向量数据库与机器学习

  1. 向量数据库的作用

    • 向量数据库用于存储和检索高维向量,通常是从文本、图像或其他数据类型中通过机器学习模型(如嵌入模型)生成的。
    • 它们允许快速、有效地执行相似度搜索,即找出与给定查询向量最接近的存储向量。这在信息检索、推荐系统等应用中非常重要。
  2. 与机器学习的关系

    • 在机器学习中,尤其是自然语言处理(NLP)和计算机视觉等领域,数据通常会被转化为向量形式(如通过嵌入技术)。
    • 使用向量表示后,我们可以利用距离和相似度计算方法(如欧氏距离和余弦相似度)来进行各种任务,例如分类、聚类和信息检索等。

与我们的接下来的工作有什么关系呢?

在这里,我们正在处理的是文本数据,目标是建立一个问答系统,需要从语义上理解和比较问题可能的答案。因此,我建议使用余弦相似度作为度量标准。通过比较问题和答案向量在语义空间中的方向,可以找到与提出的问题最匹配的答案。

--来自作者

代码实现

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构建回答并创建前端HTML页面

代码实现

Flask实现

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)

HTML页面

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

6.运行

尝试向他提问并观察效果吧!

image.png