LlamaIndex 学习笔记
git地址
https://github.com/zxliucn/LlamaIndex-Learning
目录
- 文档加载 (DataLoader)
- 节点解析器 (NodeParser)
- 嵌入模型 (Embedding Model)
- 索引与存储 (Index)
- 检索器 (Retriever)
- 索引、向量数据与数据库的关系
一、文档加载 (DataLoader)
文档加载是 LlamaIndex 构建 RAG 应用的第一步,也是最基础的一步。本文将全面介绍 LlamaIndex 中文档加载的各种方式、核心概念以及高级自定义技巧。
1.1 核心概念:Document
在 LlamaIndex 中,Document(文档)是对原始数据源的标准化封装,是整个 RAG 流程的数据入口。它不仅仅指传统的文本文档,而是涵盖所有可被加载的结构化/非结构化数据的抽象载体。
Document 的核心属性
| 属性 | 类型 | 说明 |
|---|
text | str | 核心属性,存储文档的原始文本内容 |
metadata | dict | 元数据字典,记录文档的上下文信息(文件名、路径、创建时间等) |
id_ / doc_id | str | 唯一标识符,用于区分不同 Document |
Document 的关键特性
- 多源适配性:LlamaIndex 提供 100+ 内置 Data Loader,可将不同数据源统一为 Document 对象
- 不可变性:原始 Document 加载后内容不会被修改(分割操作会生成新的 Node)
- 编码保障:加载中文文档时需通过
encoding="utf-8" 指定编码
1.2 文档加载的三种方式
方式一:使用 SimpleDirectoryReader(最常用)
SimpleDirectoryReader 是最简单易用的加载器,可以将指定目录中的文件批量加载为 Document 对象:
from llama_index.core import SimpleDirectoryReader
# 基本用法:加载目录下所有文件
documents = SimpleDirectoryReader("./data").load_data()
# 指定编码(处理中文文档)
documents = SimpleDirectoryReader("./data", encoding="utf-8").load_data()
# 查看加载结果
print(f"加载文档数量:{len(documents)}")
print(f"第一个文档内容长度:{len(documents[0].text)}字符")
print(f"文档元数据:{documents[0].metadata}")
支持的文件格式:Markdown、PDF、Word 文档、PowerPoint 演示文稿、图像、音频和视频等。
方式二:使用 LlamaHub 的数据加载器
LlamaHub 是 LlamaIndex 的数据连接器注册表,提供了数百种连接器,支持从各种数据源加载数据:
from llama_index.core import download_loader
from llama_index.readers.database import DatabaseReader
# 示例:从 SQL 数据库加载数据
reader = DatabaseReader(
scheme="postgresql",
host="localhost",
port=5432,
user="myuser",
password="mypassword",
dbname="mydb",
)
query = "SELECT * FROM users"
documents = reader.load_data(query=query)
方式三:手动构造 Document
from llama_index.core import Document
# 从文本列表创建
text_list = ["文本1内容", "文本2内容", "文本3内容"]
documents = [Document(text=t) for t in text_list]
# 创建单个文档
document = Document(text="这是文档的原始文本内容")
# 快速创建示例文档(用于原型开发)
example_doc = Document.example()
1.3 元数据(Metadata)管理
元数据是 Document 的重要组成部分,用于记录文档的上下文信息,对回答溯源和多文档管理至关重要。
设置元数据
方式一:在构造函数中设置
document = Document(
text="文档内容",
metadata={
"filename": "important_doc.pdf",
"category": "技术文档",
"author": "张三",
"creation_date": "2024-01-15"
}
)
方式二:创建后设置
document = Document(text="文档内容")
document.metadata = {"filename": "doc.txt", "category": "指南"}
方式三:使用 SimpleDirectoryReader 自动设置
from llama_index.core import SimpleDirectoryReader
# 定义元数据生成函数
filename_fn = lambda filename: {"file_name": filename, "source": "local"}
documents = SimpleDirectoryReader(
"./data",
file_metadata=filename_fn
).load_data()
自定义文档 ID
# 自动将文件路径作为 ID
documents = SimpleDirectoryReader("./data", filename_as_id=True).load_data()
# 手动设置 ID
document.doc_id = "my_custom_document_id"
高级元数据自定义
from llama_index.core import Document
from llama_index.core.schema import MetadataMode
document = Document(
text="这是一个需要处理的文档",
metadata={
"file_name": "secret_document.txt",
"category": "finance",
"author": "LlamaIndex"
},
excluded_llm_metadata_keys=["file_name"],
excluded_embed_metadata_keys=["file_name"],
)
# 查看 LLM 实际看到的内容
print(document.get_content(metadata_mode=MetadataMode.LLM))
# 查看 Embedding 模型实际看到的内容
print(document.get_content(metadata_mode=MetadataMode.EMBED))
自定义元数据格式
document = Document(
text="文档内容",
metadata={"key1": "value1", "key2": "value2"},
metadata_seperator=" | ", # 键值对之间的分隔符
metadata_template="{key}: {value}", # 每个键值对的格式
text_template="元数据:{metadata_str}\n---\n内容:{content}"
)
1.4 批量加载与高级用法
批量加载目录
from llama_index.core import SimpleDirectoryReader
documents = SimpleDirectoryReader(
input_dir="./docs",
recursive=True, # 递归读取子目录
required_exts=[".pdf", ".txt"], # 只加载特定扩展名
exclude=["temp", "draft"] # 排除特定目录
).load_data()
使用 DocStore 管理多个索引
from llama_index.core import SimpleDirectoryReader, VectorStoreIndex, SummaryIndex
from llama_index.core.storage.docstore import SimpleDocumentStore
from llama_index.core import StorageContext
from llama_index.core.node_parser import SentenceSplitter
documents = SimpleDirectoryReader("./data").load_data()
nodes = SentenceSplitter().get_nodes_from_documents(documents)
docstore = SimpleDocumentStore()
docstore.add_documents(nodes)
storage_context = StorageContext.from_defaults(docstore=docstore)
# 多个索引共享同一个文档存储
vector_index = VectorStoreIndex(nodes, storage_context=storage_context)
summary_index = SummaryIndex(nodes, storage_context=storage_context)
1.5 完整示例:从加载到索引
from llama_index.core import (
SimpleDirectoryReader,
VectorStoreIndex,
Settings
)
from llama_index.core.node_parser import SentenceSplitter
from llama_index.llms.openai import OpenAI
# 1. 配置 LLM
Settings.llm = OpenAI(model="gpt-3.5-turbo", temperature=0)
# 2. 加载文档
documents = SimpleDirectoryReader(
"./data",
encoding="utf-8",
filename_as_id=True
).load_data()
# 3. 设置文本分割
Settings.chunk_size = 512
Settings.chunk_overlap = 20
# 4. 构建索引
index = VectorStoreIndex.from_documents(documents)
# 5. 查询测试
query_engine = index.as_query_engine()
response = query_engine.query("你的问题在这里")
print(response)
1.6 常见问题与最佳实践
| 问题 | 建议 |
|---|
| 中文编码 | 务必指定 encoding="utf-8" |
| 元数据 | 保留文件路径和文件名,便于溯源;敏感信息用 excluded_llm_metadata_keys 排除 |
| 性能 | 大文档集设置合理 chunk_size(512-1024);避免不必要的深层遍历 |
| 文档刷新 | 设置 filename_as_id=True 方便检测变化并刷新索引 |
二、节点解析器 (NodeParser)
2.1 核心概念:Document vs Node
- Document:原始文档对象,可以是一整个文件或一段文本
- Node:文档切分后的"块",包含文本内容、元数据以及节点间的关系信息(如前后节点、父节点引用)
from llama_index.core.schema import TextNode, NodeRelationship, RelatedNodeInfo
node1 = TextNode(text="这是第一个文本块", id_="node_1")
node2 = TextNode(text="这是第二个文本块", id_="node_2")
node1.relationships[NodeRelationship.NEXT] = RelatedNodeInfo(node_id=node2.node_id)
node2.relationships[NodeRelationship.PREVIOUS] = RelatedNodeInfo(node_id=node1.node_id)
2.2 什么数据需要切分?什么时候不需要?
需要切分的数据类型
| 数据类型 | 切分必要性 | 推荐切分器 |
|---|
| 📄 纯文本文档 | ✅ 必须切分 | SentenceSplitter |
| 📝 Markdown文档 | ✅ 必须切分 | MarkdownNodeParser |
| 🌐 HTML网页 | ✅ 必须切分 | MarkdownNodeParser(先转Markdown) |
| 💻 代码文件 | ✅ 必须切分 | CodeSplitter |
| 📑 长文本日志 | ✅ 必须切分 | SentenceSplitter |
不需要切分的数据类型
| 数据类型 | 原子单位 | LlamaIndex 推荐方式 |
|---|
| 🗄️ SQL数据库 | 行/记录 | SQLTableRetriever + 文本到SQL |
| 📋 JSON/XML | 字段/路径 | 字段路径访问 + Jsonalyzer |
| 📊 CSV表格 | 行 | PandasQueryEngine |
| 🕸️ 知识图谱 | 三元组/节点 | KnowledgeGraphIndex |
半结构化数据:混合处理策略
# 表格部分:保留原结构,不走切分
# 文本部分:切分后建向量索引
from llama_index.core.node_parser import SentenceSplitter
text_nodes = SentenceSplitter().get_nodes_from_documents(text_docs)
vector_index = VectorStoreIndex(text_nodes)
sql_query_engine = NLSQLTableQueryEngine(sql_database)
核心原则
| 原则 | 说明 |
|---|
| 原子性优先 | 如果数据本身具有原子单位,直接使用该单位检索 |
| 破坏结构不切 | 切分会破坏数据关系时不切分 |
| 超限才切 | 只有当单个单位超过LLM上下文窗口时才切分 |
| 混合分别处理 | 半结构化数据中的表格和文字分开处理 |
2.3 自动切分 vs 手动切分
VectorStoreIndex 的自动切分
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
documents = SimpleDirectoryReader("./data/").load_data()
index = VectorStoreIndex.from_documents(documents) # 自动完成切分!
控制自动切分:
from llama_index.core import Settings
from llama_index.core.node_parser import SentenceSplitter
# 方式一:通过全局 Settings 配置
Settings.text_splitter = SentenceSplitter(chunk_size=512, chunk_overlap=50)
index = VectorStoreIndex.from_documents(documents)
# 方式二:通过 transformations 参数
text_splitter = SentenceSplitter(chunk_size=500, chunk_overlap=50)
index = VectorStoreIndex.from_documents(documents, transformations=[text_splitter])
手动切分的优势
| 能力 | 自动切分 | 手动切分 |
|---|
| 快速上手 | ✅ 一行代码 | ❌ 需要写代码 |
| 自定义元数据 | ❌ 只能文档级别 | ✅ 节点级别精细控制 |
| 节点关系(父子/前后) | ❌ 扁平结构 | ✅ 任意关系 |
| 内容过滤/清洗 | ❌ 无法干预 | ✅ 完全控制 |
| 完整性保证 | ⚠️ 可能切断关键内容 | ✅ 可保证 |
| 多模态支持 | ❌ 有限 | ✅ 灵活 |
渐进式优化建议
# 阶段一:快速验证(自动切分)
index = VectorStoreIndex.from_documents(documents)
# 阶段二:发现问题后切换手动切分
parser = SentenceSplitter(chunk_size=512, chunk_overlap=50)
nodes = parser.get_nodes_from_documents(documents)
# 阶段三:手动增强元数据
for i, node in enumerate(nodes):
node.metadata["chunk_index"] = i
node.metadata["source_file"] = documents[0].metadata.get("file_name")
if "summary" in node.text.lower():
node.metadata["is_summary"] = True
index = VectorStoreIndex(nodes)
2.4 最常用的切分方式:SentenceSplitter
from llama_index.core.node_parser import SentenceSplitter
# 创建切分器
parser = SentenceSplitter(chunk_size=512, chunk_overlap=20)
# 执行切分
nodes = parser.get_nodes_from_documents(documents)
# 全局配置
from llama_index.core import Settings
Settings.chunk_size = 512
Settings.chunk_overlap = 20
2.5 进阶切分器
MarkdownNodeParser - 保留 Markdown 结构
from llama_index.core.node_parser import MarkdownNodeParser
parser = MarkdownNodeParser()
nodes = parser.get_nodes_from_documents(documents)
CodeSplitter - 代码专用切分
from llama_index.core.node_parser import CodeSplitter
parser = CodeSplitter(
language="python",
chunk_lines=40,
chunk_lines_overlap=15,
max_chars=1500,
)
nodes = parser.get_nodes_from_documents(documents)
SemanticSplitterNodeParser - 语义切分
from llama_index.core.node_parser import SemanticSplitterNodeParser
from llama_index.embeddings.openai import OpenAIEmbedding
embed_model = OpenAIEmbedding()
parser = SemanticSplitterNodeParser(
embed_model=embed_model,
buffer_size=1,
breakpoint_percentile_threshold=95,
)
nodes = parser.get_nodes_from_documents(documents)
| 参数 | 说明 |
|---|
buffer_size | 分组评估语义相似度时的句子数量 |
breakpoint_percentile_threshold | 余弦相似度百分位数阈值,值越小切分越细 |
embed_model | 用于计算语义相似度的嵌入模型(必填) |
2.6 使用 IngestionPipeline
from llama_index.core.ingestion import IngestionPipeline
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.extractors import TitleExtractor
pipeline = IngestionPipeline(
transformations=[
SentenceSplitter(chunk_size=512, chunk_overlap=20),
TitleExtractor(),
]
)
nodes = pipeline.run(documents=documents, show_progress=True)
2.7 第三方切分器
DashScopeJsonNodeParser(阿里通义)
from llama_index.node_parser.dashscope import DashScopeJsonNodeParser
node_parser = DashScopeJsonNodeParser(
chunk_size=100,
overlap_size=0,
separator=" |,|,|。|?|!|\n|\?|\!",
language="cn"
)
2.8 切分策略总结
| 文档类型 | 推荐切分器 | 是否需要切分 |
|---|
| 普通文本文档 | SentenceSplitter | ✅ |
| Markdown文档 | MarkdownNodeParser | ✅ |
| 代码文件 | CodeSplitter | ✅ |
| 长文档/书籍 | SemanticSplitterNodeParser | ✅ |
| SQL数据库 | 不切分 | ❌ |
| JSON/XML | 不切分 | ❌ |
| CSV小表 | 不切分 | ❌ |
| 中文文档 | DashScopeJsonNodeParser | ✅ |
💡 核心提示:当你发现检索结果不够好时,手动切分往往是第一个需要优化的地方。
三、嵌入模型 (Embedding Model)
Embedding 模型将文本转换为向量表示,直接影响检索质量。
3.1 安装与基础配置
pip install llama-index-embeddings-openai # OpenAI
pip install llama-index-embeddings-huggingface # Hugging Face
pip install llama-index-embeddings-ollama # Ollama
pip install BCEmbedding # 中文推荐
全局配置
from llama_index.core import Settings
from llama_index.embeddings.openai import OpenAIEmbedding
Settings.embed_model = OpenAIEmbedding()
3.2 各主要来源 Embedding 模型
OpenAI Embedding
from llama_index.embeddings.openai import OpenAIEmbedding
embed_model = OpenAIEmbedding(
model="text-embedding-3-small", # 或 text-embedding-3-large
embed_batch_size=10,
)
Hugging Face 本地模型
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
# 轻量级
embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-small-en-v1.5")
# 高性能
embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-large-en-v1.5")
| 模型 | 特点 | 维度 |
|---|
BAAI/bge-small-en-v1.5 | 轻量快速 | 384 |
BAAI/bge-base-en-v1.5 | 平衡 | 768 |
BAAI/bge-large-en-v1.5 | 高性能 | 1024 |
Ollama 本地部署
from llama_index.embeddings.ollama import OllamaEmbedding
embed_model = OllamaEmbedding(
model_name="nomic-embed-text",
base_url="http://localhost:11434",
query_instruction="Represent the question for retrieving supporting documents:",
text_instruction="Represent the document for retrieval:",
)
ONNX 加速
from llama_index.embeddings.huggingface_optimum import OptimumEmbedding
OptimumEmbedding.create_and_save_optimum_model("BAAI/bge-small-en-v1.5", "./bge_onnx")
Settings.embed_model = OptimumEmbedding(folder_name="./bge_onnx")
3.3 中文 Embedding 模型推荐
| 模型 | 核心特点 | 向量维度 | 适用场景 |
|---|
| BGE-M3 | 100+语言,8192 tokens,混合检索 | 1024 | 跨语言长文档 |
| bce-embedding-base_v1 | 中英双语优化,RAG SOTA | 768 | 双语RAG |
| Qwen3-VL-2B | 多模态,跨语言检索强 | 2048 | 图文混合检索 |
| M3E-Turbo | 中文轻量,本地部署 | — | 本地知识库 |
| jina-embeddings-v2-base-zh | 中英双语,8192上下文 | 768 | 中文长文档 |
重点推荐:BCEmbedding
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
embed_model = HuggingFaceEmbedding(
model_name="maidalun1020/bce-embedding-base_v1",
max_length=512,
embed_batch_size=32,
device="cuda"
)
配合 Reranker(强烈推荐):
from BCEmbedding.tools.llama_index import BCERerank
reranker = BCERerank(
model="maidalun1020/bce-reranker-base_v1",
top_n=5,
device="cuda"
)
query_engine = index.as_query_engine(node_postprocessors=[reranker])
中文场景决策树
纯中文 + 本地部署 → M3E-Turbo
纯中文 + 高精度 → xiaobu-embedding-v2
中英双语 + RAG → bce-embedding-base_v1(首选)
多语言/长文档 → BGE-M3
图像+文本混合 → Qwen3-VL-2B
3.4 模型选择与性能评测
评测指标
- Hit Rate(命中率):正确答案出现在 top-k 检索结果中的查询比例
- MRR(平均倒数排名):第一个相关文档排名倒数的平均值
各模型性能对比
| Embedding 模型 | 搭配 Reranker | Hit Rate | MRR |
|---|
| JinaAI-Base | bge-reranker-large | 0.938 | 0.869 |
| OpenAI | CohereRerank | 0.927 | 0.866 |
| bge-large | CohereRerank | 0.876 | 0.823 |
关键结论:Reranker 几乎总是能显著提升效果。
3.5 完整代码示例
基础 RAG Pipeline
from llama_index.core import SimpleDirectoryReader, VectorStoreIndex, Settings
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.core.node_parser import SentenceSplitter
Settings.embed_model = HuggingFaceEmbedding(model_name="BAAI/bge-large-en-v1.5")
Settings.text_splitter = SentenceSplitter(chunk_size=512, chunk_overlap=50)
documents = SimpleDirectoryReader("./data").load_data()
index = VectorStoreIndex.from_documents(documents, show_progress=True)
index.storage_context.persist(persist_dir="./storage")
query_engine = index.as_query_engine()
response = query_engine.query("你的问题")
中文 RAG 完整示例
from llama_index.core import SimpleDirectoryReader, VectorStoreIndex, Settings
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.core.node_parser import SentenceSplitter
from BCEmbedding.tools.llama_index import BCERerank
Settings.embed_model = HuggingFaceEmbedding(
model_name="maidalun1020/bce-embedding-base_v1", device="cuda"
)
Settings.text_splitter = SentenceSplitter(chunk_size=512, chunk_overlap=50)
documents = SimpleDirectoryReader("./chinese_docs").load_data()
index = VectorStoreIndex.from_documents(documents, show_progress=True)
reranker = BCERerank(model="maidalun1020/bce-reranker-base_v1", top_n=3, device="cuda")
query_engine = index.as_query_engine(
similarity_top_k=10,
node_postprocessors=[reranker],
)
response = query_engine.query("《长安的荔枝》的主角是谁?")
四、索引与存储 (Index)
4.1 索引概述
索引(Index) 是核心数据结构,将原始 Document 对象组织起来,以便 LLM 高效查询。构建索引包括将文档解析、分块为 Node 对象。
4.2 核心索引类型
| 索引类型 | 核心机制 | 最佳适用场景 | 检索策略 |
|---|
| VectorStoreIndex | 向量嵌入存储 | 语义相似度匹配,RAG 最常用 | ANN 近似最近邻 |
| SummaryIndex | 顺序列表 | 生成文档摘要 | 全量遍历 |
| TreeIndex | 分层树状结构 | 长文档摘要、逐级下钻 | 逐层遍历 |
| KeywordTableIndex | 关键词→Node映射 | 基于关键词的精确检索 | 倒排索引 |
4.3 存储机制
核心存储组件
| 组件 | 存储内容 | 查询频率 |
|---|
| DocStore | 原始文本、Node对象 | 中 |
| VectorStore | 向量数组、映射关系 | 极高 |
| IndexStore | 索引元数据 | 低 |
| GraphStore | 知识图谱 | 低 |
| ChatStore | Agent对话历史 | 中 |
方案一:本地磁盘持久化
from llama_index.core import VectorStoreIndex, StorageContext, load_index_from_storage
# 持久化
index.storage_context.persist(persist_dir="./my_storage")
# 加载
if os.path.exists("./my_storage"):
storage_context = StorageContext.from_defaults(persist_dir="./my_storage")
index = load_index_from_storage(storage_context)
else:
index = VectorStoreIndex.from_documents(documents)
index.storage_context.persist(persist_dir="./my_storage")
方案二:向量数据库存储(生产环境首选)
import chromadb
from llama_index.vector_stores.chroma import ChromaVectorStore
db = chromadb.PersistentClient(path="./chroma_db")
chroma_collection = db.get_or_create_collection("my_documents")
vector_store = ChromaVectorStore(chroma_collection=chroma_collection)
storage_context = StorageContext.from_defaults(vector_store=vector_store)
index = VectorStoreIndex.from_documents(documents, storage_context=storage_context)
# 重启后直接加载(无需重新嵌入)
loaded_index = VectorStoreIndex.from_vector_store(vector_store)
动态更新索引
new_documents = SimpleDirectoryReader("new_data").load_data()
for doc in new_documents:
index.insert(doc)
4.4 Agent 记忆存储
- 短期记忆:SQLite 存储,维护当前会话上下文
长期记忆:三种记忆块:
- StaticMemoryBlock:静态信息(姓名、偏好)
- FactExtractionMemoryBlock:LLM 驱动自动提取事实
- VectorMemoryBlock:历史对话嵌入向量数据库
4.5 最佳实践
| 场景 | 推荐方案 |
|---|
| 数据量小/原型验证 | 本地磁盘 + VectorStoreIndex |
| 生产环境/大规模 | 向量数据库 + VectorStoreIndex |
| 关键词搜索 | KeywordTableIndex |
| 长文档摘要 | TreeIndex |
| 对话式 Agent | ChatStore + 长期记忆块 |
五、检索器 (Retriever)
5.1 Retriever 是什么?
Retriever(检索器) 负责根据用户查询获取最相关的上下文内容,是连接索引和查询引擎的核心桥梁。
| 组件 | 职责 | 类比 |
|---|
| Retriever | 从索引中检索相关节点 | 图书管理员找书 |
| Response Synthesizer | 将检索到的节点合成答案 | 阅读并总结资料 |
| Query Engine | 整合上述两者 | 前台查询服务窗口 |
用户查询 → Retriever → Response Synthesizer → 返回响应
5.2 快速上手
from llama_index.core import VectorStoreIndex
index = VectorStoreIndex.from_documents(documents)
# 获取检索器
retriever = index.as_retriever(similarity_top_k=5)
# 执行检索
nodes = retriever.retrieve("Who is Paul Graham?")
5.3 索引与 Retriever 的关系
| 索引类型 | 检索器模式 | 适用场景 |
|---|
VectorStoreIndex | 向量相似度检索 | 语义搜索 |
SummaryIndex | 列表式/LLM检索 | 摘要生成 |
KeywordTableIndex | 关键词匹配 | 精确查询 |
5.4 高阶检索技术
递归检索器(Recursive Retriever)
解耦检索与合成,通过文档代理实现复杂操作:
from llama_index.core.schema import IndexNode
from llama_index.agent.openai import OpenAIAgent
# 为每个文档构建代理
for wiki_title in wiki_titles:
vector_index = VectorStoreIndex.from_documents(docs)
summary_index = SummaryIndex.from_documents(docs)
tools = [
QueryEngineTool(query_engine=vector_index.as_query_engine(),
metadata=ToolMetadata(name="vector_tool", description="搜索具体事实")),
QueryEngineTool(query_engine=summary_index.as_query_engine(),
metadata=ToolMetadata(name="summary_tool", description="总结内容")),
]
agent = OpenAIAgent.from_tools(tools)
# 构建顶层检索器,查询会自动路由到正确的文档代理
混合检索
结合关键词匹配(BM25)和语义向量搜索:
class HybridRetriever(BaseRetriever):
def __init__(self, vector_retriever, keyword_retriever, mode="AND"):
self.vector_retriever = vector_retriever
self.keyword_retriever = keyword_retriever
self.mode = mode
def _retrieve(self, query_bundle):
vector_nodes = self.vector_retriever.retrieve(query_bundle)
keyword_nodes = self.keyword_retriever.retrieve(query_bundle)
vector_ids = {n.node.node_id for n in vector_nodes}
keyword_ids = {n.node.node_id for n in keyword_nodes}
combined = {n.node.node_id: n for n in vector_nodes}
combined.update({n.node.node_id: n for n in keyword_nodes})
if self.mode == "AND":
result_ids = vector_ids.intersection(keyword_ids)
else:
result_ids = vector_ids.union(keyword_ids)
return [combined[rid] for rid in result_ids]
检索器路由(Retriever Router)
通过向量检索动态选择最相关的查询引擎:
from llama_index.core.query_engine import ToolRetrieverRouterQueryEngine
router_query_engine = ToolRetrieverRouterQueryEngine(obj_index.as_retriever())
response = router_query_engine.query("What did the author do at Y Combinator?")
5.5 RetrieverQueryEngine
from llama_index.core.query_engine import RetrieverQueryEngine
query_engine = RetrieverQueryEngine.from_args(
retriever=my_retriever,
response_mode="compact",
node_postprocessors=[reranker],
)
支持的后处理组件
| 后处理器 | 功能 |
|---|
SimilarityPostprocessor | 按相似度分数过滤 |
KeywordNodePostprocessor | 按关键词过滤 |
CohereRerank | Cohere 重排序 |
5.6 性能优化
| 参数 | 推荐值 | 说明 |
|---|
similarity_top_k | 5-10 | 检索返回节点数量 |
chunk_size | 512-1024 | 文档分块大小 |
response_mode | compact / tree_summarize | 合成策略 |
场景选型
| 场景 | 推荐方案 |
|---|
| 中小型知识库 | LlamaIndex 开箱即用 |
| 高精度检索(法律/医疗) | 混合检索 + 重排序 |
| 多文档复杂推理 | 递归检索 + 文档代理 |
| 快速原型验证 | index.as_query_engine() |
5.7 设计哲学:渐进式复杂度
简单:index.as_retriever() → 一行代码
↓
进阶:配置 similarity_top_k、retriever_mode
↓
高级:自定义 Retriever 类、混合检索
↓
专业:递归检索、检索器路由、多 Agent 协作
六、索引、向量数据与数据库的关系
6.1 概念澄清
| 组件 | 本质 | 存储内容 | 比喻 |
|---|
| 向量 | 数值化后的文本特征 | 浮点数数组 | 文档的"DNA编码" |
| 索引(Index) | 高效检索向量的数据结构 | 向量 + ID映射 | "目录+分类卡片" |
| 数据库 | 持久化存储系统 | 向量+文本+元数据 | "书架系统" |
关键理解:
- 向量是数据本身
- 索引是数据结构(帮助快速检索)
- 数据库是载体(持久化存储向量+索引+元数据)
6.2 LlamaIndex 的三层存储架构
from llama_index.core import StorageContext
storage_context = StorageContext.from_defaults(
docstore=... # 文档存储:存原始文本和节点
vector_store=... # 向量存储:存向量嵌入
index_store=... # 索引存储:存索引元数据
)
| 组件 | 存储内容 | 数据量级 | 查询频率 |
|---|
| DocStore | 原始文本、Node | 较大 | 中 |
| VectorStore | 向量数组、映射 | 极大 | 极高 |
| IndexStore | 索引元数据 | 极小 | 低 |
6.3 不同存储方式对比
本地文件系统(SimpleVectorStore)
./storage/
├── docstore.json # 所有原始文本
├── vector_store.json # 所有向量数据(JSON格式,效率低)
└── index_store.json # 索引元数据
专业向量数据库(Chroma)
chroma_db/
├── chroma.sqlite3 # 元数据、文档ID
└── data_level0.bin # HNSW索引 + 向量数据
6.4 数据流向
构建索引阶段:
文档 → 文本分块 + 嵌入模型 → VectorStore(存向量+NodeID) + DocStore(存文本+NodeID)
查询阶段:
查询文本 → 查询向量 → VectorStore(相似度搜索) → 返回NodeID列表
→ DocStore(根据NodeID获取原始文本) → 返回结果
6.5 常见误区
| 误区 | 纠正 |
|---|
| "索引就是数据库" | 索引=检索算法的数据结构,数据库=持久化存储系统 |
| "向量数据库不存储向量" | 向量数据库专门为存储和检索向量设计 |
| "索引在内存中就不需要数据库" | 仍需持久化,否则程序退出后索引消失 |
6.6 选择决策指南
| 数据量 | 推荐方案 |
|---|
| < 1万条 | 本地文件系统(JSON) |
| 1万-10万 | Chroma/Qdrant |
| 10万-100万 | Milvus/Pinecone |
| > 100万 | 自建HNSW+对象存储 |
总结
| 概念 | 一句话定义 |
|---|
| 向量 | 文本的数学表示,用于计算相似度 |
| 索引 | 加速向量检索的数据结构 |
| 数据库 | 持久化存储向量+索引+元数据的系统 |
最佳实践:
- 小项目:用 LlamaIndex 本地文件系统
- 生产环境:用专业向量数据库(Chroma/Milvus/Qdrant)
- 超大规模:向量存对象存储,索引存 Redis 缓存