继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

提升RAG效果的高级检索技巧

狐的传说
关注TA
已关注
手记 319
粉丝 88
获赞 555
掌握高级信息检索技巧:利用Langchain的尖端技术优化选择相关文档,以创建出色的相关文档检索与生成系统
目录

·**介绍
·
向量存储的创建
·
方法:朴素检索器
·
方法:父文档检索器
·
方法:自我查询检索器**
∘查询构造器
∘ 查询翻译
·**
方法:上下文压缩重排序检索器
·
结论**

瑞典简介

让我们快速回顾一下构成“RAG”的三个缩写分别代表什么吧

  • 检索:RAG的主要目标是收集与查询相关的最相关文档或片段。
  • 增强提示:创建一个结构良好的提示,使模型能够清楚地知道它的目的、背景以及应该如何回应。
  • 生成:这时大语言模型就能大显身手了。当模型得到了好的上下文信息(由“检索”步骤提供)并且有了明确的指令(由“增强提示”步骤提供),它将为用户提供有价值的回应。

正如我们所见,对于用户查询的响应,生成取决于我们是否为了问答目的使用了RAG,这直接取决于我们如何构建“增强”和尤其是“检索”。

在这篇文章中,我们将只关注“检索”部分。在这个返回最相关文档的重要环节中,向量存储的概念浮现。

本文介绍了这些技术(图片由作者提供)。

为了创建这些检索结果,我们将使用Langchain。

本文中使用的技术简介(作者提供)。

向量数据库其实就是一个向量数据库,它以向量格式存储文档。这种向量表示来自Transformer模型的使用。我并不是告诉你你现在不知道的事。

很明显,这个向量存储越强大和完整,我们就能运行越好的检索器。我们已经知道创建这个数据库本身就是一项艺术。根据我们使用块的大小或嵌入模型的差异,我们的RAG效果会更好或更差。

做个澄清:

在这篇文章里,我们不会讨论如何创建这个向量库。
我们将讨论一些用于检索相关文档的方法和技术。

因为一张图胜过千言万语,我建议你看一下下面的内容。

RAG包括一系列清晰定义的步骤。本文仅讨论检索器部分(作者提供图片)。

因此,我要再次强调,在这篇文章里,我们将深入研究在创建一个优秀的RAG工具过程中众多重要步骤中的一个。“Retrieve”步骤至关重要,因为它直接改善了大型语言模型生成响应时的上下文环境。

我们要学习的方法是:

  • 朴素检索器
  • 父文档检索器
  • 自查询检索器
  • 上下文压缩检索器(重排)

你可以在这里找到包含笔记本的项目这里。你也可以在我的github主页上看看:

damiangilgonzalez1995 - 概览热爱数据科学,我从物理转行到数据科学。曾就职于Telefonica、HP,现为某公司的CTOgithub.com
创建向量数据存储

为了更好地说明这些方法,我们将通过一个实际案例来说明,提高说明的清晰度。因此,我们将创建一个关于《疾速追杀》电影评论的RAG(检索、检索和生成)。

为了让读者能跟上这篇帖子的每一步,他们可以访问我创建的代码仓库。在那里你能找到每个方法的代码,以及用于创建向量数据库的文档。负责这项任务的Jupyter笔记本可以在Git仓库中找到,文件名为“0__create_vectordb.ipynb”。

关于我们的RAG的数据来源,有4个csv文件,每个文件对应的是约翰·威克系列电影的评论。这些文件包含以下信息。

该项目的数据集(图片由作者制作)。

如你所见,“评论”字段是我们爬虫要抓取的内容。其他字段将作为元数据存储:

  • 电影标题:
  • 评论日期:
  • 评论标题:
  • 评论网址:
  • 作者:
  • 评分:

为了将我们文件中的每一行读取并转换为“Document”格式,我们执行以下代码:

    从langchain_community.document_loaders.csv_loader导入CSVLoader模块  
    从datetime导入datetime, timedelta;  

    docs = []  

    for i in range(1, 4):  
      loader = CSVLoader(  
        编码="utf8",  
        文件路径=f"data/john_wick_{i}.csv",  
        元数据列=["Review_Date", "Review_Title", "Review_Url", "Author", "Rating"]  
      )  

      movie_docs = loader.load()  
      for doc in movie_docs:  

        # 我们将电影编号的元数据添加如下  
        doc.metadata["Movie_Title"] = f"John Wick {i}"  

        # 将"Rating"转换为`int`,如果没有提供评分则设为5  
        doc.metadata["Rating"] = int(doc.metadata["Rating"]) if doc.metadata["Rating"] else 5  

      docs.extend(movie_docs)

我们已经有“Document”格式的文档了:

    print(documents[0])  

    文档内容(page_content=": 0\n评论:我可以用这样的方式来描述《疾速追杀》:想象一下《疾速追杀》但主角是基努·里维斯而不是连恩·尼文,保护的是他的狗而不是他的女儿。这就是电影的基本情节。约翰·威克(里维斯饰演)要为那些夺走他所爱的人复仇。这部电影的主题非常简单但很美——当动作电影变得复杂时,它们会变得糟糕,比如《虎胆隆隆2》。《疾速追杀》给了观众他们想要的东西:精彩的动作、时尚的特技、充满活力的混乱场面,以及一个让观众产生共鸣的英雄来把这一切联系在一起。《疾速追杀》在简单中取得了成功,简洁中蕴含力量。", metadata={'source': 'data/john_wick_1.csv', 'row': 0, 'Review_Date': '6 May 2015', 'Review_Title': ' 动感、简洁和时尚;《约翰·威克1》打得好。', 'Review_Url': '/review/rw3233896/?ref_=tt_urv', 'Author': 'lnvicta', 'Rating': 8, 'Movie_Title': '约翰·威克1', 'last_accessed_at': datetime.datetime(2024, 4, 8, 11, 49, 47, 92560)})

我们只需要创建一个本地的向量数据库(向量数据库)。为此,我用的是 Chroma。同时需要注意,需要使用一个嵌入模型,将我们的文档转换成向量格式以便存储。这些内容可以在下面的代码片段中看到:

from langchain_community.vectorstores import Chroma  
from langchain_openai import OpenAIEmbeddings  
import os  
from dotenv import load_dotenv  

load_dotenv()  

os.environ["OPENAI_API_KEY"] = os.getenv('OPENAI_KEY')  

embeddings = OpenAIEmbeddings(model="text-embedding-3-small")  

db = Chroma.from_documents(documents=documents, embedding=embeddings, collection_name="doc_jonhWick", persist_directory="./jonhWick_db")

这将在我们这里创建一个名为“_JohnWickdb”的数据库。这将是我们的RAG(检索和生成)使用的数据库,检索器将从中获取与用户查询最相关的文档。

现在是介绍不同创建信息检索器的方法的时候了。

方法:简单检索器,

请查看 1_naive_retriever.ipynb 文件,代码在里面。

这种方法非常简单,事实上它的名字就表明了这一点。我们用这种方式来标识这种方法,简单的理由是当我们输入查询到数据库时,我们天真地希望它能返回最相关的文档或片段。

基本上来说,我们用与建立向量数据库相同的Transformer模型来编码用户查询。一旦得到其向量表示,我们通过计算余弦相似度、距离等来衡量相似度。

我们选取与查询最匹配的排名前K的文档。

此种检索器的工作方式如下图所示。

一个简化的朴素检索器(图:作者)。

记住这个方案,我们来看看代码是怎样的。我们读数据库。

from langchain_community.vectorstores import Chroma  
from langchain_openai import OpenAIEmbeddings  
import os  
from dotenv import load_dotenv  

load_dotenv()  
os.environ["OPENAI_API_KEY"] = os.getenv('OPENAI_KEY')  

embeddings = OpenAIEmbeddings(model="text-embedding-3-small")  

vectordb = Chroma(persist_directory="./jonhWick_db",   
                  embedding_function=embeddings,   
                  collection_name="doc_jonhWick")

然后我们创建了我们的retriever。我们还可以调整相似度计算方法和其他参数。

寻回犬

# 指定 top-k
naive_retriever = vectordb.as_retriever(search_kwargs={"k": 10})

# 基于相似度阈值的检索
# naive_retriever = db.as_retriever(search_kwargs={"score_threshold": 0.8}, search_type="similarity_score_threshold")

# 最大边际相关性检索
# naive_retriever = db.as_retriever(search_type="mmr")

实际上,我们已经创建了我们的“Naive Retriever”,但为了看看它怎么运作,我们将创建完整的RAG,记得它由以下组件组成。

  • R (检索)已完成
  • A (增强)还没
  • G (生成)还没

增强现实与生成技术

    from langchain_core.prompts import ChatPromptTemplate  
    from langchain_openai import ChatOpenAI  

    # 增强版  
    TEMPLATE = """\  
    你是一个愉快的助手。请使用下面提供的上下文来回答问题。  

    如果你不知道答案,或者不确定,请说你不知道。  

    问题:  
    {question}  

    上下文:  
    {context}  
    """  

    rag_prompt = ChatPromptTemplate.from_template(TEMPLATE)  
    # 生成部分  
    chat_model = ChatOpenAI()

我们已经有了RAG的三个组件。接下来就是把它们组装起来,为此我们将使用langchain链来创建RAG。

你可能还不知道由langchain创建的语言,用于更高效地构建链。这种语言被称为LCEL(LangChain表达式语言)。如果你对在langchain中使用这种方法来构建链还不是很熟悉,这里有一个非常好的教程推荐给你:

最后一步,我们使用Langchain的链创建语言(LCEL,链创建语言)来创建我们的RAG:

    从 langchain_core.runnables 导入 RunnablePassthrough, RunnableParallel 作为变量名保持英文,以符合实际编程习惯。
    从 operator 导入 itemgetter  
    从 langchain_core.output_parsers 导入 StrOutputParser  

    setup_and_retrieval = RunnableParallel({"question": RunnablePassthrough(), "context": naive_retriever })  
    output_parser = StrOutputParser()  

    naive_retrieval_chain = setup_and_retrieval   
                            | rag_prompt   
                            | chat_model   
                            | output_parser  

    naive_retrieval_chain.invoke(
        "人们普遍喜欢《疾速追杀》吗?")  

    # 响应: 是的,人们普遍喜欢《疾速追杀》。

这是为RAG创建链的最简单方法。在Jupyter笔记本中,你可以找到一个更健壮的相似链。为了不让大家在这个话题上迷失,所以我只展示了最简单的版本。为了让我们更好地理解上述代码,我制作了这张非常清晰的图表。

使用Langchain及其LCEL语言创建RAG(RAG:检索增强生成)(图片来源:作者)。

太好了,我们的Naive RAG完成了创建。我们来试试下一个方法。

方法 Parent Document 获取器

代码位于这个文件中,。

注:原文末尾没有句号,为保持与原文一致,句号应保留但去掉多余的逗号。因此最终翻译应为:

代码位于这个文件中。

假设我们创建了一个RAG,通过输入一些症状来识别可能的疾病。如果我们有一个Naive RAG,可能会收集一系列只共享一两个症状的可能疾病,这会使得我们的工具处境尴尬。

这是使用Parent Doc Retriever的理想场景。这种技术的类型包括将大的块(父块)切割成更小的块(子块)。通过将信息分割成小块,信息变得更加集中,因此,这些小块中的信息价值不会被分散在多个段落中。

这里面有个小问题:

  • 如果我们想要精准找到最相关的文档,我们需要将文档拆分成小段
  • 但是,提供良好的上下文给大模型(LLM)也很重要,这可以通过提供大段文档来实现。

如下面的图片所示:

这两个概念或指标间的平衡(作者提供)。

看起来这个问题没辙了,因为一旦我们提高精度,上下文就会变窄,反之亦然。这时,我们就会发现一个解决方法。

主要的想法是将大的块进一步切分成更小的子文档。这样做后,用子文档搜索,找出最相关的前K个子文档,并将这些前K个子文档所属的父文档返回。

我们已经有了大致的想法,现在让我们一步步地把它具体化吧。

  1. 获取文档并创建大块内容(父级块
  2. 将每个父级块进行拆分以生成子级块
  3. 将子级块(向量表示)保存到向量存储库中。
  4. 父级块保留在内存中

以下图片中可以看到

子块是如何从父块创建的视觉展示,以及它们如何存储。这些步骤对于创建父文档检索器是必要的(作者提供)。

这可能看起来很复杂,但其实创建起来很简单,因为我们必须创建一个新的数据库来存储这些小块,同时将父块保留在内存中。另外,我们还需要知道每个子块的父块是谁。还好有 Langchain,用它来构建简直小菜一碟。

毫无疑问,你已经得出结论,为了这种方法,有必要创建一个新的向量数据库。此外,在评论约翰·威克电影的情况下,比如以CSV文件为数据源时,不需要进行第一次分割(父级内容)。这是因为我们可以把每一行看作是独立的块。

总的来说,让我们看看下面这张展示这种方法是如何工作的图片:

下面的图展示了父文档检索器的工作原理(作者提供)。

去写代码可以表示为如下:

    从langchain.retrievers导入ParentDocumentRetriever  
    从langchain.storage导入InMemoryStore  
    从langchain_text_splitters导入RecursiveCharacterTextSplitter  
    从langchain_openai导入OpenAIEmbeddings  
    从langchain_community.vectorstores导入Chroma  

    # documents = 读取CSV文件。更多细节请参见Jupyter Notebook  

    parent_docs = documents  

    # 嵌入模型(Embedding Model)  
    embeddings = OpenAIEmbeddings(model="text-embedding-3-small")  

    # 分割器(Splitters)  
    child_splitter = RecursiveCharacterTextSplitter(chunk_size=200)  
    # 因为数据来自CSV文件,每行都是一个父文档  
    # parent_splitter = RecursiveCharacterTextSplitter(chunk_size=800)  

    # 存储(Stores)  
    store = InMemoryStore()  
    vectorstore = Chroma(embedding_function=embeddings, collection_name="fullDoc", persist_directory="./JohnWick_db_parentsRD")  

    parent_document_retriever = ParentDocumentRetriever(  
        vectorstore=vectorstore,  
        docstore=store,  
        child_splitter=child_splitter,  
        # parent_splitter =parent_splitter  
    )

这里有个直观的感觉,向量存储中子块的数量应该远远超过内存中父块的数量。我们可以通过下面的代码来验证这一点:

    print(f"父节点的数量为:{len(list(store.yield_keys()))}")  

    print(f"子节点的数量为:{len(parent_document_retriever.vectorstore.get()['ids'])}")  

    注释:  
    父节点的数量为:75  
    子节点的数量为:3701  

好的,我们已经有了我们的Parent Document Retriever,我们只需基于此检索器创建一个RAG就完成了。这与之前的步骤完全一样。我在langchain中附上了创建链的代码。要查看更多细节,请查看jupyter notebook以查看更多细节。

    setup_and_retrieval = RunnableParallel({"question": RunnablePassthrough(), "context": parent_document_retriever })  # 设置并行运行
    output_parser = StrOutputParser()  # 字符串输出解析器

    parent_retrieval_chain = setup_and_retrieval | rag_prompt | chat_model | output_parser  # 设置父文档检索链

注意,这与之前的情况完全一样,唯一的区别是,在“setup_and_retrieval”变量中,我们设置为使用我们的“parent_document_retriever”,而不是使用“naive_retriever”。

方法:自我查询器

这个代码位于 3_self_query_retriever.ipynb 文件中。

这可能是让我们检索工具更高效的绝招之一。

它的主要特点是能够在其向量数据库中执行搜索,并根据元数据应用相应的过滤规则。

我们知道,当我们应用“简单检索”时,我们会计算查询与向量库中所有数据块的相似度。向量库中的数据块越多,就需要进行更多的相似度计算。现在,想象一下能够先进行元数据过滤,在选择符合元数据条件的数据块后,再计算相似度。这能大大减少计算量和时间消耗。

让我们通过一个具体例子来更好地理解什么时候应用这种检索方式。

假设我们在向量数据库中存储了大量的休闲项目和体验(例如:冲浪课程、空中滑索、美食之旅等)。我们用嵌入模型来编码每个体验的描述。此外,每个项目有三个重要属性:日期、价格和地点。

假设一个用户正在寻找这种类型的体验:一种适合全家参与的自然体验,并且是安全的。此外,价格不能超过50美元,地点在加州。

这里的事情一目了然

我们不要求您提供那些不符合用户期望价格或地点要求的活动或体验。

所以,用不满足元数据过滤条件的片段/经历来计算相似性是没有意义的。

这种情况非常适合使用_自我查询检索器(Self Query Retriever, SQR)。这种类型的检索器(如SQR)允许我们首先根据元数据进行筛选;然后,它会计算符合元数据要求的片段与用户输入之间的相似度。

这项技术可以概括为两个相当具体的步骤。

  • 查询构造器
  • 查询翻译工具
查询构建器

名为“查询构建器”这一步骤的目的是根据用户输入来创建合适的查询和过滤条件。

谁负责应用这些过滤器?你怎么知道这些过滤器是什么样的?

为此我们将使用一个大型语言模型(LLM)。在第一次出现时,“大型语言模型”后面加上“(LLM)”作为注释。这个LLM需要能够决定何时应用哪些过滤器。我们还需要提前解释元数据是什么,以及每个元数据的具体含义。简而言之,提示必须包含三个关键点:

  • 背景:性格、你应该如何表现、输出形式等。
  • 元数据:关于可用元数据的信息。
  • 查询:用户的查询或问题。

因此,需要所谓的“查询转换器”。可以把这个称为“查询转换器”,因为它能把输出转换成适合数据库的形式。

查询翻译查询

这是一个负责将大语言模型(查询构造器)的输出翻译成适当的格式以执行查询的模块。根据你使用的向量数据库,你可能需要选择不同的翻译器。在我的情况下,我使用了Chroma db,因此我需要一个专注于该数据库的翻译器。幸运的是,Langchain几乎为所有数据库提供了特定的翻译器。

正如你可能已经注意到的,我一直很欣赏图表。我们来看下面这个图表,它能很好地阐明这个问题。

如下可视化展示了自我查询器的工作原理(作者提供)。

关于之前的图片,我们可以看到一切都是从用户的查询开始的。我们创建一个包含3个关键字段的提示,并将其提供给大型语言模型(LLM),该响应包含两个关键字段:“查询”和“过滤器”。这些字段被输入查询翻译器,后者将它们翻译成Chroma DB所需的正确格式。执行查询,返回与用户最初问题最相关的文档。

重要的是,用户输入的查询不必与数据库中现有的查询一致。如图所示的流程,LLM在考虑了可用的元数据和用户的提问之后,检测到可以使用“评分”元数据创建一个过滤器。它还根据用户的查询生成了一个新的查询。

让我们通过代码来查看这一切。正如我之前解释的,为LLM详细描述向量存储中可用的元数据是非常重要的。这转化为如下代码:

    from langchain.chains.query_constructor.base导入AttributeInfo  
    from langchain.retrievers.self_query.base导入SelfQueryRetriever  
    from langchain_openai导入ChatOpenAI  
    from langchain.retrievers.self_query.chroma导入ChromaTranslator  

    元数据字段信息列表 = [  
        AttributeInfo(  
            name="Movie_Title",  
            description="电影的标题",  
            type="str",  
        ),  
        AttributeInfo(  
            name="Review_Date",  
            description="评论的日期",  
            type="str",  
        ),  
        AttributeInfo(  
            name="Review_Title",  
            description="评论的标题",  
            type="str",  
        ),  
        AttributeInfo(  
            name="Review_Url",  
            description="评论的网址链接",  
            type="str",  
        ),  
        AttributeInfo(  
            name="Author",  
            description="评论的作者",  
            type="str",  
        ),  
        AttributeInfo(  
            name="Rating",  
            description="电影的1到10分评分",  
            type="int",  
        )  
    ]

为了定义我们的检索过程,我们需要定义以下几点:

  • 要使用的大型语言模型(LLM)
  • 要使用的嵌入模型(embedding model)
  • 要访问的向量库
  • 对此向量库中的文档可能包含哪些信息的描述
  • 元数据描述
  • 您想使用的查询转换器

我们来看一下代码长什么样。

    document_content_description = "对《John Wick》电影的评论。"  
    embeddings = OpenAIEmbeddings(model="text-embedding-3-small")  
    chat_model = ChatOpenAI()  

    self_query_retriever = SelfQueryRetriever.from_llm(  
        llm=ChatOpenAI(temperature=0),  
        vectorstore=向量数据库,  
        document_contents=文档内容描述,  
        metadata_field_info=元数据字段信息,  
        verbose=详细输出,  
        structured_query_translator=Chroma翻译器()  
    )

我们先是使用了一个基本的检索器,然后换成了一个自我查询型的检索器。这样我们就能通过一个非常清晰的例子来看到我们是如何大大提升了RAG的效果的。

    Question = "总结那些给《疾速追杀3》评分高于7的评论"  
    response = naive_retrieval_chain.invoke(Question)  
    print(response),  

    '''  
    我不知道答案。  
    '''  
    ------------------------------------------------------------------------  

    response = self_retrieval_chain.invoke(Question)  
    print(response),  

    '''  
    《疾速追杀:帕比伦》几乎可以说是关于后果的,处理约翰的...  
    '''

正如我们所见,进步明显。

方法:上下文压缩检索排序器(重新排序)

代码位于 4_contextual_compression_retriever(reranking).ipynb 文件里,查看一下。

  • 上下文范围:我们从向量存储中检索到的文档越多,LLM能够给出更好回答的信息就越充足
  • 召回率:从向量存储中检索到的文档越多,获取无关片段的概率越大,因此,虽然也会增加无关片段的数量,但召回率也会上升(这不是一件好事)。

这个问题似乎没有解决办法了。每当我们在一个指标上取得进步时,另一个指标似乎就会变差。我们真的确定是这样吗?

这里介绍了这项技术,即压缩检索技术,主要集中在重排序技术上。简单来说,这项技术包括两个非常不同的步骤:

  • 步骤1:根据输入或问题获取足够量的相关文档。通常我们会设定最相关的K个文档作为初始集。
  • 步骤2:重新评估这些文档的相关性,剔除那些实际上并不相关的文档(压缩步骤)。

第一步我们使用所谓的Bi-Encoder,其实就是我们通常用来制作基本RAG的模型。将文档向量化,同时对查询进行向量化处理,并根据我们选择的度量标准计算相似度。

这一步与我们常见的不一样,它是由重排序模型或者交叉编码器来完成的。

这些模型需要输入两份文档/文本,返回它们之间的相似度评分。

如果这两个中一个是查询,另一个是,我们就能算出它们有多像。

这两种方法可以展示为:

文中提到的两种计算文本相似度的方法的可视化展示(图源作者)。

你可能已经注意到,这两种方法最终都得到了相同的结果,一种衡量两个文本相似性的标准。而这确实是正确的,但有一个关键点:

交叉编码器(cross encoder)返回的结果比双编码器(Bi-encoder)更可靠。

好的,这样效果会更好,因为我们只对与查询最相似的前K个块进行操作,因为直接这样做会非常耗费时间和计算资源,成本高昂。出于这个原因,我们将重新排序模型的使用限制在前K次。

一个好问题是:在哪里可以找到Cross-Encoder模型?幸运的是,我们可以在HuggingFace找到这些模型。在这个具体案例中,我们将使用Cohere公司提供的模型。

Cohere | 领先的人工智能平台,为企业打造Cohere 提供业内领先的语言模型 (LLMs),和 RAG 功能,以满足企业的需求和期望…cohere.com

为了更好地理解这种方法的结构,我们来看一个视觉示例。

视觉上展示了上下文压缩检索器(重排序)的工作方式(作者提供图片)。

图片显示了这些步骤。

  • 1º) 我们首先获取查询,然后使用变压器将查询编码成向量形式,并将其输入到向量库中。
  • 2º) 从数据库中收集与查询最相似的文档。我们可以使用任何检索方法。
  • 3º) 接下来我们使用Cohere交叉编码模型。在这个图示的例子中,该模型将会被使用4次。请记住,该模型的输入将是查询和一个文档/片段,以收集这两个文本的相似度。
  • 4º) 在上一步中,我们对该模型进行了4次调用,并获得了查询与每个文档之间相似度的4个新数值(介于0和1之间)。如图所示,经过重排序后,先前获得的第一片段现在排在第4位。
  • 5º) 我们添加与上下文最相关的前三个片段。

再次谈到计算成本和时间,如果直接应用交叉编码模型,每次新的查询时,都需要计算该查询与每个文档的相似度。这样做效率很低。

另一方面来看,使用双编码器模型时,文档的向量表示保持一致。

我们接着有一个执行成本高昂的更优方法,另一方面,还有一个不仅运行良好而且每次新查询计算成本较低的方法。这促使我们将这两种方法结合起来以改进RAG。这就是所谓的带有重排序的上下文压缩方法

让我们继续看代码部分。这种方法使用一个检索器,在我们这个例子中会是一个朴素的检索器。

    naive_retriever = vectordb.as_retriever(search_kwargs={ "k" : 10}) # 这行代码创建了一个检索器,设置搜索参数k为10

感谢 LangchainCohere 的集成,我们只需导入用于调用 Cohere cross-encoder 模型的模块:

    from langchain_cohere import CohereRerank  

    os.environ["COHERE_API_KEY"] = "您在Cohere获得的API密钥"  

    压缩器实例 = CohereRerank(top_n=3)

最后,我们创建了我们的基于Langchain的上下文压缩检索器

从langchain.retrievers.contextual_compression导入ContextualCompressionRetriever  # 初始化一个上下文压缩检索器

# 压缩检索器,用于压缩检索结果
compression_retriever = ContextualCompressionRetriever( 
    base_compressor=compressor,  # 压缩器
    base_retriever=naive_retriever  # 简单检索器
)

就这么简单。来看一下“Naive Retriever和Reranking Retriever”的对比:

这是重排序方法如何重新计算查询与文本片段之间相似度的一个例子。这会导致第一次检索器(在我们的情况下是朴素检索器)返回的相关文档被完全重新排序。如图所示,我们收集了前三佳的结果(图片来自作者)。

正如我们看到的,Naive 返回了前 10 个片段/文档。在这之后,经过重新排序,我们得到了前三名的相关文档/片段,可以看到明显的改变。注意,原本在排名中相关性为 第三位编号 16 的文档,在重新排序后,它成了第一名。

总结:以下内容提供更完整的语境,避免显得过于突兀或不完整。

(注:此处可添加进一步的总结内容)

我们已经看到,根据我们要应用RAG的情况的特性,我们可能会选择一种或另一种方法。另外,可能会遇到不知道该选用哪种检索方法的情况。为此,有许多库可以用来评估你的RAGs。

这里有一些工具可以用来达到这个目的。我个人比较推荐的几个选项是RAGASLangSmith一起使用。

使用Ragas和LangSmith评估RAG管道编者注: 本文与Ragas团队合作编写。这是我们常讨论的一个话题…blog.langchain.dev

我非常推荐关注这些很有才华的人的动态、学习他们的知识并观看他们的视频,正是他们激发了我写这篇文章的灵感,所以我想把这些人推荐给你们。

AI 创造空间学习构建、发布和分享大型语言模型应用!和我们一起!www.youtube.com

谢谢大家的阅读!

如果你觉得我的文章有用,你可以订阅每次我发布新文章时,你都会收到一封邮件)(https://medium.com/@damiangilgonzalez/subscribe)**订阅**。

如果你想,在 LinkedIn 上关注我

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP