DLAI-高级-RAG-构建和评估笔记-全-
DLAI 高级 RAG 构建和评估笔记(全)
001:课程介绍 🚀

在本节课中,我们将要学习构建高质量检索增强生成(RAG)系统的核心知识。课程将重点介绍两种高级检索方法以及一套用于系统评估和迭代的框架。
检索增强生成(RAG)已成为让大型语言模型基于用户自有数据回答问题的关键方法。然而,要实际构建并投入生产一个高质量的RAG系统,拥有高效的检索技术来为生成器提供高度相关的上下文,以及一个有效的评估框架来帮助你在系统初始开发和部署后维护期间高效迭代和改进,都至关重要。
本课程涵盖了两种高级检索方法:句子窗口检索和自动合并检索。它们比简单方法能为语言模型提供显著更优的上下文。课程还将介绍如何使用三个评估指标来评估你的语言模型问答系统:上下文相关性、事实依据性和答案相关性。
高级检索方法介绍 🔍
上一节我们介绍了课程的整体目标,本节中我们来看看两种核心的高级检索技术。
句子窗口检索
句子窗口检索通过不仅检索最相关的句子,还检索该句子在文档中出现位置前后的句子窗口,从而为语言模型提供更好的上下文。
自动合并检索
自动合并检索将文档组织成树状结构,其中父节点的文本被划分到其子节点中。当子节点被识别为与用户问题相关时,父节点的全部文本将作为上下文提供给语言模型。
我知道这听起来步骤很多,但别担心,我们稍后会在代码中详细讲解。主要要点是,这提供了一种比简单方法更动态地检索更连贯文本块的方式。



RAG系统评估框架 📊

为了评估基于RAG的LLM应用,RAG三元组——针对RAG执行三个主要步骤的三元组指标——非常有效。


以下是RAG三元组的三个核心评估指标:

- 上下文相关性:衡量检索到的文本块与用户问题的相关程度。这有助于你识别和调试系统在为问答系统中的LLM检索上下文时可能存在的问题。
- 事实依据性:但上下文相关性只是整个问答系统评估的一部分。
- 答案相关性:我们还将涵盖事实依据性和答案相关性等额外评估指标,让你能够系统地分析系统的哪些部分运行良好或尚不完善。
通过这种方式,你可以有针对性地去改进最需要工作的部分。如果你熟悉机器学习中的误差分析概念,这有相似之处。我发现采取这种系统性的方法能帮助你更高效地构建可靠的问答系统。


课程实践与目标 🎯
本课程的目标是帮助你构建可用于生产环境的RAG问答应用。实现生产就绪的一个重要部分是以系统化的方式对系统进行迭代。
在课程的后半部分,你将获得实践机会,使用这些检索方法和评估方法进行迭代。你还将看到如何使用系统化的实验跟踪来建立基线并快速改进。我们还将根据协助合作伙伴构建RAG应用的经验,分享一些调整这两种检索方法的建议。
总结

本节课中我们一起学习了构建高级RAG系统的概览。我们了解到,要构建高质量的RAG应用,需要掌握如句子窗口检索和自动合并检索这样的高级检索技术,以提供更优质的上下文。同时,采用包含上下文相关性、事实依据性和答案相关性的RAG三元组评估框架,能系统性地诊断和提升系统性能。接下来的课程将提供实践机会,帮助你迭代并优化系统。
002:高级RAG流程概述 🚀
在本节课中,我们将学习如何使用LlamaIndex构建一个基础的和一个高级的检索增强生成(RAG)流程。我们还将加载一个评估基准,并使用TruLens定义一组评估指标,以便将高级RAG技术与基础流程进行性能对比。




在接下来的几节课中,我们将更深入地探讨每个部分。首先,让我们了解一下基础的RAG流程是如何工作的。
基础RAG流程 🔄
一个基础的RAG流程包含三个不同的组件:数据摄取、检索和合成。
在数据摄取阶段,我们首先加载一组文档。对于每个文档,我们使用文本分割器将其分割成多个文本块。然后,对于每个文本块,我们使用嵌入模型为其生成一个向量表示。最后,我们将每个带有向量表示的文本块存储到一个索引中,这个索引通常是向量数据库(如Pinecone)的一个视图。
一旦数据存储在索引中,我们就可以针对该索引执行检索。首先,我们向索引发送一个用户查询,并获取与查询最相似的K个文本块。之后,在合成阶段,我们将这些相关的文本块与用户查询结合,放入大语言模型的提示窗口中,从而生成最终的回答。
设置基础RAG流程 ⚙️
本教程将引导你使用LlamaIndex快速设置基础和高级RAG流程。我们还将使用TruLens来建立评估基准,以便衡量相对于基线的改进。
对于这个快速入门,你需要一个OpenAI API密钥。请注意,本节课我们将使用一组辅助函数来快速启动和运行,未来课程中我们会深入探讨其中的一些部分。
接下来,我们将使用LlamaIndex创建一个简单的大语言模型应用,它内部使用了OpenAI的LLM。
在数据源方面,我们将使用Andrew Ng撰写的《如何在AI领域建立职业生涯》PDF文件。你也可以上传自己的PDF文件,我们鼓励你这样做。
首先,我们对文档内容和长度进行一些基本的检查。
# 示例:加载并检查文档
documents = load_documents("path/to/your/document.pdf")
print(f"文档数量: {len(documents)}")
print(f"第一个文档片段: {documents[0].text[:200]}")
我们看到有一个文档列表,包含41个元素,列表中的每一项都是一个文档对象。我们还会显示给定文档的文本片段。
接下来,我们将这些文档合并成一个单一的文档,因为在使用更高级的检索方法(如句子窗口检索和自动合并检索)时,这有助于提高整体文本规划的准确性。
下一步是索引这些文档,我们可以使用LlamaIndex中的VectorStoreIndex来完成。
from llama_index import VectorStoreIndex, ServiceContext
from llama_index.llms import OpenAI
from llama_index.embeddings import HuggingFaceEmbedding
# 定义LLM和嵌入模型
llm = OpenAI(model="gpt-3.5-turbo")
embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-small-en")
# 创建服务上下文
service_context = ServiceContext.from_defaults(llm=llm, embed_model=embed_model)
# 从文档创建索引
index = VectorStoreIndex.from_documents(documents, service_context=service_context)
以上几步展示了处理过程:我们加载了文档,然后通过一行代码VectorStoreIndex.from_documents,使用你指定的嵌入模型,在后台完成了分块、向量化和索引。
接下来,我们从这个索引获取一个查询引擎,它允许我们发送用户查询,并对这些数据执行检索和合成。
# 创建查询引擎
query_engine = index.as_query_engine()
让我们尝试第一个请求。查询是:“在寻找项目来积累经验时,应该采取哪些步骤?”
response = query_engine.query("what are steps to take when finding projects to build your experience?")
print(response)
回答是:“从小的项目开始,逐渐增加项目的范围和复杂性。”很好,它运行正常。
现在你已经建立了基础的RAG流程,下一步是针对这个流程设置一些评估,以了解它的表现如何。这也将为我们定义高级检索方法(句子窗口检索器和自动合并检索器)提供基础。
使用TruLens进行评估 📊
在本节中,我们使用TruLens来初始化反馈函数。我们初始化了一个辅助函数get_feedbacks,以返回一个用于评估我们应用的反馈函数列表。
这里我们创建了一个RAG评估三元组,它包含查询、回答和上下文之间的成对比较。这实际上创建了三个不同的评估模型:回答相关性、上下文相关性和事实依据性。
- 回答相关性:回答是否与查询相关?
- 上下文相关性:检索到的上下文是否与查询相关?
- 事实依据性:回答是否得到上下文的支持?
我们将在接下来的几个notebook中详细介绍如何自己设置。
我们需要做的第一件事是创建一组用于测试我们应用的问题。这里我们预先编写了前10个问题,并鼓励你添加更多。
eval_questions = [
"What are the keys to building a career in AI?",
"How can teamwork contribute to success in AI?",
# ... 其他问题
]
现在我们可以初始化TruLens模块来开始我们的评估过程。
from trulens_eval import Tru, TruChain
tru = Tru()
tru.run_dashboard() # 可选:启动仪表板
# 初始化一个预构建的TruLens记录器,包含RAG三元组评估
tru_recorder = TruChain(
query_engine,
app_id='Basic RAG Pipeline',
feedbacks=[answer_relevance, context_relevance, groundedness]
)
现在,我们可以在TruLens的上下文中再次运行查询引擎。
# 在TruLens监控下运行评估问题
for question in eval_questions:
with tru_recorder as recording:
response = query_engine.query(question)
这里发生的是,我们将每个查询发送到查询引擎,同时在后台,TruLens记录器正根据这三个指标评估每个查询。
你可以在仪表板中看到查询列表及其相关的回答。可以看到输入、输出、记录ID、标签等。你还可以在此仪表板中看到每一行的回答相关性、上下文相关性和事实依据性评分,以及平均延迟、总成本等指标。
这里我们看到回答相关性和事实依据性得分相当高,但上下文相关性相当低。现在,让我们看看是否可以通过更高级的检索技术(如句子窗口检索和自动合并检索)来提高这些指标。
高级检索技术一:句子窗口检索 🪟
我们要讨论的第一个高级技术是句子窗口检索。它的工作原理是嵌入和检索单个句子,即更细粒度的文本块。但在检索之后,检索到的句子会被替换为围绕原始检索句子的一个更大的句子窗口。
其直观想法是,这能让大语言模型对检索到的信息有更多的上下文,从而更好地回答查询,同时仍然检索更细粒度的信息片段,从而理想地提高检索和合成性能。
现在让我们看看如何设置它。首先,我们将使用GPT-3.5 Turbo。接下来,我们将在合并后的文档上构建句子窗口索引。
# 使用辅助函数构建句子窗口索引
sentence_index = create_sentence_window_index(
document,
llm=llm,
embed_model=embed_model
)
sentence_window_engine = sentence_index.as_query_engine(similarity_top_k=3)
与之前类似,我们从句子窗口索引获取一个查询引擎。现在我们已经设置好了,可以尝试运行一个示例查询。问题是:“我如何开始一个AI个人项目?”
我们得到的回答是:“要开始一个AI个人项目,首先重要的是确定项目的范围。”很好。
与之前类似,让我们尝试获取TruLens评估上下文并尝试对结果进行基准测试。
# 为句子窗口检索器使用预构建的TruLens记录器
tru_recorder_sentence = TruChain(
sentence_window_engine,
app_id='Sentence Window Retriever',
feedbacks=[answer_relevance, context_relevance, groundedness]
)
# 在评估问题上运行评估
for question in eval_questions:
with tru_recorder_sentence as recording:
response = sentence_window_engine.query(question)
现在我们已经对两种技术(基础RAG流程和句子窗口检索流程)运行了评估,让我们看一下结果的排行榜,看看情况如何。
我们看到,通常事实依据性比基线RAG高出约8个百分点。回答相关性大致相同。上下文相关性对于句子窗口查询引擎也更好。延迟大致相同,总成本更低。
由于事实依据性和上下文相关性更高,但总成本更低,我们可以推断句子窗口检索器实际上为我们提供了更相关的上下文,并且效率更高。
当我们回到UI时,可以看到直接查询引擎(基线)和句子窗口检索器之间的比较,并且可以看到我们在notebook中看到的指标也显示在UI中。
高级检索技术二:自动合并检索 🔗
我们要讨论的下一个高级检索技术是自动合并检索器。这里我们构建一个层次结构,其中较大的父节点包含引用父节点的较小的子节点。
例如,我们可能有一个块大小为512个标记的父节点。其下,有四个块大小为128个标记的子节点链接到这个父节点。
自动合并检索器的工作原理是将检索到的节点合并到更大的父节点中。这意味着在检索过程中,如果一个父节点的大部分子节点被检索到,那么我们将用父节点替换这些子节点。这允许我们分层合并检索到的节点,所有子节点的组合文本与父节点相同。
与句子窗口检索器类似,在接下来的课程中,我们将更深入地探讨它的工作原理。这里将展示如何使用我们的辅助函数进行设置。
# 使用辅助函数构建自动合并索引
automerge_index = create_automerge_index(
document,
llm=llm,
embed_model=embed_model
)
automerge_engine = automerge_index.as_query_engine(similarity_top_k=6)
我们得到了自动合并检索器的查询引擎。让我们尝试运行一个示例查询:“我如何在日志中构建AI项目组合?”在这里的日志中,你实际上可以看到合并过程:将一个或多个节点合并到父节点中,从而检索父节点而不是子节点。
回答是:“要构建AI项目组合,重要的是从简单的项目开始,逐渐进行更复杂的项目。”很好,我们看到它运行正常。
现在让我们用TruLens对结果进行基准测试。
# 为自动合并检索器使用预构建的TruLens记录器
tru_recorder_auto = TruChain(
automerge_engine,
app_id='Auto Merging Retriever',
feedbacks=[answer_relevance, context_relevance, groundedness]
)
# 在评估问题上运行评估
for question in eval_questions:
with tru_recorder_auto as recording:
response = automerge_engine.query(question)
对于每个问题,你实际上可以看到合并过程正在进行,例如将三个节点合并到父节点中。
结果对比与总结 🏆
现在我们已经运行了所有三种检索技术(基础RAG流程以及两种高级检索方法),我们可以查看一个综合排行榜,看看这三种技术如何相互比较。
我们在自动合并查询引擎上得到了非常好的结果。在评估问题上,事实依据性达到100%,回答相关性达到94%,上下文相关性达到43%,这高于句子窗口检索器和基础RAG流程。并且我们得到的总成本与句子窗口查询引擎大致相当,这意味着此处的检索效率更高,延迟相同。最后,你也可以在仪表板中查看这些结果。
本节课全面概述了如何设置基础和高级RAG流程,以及如何设置评估模块来衡量性能。在下一课中,我们将深入探讨这些评估模块,特别是RAG三元组(事实依据性、回答相关性和上下文相关性),你将了解更多关于如何使用这些模块以及每个模块需要什么。


003:Lesson 2 RAG评估指标三元组 🎯

在本节课中,我们将深入学习如何评估RAG系统。我们将介绍RAG评估指标三元组,这是一个用于评估RAG执行三个主要步骤的可扩展框架,包括上下文相关性、答案相关性和事实依据性。我们还将展示如何根据任何非结构化语料库,合成生成一个评估数据集。

设置环境与回顾
上一节我们介绍了RAG的基本概念,本节中我们来看看如何进行程序化评估。首先,我们需要完成一些基础设置。
以下是设置OpenAI API密钥的代码片段,该密钥将用于RAG的生成步骤以及通过TruLens进行评估。
import os
os.environ["OPENAI_API_KEY"] = "your-api-key-here"
接下来,我们将快速回顾使用LlamaIndex构建查询引擎的过程。第一步是设置一个TruLens的Tru对象。
from trulens_eval import Tru
tru = Tru()
tru.reset_database()
这个对象将用于记录LlamaIndex应用的提示、响应、中间结果以及我们通过TruLens设置的各种评估结果。
现在,让我们设置LlamaIndex阅读器来加载文档数据。
from llama_index import SimpleDirectoryReader
documents = SimpleDirectoryReader(input_dir="./data").load_data()
然后,我们将所有内容合并为一个大文档,并设置句子索引。
from llama_index import ServiceContext, GPTVectorStoreIndex
from llama_index.llms import OpenAI
service_context = ServiceContext.from_defaults(
llm=OpenAI(model="gpt-3.5-turbo", temperature=0.1),
embed_model="local:BAAI/bge-small-en-v1.5"
)
index = GPTVectorStoreIndex.from_documents(documents, service_context=service_context)

接着,我们设置句子窗口引擎,这将作为我们高级RAG应用的查询引擎。
from llama_index.indices.postprocessor import SentenceWindowNodePostprocessor
query_engine = index.as_query_engine(
node_postprocessors=[
SentenceWindowNodePostprocessor.from_defaults(window_size=3)
]
)

现在,让我们通过提出一个问题来查看它的实际效果。
response = query_engine.query("如何创建你的AI作品集?")
print(response)
表面上看,这个回答相当不错。接下来,我们将学习如何使用RAG三元组等反馈函数来深入评估此类响应,识别失败模式,并建立改进LLM应用的信心。
深入RAG三元组评估
现在我们已经设置了基于句子窗口的RAG应用,让我们看看如何使用RAG三元组来评估它。首先进行一些准备工作。
以下代码片段允许我们从笔记本内部启动一个Streamlit仪表板,稍后我们将使用该仪表板查看评估结果并进行实验。
tru.run_dashboard()
接下来,我们初始化OpenAI GPT-3.5 Turbo作为我们评估的默认提供者。
from trulens_eval import OpenAI as TruOpenAI
provider = TruOpenAI(model_engine="gpt-3.5-turbo")

现在,让我们深入了解RAG三元组的每一项评估。
答案相关性评估
首先,我们将讨论答案相关性。答案相关性检查最终响应是否与用户提出的查询相关。

为了给你一个具体的例子,用户提出的问题是:“利他主义如何有益于职业发展?” RAG应用给出了一个回答。答案相关性评估产生两部分输出:一个0到1的分数(例如0.9),以及支持该分数的理由或思维链推理。
我想借此机会介绍反馈函数的抽象概念。答案相关性就是反馈函数的一个具体例子。更一般地说,反馈函数在审查LLM应用的输入、输出和中间结果后,提供一个0到1的分数。
让我们以答案相关性反馈函数为例,看看反馈函数的结构。
第一个组件是提供者,这里我们使用OpenAI的LLM来实现这些反馈函数。请注意,反馈函数不一定必须使用LLM实现,我们也可以使用BERT模型等其他机制,这将在课程后面详细讨论。
第二个组件是利用该提供者实现一个反馈函数,在本例中是相关性反馈函数。我们给它一个易于理解的名称,稍后会在评估仪表板中显示。对于这个特定的反馈函数,它接收用户输入(查询)和应用的最终输出(响应)作为输入。
现在,让我们切换回笔记本,更详细地查看代码。
以下是如何在代码中定义问答相关性反馈函数。

from trulens_eval import Feedback

# 定义答案相关性反馈函数
f_answer_relevance = Feedback(
provider.relevance_with_cot_reasoning,
name="Answer Relevance"
).on_input_output()
我们给这个反馈函数一个易于理解的名称“Answer Relevance”。我们还让反馈函数能够访问输入(提示)和输出(RAG应用的最终响应)。
上下文相关性评估
接下来,我们将深入研究的反馈函数是上下文相关性。上下文相关性检查检索过程的质量,即给定一个查询,我们查看从向量数据库检索到的每一段上下文,并评估该段上下文与所提问题的相关程度。
让我们看一个简单的例子。用户的问题是:“利他主义如何有益于职业发展?” 检索到两段上下文。经过上下文相关性评估后,每段检索到的上下文都获得一个0到1的分数。然后,平均上下文相关性分数是这些检索上下文片段相关分数的平均值。
现在,让我们看看上下文相关性反馈函数的结构。这个结构的各个部分与几分钟前我们回顾的答案相关性结构相似。不同之处在于这个特定反馈函数的输入:除了用户输入或提示外,我们还与这个反馈函数共享一个指向检索上下文的指针,即RAG应用执行中的中间结果。
现在我们有了上下文选择设置,可以定义代码中的上下文相关性反馈函数了。
# 定义上下文相关性反馈函数
f_context_relevance = Feedback(
provider.qs_relevance_with_cot_reasoning,
name="Context Relevance"
).on_input().on(
TruLlamaindex.select_source_nodes().node.text
)
我们仍然使用OpenAI作为提供者,GPT-3.5作为评估LLM。我们调用问题陈述或上下文相关性反馈函数。它获取输入提示和检索到的上下文片段集合,对每个检索到的上下文片段单独运行评估函数,为每个片段获取一个分数,然后进行平均以报告最终的聚合分数。
一个额外的变体是,除了报告每段检索上下文的上下文相关性分数外,你还可以通过思维链推理来增强它,这样评估LLM不仅提供分数,还提供其评估分数的理由或解释。
事实依据性评估
现在,让我向你展示设置事实依据性反馈函数的代码片段。我们以与之前反馈函数类似的方式开始,利用LLM提供者进行评估。
事实依据性衡量标准附带思维链推理,用于证明分数的合理性。我们给它起了一个易于理解的名称“Groundedness”。它可以访问RAG应用中的检索上下文集合以及RAG的最终输出或响应。然后,最终响应中的每个句子都会获得一个事实依据性分数,这些分数被聚合、平均,以产生整个响应的最终事实依据性分数。
这里的上下文选择与设置上下文相关性反馈函数时使用的上下文选择相同。
# 定义事实依据性反馈函数
f_groundedness = Feedback(
provider.groundedness_measure_with_cot_reasoning,
name="Groundedness"
).on(
TruLlamaindex.select_source_nodes().node.text
).on_output()
执行评估与迭代改进
至此,我们已经准备好开始执行RAG应用的评估。我们已经设置了所有三个反馈函数,现在只需要一个评估集,我们可以在其上运行应用和评估,看看它们表现如何,以及是否有机会进一步迭代和改进。
现在让我们看看评估和迭代改进LLM应用的工作流程。
我们将从上一课介绍的基本LlamaIndex RAG开始,并且我们已经用TruLens RAG三元组对其进行了评估。我们将稍微关注与上下文大小相关的失败模式。然后,我们将使用一种高级RAG技术(LlamaIndex句子窗口RAG)迭代改进那个基本的RAG。接下来,我们将用TruLens RAG三元组重新评估这个新的高级RAG。
我们将重点关注这些问题:我们是否看到了改进,特别是在上下文相关性方面?其他指标呢?我们关注上下文相关性的原因通常是失败模式的出现是因为上下文太小。一旦你将上下文增加到一定程度,你可能会看到上下文相关性的改进。此外,当上下文相关性提高时,我们通常也会发现事实依据性的改进,因为完成步骤中的LLM有足够的相关上下文来生成摘要。当它没有足够的相关上下文时,它倾向于利用其预训练数据集中的内部知识来填补这些空白,这会导致事实依据性的丧失。
最后,我们将尝试不同的窗口大小,以找出哪种窗口大小能产生最佳的评估指标。回想一下,如果窗口大小太小,可能没有足够的相关上下文来获得良好的上下文相关性和事实依据性分数。另一方面,如果窗口大小变得太大,不相关的上下文可能会渗入最终响应,导致事实依据性或答案相关性的分数不佳。
我们在笔记本中走过了三个评估或反馈函数的例子:上下文相关性、答案相关性和事实依据性。所有三个都是通过LLM评估实现的。
我想指出,反馈函数可以通过不同的方式实现。我们经常看到从业者从收集真实标签开始,这可能成本高昂,但仍然是一个好的起点。我们也看到人们利用人类进行评估,这也有帮助且有意义,但在实践中难以扩展。
一个非常有趣的研究文献结果是,如果你让一组人类重新评估一个问题,大约有80%的一致性。有趣的是,当你使用LLM评估时,LLM评估和人类评估之间的一致性也大约在80%到85%之间。这表明,对于已应用的基准数据集,LLM评估与人类评估相当可比。
因此,反馈函数为我们提供了一种以编程方式扩展评估的方法。除了你看到的LLM评估外,反馈函数还可以实现传统的NLP指标,如ROUGE分数和BLEU分数。它们在特定场景中可能有帮助,但它们的一个弱点是它们非常句法化。它们寻找两段文本之间单词的重叠。
在课程中,我们给了你三个反馈函数和评估的例子:答案相关性、上下文相关性和事实依据性。TruLens提供了更广泛的评估集,以确保你构建的应用是诚实、无害和有益的。这些都可在开源库中获得,我们鼓励你在学习课程和构建LLM应用时尝试它们。
现在我们已经设置了所有的反馈函数,我们可以设置一个对象来开始记录,该对象将用于记录应用在各种记录上的执行情况。
from trulens_eval import TruLlama
# 创建TruLlama记录器
tru_recorder = TruLlama(
query_engine,
app_id='Sentence Window Engine',
feedbacks=[f_answer_relevance, f_context_relevance, f_groundedness]
)
这个tru_recorder对象将用于运行LlamaIndex应用以及这些反馈函数的评估,并将所有内容记录在本地数据库中。
现在让我们加载一些评估问题。评估问题已经设置在这个文本文件中,然后我们执行这段代码来加载它们。
# 加载评估问题
with open("eval_questions.txt", "r") as f:
eval_questions = [line.strip() for line in f.readlines()]
现在,我们一切就绪,可以进入笔记本中最激动人心的步骤了。通过这段代码,我们可以在评估问题列表中的每个问题上执行句子窗口引擎。然后,使用tru_recorder,我们将针对RAG三元组运行每个记录,并将提示、响应、中间结果和评估结果记录在Tru数据库中。
现在我们已经完成了记录,我们可以通过执行代码来查看笔记本中的日志。这里的主要观点是,你可以看到应用中检测的深度。通过tru_recorder记录了大量信息。这些关于提示、响应、评估结果等信息对于识别应用中的失败模式以及为应用的迭代和改进提供信息非常有价值。所有这些信息都以灵活的JSON格式提供,因此可以导出并由下游流程使用。
接下来,让我们看看提示、响应和反馈函数评估的更人性化格式。通过这段代码,对于每个输入提示或问题,我们看到输出及其各自的上下文相关性、事实依据性和答案相关性分数。这是在评估问题列表中的每个条目上运行的。
我刚刚向你展示了评估、提示、响应的记录级视图。现在让我们在排行榜中获得一个聚合视图,该视图汇总了所有这些单个记录,并生成数据库中10条记录的平均分数。
在排行榜中,你可以看到所有10条记录的聚合视图。我们设置了应用ID,平均上下文相关性是0.56。同样地,所有10条记录的平均事实依据性、答案相关性和延迟分数,以及总成本(以美元计)。获得这个聚合视图对于查看你的应用表现如何以及处于什么延迟和成本水平非常有用。
除了笔记本界面,TruLens还提供了一个本地的Streamlit应用仪表板,你可以用它来检查你正在构建的应用,查看评估结果,深入到记录级视图,以获取应用性能的聚合和详细评估视图。
我们可以使用tru.run_dashboard()方法启动仪表板,这会在某个URL设置一个本地数据库。让我们花几分钟时间浏览一下这个仪表板。
你可以在这里看到应用性能的聚合视图,应用处理并评估了11条记录。平均延迟是3.55秒。我们有总成本、LLM处理的总令牌数,以及RAG三元组的分数:上下文相关性0.56,事实依据性0.86,答案相关性0.92。
我们可以在这里选择应用,以获得更详细的记录级评估视图。对于每条记录,你可以看到用户输入、提示、响应、元数据、时间戳,以及记录的答案相关性、上下文相关性和事实依据性分数,还有延迟、总令牌数和总成本。
让我选一行,其中LLM评估表明RAG应用表现良好。一旦我们点击它,我们可以向下滚动,获得该表中该行不同组件的更详细视图。例如,这里的问题是:“成为AI高手的首要步骤是什么?” RAG的最终回答是:“学习基础技术技能。” 在下面,你可以看到答案相关性被认为是1(在0到1的范围内)。这是一个与所提问题非常相关的答案。
在上面,你可以看到上下文相关性,两个检索到的上下文片段的平均上下文相关性分数是0.8。我们可以看到LLM评估给这个特定RAG响应打0.8分的思维链推理原因。然后在下面,你可以看到事实依据性评估。这是最终答案中的一个子句,它得到了1分。在这里是得分的理由。
现在,让我们看一个RAG表现不佳的例子。当我浏览评估时,我看到这一行的事实依据性分数较低,为0.5。问题是:“利他主义如何有益于职业发展?” 响应是:“此外,实践利他主义可以促进个人成就感和目标感,从而增强动力和整体幸福感,最终有益于职业成功。” 虽然这很可能是事实,但在检索到的上下文片段中没有找到支持该陈述的证据。这就是我们的评估给它低分的原因。
你可以使用仪表板进行探索,查看其他一些RAG最终输出表现不佳的例子,以了解在使用RAG应用时常见的失败模式。其中一些将在我们进入关于更高级RAG技术的课程中得到解决。
总结

本节课中,我们一起学习了RAG评估的核心框架——RAG三元组,包括答案相关性、上下文相关性和事实依据性。我们通过代码示例展示了如何使用TruLens和LlamaIndex设置这些评估,并利用合成数据集进行程序化评估。我们还探讨了如何通过迭代(如调整上下文窗口大小)来改进RAG应用,并使用Streamlit仪表板直观地分析评估结果,识别失败模式。掌握这些评估方法,是构建可靠、高效高级RAG应用的关键一步。
004:句子窗口检索 🪟


在本节课中,我们将深入学习一种高级的RAG技术——句子窗口检索方法。这种方法基于更小的句子进行检索,以更好地匹配相关上下文,然后围绕该句子扩展的上下文窗口进行信息合成。我们将探讨其原理、实现步骤,并通过实验评估不同参数对模型性能的影响。
概述
标准的RAG流水线在嵌入和合成阶段使用相同的文本块。这带来一个问题:基于嵌入的检索通常在较小的文本块上效果更好,而大语言模型需要更多的上下文和更大的文本块来合成一个好的答案。句子窗口检索将这两个过程在一定程度上解耦。它首先嵌入较小的块或句子并将其存储在向量数据库中,同时为每个块添加上下文(即该句子前后出现的句子)。在检索时,我们通过相似性搜索找到与问题最相关的句子,然后用完整的周围上下文替换该句子。这允许我们扩展实际输入给LLM的上下文,以便回答问题。
设置环境与数据
上一节我们介绍了句子窗口检索的基本概念,本节中我们来看看如何具体实现。首先,我们需要设置环境并准备数据。
这个设置与之前课程中使用的相同,因此请确保安装相关包,如 LlamaIndex 和 TruLens。对于这个快速入门,您需要一个与之前课程类似的 OpenAI API 密钥,该密钥将用于嵌入、LLM 调用以及评估。
现在,我们已经设置好并检查了用于迭代和实验的文档。与第一课类似,我们鼓励您上传自己的 PDF 文件。我们将加载《如何构建AI职业生涯》电子书,这与之前的文档相同。
# 示例:加载文档
from llama_index import SimpleDirectoryReader
documents = SimpleDirectoryReader(input_dir="./data").load_data()
print(f"已加载 {len(documents)} 个文档。")
构建句子窗口索引
接下来,让我们设置句子窗口检索方法,并深入了解其配置。我们将从窗口大小为3、top K值为6开始。
首先,我们将导入一个名为 SentenceWindowNodeParser 的对象。这个解析器会将文档分割成单个句子,然后为每个句子块添加上下文窗口。
以下是该节点解析器如何工作的一个小示例:
from llama_index.node_parser import SentenceWindowNodeParser
# 创建解析器,窗口大小为3(前1句+当前句+后1句)
node_parser = SentenceWindowNodeParser.from_defaults(
window_size=3,
window_metadata_key="window",
original_text_metadata_key="original_sentence",
)
# 示例文本
sample_text = "这是一个测试。它包含多个句子。我们看看解析效果。"
sample_nodes = node_parser.get_nodes_from_documents([Document(text=sample_text)])
for i, node in enumerate(sample_nodes):
print(f"节点 {i} 文本: {node.text}")
print(f"节点 {i} 元数据(窗口上下文): {node.metadata['window']}")
print("-" * 20)
我们可以看到,我们的文本(包含三个句子)被分割成三个节点。每个节点包含一个单独的句子,其元数据中包含该句子周围更大的窗口。例如,第二个节点的元数据包含原始句子,以及它之前和之后出现的句子。
下一步是实际构建索引。首先,我们需要设置LLM。在本例中,我们将使用OpenAI的GPT-3.5 Turbo,温度设置为0.1。接着,设置一个ServiceContext对象。这是一个包装器对象,包含索引所需的所有上下文,包括LLM、嵌入模型和节点解析器。
请注意,我们指定的嵌入模型是BGE small模型,它从Hugging Face下载并在本地运行。这是一个紧凑、快速且在其尺寸下准确的嵌入模型。我们也可以使用其他嵌入模型,例如BGE large模型(下面的代码中已注释掉)。
然后,我们使用源文档设置索引。因为我们已经将节点解析器定义为ServiceContext的一部分,所以这将把源文档转换成一连串的句子(并添加上下文),进行嵌入,然后加载到向量存储中。
我们可以将索引保存到磁盘,以便以后无需重建即可加载。如果您已经构建了索引并保存了它,并且不想重建,这里有一个方便的代码块,允许您从现有文件加载索引(如果存在),否则它将构建索引。
import os
from llama_index import VectorStoreIndex, ServiceContext
from llama_index.llms import OpenAI
from llama_index.embeddings import HuggingFaceEmbedding
# 1. 设置LLM
llm = OpenAI(model="gpt-3.5-turbo", temperature=0.1)
# 2. 设置嵌入模型和节点解析器
embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-small-en")
# embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-large-en") # 替代选择
node_parser = SentenceWindowNodeParser.from_defaults(window_size=3)
service_context = ServiceContext.from_defaults(
llm=llm,
embed_model=embed_model,
node_parser=node_parser,
)
# 3. 构建或加载索引
index_path = "./sentence_window_index"
if os.path.exists(index_path):
# 从磁盘加载
index = VectorStoreIndex.load_from_disk(index_path, service_context=service_context)
else:
# 构建新索引
index = VectorStoreIndex.from_documents(
documents, service_context=service_context
)
index.save_to_disk(index_path)
配置查询引擎
索引构建完成后,下一步是设置和运行查询引擎。


首先,我们将定义一个名为 MetadataReplacementPostProcessor 的后处理器。它获取存储在元数据中的值,并用该值替换节点文本。这是在检索到节点之后、将节点发送给LLM之前完成的。
我们将首先演示其工作原理。使用我们通过句子窗口节点解析器创建的节点,我们可以测试这个后处理器。请注意,我们已经备份了原始节点。
让我们再次查看第二个节点。现在,让我们对这些节点应用后处理器。如果我们现在查看第二个节点的文本,会发现它已被完整的上下文所替换,包括当前节点之前和之后出现的句子。
下一步是添加句子转换器重排序模型。它接收查询和检索到的节点,并使用专门的任务模型根据相关性对节点重新排序。通常,您会使初始相似性搜索的top K值更大,然后重排序器会重新评分节点并返回一个更小的top N结果集,即过滤出一个更小的集合。BGE重排序器基础版就是一个例子,它是基于BGE嵌入的重排序器。
让我们看看这个重排序器是如何工作的。我们将输入一些示例数据,然后看看重排序器如何将初始节点集重新排序为一个新的节点集。假设原始查询是“I want a dog”。初始节点得分集是:“this is a cat”得分为0.6,“this is a dog”得分为0.4。直观上,您会期望第二个节点得分更高,因为它更匹配查询。这就是重排序器的作用所在。我们看到重排序器正确地突出了关于狗的节点,并给出了较高的相关性得分。
现在,让我们将其应用到实际的查询引擎中。如前所述,我们希望初始相似性搜索的top K值比重排序器选择的top N值更大,以便给重排序器一个公平的机会来筛选出正确的信息。我们设置 top_k = 6 和 top_n = 2。这意味着我们首先使用句子窗口检索获取6个最相似的块,然后使用句子重排序器过滤出2个最相关的块。
现在我们已经完成了完整的查询引擎设置,让我们运行一个基本示例。让我们问一个关于这个数据集的问题:“在AI领域构建职业生涯的关键是什么?”我们得到了回复。我们看到最终的回答是:在AI领域构建职业生涯的关键是学习基础技术技能、进行项目实践以及寻找工作机会。
from llama_index.postprocessor import MetadataReplacementPostProcessor
from llama_index.postprocessor import SentenceTransformerRerank
from llama_index import QueryEngine
# 1. 定义后处理器 - 用于扩展上下文
postproc = MetadataReplacementPostProcessor(
target_metadata_key="window"
)
# 2. 定义重排序器
rerank = SentenceTransformerRerank(
top_n=2, model="BAAI/bge-reranker-base"
)
# 3. 从索引创建检索器(使用较大的top_k)
retriever = index.as_retriever(similarity_top_k=6)
# 4. 组装查询引擎
query_engine = QueryEngine.from_args(
retriever=retriever,
node_postprocessors=[postproc, rerank],
llm=llm
)
# 5. 进行查询
response = query_engine.query("在AI领域构建职业生涯的关键是什么?")
print(response)
整合与实验
现在我们已经有了句子窗口查询引擎,让我们把所有代码整合起来。我们将很多代码放入这个笔记本单元格中,但请注意,这本质上与实用函数文件中的功能相同。
我们拥有用于构建句子窗口索引的函数,如本笔记本前面所示。它包括使用句子窗口节点解析器从文档中提取句子并用周围上下文进行增强。它包含使用ServiceContext对象设置服务上下文。它还包括使用源文档以及包含LLM、嵌入模型和节点解析器的服务上下文来设置向量存储索引。
第二部分是实际获取句子窗口查询引擎。我们展示了它包括获取句子窗口检索器、使用元数据替换后处理器来用周围上下文替换节点,最后使用重排序模块来过滤出top N结果。我们使用查询引擎模块将所有内容组合起来。
首先调用 build_sentence_window_index 函数,参数为源文档、LLM以及保存目录。然后调用第二个函数 get_sentence_window_query_engine 来获取查询引擎。
现在,您已经准备好试验句子窗口检索了。在下一节中,我们将向您展示如何使用句子窗口检索器实际运行评估,以便您可以评估结果并实际调整参数,观察它们如何影响引擎的性能。
在运行完这些示例之后,我们鼓励您添加自己的问题,甚至定义自己的评估基准,以便进行试验并了解一切是如何运作的。
使用TruLens评估句子窗口检索器
现在您已经设置了句子窗口检索器,让我们看看如何使用RAG三元组评估它,并通过实验跟踪将其性能与基础RAG进行比较。现在让我们看看如何评估和迭代句子窗口大小参数,以便在应用程序的质量和运行应用程序及评估的成本之间做出正确的权衡。
我们将逐步增加句子窗口大小,从1开始,使用TruLens和RAG三元组评估这些连续的RAG版本,并跟踪实验以选择最佳的句子窗口大小。在进行这个练习时,我们希望注意到随着窗口大小的增加,令牌使用量或成本之间的权衡。令牌使用量和成本将会上升,在许多情况下,上下文相关性也会上升。同时,在开始时增加窗口大小,我们预计会提高上下文相关性,因此也会间接提高真实性。原因之一是,当检索步骤没有产生足够相关的上下文时,完成步骤中的LLM倾向于利用其预训练阶段已有的知识来填补这些空白,而不是明确依赖检索到的上下文片段,这种选择可能导致真实性降低,因为真实性意味着最终响应的组成部分应可追溯到检索到的上下文片段。
因此,我们预期的是,随着句子窗口大小的不断增加,上下文相关性将增加到某一点,真实性也会随之增加,然后超过那一点,我们将看到上下文相关性要么趋于平缓,要么下降,真实性也可能遵循类似的模式。此外,在实践中,您还可以看到上下文相关性和真实性之间有一个非常有趣的关系:当上下文相关性低时,真实性往往也低。这是因为LLM通常会尝试利用其预训练阶段的知识来填补检索到的上下文片段中的空白,这导致真实性降低,即使答案实际上可能相当相关。随着上下文相关性的增加,真实性也倾向于增加到某一点,但如果上下文变得太大,即使上下文相关性很高,真实性也可能会下降,因为LLM可能会被过大的上下文淹没,并退回到其训练阶段的已有知识库。
现在让我们试验句子窗口大小。我将引导您完成一个笔记本,加载一些用于评估的问题,然后逐步增加句子窗口大小,并观察其对RAG三元组评估指标的影响。
首先,我们加载一组预生成的评估问题。您可以从这个列表中看到一些问题。接下来,我们运行评估。对于重新加载的评估问题集中的每个问题,然后,使用TruRecorder对象,我们记录应用程序的提示、响应、中间结果以及评估结果到TruLens数据库中。
现在让我们调整句子窗口大小参数,并观察其对不同RAG三元组评估指标的影响。我们首先重置TruLens数据库。通过这段代码片段,我们将句子窗口大小设置为1。您会注意到,在这个设置中,其他所有内容都与之前相同。然后,我们设置句子窗口引擎,使用与此索引关联的 get_sentence_window_query_engine。接下来,我们准备好设置TruRecorder,并将句子窗口大小设置为1。这设置了所有反馈函数的定义(包括答案相关性、上下文相关性和真实性)。现在,我们已经设置好一切,可以运行句子窗口大小为1的评估了。所有相关的提示、响应、中间结果以及这些反馈函数的评估结果都将被记录到TruLens数据库中。
运行完成后,让我们在仪表板中查看。您会看到这个指令启动了本地托管的Streamlit应用程序,您可以点击链接进入。应用程序排行榜向我们展示了所有21条记录的聚合指标,这些记录我们已通过TruLens运行并评估。平均延迟为4.57秒,总成本约为2美分,处理的令牌总数约为9000。您可以看到评估指标:该应用程序在答案相关性和真实性方面表现相当好,但在上下文相关性方面相当差。
现在让我们深入查看由应用程序处理并评估的单个记录。如果我向右滚动,可以看到一些示例,其中应用程序在这些指标上表现不佳。让我选择这一行,然后我们可以更深入地检查它的表现。这里的问题是:“在项目选择和执行的背景下,解释‘准备-瞄准-开火’和‘准备-开火-瞄准’方法之间的区别。提供每种方法可能更有益的示例。”您可以在此处详细查看来自RAG的总体响应。然后,如果向下滚动,我们可以看到真实性、上下文相关性和答案相关性的总体得分。在这个例子中检索到了两个上下文片段。对于其中一个检索到的上下文片段,上下文相关性相当低。让我们深入这个例子并仔细看看。您将在这个例子中看到,这个上下文片段非常小。请记住,我们使用的是大小为1的句子窗口,这意味着我们只在开头额外添加了一个句子,在结尾额外添加了一个句子,围绕检索到的上下文片段。这产生了一个相当小的上下文片段,遗漏了使其与所提问题相关的重要信息。
类似地,如果您查看真实性,我们会看到这两个片段都检索到了最终摘要中的句子,真实性得分相当低。让我们选择真实性得分较高的那个,它有更多的理由支持。如果我们看这个例子,我们会看到开头有几句话,在检索到的上下文片段中有很好的支持证据,所以这里的得分很高,是10分(满分10分)。但对于下面的这些句子,没有支持证据,因此真实性得分为0。让我们举一个具体的例子。也许这句话:“它通常用于执行成本相对较低、快速迭代和适应的能力比前期规划更重要的情境中。”这感觉像是一个合理的文本片段,可能对回答问题有用。然而,它并不在检索到的上下文片段中,没有得到检索到的上下文中任何支持证据的支持。这可能是模型在其训练阶段学习到的部分内容,可能来自Andrew关于AI职业建议的同一文档,或其他谈论相同主题的来源。模型可能学到了类似的信息,但它并非基于此。在这个特定实例中,该句子不受检索到的上下文片段的支持。
因此,当句子窗口太小时,这是一个普遍问题:上下文相关性往往较低,因此真实性也变得较低,因为LLM开始利用其训练阶段的已有知识来回答问题,而不是仅仅依赖提供的上下文。
现在我已经向您展示了句子窗口设置为1时的失败模式,我想再演示几个步骤,看看随着我们改变句子窗口大小,指标如何改善。为了快速浏览笔记本,我将重新加载评估问题。但在这次实例中,只将其设置为模型有问题的一个问题,即这个特定问题,我们刚刚在句子窗口大小设置为1时详细讨论过。然后,我想在句子窗口大小设置为3的情况下运行它。这段代码片段将设置句子窗口大小为3的RAG,并为其设置TruRecorder。我们现在已经设置了反馈函数的定义,此外还有句子窗口设置为3的RAG。接下来,我们将针对我们详细查看过的那个特定评估问题运行评估,在句子窗口设置为1时我们观察到了失败模式。现在,让我们在TruLens仪表板中查看句子窗口引擎设置为3时的结果。
您可以在此处看到结果。我在一个记录上运行了它,这个记录是我们在查看句子窗口大小为1时的问题记录。您可以看到上下文相关性大幅提升,从0.57上升到0.9。现在,如果我选择该应用程序并更详细地查看这个例子,让我们看看在句子窗口设置为3时,我们之前查看的同一个问题。这是完整的最终响应。现在,如果您查看检索到的上下文片段,您会注意到这个特定的检索上下文片段类似于我们之前在句子窗口大小为1时查看的那个,但现在它因为更大的句子窗口大小而有了扩展。如果您查看这一部分的得分,我们会看到这个上下文的上下文相关性得分为0.9,高于之前较小上下文获得的0.8分。这个例子表明,随着句子窗口大小的扩展,即使是相当好的检索上下文片段也可以变得更好。一旦完成步骤处理了这些显著更好的上下文片段,真实性得分就会大幅提升。您会看到,通过在这两个高度相关的上下文片段中找到支持证据,真实性得分实际上一直上升到1。
因此,将句子窗口大小从1增加到3,导致了RAG三元组评估指标的显著改善,真实性和上下文相关性都显著上升,答案相关性也是如此。现在我们可以看看句子窗口设置为5的情况。如果您查看这里的指标,有几件事需要注意。一是总令牌数增加了,如果我们要增加记录数量,这可能会影响成本。这就是我之前提到的权衡之一。随着句子窗口大小的增加,成本会更高,因为在评估过程中,LLM会处理更多的令牌。另一件需要注意的事情是,虽然上下文相关性和答案相关性保持平稳,但真实性实际上随着句子窗口大小的增加而下降了。这在一定程度后可能发生,因为随着上下文大小的增加,LLM在完成步骤中可能会被过多的信息淹没,并且在总结过程中,它可能开始引入自己的已有知识,而不是仅仅使用检索到的上下文片段中的信息。
因此,总结一下,事实证明,随着我们逐渐将句子窗口大小从1增加到3再到5,对于这个特定的评估,大小为3对我们来说是最佳选择。我们看到,当从1到3时,上下文相关性、答案相关性和真实性都有所增加,然后随着进一步增加到5,真实性出现了下降或退化。当您使用笔记本进行实验时,我们鼓励您在这两个步骤中使用更多记录重新运行它,检查导致上下文相关性或真实性等特定指标出现问题的单个记录,并围绕为什么会出现故障模式以及如何解决它们建立一些直觉。
在下一节中,我们将研究另一种高级RAG技术:自动合并检索,以解决其中一些故障模式。不相关的上下文可能会渗入最终响应,导致真实性或答案相关性得分不佳。
总结

本节课中,我们一起学习了高级RAG技术中的句子窗口检索方法。我们从其核心原理出发,了解了它如何通过解耦检索与合成的文本块大小来优化性能。我们逐步实现了句子窗口索引的构建、查询引擎的配置(包括元数据替换后处理器和重排序器),并
005:第四课 自动合并检索 🔄

在本节课中,我们将深入探讨另一种高级RAG技术:自动合并检索。我们将学习其工作原理、如何设置,以及如何使用评估工具来优化其性能。

概述 📋
标准的RAG流水线存在一个问题:它会检索一堆零散的上下文块放入LLM的上下文窗口。块尺寸越小,这种碎片化问题就越严重。自动合并检索通过一种启发式方法,将较小的块合并成较大的父块,从而帮助确保上下文的连贯性。
自动合并检索的原理
上一节我们介绍了标准RAG的局限性,本节中我们来看看自动合并检索如何解决这个问题。
标准RAG流水线的问题是,你检索到的是一堆零散的上下文块,并将它们放入LLM的上下文窗口。你的块尺寸越小,碎片化就越严重。例如,你可能会得到两个或多个来自大致相同部分的检索上下文块,但这些块的顺序实际上无法保证。这可能会妨碍LLM在其上下文窗口内对检索到的上下文进行综合处理的能力。
自动合并检索的工作原理如下:
- 首先,定义一个从较小块链接到较大父块的层次结构,其中每个父块可以拥有一定数量的子块。
- 其次,在检索过程中,如果链接到某个父块的较小块的集合超过了某个百分比阈值,那么我们将较小的块合并到较大的父块中。这样,我们检索的是较大的父块,以帮助确保更连贯的上下文。
设置自动合并检索器
现在让我们看看如何设置它。这个笔记本将介绍使用LlamaIndex构建自动合并检索器所需的各种组件。我们将详细介绍各个组件。与上一节类似,最后我们将展示如何使用TruEra进行参数实验和评估。
与之前一样,我们将加载OpenAI API密钥,并使用工具文件中的一个便捷辅助函数来完成。与之前的课程一样,我们也将使用“如何在AI领域建立职业生涯”这份PDF文件。同样,我们也鼓励你尝试使用自己的PDF文件。
我们加载了41个文档对象,并将它们合并成一个大文档,这使其更适合与我们的高级检索方法进行文本混合。
现在我们已经准备好设置自动合并检索器。这将由几个不同的组件组成,第一步是定义所谓的分层节点解析器。
1. 定义分层节点解析器
为了使用自动合并检索器,我们需要以分层方式解析节点。这意味着节点按尺寸递减的方式解析,并在其父节点中包含关系。
以下是一个演示节点解析器工作原理的小例子。
我们创建一个具有小块尺寸的示例解析器进行演示。
请注意,我们使用的块尺寸是2048、512和128。你可以将块尺寸更改为任何你想要的递减顺序,这里我们使用4的倍数。
现在让我们从文档中获取节点。这样做实际上会返回所有节点:叶子节点、中间节点以及父节点。因此,叶子节点、中间节点和父节点之间的信息和内容会有相当多的重叠。
如果我们只想检索叶子节点,可以调用LlamaIndex中的一个函数 get_leaf_nodes,并查看其内容。
在这个例子中,我们在原始节点集上调用 get_leaf_nodes,并查看第31个节点的文本。
我们看到文本块实际上相当小,这是一个叶子节点的例子,因为叶子节点是128个标记的最小块尺寸。
现在我们已经展示了叶子节点的样子,我们也可以探索节点间的关系。
我们可以打印上述节点的父节点,并观察到它是一个包含叶子节点文本的更大块,而且内容更多。更具体地说,父节点包含512个标记,同时拥有四个包含128个标记的叶子节点。有四个叶子节点是因为块尺寸除以了4的倍数。
这是第31个叶子节点的父节点示例。
2. 构建索引
现在我们已经向你展示了节点层次结构的样子,我们可以构建索引了。
我们将使用OpenAI LLM,特别是GPT-3.5 Turbo。我们还将定义一个包含LLM、嵌入模型和分层节点解析器的服务上下文对象。
与之前的笔记本一样,我们将使用BGE-small-en嵌入模型。
下一步是构建我们的索引。索引的工作方式是,我们专门在叶子节点上构建向量索引。所有其他中间节点和父节点都存储在文档存储中,并在检索期间动态检索。但在初始的top-K嵌入查找中,我们实际获取的是叶子节点,并且我们嵌入的也是叶子节点。
在这段代码中,我们看到定义了一个存储上下文对象,默认情况下使用内存中的文档存储进行初始化,我们调用 storage_context.docstore.add_documents 将所有节点添加到这个内存中的文档存储。然而,当我们创建向量存储索引(此处称为 automerging_index)时,我们只传入叶子节点进行向量索引。
这意味着叶子节点专门使用嵌入模型进行嵌入和索引,但我们也传入了存储上下文和服务上下文。因此,向量索引确实知道包含所有节点的底层文档存储。最后,我们持久化这个索引。如果你已经构建了这个索引并想从存储中加载,你可以直接复制并粘贴这段代码块,如果索引不存在,它将重建索引,否则从存储中加载。
3. 设置检索器和运行查询引擎
定义好自动合并索引后,最后一步是设置检索器并运行查询引擎。
自动合并检索器控制合并逻辑。如果给定父节点的大部分子节点被检索到,它们将被替换为父节点。为了使这种合并工作良好,我们为叶子节点设置了一个较大的top-K值。请记住,叶子节点的块尺寸较小,为128个标记。为了减少标记使用量,我们在合并发生后应用了一个重排序器。例如,我们可能检索前12个,合并后得到前10个,然后重排序为前6个。重排序器的最终top-N值可能看起来较大,但请记住,基础块尺寸只有128个标记,而上一级父节点是512个标记。

我们导入一个名为 AutoMergingRetriever 的类。然后我们定义一个句子转换器重排序模块。我们将自动合并检索器和重排序模块组合到我们的检索器查询引擎中,该引擎处理检索和综合。

现在我们已经完成了整个设置,让我们实际测试一下。以“网络在AI中的重要性是什么?”为例,我们得到了回答。它说网络在AI中很重要,因为它允许个人发展强大的专业网络等等。
4. 整合所有步骤
下一步是将所有步骤整合在一起。我们将创建两个高级函数:build_automerging_index 和 get_automerging_query_engine。这基本上囊括了我们刚才展示的所有步骤。
第一个函数 build_automerging_index 将使用分层节点解析器解析出子节点到父节点的层次结构。它将定义服务上下文。它将从叶子节点创建向量存储索引,同时也链接到所有节点的文档存储。
第二个函数 get_automerging_query_engine 利用我们的自动合并检索器,该检索器能够动态地将叶子节点合并到父节点中,并且使用我们的重排序模块,然后将其与整体的检索器查询引擎结合。
因此,我们使用原始源文档、LLM设置为GPT-3.5 Turbo以及索引保存目录,通过 build_automerging_index 函数构建索引。
然后对于查询引擎,我们基于索引调用 get_automerging_query_engine,同时我们将相似度top-K设置为等于6。
作为下一步,我们将展示如何使用TruEra评估自动合并检索器,并迭代参数。我们鼓励你也尝试自己的问题,并迭代自动检索的参数,例如,当你更改块尺寸、top-K值或重排序器的最终top-N值时会发生什么。尝试一下并告诉我们结果如何。
评估与参数迭代 🧪
太棒了,Jerry。现在你已经设置了自动合并检索器,让我们看看如何用RAG三元组评估它,并通过实验跟踪将其性能与基础RAG进行比较。
让我们设置这个自动合并索引。你会注意到它是两层结构。最底层的块(叶子节点)的块尺寸为512。层次结构中的上一层块尺寸为2048,这意味着每个父节点将拥有四个叶子节点,每个叶子节点512个标记。设置的其他部分与Jerry之前向你展示的完全相同。
你可能想尝试两层自动合并结构的一个原因是它更简单。创建索引所需的工作更少。同样,在检索步骤中,所需的工作也更少,因为所有第三层块都消失了。如果它的性能相当好,那么理想情况下,我们希望使用更简单的结构。
现在我们已经用这个两层自动合并结构创建了索引,让我们为这个设置设置自动合并引擎。我将保持top-K值与之前相同,即12。重排序步骤也将保持相同的n=6。这将使我们能够在这个应用设置与Jerry之前设置的三层自动合并层次结构应用之间进行更直接的正面比较。
现在让我们用这个自动合并引擎设置Tru记录器。我们将给它一个应用ID:app_0。现在让我们加载一些用于评估的问题,从我们之前设置的生成问题文本文件中加载。
现在我们可以定义这些评估问题的运行。对于about_questions中的每个问题,我们将进行设置,以便在调用Tru记录器对象与RAG链时,记录提示、响应和评估结果,利用查询引擎。
现在我们的评估已经完成,让我们看一下排行榜。我们可以看到App_0在这里,上下文相关性似乎较低。另外两个指标更好。这是我们的两层层次结构,叶子节点块尺寸为512,父节点为2048个标记,所以叶子节点是512个标记。
现在我们可以运行Tru仪表板,并在更详细的记录级别查看评估结果。让我们检查应用排行榜。
你可以看到,在处理了24条记录后,上下文相关性在聚合层面上相当低,尽管该应用在答案相关性和事实基础性方面表现更好。我可以选择该应用。
现在让我们查看App_0的各个记录,看看各项评估分数如何。你可以向右滚动,查看答案相关性、上下文相关性和事实基础性的分数。让我们选一个上下文相关性低的记录。
这里有一个。如果你点击它,你会在下面看到更详细的视图。问题是“讨论为资源预算对AI项目成功执行的重要性”。右边是回答。如果你进一步向下滚动,可以看到上下文相关性的更详细视图。有六条检索到的上下文,每一条的评估分数都特别低,在0到0.2之间。如果你选择其中任何一条并点击它,可以看到回答与所提问题不太相关。你也可以向上滚动,探索其他一些记录。你可以选择分数好的记录,例如这个,并探索应用在不同问题上的表现,它的优势在哪里,失败模式是什么,从而对什么有效、什么无效建立一些直觉。
现在让我们将之前的应用与Jerry之前介绍的自动合并设置进行比较。现在我们的层次结构中将有三层,叶子节点级别从128个标记开始,上一层是512,最高层是2048。所以在每一层,每个父节点有4个子节点。
现在,让我们为这个应用设置设置查询引擎、Tru记录器,所有步骤都与前一个应用相同。最后,我们准备好运行评估。
现在我们设置了App_1,可以在这里快速查看排行榜,你可以看到相对于App_0,相同记录数下App_1处理的标记数大约是一半,总成本也大约是一半。这是因为回想一下,这个层次结构有三层,块尺寸是128个标记,而不是App_0中最小的叶子节点标记尺寸512。这导致了成本降低。还要注意,上下文相关性增加了约20%。部分原因是,在这种新的应用设置下,合并可能发生得更好。
我们也可以深入查看App_1的更多细节,像之前一样。我们可以查看各个记录。让我们选择之前查看过的同一个记录,即App_0中关于预算重要性的问题。现在你可以看到上下文相关性表现更好,事实基础性也显著提高。如果你选择一个示例回答,你会看到它实际上非常具体地讨论了为资源预算。所以在这个特定实例中以及聚合层面上都有改进。
总结与关键要点 🎯
现在让我总结一下第四课的一些关键要点。
我们引导你了解了一种评估和迭代自动合并检索这一高级RAG技术的方法。特别是,我们向你展示了如何迭代不同的层次结构、层数、子节点数量和块尺寸。对于这些不同的应用版本,你可以用RAG三元组评估它们,并通过跟踪实验来为你的用例选择最佳结构。
需要注意的一点是,你不仅获得了作为评估一部分的RAG三元组相关指标,而且深入到记录级别可以帮助你获得关于哪些超参数最适合某些文档类型的直觉。例如,根据文档的性质(如雇佣合同与发票),你可能会发现不同的块尺寸和层次结构效果最好。
最后,另一件需要注意的事情是,自动合并与句子窗口检索是互补的。一种思考方式是,假设你有一个父节点的四个子节点,通过自动合并,你可能会发现子节点1和子节点4与所提查询非常相关,然后它们在自动合并范式下被合并。相比之下,句子窗口可能不会导致这种合并,因为它们不在文本的连续部分中。

这就结束了第四课。我们观察到,通过高级RAG技术(如句子窗口和自动合并检索),并结合评估、实验跟踪和迭代的力量,你可以显著改进你的RAG应用。此外,虽然本课程重点介绍了这两种技术以及相关的RAG三元组评估,但还有许多其他评估方法可供尝试,以确保你的LLM应用是诚实、无害和有益的。这张幻灯片列出了TruLens中现成可用的一些评估方法,我们鼓励你去尝试TruLens,探索笔记本,并将你的学习提升到新的水平。
006:课程总结与展望 🎯
在本节课中,我们将对构建和评估高级RAG应用的全过程进行总结,并展望未来的学习方向与实践建议。
恭喜你完成本课程。希望你已经掌握了如何构建、评估并迭代你的RAG应用,使其更接近生产就绪状态。
无论你来自数据科学、机器学习背景,还是传统的软件开发背景,都需要学习这些核心的开发原则,以便成为一名能够构建稳健大语言模型软件系统的优秀AI工程师。
随着领域的发展,减少大语言模型的幻觉将是每位开发者的首要任务。我们很高兴看到基础模型在不断进步,大规模评估也变得更加廉价和易于获取,使得每个人都能着手进行。
提升RAG性能的后续步骤 🔍
上一节我们总结了课程的核心目标,本节中我们来看看如何进一步深化学习与实践。作为下一步,我建议你更深入地理解你的数据管道、检索策略和大语言模型提示,以帮助提升RAG的性能。
我们展示的两种技术仅仅是冰山一角。你应该探索从文本块大小到混合搜索等检索技术,再到基于大语言模型的推理(如思维链)等各个方面。

以下是你可以深入探索的关键方向列表:
- 数据管道优化:例如调整
chunk_size参数。 - 高级检索技术:例如实现
hybrid_search(结合关键词与向量搜索)。 - 大语言模型推理:例如应用
Chain-of-Thought提示技术。


评估框架与未来主题 📊
RA三元组是评估基于检索的大语言模型应用的一个绝佳起点。我鼓励你在大语言模型及其驱动的应用评估领域进行更深入的挖掘。


这包括评估模型置信度、校准、不确定性、可解释性、隐私性、公平性以及在正常与对抗性环境下的毒性等主题。

我们期待看到你接下来的构建成果。


本节课总结:本节课中我们一起学习了课程的核心收获,明确了减少大模型幻觉是开发重点。我们回顾了提升RAG性能的关键方向,包括数据管道、检索策略和提示工程,并介绍了RA三元组这一重要的评估起点。最后,我们展望了未来需要深入研究的评估主题,为你的持续学习指明了方向。

浙公网安备 33010602011771号