03-易速鲜花本地知识库 | 豆包MarsCode AI刷题

61 阅读7分钟

项目及实现框架

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

介绍:“易速鲜花”公司需要一个系统来帮助员工更便捷地访问和理解内部手册中的内容。因为手册信息分散且容易过时,员工可能无法快速找到最新的政策或流程。为了解决这个问题,我们将开发一个基于LangChain框架的“Doc-QA”系统。这个系统将帮助员工提出问题,并提供基于最新手册内容的准确答案,从而提高工作效率。

框架

image.png

准备

我们要用到用OpenAI的Embedding Model做嵌入,当然了我们用的是OpenAI的平替。
火山方舟管理控制台进入控制台

image.png 名字随意,选择模型并创建

image.png 之后的方法和之前创建模型的时候一样,找到你的mode的id复制,配置到环境变量。
export EMBEDDING_MODELEND="你自己的模型id"

代码和讲解

总代码

# 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
from langchain.embeddings.base import Embeddings
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'

# 加载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.Split 将Documents切分成块以便后续进行嵌入和向量存储
from langchain.text_splitter import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=10)
chunked_documents = text_splitter.split_documents(documents)


# 3.Store 将分割嵌入并存储在矢量数据库Qdrant中
from langchain_community.vectorstores import Qdrant


class DoubaoEmbeddings(BaseModel, Embeddings):
    client: Ark = None
    api_key: str = ""
    model: str

    def __init__(self, **data: Any):
        super().__init__(**data)
        if self.api_key == "":
            self.api_key = os.environ["OPENAI_API_KEY"]
        self.client = Ark(
            base_url=os.environ["OPENAI_BASE_URL"],
            api_key=self.api_key
        )

    def embed_query(self, text: str) -> List[float]:
        """
        生成输入文本的 embedding.
        Args:
            texts (str): 要生成 embedding 的文本.
        Return:
            embeddings (List[float]): 输入文本的 embedding,一个浮点数值列表.
        """
        embeddings = self.client.embeddings.create(model=self.model, input=text)
        return embeddings.data[0].embedding

    def embed_documents(self, texts: List[str]) -> List[List[float]]:
        return [self.embed_query(text) for text in texts]

    class Config:
        arbitrary_types_allowed = True


vectorstore = Qdrant.from_documents(
    documents=chunked_documents,  # 以分块的文档
    embedding=DoubaoEmbeddings(
        model=os.environ["EMBEDDING_MODELEND"],
    ),  # 用OpenAI的Embedding Model做嵌入
    location=":memory:",  # in-memory 存储
    collection_name="my_documents",
)  # 指定collection_name

# 4. Retrieval 准备模型和Retrieval链
import logging  # 导入Logging工具
from langchain_openai 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=os.environ["LLM_MODELEND"], 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实现
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)

解释如下:

1. 加载文档 (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
from langchain.embeddings.base import Embeddings
from langchain.pydantic_v1 import BaseModel
from volcenginesdkarkruntime import Ark
  • os: 用于操作文件和目录路径。

  • langchain_community.document_loaders: 这些加载器用于读取不同格式的文档:

    • PyPDFLoader: 用于读取PDF格式文件。
    • Docx2txtLoader: 用于读取Word文档(.docx格式)。
    • TextLoader: 用于读取纯文本文件(.txt格式)。
  • typing: 用于类型注解,帮助描述数据结构。

  • langchain.embeddings.base: Embeddings用于表示文本嵌入的基础模型。

  • langchain.pydantic_v1: 用于定义数据模型。

  • volcenginesdkarkruntime: 用于访问Ark SDK,生成文本嵌入。

加载文档

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())
  • base_dir指定了文档存放的文件夹路径。
  • os.listdir(base_dir)列出该目录下的所有文件。
  • 根据文件后缀(.pdf.docx.txt)选择相应的加载器,并加载文件内容。
  • 加载的文档内容会被追加到documents列表中。

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)
  • RecursiveCharacterTextSplitter: 这是LangChain提供的一个文本切分器,用于将较长的文档切分成多个小块,以便后续处理。

    • chunk_size=200指定每个文本块的最大字符数为200。
    • chunk_overlap=10指定相邻文本块之间的重叠部分为10个字符,确保切割不会破坏上下文。
  • chunked_documents存储切分后的文档块。

3. 存储嵌入 (Store Embeddings)

from langchain_community.vectorstores import Qdrant

class DoubaoEmbeddings(BaseModel, Embeddings):
    client: Ark = None
    api_key: str = ""
    model: str

    def __init__(self, **data: Any):
        super().__init__(**data)
        if self.api_key == "":
            self.api_key = os.environ["OPENAI_API_KEY"]
        self.client = Ark(
            base_url=os.environ["OPENAI_BASE_URL"],
            api_key=self.api_key
        )

    def embed_query(self, text: str) -> List[float]:
        embeddings = self.client.embeddings.create(model=self.model, input=text)
        return embeddings.data[0].embedding

    def embed_documents(self, texts: List[str]) -> List[List[float]]:
        return [self.embed_query(text) for text in texts]

    class Config:
        arbitrary_types_allowed = True
  • DoubaoEmbeddings: 定义了一个自定义的嵌入模型类,用于生成文本的嵌入。

    • embed_query方法:将单个文本转换为嵌入(即将文本转化为数值向量)。
    • embed_documents方法:将多个文本转化为嵌入。
    • 使用Ark客户端与指定的API密钥进行通信来生成嵌入。
  • Qdrant: 是一个向量数据库,用于存储文本的嵌入。在此,Qdrant.from_documents将分割后的文档嵌入并存储到Qdrant中。

4. 模型和检索 (Model and Retrieval Chain)

import logging
from langchain_openai import ChatOpenAI
from langchain.retrievers.multi_query import MultiQueryRetriever
from langchain.chains import RetrievalQA
  • logging: 用于设置日志记录,方便调试和记录运行过程。
  • ChatOpenAI: 使用OpenAI的GPT-3模型来生成答案。
  • MultiQueryRetriever: 这个工具用于从Qdrant数据库中检索多个相关的文档,以便生成回答。
  • RetrievalQA: 这是一个结合了检索和问答生成的链,用于回答问题。
logging.basicConfig()
logging.getLogger("langchain.retrievers.multi_query").setLevel(logging.INFO)

llm = ChatOpenAI(model=os.environ["LLM_MODELEND"], temperature=0)

retriever_from_llm = MultiQueryRetriever.from_llm(
    retriever=vectorstore.as_retriever(), llm=llm
)

qa_chain = RetrievalQA.from_chain_type(llm, retriever=retriever_from_llm)
  • llm:实例化一个OpenAI的GPT-3模型,作为问答生成模型。
  • retriever_from_llm:通过MultiQueryRetriever来结合检索和模型,获取与问题相关的文档。
  • qa_chain:构建一个检索+生成的问答链。

5. 输出界面 (Output - Flask UI)

from flask import Flask, request, render_template

app = Flask(__name__)

@app.route("/", methods=["GET", "POST"])
def home():
    if request.method == "POST":
        question = request.form.get("question")
        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)
  • Flask:用于创建Web应用程序并处理前端请求。

    • @app.route装饰器定义了根路由,支持GET和POST请求。
    • 用户输入问题时,表单提交到后端,通过qa_chain生成答案并渲染到网页上。
    • render_template用于渲染HTML模板,将回答结果传递给前端显示。

运行结果

image.png

image.png

总结

1. 加载文档

首先,系统会从指定的文件夹中加载不同格式的文档(如PDF、Word、文本文件)。这些文档可能包含员工手册、公司政策等内容。系统会根据文件的格式选择不同的加载器(如PDF加载器、Docx加载器等)来提取文件中的文本。

2. 切分文档

由于文档内容可能很长,系统会将加载的文档切分成更小的块(称为“文本块”)。每个文本块大小适中(例如200个字符),这样便于后续处理。切分后,每个文档都会变成多个小片段,保证信息不丢失。

3. 生成嵌入并存储

接着,系统会将每个文本块转换成数字形式的“嵌入”,这些嵌入是文本内容的数学表示。嵌入可以帮助系统理解文档内容的语义关系。这些嵌入会被存储到一个向量数据库(如Qdrant)中,以便后续检索。

4. 构建检索与问答链

当用户提出问题时,系统首先通过检索功能找到与问题相关的文档片段。为了提高答案的准确性,系统会使用一个多查询检索工具(MultiQueryRetriever)从存储的嵌入中找到最相关的文档块。然后,使用OpenAI的GPT模型生成答案,结合文档内容,系统能够准确回答用户的问题。

5. Web界面提供交互

最后,系统通过Flask框架提供一个Web界面,允许用户输入问题并显示答案。用户通过简单的网页表单提交问题,系统将问题发送给问答链处理,并将生成的答案显示在网页上,供用户查看。

流程:

整个系统的核心流程是:加载和切分文档 -> 生成嵌入并存储 -> 检索相关内容并生成答案 -> 通过Web界面与用户交互。通过这种方式,系统能够根据用户的提问,基于最新的文档内容提供精准的答案。