2.伴学笔记|用LangChain快速构建基于“易速鲜花”本地知识库的智能问答系统|豆包MarsCode AI刷题

240 阅读5分钟

项目名称:“易速鲜花”内部员工知识库问答系统

项目介绍:传统的大语言模型,虽然有很强的推理能力和知识,但通常无法获取内部信息。“易速鲜花”,有很多员工手册、sop和规则等内容,大模型通常无法直接访问,使用微调等手段往往效率不高,此时我们通常采用RAG(Retrieval Augmented LLM)的技术来实现访问内部信息。然后本笔记简要简述一下问答系统每个模块的内容。

架构图

上图是构建langchain的架构图,整个架构分为三个部分 1.数据源,有pdf这种非结构化数据,代码(python)和sql、execel这种结构化数据 2.大模型的应用开发,以大模型为逻辑引擎,生成所需答案 3.使用例子,例如问答或者聊天机器人

实现机制

如上图所示,实现上面的私有化问答的功能需要5个部分,第一部分是数据的加载,第二部分是数据的切割,第三部分是数据向量存储,第四部分是检索,第五部分是结合大模型进行输出。

数据加载

对于“易速鲜花”项目的资料,包括pdf、word和txt,如果使用传统的载入会有点复杂。但langchain能很方便加载doc、pdf和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
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())

文本分割

将我的非结构化文本,切割成chunk,并用openai的Embedding模型来向量化chunk并存入到向量存储库中,我们使用LangChian的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表示截断大小,chunk_overlap表示每个chunk之间重叠的字数

向量存储库

为了方便计算机理解文本、图像、音频等内容,通常会使用embedding模型。目的是为了将这些内容转成高维的向量,这些向量的目的就是为了抽象内容,例如通过某种词嵌入技术,我们可能会得到: "国王" -> [1.2, 0.5, 3.1, ...] "皇帝" -> [1.3, 0.6, 2.9, ...] "苹果" -> [0.9, -1.2, 0.3, ...]  

从这些向量中,我们可以看到“国王”和“皇帝”这两个词的向量在某种程度上是相似的,而与“苹果”这个词相比,它们的向量则相差很大,因为这两个概念在语义上是不同的。

随着大模型的兴起,向量数据库也应运而生,相较于传统的关系型数据库,存储和查询高纬的向量数据时,往往面临着效率和性能的问题。因此,向量数据库被设计出来解决这一问题,来更好地处理这些高维数据。常用的向量数据库有Pinecone,Chroma和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

相关信息的获取(检索)

为了根据我们的query来查询向量存储库的文本内容,通常会考虑到向量之间的距离或者相似度计算,就是欧氏距离和余弦相似度。 欧式距离:表示空间中任意两点之间的直线距离,在高维空间中,两个向量的欧氏距离就是各个对应维度差的平方和的平方根。

余弦相似度:- 在很多情况下,我们更关心向量的方向而不是它的大小。例如在文本处理中,一个词的向量可能会因为文本长度的不同,而在大小上有很大的差距,但方向更能反映其语义。余弦相似度就是度量向量之间方向的相似性,它的值范围在-1到1之间,值越接近1,表示两个向量的方向越相似。

欧氏距离和余弦相似度的选择

关心数量等大小差异时用欧氏距离,关心文本等语义差异时用余弦相似度

欧式距离是绝对距离,能很好地反映向量的差异,例如在物品推荐系统中,用户的购买量可能反映他们的偏好强度,此时可以考虑使用欧氏距离。同样,在数据集中各个向量的大小相似,且数据分布大致均匀时,使用欧氏距离也比较适合。

余弦相似度度量的是方向的相似性,它更关心的是两个向量的角度差异,而不是它们的大小差异。在处理文本数据或者其他高维稀疏数据的时候,余弦相似度特别有用。

检索代码如下

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

生成回答展示

这里使用的是Flask进行前后端交互,通过前端的form表单,获取question的id传入问题,后端就是调用qa_chain来获取前端的问题,最后后端将result传到前端渲染

#Flask
# 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)
#前端
<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>