大模型应用技术之数据编排 LlamaIndex
这份文档是关于 LlamaIndex 的权威指南,内容涵盖从入门到精通的全过程。文档首先对比了 LangChain 和 LlamaIndex 的定位差异,明确 LlamaIndex 在数据处理和检索方面的优势。接着,详细介绍了 LlamaIndex 的核心功能,包括多种数据连接器、索引类型和查询引擎。文档还提供了丰富的 Python 代码示例,涵盖从基础 RAG 到进阶的路由模式、子问题分解、切块
前言:为什么有了 LangChain 还需要 LlamaIndex?
在构建 LLM(大语言模型)应用时,开发者最常面临的两个挑战是:
- 如何让模型按既定逻辑办事? (Agent/Chain 编排)
- 如何让模型准确理解私有数据? (RAG/Data 检索)
LangChain 也是一把瑞士军刀,它起初侧重于解决第一个问题(编排),虽然现在也有检索功能,但其核心基因在于“通过链(Chain)将组件连接起来”。
LlamaIndex (原 GPT Index) 则专注于解决第二个问题(数据)。它是一个数据框架,专门用于将自定义数据源连接到大语言模型。
LangChain 可以自己搞,就像你可以用 Python 原生代码写一个 Web 服务器,但你通常会用 Django 或 FastAPI。
LlamaIndex 的核心优势在于:
- 数据处理的颗粒度:LlamaIndex 提供了极其精细的数据分块(Chunking)、节点(Node)管理和元数据提取策略,这是 LangChain 的通用 Retriever 难以比拟的。
- 索引结构的丰富性:LangChain 主要是向量检索(Vector Store)。而 LlamaIndex 支持树状索引 (Tree Index)、关键词表索引 (Keyword Table Index)、知识图谱索引 (Knowledge Graph Index) 以及组合索引 (Composable Indices)。这对于复杂的查询(如“总结全书”或“对比两个文档的差异”)至关重要。
- 检索策略的优化:LlamaIndex 拥有更高级的检索后处理(Post-processing)和重排序(Re-ranking)机制,能显著提高 RAG(检索增强生成)的准确率。
第一部分:LangChain vs LlamaIndex —— 深度对比
1.1 定位差异
| 特性 | LangChain | LlamaIndex |
|---|---|---|
| 核心隐喻 | 管道/胶水 (Glue) | 图书馆管理员 (Librarian) |
| 主要关注点 | 交互流程、Agent 行为、多组件编排 | 数据摄入、数据索引、数据检索策略 |
| 最强项 | 打造全能 Agent、工具调用、记忆管理 | 处理海量、杂乱、非结构化数据的 RAG 优化 |
| 数据结构 | 主要是 Document 对象,相对扁平 | 主要是 Nodes (节点) 和 Indices (索引),支持层级关系 |
1.2 哪些问题 LangChain “搞不定” (或很难搞)?
虽然 LangChain 几乎是图灵完备的,但在以下场景中,使用 LlamaIndex 是降维打击:
-
跨文档的复杂推理:
- 场景:你有 5 个 PDF,需要问“对比这 5 个文件中提到的财务策略有何不同?”
- LangChain:通常只是把所有相关片段找出来塞给 LLM,容易超出 Context Window 或造成幻觉。
- LlamaIndex:可以使用 SubQuestionQueryEngine 把大问题拆解为针对每个文档的小问题,或者使用 Tree Index 进行层级摘要,最终汇总答案。
-
结构化与非结构化混合查询:
- 场景:想在 SQL 数据库和 PDF 文档中同时查找信息。
- LlamaIndex:拥有原生的 Text-to-SQL 和 RouterQueryEngine,可以智能判断该去查数据库还是查向量库,甚至同时查并合并结果。
-
索引更新成本:
- 场景:文档库每天新增 100 个文件。
- LlamaIndex:索引结构设计之初就考虑了增量更新(Insertion),维护成本更低。
第二部分:LlamaIndex 完整功能清单
在深入学习之前,我们先全面了解 LlamaIndex 的能力矩阵。LlamaIndex 不仅仅是一个 RAG 库,它是一个完整的数据到 LLM 的桥梁生态系统。
2.1 核心功能模块
📦 数据连接器 (Data Connectors)
LlamaIndex 支持 100+ 种数据源,远超 LangChain:
| 数据源类型 | 连接器示例 | 用途 |
|---|---|---|
| 文件系统 | SimpleDirectoryReader, MarkdownReader, PDFReader |
读取本地文件 |
| 数据库 | PostgresReader, MongoReader, SQLDatabaseReader |
连接关系型/非关系型数据库 |
| 云存储 | S3Reader, GCSReader, AzureBlobStorageReader |
读取云存储文件 |
| API 数据 | NotionReader, SlackReader, DiscordReader |
读取第三方服务数据 |
| 网页 | BeautifulSoupWebReader, TrafilaturaWebReader |
爬取网页内容 |
| 代码仓库 | GithubRepositoryReader |
读取 GitHub 代码 |
| 结构化数据 | PandasCSVReader, ExcelReader |
处理表格数据 |
| 音视频 | YoutubeTranscriptReader, WhisperTranscriber |
处理多媒体内容 |
🔍 索引类型 (Index Types)
这是 LlamaIndex 的核心竞争力,LangChain 主要只有向量检索:
| 索引类型 | 适用场景 | 优势 |
|---|---|---|
| VectorStoreIndex | 语义相似度检索 | 快速、准确,适合大多数 RAG 场景 |
| TreeIndex | 层级摘要、文档总结 | 自底向上构建摘要树,适合"总结全书"类查询 |
| KeywordTableIndex | 关键词精确匹配 | 不依赖 Embedding,适合结构化查询 |
| KnowledgeGraphIndex | 实体关系查询 | 提取实体和关系,支持"谁和谁有关系"类问题 |
| DocumentSummaryIndex | 多文档摘要检索 | 预先为每个文档生成摘要,检索时先查摘要 |
| ComposableGraphIndex | 组合多种索引 | 将多个索引组合,实现复杂查询策略 |
| PandasIndex | 表格数据查询 | 直接查询 DataFrame,支持自然语言转 SQL |
🛠️ 查询引擎 (Query Engines)
不同的查询引擎解决不同的查询需求:
| 查询引擎 | 功能 | 示例场景 |
|---|---|---|
| RetrieverQueryEngine | 检索+生成 | 标准 RAG 流程 |
| RouterQueryEngine | 智能路由 | 根据问题选择不同的数据源 |
| SubQuestionQueryEngine | 子问题分解 | “对比5个文档的差异” → 拆解为5个子问题 |
| MultiStepQueryEngine | 多步推理 | 需要多轮检索和推理的复杂问题 |
| TransformQueryEngine | 查询转换 | 在检索前改写查询,提高召回率 |
| RetrieverQueryEngine + ReRanking | 重排序 | 检索后对结果重新排序,提高准确率 |
✂️ 切块方式 (Node Parsers)
不同的切块策略决定了 LLM 能看到什么样的上下文:
| 切块方式 (Node Parser) | 核心原理 | 适用场景 |
|---|---|---|
| SentenceSplitter (默认) | 句子边界保持 尽量在句号、换行符处切断,保持句子完整性。 |
通用场景 大多数普通文本、文章、PDF 转文本的首选。 |
| TokenTextSplitter | 硬切分 严格按照 Token 数量强制切断,不考虑语法结构。 |
严格限制窗口 对上下文长度极其敏感,或文本本身无标点符号。 |
| MarkdownNodeParser | 结构化切分 根据 Markdown 标题层级 (#, ##) 切分,保留父级标题作为元数据。 |
技术文档 README、开发手册、Notion 导出笔记。 |
| JSONNodeParser | 层级保留 解析 JSON 结构,保持 Key-Value 的层级关系不被打散。 |
结构化数据 API 返回结果、NoSQL 数据库导出内容。 |
| SemanticSplitterNodeParser | 语义聚合 利用 Embedding 模型判断前后句相似度,在话题转变的地方切分。 |
长文/论文 确保每一块都在讲同一个完整的主题,避免语义断裂。 |
| HierarchicalNodeParser | 父子节点 (Small-to-Big) 生成“父节点”和对应的“子节点”。索引子节点,返回父节点。 |
复杂长文档 配合 AutoMergingRetriever,既要精准匹配细节,又要看大段上下文。 |
| SentenceWindowNodeParser | 句子窗口 切成单句,但元数据里附带前后 N 句的内容。 |
高精度问答 配合 MetadataReplacementPostProcessor,极大减少幻觉。 |
🧩 节点与文档处理 (Node Processing)
LlamaIndex 的节点系统比 LangChain 的 Document 更强大:
- Node 元数据:每个节点可以携带丰富的元数据(文件名、页码、创建时间等)
- 节点关系:自动维护 Previous/Next 关系,保留上下文
- 自定义解析器:支持代码、Markdown、HTML 等结构化解析
- 后处理:去重、过滤、合并等操作
🔄 数据更新与维护 (Data Management)
- 增量更新:只更新变化的文档,无需重建整个索引
- 版本控制:支持索引版本管理
- 数据清理:自动清理过期或无效数据
🎯 评估与优化 (Evaluation)
- 检索评估:评估检索质量(Hit Rate, MRR 等)
- 生成评估:评估生成质量(相关性、准确性)
- 端到端评估:评估整个 RAG 流程的效果
🤖 Agent 集成
- ReAct Agent:基于 LlamaIndex 的检索能力构建 Agent
- OpenAI Agent:与 OpenAI Function Calling 集成
- LangChain Agent:作为 LangChain Agent 的工具
第三部分:LlamaIndex 入门 (Python 实现)
我们将基于 LlamaIndex v0.10+ (引入了 llama-index-core 命名空间) 和 Python 3.10+ 进行演示。
3.1 环境准备
版本说明
本文基于以下版本编写:
- LlamaIndex: v0.10+ (最新稳定版)
- Python: 3.10+
- LangChain: 1.0+ (如需要集成)
安装步骤
# 1. 安装核心库(最新版本)
pip install llama-index
# 2. 安装 LLM 集成(选择你需要的)
pip install llama-index-llms-openai # OpenAI
pip install llama-index-llms-anthropic # Anthropic Claude
pip install llama-index-llms-ollama # 本地 Ollama
pip install llama-index-llms-azure-openai # Azure OpenAI
# 3. 安装 Embedding 集成
pip install llama-index-embeddings-openai # OpenAI Embeddings
pip install llama-index-embeddings-huggingface # HuggingFace
pip install llama-index-embeddings-voyageai # Voyage AI
# 4. 安装向量数据库集成(选择你需要的)
pip install llama-index-vector-stores-chroma # Chroma
pip install llama-index-vector-stores-pinecone # Pinecone
pip install llama-index-vector-stores-qdrant # Qdrant
pip install llama-index-vector-stores-weaviate # Weaviate
pip install llama-index-vector-stores-milvus # Milvus
# 5. 安装数据连接器(按需安装)
pip install llama-index-readers-file # 文件读取
pip install llama-index-readers-pdf # PDF
pip install llama-index-readers-web # 网页
pip install llama-index-readers-database # 数据库
pip install llama-index-readers-notion # Notion
pip install llama-index-readers-github # GitHub
pip install llama-index-readers-youtube # YouTube
# 6. 安装评估工具(可选)
pip install llama-index-evaluation
# 7. 如果需要与 LangChain 集成
pip install langchain langchain-openai
验证安装
import llama_index
print(llama_index.__version__) # 应该显示 0.10.x 或更高版本
设置 API Key:
import os
from llama_index.core import Settings
# 方式1:环境变量
os.environ["OPENAI_API_KEY"] = "sk-..."
# 方式2:直接在 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.chunk_size = 1024
Settings.chunk_overlap = 20
3.2 Hello World:最简单的 RAG
假设你有一个文件夹 data,里面放着你的 knowledge.txt。
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
# 1. 加载数据 (Data Ingestion)
# SimpleDirectoryReader 极其强大,支持 PDF, Markdown, Word 等多种格式
documents = SimpleDirectoryReader("data").load_data()
# 2. 建立索引 (Indexing)
# 这步会自动进行分块(Chunking)、Embedding 并存入内存向量库
index = VectorStoreIndex.from_documents(documents)
# 3. 创建查询引擎 (Query Engine)
query_engine = index.as_query_engine()
# 4. 提问
response = query_engine.query("这篇文档的核心观点是什么?")
print(response)
print(f"\n来源节点: {response.source_nodes}")
3.3 进阶示例:带持久化的 RAG
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, StorageContext, load_index_from_storage
import os
# 检查是否已有索引
persist_dir = "./storage"
if not os.path.exists(persist_dir):
# 首次运行:创建索引
documents = SimpleDirectoryReader("data").load_data()
index = VectorStoreIndex.from_documents(documents)
# 持久化
index.storage_context.persist(persist_dir=persist_dir)
print("索引已创建并保存")
else:
# 后续运行:加载索引
storage_context = StorageContext.from_defaults(persist_dir=persist_dir)
index = load_index_from_storage(storage_context)
print("索引已加载")
# 查询
query_engine = index.as_query_engine()
response = query_engine.query("文档中提到了哪些关键技术?")
print(response)
3.4 使用外部向量数据库(Chroma)
import chromadb
from llama_index.vector_stores.chroma import ChromaVectorStore
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, StorageContext
# 初始化 Chroma 客户端
chroma_client = chromadb.PersistentClient(path="./chroma_db")
chroma_collection = chroma_client.get_or_create_collection("knowledge_base")
# 创建向量存储
vector_store = ChromaVectorStore(chroma_collection=chroma_collection)
storage_context = StorageContext.from_defaults(vector_store=vector_store)
# 加载文档并建立索引
documents = SimpleDirectoryReader("data").load_data()
index = VectorStoreIndex.from_documents(
documents,
storage_context=storage_context
)
# 查询
query_engine = index.as_query_engine()
response = query_engine.query("查询问题")
print(response)
第四部分:LlamaIndex 切块方式详解
切块(Chunking)是 RAG 系统的核心环节,直接影响检索质量。LlamaIndex 提供了丰富的切块策略,比 LangChain 更加灵活和强大。
4.1 为什么切块很重要?
切块质量直接影响:
- ✅ 检索准确率:切块太小会丢失上下文,太大会导致噪声
- ✅ 召回率:合理的切块能提高相关内容的召回
- ✅ 生成质量:合适的块大小能提供足够的上下文给 LLM
LlamaIndex vs LangChain 切块对比:
| 特性 | LangChain | LlamaIndex |
|---|---|---|
| 切块策略 | 主要是文本分割 | 多种专业切块器 |
| 上下文保留 | 基础支持 | 自动维护节点关系 |
| 元数据 | 有限 | 丰富的元数据支持 |
| 代码切块 | 需要手动处理 | 原生支持代码切块 |
| 语义切块 | 不支持 | 支持语义感知切块 |
4.2 基础切块器:SentenceSplitter
最常用的切块方式,按句子分割。
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core import Settings, SimpleDirectoryReader, VectorStoreIndex
# 创建句子分割器
sentence_splitter = SentenceSplitter(
chunk_size=512, # 每个块的最大字符数
chunk_overlap=50, # 块之间的重叠字符数(保留上下文)
separator=" ", # 分隔符
paragraph_separator="\n\n", # 段落分隔符
secondary_chunking_regex="[^,.;。]+[,.;。]?", # 二级切分正则
)
# 应用到全局设置
Settings.node_parser = sentence_splitter
# 或者只用于特定索引
documents = SimpleDirectoryReader("data").load_data()
index = VectorStoreIndex.from_documents(
documents,
transformations=[sentence_splitter] # 指定切块器
)
# 查看切块结果
nodes = sentence_splitter.get_nodes_from_documents(documents)
print(f"共切分为 {len(nodes)} 个节点")
for i, node in enumerate(nodes[:3]): # 查看前3个节点
print(f"\n节点 {i+1}:")
print(f"文本长度: {len(node.text)}")
print(f"文本预览: {node.text[:100]}...")
print(f"元数据: {node.metadata}")
参数说明:
chunk_size: 建议 256-1024,根据文档类型调整chunk_overlap: 建议 10-20% 的 chunk_size,保留上下文连贯性separator: 默认空格,可根据语言调整
4.3 Token 切块器:TokenTextSplitter
按 Token 数量切块,更精确控制块大小。
from llama_index.core.node_parser import TokenTextSplitter
# Token 切块器(适合需要精确控制 Token 数的场景)
token_splitter = TokenTextSplitter(
chunk_size=1024, # 每个块的最大 Token 数
chunk_overlap=100, # 重叠 Token 数
separator=" ", # 分隔符
backup_separators=["\n", "\n\n", "。", "!", "?"], # 备用分隔符
)
Settings.node_parser = token_splitter
# 使用
documents = SimpleDirectoryReader("data").load_data()
index = VectorStoreIndex.from_documents(documents)
# 查看 Token 统计
nodes = token_splitter.get_nodes_from_documents(documents)
for node in nodes[:3]:
# 估算 Token 数(实际需要调用 LLM 的 tokenizer)
print(f"节点文本长度: {len(node.text)} 字符")
适用场景:
- 需要精确控制输入 LLM 的 Token 数
- 使用按 Token 计费的 API(如 OpenAI)
4.4 代码切块器:CodeSplitter
专门用于代码文件的切块,保持代码结构完整。
from llama_index.core.node_parser import CodeSplitter
# Python 代码切块
python_splitter = CodeSplitter(
language="python",
max_chars=2000, # 每个块的最大字符数
chunk_lines=40, # 每个块的最大行数
chunk_lines_overlap=5, # 重叠行数
)
# JavaScript 代码切块
js_splitter = CodeSplitter(
language="javascript",
max_chars=2000,
)
# TypeScript 代码切块
ts_splitter = CodeSplitter(
language="typescript",
max_chars=2000,
)
# 使用
from llama_index.readers.github import GithubRepositoryReader
loader = GithubRepositoryReader(
github_token="your_token",
owner="owner",
repo="repo",
filter_file_extensions=([".py"], GithubRepositoryReader.FilterType.INCLUDE)
)
documents = loader.load_data(branch="main")
# 为代码文档使用代码切块器
Settings.node_parser = python_splitter
index = VectorStoreIndex.from_documents(documents)
支持的编程语言:
- Python, JavaScript, TypeScript, Java, C++, Go, Rust, PHP, Ruby, Swift, Kotlin 等
4.5 语义切块器:SemanticSplitterNodeParser
基于语义相似度切块,比固定大小切块更智能。
from llama_index.core.node_parser import SemanticSplitterNodeParser
from llama_index.embeddings.openai import OpenAIEmbedding
# 需要 Embedding 模型
embed_model = OpenAIEmbedding(model="text-embedding-3-small")
# 创建语义切块器
semantic_splitter = SemanticSplitterNodeParser(
buffer_size=1, # 缓冲区大小
breakpoint_percentile_threshold=95, # 切分阈值(百分位)
embed_model=embed_model,
)
Settings.node_parser = semantic_splitter
# 使用
documents = SimpleDirectoryReader("data").load_data()
index = VectorStoreIndex.from_documents(documents)
# 语义切块会在语义变化大的地方切分,保持语义完整性
nodes = semantic_splitter.get_nodes_from_documents(documents)
print(f"语义切块结果: {len(nodes)} 个节点")
优势:
- ✅ 在语义边界切分,保持语义完整性
- ✅ 自动适应不同长度的段落
- ✅ 减少跨主题的块
适用场景:
- 长文档(书籍、报告)
- 多主题文档
- 需要保持语义连贯性的场景
4.6 Markdown 切块器:MarkdownNodeParser
专门处理 Markdown 文档,按标题层级切块。
from llama_index.core.node_parser import MarkdownNodeParser
# Markdown 切块器
markdown_splitter = MarkdownNodeParser()
# 使用
from llama_index.readers.file import MarkdownReader
loader = MarkdownReader()
documents = loader.load_data(file_path="documentation.md")
# 按 Markdown 结构切块
nodes = markdown_splitter.get_nodes_from_documents(documents)
# 查看切块结果(会保留标题层级信息)
for node in nodes[:3]:
print(f"标题: {node.metadata.get('heading', 'N/A')}")
print(f"内容: {node.text[:100]}...")
特点:
- 按标题(H1, H2, H3…)切分
- 保留标题层级信息在元数据中
- 适合技术文档、Wiki 等
4.7 HTML 切块器:HTMLNodeParser
处理 HTML 文档,按标签结构切块。
from llama_index.core.node_parser import HTMLNodeParser
# HTML 切块器
html_splitter = HTMLNodeParser(
tags=["p", "div", "section", "article"], # 要提取的标签
ignore_tags=["script", "style", "nav"], # 忽略的标签
)
# 使用
from llama_index.readers.web import BeautifulSoupWebReader
loader = BeautifulSoupWebReader()
documents = loader.load_data(urls=["https://example.com"])
nodes = html_splitter.get_nodes_from_documents(documents)
4.8 自定义切块器
当内置切块器不满足需求时,可以自定义。
from llama_index.core.node_parser import BaseNodeParser
from llama_index.core.schema import Document, NodeWithScore, TextNode
from typing import List
class CustomParagraphSplitter(BaseNodeParser):
"""按段落切块的自定义切块器"""
def __init__(self, chunk_size: int = 1000, chunk_overlap: int = 100):
self.chunk_size = chunk_size
self.chunk_overlap = chunk_overlap
def _get_nodes_from_documents(
self,
documents: List[Document],
show_progress: bool = False,
) -> List[TextNode]:
"""将文档切分为节点"""
nodes = []
for doc in documents:
# 按双换行符分割段落
paragraphs = doc.text.split("\n\n")
current_chunk = ""
for para in paragraphs:
# 如果当前块加上新段落不超过大小限制
if len(current_chunk) + len(para) <= self.chunk_size:
current_chunk += "\n\n" + para if current_chunk else para
else:
# 保存当前块
if current_chunk:
node = TextNode(
text=current_chunk,
metadata=doc.metadata.copy()
)
nodes.append(node)
# 开始新块(带重叠)
overlap_text = current_chunk[-self.chunk_overlap:] if current_chunk else ""
current_chunk = overlap_text + "\n\n" + para
# 保存最后一个块
if current_chunk:
node = TextNode(
text=current_chunk,
metadata=doc.metadata.copy()
)
nodes.append(node)
return nodes
# 使用自定义切块器
custom_splitter = CustomParagraphSplitter(chunk_size=1000, chunk_overlap=100)
Settings.node_parser = custom_splitter
documents = SimpleDirectoryReader("data").load_data()
index = VectorStoreIndex.from_documents(documents)
4.9 切块策略对比与选择
| 切块器 | 适用场景 | 优势 | 劣势 |
|---|---|---|---|
| SentenceSplitter | 通用文本、自然语言 | 简单、快速、保留句子完整性 | 可能切分跨段落 |
| TokenTextSplitter | 需要精确 Token 控制 | 精确控制 Token 数 | 可能切分句子 |
| CodeSplitter | 代码文件 | 保持代码结构、语法完整 | 只适用于代码 |
| SemanticSplitter | 长文档、多主题文档 | 语义感知、智能切分 | 需要 Embedding,速度较慢 |
| MarkdownNodeParser | Markdown 文档 | 保留文档结构 | 只适用于 Markdown |
| HTMLNodeParser | 网页内容 | 保留 HTML 结构 | 只适用于 HTML |
4.10 切块最佳实践
实践1:根据文档类型选择切块器
from llama_index.core.node_parser import (
SentenceSplitter,
CodeSplitter,
MarkdownNodeParser,
SemanticSplitterNodeParser
)
# 根据文件类型动态选择切块器
def get_node_parser(file_path: str):
if file_path.endswith(('.py', '.js', '.ts', '.java')):
return CodeSplitter(language="python", max_chars=2000)
elif file_path.endswith('.md'):
return MarkdownNodeParser()
elif file_path.endswith(('.html', '.htm')):
from llama_index.core.node_parser import HTMLNodeParser
return HTMLNodeParser()
else:
return SentenceSplitter(chunk_size=512, chunk_overlap=50)
# 使用
documents = SimpleDirectoryReader("data").load_data()
for doc in documents:
file_path = doc.metadata.get("file_path", "")
parser = get_node_parser(file_path)
nodes = parser.get_nodes_from_documents([doc])
# 处理节点...
实践2:调整切块大小
# 短文档(邮件、聊天记录)
short_doc_splitter = SentenceSplitter(
chunk_size=256,
chunk_overlap=25
)
# 中等文档(文章、报告)
medium_doc_splitter = SentenceSplitter(
chunk_size=512,
chunk_overlap=50
)
# 长文档(书籍、长报告)
long_doc_splitter = SentenceSplitter(
chunk_size=1024,
chunk_overlap=100
)
# 超长文档(使用语义切块)
from llama_index.core.node_parser import SemanticSplitterNodeParser
very_long_doc_splitter = SemanticSplitterNodeParser(
buffer_size=2,
breakpoint_percentile_threshold=90
)
实践3:保留上下文关系
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core import Settings
# 设置重叠以保留上下文
splitter = SentenceSplitter(
chunk_size=512,
chunk_overlap=50, # 10% 重叠
)
Settings.node_parser = splitter
# LlamaIndex 会自动维护节点之间的 Previous/Next 关系
documents = SimpleDirectoryReader("data").load_data()
index = VectorStoreIndex.from_documents(documents)
# 查询时可以获取相邻节点
retriever = index.as_retriever(similarity_top_k=3)
nodes = retriever.retrieve("查询问题")
# 获取相邻节点以提供更多上下文
for node in nodes:
if hasattr(node, 'prev_node') and node.prev_node:
print(f"前一个节点: {node.prev_node.text[:50]}...")
if hasattr(node, 'next_node') and node.next_node:
print(f"后一个节点: {node.next_node.text[:50]}...")
实践4:添加元数据到节点
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core import Document
# 创建带元数据的文档
documents = [
Document(
text="文档内容...",
metadata={
"file_name": "report.pdf",
"page_number": 1,
"section": "introduction",
"author": "张三",
"created_at": "2024-01-01"
}
)
]
# 切块时元数据会自动继承到节点
splitter = SentenceSplitter(chunk_size=512, chunk_overlap=50)
nodes = splitter.get_nodes_from_documents(documents)
# 每个节点都包含元数据
for node in nodes:
print(f"文件: {node.metadata.get('file_name')}")
print(f"页码: {node.metadata.get('page_number')}")
print(f"文本: {node.text[:100]}...")
实践5:切块质量检查
def analyze_chunks(nodes):
"""分析切块质量"""
chunk_sizes = [len(node.text) for node in nodes]
print(f"总节点数: {len(nodes)}")
print(f"平均块大小: {sum(chunk_sizes) / len(chunk_sizes):.0f} 字符")
print(f"最小块: {min(chunk_sizes)} 字符")
print(f"最大块: {max(chunk_sizes)} 字符")
print(f"块大小标准差: {__import__('statistics').stdev(chunk_sizes):.0f}")
# 检查是否有过小的块
small_chunks = [s for s in chunk_sizes if s < 50]
if small_chunks:
print(f"警告: 有 {len(small_chunks)} 个过小的块(<50字符)")
# 检查是否有过大的块
large_chunks = [s for s in chunk_sizes if s > 2000]
if large_chunks:
print(f"警告: 有 {len(large_chunks)} 个过大的块(>2000字符)")
# 使用
documents = SimpleDirectoryReader("data").load_data()
splitter = SentenceSplitter(chunk_size=512, chunk_overlap=50)
nodes = splitter.get_nodes_from_documents(documents)
analyze_chunks(nodes)
4.11 切块参数调优指南
# 参数调优示例
def optimize_chunking(documents, target_avg_size=512):
"""根据文档特征自动优化切块参数"""
# 分析文档
total_chars = sum(len(doc.text) for doc in documents)
avg_doc_size = total_chars / len(documents)
# 根据文档大小调整参数
if avg_doc_size < 1000:
# 短文档:小块、小重叠
chunk_size = 256
chunk_overlap = 25
elif avg_doc_size < 10000:
# 中等文档:标准参数
chunk_size = 512
chunk_overlap = 50
else:
# 长文档:大块、大重叠,或使用语义切块
chunk_size = 1024
chunk_overlap = 100
splitter = SentenceSplitter(
chunk_size=chunk_size,
chunk_overlap=chunk_overlap
)
nodes = splitter.get_nodes_from_documents(documents)
actual_avg = sum(len(n.text) for n in nodes) / len(nodes)
print(f"目标平均大小: {target_avg_size}")
print(f"实际平均大小: {actual_avg:.0f}")
print(f"参数: chunk_size={chunk_size}, overlap={chunk_overlap}")
return splitter
# 使用
documents = SimpleDirectoryReader("data").load_data()
optimized_splitter = optimize_chunking(documents)
Settings.node_parser = optimized_splitter
第五部分:进阶 —— LlamaIndex 的核心魔法
要精通 LlamaIndex,必须理解它区别于 LangChain 的三个核心概念:Nodes (节点)、Indices (索引) 和 Router (路由)。
5.1 节点关系与上下文管理
LlamaIndex 的核心优势之一是自动维护节点之间的关系,保留上下文连贯性。
LlamaIndex 允许我们将文档解析为带有丰富元数据(Metadata)的节点。
from llama_index.core.node_parser import SentenceSplitter, TokenTextSplitter, CodeSplitter
from llama_index.core import Settings, Document
# 示例1:句子分割器(适合自然语言)
sentence_splitter = SentenceSplitter(
chunk_size=512,
chunk_overlap=20,
)
# 示例2:Token 分割器(适合代码)
token_splitter = TokenTextSplitter(
chunk_size=1024,
chunk_overlap=50,
)
# 示例3:代码分割器(专门处理代码)
code_splitter = CodeSplitter(
language="python",
max_chars=2000,
)
# 全局设置
Settings.text_splitter = sentence_splitter
# 创建带元数据的文档
documents = [
Document(
text="这是文档内容...",
metadata={
"file_name": "report.pdf",
"page_number": 1,
"author": "张三",
"created_at": "2024-01-01"
}
)
]
# 建立索引,每个 Node 都会携带元数据
index = VectorStoreIndex.from_documents(documents)
# 查询时可以过滤元数据
retriever = index.as_retriever(
filters=MetadataFilters(
filters=[
ExactMatchFilter(key="author", value="张三")
]
)
)
5.2 持久化存储 (Storage Context)
默认索引在内存中,生产环境需要存到磁盘或向量数据库(如 Chroma, Qdrant)。
from llama_index.core import StorageContext, load_index_from_storage
# 保存
index.storage_context.persist(persist_dir="./storage")
# 读取
storage_context = StorageContext.from_defaults(persist_dir="./storage")
index = load_index_from_storage(storage_context)
5.3 路由模式 (Router Engine) —— LlamaIndex 的杀手锏
这是 LangChain 用户最应该学习的功能。假设你有一份关于"苹果公司财报"的文档,和一份关于"乔布斯传记"的文档。
- 问"2023年营收多少" -> 应该只查财报。
- 问"乔布斯的童年" -> 应该只查传记。
- 问"乔布斯对苹果营收的影响" -> 应该两边都查。
LlamaIndex 可以自动选择工具:
from llama_index.core.tools import QueryEngineTool, ToolMetadata
from llama_index.core.query_engine import RouterQueryEngine
from llama_index.core.selectors import LLMSingleSelector, LLMMultiSelector
# 假设 index_finance 和 index_bio 已经建立好
finance_tool = QueryEngineTool(
query_engine=index_finance.as_query_engine(),
metadata=ToolMetadata(
name="finance_data",
description="用于查询具体的财务数据、营收、利润等信息"
)
)
bio_tool = QueryEngineTool(
query_engine=index_bio.as_query_engine(),
metadata=ToolMetadata(
name="biography_data",
description="用于查询乔布斯的生平、轶事和个人生活"
)
)
# 方式1:单选路由(选择一个最相关的)
query_engine_single = RouterQueryEngine(
selector=LLMSingleSelector.from_defaults(),
query_engine_tools=[finance_tool, bio_tool]
)
# 方式2:多选路由(选择多个相关的,然后合并结果)
query_engine_multi = RouterQueryEngine(
selector=LLMMultiSelector.from_defaults(),
query_engine_tools=[finance_tool, bio_tool]
)
# 智能路由
response = query_engine_single.query("乔布斯在建立苹果初期赚了多少钱?")
print(response)
5.4 Tree Index:层级摘要索引
Tree Index 是 LlamaIndex 独有的强大功能,适合"总结全书"类查询。
from llama_index.core import TreeIndex, SimpleDirectoryReader
# 加载文档
documents = SimpleDirectoryReader("data").load_data()
# 创建树状索引
# Tree Index 会自底向上构建摘要树
tree_index = TreeIndex.from_documents(documents)
# 查询引擎
query_engine = tree_index.as_query_engine(
child_branch_factor=2, # 每个节点查询2个子节点
similarity_top_k=3 # 返回最相似的3个结果
)
# 适合的查询类型
response = query_engine.query("总结这份文档的核心内容")
print(response)
5.5 Keyword Table Index:关键词索引
不依赖 Embedding,适合精确匹配和结构化查询。
from llama_index.core import KeywordTableIndex, SimpleDirectoryReader
documents = SimpleDirectoryReader("data").load_data()
# 创建关键词表索引
keyword_index = KeywordTableIndex.from_documents(documents)
# 查询
query_engine = keyword_index.as_query_engine()
response = query_engine.query("文档中提到了哪些技术栈?")
print(response)
5.6 Knowledge Graph Index:知识图谱索引
提取实体和关系,构建知识图谱,适合关系查询。
from llama_index.core import KnowledgeGraphIndex, SimpleDirectoryReader
from llama_index.core.graph_stores import SimpleGraphStore
# 创建图存储
graph_store = SimpleGraphStore()
# 加载文档
documents = SimpleDirectoryReader("data").load_data()
# 创建知识图谱索引
kg_index = KnowledgeGraphIndex.from_documents(
documents,
max_triplets_per_chunk=10, # 每个块最多提取10个三元组
graph_store=graph_store
)
# 查询
query_engine = kg_index.as_query_engine()
response = query_engine.query("张三和谁有关系?")
print(response)
# 也可以直接查询图谱
g = kg_index.get_networkx_graph()
# 可视化或分析图谱
5.7 SubQuestionQueryEngine:子问题分解引擎
这是解决"跨文档复杂推理"的核心工具。
from llama_index.core.query_engine import SubQuestionQueryEngine
from llama_index.core.tools import QueryEngineTool, ToolMetadata
# 假设有多个文档索引
index1 = VectorStoreIndex.from_documents(docs1)
index2 = VectorStoreIndex.from_documents(docs2)
index3 = VectorStoreIndex.from_documents(docs3)
# 创建工具
tools = [
QueryEngineTool(
query_engine=index1.as_query_engine(),
metadata=ToolMetadata(name="文档1", description="关于财务策略的文档")
),
QueryEngineTool(
query_engine=index2.as_query_engine(),
metadata=ToolMetadata(name="文档2", description="关于市场分析的文档")
),
QueryEngineTool(
query_engine=index3.as_query_engine(),
metadata=ToolMetadata(name="文档3", description="关于技术架构的文档")
),
]
# 创建子问题查询引擎
query_engine = SubQuestionQueryEngine.from_defaults(
query_engine_tools=tools
)
# 复杂查询:会自动拆解为多个子问题
response = query_engine.query(
"对比这三个文档中提到的策略有何不同?"
)
print(response)
5.8 多步查询引擎 (MultiStepQueryEngine)
需要多轮检索和推理的复杂问题。
from llama_index.core.query_engine import MultiStepQueryEngine
from llama_index.core.query_engine.router_query_engine import RouterQueryEngine
# 创建多步查询引擎
multi_step_engine = MultiStepQueryEngine(
query_engine=query_engine, # 基础查询引擎
num_outputs=3, # 每步输出3个结果
early_stopping=True # 提前停止
)
response = multi_step_engine.query("深入分析这个问题的多个方面")
print(response)
5.9 重排序 (Re-ranking) 提高准确率
from llama_index.core.postprocessor import SentenceTransformerRerank
from llama_index.core import VectorStoreIndex
# 创建重排序器
reranker = SentenceTransformerRerank(
model="cross-encoder/ms-marco-MiniLM-L-6-v2",
top_n=3 # 只保留前3个最相关的结果
)
# 创建带重排序的查询引擎
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine(
similarity_top_k=10, # 先检索10个
node_postprocessors=[reranker] # 然后重排序到3个
)
response = query_engine.query("查询问题")
print(response)
第六部分:数据连接器实战示例
LlamaIndex 的强大之处在于它支持 100+ 种数据源。以下是常见场景的示例。
6.1 读取 PDF 文档
from llama_index.readers.pdf import PDFReader
from llama_index.core import VectorStoreIndex
# 读取 PDF
loader = PDFReader()
documents = loader.load_data(file_path="report.pdf")
# 建立索引
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()
response = query_engine.query("PDF 中的关键信息是什么?")
6.2 读取网页内容
from llama_index.readers.web import BeautifulSoupWebReader
from llama_index.core import VectorStoreIndex
# 读取网页
loader = BeautifulSoupWebReader()
documents = loader.load_data(urls=["https://example.com/article"])
# 建立索引
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()
response = query_engine.query("这篇文章讲了什么?")
6.3 连接数据库(PostgreSQL)
from llama_index.readers.database import DatabaseReader
from llama_index.core import VectorStoreIndex
import psycopg2
# 连接数据库
connection_string = "postgresql://user:password@localhost/dbname"
db = DatabaseReader(connection_string=connection_string)
# 查询数据
documents = db.load_data(
query="SELECT * FROM articles WHERE category = 'tech'",
max_rows=1000
)
# 建立索引
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()
response = query_engine.query("这些文章的共同主题是什么?")
6.4 读取 Notion 页面
from llama_index.readers.notion import NotionPageReader
from llama_index.core import VectorStoreIndex
# 需要 Notion Integration Token
integration_token = "your_notion_token"
reader = NotionPageReader(integration_token=integration_token)
# 读取页面
page_ids = ["page-id-1", "page-id-2"]
documents = reader.load_data(page_ids=page_ids)
# 建立索引
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()
response = query_engine.query("Notion 页面中的关键信息")
6.5 读取 GitHub 代码仓库
from llama_index.readers.github import GithubRepositoryReader
from llama_index.core import VectorStoreIndex
# 需要 GitHub Token
github_token = "your_github_token"
owner = "owner_name"
repo = "repo_name"
# 读取代码
loader = GithubRepositoryReader(
github_token=github_token,
owner=owner,
repo=repo,
filter_file_extensions=([".py", ".js", ".ts"], GithubRepositoryReader.FilterType.INCLUDE),
verbose=True
)
documents = loader.load_data(branch="main")
# 建立索引
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()
response = query_engine.query("这个项目的核心功能是什么?")
6.6 读取 YouTube 视频字幕
from llama_index.readers.youtube import YoutubeTranscriptReader
from llama_index.core import VectorStoreIndex
# 读取 YouTube 视频字幕
loader = YoutubeTranscriptReader()
documents = loader.load_data(ytlinks=["https://www.youtube.com/watch?v=VIDEO_ID"])
# 建立索引
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()
response = query_engine.query("这个视频的主要内容是什么?")
6.7 读取 S3 存储的文件
from llama_index.readers.s3 import S3Reader
from llama_index.core import VectorStoreIndex
# 配置 S3
s3_reader = S3Reader(
bucket="my-bucket",
aws_access_id="your_access_id",
aws_access_secret="your_access_secret"
)
# 读取文件
documents = s3_reader.load_data(key="documents/")
# 建立索引
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()
第六部分:LlamaIndex 的高级应用场景
6.1 结构化数据提取 (Extraction)
不仅仅是对话,LlamaIndex 擅长从非结构化文本中提取结构化数据。
from llama_index.core.program import LLMTextCompletionProgram
from llama_index.llms.openai import OpenAI
from pydantic import BaseModel, Field
from typing import List
# 定义数据结构
class Product(BaseModel):
name: str = Field(description="产品名称")
price: float = Field(description="价格")
category: str = Field(description="类别")
class ProductList(BaseModel):
products: List[Product] = Field(description="产品列表")
# 创建提取程序
llm = OpenAI(model="gpt-4")
program = LLMTextCompletionProgram.from_defaults(
output_cls=ProductList,
prompt_template_str="从以下文本中提取所有产品信息:\n{text}",
llm=llm
)
# 执行提取
text = """
我们有以下产品:
1. iPhone 15 Pro,价格 8999 元,属于手机类别
2. MacBook Pro,价格 12999 元,属于电脑类别
3. AirPods Pro,价格 1899 元,属于耳机类别
"""
output = program(text=text)
print(output.products)
# 输出: [Product(name='iPhone 15 Pro', price=8999.0, category='手机'), ...]
6.2 递归检索 (Recursive Retrieval)
这是解决表格和图片检索的关键。
from llama_index.core import VectorStoreIndex, Document
from llama_index.core.node_parser import SimpleNodeParser
# 创建包含表格摘要的文档
documents = [
Document(
text="这是一份财务报告。",
metadata={"type": "summary"}
),
Document(
text="详细财务数据表格:收入1000万,支出600万,利润400万。",
metadata={"type": "detail", "parent_id": "summary"}
)
]
# 建立索引
index = VectorStoreIndex.from_documents(documents)
# 递归检索:先检索到摘要,然后自动获取详细信息
query_engine = index.as_query_engine(
similarity_top_k=2,
# 自动处理父子关系
)
response = query_engine.query("财务报告中的利润是多少?")
print(response)
6.3 Text-to-SQL:自然语言查询数据库
from llama_index.core import SQLDatabase
from llama_index.core.query_engine import NLSQLTableQueryEngine
from sqlalchemy import create_engine
# 连接数据库
engine = create_engine("sqlite:///example.db")
sql_database = SQLDatabase(engine)
# 创建 Text-to-SQL 查询引擎
query_engine = NLSQLTableQueryEngine(
sql_database=sql_database,
tables=["users", "orders"] # 指定可查询的表
)
# 自然语言查询
response = query_engine.query("查询最近一个月订单金额超过1000的用户")
print(response)
print(f"执行的 SQL: {response.metadata['sql_query']}")
6.4 混合检索:向量 + 关键词
from llama_index.core import VectorStoreIndex, KeywordTableIndex
from llama_index.core.query_engine import RetrieverQueryEngine
from llama_index.core.retrievers import VectorIndexRetriever, KeywordTableSimpleRetriever
# 创建向量索引和关键词索引
vector_index = VectorStoreIndex.from_documents(documents)
keyword_index = KeywordTableIndex.from_documents(documents)
# 创建混合检索器
vector_retriever = VectorIndexRetriever(index=vector_index, similarity_top_k=3)
keyword_retriever = KeywordTableSimpleRetriever(index=keyword_index)
# 组合检索器(需要自定义实现)
class HybridRetriever:
def __init__(self, vector_retriever, keyword_retriever):
self.vector_retriever = vector_retriever
self.keyword_retriever = keyword_retriever
def retrieve(self, query_str):
vector_nodes = self.vector_retriever.retrieve(query_str)
keyword_nodes = self.keyword_retriever.retrieve(query_str)
# 合并并去重
all_nodes = {node.node_id: node for node in vector_nodes + keyword_nodes}
return list(all_nodes.values())
hybrid_retriever = HybridRetriever(vector_retriever, keyword_retriever)
query_engine = RetrieverQueryEngine.from_args(retriever=hybrid_retriever)
response = query_engine.query("查询问题")
6.5 流式响应
from llama_index.core import VectorStoreIndex
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine(streaming=True)
# 流式输出
response = query_engine.query("长问题...")
response.print_response_stream()
6.6 与 LangChain 协同工作
你不需要二选一。最佳实践是:用 LangChain 做 Agent 骨架,用 LlamaIndex 做超级检索工具。
from langchain.agents import initialize_agent, Tool
from langchain_openai import ChatOpenAI
from llama_index.core import VectorStoreIndex
# 1. 构建 LlamaIndex 引擎
llama_index = VectorStoreIndex.from_documents(docs)
llama_engine = llama_index.as_query_engine()
# 2. 包装成 LangChain Tool
tools = [
Tool(
name="LlamaIndex_Knowledge_Base",
func=lambda q: str(llama_engine.query(q)),
description="当用户询问关于私有知识库内容时使用此工具"
)
]
# 3. 在 LangChain Agent 中使用
llm = ChatOpenAI(model="gpt-4", temperature=0)
agent = initialize_agent(
tools=tools,
llm=llm,
agent="zero-shot-react-description",
verbose=True
)
result = agent.run("查询知识库中的信息,然后总结")
6.7 LlamaIndex Agent
LlamaIndex 也支持构建自己的 Agent。
from llama_index.core.agent import ReActAgent
from llama_index.core.tools import QueryEngineTool, ToolMetadata
from llama_index.llms.openai import OpenAI
# 创建工具
query_engine = index.as_query_engine()
tools = [
QueryEngineTool(
query_engine=query_engine,
metadata=ToolMetadata(
name="知识库",
description="用于查询公司内部知识库"
)
)
]
# 创建 Agent
llm = OpenAI(model="gpt-4")
agent = ReActAgent.from_tools(tools, llm=llm, verbose=True)
# 使用 Agent
response = agent.chat("帮我查询知识库,然后总结关键信息")
print(response)
第八部分:评估与优化
8.1 检索评估
评估检索质量是优化 RAG 系统的关键。
from llama_index.core.evaluation import (
RetrieverEvaluator,
generate_question_context_pairs,
EmbeddingFaithfulnessEvaluator,
EmbeddingRelevancyEvaluator
)
from llama_index.core import VectorStoreIndex
# 创建索引
index = VectorStoreIndex.from_documents(documents)
retriever = index.as_retriever(similarity_top_k=5)
# 生成评估数据集
eval_questions = [
"文档中提到了哪些技术?",
"核心观点是什么?",
"有哪些关键数据?"
]
# 评估检索器
evaluator = RetrieverEvaluator.from_metric_names(
["mrr", "hit_rate"] # Mean Reciprocal Rank, Hit Rate
)
results = []
for question in eval_questions:
result = evaluator.evaluate(question, retriever)
results.append(result)
print(f"问题: {question}")
print(f"MRR: {result.mrr}, Hit Rate: {result.hit_rate}")
# 计算平均指标
avg_mrr = sum(r.mrr for r in results) / len(results)
avg_hit_rate = sum(r.hit_rate for r in results) / len(results)
print(f"\n平均 MRR: {avg_mrr:.2f}, 平均 Hit Rate: {avg_hit_rate:.2f}")
8.2 生成评估
评估生成质量(相关性、准确性)。
from llama_index.core.evaluation import (
FaithfulnessEvaluator,
RelevancyEvaluator,
CorrectnessEvaluator
)
# 创建评估器
faithfulness_evaluator = FaithfulnessEvaluator()
relevancy_evaluator = RelevancyEvaluator()
# 评估生成结果
query = "文档的核心观点是什么?"
response = query_engine.query(query)
# 评估忠实度(是否基于检索内容)
faithfulness_result = faithfulness_evaluator.evaluate_response(
query=query,
response=response,
contexts=response.source_nodes
)
print(f"忠实度分数: {faithfulness_result.score}")
# 评估相关性(回答是否相关)
relevancy_result = relevancy_evaluator.evaluate_response(
query=query,
response=response
)
print(f"相关性分数: {relevancy_result.score}")
8.3 端到端评估
from llama_index.core.evaluation import DatasetGenerator, QueryResponseDataset
# 生成评估数据集
dataset_generator = DatasetGenerator.from_documents(documents)
eval_dataset = dataset_generator.generate_questions_from_nodes(num=10)
# 保存数据集
eval_dataset.save_json("eval_dataset.json")
# 加载数据集
eval_dataset = QueryResponseDataset.from_json("eval_dataset.json")
# 批量评估
from llama_index.core.evaluation import BatchEvalRunner
runner = BatchEvalRunner(
evaluators={
"faithfulness": faithfulness_evaluator,
"relevancy": relevancy_evaluator
},
workers=4 # 并行评估
)
results = runner.evaluate_dataset(
dataset=eval_dataset,
query_engine=query_engine
)
print(results)
8.4 优化策略
优化1:调整切块策略
from llama_index.core.node_parser import (
SentenceSplitter,
SemanticSplitterNodeParser
)
# 方式1:基于语义的分块(更智能)
semantic_splitter = SemanticSplitterNodeParser(
buffer_size=1,
breakpoint_percentile_threshold=95
)
# 方式2:自定义分块
class CustomNodeParser:
def get_nodes_from_documents(self, documents):
# 自定义逻辑
nodes = []
for doc in documents:
# 按段落分割
paragraphs = doc.text.split("\n\n")
for para in paragraphs:
nodes.append(Node(text=para, metadata=doc.metadata))
return nodes
Settings.node_parser = semantic_splitter
优化2:调整检索参数
# 增加检索数量,然后重排序
query_engine = index.as_query_engine(
similarity_top_k=20, # 先检索20个
node_postprocessors=[
SentenceTransformerRerank(top_n=5) # 重排序到5个
]
)
优化3:使用更好的 Embedding 模型
from llama_index.embeddings.openai import OpenAIEmbedding
# 使用更大的模型
Settings.embed_model = OpenAIEmbedding(
model="text-embedding-3-large" # 更大的模型,更好的效果
)
第九部分:实际项目案例
案例1:企业知识库问答系统
"""
场景:企业内部有大量技术文档、产品手册、会议记录等,
需要构建一个智能问答系统。
"""
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, StorageContext
from llama_index.vector_stores.chroma import ChromaVectorStore
from llama_index.core.query_engine import RouterQueryEngine
from llama_index.core.tools import QueryEngineTool, ToolMetadata
import chromadb
# 1. 数据准备:读取多个目录的文档
tech_docs = SimpleDirectoryReader("data/tech").load_data()
product_docs = SimpleDirectoryReader("data/product").load_data()
meeting_docs = SimpleDirectoryReader("data/meetings").load_data()
# 2. 为不同类别建立索引
chroma_client = chromadb.PersistentClient(path="./chroma_db")
tech_collection = chroma_client.get_or_create_collection("tech")
tech_vector_store = ChromaVectorStore(chroma_collection=tech_collection)
tech_index = VectorStoreIndex.from_documents(
tech_docs,
storage_context=StorageContext.from_defaults(vector_store=tech_vector_store)
)
product_collection = chroma_client.get_or_create_collection("product")
product_vector_store = ChromaVectorStore(chroma_collection=product_collection)
product_index = VectorStoreIndex.from_documents(
product_docs,
storage_context=StorageContext.from_defaults(vector_store=product_vector_store)
)
# 3. 创建路由查询引擎
tech_tool = QueryEngineTool(
query_engine=tech_index.as_query_engine(),
metadata=ToolMetadata(
name="技术文档",
description="用于查询技术文档、API文档、开发指南等"
)
)
product_tool = QueryEngineTool(
query_engine=product_index.as_query_engine(),
metadata=ToolMetadata(
name="产品文档",
description="用于查询产品手册、用户指南、功能介绍等"
)
)
router_engine = RouterQueryEngine.from_defaults(
query_engine_tools=[tech_tool, product_tool]
)
# 4. 使用
response = router_engine.query("如何配置API密钥?")
print(response)
案例2:多文档对比分析系统
"""
场景:需要对比多个文档的差异,比如对比不同版本的合同、
不同公司的财报等。
"""
from llama_index.core import VectorStoreIndex
from llama_index.core.query_engine import SubQuestionQueryEngine
from llama_index.core.tools import QueryEngineTool, ToolMetadata
# 为每个文档建立索引
doc1_index = VectorStoreIndex.from_documents(doc1)
doc2_index = VectorStoreIndex.from_documents(doc2)
doc3_index = VectorStoreIndex.from_documents(doc3)
# 创建工具
tools = [
QueryEngineTool(
query_engine=doc1_index.as_query_engine(),
metadata=ToolMetadata(name="文档1", description="2023年财报")
),
QueryEngineTool(
query_engine=doc2_index.as_query_engine(),
metadata=ToolMetadata(name="文档2", description="2024年财报")
),
QueryEngineTool(
query_engine=doc3_index.as_query_engine(),
metadata=ToolMetadata(name="文档3", description="行业报告")
),
]
# 创建子问题查询引擎
comparison_engine = SubQuestionQueryEngine.from_defaults(
query_engine_tools=tools
)
# 对比查询
response = comparison_engine.query(
"对比这三份文档中关于营收增长策略的差异"
)
print(response)
案例3:代码库智能问答
"""
场景:为大型代码库构建智能问答系统,帮助开发者快速理解代码。
"""
from llama_index.readers.github import GithubRepositoryReader
from llama_index.core import VectorStoreIndex
from llama_index.core.node_parser import CodeSplitter
# 读取代码
loader = GithubRepositoryReader(
github_token="your_token",
owner="owner",
repo="repo",
filter_file_extensions=([".py", ".js", ".ts"], GithubRepositoryReader.FilterType.INCLUDE)
)
documents = loader.load_data(branch="main")
# 使用代码分割器
Settings.node_parser = CodeSplitter(language="python", max_chars=2000)
# 建立索引
index = VectorStoreIndex.from_documents(documents)
# 查询
query_engine = index.as_query_engine()
response = query_engine.query("这个项目是如何处理用户认证的?")
print(response)
案例4:法律文档智能检索
"""
场景:律师事务所需要快速检索法律条文、案例等。
"""
from llama_index.core import VectorStoreIndex, TreeIndex
from llama_index.core.query_engine import RouterQueryEngine
# 法律条文需要精确匹配,使用关键词索引
from llama_index.core import KeywordTableIndex
law_index = KeywordTableIndex.from_documents(law_documents)
# 案例需要语义理解,使用向量索引
case_index = VectorStoreIndex.from_documents(case_documents)
# 创建路由引擎
law_tool = QueryEngineTool(
query_engine=law_index.as_query_engine(),
metadata=ToolMetadata(
name="法律条文",
description="用于精确查询法律条文、法规等"
)
)
case_tool = QueryEngineTool(
query_engine=case_index.as_query_engine(),
metadata=ToolMetadata(
name="案例",
description="用于查询相关案例、判例等"
)
)
router = RouterQueryEngine.from_defaults(
query_engine_tools=[law_tool, case_tool]
)
response = router.query("关于劳动法加班费的规定是什么?")
print(response)
案例5:实时文档更新系统
"""
场景:文档库每天新增大量文档,需要增量更新索引。
"""
from llama_index.core import VectorStoreIndex, Document
from llama_index.core.storage.storage_context import StorageContext
# 加载已有索引
storage_context = StorageContext.from_defaults(persist_dir="./storage")
index = load_index_from_storage(storage_context)
# 读取新文档
new_documents = SimpleDirectoryReader("data/new").load_data()
# 增量插入(只添加新文档,不重建整个索引)
for doc in new_documents:
index.insert(doc)
# 保存更新后的索引
index.storage_context.persist(persist_dir="./storage")
# 查询(包含新旧文档)
query_engine = index.as_query_engine()
response = query_engine.query("查询问题")
第十部分:LlamaIndex 应用场景总结
10.1 企业级应用场景
| 场景 | 核心需求 | LlamaIndex 优势 |
|---|---|---|
| 企业知识库 | 多源数据、智能检索 | Router Engine、多索引组合 |
| 客服系统 | 快速准确回答 | 向量检索 + 重排序 |
| 代码助手 | 理解代码库 | 代码分割器、结构化解析 |
| 法律检索 | 精确匹配 + 语义理解 | 关键词索引 + 向量索引 |
| 财务分析 | 多文档对比 | SubQuestion Engine |
| 文档总结 | 层级摘要 | Tree Index |
| 数据提取 | 结构化输出 | Extraction Program |
10.2 技术场景
| 场景 | 技术方案 |
|---|---|
| 大规模文档库 | 向量数据库(Chroma/Pinecone)+ 持久化 |
| 实时更新 | 增量插入 + 版本控制 |
| 多模态数据 | 图片/音频连接器 + 递归检索 |
| 混合查询 | 向量检索 + 关键词检索 + SQL 查询 |
| 复杂推理 | SubQuestion Engine + MultiStep Engine |
第十一部分:总结与建议
11.1 什么时候该用 LlamaIndex?
强烈推荐使用 LlamaIndex 的场景:
- ✅ 数据密集型应用:你的应用核心是处理大量、多样的数据
- ✅ 复杂查询需求:需要跨文档推理、对比分析、层级摘要
- ✅ 多数据源整合:需要从数据库、文件、API 等多种来源整合数据
- ✅ 检索质量要求高:LangChain 的 Retriever 无法满足准确率要求
- ✅ 需要增量更新:文档库频繁更新,需要高效的增量索引
- ✅ 结构化数据提取:需要从非结构化文本中提取结构化信息
可以考虑 LangChain 的场景:
- ⚠️ 简单 RAG:只需要基本的向量检索,不需要复杂索引
- ⚠️ Agent 为主:应用核心是 Agent 编排,检索只是辅助功能
- ⚠️ 快速原型:需要快速搭建原型,LangChain 上手更快
11.2 常见问题与解决方案
Q1: LlamaIndex 和 LangChain 可以一起用吗?
A: 完全可以!最佳实践是用 LangChain 做 Agent 编排,用 LlamaIndex 做检索工具。
Q2: 索引太大,内存不够怎么办?
A: 使用外部向量数据库(Chroma、Pinecone、Qdrant)进行持久化存储。
Q3: 检索结果不准确怎么办?
A:
- 调整分块策略(使用语义分割)
- 增加检索数量 + 重排序
- 使用混合检索(向量 + 关键词)
- 优化 Embedding 模型
Q4: 如何提高查询速度?
A:
- 使用更快的向量数据库(如 Qdrant)
- 减少 similarity_top_k
- 使用缓存机制
- 异步查询
Q5: 如何处理多语言文档?
A: 使用支持多语言的 Embedding 模型(如 multilingual-e5)。
Q6: 导入错误:ModuleNotFoundError
A:
# 错误:from llama_index import VectorStoreIndex
# 正确(v0.10+):
from llama_index.core import VectorStoreIndex
Q9: 向量维度不匹配
A:
# 确保 Embedding 模型和向量数据库的维度一致
# OpenAI text-embedding-3-small: 1536 维
# OpenAI text-embedding-3-large: 3072 维
11.4 最佳实践
实践1:合理设置分块大小
# 对于长文档(如书籍)
Settings.chunk_size = 1024
Settings.chunk_overlap = 100
# 对于短文档(如邮件、聊天记录)
Settings.chunk_size = 256
Settings.chunk_overlap = 50
# 对于代码
from llama_index.core.node_parser import CodeSplitter
Settings.node_parser = CodeSplitter(language="python", max_chars=2000)
实践2:使用元数据过滤
from llama_index.core.vector_stores import MetadataFilters, ExactMatchFilter
# 创建带元数据的文档
documents = [
Document(
text="内容...",
metadata={"department": "tech", "year": 2024}
)
]
# 查询时过滤
retriever = index.as_retriever(
filters=MetadataFilters(
filters=[
ExactMatchFilter(key="department", value="tech"),
ExactMatchFilter(key="year", value=2024)
]
)
)
实践3:错误处理和重试
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10))
def query_with_retry(query_engine, query):
try:
response = query_engine.query(query)
return response
except Exception as e:
print(f"查询失败: {e}")
raise
# 使用
response = query_with_retry(query_engine, "查询问题")
实践4:日志记录
import logging
from llama_index.core import Settings
# 启用日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("llama_index")
# 记录查询
def logged_query(query_engine, query):
logger.info(f"查询: {query}")
response = query_engine.query(query)
logger.info(f"响应: {response}")
return response
实践5:性能监控
import time
from llama_index.core.callbacks import CallbackManager, LlamaDebugHandler
# 启用调试回调
debug_handler = LlamaDebugHandler()
callback_manager = CallbackManager([debug_handler])
Settings.callback_manager = callback_manager
# 查询并监控
start_time = time.time()
response = query_engine.query("查询问题")
end_time = time.time()
print(f"查询耗时: {end_time - start_time:.2f}秒")
print(f"检索到的节点数: {len(response.source_nodes)}")
实践6:缓存机制
from llama_index.core.cache import SimpleCache
from llama_index.core import Settings
# 启用缓存
cache = SimpleCache()
Settings.cache = cache
# 相同查询会直接返回缓存结果
response1 = query_engine.query("查询问题") # 第一次,会执行
response2 = query_engine.query("查询问题") # 第二次,从缓存返回
结语
LlamaIndex 不仅仅是一个库,它代表了一种**“以数据为中心” (Data-Centric) 的 AI 开发理念**。在 AI 应用日益复杂的今天,掌握它将是你构建企业级知识库应用的杀手锏。
核心要点回顾:
- LlamaIndex 专注于数据:它是数据到 LLM 的桥梁,而 LangChain 专注于流程编排
- 丰富的索引类型:不仅仅是向量检索,还有树状、关键词、知识图谱等
- 智能查询引擎:Router、SubQuestion、MultiStep 等引擎解决复杂查询问题
- 强大的数据连接:支持 100+ 种数据源
- 生产级特性:持久化、增量更新、评估优化等
更多推荐



所有评论(0)