青训营学习4:问答系统构建实践 | 豆包MarsCode AI 刷题

168 阅读10分钟

青训营学习4:构建内部知识库问答系统


项目背景与目标

项目名称

“易速鲜花”内部知识库问答系统

项目简介

“易速鲜花”是一家大型在线鲜花销售平台,内部拥有丰富的业务流程和员工手册。然而,由于信息分散、不易查找以及文档更新滞后等问题,新员工在培训或实际工作中常常难以获取准确、及时的内容。为了解决这一问题,本项目旨在开发一套基于 LangChain 框架的 Doc-QA 问答系统,通过整合内部知识手册,提供精确的员工问题答案。

项目框架

整个项目分为三个核心部分:

  1. 数据源:处理非结构化数据(如 PDF、Word 等文档)。
  2. 大模型应用:使用大语言模型(LLM)作为逻辑引擎生成答案。
  3. 用例:生成问答系统或聊天机器人。

image.png

核心实现机制基于以下 数据处理管道(Pipeline)

  1. 文档加载(Loading):文档加载器把Documents 加载为以LangChain能够读取的形式。
  2. 文本分割(Splitting):文本分割器把Documents 切分为指定大小的分割,我把它们称为“文档块”或者“文档片”。
  3. 嵌入存储(Storage):将上一步中分割好的“文档块”以“嵌入”(Embedding)的形式存储到向量数据库(Vector DB)中,形成一个个的“嵌入片”。
  4. 信息检索(Retrieval):应用程序从存储中检索分割后的文档(例如通过比较余弦相似度,找到与输入问题类似的嵌入片)。
  5. 答案生成与输出(Output):把问题和相似的嵌入片传递给语言模型(LLM),使用包含问题和检索到的分割的提示生成答案

image.png


数据处理管道解析

1. 文档加载(Loading)

实现方式

使用 LangChain 的 document_loaders 模块加载多种格式的文档,如 PDF、Word、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
...
注意事项
  • 需要安装相关工具包(如 PyPDF2, docx2txt)。
  • OpenAI API Key 配置,用于后续模型调用。用OpenAI的Embedding模型为文档做嵌入;调用OpenAI的GPT模型来生成问答系统中的回答。
思考

这一阶段的关键在于 文档多样性支持与正确解析,选择兼容性高的加载器尤为重要。此外,可以通过扩展支持更多格式(如图片中的文字)来提升实用性。


2. 文本分割(Splitting)

实现方式

利用 RecursiveCharacterTextSplitter 将长文档切分为较小的块以便后续处理。

代码:

from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=10)
chunked_documents = text_splitter.split_documents(documents)
思考

现在,文档被切成了一个个200字符左右的文档块。这一步,是为把它们存储进下面的向量数据库做准备。文本分割是问答系统性能的基础,分割粒度需要根据具体场景权衡:粒度过大,影响检索精度;粒度过小,可能导致上下文割裂。


3. 嵌入存储(Storage)

实现方式

接着需要将这些分割后的文本转换成嵌入的形式,并将其存储在一个向量数据库中。使用 OpenAI 的 Embedding 模型生成文档嵌入,并存储到向量数据库中(如 Qdrant),需要先pip install qdrant-client。

嵌入的概念:词嵌入是自然语言处理和机器学习中的一个概念,它将文字或词语转换为一系列数字,通常是一个向量。简单地说,词嵌入就是一个为每个词分配的数字列表。这些数字不是随机的,而是捕获了这个词的含义和它在文本中的上下文。因此,语义上相似或相关的词在这个数字空间中会比较接近。

向量数据库:也称为矢量数据库或者向量搜索引擎,专门用于存储和搜索向量形式的数据的数据库。在众多的机器学习和人工智能应用中,尤其是自然语言处理和图像识别这类涉及大量非结构化数据的领域,将数据转化为高维度的向量是常见的处理方式。这些向量可能拥有数百甚至数千个维度,是对复杂的非结构化数据如文本、图像的一种数学表述,从而使这些数据能被机器理解和处理。然而,传统的关系型数据库在存储和查询如此高维度和复杂性的向量数据时,往往面临着效率和性能的问题。因此,向量数据库被设计出来以解决这一问题,它具备高效存储和处理高维向量数据的能力,从而更好地支持涉及非结构化数据处理的人工智能应用。向量数据库有很多种,比如Pinecone、Chroma和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
思考

嵌入的质量直接决定检索效果,可探索替代嵌入模型(如 Sentence-BERT)或其他向量数据库(如 Pinecone、Chroma)。此外,应根据数据量选择合适的存储方式(内存 vs. 磁盘)。

目前所有内部文档都以“文档块嵌入片”的格式被存储在向量数据库里面了。后续只需要查询这个向量数据库,就可以找到大体上相关的信息了。


4. 信息检索(Retrieval)

实现方式

基于用户输入的问题生成向量,与数据库中的嵌入进行相似度匹配,提取相关文档。

向量之间的比较通常基于向量的距离或者相似度。在高维空间中,常用的向量距离或相似度计算方法有欧氏距离和余弦相似度。相似度计算方式:

  • 欧氏距离:适合关注向量整体大小。最直接的距离度量方式,就像在二维平面上测量两点之间的直线距离那样。在高维空间中,两个向量的欧氏距离就是各个对应维度差的平方和的平方根。
  • 余弦相似度:更适合关注向量方向(推荐用于文本处理)。例如在文本处理中,一个词的向量可能会因为文本长度的不同,而在大小上有很大的差距,但方向更能反映其语义。余弦相似度就是度量向量之间方向的相似性,它的值范围在-1到1之间,值越接近1,表示两个向量的方向越相似。

image.png

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

代码部分,会创建一个聊天模型。然后创建一个 RetrievalQA 链,它是一个检索式问答模型,用于生成问题的答案。在RetrievalQA 链中有下面两大重要组成部分:LLM是大模型,负责回答问题;retriever(vectorstore.as_retriever())负责根据问题检索相关的文档,找到具体的“嵌入片”。这些“嵌入片”对应的“文档块”就会作为知识信息,和问题一起传递进入大模型。本地文档中检索而得的知识很重要,因为从互联网信息中训练而来的大模型不可能拥有“易速鲜花”作为一个私营企业的内部知识

代码:

import logging
from langchain.chat_models import ChatOpenAI # ChatOpenAI模型
from langchain.retrievers.multi_query import MultiQueryRetriever # MultiQueryRetriever工具
from langchain.chains import RetrievalQA # RetrievalQA链

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. 答案生成与输出(Output)

实现方式

结合检索结果和用户问题,通过大语言模型生成答案,并将其呈现给用户。

目标是实现问答系统应用的主要UI交互部分,会创建一个 Flask 应用(需要安装Flask包)来接收用户的问题,并生成相应的答案,最后通过 index.html 对答案进行渲染和呈现。使用了之前创建的 RetrievalQA 链来获取相关的文档和生成答案。然后将这些信息返回给用户,显示在网页上。

代码:

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

通过简洁直观的界面提升用户体验,同时可以探索更多呈现形式(如语音助手或移动端 App)来扩展应用场景。运行程序之后,访问网页http://127.0.0.1:5000/。与网页互动可以发现,问答系统可以生成专属于易速鲜花内部资料的回答。

运行效果:

image.png

image.png

image.png

关键概念学习积累

嵌入(Embedding)

  • 定义:将文本转换为高维向量,捕捉语义信息。
  • 应用:文本检索、问答系统等。

向量存储(Vector Storage)

  • 定义:存储嵌入向量的数据库,用于快速相似度计算。
  • 工具:Qdrant、Pinecone、Weaviate 等。

相似度计算

  • 欧氏距离:度量绝对距离。
  • 余弦相似度:度量方向相似性,适合文本场景。

总结与思考

本项目以 LangChain 框架为核心,完成了基于企业内部文档的问答系统构建。先把本地知识切片后做Embedding,存储到向量数据库中,然后把用户的输入和从向量数据库中检索到的本地知识传递给大模型,最终生成所想要的回答。

image.png

其核心价值在于将分散、非结构化的知识通过标准化流程处理并高效利用。以下是几点思考:

  1. 应用拓展:此系统不仅适用于企业内部问答,也可延伸至客户支持等领域。
  2. 模型优化:探索更高效的嵌入模型和更智能的检索算法以提升性能。
  3. 持续更新:确保数据库内容与企业知识手册实时同步,保持回答的准确性。
  4. 用户反馈:通过用户反馈优化问答效果。

收获:通过 LangChain 实现的问答系统,为知识管理和智能问答的落地提供了可操作性强的范例。在构建“易速鲜花”内部员工知识库问答系统的过程中,我深刻体会到数据处理的重要性。尤其是在非结构化数据的处理上,文本分割的粒度直接影响系统的响应速度和准确性。通过反复调试分割参数,我发现适当的文本块大小能够在保持上下文完整性的同时,显著提高检索效率。此外,嵌入模型的选择也让我意识到模型性能与实际应用场景的匹配度至关重要。通过对比不同的嵌入方案,我逐渐掌握了如何在性能和资源消耗之间找到最佳平衡点。这次实践不仅提升了我对 LangChain 框架的理解,也让我在模型优化和系统集成方面积累了宝贵经验。