【RAG】【retrievers11】递归检索器 + 节点引用 + Braintrust评估
案例目标本案例展示如何使用递归检索(Recursive Retrieval)遍历节点关系并基于引用获取节点。节点引用是一个强大的概念在初次检索时您可能希望获取引用而非原始文本。多个引用可以指向同一个节点。案例探索了节点引用的不同用法分块引用不同大小的分块引用更大的分块元数据引用摘要和生成的问题引用更大的分块通过Braintrust评估系统我们量化了递归检索节点引用方法的效果证明这种方法相比传统检索方法有显著提升。技术栈与核心依赖llama-index-llms-openaillama-index-readers-filellama-index-corebraintrustautoevalspypdftransformerstorch环境配置# 安装必要的依赖pip install llama-index-llms-openai llama-index-readers-filepip install -U llama_hub llama_index braintrust autoevals pypdf pillow transformers torch torchvision# 设置API密钥import osos.environ[OPENAI_API_KEY] your_openai_api_keyos.environ[BRAINTRUST_API_KEY] your_braintrust_api_keyos.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.pdffrom pathlib import Pathfrom llama_index.readers.file import PDFReaderloader PDFReader()docs0 loader.load_data(filePath(./data/llama2.pdf))# 合并文档内容from llama_index.core import Documentdoc_text \\n\\n.join([d.get_content() for d in docs0])docs [Document(textdoc_text)]2. 创建基础节点步骤 2创建基础节点分块大小1024from llama_index.core.node_parser import SentenceSplitterfrom llama_index.core.schema import IndexNode# 创建文本分割器node_parser SentenceSplitter(chunk_size1024)# 获取节点base_nodes node_parser.get_nodes_from_documents(docs)# 设置节点IDfor idx, node in enumerate(base_nodes):node.id_ fnode-{idx}3. 基线检索器步骤 3创建基线检索器通过嵌入相似度获取top-k原始文本节点from llama_index.core import VectorStoreIndexfrom llama_index.core.embeddings import resolve_embed_modelfrom llama_index.llms.openai import OpenAI# 设置嵌入模型和LLMembed_model resolve_embed_model(local:BAAI/bge-small-en)llm OpenAI(modelgpt-3.5-turbo)# 创建向量索引和检索器base_index VectorStoreIndex(base_nodes, embed_modelembed_model)base_retriever base_index.as_retriever(similarity_top_k2)4. 分块引用小子块引用大父块步骤 4构建小子块指向大父块的图结构from llama_index.core.retrievers import RecursiveRetriever# 定义子块大小sub_chunk_sizes [128, 256, 512]sub_node_parsers [SentenceSplitter(chunk_sizec) 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_modelembed_model)vector_retriever_chunk vector_index_chunk.as_retriever(similarity_top_k2)# 创建递归检索器retriever_chunk RecursiveRetriever(vector,retriever_dict{vector: vector_retriever_chunk},node_dictall_nodes_dict,verboseTrue,)5. 元数据引用摘要和生成的问题引用更大的块步骤 6提取元数据摘要和问题并创建引用from llama_index.core.extractors import (SummaryExtractor,QuestionsAnsweredExtractor,)# 创建提取器extractors [SummaryExtractor(summaries[self], show_progressTrue),QuestionsAnsweredExtractor(questions5, show_progressTrue),]# 运行元数据提取器metadata_dicts []for extractor in extractors:metadata_dicts.extend(extractor.extract(base_nodes))步骤 7保存和加载元数据import jsonimport 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(textd[questions_this_excerpt_can_answer],index_idbase_nodes[idx].node_id,)inode_s IndexNode(textd[section_summary],index_idbase_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_k2)# 创建递归检索器retriever_metadata RecursiveRetriever(vector,retriever_dict{vector: vector_retriever_metadata},node_dictall_nodes_dict,verboseTrue,)6. 评估设置步骤 9生成评估数据集from llama_index.core.evaluation import (generate_question_context_pairs,EmbeddingQAFinetuneDataset,)import nest_asyncionest_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 pdimport braintrust# 准备数据queries eval_dataset.queriesrelevant_docs eval_dataset.relevant_docsdata [({input: queries[query], expected: relevant_docs[query]})for query in queries.keys()]# 定义评分函数def hitRateScorer(input, expected, outputNone):is_hit any([id in expected for id in output])return 1 if is_hit else 0def mrrScorer(input, expected, outputNone):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_k10)retriever_chunk RecursiveRetriever(vector,retriever_dict{vector: vector_retriever_chunk},node_dictall_nodes_dict,verboseFalse,)# 定义运行函数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(namellamaindex-recurisve-retrievers,datadata,taskrunChunkRetriever,scores[hitRateScorer, mrrScorer],)步骤 12评估元数据检索器# 创建元数据检索器vector_retriever_metadata vector_index_metadata.as_retriever(similarity_top_k10)retriever_metadata RecursiveRetriever(vector,retriever_dict{vector: vector_retriever_metadata},node_dictall_nodes_dict,verboseFalse,)# 定义运行函数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(namellamaindex-recurisve-retrievers,datadata,taskrunMetaDataRetriever,scores[hitRateScorer, mrrScorer],)步骤 13评估基线检索器# 创建基线检索器base_retriever base_index.as_retriever(similarity_top_k10)# 定义运行函数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(namellamaindex-recurisve-retrievers,datadata,taskrunBaseRetriever,scores[hitRateScorer, mrrScorer],)案例效果通过Braintrust评估系统我们比较了三种检索器的性能基线检索器、分块引用递归检索器和元数据引用递归检索器。评估指标包括命中率(hit_rate)和平均倒数排名(MRR)。评估指标说明命中率(Hit Rate)衡量在检索结果中是否包含至少一个相关文档平均倒数排名(MRR)衡量第一个相关文档在检索结果中的位置排名的倒数平均值检索器类型命中率(Hit Rate)平均倒数排名(MRR)基线检索器较低较低分块引用递归检索器中等中等元数据引用递归检索器较高较高评估结果分析评估结果表明使用节点引用无论是分块引用还是元数据引用的检索器性能优于直接获取原始分块的基线检索器。这是因为分块引用允许检索更小的粒度但返回更大的上下文提高了相关性元数据引用通过摘要和问题提供了更丰富的语义信息增强了检索的准确性递归检索机制使得系统能够根据引用关系自动获取更相关的信息案例实现思路递归检索器节点引用的核心思路是通过构建节点间的引用关系实现更精确、更全面的检索节点引用概念将节点分为引用节点和源节点引用节点包含指向源节点的ID分块引用策略创建不同大小的子块每个子块引用更大的父块实现细粒度检索但获取大上下文元数据引用策略提取摘要和生成问题作为引用提供更丰富的语义信息递归检索机制检索时首先获取引用节点然后根据引用关系递归获取源节点评估驱动优化通过Braintrust评估系统量化不同策略的效果指导优化方向这种方法特别适用于需要精确匹配但同时又需要丰富上下文的场景。通过节点引用系统能够在保持检索精度的同时提供更全面的信息从而提高RAG系统的整体性能。扩展建议多级引用构建多级引用关系实现更复杂的检索路径动态引用生成根据查询内容动态生成引用节点引用权重学习通过机器学习学习不同引用类型的权重混合引用策略结合分块引用和元数据引用发挥各自优势引用关系可视化开发可视化工具展示节点引用关系便于调试和优化领域特定引用针对特定领域设计专门的引用策略实时引用更新支持引用关系的实时更新和维护总结递归检索器节点引用是一种强大的检索增强技术通过构建节点间的引用关系实现了更精确、更全面的检索效果。本案例展示了两种主要的引用策略分块引用和元数据引用并通过Braintrust评估系统证明了它们相对于传统检索方法的优势。这种方法的核心价值在于它解决了传统检索中精度和上下文之间的权衡问题。通过节点引用系统能够在保持高精度的同时获取更丰富的上下文信息从而显著提高RAG系统的性能。随着RAG技术的不断发展这种基于引用关系的检索方法将在构建更智能、更全面的信息检索系统中发挥越来越重要的作用。