LlamaIndex 索引(Indexing)模块深入指南:从 Document 到可检索数据结构的完整解析
【免费下载链接】llama_indexLlamaIndex is the document processing platform for AI项目地址: https://gitcode.com/GitHub_Trending/ll/llama_index
LlamaIndex 将Index(索引)定义为一种允许我们快速检索用户查询所需上下文的数据结构,它是检索增强生成(RAG)用例的核心基础。本文以官方 Indexing 模块指南为主线,结合llama-index-core源码实现,系统讲解 Index 的概念与架构、各类索引(Vector Store Index、Summary Index、Tree Index、Keyword Table Index、Property Graph Index)的构建与查询原理,以及索引的增删改刷新、持久化加载和 Query/Chat Engine 装配,帮助你根据真实业务场景做出正确的索引选型与工程落地。
Index 是什么:RAG 检索的核心数据结构
根据官方文档的定义,Index是一种允许我们快速检索用户查询所需上下文的数据结构,对于 LlamaIndex 而言,它是检索增强生成(RAG)用例的核心基础。
在高层次上,索引由 Documents 构建,并被用来搭建 Query Engines 和 Chat Engines,从而实现对数据内容的问答与聊天。
在底层,Index 将数据存储在Node对象中(Node 代表原始文档切分后的文本块),并对外暴露一个支持额外配置与自动化的 Retriever 接口。从源码结构看,llama_index.core.indices.base中的BaseIndex是所有索引类型的抽象基类,它通过模板方法模式统一了"从文档构建索引""插入/删除/更新/刷新节点""转换为 Retriever/Query Engine/Chat Engine"等全套生命周期操作(base.py)。
关键概念链条:Document → Node → Index → Retriever
这一条数据流是理解整个 Indexing 模块的钥匙:
- Document:原始数据载体(如
SimpleDirectoryReader加载的文件)。 - Node:Document 经解析/切分(chunking)后得到的文本块,携带元数据(metadata)与节点间关系。
- Index:对 Nodes 进行组织的数据结构(列表、向量表、树、关键词表、属性图等)。
- Retriever:Index 暴露的检索接口,负责在查询时取回最相关的 Node 集合。
构建索引的两种入口:from_documents与构造函数
BaseIndex.from_documents是文档驱动的标准入口。从源码看,它内部会先将每个文档的哈希写入 docstore(用于后续的 refresh 增量更新判断),再调用run_transformations把 Documents 通过变换管线(默认是文本切分)转换为 Nodes,最后调用索引构造函数完成构建(base.py):
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader documents = SimpleDirectoryReader( "../../examples/data/paul_graham" ).load_data() index = VectorStoreIndex.from_documents(documents)需要特别注意的是,构造函数本身只接受Node 对象列表。源码中有显式校验:如果传入的是Document列表,会直接抛出ValueError,提示"构造函数现在接收 Node 列表,若要传入 Document 请使用from_documents"(base.py)。因此两种入口的适用场景是:
| 入口 | 输入 | 适用场景 |
|---|---|---|
Index.from_documents(documents) | 原始文档列表 | 快速起步,默认切分逻辑 |
Index(nodes) | 手动构造的 Node 列表 | 需要完全控制切分、元数据与节点关系 |
索引类型全景:内置 10 种索引
llama-index-core中的索引注册表INDEX_STRUCT_TYPE_TO_INDEX_CLASS定义了全部内置索引类型(registry.py):
| 索引类型 | 类 | 索引结构特点 |
|---|---|---|
VectorStoreIndex | 向量存储索引 | 为每个 Node 生成 embedding 并存入向量库,按相似度检索 |
SummaryIndex(原 List Index) | 顺序链式列表 | 将 Nodes 存为顺序链 |
TreeIndex | 层次化树 | 从 Nodes 构建层级树(叶子节点),自根向下遍历 |
KeywordTableIndex | 关键词表 | 提取关键词并建立关键词到 Nodes 的映射 |
PropertyGraphIndex | 属性图(LPG) | 构建带标签节点与关系的知识图谱 |
KnowledgeGraphIndex | 知识图谱 | 基于三元组的知识图谱索引 |
SQLStructStoreIndex | SQL 结构化索引 | 面向结构化 SQL 查询 |
PandasIndex | Pandas 数据框索引 | 面向表格数据分析 |
DocumentSummaryIndex | 文档摘要索引 | 为每个文档建立摘要并检索 |
MultiModalVectorStoreIndex | 多模态向量索引 | 同时支持文本与图像节点 |
EmptyIndex | 空索引 | 无存储结构的占位索引 |
其中,VectorStoreIndex是目前使用最广泛的索引,官方文档也明确推荐从 VectorStoreIndex 使用指南 开始学习。更多索引类型的完整清单可参见 modules.md。
VectorStoreIndex 深度实践:最常用的 RAG 索引
向量存储是 RAG 的关键组件,因此你在几乎每个 LlamaIndex 应用中都会直接或间接用到它。向量存储接收一组Node对象并基于它们构建索引。下面按官方指南的脉络,从数据加载、自定义构建到存储与组合检索逐步展开。
方式一:from_documents快速构建
最简单的用法是加载文档集合并通过from_documents构建索引(vector_store_index.mdx):
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader documents = SimpleDirectoryReader( "../../examples/data/paul_graham" ).load_data() index = VectorStoreIndex.from_documents(documents)使用from_documents时,你的 Documents 会被切分并解析为Node对象——它们是文本字符串上的轻量抽象,同时跟踪元数据与节点间关系。
两个实用提示:
- 在命令行中构建索引时,可传入
show_progress=True显示进度条。 - 默认情况下,
VectorStoreIndex会以2048 个节点为一批批量生成并插入向量。如果内存受限(或内存充裕),可以通过insert_batch_size参数调整批大小,这在向远端托管的向量数据库插入时尤其有用(vector_store/base.py)。
从源码看,批量插入逻辑位于_add_nodes_to_index:先用embed_nodes分批计算 embedding,再调用vector_store.add()写入;若向量库本身不存储文本(stores_text=False),还会将去 embedding 后的 Node 同步进 docstore 与 index_struct,保证后续能按ref_doc_id追溯原始文档(vector_store/base.py)。
方式二:使用 Ingestion Pipeline 精细控制
如果想对文档如何被索引拥有更多控制权,官方推荐使用 ingestion pipeline,它允许自定义切分(chunking)、元数据与 embedding(vector_store_index.mdx):
from llama_index.core import Document from llama_index.embeddings.openai import OpenAIEmbedding from llama_index.core.node_parser import SentenceSplitter from llama_index.core.extractors import TitleExtractor from llama_index.core.ingestion import IngestionPipeline, IngestionCache pipeline = IngestionPipeline( transformations=[ SentenceSplitter(chunk_size=25, chunk_overlap=0), TitleExtractor(), OpenAIEmbedding(), ] ) nodes = pipeline.run(documents=[Document.example()])这里的SentenceSplitter(chunk_size=25, chunk_overlap=0)定义了文本切分粒度与重叠窗口,TitleExtractor负责抽取标题元数据,OpenAIEmbedding负责向量化——管线产出的nodes可直接喂给索引构造函数。
方式三:手动创建与管理 Node
如果你希望完全掌控索引,可以手动创建TextNode并直接传给索引构造函数(vector_store_index.mdx):
from llama_index.core.schema import TextNode node1 = TextNode(text="<text_chunk>", id_="<node_id>") node2 = TextNode(text="<text_chunk>", id_="<node_id>") nodes = [node1, node2] index = VectorStoreIndex(nodes)当数据源随时间变化时,Index类还提供插入(insertion)、删除(deletion)、更新(update)、刷新(refresh)四类操作,详见后文"文档管理"章节及 document_management.md、metadata_extraction.md。
存储:从内存到持久化向量库
默认情况下VectorStoreIndex将一切保存在内存中。LlamaIndex 支持数十种向量存储,通过传入StorageContext并指定vector_store参数即可切换到持久化后端(vector_store_index.mdx):
import pinecone from llama_index.core import ( VectorStoreIndex, SimpleDirectoryReader, StorageContext, ) from llama_index.vector_stores.pinecone import PineconeVectorStore pinecone.init(api_key="<api_key>", environment="<environment>") pinecone.create_index( "quickstart", dimension=1536, metric="euclidean", pod_type="p1" ) storage_context = StorageContext.from_defaults( vector_store=PineconeVectorStore(pinecone.Index("quickstart")) ) documents = SimpleDirectoryReader( "../../examples/data/paul_graham" ).load_data() index = VectorStoreIndex.from_documents( documents, storage_context=storage_context )对应的向量库实现均位于llama-index-integrations/vector_stores/目录下(如llama-index-vector-stores-pinecone),各集成包的用法示例可参考 vector_store_guide.ipynb。
组合式检索(Composable Retrieval)
VectorStoreIndex(以及任何索引/检索器)都具备检索通用对象的能力,包括:
- 节点引用(references to nodes)
- 查询引擎(query engines)
- 检索器(retrievers)
- 查询管线(query pipelines)
当这些对象被检索到时,它们会自动使用提供的查询语句执行(vector_store_index.mdx):
from llama_index.core.schema import IndexNode query_engine = other_index.as_query_engine obj = IndexNode( text="A query engine describing X, Y, and Z.", obj=query_engine, index_id="my_query_engine", ) index = VectorStoreIndex(nodes=nodes, objects=[obj]) retriever = index.as_retriever(verbose=True)如果包含该查询引擎的 IndexNode 被检索命中,查询引擎会被自动执行,其结果作为节点返回。源码中BaseIndex通过_object_map维护index_id → 对象的映射,并在序列化前将obj置空以避免序列化问题(base.py)。
各类索引的工作原理与查询流程
index_guide.md系统讲解了各索引类型的构建与查询机制(index_guide.md)。核心术语:Node对应 Document 中的文本块;Response Synthesis负责根据检索到的 Node 合成回答。
Vector Store Index
向量存储索引将每个 Node 及其对应的 embedding 存储在向量存储中,其构建流程如下图所示:
查询时,它会获取最相似的 top-k 个 Node,并将其传入 Response Synthesis 模块合成答案:
Summary Index(原 List Index)
摘要索引将 Nodes 存储为顺序链,这是最简单的索引结构:
查询时,如果未指定其他查询参数,LlamaIndex 会把列表中的所有 Nodes 全部加载进 Response Synthesis 模块。它同时支持多种查询方式:基于 embedding 的查询(取回 top-k 近邻)、叠加关键词过滤器、或基于 LLM 的查询——对应源码中SummaryIndex.as_retriever的三种模式ListRetrieverMode.DEFAULT / EMBEDDING / LLM(list/base.py)。
Tree Index
树索引从一组 Nodes 构建层次树(这些 Nodes 成为树的叶子节点):
查询树索引时,从根节点向下遍历到叶子节点。默认情况下(child_branch_factor=1),查询在给定父节点时只选择一个子节点;若child_branch_factor=2,则每层选择两个子节点。对应的检索器实现位于llama-index-core/llama_index/core/indices/tree/目录(含select_leaf_retriever.py、tree_root_retriever.py、all_leaf_retriever.py等)。
Keyword Table Index
关键词表索引从每个 Node 中提取关键词,并建立"关键词 → 对应 Nodes"的映射:
查询时,从查询语句中提取相关关键词,与预提取的 Node 关键词匹配,取回对应 Nodes 后交给 Response Synthesis 模块。其实现位于llama-index-core/llama_index/core/indices/keyword_table/,支持简单关键词提取(simple_base.py)与 RAKE 算法(rake_base.py)两种底层提取策略。
Property Graph Index
属性图索引通过首先构建包含带标签节点与关系的知识图谱来工作,其构建过程高度可定制:从让 LLM 自由抽取,到按严格 schema 抽取,甚至实现你自己的抽取模块。可选项包括:为节点生成 embedding 以便后续向量检索;也可以跳过图构建,直接通过 Neo4j 等集成连接已有知识图谱。
查询属性图索引同样高度灵活:检索通过多个子检索器组合结果实现。默认使用关键词 + 同义词扩展,以及在图谱已嵌入时的向量检索来取回相关三元组;还可以选择在检索结果中包含源文本(对于在 LlamaIndex 之外创建的图谱则不可用)。完整的子检索器与变换器实现参见 llama-index-core/llama_index/core/indices/property_graph/,深入指南见 lpg_index_guide.md。
文档管理:索引的插入、删除、更新与刷新
大多数 LlamaIndex 索引结构都支持insertion、deletion、update、refresh四类操作(document_management.md)。这些操作在BaseIndex中均有同步与异步(a前缀)两套实现(base.py)。
插入(Insertion)
构建索引后,可以随时将新 Document 插入任意索引结构,该文档会被切分为 Nodes 并摄入索引。底层机制取决于索引类型:对 Summary Index,新 Document 作为额外节点追加进列表;对 Vector Store Index,新 Document(及其 embeddings)被插入底层文档/向量存储:
from llama_index.core import SummaryIndex, Document index = SummaryIndex([]) text_chunks = ["text_chunk_1", "text_chunk_2", "text_chunk_3"] doc_chunks = [] for i, text in enumerate(text_chunks): doc = Document(text=text, id_=f"doc_id_{i}") doc_chunks.append(doc) for doc_chunk in doc_chunks: index.insert(doc_chunk)从源码看,insert(document)会先对文档执行变换管线生成 Nodes,然后调用insert_nodes写入 docstore 与索引结构,并记录文档哈希(base.py)。
删除(Deletion)
通过指定document_id即可从大多数索引结构中删除 Document(注意:Tree Index 当前不支持删除),该文档对应的所有节点都会被删除:
index.delete_ref_doc("doc_id_0", delete_from_docstore=True)delete_from_docstore默认为False——当你使用同一个 docstore 在多个索引间共享节点时,节点会被从索引的index_struct中移除(index_struct记录了可用于查询的节点),从而在查询时不再使用;True则会进一步清理 docstore。从源码看,delete_ref_doc通过 docstore 中的RefDocInfo找到该文档的所有node_ids,再逐个删除(base.py)。
更新(Update)
如果某个 Document 已存在于索引中,可以通过相同id_更新它(例如文档内容发生变化时):
doc_chunks[0].text = "Brand new document text" index.update_ref_doc(doc_chunks[0])源码实现update_ref_doc本质上等价于"先删除、再插入":先以delete_from_docstore=True删除旧文档及其节点,再重新插入新文档(base.py)。
刷新(Refresh)
如果加载数据时为每个文档设置了id_,可以自动刷新索引。refresh()只会更新相同id_但文本内容不同的文档;完全不在索引中的文档也会被插入。它返回一个布尔列表,指示输入文档中哪些已被刷新:
doc_chunks[0] = Document(text="Super new document text", id_="doc_id_0") doc_chunks.append( Document( text="This isn't in the index yet, but it will be soon!", id_="doc_id_3", ) ) refreshed_docs = index.refresh_ref_docs(doc_chunks) # refreshed_docs[0] 和 refreshed_docs[-1] 应为 True print(refreshed_docs) # > [True, False, False, True]这一功能最适合"目录不断更新新信息"的场景。其原理是利用from_documents时写入 docstore 的文档哈希:refresh_ref_docs逐个比较existing_doc_hash与document.hash——哈希不存在则插入、哈希不一致则更新(base.py)。若使用SimpleDirectoryReader,可设置filename_as_id标志自动为文档设置id_。
文档追踪(Document Tracking)
任何使用 docstore 的索引(即除大多数向量存储集成之外的所有索引)都可以查看已摄入的文档:
print(index.ref_doc_info) # > {'doc_id_1': RefDocInfo(node_ids=['071a66a8-3c47-49ad-84fa-7010c6277479'], metadata={}), # 'doc_id_2': RefDocInfo(node_ids=['9563e84b-f934-41c3-acfd-22e88492c869'], metadata={}), # 'doc_id_0': RefDocInfo(node_ids=['b53e6c2f-16f7-4024-af4c-42890e945f36'], metadata={}), # 'doc_id_3': RefDocInfo(node_ids=['6bedb29f-15db-4c7c-9885-7490e10aa33f'], metadata={})}输出以摄入文档的id_为键,值为其切分后对应的node_ids列表,同时保留每个输入文档的原始metadata字典。
索引的持久化与重新加载
索引构建后可通过StorageContext持久化,并通过load_index_from_storage重新加载。源码实现从 index store 读取IndexStruct,依据其类型从注册表反查出对应索引类并重建实例(loading.py):
from llama_index.core import StorageContext, load_index_from_storage storage_context = StorageContext.from_defaults(persist_dir="./storage") index = load_index_from_storage(storage_context)若 index store 中存在多个索引,需显式传入index_id,否则会因无法确定加载目标而抛出ValueError。
从索引到 Retriever、Query Engine 与 Chat Engine
索引的最终目的是支撑检索与生成。BaseIndex提供三个标准转换入口(base.py):
as_retriever(**kwargs):返回BaseRetriever。以VectorStoreIndex为例,它返回VectorIndexRetriever,并注入索引结构中的全部节点 ID 与对象映射(vector_store/base.py)。as_query_engine(llm=None, **kwargs):内部先调用as_retriever获取检索器,再包装为RetrieverQueryEngine,将检索与响应合成串联起来,实现"基于你的数据问答"。as_chat_engine(chat_mode=..., **kwargs):在查询引擎基础上包装多轮对话能力。支持ChatMode.BEST(默认,使用带查询引擎工具的 Agent)、CONTEXT(检索上下文)、CONDENSE_QUESTION(问题压缩)、CONDENSE_PLUS_CONTEXT、SIMPLE等模式。
小结:如何选择合适的索引
官方文档给出了明确的选型指引(modules.md):
- 通用 RAG 场景:首选
VectorStoreIndex,语义相似度检索 + 向量数据库持久化,生态支持最完善。 - 文档级摘要检索:
SummaryIndex适合需要遍历/汇总全部内容的场景,或DocumentSummaryIndex做文档粒度摘要。 - 层级化推理:
TreeIndex适合需要自顶向下逐层筛选的查询。 - 关键词精确匹配:
KeywordTableIndex适合关键词驱动、对语义向量不敏感的场景。 - 实体与关系推理:
PropertyGraphIndex/KnowledgeGraphIndex适合知识图谱类应用,支持 LLM 抽取、严格 schema 或自定义抽取模块,并能对接 Neo4j 等已有图谱。 - 结构化数据:
SQLStructStoreIndex、PandasIndex面向数据库与表格分析。
完整的模块索引与示例入口可参考 modules.md,各索引的构建与查询图示汇总于 index_guide.md,而所有索引类型的统一生命周期实现可深入阅读 llama-index-core/llama_index/core/indices/ 下的源码。
【免费下载链接】llama_indexLlamaIndex is the document processing platform for AI项目地址: https://gitcode.com/GitHub_Trending/ll/llama_index
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考