课程0基础Agent开发课 / 框架与平台对比 / LlamaIndex深度使用-RAG专项框架指南
— 21 min read

LlamaIndex深度使用-RAG专项框架指南

在知识库问答、企业文档检索等场景中,LlamaIndex 的设计粒度比 LangChain 更细。LangChain 的 RAG(检索增强生成:先从知识库检索相关文档,再让 LLM 基于这些文档回答问题)实现偏向快速拼装,LlamaIndex 则将整个数据流水线拆分成清晰的层次,每一层都有独立的接口可以替换和扩展。

LlamaIndex 深度使用:RAG 专项框架指南

在知识库问答、企业文档检索等场景中,LlamaIndex 的设计粒度比 LangChain 更细。LangChain 的 RAG(检索增强生成:先从知识库检索相关文档,再让 LLM 基于这些文档回答问题)实现偏向快速拼装,LlamaIndex 则将整个数据流水线拆分成清晰的层次,每一层都有独立的接口可以替换和扩展。

本文介绍 LlamaIndex 的核心概念、三种主要索引类型、高级检索策略,以及与 LangChain 的集成方式,最后给出选型建议。

1. 核心概念体系

LlamaIndex核心组件图
LlamaIndex 核心组件层级关系 — Document -> Node -> Index -> Retriever -> QueryEngine

LlamaIndex 用一套清晰的术语描述 RAG 流水线中的每个环节。理解这些术语,是使用框架的前提。

Document:数据源的原始表示。一个 PDF 文件、一个网页、一条数据库记录,都可以被加载为 Document 对象。Document 携带内容(text)和元数据(metadata),元数据可以存储文件名、创建时间、来源 URL 等信息。

Node:Document 切块后的最小单位。一个 Document 经过 NodeParser(分块器:把长文档切成小段落)处理后,会被切分成多个 Node,每个 Node 对应一段可以被独立检索的文本片段。Node 之间可以保留前后关系(prev_nodenext_node),这对于上下文感知检索很有价值。

Index:建立在 Node 集合上的索引结构。Index 决定了数据以何种方式被组织和检索。不同的 Index 类型对应不同的检索策略。

QueryEngine:查询引擎,接收用户问题,内部完成检索和生成两步,返回最终答案。是对外暴露的主要接口。

Retriever:检索器,负责从 Index 中找出与问题最相关的 Node。Retriever 可以单独使用,也可以组合成复杂的检索策略。

原始数据
PDF / 数据库 / 网页

Document Loader
加载为 Document 对象

Node Parser
切块为 Node

Index
建立索引结构

Retriever
检索相关 Node

Response Synthesizer
生成最终回答

用户得到答案

用户提问

2. 三种 Index 类型

LlamaIndex 提供多种 Index 类型,每种类型对应不同的数据组织方式和检索逻辑。

2.1. VectorStoreIndex:语义检索的基础

最常用的类型。将所有 Node 的文本转换为向量,存储在向量数据库中,查询时通过向量相似度找到最相关的 Node。

python
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,让模型基于完整的文档内容生成答案。适合"总结这份文档的主要内容"这类需要通读全文才能回答的问题。

python
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 公司有什么合作关系"。

python
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 把复杂问题拆解成若干个子问题,每个子问题在最合适的索引上独立检索,最后合并所有子问题的答案生成最终回复。

python
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)

运行时可以看到类似如下的中间步骤输出:

code
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 可以根据问题内容自动选择最合适的索引进行检索,无需用户手动指定。

python
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 的整体编排和工具调用管理。

python
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. 多文档问答系统完整示例

以下是一个更完整的多文档问答系统,综合运用了前面介绍的各项能力。

python
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 对话协作。

本页目录