本案例演示了如何评估 LlamaIndex 中不同转换(Transform)组件的质量,包括文档解析器和元数据提取器。通过系统性的评估,帮助开发者选择最适合其应用场景的数据处理策略。
1. 案例目标
本案例的主要目标是:
- 评估不同文档解析器:测试不同的句子分割策略(特别是重叠设置)对检索增强生成(RAG)系统性能的影响。
- 评估不同元数据提取器:比较各种元数据提取方法(如摘要提取、问答对提取)对系统回答质量的影响。
- 建立评估框架:提供一套完整的评估流程,包括数据集生成、评估指标定义和结果比较。
- 优化数据处理管道:通过实验结果指导如何构建高效的文档摄取管道(IngestionPipeline)。
2. 技术栈与核心依赖
- LlamaIndex:核心框架,用于构建RAG系统和数据处理管道
- OpenAI API:用于语言模型和嵌入模型
- Pandas:用于数据处理和结果展示
- Jupyter Notebook:用于交互式实验和结果可视化
核心依赖包
# 核心LlamaIndex包 !pip install llama-index # 文件读取器 %pip install llama-index-readers-file # OpenAI集成 %pip install llama-index-llms-openai %pip install llama-index-embeddings-openai3. 环境配置
本案例需要配置OpenAI API密钥,可以通过环境变量设置:
import os os.environ["OPENAI_API_KEY"] = "your-api-key-here"同时,需要设置Pandas显示选项,以便完整查看评估结果:
import pandas as pd pd.set_option("display.max_rows", None) pd.set_option("display.max_columns", None) pd.set_option("display.width", None) pd.set_option("display.max_colwidth", None)4. 案例实现
4.1 数据准备
案例使用特斯拉2020年和2021年的10-K财务报告作为测试数据:
# 下载特斯拉财务报告 !wget "https://www.dropbox.com/scl/fi/mlaymdy1ni1ovyeykhhuk/tesla_2021_10k.htm?rlkey=qf9k4zn0ejrbm716j0gg7r802&dl=1" -O tesla_2021_10k.htm !wget "https://www.dropbox.com/scl/fi/rkw0u959yb4w8vlzz76sa/tesla_2020_10k.htm?rlkey=tfkdshswpoupav5tqigwz1mp7&dl=1" -O tesla_2020_10k.htm # 使用FlatReader加载文档 from llama_index.readers.file import FlatReader from pathlib import Path reader = FlatReader() docs = reader.load_data(Path("./tesla_2020_10k.htm"))4.2 评估数据集生成
为了评估不同转换策略的效果,首先需要生成一个"黄金"评估数据集:
from llama_index.core.evaluation import DatasetGenerator, QueryResponseDataset from llama_index.llms.openai import OpenAI from llama_index.embeddings.openai import OpenAIEmbedding from llama_index.readers.file import FlatReader from llama_index.core.node_parser import HTMLNodeParser, SentenceSplitter from llama_index.core.ingestion import IngestionPipeline from pathlib import Path import nest_asyncio nest_asyncio.apply() # 创建摄取管道 reader = FlatReader() docs = reader.load_data(Path("./tesla_2020_10k.htm")) pipeline = IngestionPipeline( documents=docs, transformations=[ HTMLNodeParser.from_defaults(), SentenceSplitter(chunk_size=1024, chunk_overlap=200), OpenAIEmbedding(), ], ) eval_nodes = pipeline.run(documents=docs) # 生成评估数据集 eval_llm = OpenAI(model="gpt-3.5-turbo") dataset_generator = DatasetGenerator( eval_nodes[:100], llm=eval_llm, show_progress=True, num_questions_per_chunk=3, ) # 生成100个问答对 eval_dataset = await dataset_generator.agenerate_dataset_from_nodes(num=100) eval_dataset.save_json("data/tesla10k_eval_dataset.json")4.3 评估器设置
定义评估指标和评估器:
from llama_index.core.evaluation import ( CorrectnessEvaluator, SemanticSimilarityEvaluator, ) from llama_index.core.evaluation.eval_utils import ( get_responses, get_results_df, ) from llama_index.core.evaluation import BatchEvalRunner # 创建评估器 evaluator_c = CorrectnessEvaluator(llm=eval_llm) evaluator_s = SemanticSimilarityEvaluator(llm=eval_llm) evaluator_dict = { "correctness": evaluator_c, "semantic_similarity": evaluator_s, } batch_eval_runner = BatchEvalRunner( evaluator_dict, workers=2, show_progress=True )4.4 评估函数定义
定义一个通用的评估函数,用于测试不同的管道配置:
from llama_index.core import VectorStoreIndex async def run_evals( pipeline, batch_eval_runner, docs, eval_qs, eval_responses_ref ): # 获取查询引擎 nodes = pipeline.run(documents=docs) # 定义向量索引 (top-k = 2) vector_index = VectorStoreIndex(nodes) query_engine = vector_index.as_query_engine() pred_responses = get_responses(eval_qs, query_engine, show_progress=True) eval_results = await batch_eval_runner.aevaluate_responses( eval_qs, responses=pred_responses, reference=eval_responses_ref ) return eval_results4.5 实验1:不同句子分割策略评估
测试不同重叠设置对性能的影响:
from llama_index.core.node_parser import HTMLNodeParser, SentenceSplitter # 定义不同重叠设置的句子分割器 sent_parser_o0 = SentenceSplitter(chunk_size=1024, chunk_overlap=0) sent_parser_o200 = SentenceSplitter(chunk_size=1024, chunk_overlap=200) sent_parser_o500 = SentenceSplitter(chunk_size=1024, chunk_overlap=600) html_parser = HTMLNodeParser.from_defaults() parser_dict = { "sent_parser_o0": sent_parser_o0, "sent_parser_o200": sent_parser_o200, "sent_parser_o500": sent_parser_o500, } # 为每个解析器创建管道 from llama_index.embeddings.openai import OpenAIEmbedding from llama_index.core.ingestion import IngestionPipeline pipeline_dict = {} for k, parser in parser_dict.items(): pipeline = IngestionPipeline( documents=docs, transformations=[ html_parser, parser, OpenAIEmbedding(), ], ) pipeline_dict[k] = pipeline # 运行评估 eval_results_dict = {} for k, pipeline in pipeline_dict.items(): eval_results = await run_evals( pipeline, batch_eval_runner, docs, eval_qs, ref_response_strs ) eval_results_dict[k] = eval_results4.6 实验2:不同元数据提取器评估
测试不同元数据提取器对性能的影响:
from llama_index.core.extractors import ( TitleExtractor, QuestionsAnsweredExtractor, SummaryExtractor, ) # 定义不同的提取器 extractor_dict = { "summary": SummaryExtractor(in_place=False), "qa": QuestionsAnsweredExtractor(in_place=False), "default": None, } # 固定解析器设置 html_parser = HTMLNodeParser.from_defaults() sent_parser_o200 = SentenceSplitter(chunk_size=1024, chunk_overlap=200) # 为每个提取器创建管道 pipeline_dict = {} for k, extractor in extractor_dict.items(): if k == "default": transformations = [ html_parser, sent_parser_o200, OpenAIEmbedding(), ] else: transformations = [ html_parser, sent_parser_o200, extractor, OpenAIEmbedding(), ] pipeline = IngestionPipeline(transformations=transformations) pipeline_dict[k] = pipeline # 运行评估 eval_results_dict_2 = {} for k, pipeline in pipeline_dict.items(): eval_results = await run_evals( pipeline, batch_eval_runner, docs, eval_qs, ref_response_strs ) eval_results_dict_2[k] = eval_results5. 案例效果
5.1 句子分割策略评估结果
不同重叠设置的评估结果如下:
| 配置名称 | 正确性得分 | 语义相似度 |
|---|---|---|
| sent_parser_o0 | 4.310 | 0.972838 |
| sent_parser_o200 | 4.335 | 0.978842 |
| sent_parser_o500 | 4.270 | 0.971759 |
分析:
- 适度的重叠(200个字符)表现最佳,在正确性和语义相似度上都取得了最高分。
- 无重叠(0个字符)的性能居中。
- 过大的重叠(600个字符)反而降低了性能,可能是因为引入了过多冗余信息。
5.2 元数据提取器评估结果
不同元数据提取器的评估结果如下:
| 配置名称 | 正确性得分 | 语义相似度 |
|---|---|---|
| summary | 4.315 | 0.976951 |
| qa | 4.355 | 0.978807 |
| default | 4.305 | 0.978451 |
分析:
- 问答对提取器(qa)在正确性得分上表现最佳,表明生成问答对有助于提高回答质量。
- 在语义相似度方面,三种配置差异不大,但问答对提取器仍然略占优势。
- 摘要提取器(summary)相比默认配置略有提升,但不如问答对提取器明显。
6. 案例实现思路
6.1 整体架构
本案例采用了系统性的实验方法,通过控制变量来评估不同组件的影响:
- 数据准备:使用真实世界的财务文档作为测试数据,确保实验的实用价值。
- 评估数据集生成:利用LLM自动生成问答对,创建标准评估数据集。
- 组件隔离测试:分别测试文档解析器和元数据提取器,避免相互干扰。
- 多维度评估:同时考虑正确性和语义相似度,提供全面的性能评估。
6.2 关键技术点
摄取管道(IngestionPipeline)
LlamaIndex的IngestionPipeline是本案例的核心组件,它允许将多个转换步骤串联起来:
pipeline = IngestionPipeline( documents=docs, transformations=[ HTMLNodeParser.from_defaults(), # HTML解析 SentenceSplitter(chunk_size=1024, chunk_overlap=200), # 文本分块 OpenAIEmbedding(), # 嵌入生成 ], )评估指标
案例使用了两种主要评估指标:
- 正确性评估器(CorrectnessEvaluator):使用LLM评估生成答案与参考答案的正确性。
- 语义相似度评估器(SemanticSimilarityEvaluator):计算生成答案与参考答案的语义相似度。
批量评估运行器(BatchEvalRunner)
使用BatchEvalRunner可以并行运行多个评估器,提高评估效率:
batch_eval_runner = BatchEvalRunner( evaluator_dict, workers=2, show_progress=True )6.3 缓存机制
案例中提到了使用缓存来优化性能,特别是在测试多个提取器时:
# 持久化缓存以便重用 for k, pipeline in pipeline_dict.items(): pipeline.cache.persist(f"./cache/{k}.json")这种缓存机制可以避免重复计算,特别是在处理大型文档集时非常有用。
7. 扩展建议
7.1 实验扩展
- 更多文档类型测试:当前案例主要使用HTML文档,可以扩展测试PDF、Markdown等不同格式的文档。
- 更多分块策略:测试不同的分块大小和重叠设置,找到最优参数组合。
- 组合提取器:测试同时使用多种提取器的效果,如摘要+问答对组合。
- 不同嵌入模型:评估不同嵌入模型(如OpenAI、Cohere、本地模型)对性能的影响。
7.2 评估方法扩展
- 人工评估:结合人工评估,提供更准确的质量判断。
- 领域特定评估:针对特定领域(如法律、医疗)设计专门的评估指标。
- 端到端评估:不仅评估检索质量,还评估最终用户体验。
- 成本效益分析:评估不同配置下的API调用成本和性能平衡。
7.3 系统优化
- 自适应分块:根据文档内容动态调整分块策略。
- 智能提取器选择:根据文档类型自动选择最适合的提取器。
- 增量评估:支持对新增数据的增量评估,避免全量重新计算。
- 分布式处理:对于大规模数据集,实现分布式评估处理。
8. 总结
本案例展示了如何系统性地评估LlamaIndex中不同转换组件的性能,为构建高效的RAG系统提供了数据支持。通过实验,我们得出以下关键结论:
- 适度的文本重叠:在文档分块时,适度的重叠(如200个字符)可以提高检索和回答质量,但过多的重叠可能引入噪声。
- 元数据提取的价值:适当的元数据提取(特别是问答对提取)可以显著提高系统回答的正确性。
- 评估框架的重要性:建立系统的评估框架对于优化RAG系统至关重要,可以帮助开发者做出数据驱动的决策。
- 平衡性能与成本:在选择组件配置时,需要平衡性能提升与API调用成本,找到最适合应用场景的方案。
本案例提供的评估方法和框架可以广泛应用于各种RAG系统的优化,帮助开发者构建更高效、更准确的信息检索和问答系统。