用Langchain和Deep Lake查询您的文件!

使用Langchain和Deep Lake查询文件!

介绍

像Langchain和Deep Lake这样的大型语言模型在文档问答和信息检索方面取得了长足的进步。这些模型对世界了解很多,但有时候,它们难以知道自己不知道某件事。这导致它们编造东西来填补空白,这是不好的。

然而,一种名为“检索增强生成”(RAG)的新方法似乎很有希望。使用RAG来查询带有私有知识库的LLM。它通过从数据源中添加额外信息来帮助这些模型变得更好。这使它们更具创新性,并在没有足够信息时有助于减少错误。

RAG通过增强提示信息来改进专有数据,从而提高这些大型语言模型的知识,同时减少幻觉的发生。

学习目标

1. 了解RAG方法及其优势

2. 了解文档问答中的挑战

3. 简单生成和检索增强生成之间的区别

4. 在类似文档问答的行业应用案例中实际实施RAG

通过完成本篇学习文章,您应该对检索增强生成(RAG)及其在文档问答和信息检索中提高LLM性能的应用有坚实的理解。

本文是数据科学博文馆的一部分。

入门指南

关于文档问答,理想的解决方案是在问问题时为模型提供所需的具体信息。然而,确定哪些信息是相关的可能是棘手的,并取决于大型语言模型的预期任务。这就是RAG概念的重要性所在。

让我们看看RAG流程是如何工作的:

检索增强生成

RAG是一种尖端的生成式AI架构,它利用语义相似性自主识别相关信息以回应查询。以下是RAG的简明概述:

  1. 向量数据库:在RAG系统中,您的文档存储在专门的向量数据库中。每个文档都根据嵌入模型生成的语义向量进行索引。这种方法可以快速检索与给定查询向量密切相关的文档。每个文档都被分配一个表示其语义含义的数字表示(向量)。
  2. 查询向量生成:当提交一个查询时,相同的嵌入模型会生成代表查询的语义向量。
  3. 基于向量的检索:随后,模型利用向量搜索来识别与查询向量密切对齐的数据库中的文档。这一步是确定最相关文档的关键。
  4. 响应生成:在检索到相关文档后,模型将它们与查询一起使用以生成响应。这种策略使模型能够在需要时精确访问外部数据,增强其内部知识。

示意图

下面的示意图总结了上述所有步骤:

从上面的图中,有两个重要的要点:

  1. 在简单生成中,我们永远不会知道源信息。
  2. 当模型过时或其知识截止时间在查询提出之前时,简单生成可能导致错误的信息生成。

通过RAG方法,我们的LLM的提示将是我们给出的指令、检索上下文和用户的查询。现在,我们有了检索到的信息的证据。

所以,不需要多次对不断变化的信息场景进行繁琐的训练,您可以将更新的信息添加到向量存储/数据存储中。用户下次可以提出类似的问题,其答案现在已经改变(以XYZ公司的一些财务记录为例)。您已经准备就绪。

希望这样会让您对RAG的工作原理有所了解。现在,让我们进入正题。是的,代码。

我知道您来这里不是为了闲聊。👻

让我们跳转到好的部分!

1:创建VSCode项目结构

打开VSCode或您喜欢的代码编辑器,并按照以下方式创建项目目录(仔细按照文件夹结构) –

记得使用Python ≥ 3.9创建虚拟环境,并在requirements.txt文件中安装依赖项。(别担心,我会分享资源的GitHub链接。)

2:创建检索和嵌入操作的类

在controller.py文件中,粘贴下面的代码并保存。

from retriever.retrieval import Retriever

# 创建一个Controller类来管理文档嵌入和检索
class Controller:
    def __init__(self):
        self.retriever = None
        self.query = ""

    def embed_document(self, file):
        # 如果提供了'file',则嵌入文档
        if file is not None:
            self.retriever = Retriever()
            # 为提供的文档文件创建并添加嵌入
            self.retriever.create_and_add_embeddings(file.name)

    def retrieve(self, query):
        # 根据用户的查询检索文本
        texts = self.retriever.retrieve_text(query)
        return texts  

这是一个帮助类,用于创建我们的Retriever对象。它实现了两个函数 –

embed_document:生成文档的嵌入

retrieve:当用户提问时检索文本

在以后的过程中,我们将更深入地了解Retriever中的create_and_add_embeddings和retrieve_text辅助函数!

3:编写我们的检索流程!

在retrieval.py文件中,粘贴下面的代码并保存。

3.1:导入必要的库和模块

import os
from langchain import PromptTemplate
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores.deeplake import DeepLake
from langchain.document_loaders import TextLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain.document_loaders import PyMuPDFLoader
from langchain.chat_models.openai import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.memory import ConversationBufferWindowMemory

from .utils import save

import config as cfg

3.2:初始化Retriever类

# 定义Retriever类
class Retriever:
    def __init__(self):
        self.text_retriever = None
        self.text_deeplake_schema = None
        self.embeddings = None
        self.memory = ConversationBufferWindowMemory(k=2, return_messages=True)csv

3.3:让我们来编写创建并添加文档嵌入到Deep Lake的代码

def create_and_add_embeddings(self, file):
    # 如果目录"data"不存在,则创建一个名为"data"的目录
    os.makedirs("data", exist_ok=True)

    # 使用OpenAIEmbeddings初始化嵌入
    self.embeddings = OpenAIEmbeddings(
        openai_api_key=cfg.OPENAI_API_KEY,
        chunk_size=cfg.OPENAI_EMBEDDINGS_CHUNK_SIZE,
    )

    # 使用PyMuPDFLoader从提供的文件中加载文档
    loader = PyMuPDFLoader(file)
    documents = loader.load()

    # 使用CharacterTextSplitter将文本分成块
    text_splitter = CharacterTextSplitter(
        chunk_size=cfg.CHARACTER_SPLITTER_CHUNK_SIZE,
        chunk_overlap=0,
    )
    docs = text_splitter.split_documents(documents)

    # 为文本文档创建DeepLake模式
    self.text_deeplake_schema = DeepLake(
        dataset_path=cfg.TEXT_VECTORSTORE_PATH,
        embedding_function=self.embeddings,
        overwrite=True,
    )

    # 将拆分的文档添加到DeepLake模式中
    self.text_deeplake_schema.add_documents(docs)

    # 使用搜索类型“similarity”从DeepLake模式创建一个文本检索器
    self.text_retriever = self.text_deeplake_schema.as_retriever(
        search_type="similarity"
    )

    # 配置文本检索器的搜索参数
    self.text_retriever.search_kwargs["distance_metric"] = "cos"
    self.text_retriever.search_kwargs["fetch_k"] = 15
    self.text_retriever.search_kwargs["maximal_marginal_relevance"] = True
    self.text_retriever.search_kwargs["k"] = 3

3.4:现在,让我们编写一个能够检索文本的函数!

def retrieve_text(self, query):
    # 以只读模式为文本文档创建一个DeepLake模式
    self.text_deeplake_schema = DeepLake(
        dataset_path=cfg.TEXT_VECTORSTORE_PATH,
        read_only=True,
        embedding_function=self.embeddings,
    )

    # 为向模型提供指令定义一个提示模板
    prompt_template = """您是一款高级人工智能,能够分析文档中的文本并提供详细的答案给用户查询。您的目标是提供全面的回答,以消除用户需要重新访问文档的需求。如果您没有答案,请承认而不是编造信息。
    {context}
    问题:{question} 
    答案:
    """

    # 使用"context"和"question"创建一个PromptTemplate
    PROMPT = PromptTemplate(
        template=prompt_template, input_variables=["context", "question"]
    )

    # 定义链式类型
    chain_type_kwargs = {"prompt": PROMPT}

    # 初始化ChatOpenAI模型
    model = ChatOpenAI(
        model_name="gpt-3.5-turbo",
        openai_api_key=cfg.OPENAI_API_KEY,
    )

    # 创建模型的RetrievalQA实例
    qa = RetrievalQA.from_chain_type(
        llm=model,
        chain_type="stuff",
        retriever=self.text_retriever,
        return_source_documents=False,
        verbose=False,
        chain_type_kwargs=chain_type_kwargs,
        memory=self.memory,
    )

    # 使用用户的问题查询模型
    response = qa({"query": query})

    # 从llm返回响应
    return response["result"]

4:查询我们的流水线并提取结果的实用函数

将以下代码粘贴到utils.py文件中:

def save(query, qa):
    # 使用get_openai_callback函数
    with get_openai_callback() as cb:
        # 使用用户的问题查询qa对象
        response = qa({"query": query}, return_only_outputs=True)
        
        # 从llm的响应中返回答案
        return response["result"]

5:用于存储密钥的配置文件…没有花哨的东西!

将以下代码粘贴到config.py文件中:

import os
OPENAI_API_KEY = os.getenv(OPENAI_API_KEY)
TEXT_VECTORSTORE_PATH = "data\deeplake_text_vectorstore"
CHARACTER_SPLITTER_CHUNK_SIZE = 75
OPENAI_EMBEDDINGS_CHUNK_SIZE = 16

最后,我们可以为演示编写我们的Gradio应用!

6:Gradio应用!

将以下代码粘贴到app.py文件中:

# 导入必要的库
import os
from controller import Controller
import gradio as gr

# 为了提高性能,禁用分词器的并行处理
os.environ["TOKENIZERS_PARALLELISM"] = "false"

# 初始化Controller类
controller = Controller()

# 定义一个处理上传PDF文件的函数
def process_pdf(file):
    if file is not None:
        controller.embed_document(file)
    return (
        gr.update(visible=True),
        gr.update(visible=True),
        gr.update(visible=True),
        gr.update(visible=True),
    )

# 定义一个响应用户消息的函数
def respond(message, history):
    botmessage = controller.retrieve(message)
    history.append((message, botmessage))
    return "", history

# 定义一个清除对话历史记录的函数
def clear_everything():
    return (None, None, None)

# 创建一个Gradio界面
with gr.Blocks(css=CSS, title="") as demo:
    # 显示标题和描述
    gr.Markdown("# AskPDF ", elem_id="app-title")
    gr.Markdown("## 上传PDF并提问!", elem_id="select-a-file")
    gr.Markdown(
        "拖放一个有趣的PDF并提问!",
        elem_id="select-a-file",
    )
    
    # 创建上传部分
    with gr.Row():
        with gr.Column(scale=3):
            upload = gr.File(label="上传PDF", type="file")
            with gr.Row():
                clear_button = gr.Button("清除", variant="secondary")

    # 创建聊天机器人界面
    with gr.Column(scale=6):
        chatbot = gr.Chatbot()
        with gr.Row().style(equal_height=True):
            with gr.Column(scale=8):
                question = gr.Textbox(
                    show_label=False,
                    placeholder="例如:文档关于什么?",
                    lines=1,
                    max_lines=1,
                ).style(container=False)
            with gr.Column(scale=1, min_width=60):
                submit_button = gr.Button(
                    "请问我 🤖", variant="primary", elem_id="submit-button"
                )

    # 定义按钮
    upload.change(
        fn=process_pdf,
        inputs=[upload],
        outputs=[
            question,
            clear_button,
            submit_button,
            chatbot,
        ],
        api_name="upload",
    )
    question.submit(respond, [question, chatbot], [question, chatbot])
    submit_button.click(respond, [question, chatbot], [question, chatbot])
    clear_button.click(
        fn=clear_everything,
        inputs=[],
        outputs=[upload, question, chatbot],
        api_name="clear",
    )

# 启动Gradio界面
if __name__ == "__main__":
    demo.launch(enable_queue=False, share=False)

准备好你的🧋,因为现在是时候看看我们的流水线是如何工作的了!

要启动Gradio应用程序,请打开一个新的终端实例并输入以下命令:

python app.py

注意:确保虚拟环境已激活,并且你在当前项目目录中。

Gradio将在本地服务器上启动您的应用程序的新实例,如下所示:

你只需要按住CTRL并点击本地主机URL(最后一行),你的应用程序将在浏览器中打开。

耶!

我们的Gradio应用程序来了!

让我们放一个有趣的PDF文件!我将使用这个Kaggle存储库中包含《哈利·波特》第1章PDF的PDF文件,其中包含了《哈利·波特》第1至7章的PDF格式。

呼光!愿光明与你同在🪄

现在,只要你上传文件,就会激活查询文本框,如下所示:

现在让我们进入最令人期待的部分——问答!

哇! 😲

我喜欢答案的准确性!

此外,看看Langchain的记忆如何保持链状态,将过去运行的上下文纳入其中。

它记得在这里是我们心爱的麦格教授!❤️‍🔥

应用程序工作的简短演示!

RAG的实用负责任的方法对于各个研究领域的数据科学家来说非常有用,可以构建准确且负责任的人工智能产品。

1. 在医疗保健诊断中,将RAG用于帮助医生和科学家诊断复杂的医疗状况,通过将患者记录、医学文献、研究论文和期刊集成到知识库中,帮助在做出关键决策和进行医疗研究时检索最新信息。

2. 在客户支持方面,公司可以方便地使用由RAG驱动的对话式人工智能聊天机器人来帮助解决客户的查询、投诉以及有关产品和手册的信息,通过提供准确的回答来改善客户体验!

3. 在金融科技领域,分析师可以将实时金融数据、市场新闻和历史股价纳入他们的知识库中,RAG框架将快速高效地回应有关市场趋势、公司财务、投资和收入的查询,帮助进行强有力且负责任的决策。

4. 在教育科技市场中,可以部署由RAG制作的聊天机器人,以帮助学生解决他们的疑问,根据大量的教科书、研究文章和教育资源库提供建议、全面的答案和解决方案。这使学生能够在不需要大量手动研究的情况下加深对学科的理解。

范围是无限的!

结论

在本文中,我们探讨了使用Langchain和Deep Lake的RAG机制,其中语义相似性在确定相关信息方面起着关键作用。通过向量数据库、查询向量生成和基于向量的检索,这些模型在需要时精确地访问外部数据。

结果呢?更精确、上下文适当的响应,以及丰富的专有数据。希望您喜欢并在其中学到了一些东西!随时从我的GitHub存储库下载完整的代码,进行尝试。

重点要点

  1. RAG简介:Retrieval Augmented Generation(RAG)是大型语言模型(LLM)中的一种有前途的技术,通过添加来自其自身数据源的额外信息来增强其知识,使其更加智能,减少在缺乏信息时的错误。
  2. 文档问答中的挑战:大型语言模型在文档问答(QnA)方面取得了显著进展,但有时会难以区分何时缺少信息,从而导致错误。
  3. RAG流程:RAG流程利用语义相似性来识别相关的查询信息。它涉及向量数据库、查询向量生成、基于向量的检索和响应生成,最终提供更精确和上下文适当的响应。
  4. RAG的好处:RAG允许模型为其检索到的信息提供证据,减少在快速变化的信息场景中频繁重新训练的需求。
  5. 实际实施:本文提供了一个实施RAG流程的实际指南,包括设置项目结构、创建检索和嵌入类、编码检索流程以及构建用于实时交互的Gradio应用程序。

常见问题

本文中显示的媒体不归Analytics Vidhya所有,由作者自行决定使用。