LlamaIndex 语法详解与 RAG 系统构建指南

前言

LlamaIndex 是一个专为大语言模型设计的数据框架,核心能力是将私有数据(文档、数据库、API)与 LLM 连接起来。本文基于 4 个实战脚本,系统讲解 LlamaIndex 的核心语法和关键概念。


1. 环境配置与全局设置

1.1 基础依赖

from dotenv import load_dotenv
load_dotenv()

import os
from llama_index.core import Settings
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.llms.openai_like import OpenAILike
from llama_index.embeddings.dashscope import DashScopeEmbedding, DashScopeTextEmbeddingModels

1.2 Settings 全局配置

Settings 是 LlamaIndex 的核心配置对象,设置一次,全局生效:

# LLM 配置
Settings.llm = OpenAILike(
    model="qwen-plus",
    api_base="https://dashscope.aliyuncs.com/compatible-mode/v1",
    api_key=os.getenv("DASHCOPE_KEY"),
    is_chat_model=True
)

# Embedding 配置
Settings.embed_model = DashScopeEmbedding(
    model_name=DashScopeTextEmbeddingModels.TEXT_EMBEDDING_V3,
    embed_batch_size=6,
    embed_input_length=8192
)
参数 含义 说明
model LLM 模型名 qwen-plus / gpt-4 / claude 等
api_base API 地址 兼容 OpenAI 协议的地址
is_chat_model 是否对话模型 True 使用 chat 接口
model_name Embedding 模型 text-embedding-v3 等
embed_batch_size 批量大小 DashScope 限制最大 10
embed_input_length 最大输入长度 单条文本 token 上限

踩坑:DashScopeEmbedding 底层 SDK 需要 DASHSCOPE_API_KEY 环境变量,如果 .env 中变量名不同,需显式设置 dashscope.api_key = os.getenv("DASHCOPE_KEY")


2. 文档加载(Reader)

2.1 SimpleDirectoryReader:基础加载器

# 基础用法
documents = SimpleDirectoryReader("data").load_data()

# 进阶用法
documents = SimpleDirectoryReader(
    "data",
    required_exts=[".txt", ".pdf"],  # 只读取特定格式
    recursive=True,                   # 递归子目录
    exclude_hidden=True               # 排除隐藏文件
).load_data()

支持格式:.txt.pdf.docx.csv.pptx.html.json 等。

2.2 SmartPDFLoader:智能 PDF 解析

普通 PDF 解析只提取纯文本,丢失布局信息。SmartPDFLoader 通过 LLMSherpa 服务识别 PDF 的章节、段落、表格结构:

from llama_index.readers.smart_pdf_loader import SmartPDFLoader

llmsherpa_api_url = "https://readers.llmsherpa.com/api/document/developer/parseDocument?renderFormat=all"
pdf_url = "data/1910.13461v1.pdf"  # 支持 URL 或本地路径
documents = SmartPDFLoader(llmsherpa_api_url=llmsherpa_api_url).load_data(pdf_url)

布局识别效果

普通解析 SmartPDFLoader
纯文本,丢失结构 保留章节、段落、表格层级
表格变纯文本 保留表格行列关系
无元数据 每个 chunk 带 chunk_type 标签

2.3 自定义 Reader

当内置加载器不满足需求时,继承 BaseReader 实现自定义加载器:

from llama_index.core.readers.base import BaseReader
from llama_index.core.schema import Document
from typing import List, Optional, Dict

class CustomReader(BaseReader):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
    
    def load_data(
        self, file_path: str, extra_info: Optional[Dict] = None
    ) -> List[Document]:
        # 自定义解析逻辑
        results = []
        # ... 解析文件 ...
        document = Document(
            text="解析后的文本",
            extra_info={"source": file_path, "type": "custom"}
        )
        results.append(document)
        return results

关键规则

  1. 继承 BaseReader
  2. 实现 load_data() 方法
  3. 返回 List[Document]
  4. 通过 extra_info 传递元数据

3. 文档与节点(Document vs Node)

3.1 Document:完整文档对象

from llama_index.core import Document

doc = Document(
    text="CEO 可以直接请假,无需向直接领导汇报",
    metadata={
        "author": "wilson yin",
        "title": "CEO 请假申请",
        "id": "1234567890"
    }
)

Document 的属性

属性 类型 说明
text str 文档文本内容
metadata Dict 元数据(作者、标题、来源等)
id_ str 自动生成或手动指定的唯一 ID
embedding List[float] 向量表示(构建索引后生成)

3.2 TextNode:文档片段

from llama_index.core.schema import TextNode

# 手动创建节点
n1 = TextNode(text=doc.text[0:8], doc_id=doc.id_)
n2 = TextNode(text=doc.text[9:16], doc_id=doc.id_)

Document 与 Node 的关系

Document(完整文档)
    ↓ NodeParser 切分
Node(文档片段)
    ↓ Embedding 向量化
向量索引中的条目
对比项 Document Node
粒度 完整文档 文档的一个片段
创建方式 手动或 Reader 加载 切分器自动生成或手动创建
用途 输入给切分器 输入给索引和检索
元数据 原始元数据 继承自 Document + 切分器添加的元数据

4. 文本切分器(NodeParser)

切分器决定文档如何被切分为 Node,直接影响检索质量。

4.1 TokenTextSplitter:按 Token 切分

from llama_index.core.node_parser import TokenTextSplitter

splitter = TokenTextSplitter(
    chunk_size=32,        # 每块最多 32 个 token
    chunk_overlap=4,      # 块间重叠 4 个 token
    separator="\n"        # 优先在换行符处切分
)

nodes = splitter.get_nodes_from_documents([doc])

for node in nodes:
    print(node.text)      # 切分后的文本
    print(node.metadata)  # 继承自 Document 的元数据

参数说明

chunk_size=32     → 每块最多 32 个 token(约 50-100 中文字符)
chunk_overlap=4   → 相邻块重叠 4 个 token(避免关键信息被切断)
separator="\n"    → 优先在换行符处切分(保持段落完整性)

切分效果示例

原文:
### 第七条 事假
1. 员工因私事必须本人处理的,可申请事假。
2. 事假需提前申请并获直属主管批准...

切分后(chunk_size=32):
Node 1: "### 第七条 事假\n1. 员工因私事必须本人处理的,可申请事假。"
Node 2: "2. 事假需提前申请并获直属主管批准,紧急情况可事后补办手续。"
Node 3: "3. 事假为无薪假,按日扣除相应工资。"
Node 4: "4. 每月事假原则上不超过 3 天,全年累计不超过 15 天..."

4.2 SentenceSplitter:按句子切分

from llama_index.core.node_parser import SentenceSplitter

sentence_splitter = SentenceSplitter(
    chunk_size=512,       # 每块约 512 字符
    chunk_overlap=50      # 块间重叠 50 字符
)

适用场景:中文文档(默认推荐),按句子边界切分,保持语义完整性。

4.3 SentenceWindowNodeParser:句子窗口切分

from llama_index.core.node_parser import SentenceWindowNodeParser

sentence_window_splitter = SentenceWindowNodeParser.from_defaults(
    window_size=3,                        # 窗口大小(前后各 3 句)
    window_metadata_key="window",         # 窗口文本的元数据键
    original_text_metadata_key="original_text"  # 原文本的元数据键
)

原理:检索时用单句匹配(精准),生成时用窗口上下文(完整)。

检索时:匹配单句 "事假为无薪假"
生成时:使用窗口 "事假需提前申请...事假为无薪假...每月事假不超过 3 天"

配合后处理器使用

from llama_index.core.node_parser import MetadataReplacementPostProcessor

query_engine = index.as_query_engine(
    similarity_top_k=5,
    streaming=True,
    node_postprocessors=[
        MetadataReplacementPostProcessor(target_metadata_key="window")
    ]
)

4.4 SemanticSplitterNodeParser:语义切分

from llama_index.core.node_parser import SemanticSplitterNodeParser

semantic_splitter = SemanticSplitterNodeParser(
    buffer_size=1,                    # 缓冲句子数
    breakpoint_percentile_threshold=95,  # 语义差异阈值
    embed_model=Settings.embed_model  # 需要 Embedding 模型
)

原理:计算相邻句子的语义相似度,在相似度骤降处切分。

句子 A ←→ 句子 B:相似度 0.92(同一话题,不切分)
句子 B ←→ 句子 C:相似度 0.31(话题转换,在此切分)

适用场景:长文档、主题切换频繁的文档。

4.5 MarkdownNodeParser:Markdown 切分

from llama_index.core.node_parser import MarkdownNodeParser

markdown_splitter = MarkdownNodeParser()

原理:按 Markdown 标题层级(######)切分,保持文档结构。

4.6 切分器对比总结

切分器 切分依据 适用场景 是否需要 Embedding
TokenTextSplitter Token 数量 英文/混合文本
SentenceSplitter 句子边界 中文文档(通用)
SentenceWindowNodeParser 句子 + 窗口 需要上下文的问答
SemanticSplitterNodeParser 语义相似度 长文档、主题切换
MarkdownNodeParser Markdown 标题 Markdown 文档

5. 向量索引与检索

5.1 构建索引

# 从文档构建
index = VectorStoreIndex.from_documents(documents)

# 从节点构建
index = VectorStoreIndex(nodes)

# 持久化保存
index.storage_context.persist(persist_dir="./storage")

# 加载已保存的索引
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)

5.2 检索器(Retriever)

# 基础检索器
retriever = index.as_retriever(similarity_top_k=5)

# 检索
nodes = retriever.retrieve("怎么休事假?")

for i, node in enumerate(nodes):
    print(f"Node {i+1} (相似度: {node.score:.4f}): {node.text[:50]}...")

检索结果示例

5.3 查询引擎(Query Engine)

# 基础查询
query_engine = index.as_query_engine()
response = query_engine.query("怎么休事假?")
print(response)

# 流式查询
streaming_engine = index.as_query_engine(streaming=True)
response = streaming_engine.query("怎么休事假?")
response.print_response_stream()

# 对话引擎(多轮问答)
chat_engine = index.as_chat_engine()
response = chat_engine.chat("我想请事假,需要什么流程?")

6. 后处理器(PostProcessor)

后处理器在检索后、生成前对节点进行过滤或重排,提升答案质量。

6.1 SimilarityPostprocessor:相似度过滤

from llama_index.core.postprocessor import SimilarityPostprocessor

# 创建后处理器,设置相似度阈值
similarity_postprocessor = SimilarityPostprocessor(similarity_cutoff=0.71)

# 应用后处理器
filtered_nodes = similarity_postprocessor.postprocess_nodes(nodes)

print(f"原始 Node 数: {len(nodes)}, 过滤后 Node 数: {len(filtered_nodes)}")

效果

6.2 其他常用后处理器

后处理器 作用 使用场景
SimilarityPostprocessor 按相似度阈值过滤 去除低相关性结果
KeywordNodePostprocessor 按关键词过滤 确保结果包含特定词
MetadataReplacementPostProcessor 替换元数据 句子窗口切分后恢复上下文
LongContextReorder 重排长上下文 避免"中间丢失"问题
SentenceEmbeddingOptimizer 优化句子嵌入 提升检索精度

6.3 在查询引擎中使用后处理器

query_engine = index.as_query_engine(
    similarity_top_k=10,              # 先检索 10 个
    node_postprocessors=[
        SimilarityPostprocessor(similarity_cutoff=0.7),  # 过滤到相似度 > 0.7
        KeywordNodePostprocessor(required_keywords=["事假"])  # 确保包含关键词
    ]
)

处理流程

用户提问 → 检索 Top-10 → 相似度过滤 → 关键词过滤 → LLM 生成答案

7. 完整实战:企业知识库问答系统

7.1 系统架构

7.2 完整代码

from dotenv import load_dotenv
load_dotenv()

import os
import dashscope
from llama_index.core import Settings
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.llms.openai_like import OpenAILike
from llama_index.embeddings.dashscope import DashScopeEmbedding, DashScopeTextEmbeddingModels
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.postprocessor import SimilarityPostprocessor

# 配置
dashscope.api_key = os.getenv("DASHCOPE_KEY")

Settings.llm = OpenAILike(
    model="qwen-plus",
    api_base="https://dashscope.aliyuncs.com/compatible-mode/v1",
    api_key=os.getenv("DASHCOPE_KEY"),
    is_chat_model=True
)

Settings.embed_model = DashScopeEmbedding(
    model_name=DashScopeTextEmbeddingModels.TEXT_EMBEDDING_V3,
    embed_batch_size=6,
    embed_input_length=8192
)

Settings.transformations = [
    SentenceSplitter(chunk_size=256, chunk_overlap=32)
]

# 加载文档
documents = SimpleDirectoryReader("data").load_data()

# 构建索引
index = VectorStoreIndex.from_documents(documents)

# 创建查询引擎(带后处理器)
query_engine = index.as_query_engine(
    similarity_top_k=5,
    node_postprocessors=[
        SimilarityPostprocessor(similarity_cutoff=0.7)
    ]
)

# 查询
response = query_engine.query("怎么休事假?")
print(response)

7.3 运行效果

Q: 怎么休事假?
A: 员工因私事必须本人处理的,可申请事假。需提前向直属主管提出申请并获得批准;
   如遇紧急情况,可事后补办手续。事假为无薪假,按日扣除相应工资。
   每月事假原则上不超过 3 天,全年累计不得超过 15 天。

Q: 元旦休假几天?
A: 元旦休假 1 天。

Q: 春节休假几天?
A: 春节休假 3 天。

8. LlamaIndex 核心语法速查

8.1 导入路径

# 核心组件
from llama_index.core import Settings, Document, VectorStoreIndex, SimpleDirectoryReader
from llama_index.core import StorageContext, load_index_from_storage

# Schema
from llama_index.core.schema import TextNode, Document

# NodeParser(切分器)
from llama_index.core.node_parser import (
    TokenTextSplitter,
    SentenceSplitter,
    SentenceWindowNodeParser,
    SemanticSplitterNodeParser,
    MarkdownNodeParser
)

# PostProcessor(后处理器)
from llama_index.core.postprocessor import (
    SimilarityPostprocessor,
    KeywordNodePostprocessor,
    MetadataReplacementPostProcessor
)

# Reader(加载器)
from llama_index.readers.smart_pdf_loader import SmartPDFLoader
from llama_index.core.readers.base import BaseReader

# LLM & Embedding
from llama_index.llms.openai_like import OpenAILike
from llama_index.embeddings.dashscope import DashScopeEmbedding

8.2 核心 API

操作 API 说明
加载文档 SimpleDirectoryReader(path).load_data() 返回 List[Document]
创建文档 Document(text=..., metadata={...}) 手动创建文档
切分文档 splitter.get_nodes_from_documents(docs) 返回 List[Node]
构建索引 VectorStoreIndex.from_documents(docs) 自动向量化
持久化 index.storage_context.persist(dir) 保存到磁盘
加载索引 load_index_from_storage(ctx) 从磁盘加载
创建检索器 index.as_retriever(top_k=5) 返回 Retriever
创建查询引擎 index.as_query_engine() 返回 QueryEngine
创建对话引擎 index.as_chat_engine() 返回 ChatEngine
检索 retriever.retrieve(query) 返回 List[NodeWithScore]
查询 query_engine.query(query) 返回 Response
后处理 postprocessor.postprocess_nodes(nodes) 过滤/重排节点

8.3 数据流

文件 → Reader → Document → NodeParser → Node → Index → Retriever → PostProcessor → LLM → Response

9. 常见问题与最佳实践

9.1 切分粒度选择

场景 推荐切分器 chunk_size
中文文档 SentenceSplitter 256-512
英文文档 TokenTextSplitter 128-256
长文档 SemanticSplitterNodeParser 自动
Markdown MarkdownNodeParser 按标题
需要上下文 SentenceWindowNodeParser window_size=3

9.2 相似度阈值调优

# 阈值过高:过滤掉相关结果
SimilarityPostprocessor(similarity_cutoff=0.9)  # 太严格

# 阈值过低:包含不相关结果
SimilarityPostprocessor(similarity_cutoff=0.5)  # 太宽松

# 推荐范围
SimilarityPostprocessor(similarity_cutoff=0.7)  # 平衡

9.3 性能优化

# 1. 持久化索引,避免重复构建
index.storage_context.persist(persist_dir="./storage")

# 2. 批量 Embedding,减少 API 调用
Settings.embed_model = DashScopeEmbedding(embed_batch_size=10)

# 3. 流式输出,提升用户体验
query_engine = index.as_query_engine(streaming=True)

10. 总结

核心概念

概念 作用 关键类
Reader 加载数据源 SimpleDirectoryReaderBaseReader
Document 完整文档 Document
NodeParser 切分文档 SentenceSplitterTokenTextSplitter
Node 文档片段 TextNode
Index 向量索引 VectorStoreIndex
Retriever 语义检索 as_retriever()
PostProcessor 结果过滤 SimilarityPostprocessor
QueryEngine 检索 + 生成 as_query_engine()

学习路径

1. 环境配置 → Settings 全局设置
2. 文档加载 → SimpleDirectoryReader
3. 文档切分 → SentenceSplitter / TokenTextSplitter
4. 索引构建 → VectorStoreIndex
5. 检索查询 → Retriever / QueryEngine
6. 结果优化 → PostProcessor
7. 高级功能 → 自定义 Reader / 多模态 / Agent

更多推荐