DLAI-高级-RAG-笔记-全-

DLAI 高级 RAG 笔记(全)

001:开篇与介绍 🎬

在本节课中,我们将要学习构建高级检索增强生成(RAG)应用的核心概念与评估方法。课程将介绍两种高级检索技术以及一套系统的评估框架,旨在帮助你构建并迭代出生产就绪的高质量RAG系统。


检索增强生成(RAG)已成为让大型语言模型(LLM)能够回答关于用户自身数据问题的关键方法。然而,要实际构建并部署高质量的RAG系统,需要有效的检索技术为LLM提供高度相关的上下文来生成答案。同时,一个有效的评估框架也至关重要,它可以帮助你在系统开发的初始阶段以及部署后的维护期间,持续地迭代和改进你的RAG系统。

本课程涵盖了两种新兴的高级检索方法:句子窗口检索自动合并检索。与更简单的方法相比,它们能为LLM提供更好的上下文。课程还将介绍如何使用三个评估指标来评估你的LLM问答系统:上下文相关性事实依据性答案相关性


上一节我们概述了课程目标,本节中我们来看看课程的主讲人。

很高兴向大家介绍Jerry Liu,他是Llamaindex的新任联合创始人兼首席执行官,以及Anupam Datta,他是TruEra的联合创始人兼首席科学家。长期以来,我都很喜欢在社交媒体上关注Jerry和Llamaindex,并从中获取关于不断发展的RAG实践技巧。因此,我期待他能在这个新平台上更系统地传授这套知识体系。Jerry是卡内基梅隆大学的教授,并且已经在可信人工智能领域,特别是如何监控、评估和优化AI系统有效性方面,进行了十多年的研究。

谢谢Andrew。很高兴来到这里,也很高兴与你一起合作,Andrew。


现在,让我们深入了解本课程将涵盖的两种核心检索方法。

句子窗口检索不仅会检索与问题最相关的单个句子,还会检索该句子前后的文本窗口,从而为LLM提供更连贯、信息更丰富的上下文。

自动合并检索则采用了一种不同的思路。它将文档组织成树状结构,其中每个父节点的文本内容被划分到其子节点中。当足够多的子节点被识别为与用户问题相关时,整个父节点的文本就会作为上下文提供给LLM。

我知道这听起来步骤很多,但不用担心,我们稍后会在代码中详细演示。主要的收获是,与使用简单的文本块进行检索相比,这提供了一种动态检索更连贯上下文的方法。


仅仅拥有先进的检索方法还不够,我们还需要科学地评估系统表现。上一节我们介绍了高级检索方法,本节中我们来看看如何评估基于RAG的LLM应用程序。

我们将使用RAG三元组指标来评估RAG系统执行的三个主要步骤。这套方法非常有效。

以下是三个核心评估指标:

  • 上下文相关性:用于衡量检索到的文本块与用户问题的相关程度。这可以帮助你识别和调试系统在检索LLM上下文时可能存在的问题。
  • 事实依据性:用于评估LLM生成的答案在多大程度上依赖于所提供的上下文,而不是其内部知识或产生幻觉。
  • 答案相关性:用于直接衡量生成的答案与原始问题的匹配和有用程度。

但这只是整个QA系统评估的一部分。采用这种系统性的方法,可以让你清晰地分析系统的哪些部分运行良好,哪些部分尚需改进,从而使你能够有针对性地优化最需要工作的环节。如果你熟悉机器学习中的错误分析概念,会发现这有相似之处。我发现采用这种系统方法可以帮助你更有效地构建可靠的QA系统。


本课程的目标是帮助你构建基于生产就绪的LLM应用程序,并掌握对系统进行系统性迭代的重要部分。

在本课程的后半部分,你将通过实践练习,使用这些检索方法和评估指标进行迭代。你还将学习如何使用系统化的实验跟踪来建立性能基线,并在此基础上快速改进。我们还将根据协助合作伙伴构建RAG应用的经验,分享一些调整这两种检索方法的实用建议。

许多人都为创建这门课程付出了努力。我要感谢来自Llamaindex的Logan Markewich和来自DeepLearning.AI的Sharon Zhou、Joshua Romero以及Barbara Lewis。Eddie Shu和Diala Lattouf也对本课程做出了贡献。


下一节课将概述你在课程后续部分将会看到的内容。你将尝试构建使用句子窗口检索或自动合并检索的问答系统,并比较它们在RAG三元组(上下文相关性、事实依据性和答案相关性)上的表现。

这听起来非常棒,让我们开始吧!我认为你真的能通过这些内容掌握RAG的核心要点。


本节课中我们一起学习了:构建高级RAG应用的重要性,两种高级检索方法(句子窗口检索和自动合并检索)的基本原理,以及一套用于系统评估与迭代的RAG三元组指标(上下文相关性、事实依据性、答案相关性)。这些是构建生产级RAG系统的基石。

002:构建高级RAG应用 🚀

概述

在本节课中,我们将全面学习如何使用 LlamaIndex 设置基本和高级的 RAG 管道。我们将加载评估基准,并使用真实数据定义一组指标,以便对高级 RAG 技术进行基准测试。在接下来的几节中,我们将针对基线(基本)管道进行技术研究,并更深入地探讨每种高级检索方法。


基本RAG管道的工作原理

上一节我们介绍了课程目标,本节中我们来看看基本检索增强生成管道的工作原理。

它由三个不同的组件组成:摄取检索合成

在摄取阶段,我们首先为每个文档加载一组文档。我们使用文本分割器将其拆分为一组文本块。然后,对于每个块,我们使用嵌入模型为该块生成嵌入。为每个块生成嵌入后,我们将其卸载到索引中,该索引是存储系统(例如矢量数据库)的视图。

一旦数据存储在索引中,我们就会针对该索引执行检索。首先,针对该索引启动用户查询,然后获取前 K 个与用户查询最相似的块。然后,我们将这些相关块与用户查询结合起来,并在合成阶段将其放入大语言模型的提示窗口中。这使我们能够生成最终响应。

以下代码片段展示了使用 LlamaIndex 创建索引的核心步骤:

# 伪代码示例:创建索引
from llama_index import VectorStoreIndex, ServiceContext
from llama_index.llms import OpenAI
from llama_index.embeddings import OpenAIEmbedding

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-adv-rag/img/e98287d86d42360d152cab5b26f1e122_11.png)

# 定义服务上下文,指定LLM和嵌入模型
service_context = ServiceContext.from_defaults(
    llm=OpenAI(model="gpt-3.5-turbo"),
    embed_model=OpenAIEmbedding(model="text-embedding-ada-002")
)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-adv-rag/img/e98287d86d42360d152cab5b26f1e122_13.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-adv-rag/img/e98287d86d42360d152cab5b26f1e122_15.png)

# 从文档创建向量存储索引
index = VectorStoreIndex.from_documents(documents, service_context=service_context)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-adv-rag/img/e98287d86d42360d152cab5b26f1e122_17.png)

# 获取查询引擎
query_engine = index.as_query_engine()

设置评估基准

现在我们已经设置了基本的 RAG 管道,下一步是针对该管道设置一些评估,以了解它的执行情况。这也将为我们定义高级检索方法(句子窗口检索器以及自动合并检索器)提供基础。

在本节中,我们使用 TruLens 来初始化反馈函数。我们初始化一个辅助函数来返回反馈函数列表,以评估我们的应用程序。在这里,我们创建了一个 RAG 评估三元组,它由查询、响应和上下文之间的成对比较组成。这实际上创建了三个不同的评估模块:答案相关性上下文相关性事实依据性

以下是这三个评估模块的定义:

  • 答案相关性:衡量响应与查询的相关程度。
  • 上下文相关性:衡量检索到的上下文与查询的相关程度。
  • 事实依据性:衡量响应是否由检索到的上下文所支持。

我们需要做的第一件事是创建一组问题来测试我们的应用程序。在这里,我们已经预先编写了前 10 个问题,例如:

  • 建立人工智能职业生涯的关键是什么?
  • 团队合作如何为人工智能的成功做出贡献?

我们鼓励您也添加到此列表中。例如,我们可以指定一个新问题:“什么是适合我的人工智能工作?”,并将其添加到评估问题列表中。

现在我们可以初始化 TruLens 模块以开始我们的评估过程。我们已经初始化了 TruLens 数据库,我们现在可以初始化我们的评估模块。LLM 正在成为大规模评估生成式 AI 应用程序的标准机制,而不是依赖于昂贵的人工评估或设定基准。LLM 使我们能够以一种根据我们的操作领域定制的方式,并且能够根据应用程序不断变化的需求进行动态调整。

在这里,我们预先构建了一个记录器用于此示例。在记录器中,我们包含了用于评估的标准评估三元组(上下文相关性、答案相关性和事实依据性)。我们还将指定一个应用程序 ID,以便我们可以在实验时跟踪应用程序的此版本。我们可以通过简单地更改应用程序 ID 来跟踪新版本。

现在,我们可以使用以下命令再次运行查询引擎并附带上下文。这里发生的事情是,我们将每个查询发送到我们的查询引擎,并且在后台,TruLens 记录器正在评估每个查询的指标。如果您看到一些警告消息,不用担心,其中一些是系统性的。

在仪表板中,您可以看到您的评估指标,例如上下文相关性、答案相关性和事实依据性,以及平均延迟、总成本等。在此处的 UI 中,我们看到答案相关性和事实依据性相当高,但上下文相关性相当低。现在,让我们看看是否可以使用更高级的检索技术来改进这些指标。


高级检索技术:句子窗口检索

我们将讨论的第一个高级技术是句子窗口检索。它通过嵌入和检索单个句子来实现更细粒度的分割,但检索后,句子被替换为在原始检索句子周围有更大的句子窗口。

直觉是这允许大语言模型为检索到的信息提供更多上下文,以便更好地回答查询,同时仍然检索更细粒度的信息。因此,理想情况下,它也可以改善检索和综合性能。

现在让我们看看如何实现。我们将使用 OpenAI 的 GPT-3.5 Turbo。接下来,我们将在给定文档上构建句子窗口索引。只是提醒一下,我们有一个用于构建句子窗口索引的辅助函数。类似于之前,我们将从句子窗口索引中获取查询引擎。

现在我们已经设置了它,我们可以尝试运行一个示例查询。问题是“我如何在人工智能中构建项目组合?”。我们得到回复:“开始在人工智能中的个人项目,首先重要的是确定项目的范围...”。

就像之前一样,让我们尝试获得 TruLens 评估上下文,并尝试对结果进行基准测试。因此,在这里我们导入 TruLens 记录器。sentence_window 是句子窗口索引的预构建 TruLens 记录器。现在,我们将在这些评估问题之上运行句子窗口检索器,然后比较性能。

在这里,我们可以在评估模块的三元组中看到响应。运行一些问题或响应的示例:

  • 问题:团队合作如何为人工智能的成功做出贡献?
    响应:团队合作可以通过允许个人利用他们同事的专业知识和能力以及见解来为人工智能的成功做出贡献。
  • 问题:网络在人工智能中的重要性是什么?
    响应:网络在人工智能中很重要,因为它允许个人与在该领域拥有经验和知识的其他人建立联系。

现在我们已经对基本 RAG 管道和句子窗口检索管道这两种技术进行了评估,让我们获取结果的排行榜看看这里发生了什么。

我们看到,与基线 RAG 相比,句子窗口检索在事实依据性上好了八个百分点,答案相关性或多或少相同,上下文相关性也更好。对于句子窗口查询引擎,延迟或多或少是相同的,并且总成本较低。因为事实依据性和上下文相关性较高,但总成本较低。我们可以凭直觉认为,句子窗口检索器实际上为我们提供了更相关的上下文,并且更高效。

当我们返回 UI 时,我们可以更高效地看到,我们现在对直接查询引擎(基线)以及句子窗口进行了比较,并且我们还可以看到我们刚刚在笔记本中看到的矩阵。


高级检索技术:自动合并检索器

我们将讨论的下一个高级检索技术是自动合并检索器。这里我们构建一个由较大父节点和引用父节点的较小子节点组成的层次结构。例如,我们可能有一个块大小为 512 个令牌的父节点,并且在它下面,是链接到此父节点的、块大小为 128 个令牌的四个子节点。

自动合并检索器通过将检索到的节点合并到更大的父节点中来工作。这意味着在检索期间,如果父节点实际上检索了其大部分子节点,那么我们将用父节点替换这些子节点。因此,这允许我们分层合并检索到的节点。所有子节点的组合文本与父节点的文本相同。

类似于句子窗口检索器,在接下来的几课中,我们将更深入地了解它是如何工作的。在这里,我们将向您展示如何使用它。我们再次使用 GPT-3.5 Turbo 作为 LLM 模型。我们从自动合并检索器获得查询引擎。

让我们尝试运行一个示例查询:“我如何在人工智能中构建项目组合?”。在日志中,您实际上看到了合并节点过程,我们正在将节点合并到父节点中,基本上是用父节点而不是子节点来构建人工智能项目组合的答案。回复是:“重要的是从简单的任务开始,并逐渐进展到一些更复杂的任务。”

然后,我们在我们的评估问题之上运行具有 TruLens 的自动合并检索器。对于每个问题,您实际上看到正在进行的合并过程,例如“将三个节点合并到父节点中”。如果我们向下滚动一点,我们会看到对于其中一些其他问题,我们也在执行合并过程,例如“将三个节点合并到父节点中”、“将一个节点合并到父节点中”。

示例问题响应对:

  • 问题:网络在人工智能中的重要性是什么?
    响应:网络在人工智能中很重要,因为它有助于建立一个强大的专业网络社区。


结果比较与总结

现在我们已经运行了所有三种检索技术(基本的 RAG 管道以及两种高级检索方法),我们可以查看全面的排行榜,看看这三种技术是如何比较的。

除了评估问题之外,我们还得到了自动合并查询引擎的相当不错的结果。我们在事实依据性方面得到了 100%,在答案相关性方面得到了 94%,在上下文相关性方面得到了 43%,这比句子窗口和基线 RAG 管道都要高。我们得到与句子窗口查询引擎大致相同的总成本,这意味着这里的检索效率是合理的。

最后,您也可以在仪表板中查看这一点。

本节课给出了您将全面了解如何设置基本和高级 RAG 管道,以及如何设置评估模块来衡量性能。下一课,我们将深入研究这些评估模块,特别是 RAG 三元组(答案相关性、上下文相关性和事实依据性)。您将更多地了解如何使用这些模块以及每个模块的细节。

003:RAG指标三元组 (RAG Triad) 📊

在本节课中,我们将深入探讨如何评估RAG系统的核心概念。具体来说,我们将介绍RAG Triad,这是一个用于评估RAG执行上下文三个主要步骤的指标三元组。我们将学习如何利用反馈功能框架,对LLM应用程序进行编程式评估,并向您展示如何在给定任何非结构化语料库的情况下,综合生成评估数据集。

环境设置与回顾

上一节我们介绍了RAG的基本概念,本节中我们来看看如何为评估做准备。首先,您需要完成一些环境设置。

以下是设置步骤:

  1. 设置OpenAI API密钥。该密钥将用于完成RAG步骤以及使用TruLens进行评估。
  2. 导入必要的库,包括TruLens和LlamaIndex。

# 设置OpenAI API密钥
import os
os.environ["OPENAI_API_KEY"] = "your-api-key-here"

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-adv-rag/img/a2005aee8b790bf2f86eafe6547573fe_18.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-adv-rag/img/a2005aee8b790bf2f86eafe6547573fe_20.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-adv-rag/img/a2005aee8b790bf2f86eafe6547573fe_22.png)

# 导入TruLens类并设置Tru对象
from trulens_eval import Tru
tru = Tru()
tru.reset_database() # 重置数据库,用于记录应用程序的提示、响应和中间结果

接下来,我们将快速回顾如何使用LlamaIndex构建查询引擎。我们将使用TruLens阅读器加载一个PDF文档(例如,Andrew Ng关于AI职业发展的文章),并将数据加载到文档对象中。

from trulens_eval.tru_llama import TruLlama
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader

# 使用TruLens阅读器加载文档
documents = SimpleDirectoryReader("./data").load_data()

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-adv-rag/img/a2005aee8b790bf2f86eafe6547573fe_29.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-adv-rag/img/a2005aee8b790bf2f86eafe6547573fe_31.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-adv-rag/img/a2005aee8b790bf2f86eafe6547573fe_33.png)

# 将所有内容合并为一个大文档,而不是默认的每页一个文档
from llama_index.core.node_parser import SimpleNodeParser
parser = SimpleNodeParser.from_defaults(chunk_size=512)
nodes = parser.get_nodes_from_documents(documents)

然后,我们利用LlamaIndex的实用程序设置句子窗口索引。

from llama_index.core import Settings
from llama_index.llms.openai import OpenAI
from llama_index.embeddings.huggingface import HuggingFaceEmbedding

# 配置LLM和嵌入模型
Settings.llm = OpenAI(temperature=0.1, model="gpt-3.5-turbo")
Settings.embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-small-en-v1.5")

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-adv-rag/img/a2005aee8b790bf2f86eafe6547573fe_37.png)

# 创建索引和查询引擎
from llama_index.core import VectorStoreIndex
index = VectorStoreIndex(nodes)
query_engine = index.as_query_engine(similarity_top_k=2)

现在,我们已经为基于句子窗口的RAG设置了查询引擎。让我们通过询问一个具体问题(例如“如何创建AI作品集”)来实际操作一下。这将返回一个包含LLM最终响应、检索到的上下文片段以及其他元数据的完整对象。

response = query_engine.query("如何创建AI作品集")
print(response)

深入RAG Triad评估

现在我们已经设置了基于句子窗口的RAG应用程序,我们可以使用RAG Triad来更深入地评估其响应,识别故障模式,并建立改进LLM应用程序的信心。

首先,我们进行一些内务处理。启动一个Streamlit仪表板,用于查看评估结果和运行实验。

tru.run_dashboard() # 从笔记本内部启动仪表板

我们初始化一个LLM(例如OpenAI GPT-3.5 Turbo)作为我们评估的默认提供者,该提供者将用于实现不同的反馈功能。

from trulens_eval import Feedback, Select
from trulens_eval.feedback import Groundedness
from trulens_eval.feedback.provider.openai import OpenAI as OpenAIProvider

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-adv-rag/img/a2005aee8b790bf2f86eafe6547573fe_55.png)

# 初始化反馈提供者
provider = OpenAIProvider(model_engine="gpt-3.5-turbo")

接下来,让我们更深入地了解RAG Triad的每一项评估。我们将在幻灯片和笔记本之间来回切换,以便为您提供完整的上下文。

1. 答案相关性 (Answer Relevance)

答案相关性检查最终响应是否与用户提出的查询相关。

以下是答案相关性反馈函数的结构示例:

  • 提供者 (Provider): 我们使用来自OpenAI的LLM来实现反馈功能。请注意,反馈功能不一定非要使用LLM,也可以使用BERT模型等其他机制。
  • 功能实现: 我们实现一个名为“答案相关性”的反馈功能。它接收用户输入(查询)和应用程序的最终输出(响应)作为输入。
  • 输出: 给定用户问题和RAG的最终答案,此反馈功能将利用LLM提供者在0到1的范围内给出一个分数,并提供支持该分数的思想链推理。

现在让我们在代码中定义答案相关性反馈函数。

# 定义答案相关性反馈函数
f_answer_relevance = Feedback(provider.relevance_with_cot_reasoning,
                              name="Answer Relevance"
                             ).on_input_output()
# `on_input_output()` 表示该函数将应用于用户输入和最终输出

2. 上下文相关性 (Context Relevance)

上下文相关性检查给定查询的检索过程的好坏。它评估从向量数据库中检索到的每段文本与所提出问题的相关程度。

上下文相关性反馈函数的结构与答案相关性类似,但输入不同:

  • 输入: 除了用户输入,该函数还接收一个指向检索上下文的指针,这是执行RAG应用程序的中间结果。
  • 输出: 它为每个检索到的上下文片段返回一个分数,评估该上下文对于查询的相关性,然后对所有片段的分数进行平均以获得最终分数。

在代码中定义上下文相关性反馈函数。

# 定义上下文相关性反馈函数
f_context_relevance = Feedback(provider.context_relevance_with_cot_reasoning,
                               name="Context Relevance"
                              ).on_input().on(Select.Record.app.combine_documents_chain._call.args.inputs.input_documents[:].page_content)
# 此函数应用于用户输入和检索到的上下文

3. 事实基础性 (Groundedness)

事实基础性检查最终答案中的陈述是否得到检索到的上下文的支持,用于检测幻觉。

基础性反馈函数的结构与上下文相关性非常相似:

  • 输入: 访问RAG应用程序中检索到的上下文集合以及RAG的最终输出。
  • 输出: 最终响应中的每个句子都会获得一个基础性分数,这些分数被汇总平均以产生完整的最终基础性分数。

用于设置基础性反馈功能的代码片段。

# 定义基础性反馈函数
grounded = provider.groundedness_measure_with_cot_reasoning
f_groundedness = Feedback(grounded,
                          name="Groundedness"
                         ).on(Select.Record.app.combine_documents_chain._call.args.inputs.input_documents[:].page_content
                             ).on_output()
# 此函数应用于检索到的上下文和最终输出

评估工作流程与数据集生成

现在我们已经设置了所有三个反馈功能(答案相关性、上下文相关性和事实基础性),我们需要一个评估集来运行应用程序和评估。

我们将遵循以下工作流程来评估和迭代改进LLM应用程序:

  1. 从基本LlamaIndex RAG开始,并使用TruLens RAG Triad对其进行评估。
  2. 关注与上下文相关性相关的故障模式。
  3. 使用高级RAG技术(如LlamaIndex句子窗口RAG)迭代改进基本RAG。
  4. 使用TruLens RAG Triad重新评估新的高级RAG,重点关注在上下文相关性等方面是否看到了具体改进。

我们关注上下文相关性的原因,是由于上下文太小经常会导致失败模式。一旦将上下文增加到某个点,您可能会看到上下文相关性的改进。此外,当上下文相关性上升时,我们可能会看到事实基础性方面的改进,因为LLM有足够的相关上下文来生成摘要。

现在,让我们加载一些预设的评估问题。

# 加载评估问题
eval_questions = []
with open('eval_questions.txt', 'r') as file:
    for line in file:
        # 移除可能的换行符并过滤空行
        item = line.strip()
        if item:
            eval_questions.append(item)
print(eval_questions)

运行评估与结果分析

现在我们已经完成了所有设置,可以执行评估了。我们将对评估问题列表中的每个问题运行句子窗口引擎,然后使用TruLens记录器针对RAG Triad运行每条记录。

from trulens_eval.tru_llama import TruLlama
from trulens_eval import FeedbackMode

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-adv-rag/img/a2005aee8b790bf2f86eafe6547573fe_148.png)

# 创建TruLlama记录器对象
tru_recorder = TruLlama(query_engine,
                        app_id='App1',
                        feedbacks=[f_answer_relevance, f_context_relevance, f_groundedness])

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-adv-rag/img/a2005aee8b790bf2f86eafe6547573fe_150.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-adv-rag/img/a2005aee8b790bf2f86eafe6547573fe_152.png)

# 对每个评估问题运行评估
for question in eval_questions:
    with tru_recorder as recording:
        response = query_engine.query(question)
        print(question)
        print(response)

评估完成后,我们可以获取记录和反馈,查看日志。

# 获取记录
records, feedback = tru.get_records_and_feedback(app_ids=["App1"])
print(records[['input', 'output'] + feedback.columns] .head())

我们还可以在排行榜中获得一个聚合视图,该视图聚合所有单独的记录并生成平均值。

# 获取聚合视图(排行榜)
tru.get_leaderboard(app_ids=["App1"])

TruLens还提供了一个本地简化的应用程序仪表板,您可以使用它来检查正在构建的应用程序,查看评估结果,并深入到记录级别视图中。

# 运行仪表板以进行可视化检查
tru.run_dashboard()

在仪表板中,您可以:

  • 查看应用程序性能的聚合视图(平均延迟、总成本、各指标平均分)。
  • 选择单个应用程序,查看每条记录的详细评估视图(用户输入、响应、各分数)。
  • 点击某一行,深入查看答案相关性、上下文相关性和事实基础性的详细评估结果,包括LLM给出分数的思想链原因。

通过分析得分较低的案例(例如基础性分数为0.5),我们可以识别故障模式。例如,最终答案中的某些陈述可能在检索到的上下文中没有支持证据,从而导致低分。这些洞察可以指导我们后续对RAG应用程序进行迭代和改进。

总结

在本节课中,我们一起学习了RAG指标三元组(RAG Triad)的核心概念:答案相关性上下文相关性事实基础性。我们了解了如何将这些评估指标实现为可编程的反馈函数,并利用TruLens框架对一个实际的句子窗口RAG应用程序进行评估。通过设置评估集、运行评估和分析仪表板结果,我们掌握了识别RAG系统故障模式、建立性能基准并指导其迭代改进的基本方法。在下一课中,我们将介绍更高级的RAG技术,并展示如何利用这些评估方法来优化应用程序性能。

004:句子窗口检索 (Sentence Window Retrieval) 📚

在本节课中,我们将深入学习一种名为句子窗口检索的高级RAG技术。我们将探讨其工作原理、如何构建,以及如何使用评估工具来优化其性能。


概述

标准的RAG管道使用相同的文本块进行嵌入和答案合成。这带来一个问题:嵌入检索通常适用于较小的文本块,而大语言模型(LLM)则需要更多的上下文和更大的块来合成一个好的答案。

句子窗口检索技术将这两个过程解耦。它首先嵌入较小的句子块,然后在检索时,用该句子周围的扩展上下文窗口来替换检索到的句子,从而为LLM提供更丰富的背景信息。


句子窗口检索的工作原理

上一节我们介绍了标准RAG的局限性,本节中我们来看看句子窗口检索是如何解决这个问题的。

句子窗口检索的核心思想是将检索与合成解耦。具体步骤如下:

  1. 嵌入阶段:将文档分割成较小的句子块,并将这些句子嵌入到向量数据库中。同时,为每个句子块存储其前后出现的句子作为“上下文窗口”。
  2. 检索阶段:当用户提出问题时,使用相似性搜索在向量数据库中查找最相关的句子
  3. 替换与合成阶段:在将检索结果发送给LLM之前,用存储的完整周围上下文窗口替换掉检索到的单个句子。这样,LLM在生成答案时就能获得更全面的信息。

这种方法允许我们使用更精确的小块进行检索,同时又能为LLM提供生成高质量答案所需的大块上下文。


构建句子窗口检索器

以下是构建一个句子窗口检索器所需的核心组件和步骤。

1. 设置环境与加载文档

首先,需要安装必要的软件包并设置API密钥。本例中使用LlamaIndex和TruLens进行评估。

# 示例:导入必要库
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, ServiceContext
from llama_index.core.node_parser import SentenceWindowNodeParser
# ... 其他导入

加载您的文档。这里我们使用一个示例PDF电子书,它包含41页,被加载为一个文档对象列表。

2. 配置句子窗口节点解析器

SentenceWindowNodeParser 是核心组件,它负责将文档分割成句子,并用上下文窗口来增强每个句子节点。

以下是其工作方式的说明:

  • 输入:一段包含多个句子的文本。
  • 输出:多个节点(Node),每个节点包含一个核心句子及其元数据。元数据中存储了该句子前后指定数量的句子作为上下文窗口。

例如,设置窗口大小为3,意味着每个节点会包含核心句子、其前一句和后一句(如果存在)。

3. 创建向量索引

接下来,使用增强后的句子节点来构建向量索引。

# 创建服务上下文,指定嵌入模型和节点解析器
service_context = ServiceContext.from_defaults(
    llm=llm, # 例如 OpenAI 的 gpt-3.5-turbo
    embed_model=“local:BAAI/bge-small-en-v1.5”, # 使用本地BGE小模型
    node_parser=SentenceWindowNodeParser.from_defaults(window_size=3)
)

# 使用源文档和服务上下文构建向量索引
index = VectorStoreIndex.from_documents(documents, service_context=service_context)
# 可以将索引保存到磁盘以便后续加载
index.storage_context.persist(persist_dir=“./sentence_index”)

4. 组装查询引擎

查询引擎负责处理用户查询。我们需要添加一个后处理器,用于在检索后,将节点文本替换为完整的上下文窗口。

from llama_index.core.postprocessor import MetadataReplacementPostProcessor

# 定义元数据替换后处理器
postproc = MetadataReplacementPostProcessor(target_metadata_key=“window”)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-adv-rag/img/b7090f6ba35657c8baf6c44047933359_59.png)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-adv-rag/img/b7090f6ba35657c8baf6c44047933359_61.png)

# 从索引创建查询引擎,并添加后处理器
query_engine = index.as_query_engine(node_postprocessors=[postproc])

后处理器的作用:检索到的节点最初只包含单个句子。后处理器会读取该节点的元数据(即“window”字段),并用其中存储的完整上下文文本来替换节点的原始文本内容。

为了进一步提升效果,可以添加一个重排序器(Reranker)。它会对初步检索到的多个节点(例如top_k=6)进行重新评分和排序,返回最相关的少数几个(例如top_n=2),从而过滤掉噪音。

from llama_index.core.postprocessor import SentenceTransformerRerank

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-adv-rag/img/b7090f6ba35657c8baf6c44047933359_67.png)

# 定义重排序器
rerank = SentenceTransformerRerank(model=“BAAI/bge-reranker-base”, top_n=2)

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-adv-rag/img/b7090f6ba35657c8baf6c44047933359_69.png)

# 将重排序器也加入查询引擎
query_engine = index.as_query_engine(
    node_postprocessors=[postproc, rerank],
    similarity_top_k=6 # 初始检索数量
)

5. 进行查询

现在,可以使用组装好的查询引擎进行提问。

response = query_engine.query(“在人工智能领域建立职业生涯的关键是什么?”)
print(response)

评估与参数调优 🧪

上一节我们构建了句子窗口检索器,本节中我们来看看如何评估其性能并优化关键参数——窗口大小

我们将使用TruLens进行RAG三元组评估,跟踪答案相关性上下文相关性基础性这三个核心指标。同时,我们还需要关注令牌使用量(成本)

实验设计

我们将逐步增加句子窗口大小(例如1, 3, 5),并观察其对评估指标和成本的影响。预期关系如下:

  • 窗口大小 vs 上下文相关性:开始时,增大窗口能提供更多背景,提升相关性;但过大后,可能引入无关信息,导致相关性持平或下降。
  • 窗口大小 vs 基础性:基础性依赖于上下文相关性。当上下文相关性低时,LLM会依赖自身知识“编造”内容,导致基础性也低。当上下文相关性提升,基础性通常随之提升。但窗口过大时,LLM可能被过多信息淹没,再次转向自身知识,导致基础性下降。
  • 窗口大小 vs 成本:窗口越大,发送给LLM和评估模型的令牌数越多,成本越高。

评估结果分析

以下是进行实验后可能观察到的模式:

  1. 窗口大小 = 1
    • 问题:上下文窗口太小,检索到的片段可能遗漏关键信息,导致上下文相关性得分低。
    • 后果:LLM因缺乏足够的相关上下文,转而利用预训练知识填补空白,这使得生成的答案虽然可能相关,但基础性得分很低(因为无法追溯到提供的上下文)。
  2. 窗口大小 = 3
    • 改进:扩展的上下文为LLM提供了更充分的支撑信息。
    • 结果上下文相关性显著提高。由于LLM现在能更好地依据检索到的上下文生成答案,基础性答案相关性也随之提升。这是成本与效果的一个较好平衡点。
  3. 窗口大小 = 5
    • 新问题:上下文可能变得过于庞大。
    • 结果上下文相关性答案相关性可能保持平稳甚至略有下降。基础性可能因为LLM被海量信息干扰而再次下降。同时,令牌使用量和成本明显增加。

通过TruLens仪表板,我们可以深入查看每条记录的评估细节,分析具体哪些句子因缺乏支持证据而得分低,从而直观理解故障模式。

结论:对于本实验中的特定数据集和评估集,窗口大小为3在各项评估指标和成本之间取得了最佳平衡。在实际应用中,你需要通过类似的评估来确定自己任务的最佳窗口大小。


总结

本节课中我们一起学习了句子窗口检索这一高级RAG技术。

  • 我们首先理解了其核心原理:通过解耦检索(用小句子)与合成(用大上下文)来解决标准RAG的块大小矛盾。
  • 接着,我们逐步构建了一个句子窗口检索器,涉及节点解析、索引创建、以及关键的元数据替换后处理器。
  • 最后,我们使用TruLens评估框架,通过实验分析了窗口大小这一关键参数对RAG三元组指标(答案相关性、上下文相关性、基础性)以及运行成本的影响,并学会了如何根据评估结果进行调优。

通过本课,你掌握了如何实现并优化句子窗口检索,使其能够更精准地检索信息,并为LLM提供更合适的上下文,从而生成更准确、更基于文档的答案。

005:构建高级RAG应用 - 自动合并检索 (Auto-merging Retrieval) 🧩

在本节课中,我们将深入学习一种高级RAG(检索增强生成)技术——自动合并检索。我们将探讨其工作原理、如何设置,以及如何通过评估和实验来优化其性能。


概述

在标准的RAG流程中,我们检索一系列文本片段(块)放入大语言模型(LLM)的上下文窗口。块越小,检索到的信息可能越零散,这会影响LLM综合信息的能力。自动合并检索技术通过定义文本块的层次结构,并在检索时动态地将相关的子块合并为更大的父块,从而提供更连贯的上下文。

上一节我们介绍了句子窗口检索,本节中我们来看看另一种解决信息碎片化的方法。


自动合并检索的原理

标准RAG管道的一个问题是:您检索一堆零散的上下文数据块放入LLM的上下文窗口。并且,您的块越小,信息碎片化就越严重。

例如,您可能会在大致相同的文档部分中返回两个或更多检索到的上下文块,但这些块的排序无法保证,可能会妨碍LLM在其上下文窗口内综合此检索上下文的能力。

因此,自动合并检索首先执行以下操作:定义链接到较大父块的较小块的层次结构。其中,每个父块可以有一些子块。

在检索期间,如果链接到某个父块的较小块的集合超过某个百分比阈值,那么我们将这些较小的块合并到较大的父块中。因此,我们最终检索的是较大的父块,以帮助确保更连贯的上下文。


如何设置自动合并检索器

现在让我们看看如何设置。本部分将介绍使用LlamaIndex构建自动合并检索器所需的各种组件。

与上一节类似,我们将加载OpenAI API密钥,并使用utils文件中的辅助函数加载它。我们还将使用《如何在AI中建立职业生涯》这份PDF文档作为示例。鼓励您尝试自己的PDF文件。

我们加载文档对象,并将它们合并到一个大文档中,这使得它更适合使用分层方法进行文本分割。

第一步:定义分层节点解析器

为了使用自动合并检索器,我们需要以分层方式解析节点。这意味着节点以递减的大小进行解析,并包含与其父节点的关系。

以下是定义节点解析器的一个示例。我们创建了一个具有小块大小的解析器来演示。

# 示例:定义层次结构节点解析器
from llama_index.node_parser import HierarchicalNodeParser

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-adv-rag/img/c023682d06c152d46f90e15c85ce2f07_19.png)

# 定义递减的块大小,例如 2048, 512, 128
chunk_sizes = [2048, 512, 128]
node_parser = HierarchicalNodeParser(chunk_sizes=chunk_sizes)

我们现在从文档中获取节点集。这返回所有叶节点、中间节点以及父节点。

如果我们只想检索叶节点,可以调用一个名为get_leaf_nodes的函数。叶节点是拥有最小块大小(例如128个标记)的块。

我们还可以探索节点之间的关系。例如,打印一个叶节点的父节点,可以观察到它是一个包含该叶节点的更大块。具体来说,父节点可能包含512个令牌,而四个叶节点各包含128个令牌。

第二步:构建索引与服务上下文

现在我们已经了解了节点层次结构的样子,我们可以开始构建索引。

我们将使用的LLM是OpenAI GPT-3.5 Turbo。我们还将定义一个服务上下文对象,其中包含LLM、嵌入模型和之前定义的解析器层次结构。与之前一样,我们将使用BAAI/bge-small-en模型作为嵌入模型。

下一步是构建索引。索引的工作方式是:我们实际上在特定的叶节点上构造一个向量索引。所有其他中间节点和父节点都存储在文档存储中,并在检索期间动态检索。但在初始的top-k嵌入查找期间,我们实际获取的是特定的叶节点。

以下是构建索引的示例代码:

from llama_index import VectorStoreIndex, ServiceContext, StorageContext
from llama_index.vector_stores import SimpleVectorStore

![](https://github.com/OpenDocCN/dsai-notes-pt1-zh/raw/master/docs/dlai-adv-rag/img/c023682d06c152d46f90e15c85ce2f07_31.png)

# 定义存储上下文
storage_context = StorageContext.from_defaults()
# 将所有节点添加到内存文档存储中
storage_context.docstore.add_documents(nodes)

# 但创建向量存储索引时,仅传入叶节点
vector_index = VectorStoreIndex(
    leaf_nodes, # 假设 leaf_nodes 是之前获取的叶节点列表
    storage_context=storage_context,
    service_context=service_context
)

这意味着具体的叶节点是使用嵌入模型嵌入并建立索引的,但向量索引也了解包含所有节点的底层文档存储。

如果您已经构建了此索引并希望从存储加载它,可以使用相应的加载代码。

第三步:设置检索器与查询引擎

我们已经定义了自动合并索引,最后一步是设置检索器并运行查询引擎。

自动合并检索器控制合并逻辑:如果为给定父节点检索到大多数子节点,则将它们“交换”为父节点。为了使此合并正常工作,我们为叶节点设置一个较大的top-k值。记住,叶节点的块大小较小(例如128个标记)。为了减少令牌使用,我们在合并发生后应用重排序器。

我们导入一个名为AutoMergingRetriever的类,然后定义一个句子Transformer重排模块。我们将自动合并检索器和重排模块合并到我们的检索器查询引擎中,它负责处理检索和内容合成。

以下是设置查询引擎的示例:

from llama_index.retrievers import AutoMergingRetriever
from llama_index.postprocessor import SentenceTransformerRerank

# 定义自动合并检索器
retriever = AutoMergingRetriever(
    vector_index.as_retriever(similarity_top_k=12),
    storage_context=storage_context,
    simple_ratio_thresh=0.5 # 合并阈值,例如50%的子节点被检索到则合并
)

# 定义重排器
reranker = SentenceTransformerRerank(top_n=6)

# 组合成查询引擎
query_engine = RetrieverQueryEngine(
    retriever=retriever,
    node_postprocessors=[reranker]
)

现在我们已经将整个流程端到端地设置好了,让我们实际测试一下。以一个关于“人工智能中网络的重要性”的示例问题,我们得到了一个连贯的回复。


整合与评估

下一步是将所有步骤整合在一起。我们创建两个高级函数:一个用于构建自动合并索引,另一个用于获取自动合并查询引擎。

第一个函数build_auto_merging_index使用层次结构节点解析器来解析子节点到父节点的层次结构,定义服务上下文,并从叶节点创建向量存储索引,同时链接到所有节点的文档存储。

第二个函数get_auto_merging_query_engine利用我们的自动合并检索器(它能够动态地将叶节点合并到父节点中),并且还使用我们的重排模块,然后将其与整体的检索器查询引擎结合起来。

使用RAG Triad进行评估

现在您已经设置了自动合并检索器,让我们看看如何使用RAG Triad对其进行评估,并通过实验跟踪将其性能与基础RAG进行比较。

我们设置一个新的两层自动合并索引。值得注意的是,最低层叶节点的块大小为512,上一层的块大小为2048。这意味着每个父节点将有四个叶节点。

设置此索引的部分与之前展示的完全相同。尝试两层结构的一个原因是它更简单,创建索引以及检索步骤所需的工作量更少。如果它的性能相当好,那么理想情况下我们希望使用更简单的结构。

对于此设置的查询引擎,我将similarity_top_k保持为与之前相同的值(12),并且重排步骤的top_n也保持为6。这使我们能在不同应用程序设置之间进行更直接的比较。

现在让我们使用此自动合并引擎设置一个评估记录器。我们将从生成的问题中加载一些问题以进行评估。我们可以为每个评估问题定义运行,设置记录器对象来记录提示、响应和利用查询引擎的评估结果。

评估完成后,让我们看一下排行榜。我们可以看到,在这个两级层次结构中,上下文相关性似乎很低,但其他两个指标(答案相关性和忠实性)更好。

我们可以运行真正的评估仪表板并查看记录级别的详细信息。例如,选择一个上下文相关性较低的记录,问题是“讨论资源预算对人工智能项目成功执行的重要性”。响应和检索到的上下文可能相关性不高。

我们也可以探索一些在其他记录中得分很好的例子。这有助于我们了解应用程序在各种问题上的表现如何,以及它的优势和故障模式在哪里,从而建立一些关于什么是有效的直觉。

比较不同层次结构

现在让我们将之前的应用程序与一个三层自动合并设置进行比较。在层次结构中,我们从叶节点级别128个令牌开始,向上一层有512个令牌,在最高层有2048个令牌。因此在每一层中,父级有四个子级。

让我们为这个新设置也运行评估。设置完成后,我们可以快速查看排行榜。您可以看到,相对于两层结构(App0),三层结构(App1)在处理相同数量记录时使用的令牌数量约为一半,总成本也约为一半。这是因为三层结构的最小叶节点标记大小是128,而不是512,因此导致成本降低。

同时,三层结构的上下文相关性增加了约20%。发生这种情况的部分原因是,通过这个新的应用程序设置,合并可能会发生得更好。

我们可以更详细地查看应用程序一的各个记录。选择与之前查看的相同记录(关于预算的重要性),现在你可以看到上下文相关性做得更好,忠实性也相当高。选择一个示例响应,你会发现它正在非常具体地谈论资源预算,所以在这方面有改进。


总结

在本节课中,我们一起学习了一种使用自动合并检索进行评估和迭代的高级RAG技术。

我们向您展示了如何使用不同的层次结构(级别数、子节点数和块大小)进行迭代。对于这些不同的应用程序版本,您可以使用RAG Triad来评估它们,并跟踪实验来为您的用例选择最佳结构。

需要注意的一点是,您不仅获取与RAG Triad相关的指标作为评估的一部分,但深入到记录级别可以帮助您获得关于最适合某些文档类型的超参数的直觉。例如,取决于文档的性质(如雇佣合同与发票),您可能会发现不同的块大小和层次结构最有效。

最后,自动合并检索是对句子窗口检索的补充。考虑这一点的一种方法是:假设您有一个父节点的四个子节点。在自动合并范式下,如果子项一和子项四与查询非常相关,它们可能会被合并。相比之下,句子窗口化可能不会导致这种合并,因为它们可能不在文本的连续部分。

我们观察到,通过先进的RAG技术,例如句子窗口和自动合并检索,加上评估、实验跟踪和迭代的能力,您可以显著改进您的RAG应用。

此外,虽然本课程重点关注这两种技术以及相关的RAG Triad进行评估,但您还可以使用许多其他评估工具,以确保您的LLM申请是诚实、无害且有用的。我们鼓励您去尝试这些工具,探索笔记本,并将您的学习提升到一个新的水平。

006:构建高级RAG应用 - 终篇与结论 🎯

在本节课中,我们将对“构建高级RAG应用”系列课程进行总结,回顾核心学习成果,并为你的后续学习与实践提供明确的指导方向。

课程概述

恭喜你完成了本课程。希望你已掌握关于如何构建、评估和迭代RAG应用程序的技能,以使其更适合生产环境。

无论你来自数据科学、机器学习背景,还是传统的软件开发背景,学习其中一些核心开发原理,都能使你成为一名能够构建强大LLM软件系统的人工智能工程师。

核心任务与未来方向

随着该领域的发展,减少LLM的“幻觉”将成为每位开发人员的首要任务。我们很高兴看到基础模型变得更好、评估成本变得更低,并且评估工具的设置和运行对每个人来说都变得更加容易。

深化RAG应用理解

作为下一步,建议你更深入地了解你的数据管道、检索策略和LLM提示,以帮助提高RAG应用的性能。

我们课程中展示的两种技术只是冰山一角。你应该研究从块大小检索技术(例如混合搜索),再到基于LLM的推理(例如思维链,Chain of Thought)的所有内容。

评估的起点

RAG应用评估三元组(RAG Triad)是一个很好的起点,可以开始评估基于RAG的LLM应用程序。

深入探索评估领域

作为下一步,鼓励你更深入地挖掘评估LLM及其支持应用程序的领域。这包括评估以下主题:

  • 模型置信度
  • 校准
  • 不确定性
  • 可解释性
  • 隐私
  • 公平性
  • 毒性(在良性和对抗环境中)

课程总结

本节课中,我们一起回顾了整个“构建高级RAG应用”课程的核心目标。我们强调了掌握构建、评估和迭代技能的重要性,使你能够开发出适用于生产环境的强大AI系统。课程指明了未来的关键方向:持续优化以减轻模型幻觉,并深入探索数据管道、检索策略、提示工程以及全面的模型评估领域,从而在LLM应用开发道路上不断精进。

posted @ 2026-03-26 08:14  绝不原创的飞龙  阅读(6)  评论(0)    收藏  举报