“易速鲜花”本地知识库智能问答系统 | 豆包MarsCode AI刷题

165 阅读4分钟

这门课,我们将学习如何使用RAG技术来构建一个内部员工问答系统。

Question:

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

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

我们将分别从非结构化数据结构化数据中提取知识,然后通过LLM,实现一个QA/Chat机器人,现在我们一般叫RAG,检索增强生成。

整个框架分为三个部分。

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

整个流程分为五个部分:

加载文档Loading->文档拆分Splitting->文档嵌入Embedding->文档保存Storage->文档检索Retrieval->输出Output

其中文档嵌入和保存可以算成一步,就是对文档计算向量后保存在向量数据库内。

1. 加载文档

我们可以使用langchain.document_loaders来导入各种我们需要的包,例如解码PDF/Word/txt

from langchain.document_loaders import PyPDFLoader 
from langchain.document_loaders import Docx2txtLoader 
from langchain.document_loaders import TextLoader

然后我们可以通过加载器通过路径加载文件。

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里自带了文本分割器text_splitter

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

chunk_size是指一个块的大小, chun_overlap是相邻两个块之间的重叠数。

3. 向量数据库存储

也是调库,你可以自己选择自己想要的模型,在Langchain上找对应的文档,一行代码,参数是文档,返回的是embeddings。这里使用OpenAIEmbeddings。

向量数据库同理,可以使用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

4. 相关信息获取

现在我们的文档,不管原来是什么形式,是图片,是PDF,还是WORD,现在都是一条条的嵌入向量。于是我们就可以进行具体的查询操作了,一般是基于向量的距离或者相似度进行,例如欧氏距离和余弦相似度。

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

Langchain的检索十分简单,我们上一步得到了向量数据库,直接把我们要查询的内容,丢入向量数据库就可以,然后向量数据库会返回给我们最相近的几条数据。

from langchain.chat_models import ChatOpenAI
from langchain.retrievers.multi_query import MultiQueryRetriever
from langchain.chains import RetrievalQA

llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0)

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

# 实例化一个RetrievalQA链
qa_chain = RetrievalQA.from_chain_type(llm,retriever=retriever_from_llm)

这里的MultiQueryRetriever是多重查询的作用,根据用户输入的问题,让大模型生成多个询问,选中较多且相近的回答,使得回答更准确。

除此之外,还有上下文压缩,最大相似性,最大边际相似性等等。

5. 生成回答并展示

将用户输入的查询,丢入我们刚刚写好的链,即可得到回答

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)