LlamaIndex深度使用-RAG专项框架指南
在知识库问答、企业文档检索等场景中,LlamaIndex 的设计粒度比 LangChain 更细。LangChain 的 RAG(检索增强生成:先从知识库检索相关文档,再让 LLM 基于这些文档回答问题)实现偏向快速拼装,LlamaIndex 则将整个数据流水线拆分成清晰的层次,每一层都有独立的接口可以替换和扩展。
LlamaIndex 深度使用:RAG 专项框架指南
在知识库问答、企业文档检索等场景中,LlamaIndex 的设计粒度比 LangChain 更细。LangChain 的 RAG(检索增强生成:先从知识库检索相关文档,再让 LLM 基于这些文档回答问题)实现偏向快速拼装,LlamaIndex 则将整个数据流水线拆分成清晰的层次,每一层都有独立的接口可以替换和扩展。
本文介绍 LlamaIndex 的核心概念、三种主要索引类型、高级检索策略,以及与 LangChain 的集成方式,最后给出选型建议。
1. 核心概念体系
LlamaIndex 核心组件层级关系 — Document -> Node -> Index -> Retriever -> QueryEngine
LlamaIndex 用一套清晰的术语描述 RAG 流水线中的每个环节。理解这些术语,是使用框架的前提。
Document:数据源的原始表示。一个 PDF 文件、一个网页、一条数据库记录,都可以被加载为 Document 对象。Document 携带内容(text)和元数据(metadata),元数据可以存储文件名、创建时间、来源 URL 等信息。
Node:Document 切块后的最小单位。一个 Document 经过 NodeParser(分块器:把长文档切成小段落)处理后,会被切分成多个 Node,每个 Node 对应一段可以被独立检索的文本片段。Node 之间可以保留前后关系(prev_node、next_node),这对于上下文感知检索很有价值。
Index:建立在 Node 集合上的索引结构。Index 决定了数据以何种方式被组织和检索。不同的 Index 类型对应不同的检索策略。
QueryEngine:查询引擎,接收用户问题,内部完成检索和生成两步,返回最终答案。是对外暴露的主要接口。
Retriever:检索器,负责从 Index 中找出与问题最相关的 Node。Retriever 可以单独使用,也可以组合成复杂的检索策略。
2. 三种 Index 类型
LlamaIndex 提供多种 Index 类型,每种类型对应不同的数据组织方式和检索逻辑。
2.1. VectorStoreIndex:语义检索的基础
最常用的类型。将所有 Node 的文本转换为向量,存储在向量数据库中,查询时通过向量相似度找到最相关的 Node。
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.core.node_parser import SentenceSplitter
# 加载目录下所有文档
documents = SimpleDirectoryReader("./docs").load_data()
# SentenceSplitter 按句子边界切块,避免在句子中间截断
# chunk_size=512 控制每个 Node 的最大 token 数
# chunk_overlap=50 相邻 Node 之间的重叠 token,保证上下文连续性
splitter = SentenceSplitter(chunk_size=512, chunk_overlap=50)
nodes = splitter.get_nodes_from_documents(documents)
# 建立向量索引,内部自动调用 Embedding API 生成向量
index = VectorStoreIndex(nodes)
# as_query_engine() 返回一个完整的查询引擎
query_engine = index.as_query_engine(similarity_top_k=3)
response = query_engine.query("产品的退款政策是什么?")
print(response)
# 查看检索到了哪些 Node(调试时非常有用)
for node in response.source_nodes:
print(f"来源: {node.metadata.get('file_name', '未知')}")
print(f"分数: {node.score:.3f}")
print(f"内容: {node.text[:100]}...")
print("---")
2.2. SummaryIndex:全文摘要检索
SummaryIndex 不做向量检索,而是将所有 Node 依次传入 LLM,让模型基于完整的文档内容生成答案。适合"总结这份文档的主要内容"这类需要通读全文才能回答的问题。
from llama_index.core import SummaryIndex
# SummaryIndex 按顺序遍历所有 Node,适合全文摘要类问题
# 注意:Node 数量过多时 LLM 调用成本会很高
summary_index = SummaryIndex(nodes)
# response_mode="tree_summarize" 使用树形摘要:
# 先对每批 Node 生成局部摘要,再对局部摘要生成最终摘要
# 避免超出 LLM 上下文窗口
query_engine = summary_index.as_query_engine(
response_mode="tree_summarize"
)
response = query_engine.query("这份合同的主要条款是什么?")
print(response)
2.3. KnowledgeGraphIndex:结构化关系检索
将文档中的实体和关系提取出来,构建知识图谱(Knowledge Graph,知识图谱:以节点表示实体、以连线表示关系的结构化知识网络,例如"OpenAI"-"投资方"-"微软")。适合需要理解实体间关系的查询,例如"A 公司与 B 公司有什么合作关系"。
from llama_index.core import KnowledgeGraphIndex
from llama_index.core.graph_stores import SimpleGraphStore
# KnowledgeGraphIndex 在建索引时会调用 LLM 提取实体和关系
# max_triplets_per_chunk 控制每个 Node 最多提取多少个三元组
graph_store = SimpleGraphStore()
kg_index = KnowledgeGraphIndex(
nodes,
graph_store=graph_store,
max_triplets_per_chunk=5,
include_embeddings=True, # 同时建向量索引,支持混合检索
)
query_engine = kg_index.as_query_engine(
include_text=True,
response_mode="tree_summarize",
)
response = query_engine.query("OpenAI 和微软有什么关系?")
print(response)
三种 Index 类型对比:
| Index 类型 | 检索方式 | 适用场景 | 成本 |
|---|---|---|---|
| VectorStoreIndex | 向量相似度 | 精确问答、段落检索 | 低(只调 Embedding) |
| SummaryIndex | 顺序遍历全文 | 全文摘要、通读类问题 | 高(多次 LLM 调用) |
| KnowledgeGraphIndex | 图谱遍历 + 向量 | 实体关系查询 | 高(建索引时提取关系) |
3. 高级检索:SubQuestionQueryEngine
面对复杂问题,单次向量检索往往不够精准。例如"比较 A 产品和 B 产品在价格和功能上的差异",这个问题实际上包含四个子问题,针对不同数据源的检索,单一的向量查询无法覆盖所有信息。
SubQuestionQueryEngine 的策略是:先让 LLM 把复杂问题拆解成若干个子问题,每个子问题在最合适的索引上独立检索,最后合并所有子问题的答案生成最终回复。
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.core.tools import QueryEngineTool, ToolMetadata
from llama_index.core.query_engine import SubQuestionQueryEngine
# 两个不同的文档集合,分别建立索引
product_a_docs = SimpleDirectoryReader("./product_a").load_data()
product_b_docs = SimpleDirectoryReader("./product_b").load_data()
index_a = VectorStoreIndex.from_documents(product_a_docs)
index_b = VectorStoreIndex.from_documents(product_b_docs)
# 把每个 QueryEngine 包装成 Tool,并提供清晰的描述
# LLM 会根据描述决定把子问题路由到哪个 Tool
tools = [
QueryEngineTool(
query_engine=index_a.as_query_engine(),
metadata=ToolMetadata(
name="product_a_engine",
description="包含 A 产品的价格、功能、规格和用户手册",
),
),
QueryEngineTool(
query_engine=index_b.as_query_engine(),
metadata=ToolMetadata(
name="product_b_engine",
description="包含 B 产品的价格、功能、规格和用户手册",
),
),
]
# SubQuestionQueryEngine 会自动拆解问题、并行检索、汇总答案
sub_question_engine = SubQuestionQueryEngine.from_defaults(
query_engine_tools=tools,
use_async=True, # 并行执行子问题,减少总耗时
verbose=True, # 打印子问题拆解过程,便于调试
)
response = sub_question_engine.query(
"比较 A 产品和 B 产品在价格和核心功能上的差异"
)
print(response)
运行时可以看到类似如下的中间步骤输出:
Generated 4 sub questions.
[product_a_engine] Q: A 产品的价格是多少?
[product_b_engine] Q: B 产品的价格是多少?
[product_a_engine] Q: A 产品有哪些核心功能?
[product_b_engine] Q: B 产品有哪些核心功能?
4. RouterQueryEngine:自动路由
当拥有多个针对不同主题的索引时,RouterQueryEngine 可以根据问题内容自动选择最合适的索引进行检索,无需用户手动指定。
from llama_index.core.query_engine import RouterQueryEngine
from llama_index.core.selectors import LLMSingleSelector
# 准备不同主题的索引
faq_index = VectorStoreIndex.from_documents(faq_docs)
policy_index = VectorStoreIndex.from_documents(policy_docs)
product_index = VectorStoreIndex.from_documents(product_docs)
# 同样包装成 Tool,描述越精确,路由越准确
tools = [
QueryEngineTool(
query_engine=faq_index.as_query_engine(),
metadata=ToolMetadata(
name="faq",
description="常见问题解答,包括账号注册、密码找回、基础操作",
),
),
QueryEngineTool(
query_engine=policy_index.as_query_engine(),
metadata=ToolMetadata(
name="policy",
description="公司政策文件,包括退款政策、隐私条款、服务协议",
),
),
QueryEngineTool(
query_engine=product_index.as_query_engine(),
metadata=ToolMetadata(
name="product",
description="产品技术文档,包括功能说明、API 接口、集成指南",
),
),
]
# LLMSingleSelector 使用 LLM 判断应该路由到哪个工具
# 也可以使用 EmbeddingSingleSelector,用向量相似度做路由,成本更低
router_engine = RouterQueryEngine(
selector=LLMSingleSelector.from_defaults(),
query_engine_tools=tools,
verbose=True,
)
# 自动路由到 policy 索引
response = router_engine.query("你们的退款政策是什么?")
print(response)
# 自动路由到 faq 索引
response = router_engine.query("怎么找回密码?")
print(response)
5. 与 LangChain 集成
LlamaIndex 和 LangChain 不是非此即彼的关系,两者可以互补组合:LlamaIndex 负责精细的 RAG 检索,LangChain 负责 Agent 的整体编排和工具调用管理。
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.core.langchain_helpers.agents import (
IndexToolConfig,
LlamaIndexTool,
)
from langchain_openai import ChatOpenAI
from langchain.agents import initialize_agent, AgentType
# 第一步:用 LlamaIndex 建立文档索引
documents = SimpleDirectoryReader("./knowledge_base").load_data()
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine(similarity_top_k=5)
# 第二步:把 LlamaIndex 查询引擎包装成 LangChain Tool
tool_config = IndexToolConfig(
query_engine=query_engine,
name="knowledge_base_search",
description=(
"搜索公司内部知识库。当问题涉及公司产品、政策、流程时使用此工具。"
"输入应该是一个完整的自然语言问题。"
),
)
llama_tool = LlamaIndexTool.from_tool_config(tool_config)
# 第三步:在 LangChain Agent 中注册这个工具
llm = ChatOpenAI(model="gpt-4o", temperature=0)
agent = initialize_agent(
tools=[llama_tool],
llm=llm,
agent=AgentType.OPENAI_FUNCTIONS,
verbose=True,
)
# Agent 会自动判断何时调用知识库检索
response = agent.run("我们公司对于客户数据的保留期限是多久?")
print(response)
这种组合的典型使用场景是:用 LlamaIndex 构建精细的多文档检索系统(包括分层索引、混合检索、子问题分解),再用 LangChain 在外层添加 Agent 逻辑、工具调用、对话历史管理等能力。
6. 多文档问答系统完整示例
以下是一个更完整的多文档问答系统,综合运用了前面介绍的各项能力。
import os
from pathlib import Path
from llama_index.core import (
VectorStoreIndex,
SummaryIndex,
SimpleDirectoryReader,
StorageContext,
load_index_from_storage,
)
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.tools import QueryEngineTool, ToolMetadata
from llama_index.core.query_engine import RouterQueryEngine
from llama_index.core.selectors import LLMSingleSelector
from llama_index.llms.openai import OpenAI
from llama_index.core import Settings
def build_document_qa_system(docs_dir: str, persist_dir: str):
"""
构建支持精确问答和全文摘要的多文档问答系统。
使用持久化存储避免每次重新构建索引。
"""
# 配置全局 LLM 和 Embedding 模型
Settings.llm = OpenAI(model="gpt-4o-mini", temperature=0)
persist_path = Path(persist_dir)
# 检查是否已有持久化的索引,有则直接加载,避免重复调用 Embedding API
if persist_path.exists():
print("从磁盘加载已有索引...")
storage_context = StorageContext.from_defaults(persist_dir=persist_dir)
vector_index = load_index_from_storage(
storage_context, index_id="vector_index"
)
summary_index = load_index_from_storage(
storage_context, index_id="summary_index"
)
else:
print("首次构建索引...")
documents = SimpleDirectoryReader(docs_dir).load_data()
# 切块:chunk_size=1024 比默认的 512 更大,减少检索次数
splitter = SentenceSplitter(chunk_size=1024, chunk_overlap=100)
nodes = splitter.get_nodes_from_documents(documents)
print(f"共加载 {len(documents)} 份文档,切分为 {len(nodes)} 个 Node")
# 建立两种索引,分别服务不同类型的查询
vector_index = VectorStoreIndex(nodes, index_id="vector_index")
summary_index = SummaryIndex(nodes, index_id="summary_index")
# 持久化到磁盘
vector_index.storage_context.persist(persist_dir=persist_dir)
summary_index.storage_context.persist(persist_dir=persist_dir)
print(f"索引已保存至 {persist_dir}")
# 向量检索引擎:适合精确的事实性问题
vector_engine = vector_index.as_query_engine(
similarity_top_k=5,
response_mode="compact", # 合并多个 Node 再生成答案,减少 LLM 调用次数
)
# 摘要检索引擎:适合需要通读全文的问题
summary_engine = summary_index.as_query_engine(
response_mode="tree_summarize",
)
# 用 RouterQueryEngine 自动选择合适的引擎
router_engine = RouterQueryEngine(
selector=LLMSingleSelector.from_defaults(),
query_engine_tools=[
QueryEngineTool(
query_engine=vector_engine,
metadata=ToolMetadata(
name="vector_search",
description=(
"适合精确查询,例如查找特定条款、数字、日期、人名等。"
"输入应该是具体的问题。"
),
),
),
QueryEngineTool(
query_engine=summary_engine,
metadata=ToolMetadata(
name="summary_search",
description=(
"适合全文摘要类问题,例如总结文档主旨、概述主要内容。"
"当问题需要通读整篇文档才能回答时使用。"
),
),
),
],
verbose=True,
)
return router_engine
# 使用示例
engine = build_document_qa_system(
docs_dir="./company_docs",
persist_dir="./index_storage",
)
questions = [
"公司在上海的办公室地址是什么?", # 精确问答 → 路由到 vector_search
"请总结一下公司的企业文化和价值观。", # 摘要类 → 路由到 summary_search
"差旅报销的上限金额是多少?", # 精确问答 → 路由到 vector_search
]
for q in questions:
print(f"\n问题:{q}")
response = engine.query(q)
print(f"回答:{response}")
7. 选型建议
选 LlamaIndex 的场景:
- 核心需求是文档检索和知识库问答,需要对 RAG 流水线的每个环节进行调优
- 文档数量多、结构复杂,需要层次化索引或子问题分解
- 需要持久化索引到向量数据库(Pinecone、Weaviate、Qdrant),避免重复构建
- 需要混合检索(向量 + BM25 关键词),或者需要重排序(Reranker,对检索结果用更精确的模型重新打分排序,提升准确率)
选 LangChain 的场景:
- 需要复杂的 Agent 逻辑,例如多工具编排、条件分支、循环执行
- 需要对话历史管理和多轮会话能力
- 团队已有 LangChain 积累,RAG 是其中一个功能而非全部
两者组合的场景:
- 构建企业级知识库助手:LlamaIndex 做精细检索,LangChain 做 Agent 编排,各取所长
| 维度 | LlamaIndex | LangChain |
|---|---|---|
| RAG 能力 | 精细,层次化,可扩展 | 基础,适合快速起步 |
| Agent 能力 | 有但较弱 | 强,生态丰富 |
| 高级检索(子问题、路由) | 原生支持 | 需要手动实现 |
| 索引持久化 | 内置,方便 | 依赖外部向量库 |
| 学习曲线 | 概念稍多,但分层清晰 | 入门快,但深度使用复杂 |
小结: LlamaIndex 的优势集中在 RAG——三种 Index 类型针对不同检索需求,SubQuestionQueryEngine 和 RouterQueryEngine 处理复杂查询,持久化存储省去重复建索引的开销。以文档检索为核心的系统,值得把它用透。下一篇讲微软的 AutoGen 框架,专注于多 Agent 对话协作。