1. 从数据孤岛到智能应用:为什么我们需要LlamaIndex?
如果你正在构建基于大语言模型(LLM)的应用,无论是企业内部的知识库问答、智能客服,还是个人文档助手,几乎都会遇到一个核心矛盾:LLM强大的通用知识生成能力,与你手中那些非结构化、分散的私有数据之间,存在着一道难以逾越的鸿沟。这些数据可能是堆积如山的PDF报告、散落在各处的Word文档、公司内部的数据库,或是API返回的JSON流。LLM本身并不“知道”这些信息,而直接将这些原始数据“喂”给LLM,不仅效率低下、成本高昂,还常常因为上下文长度限制和无关信息干扰,导致回答质量堪忧。
这就是LlamaIndex(原GPT Index)诞生的背景。它不是一个简单的“包装器”,而是一个专为LLM应用设计的数据框架。你可以把它想象成一个超级智能的“图书管理员”兼“研究员”。它的核心工作流程是:首先,帮你从各种数据源(本地文件、网络、数据库等)中“采集”信息;然后,用一种LLM能够高效理解和检索的方式(如向量索引、知识图谱)来“整理编目”这些信息;最后,当用户提出问题时,它能从海量资料中精准定位最相关的片段,并组织成一份精炼的“参考资料”提交给LLM,从而让LLM生成准确、有据可依的回答。
简单来说,LlamaIndex解决了LLM应用落地的“最后一公里”问题——如何让LLM安全、可靠、高效地利用你的私有数据。它既提供了“开箱即用”的高级API,让新手在5行代码内搭建起一个可用的检索增强生成(RAG)系统;也提供了高度模块化和可扩展的低级API,让资深开发者可以深度定制数据连接器、索引策略、检索器和查询引擎的每一个环节,以满足复杂的企业级需求。
2. 核心架构与核心概念深度解析
要真正用好LlamaIndex,不能只停留在调用VectorStoreIndex.from_documents的层面。理解其核心架构和设计哲学,能帮助你在遇到复杂场景时游刃有余。整个框架可以抽象为一条清晰的数据处理与查询流水线。
2.1 核心模块拆解
1. 数据连接器(Data Connectors / Readers)这是数据进入LlamaIndex世界的入口。框架内置了数十种连接器,覆盖了最常见的格式:
- 文件类:
SimpleDirectoryReader(目录读取)、PDFReader、DocxReader、MarkdownReader等。 - 数据库类:
DatabaseReader(支持SQLAlchemy)、MongoReader。 - 应用类:
SlackReader、NotionPageReader、GoogleDocsReader。 - 网络类:
BeautifulSoupWebReader、RssReader。
注意:连接器的选择直接影响数据提取的质量。例如,对于复杂的PDF(包含表格、图表),默认的PDF解析器可能效果不佳,此时就需要考虑使用更强大的解析服务,如后面会提到的LlamaParse。
2. 文档与节点(Documents & Nodes)从连接器读取的原始数据,首先被封装成Document对象。一个Document代表一份完整的文档,包含文本内容和元数据(如来源、作者)。但直接处理整篇文档效率太低,因此LlamaIndex引入了Node(节点)的概念。
- 节点:是文档经过“分块”后得到的基本语义单元。分块策略(Chunking)至关重要,它决定了检索的粒度。分块过大,可能包含无关信息,稀释关键内容;分块过小,可能丢失上下文,导致语义不完整。LlamaIndex提供了
TokenTextSplitter、SentenceSplitter等多种分块器,也允许你自定义。 - 节点关系:除了文本内容,节点之间还可以建立关系(如前驱、后继、父子),这是构建更复杂索引(如知识图谱)的基础。
3. 索引(Indices)这是LlamaIndex的“大脑”,负责以某种数据结构组织节点,以便快速检索。最常见的索引是VectorStoreIndex(向量存储索引),它通过嵌入模型(Embedding Model)将每个节点的文本转换为高维向量,并存储在向量数据库(如Chroma、Pinecone、Weaviate)中。检索时,将查询问题也转换为向量,通过计算余弦相似度找到最相关的节点。
- 其他索引类型:
SummaryIndex:为每个文档生成摘要,适合快速概览。TreeIndex:以树形结构组织节点,支持从粗到细的层层递进查询。KeywordTableIndex:基于关键词的倒排索引,适合精确术语匹配。KnowledgeGraphIndex:提取实体和关系构建图谱,适合进行复杂的关联推理。
4. 检索器(Retrievers)给定一个索引,检索器定义了“如何获取相关节点”的策略。例如,对于VectorStoreIndex,其对应的VectorIndexRetriever会执行向量相似度搜索。你可以配置similarity_top_k参数来控制返回节点的数量。更高级的检索器还包括:
BM25Retriever:基于传统的关键词匹配算法,与向量检索结合(混合检索)效果往往更好。AutoMergingRetriever:自动合并相邻的小节点,返回更完整的上下文。
5. 查询引擎(Query Engines)检索器只负责“找材料”,查询引擎则负责“组织答案”。它接收用户查询,调用检索器获取相关上下文,然后将“查询+上下文”组合成一个提示(Prompt),发送给LLM生成最终答案。这是RAG流程的最终组装环节。
- 你可以为查询引擎配置不同的响应模式(Response Modes),如
compact(压缩上下文以节省token)、refine(迭代优化答案)、tree_summarize(基于树索引的总结)等。
6. 智能体(Agents)这是LlamaIndex向更高阶应用迈进的体现。智能体不再局限于单次问答,而是具备使用工具(Tools)、记忆(Memory)和规划(Planning)能力的自主系统。例如,一个文档分析智能体可以按顺序执行以下工具:parse_tool(解析文档)、summary_tool(生成摘要)、qa_tool(回答具体问题)。LlamaIndex的ReActAgent、OpenAIAgent等实现了与LLM的交互,让应用能够处理多步骤的复杂任务。
2.2 全局设置(Settings)的妙用
在最新版本的LlamaIndex中,Settings是一个全局配置对象,极大地简化了LLM、嵌入模型等组件的管理。如示例所示:
from llama_index.core import Settings from llama_index.llms.openai import OpenAI from llama_index.embeddings.openai import OpenAIEmbedding Settings.llm = OpenAI(model=“gpt-4”, temperature=0.1) Settings.embed_model = OpenAIEmbedding(model=“text-embedding-3-small”)一旦这样设置,后续创建索引、查询引擎时,如果没有显式指定,都会自动使用这些全局配置。这在开发中非常方便,尤其是在切换不同模型进行测试时,只需修改一处。
实操心得:虽然
Settings很方便,但在生产环境的微服务中,我建议谨慎使用全局状态。更好的做法是在每个应用实例或请求上下文中显式地创建和传递LLM、Embedding实例,这能避免潜在的线程安全问题,并使依赖关系更清晰。
3. 从零到一:构建你的第一个生产级RAG应用
让我们超越“Hello World”示例,构建一个更健壮、更实用的个人知识库助手。假设我们要处理一个包含多格式文档(PDF、MD、TXT)的文件夹,并提供一个命令行问答界面。
3.1 环境准备与依赖安装
首先,我们采用“核心+定制集成”的安装方式,这比安装完整的llama-index包更轻量,且允许我们只引入需要的组件。
# 安装核心框架 pip install llama-index-core # 安装我们需要的集成包 # 使用OpenAI的LLM和Embedding pip install llama-index-llms-openai pip install llama-index-embeddings-openai # 使用本地向量数据库Chroma(轻量,无需外部服务) pip install llama-index-vector-stores-chroma # 可能需要用到的文本分割器 pip install llama-index-text-splitters # 安装ChromaDB客户端 pip install chromadb3.2 进阶数据加载与预处理
SimpleDirectoryReader是入门利器,但对于生产环境,我们需要更多控制。
import os from llama_index.core import SimpleDirectoryReader from llama_index.core.node_parser import SentenceSplitter from llama_index.core.schema import MetadataMode # 1. 配置数据读取 documents = SimpleDirectoryReader( input_dir=“./your_data_directory”, required_exts=[“.pdf”, “.md”, “.txt”], # 指定文件类型 recursive=True, # 递归读取子目录 filename_as_id=True, # 用文件名作为文档ID的一部分 ).load_data() # 2. 高级文本分割与元数据处理 text_splitter = SentenceSplitter( chunk_size=1024, # 每个块的目标token数(约等于字符数/4) chunk_overlap=200, # 块之间的重叠token数,避免语义割裂 separator=“ “, # 分割符 ) # 为每个文档添加自定义元数据,这对后续检索过滤很有用 for doc in documents: file_path = doc.metadata.get(“file_path”, “”) doc.metadata[“category”] = “technical_docs” # 自定义分类 doc.metadata[“year”] = “2023” # 确保元数据也被用于嵌入生成(如果需要的话) # 默认情况下,只有文本内容被嵌入。你可以通过重写`get_content`方法改变这一点。 nodes = text_splitter.get_nodes_from_documents(documents) print(f“将 {len(documents)} 个文档分割成了 {len(nodes)} 个节点。”)注意事项:
chunk_size的选择是艺术也是科学。对于通用文档,512-1024是一个不错的起点。对于代码或结构化文本,可以更小。重叠(chunk_overlap)通常设置为chunk_size的10%-20%,它能有效防止一个完整的句子或概念被硬生生切开,在边界处保留上下文。
3.3 配置LLM与嵌入模型
我们将使用OpenAI的API,但会配置更合理的参数以平衡成本与效果。
from llama_index.llms.openai import OpenAI from llama_index.embeddings.openai import OpenAIEmbedding from llama_index.core import Settings # 配置LLM Settings.llm = OpenAI( model=“gpt-3.5-turbo”, # 对于大多数RAG任务,3.5-turbo性价比极高 temperature=0.1, # 低温度保证答案的确定性和一致性,适合事实性问答 max_tokens=1500, # 限制单次回答长度 timeout=60, # 设置超时,避免长时间等待 # api_key=os.environ[“OPENAI_API_KEY”], # 最佳实践是从环境变量读取 ) # 配置嵌入模型 Settings.embed_model = OpenAIEmbedding( model=“text-embedding-3-small”, # 性能接近ada-002,但更便宜 embed_batch_size=100, # 批量处理提高效率 ) # 如果你有大量数据,可以考虑使用异步客户端 # from llama_index.llms.openai import AsyncOpenAI # Settings.llm = AsyncOpenAI(...)3.4 构建并持久化向量索引
这里我们使用ChromaDB作为向量存储,它将数据持久化在本地。
import chromadb from llama_index.core import StorageContext, VectorStoreIndex from llama_index.vector_stores.chroma import ChromaVectorStore # 1. 初始化Chroma客户端,指定持久化路径 chroma_client = chromadb.PersistentClient(path=“./chroma_db”) # 创建一个集合(类似数据库的表) chroma_collection = chroma_client.get_or_create_collection(“my_knowledge_base”) # 2. 创建LlamaIndex的向量存储包装器 vector_store = ChromaVectorStore(chroma_collection=chroma_collection) # 3. 创建存储上下文,关联向量存储 storage_context = StorageContext.from_defaults(vector_store=vector_store) # 4. 从节点创建索引,并指定存储上下文 index = VectorStoreIndex( nodes=nodes, storage_context=storage_context, show_progress=True # 显示构建进度条 ) # 5. 索引构建完成后,存储上下文会自动将向量数据写入ChromaDB。 # 我们也可以显式持久化索引的元数据(非向量部分)。 index.storage_context.persist(persist_dir=“./storage”) print(“索引构建并持久化完成。”)3.5 实现高级查询功能
一个基础的query_engine.query()可能不够用。我们来实现一个支持多种检索模式、并包含后处理(重排序)的查询引擎。
from llama_index.core import VectorStoreIndex, get_response_synthesizer from llama_index.core.retrievers import VectorIndexRetriever from llama_index.core.postprocessor import SimilarityPostprocessor, LLMRerank from llama_index.core.query_engine import RetrieverQueryEngine # 1. 从持久化的存储中加载索引(如果是第二次运行) # storage_context = StorageContext.from_defaults(persist_dir=“./storage”) # index = load_index_from_storage(storage_context) # 2. 配置检索器 retriever = VectorIndexRetriever( index=index, similarity_top_k=10, # 初步检索出10个相关节点 ) # 3. 配置响应合成器 response_synthesizer = get_response_synthesizer( response_mode=“compact”, # 压缩模式,会尽量将相关上下文填满LLM的上下文窗口 structured_answer_filtering=True, # 尝试从LLM输出中提取结构化答案 ) # 4. 配置后处理器(非常重要!) # a) 相似度过滤:过滤掉相似度分数太低的节点 similarity_postprocessor = SimilarityPostprocessor(similarity_cutoff=0.7) # b) LLM重排序:让LLM对初步检索结果进行精排,选出最相关的几个。效果显著,但会增加延迟和成本。 llm_rerank = LLMRerank(choice_batch_size=5, top_n=3) # 5. 组装查询引擎 query_engine = RetrieverQueryEngine( retriever=retriever, response_synthesizer=response_synthesizer, node_postprocessors=[similarity_postprocessor, llm_rerank] # 按顺序应用后处理器 ) # 6. 进行查询 response = query_engine.query(“LlamaIndex中如何对PDF文件进行分块?”) print(response) # 访问源节点 for i, source_node in enumerate(response.source_nodes): print(f“\n--- 来源 {i+1} (相似度分数: {source_node.score:.3f}) ---”) print(f“文件: {source_node.metadata.get(‘file_name’, ‘N/A’)}”) print(f“内容片段: {source_node.text[:500]}…”) # 打印前500字符核心技巧:
LLMRerank是提升RAG答案准确性的“神器”。它的原理是让LLM对初步检索到的多个节点进行判断:“哪一个与问题最相关?”。虽然它引入了额外的LLM调用,但通过将top_n设小(如3),并将重排序问题设计得非常简单,其带来的精度提升通常远高于其成本。对于关键任务,强烈建议启用。
4. 超越基础:应对复杂场景与性能优化
当你的应用从原型走向生产,会遇到更多挑战。下面分享几个关键场景的解决方案。
4.1 处理复杂文档解析:集成LlamaParse
对于扫描版PDF、表格密集的报表、或格式混乱的文档,默认解析器可能力不从心。LlamaIndex官方提供了企业级解析平台LlamaParse,它基于AI代理进行OCR和文档理解,效果远超普通工具。
# 首先,需要注册LlamaCloud获取API Key: https://cloud.llamaindex.ai import os from llama_index.core import SimpleDirectoryReader from llama_parse import LlamaParse os.environ[“LLAMA_CLOUD_API_KEY”] = “your_api_key” # 使用LlamaParse作为解析器 parser = LlamaParse(result_type=“markdown”) # 结果可以是markdown或text documents = SimpleDirectoryReader( input_files=[“./complex_report.pdf”], file_extractor={“.pdf”: parser} # 指定.pdf文件用LlamaParse处理 ).load_data() # 得到的documents已经是高质量解析后的文本,包含表格、列表等结构信息。4.2 实现多索引与路由
如果你的知识库包含不同类型的内容(如产品手册、技术博客、客服对话),为每种类型建立独立的索引,并让一个“路由查询引擎”根据问题决定查询哪个索引,效果会更好。
from llama_index.core import VectorStoreIndex, SummaryIndex from llama_index.core.tools import QueryEngineTool from llama_index.core.query_engine import RouterQueryEngine from llama_index.core.selectors import LLMSingleSelector # 假设我们已经构建了两个索引 product_index = VectorStoreIndex.from_documents(product_docs) blog_index = SummaryIndex.from_documents(blog_docs) # 摘要索引适合概览性查询 # 为每个索引创建查询引擎,并包装成“工具” product_tool = QueryEngineTool.from_defaults( query_engine=product_index.as_query_engine(), description=“适用于查询具体产品功能、规格、使用方法的文档。” ) blog_tool = QueryEngineTool.from_defaults( query_engine=blog_index.as_query_engine(), description=“适用于查询技术概念、架构解析、最佳实践等博客文章。” ) # 创建路由查询引擎,它会用LLM根据问题描述选择最合适的工具 router_query_engine = RouterQueryEngine( selector=LLMSingleSelector.from_defaults(), # 使用LLM进行选择 query_engine_tools=[product_tool, blog_tool] ) # 现在,问一个产品相关问题,它会自动路由到product_tool response = router_query_engine.query(“Model X的最大续航里程是多少?”)4.3 构建具备记忆与工具使用能力的智能体
这是将静态问答系统升级为动态助手的关键。智能体可以记住对话历史,并调用外部工具(如计算器、搜索引擎、数据库查询)。
from llama_index.core.agent import ReActAgent from llama_index.core.tools import FunctionTool import requests # 1. 定义自定义工具(例如,一个获取天气的工具) def get_weather(city: str) -> str: “”“获取指定城市的当前天气。Args: city (str): 城市名。”“” # 这里简化处理,实际应调用天气API weather_map = {“北京”: “晴,25°C”, “上海”: “多云,28°C”} return weather_map.get(city, f“未找到{city}的天气信息。”) weather_tool = FunctionTool.from_defaults(fn=get_weather) # 2. 将之前的查询引擎也包装成工具 qa_tool = QueryEngineTool.from_defaults( query_engine=query_engine, # 使用我们之前构建的RAG查询引擎 description=“用于回答基于公司内部知识库的问题。” ) # 3. 创建智能体,并赋予它工具和记忆 agent = ReActAgent.from_tools( tools=[weather_tool, qa_tool], llm=Settings.llm, verbose=True, # 打印出智能体的思考过程,便于调试 max_iterations=10, # 限制最大推理步骤,防止死循环 ) # 4. 与智能体聊天 response = agent.chat(“北京今天天气怎么样?”) # 它会调用get_weather工具 print(response) response = agent.chat(“根据知识库,我们项目的主要技术栈是什么?”) # 它会调用qa_tool print(response) response = agent.chat(“我上个问题问的是什么?”) # 它能记住对话历史! print(response)4.4 性能优化与成本控制
1. 嵌入缓存为相同的文本重复计算嵌入向量是巨大的浪费。LlamaIndex支持本地缓存(如SQLite)或远程缓存(如Redis)。
from llama_index.core import Settings from llama_index.embeddings.openai import OpenAIEmbedding from llama_index.embeddings.cache import CacheRetriever import sqlite3 # 使用SQLite进行本地嵌入缓存 embed_model = OpenAIEmbedding() # 创建一个缓存检索器包装原始的嵌入模型 cached_embed_model = CacheRetriever( embed_model, cache=SQLiteCache(sqlite3.connect(“./embed_cache.db”)), ) Settings.embed_model = cached_embed_model首次运行时,嵌入向量会被计算并存入数据库。后续遇到相同文本,直接读取缓存,能显著降低API调用成本和延迟。
2. 异步处理对于批量文档处理或高并发查询,使用异步接口可以大幅提升吞吐量。
import asyncio from llama_index.core.async_utils import run_async_tasks from llama_index.core import VectorStoreIndex from llama_index.llms.openai import AsyncOpenAI async def async_index_documents(docs): Settings.llm = AsyncOpenAI(...) Settings.embed_model = ... # 也需要支持异步的Embedding模型 index = await VectorStoreIndex.afrom_documents(docs) # 注意是异步方法 return index # 在主程序中运行 index = asyncio.run(async_index_documents(documents))3. 成本监控使用OpenAI等付费API时,监控token消耗至关重要。你可以在调用LLM和Embedding时,通过回调函数记录消耗。
from llama_index.core.callbacks import CallbackManager, TokenCountingHandler import tiktoken token_counter = TokenCountingHandler( tokenizer=tiktoken.encoding_for_model(“gpt-3.5-turbo”).encode ) Settings.callback_manager = CallbackManager([token_counter]) # ... 执行查询操作后 ... print(f“本次查询消耗的Prompt Tokens: {token_counter.prompt_llm_token_count}”) print(f“本次查询消耗的Completion Tokens: {token_counter.completion_llm_token_count}”) print(f“本次查询消耗的Embedding Tokens: {token_counter.total_embedding_token_count}”) token_counter.reset_counts() # 重置计数器5. 常见问题排查与实战避坑指南
在实际开发中,你一定会遇到各种问题。下面是我踩过坑后总结出的经验。
5.1 检索效果不佳
症状:LLM的回答与问题无关,或未能从提供的上下文中找到正确答案。
- 可能原因1:分块策略不当。
- 排查:检查检索到的源节点文本。它们是否完整包含了答案?还是答案被切分到了两个块里?
- 解决:调整
chunk_size和chunk_overlap。尝试按句子、段落或固定标记数分割。对于技术文档,按章节标题分割可能更有效。
- 可能原因2:嵌入模型不匹配。
- 排查:用于构建索引的嵌入模型和当前查询时使用的嵌入模型是否一致?如果不一致,向量空间将对不上。
- 解决:确保
Settings.embed_model在索引构建和查询时是同一个实例或相同配置。将嵌入模型名称和参数记录在配置文件中。
- 可能原因3:检索到的节点数量不足或过多。
- 排查:检查
similarity_top_k的值。太小可能漏掉相关文档,太大可能引入噪声。 - 解决:尝试不同的k值(如5, 10, 20),并结合
SimilarityPostprocessor设置一个相似度阈值(如0.75)进行过滤。启用LLMRerank是提升精度的最有效手段之一。
- 排查:检查
- 可能原因4:查询问题表述不清。
- 解决:实现“查询重写”(Query Rewriting)。在检索前,先用一个简单的LLM调用将用户的口语化问题改写成更贴近文档表述的关键词组合。例如,将“咋装这个软件?”重写为“安装步骤 配置要求”。
5.2 回答出现“幻觉”(Hallucination)
症状:LLM的答案听起来合理,但细看发现是编造的,上下文中并无依据。
- 根本原因:LLM的生成能力太强,当检索到的上下文不相关或信息不足时,它会倾向于“脑补”。
- 解决:
- 强化提示工程:在发送给LLM的提示词中,明确指令“严格基于提供的上下文信息回答问题。如果上下文没有提供足够信息,请直接说‘根据已知信息无法回答该问题’,不要编造信息。”
- 引用溯源:要求LLM在答案中引用来源节点的ID或片段。这不仅能验证答案,也增加了可信度。可以通过定制
response_synthesizer的提示模板实现。 - 设置置信度阈值:如果所有检索到的节点相似度分数都低于某个阈值(如0.6),则直接返回“未找到相关信息”,不调用LLM。
5.3 处理速度慢
症状:索引构建或查询响应时间过长。
- 索引构建慢:
- 批量处理嵌入:确保
embed_batch_size设置合理(OpenAI建议最大2048)。对于数万文档,考虑使用异步并行处理。 - 使用本地嵌入模型:对于对延迟敏感或数据敏感的场景,可以换用本地部署的嵌入模型,如
HuggingFaceEmbedding(选用BAAI/bge-small-zh-v1.5等轻量级模型)。
- 批量处理嵌入:确保
- 查询响应慢:
- 向量数据库优化:如果使用本地Chroma,确保有足够内存。对于超大规模数据(百万级以上),考虑使用专业的向量数据库如Pinecone、Weaviate或Milvus,它们支持索引优化和近似最近邻搜索。
- 精简上下文:使用
response_mode=“compact”或“tree_summarize”,它们会尝试压缩上下文,减少发送给LLM的token数,从而降低生成时间。 - 缓存查询结果:对于常见、重复的问题,可以在应用层实现查询结果的缓存(如使用
functools.lru_cache)。
5.4 部署与运维问题
- 版本兼容性:LlamaIndex更新较快,注意核心包(
llama-index-core)与各集成包(llama-index-llms-openai等)的版本兼容性。建议在requirements.txt中锁定主要版本号。 - 环境变量管理:API密钥等敏感信息切勿硬编码在代码中。使用
.env文件配合python-dotenv,或使用容器/云服务的秘密管理功能。 - 持久化与恢复:确保向量数据库(如Chroma的
persist_directory)和LlamaIndex的元数据存储路径(persist_dir)在容器化部署时被挂载到持久化卷上,否则重启后数据会丢失。 - 监控与日志:为关键操作(数据加载、索引构建、查询)添加详细的日志记录。监控LLM API的调用成功率、延迟和token消耗,设置告警。
5.5 一个真实的调试案例:表格数据检索失败
我曾遇到一个需求:从包含大量数据表格的PDF年报中查询具体财务数字。使用默认流程,检索到的文本片段总是丢失表格结构,导致LLM无法正确解读。
排查过程:
- 首先检查原始解析文本,发现默认PDF解析器将表格转换成了杂乱无章的行,失去了行列关系。
- 尝试换用
LlamaParse,并指定result_type=“markdown”。解析后,表格被很好地转换成了Markdown表格语法。 - 但检索时发现,向量搜索基于语义相似度,而像“2023年Q4净利润”这样的查询,其语义与表格中具体的数字单元格(如“1.23亿”)并不直接相似。
- 解决方案:采用混合检索策略。
- 路径A(语义检索):对Markdown文本(包含表格描述性文字)建立向量索引。
- 路径B(关键词检索):同时,使用
KeywordTableIndex或BM25Retriever对纯文本(包含数字和特定术语)建立关键词索引。 - 在查询时,同时使用两种检索器获取结果,然后通过
ReciprocalRerankFusion或自定义逻辑对结果进行融合重排。这样,既保证了语义理解,又不会错过关键的数字和术语。
最终,通过结合更强大的解析工具(LlamaParse)和混合检索策略,成功解决了表格数据查询的难题。这个案例告诉我们,面对复杂的数据格式和查询需求,往往需要组合多种工具和索引策略,没有一成不变的银弹。