当前位置: 首页 > news >正文

【RAG】【retrievers11】递归检索器 + 节点引用 + Braintrust评估

案例目标

本案例展示如何使用递归检索(Recursive Retrieval)遍历节点关系,并基于"引用"获取节点。节点引用是一个强大的概念,在初次检索时,您可能希望获取引用而非原始文本。多个引用可以指向同一个节点。

案例探索了节点引用的不同用法:

  • 分块引用:不同大小的分块引用更大的分块
  • 元数据引用:摘要和生成的问题引用更大的分块

通过Braintrust评估系统,我们量化了递归检索+节点引用方法的效果,证明这种方法相比传统检索方法有显著提升。

技术栈与核心依赖

llama-index-llms-openai

llama-index-readers-file

llama-index-core

braintrust

autoevals

pypdf

transformers

torch

环境配置

# 安装必要的依赖
pip install llama-index-llms-openai llama-index-readers-file
pip install -U llama_hub llama_index braintrust autoevals pypdf pillow transformers torch torchvision

# 设置API密钥
import os

os.environ["OPENAI_API_KEY"] = "your_openai_api_key"
os.environ["BRAINTRUST_API_KEY"] = "your_braintrust_api_key"
os.environ["TOKENIZERS_PARALLELISM"] = "true" # 避免Chroma的警告信息

案例实现

1. 数据准备

步骤 1

下载并加载Llama 2论文:

!mkdir data
!wget --user-agent "Mozilla" "https://arxiv.org/pdf/2307.09288.pdf" -O "data/llama2.pdf"

from pathlib import Path
from llama_index.readers.file import PDFReader

loader = PDFReader()
docs0 = loader.load_data(file=Path("./data/llama2.pdf"))

# 合并文档内容
from llama_index.core import Document
doc_text = "\\n\\n".join([d.get_content() for d in docs0])
docs = [Document(text=doc_text)]

2. 创建基础节点

步骤 2

创建基础节点(分块大小1024):

from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.schema import IndexNode

# 创建文本分割器
node_parser = SentenceSplitter(chunk_size=1024)

# 获取节点
base_nodes = node_parser.get_nodes_from_documents(docs)

# 设置节点ID
for idx, node in enumerate(base_nodes):
node.id_ = f"node-{idx}"

3. 基线检索器

步骤 3

创建基线检索器,通过嵌入相似度获取top-k原始文本节点:

from llama_index.core import VectorStoreIndex
from llama_index.core.embeddings import resolve_embed_model
from llama_index.llms.openai import OpenAI

# 设置嵌入模型和LLM
embed_model = resolve_embed_model("local:BAAI/bge-small-en")
llm = OpenAI(model="gpt-3.5-turbo")

# 创建向量索引和检索器
base_index = VectorStoreIndex(base_nodes, embed_model=embed_model)
base_retriever = base_index.as_retriever(similarity_top_k=2)

4. 分块引用:小子块引用大父块

步骤 4

构建小子块指向大父块的图结构:

from llama_index.core.retrievers import RecursiveRetriever

# 定义子块大小
sub_chunk_sizes = [128, 256, 512]
sub_node_parsers = [SentenceSplitter(chunk_size=c) for c in sub_chunk_sizes]

all_nodes = []

# 为每个基础节点创建子节点和引用
for base_node in base_nodes:
for n in sub_node_parsers:
sub_nodes = n.get_nodes_from_documents([base_node])
sub_inodes = [
IndexNode.from_text_node(sn, base_node.node_id) for sn in sub_nodes
]
all_nodes.extend(sub_inodes)

# 添加原始节点
original_node = IndexNode.from_text_node(base_node, base_node.node_id)
all_nodes.append(original_node)

步骤 5

创建递归检索器:

# 创建节点字典
all_nodes_dict = {n.node_id: n for n in all_nodes}

# 创建向量索引
vector_index_chunk = VectorStoreIndex(all_nodes, embed_model=embed_model)
vector_retriever_chunk = vector_index_chunk.as_retriever(similarity_top_k=2)

# 创建递归检索器
retriever_chunk = RecursiveRetriever(
"vector",
retriever_dict={"vector": vector_retriever_chunk},
node_dict=all_nodes_dict,
verbose=True,
)

5. 元数据引用:摘要和生成的问题引用更大的块

步骤 6

提取元数据(摘要和问题)并创建引用:

from llama_index.core.extractors import (
SummaryExtractor,
QuestionsAnsweredExtractor,
)

# 创建提取器
extractors = [
SummaryExtractor(summaries=["self"], show_progress=True),
QuestionsAnsweredExtractor(questions=5, show_progress=True),
]

# 运行元数据提取器
metadata_dicts = []
for extractor in extractors:
metadata_dicts.extend(extractor.extract(base_nodes))

步骤 7

保存和加载元数据:

import json
import copy

# 保存元数据
def save_metadata_dicts(path):
with open(path, "w") as fp:
for m in metadata_dicts:
fp.write(json.dumps(m) + "\\n")

# 加载元数据
def load_metadata_dicts(path):
with open(path, "r") as fp:
metadata_dicts = [json.loads(l) for l in fp.readlines()]
return metadata_dicts

# 保存和加载
save_metadata_dicts("data/llama2_metadata_dicts.jsonl")
metadata_dicts = load_metadata_dicts("data/llama2_metadata_dicts.jsonl")

步骤 8

创建包含源节点和元数据的所有节点:

# 创建所有节点(源节点 + 元数据)
all_nodes = copy.deepcopy(base_nodes)
for idx, d in enumerate(metadata_dicts):
inode_q = IndexNode(
text=d["questions_this_excerpt_can_answer"],
index_id=base_nodes[idx].node_id,
)
inode_s = IndexNode(
text=d["section_summary"],
index_id=base_nodes[idx].node_id
)
all_nodes.extend([inode_q, inode_s])

# 创建节点字典
all_nodes_dict = {n.node_id: n for n in all_nodes}

# 创建向量索引和检索器
vector_index_metadata = VectorStoreIndex(all_nodes)
vector_retriever_metadata = vector_index_metadata.as_retriever(similarity_top_k=2)

# 创建递归检索器
retriever_metadata = RecursiveRetriever(
"vector",
retriever_dict={"vector": vector_retriever_metadata},
node_dict=all_nodes_dict,
verbose=True,
)

6. 评估设置

步骤 9

生成评估数据集:

from llama_index.core.evaluation import (
generate_question_context_pairs,
EmbeddingQAFinetuneDataset,
)
import nest_asyncio

nest_asyncio.apply()

# 生成问题-上下文对
eval_dataset = generate_question_context_pairs(base_nodes)
eval_dataset.save_json("data/llama2_eval_dataset.json")

# 加载数据集
eval_dataset = EmbeddingQAFinetuneDataset.from_json(
"data/llama2_eval_dataset.json"
)

步骤 10

定义评估指标和函数:

import pandas as pd
import braintrust

# 准备数据
queries = eval_dataset.queries
relevant_docs = eval_dataset.relevant_docs
data = [
({"input": queries[query], "expected": relevant_docs[query]})
for query in queries.keys()
]

# 定义评分函数
def hitRateScorer(input, expected, output=None):
is_hit = any([id in expected for id in output])
return 1 if is_hit else 0

def mrrScorer(input, expected, output=None):
for i, id in enumerate(output):
if id in expected:
return 1 / (i + 1)
return 0

步骤 11

评估分块检索器:

# 设置向量检索器相似度top k为更高值
top_k = 10

# 创建分块检索器
vector_retriever_chunk = vector_index_chunk.as_retriever(similarity_top_k=10)
retriever_chunk = RecursiveRetriever(
"vector",
retriever_dict={"vector": vector_retriever_chunk},
node_dict=all_nodes_dict,
verbose=False,
)

# 定义运行函数
def runChunkRetriever(input, hooks):
retrieved_nodes = retriever_chunk.retrieve(input)
retrieved_ids = [node.node.node_id for node in retrieved_nodes]
return retrieved_ids

# 运行评估
chunkEval = await braintrust.Eval(
name="llamaindex-recurisve-retrievers",
data=data,
task=runChunkRetriever,
scores=[hitRateScorer, mrrScorer],
)

步骤 12

评估元数据检索器:

# 创建元数据检索器
vector_retriever_metadata = vector_index_metadata.as_retriever(similarity_top_k=10)
retriever_metadata = RecursiveRetriever(
"vector",
retriever_dict={"vector": vector_retriever_metadata},
node_dict=all_nodes_dict,
verbose=False,
)

# 定义运行函数
def runMetaDataRetriever(input, hooks):
retrieved_nodes = retriever_metadata.retrieve(input)
retrieved_ids = [node.node.node_id for node in retrieved_nodes]
return retrieved_ids

# 运行评估
metadataEval = await braintrust.Eval(
name="llamaindex-recurisve-retrievers",
data=data,
task=runMetaDataRetriever,
scores=[hitRateScorer, mrrScorer],
)

步骤 13

评估基线检索器:

# 创建基线检索器
base_retriever = base_index.as_retriever(similarity_top_k=10)

# 定义运行函数
def runBaseRetriever(input, hooks):
retrieved_nodes = base_retriever.retrieve(input)
retrieved_ids = [node.node.node_id for node in retrieved_nodes]
return retrieved_ids

# 运行评估
baseEval = await braintrust.Eval(
name="llamaindex-recurisve-retrievers",
data=data,
task=runBaseRetriever,
scores=[hitRateScorer, mrrScorer],
)

案例效果

通过Braintrust评估系统,我们比较了三种检索器的性能:基线检索器、分块引用递归检索器和元数据引用递归检索器。评估指标包括命中率(hit_rate)和平均倒数排名(MRR)。

评估指标说明

  • 命中率(Hit Rate):衡量在检索结果中是否包含至少一个相关文档
  • 平均倒数排名(MRR):衡量第一个相关文档在检索结果中的位置排名的倒数平均值
检索器类型命中率(Hit Rate)平均倒数排名(MRR)
基线检索器较低较低
分块引用递归检索器中等中等
元数据引用递归检索器较高较高

评估结果分析

评估结果表明,使用节点引用(无论是分块引用还是元数据引用)的检索器性能优于直接获取原始分块的基线检索器。这是因为:

  • 分块引用允许检索更小的粒度,但返回更大的上下文,提高了相关性
  • 元数据引用通过摘要和问题提供了更丰富的语义信息,增强了检索的准确性
  • 递归检索机制使得系统能够根据引用关系自动获取更相关的信息

案例实现思路

递归检索器+节点引用的核心思路是通过构建节点间的引用关系,实现更精确、更全面的检索:

  1. 节点引用概念:将节点分为引用节点和源节点,引用节点包含指向源节点的ID
  2. 分块引用策略:创建不同大小的子块,每个子块引用更大的父块,实现细粒度检索但获取大上下文
  3. 元数据引用策略:提取摘要和生成问题作为引用,提供更丰富的语义信息
  4. 递归检索机制:检索时首先获取引用节点,然后根据引用关系递归获取源节点
  5. 评估驱动优化:通过Braintrust评估系统量化不同策略的效果,指导优化方向

这种方法特别适用于需要精确匹配但同时又需要丰富上下文的场景。通过节点引用,系统能够在保持检索精度的同时,提供更全面的信息,从而提高RAG系统的整体性能。

扩展建议

  • 多级引用:构建多级引用关系,实现更复杂的检索路径
  • 动态引用生成:根据查询内容动态生成引用节点
  • 引用权重学习:通过机器学习学习不同引用类型的权重
  • 混合引用策略:结合分块引用和元数据引用,发挥各自优势
  • 引用关系可视化:开发可视化工具展示节点引用关系,便于调试和优化
  • 领域特定引用:针对特定领域设计专门的引用策略
  • 实时引用更新:支持引用关系的实时更新和维护

总结

递归检索器+节点引用是一种强大的检索增强技术,通过构建节点间的引用关系,实现了更精确、更全面的检索效果。本案例展示了两种主要的引用策略:分块引用和元数据引用,并通过Braintrust评估系统证明了它们相对于传统检索方法的优势。

这种方法的核心价值在于它解决了传统检索中精度和上下文之间的权衡问题。通过节点引用,系统能够在保持高精度的同时获取更丰富的上下文信息,从而显著提高RAG系统的性能。随着RAG技术的不断发展,这种基于引用关系的检索方法将在构建更智能、更全面的信息检索系统中发挥越来越重要的作用。

http://www.jsqmd.com/news/872021/

相关文章:

  • 2026 苏州高评价上门回收黄金实测:哪家靠谱不踩坑? - 速递信息
  • 2026 全国翻译公司实力排行榜:专业、合规、高效机构权威推荐 - 速递信息
  • 利用Taotoken模型广场为你的网站选择最合适的AI模型
  • MySQL 运维实战系列(一)二进制安装 Mysql 8.0
  • 论文初稿被批太水?,有哪些真正亲测好用的的降AI率软件推荐?
  • taotoken的openai兼容协议让现有应用迁移成本降至最低
  • GHelper:华硕笔记本硬件控制的模块化架构与技术实现深度解析
  • 海事边缘计算实战:基于 Linux 构建船舶多链路高可用网络与隔离防线
  • 7天深度拆解:openpilot自动驾驶系统技术实现与二次开发指南
  • 2026年海口黄金回收平台深度盘点,标杆企业花落谁家? - 黄金上门回收
  • 下载 | Win10正式版最新系统ISO映像 (22H2、19045.7291、多合一版本)-修复系统问题
  • 市面上有哪些是真正性价比高的降AIGC软件(轻松压低AI生成疑似率)
  • 一多操作系统的生命体架构与当前主流开发语言的区别
  • Python之运算符优先级规则
  • TVBoxOSC:打造全能电视媒体中心的终极解决方案
  • [具身智能-855]:什么是AI应用?AI 应用、AI 模型、AI Agent三者区别?
  • 2026年东莞黄金回收避坑指南——福昌夏等六大机构实测对比 - 黄金上门回收
  • GPU 池化5个真实场景告诉你,为什么需要 OrionX 社区版
  • 协议分层传输、TCP报头与TCP三次握手介绍
  • 5大长期记忆系统终极横评!谁是AI Agent的「最强大脑」
  • 什么是AI Agent
  • 如何用嘎嘎降AI处理哲学论文:哲学毕业论文降AI4.8元完整操作教程 - 还在做实验的师兄
  • 心理学论文降AI工具免费推荐:2026年心理学毕业论文知网维普降AI4.8元亲测完整方案
  • Android Studio 打包 React 网页项目
  • Python之anonymous包语法、参数和实际应用案例
  • 利用 TaoToken 多模型聚合能力优化 AIGC 内容生成管线
  • Windows 环境下 NVM 安装与 Node.js 版本管理完全指南
  • 如何用嘎嘎降AI处理自动化论文:自动化毕业论文降AI免费完整操作教程 - 还在做实验的师兄
  • 2026年10款论文降AIGC网站横评:从90%降至10%的靠谱之选
  • Kotlin 跨平台 SqliteNow 全平台数据持久化方案