深入理解 LlamaIndex:RAG 框架核心概念与实践

📚 1. LlamaIndex 介绍

官网标题:「 Build AI Knowledge Assistants over your enterprise data 」

LlamaIndex 是一个为开发「知识增强」的大语言模型应用的框架。知识增强,泛指任何在私有或特定领域数据基础上应用大语言模型的情况。例如:

在这里补充一张图。

  • 💬Question-Answering Chatbots(也就是 RAG)
  • 📄Document Understanding and Extraction(文档理解与信息抽取)
  • 🤖Autonomous Agentsthat can perform research and take actions (智能体应用)
  • 🔄Workflow orchestratingsingle and multi-agent (编排单个或多个智能体形成工作流)

LlamaIndex 有 Python 和 Typescript 两个版本,Python 版的文档相对更完善。

LlamaIndex 的核心模块

💻 2. 安装 LlamaIndex

bash 复制代码
pip install llama-index

💡 2.1 核心概念

在深入学习 LlamaIndex 之前,我们先了解几个核心概念:

  • 📄Document(文档)

LlamaIndex 中的基本数据单元,代表一个完整的文档(如 PDF、网页、文本文件等)。每个 Document 包含文本内容以及可选的元数据(metadata)。

  • 📝Node(节点)

Document 被切分后的文本片段,是检索和索引的基本单位。每个 Node 包含文本内容、元数据以及与其他 Node 的关系信息。

  • 📊Index(索引)

为了快速检索而构建的数据结构。LlamaIndex 支持多种索引类型,如向量索引、关键词索引等。

  • 🔍Retriever(检索器)

负责从索引中检索相关 Node 的组件,根据查询返回最相关的文档片段。

  • 🤖Query Engine(查询引擎)

结合检索器和 LLM,根据检索到的上下文生成最终答案。

  • 💬Chat Engine(对话引擎)

支持多轮对话的查询引擎,能够维护对话历史上下文。

数据流转过程

📄 Document → 📝 Node → 📊 Index → 🔍 Retriever → 🤖 Query/Chat Engine → 💬 Response

📥 3. 数据加载(Loading)

📂 3.1、加载本地数据

SimpleDirectoryReader是一个简单的本地文件加载器。它会遍历指定目录,并根据文件扩展名自动加载文件(文本内容)。

支持的文件类型:

  • .csv- comma-separated values
  • .docx- Microsoft Word
  • .epub- EPUB ebook format
  • .hwp- Hangul Word Processor
  • .ipynb- Jupyter Notebook
  • .jpeg,.jpg- JPEG image
  • .mbox- MBOX email archive
  • .md- Markdown
  • .mp3,.mp4- audio and video
  • .pdf- Portable Document Format
  • .png- Portable Network Graphics
  • .ppt,.pptm,.pptx- Microsoft PowerPoint
python 复制代码
# 辅助函数:用于格式化展示 JSON 数据
import json
from pydantic.v1 import BaseModel

def show_json(data):
    """用于展示json数据"""
    if isinstance(data, str):
        obj = json.loads(data)
        print(json.dumps(obj, indent=4, ensure_ascii=False))
    elif isinstance(data, dict) or isinstance(data, list):
        print(json.dumps(data, indent=4, ensure_ascii=False))
    elif issubclass(type(data), BaseModel):
        print(json.dumps(data.dict(), indent=4, ensure_ascii=False))

def show_list_obj(data):
    """用于展示一组对象"""
    if isinstance(data, list):
        for item in data:
            show_json(item)
    else:
        raise ValueError("Input is not a list")
python 复制代码
from llama_index.core import SimpleDirectoryReader

# 创建目录读取器
# input_dir: 要读取的目录路径
# recursive: 是否递归读取子目录(True/False)
# required_exts: 可选,指定只读取特定扩展名的文件
reader = SimpleDirectoryReader(
        input_dir="./data", 
        recursive=False, 
        required_exts=[".pdf"] 
    )

# 加载文档,返回 Document 对象列表
documents = reader.load_data()
python 复制代码
print(documents[0].text)
show_json(documents[0].json())

⚠️注意:对图像、视频、语音类文件,默认不会自动提取其中文字。如需提取,参考下面介绍的Data Connectors。

默认的PDFReader效果并不理想,我们可以更换文件加载器

LlamaParse

首先,登录并从 cloud.llamaindex.ai ↗ 注册并获取 api-key 。

然后,安装该包:

bash 复制代码
pip install llama-cloud-services
python 复制代码
# 在系统环境变量里配置 LLAMA_CLOUD_API_KEY=XXX
# 或使用 .env 文件配置(推荐)

from llama_cloud_services import LlamaParse
from llama_index.core import SimpleDirectoryReader
import nest_asyncio
from dotenv import load_dotenv

# 只在 Jupyter 环境中需要,解决异步事件循环问题
nest_asyncio.apply()

# 从 .env 文件加载环境变量
load_dotenv()

# 创建 LlamaParse 解析器
# result_type: 输出格式,"markdown" 或 "text"
parser = LlamaParse(result_type="markdown")

# 将 PDF 文件的解析器指定为 LlamaParse
file_extractor = {".pdf": parser}

# 使用自定义解析器加载文档
documents = SimpleDirectoryReader(
    input_dir="./data", 
    required_exts=[".pdf"], 
    file_extractor=file_extractor
).load_data()

print(documents[0].text)

🔌 3.2、Data Connectors

用于处理更丰富的数据类型,并将其读取为Document的形式。

例如:直接读取网页

bash 复制代码
pip install llama-index-readers-web
python 复制代码
from llama_index.readers.web import SimpleWebPageReader

documents = SimpleWebPageReader(html_to_text=True).load_data(
    ["https://edu.guangjuke.com/tx/"]
)

print(documents[0].text)

💡更多 Data Connectors📁 内置的文件加载器🔌 连接三方服务的数据加载器,例如数据库🌐 更多加载器可以在 LlamaHub 上找到

✂️ 4. 文本切分与解析(Chunking)

为方便检索,我们通常把Document切分为Node

在 LlamaIndex 中,Node被定义为一个文本的「chunk」。

✂️ 4.1、使用 TextSplitters 对文本做切分

例如:TokenTextSplitter按指定 token 数切分文本

python 复制代码
from llama_index.core import Document
from llama_index.core.node_parser import TokenTextSplitter

# 创建文本切分器
# chunk_size: 每个 chunk 的最大 token 数(建议 256-1024,根据模型上下文窗口调整)
# chunk_overlap: chunk 之间的重叠 token 数(建议为 chunk_size 的 10-20%,保证上下文连续性)
node_parser = TokenTextSplitter(
    chunk_size=512,  
    chunk_overlap=200
)

# 将 Document 切分为 Node 列表
# show_progress: 是否显示进度条(处理大量文档时有用)
nodes = node_parser.get_nodes_from_documents(
    documents, 
    show_progress=False
)

# 查看切分后的 Node 结构
show_json(nodes[1].json())
show_json(nodes[2].json())

LlamaIndex 提供了丰富的TextSplitter,例如:

  • SentenceSplitter:在切分指定长度的 chunk 同时尽量保证句子边界不被切断;
  • CodeSplitter:根据 AST(编译器的抽象句法树)切分代码,保证代码功能片段完整;
  • SemanticSplitterNodeParser:根据语义相关性对将文本切分为片段。

📄 4.2、使用 NodeParsers 对有结构的文档做解析

例如:HTMLNodeParser解析 HTML 文档

python 复制代码
from llama_index.core.node_parser import HTMLNodeParser
from llama_index.readers.web import SimpleWebPageReader

documents = SimpleWebPageReader(html_to_text=False).load_data(
    ["https://www.baidu.com/"]
)

# 默认解析 ["p", "h1", "h2", "h3", "h4", "h5", "h6", "li", "b", "i", "u", "section"]
parser = HTMLNodeParser(tags=["span"])  # 可以自定义解析哪些标签
nodes = parser.get_nodes_from_documents(documents)

for node in nodes:
    print(node.text+"\n")

更多的NodeParser包括MarkdownNodeParserJSONNodeParser等等。

📌 4.3 文本切分最佳实践

Chunk Size 选择建议:

  • 小文档(< 1000 字)

chunk_size=256-512,chunk_overlap=50-100

  • 中等文档(1000-10000 字)

chunk_size=512-1024,chunk_overlap=100-200

  • 大文档(> 10000 字)

chunk_size=1024-2048,chunk_overlap=200-400

注意事项:

  • Chunk size 需要根据 LLM 的上下文窗口大小调整(如 GPT-4 支持 128K,但实际使用时建议单个 chunk 不超过 2048 tokens)
  • Overlap 过小可能导致上下文断裂,过大则增加存储和计算成本
  • 对于代码文档,建议使用CodeSplitter保持代码块完整性
  • 对于结构化文档(HTML、Markdown),优先使用对应的NodeParser而非简单切分

🔍 5. 索引(Indexing)与检索(Retrieval)

基础概念

在「检索」相关的上下文中,「索引」即index, 通常是指为了实现快速检索而设计的特定「数据结构」。

索引的具体原理与实现不是本课程的教学重点,感兴趣的同学可以参考:传统索引、向量索引

🔍 5.1、向量检索

  1. VectorStoreIndex直接在内存中构建一个 Vector Store 并建索引
python 复制代码
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.core.node_parser import TokenTextSplitter, SentenceSplitter

# 1. 加载 PDF 文档
documents = SimpleDirectoryReader(
    "./data", 
    required_exts=[".pdf"],
).load_data()

# 2. 定义 Node Parser 用于切分文档
node_parser = TokenTextSplitter(chunk_size=512, chunk_overlap=200)

# 3. 将文档切分为 Node
nodes = node_parser.get_nodes_from_documents(documents)

# 4. 构建向量索引(默认在内存中,适合小规模数据)
# 构建过程会自动调用 Embedding 模型为每个 Node 生成向量
index = VectorStoreIndex(nodes)

# 方式二:直接从 Document 构建(会自动应用 transformations)
# index = VectorStoreIndex.from_documents(
#     documents=documents, 
#     transformations=[SentenceSplitter(chunk_size=512)]
# )

# 5. 持久化索引到本地(可选,用于后续复用)
# index.storage_context.persist(persist_dir="./doc_emb")

# 6. 创建检索器
# similarity_top_k: 返回最相似的前 k 个结果(建议 3-10,根据需求调整)
vector_retriever = index.as_retriever(
    similarity_top_k=2
)

# 7. 执行检索
# 检索过程:将查询文本转换为向量 → 在向量空间中搜索相似 Node → 返回结果
results = vector_retriever.retrieve("deepseek v3数学能力怎么样?")

# 8. 查看检索结果
print(results[0].text)
  1. 使用自定义的 Vector Store,以Qdrant为例:
bash 复制代码
pip install llama-index-vector-stores-qdrant
python 复制代码
from llama_index.core.indices.vector_store.base import VectorStoreIndex
from llama_index.vector_stores.qdrant import QdrantVectorStore
from llama_index.core import StorageContext

from qdrant_client import QdrantClient
from qdrant_client.models import VectorParams, Distance

# 1. 创建 Qdrant 客户端
# location=":memory:": 内存模式(重启后数据丢失)
# location="./qdrant_db": 持久化到本地目录(推荐生产环境使用)
client = QdrantClient(location=":memory:")

# 2. 创建向量集合(Collection)
collection_name = "demo"
# size: 向量维度,需与 Embedding 模型输出维度一致(如 OpenAI text-embedding-3-small 为 1536)
# distance: 距离度量方式,COSINE(余弦相似度)适合文本检索
collection = client.create_collection(
    collection_name=collection_name,
    vectors_config=VectorParams(size=1536, distance=Distance.COSINE)
)

# 3. 创建 Qdrant Vector Store
vector_store = QdrantVectorStore(client=client, collection_name=collection_name)

# 4. 创建存储上下文,关联自定义的 Vector Store
storage_context = StorageContext.from_defaults(vector_store=vector_store)

# 5. 使用自定义 Vector Store 创建索引
# 数据会存储在 Qdrant 中,而非内存
index = VectorStoreIndex(nodes, storage_context=storage_context)

# 6. 创建检索器
vector_retriever = index.as_retriever(similarity_top_k=1)

# 7. 执行检索
results = vector_retriever.retrieve("deepseek v3数学能力怎么样")

print(results[0])

🔎 5.2、更多索引与检索方式

LlamaIndex 内置了丰富的检索机制,例如:

  • 关键字检索
  • RAG-FusionQueryFusionRetriever
  • 还支持 KnowledgeGraph、SQL、Text-to-SQL 等等

🔄 5.3、检索后处理

LlamaIndex 的Node Postprocessors提供了一系列检索后处理模块。

例如:我们可以用不同模型对检索后的Nodes做重排序

python 复制代码
# 获取 retriever
vector_retriever = index.as_retriever(similarity_top_k=5)

# 检索
nodes = vector_retriever.retrieve("deepseek v3有多少参数?")

for i, node in enumerate(nodes):
    print(f"[{i}] {node.text}\n")
python 复制代码
from llama_index.core.postprocessor import LLMRerank

postprocessor = LLMRerank(top_n=2)

nodes = postprocessor.postprocess_nodes(nodes, query_str="deepseek v3有多少参数?")

for i, node in enumerate(nodes):
    print(f"[{i}] {node.text}")

更多的 Rerank 及其它后处理方法,参考官方文档:Node Postprocessor Modules

📌 5.4 索引与检索最佳实践

内存 vs 持久化存储选择:

  • 内存存储:适合小规模数据(< 1000 文档)、快速原型开发、一次性查询
  • 持久化存储:适合生产环境、大规模数据、需要长期保存索引的场景

检索策略选择:

  • 向量检索:适合语义搜索、相似度查询、多语言场景
  • 关键词检索(BM25):适合精确匹配、术语查询、代码搜索
  • 混合检索 :结合向量和关键词检索,通常效果最好(可用QueryFusionRetriever

Embedding 模型选择:

  • 中文场景 :推荐使用支持中文的模型,如text-embedding-v3(DashScope)、bge-large-zh-v1.5(HuggingFace)
  • 多语言场景 :推荐text-embedding-3-small/large(OpenAI)、multilingual-e5-large
  • 成本考虑:开源模型可本地部署,API 模型按调用次数收费

💬 6. 生成回复(QA & Chat)

💬 6.1 单轮问答(Query Engine)

python 复制代码
# 创建查询引擎(单轮问答)
# Query Engine 内部流程:检索相关 Node → 构建 Prompt → 调用 LLM 生成回答
qa_engine = index.as_query_engine()

# 执行查询
response = qa_engine.query("deepseek v3数学能力怎么样?")

# 输出结果
print(response)

流式输出

python 复制代码
# 启用流式输出,适合实时交互场景
qa_engine = index.as_query_engine(streaming=True)
response = qa_engine.query("deepseek v3数学能力怎么样?")

# 方式一:使用内置方法打印流式输出
response.print_response_stream()

# 方式二:手动控制输出(更灵活)
# for token in response.response_gen:
#     print(token, end="", flush=True)

💭 6.2 多轮对话(Chat Engine)

python 复制代码
chat_engine = index.as_chat_engine()
response = chat_engine.chat("deepseek v3数学能力怎么样?")
print(response)
python 复制代码
response = chat_engine.chat("代码能力呢?")
print(response)

流式输出

python 复制代码
chat_engine = index.as_chat_engine()

# 流式对话,逐 token 输出
streaming_response = chat_engine.stream_chat("deepseek v3数学能力怎么样?")

# 方式一:使用内置方法
# streaming_response.print_response_stream()

# 方式二:手动控制输出(推荐,可自定义格式)
for token in streaming_response.response_gen:
    print(token, end="", flush=True)

🔄 6.3 Query Engine vs Chat Engine

Query Engine(单轮问答):

  • 每次查询都是独立的,不保留历史对话
  • 适合:一次性问答、文档查询、信息检索
  • 性能:更快,资源消耗更少

Chat Engine(多轮对话):

  • 维护对话历史,支持上下文理解
  • 适合:交互式对话、需要理解前文的问题(如"它呢?"、"详细说明一下")
  • 性能:需要存储历史,资源消耗更多

选择建议:

  • 简单问答场景 → Query Engine
  • 需要上下文理解 → Chat Engine
  • 生产环境建议根据实际需求选择,避免不必要的资源消耗

⚙️ 7. 底层接口:Prompt、LLM 与 Embedding

📝 7.1 Prompt 模板

PromptTemplate定义提示词模板

python 复制代码
from llama_index.core import PromptTemplate

# 创建提示词模板,使用 {变量名} 作为占位符
prompt = PromptTemplate("写一个关于{topic}的笑话")

# 格式化模板,替换占位符
result = prompt.format(topic="小明")
print(result)
# 输出:'写一个关于小明的笑话'

ChatPromptTemplate定义多轮消息模板

python 复制代码
from llama_index.core.llms import ChatMessage, MessageRole
from llama_index.core import ChatPromptTemplate

chat_text_qa_msgs = [
    ChatMessage(
        role=MessageRole.SYSTEM,
        content="你叫{name},你必须根据用户提供的上下文回答问题。",
    ),
    ChatMessage(
        role=MessageRole.USER, 
        content=(
            "已知上下文:\n" \
            "{context}\n\n" \
            "问题:{question}"
        )
    ),
]
text_qa_template = ChatPromptTemplate(chat_text_qa_msgs)

print(
    text_qa_template.format(
        name="小明",
        context="这是一个测试",
        question="这是什么"
    )
)

system: 你叫小明,你必须根据用户提供的上下文回答问题。 user: 已知上下文: 这是一个测试

问题:这是什么 assistant:

🤖 7.2 语言模型

python 复制代码
from llama_index.llms.openai import OpenAI

llm = OpenAI(temperature=0, model="gpt-4o")
python 复制代码
response = llm.complete(prompt.format(topic="小明"))

print(response.text)
python 复制代码
response = llm.complete(
    text_qa_template.format(
        name="小明",
        context="这是一个测试",
        question="你是谁,我们在干嘛"
    )
)

print(response.text)

连接DeepSeek

bash 复制代码
pip install llama-index-llms-deepseek
python 复制代码
import os
from llama_index.llms.deepseek import DeepSeek

llm = DeepSeek(model="deepseek-chat", api_key=os.getenv("DEEPSEEK_API_KEY"), temperature=1.5)

response = llm.complete("写个笑话")
print(response)

设置全局使用的语言模型

python 复制代码
from llama_index.core import Settings

Settings.llm = DeepSeek(model="deepseek-chat", api_key=os.getenv("DEEPSEEK_API_KEY"), temperature=1.5)

除 OpenAI 外,LlamaIndex 已集成多个大语言模型,包括云服务 API 和本地部署 API,详见官方文档:Available LLM integrations

🧮 7.3 Embedding 模型

python 复制代码
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.core import Settings

# 全局设定 Embedding 模型
# model: 模型名称
# dimensions: 向量维度(可选,某些模型支持自定义维度以节省成本)
Settings.embed_model = OpenAIEmbedding(
    model="text-embedding-3-small", 
    dimensions=512  # 降低维度可节省存储和计算成本,但可能影响精度
)

LlamaIndex 同样集成了多种 Embedding 模型,包括云服务 API 和开源模型(HuggingFace)等,详见官方文档。

📌 7.4 模型配置最佳实践

LLM 选择建议:

  • 开发测试:使用成本较低的模型(如 GPT-3.5-turbo、DeepSeek)
  • 生产环境:根据任务复杂度选择,简单任务用轻量模型,复杂任务用强模型
  • 本地部署:考虑使用开源模型(如 Llama、Qwen)降低成本

Temperature 参数调整:

  • 0-0.3:确定性输出,适合事实性问答、代码生成
  • 0.5-0.7:平衡创造性和准确性,适合一般对话
  • 0.8-1.5:高创造性,适合创意写作、头脑风暴

Embedding 模型选择:

  • 确保向量维度与向量数据库配置一致
  • 中文场景优先选择支持中文的模型
  • 考虑成本:开源模型可本地部署,API 模型按调用收费

🏗️ 8. 基于 LlamaIndex 实现一个功能较完整的 RAG 系统

功能要求:

  • 加载指定目录的文件
  • 支持 RAG-Fusion
  • 使用 Qdrant 向量数据库,并持久化到本地
  • 支持检索后排序
  • 支持多轮对话
python 复制代码
from qdrant_client import QdrantClient
from qdrant_client.models import VectorParams, Distance

EMBEDDING_DIM = 1536
COLLECTION_NAME = "full_demo"
PATH = "./qdrant_db"

client = QdrantClient(path=PATH)
python 复制代码
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, get_response_synthesizer
from llama_index.vector_stores.qdrant import QdrantVectorStore
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.response_synthesizers import ResponseMode
from llama_index.core.ingestion import IngestionPipeline
from llama_index.core import Settings
from llama_index.core import StorageContext
from llama_index.core.postprocessor import LLMRerank, SimilarityPostprocessor
from llama_index.core.retrievers import QueryFusionRetriever
from llama_index.core.query_engine import RetrieverQueryEngine
from llama_index.core.chat_engine import CondenseQuestionChatEngine
from llama_index.llms.dashscope import DashScope, DashScopeGenerationModels
from llama_index.embeddings.dashscope import DashScopeEmbedding, DashScopeTextEmbeddingModels
import os

# ========== 第一步:配置全局模型 ==========
# 1. 设置 LLM(用于生成回答)
Settings.llm = DashScope(
    model_name=DashScopeGenerationModels.QWEN_MAX,
    api_key=os.getenv("DASHSCOPE_API_KEY")
)

# 2. 设置 Embedding 模型(用于生成向量)
Settings.embed_model = DashScopeEmbedding(
    model_name=DashScopeTextEmbeddingModels.TEXT_EMBEDDING_V1
)

# 3. 设置全局文档处理管道(自动应用于所有文档)
Settings.transformations = [
    SentenceSplitter(chunk_size=512, chunk_overlap=200)
]

# ========== 第二步:加载和索引文档 ==========
# 4. 加载本地文档
documents = SimpleDirectoryReader("./data").load_data()

# 5. 清理已存在的集合(可选,用于重新构建索引)
if client.collection_exists(collection_name=COLLECTION_NAME):
    client.delete_collection(collection_name=COLLECTION_NAME)

# 6. 创建 Qdrant 集合
client.create_collection(
    collection_name=COLLECTION_NAME,
    vectors_config=VectorParams(size=EMBEDDING_DIM, distance=Distance.COSINE)
)

# 7. 创建 Vector Store 并关联到 Qdrant
vector_store = QdrantVectorStore(client=client, collection_name=COLLECTION_NAME)
storage_context = StorageContext.from_defaults(vector_store=vector_store)

# 8. 构建索引(会自动应用 Settings.transformations 切分文档)
index = VectorStoreIndex.from_documents(
    documents, 
    storage_context=storage_context
)

# ========== 第三步:配置检索和排序 ==========
# 9. 定义检索后处理器
# LLMRerank: 使用 LLM 对检索结果重新排序,提高准确性(但会增加延迟和成本)
reranker = LLMRerank(top_n=2)

# SimilarityPostprocessor: 过滤相似度低于阈值的文档(未使用,仅作示例)
# sp = SimilarityPostprocessor(similarity_cutoff=0.6)

# 10. 创建 RAG-Fusion 检索器
# RAG-Fusion 原理:将原始查询扩展为多个相关查询,分别检索后合并结果
fusion_retriever = QueryFusionRetriever(
    [index.as_retriever()],  # 可以传入多个检索器进行融合
    similarity_top_k=5,      # 每个查询返回 top 5 结果
    num_queries=3,           # 生成 3 个扩展查询
    use_async=False,         # 是否异步执行(True 可提升速度)
)

# ========== 第四步:构建查询引擎 ==========
# 11. 构建单轮查询引擎
# ResponseMode.REFINE: 先基于第一个 chunk 生成回答,再用其他 chunk 逐步优化
query_engine = RetrieverQueryEngine.from_args(
    fusion_retriever,
    node_postprocessors=[reranker],  # 应用重排序
    response_synthesizer=get_response_synthesizer(
        response_mode=ResponseMode.REFINE  # 其他模式:COMPACT, TREE_SUMMARIZE, SIMPLE_SUMMARIZE
    )
)

# ========== 第五步:构建对话引擎 ==========
# 12. 创建多轮对话引擎
# CondenseQuestionChatEngine: 将历史对话压缩为独立查询,适合长对话
chat_engine = CondenseQuestionChatEngine.from_defaults(
    query_engine=query_engine,
    # 可以自定义问题压缩的 prompt 模板
    # condense_question_prompt="..."
)
python 复制代码
# 测试多轮对话
# User: deepseek v3有多少参数
# User: 每次激活多少

while True:
    question=input("User:")
    if question.strip() == "":
        break
    response = chat_engine.chat(question)
    print(f"AI: {response}")

❓ 9. 常见问题 FAQ

📥 9.1 数据加载相关问题

Q: 如何处理大文件或大量文件?

A: 有几种策略:

  • 使用recursive=True递归读取子目录
  • 分批加载文件,避免一次性加载所有文件到内存
  • 使用流式处理(Streaming),边读边处理
  • 对于超大文件,考虑先预处理,提取关键部分
python 复制代码
# 示例:分批加载
files = ["file1.pdf", "file2.pdf", "file3.pdf"]
for file in files:
    documents = SimpleDirectoryReader(input_files=[file]).load_data()
    # 处理 documents

Q: PDF 文件解析效果不好怎么办?

A: 可以尝试以下方法:

  • 使用LlamaParse替代默认的 PDFReader(效果更好但需要 API key)
  • 对于扫描版 PDF,先使用 OCR 工具提取文字
  • 检查 PDF 是否包含可提取的文本层(非纯图片)

Q: 如何处理图像、视频、音频文件中的文字?

A: 需要使用专门的 Data Connectors:

  • 图像 OCR:使用ImageReader或集成 OCR 服务
  • 视频字幕:提取视频中的字幕文件
  • 音频转文字:使用语音识别服务(如 Whisper)

✂️ 9.2 文本切分相关问题

Q: Chunk size 应该设置多大?

A: 需要根据以下因素综合考虑:

  • LLM 上下文窗口:确保 chunk + 查询 + 回答不超过模型限制
  • 文档类型:代码文档建议 512-1024,自然语言建议 256-512
  • 检索精度:chunk 太小可能丢失上下文,太大可能包含无关信息
  • 经验值:大多数场景下,512-1024 tokens 是比较好的选择

Q: Chunk overlap 设置多少合适?

A: 一般建议:

  • 设置为 chunk_size 的 10-20%(如 chunk_size=512,overlap=50-100)
  • 对于长文档或需要保持上下文连续性的场景,可以适当增大
  • 注意:overlap 过大会增加存储和计算成本

Q: 为什么切分后的结果不理想?

A: 可能的原因和解决方案:

  • 问题 :在句子中间切断 →解决 :使用SentenceSplitter而非TokenTextSplitter
  • 问题 :代码块被破坏 →解决 :使用CodeSplitter
  • 问题 :语义不完整 →解决 :使用SemanticSplitterNodeParser
  • 问题 :结构化文档解析错误 →解决 :使用对应的NodeParser(如HTMLNodeParserMarkdownNodeParser

🔍 9.3 检索相关问题

Q: 检索效果不好,返回的结果不相关怎么办?

A: 可以尝试以下优化方法:

  1. 调整 chunk size:太大或太小都可能影响效果
  2. 尝试不同的 TextSplitterSentenceSplitter通常比TokenTextSplitter效果更好
  3. 使用 Rerank :使用LLMRerank对检索结果重新排序
  4. 检查 Embedding 模型:确保使用的模型适合你的数据(如中文数据使用中文模型)
  5. 使用混合检索:结合向量检索和关键字检索(如 RAG-Fusion)
  6. 调整 similarity_top_k:适当增大返回结果数量,然后通过 Rerank 筛选

Q: 检索速度太慢怎么办?

A: 优化建议:

  • 使用持久化向量数据库(如 Qdrant)而非内存存储
  • 减少similarity_top_k的值
  • 使用异步检索(use_async=True
  • 考虑使用更快的 Embedding 模型(如本地部署的轻量模型)
  • 对于大规模数据,考虑使用专业的向量数据库服务(如 Pinecone)

Q: 向量检索和关键字检索应该选哪个?

A: 选择建议:

  • 向量检索:适合语义搜索、理解同义词、多语言场景
  • 关键字检索(BM25):适合精确匹配、术语查询、代码搜索
  • 混合检索:结合两者优势,通常效果最好(推荐)

🇨🇳 9.4 中文文档处理问题

Q: 如何处理中文文档?

A: 关键点:

  1. 使用支持中文的 Embedding 模型
  2. 使用适合中文的 TextSplitter
  3. 确保 LLM 支持中文
python 复制代码
# 中文场景推荐配置
from llama_index.embeddings.dashscope import DashScopeEmbedding
from llama_index.core.node_parser import SentenceSplitter

Settings.embed_model = DashScopeEmbedding(
    model_name=DashScopeTextEmbeddingModels.TEXT_EMBEDDING_V3
)
Settings.transformations = [SentenceSplitter(chunk_size=512, chunk_overlap=100)]

⚡ 9.5 性能和成本问题

Q: 如何降低 API 调用成本?

A: 优化策略:

  1. 使用开源模型:本地部署开源 LLM 和 Embedding 模型(如 Llama、Qwen、BGE)
  2. 缓存机制:对相同查询结果进行缓存
  3. 减少 Rerank 调用:只在必要时使用 LLMRerank(会增加成本)
  4. 优化 chunk 数量:减少不必要的 chunk,降低 Embedding 调用次数
  5. 选择合适的模型:简单任务使用轻量模型,复杂任务再用强模型

Q: 内存占用太大怎么办?

A: 解决方案:

  • 使用持久化存储(Qdrant、Chroma 等)而非内存存储
  • 分批处理文档,不要一次性加载所有数据
  • 使用流式处理,边处理边释放内存
  • 考虑使用更轻量的 Embedding 模型

Q: 索引构建速度慢怎么办?

A: 优化方法:

  • 使用异步处理(use_async=True
  • 批量处理文档而非逐个处理
  • 使用更快的 Embedding 模型
  • 对于大规模数据,考虑分布式处理

🏭 9.6 部署和生产环境问题

Q: 如何持久化索引以便后续复用?

A: 两种方式:

  1. 使用向量数据库(推荐):
python 复制代码
# 使用 Qdrant 等向量数据库,数据自动持久化
client = QdrantClient(path="./qdrant_db")
  1. 使用本地文件存储
python 复制代码
# 保存索引到本地
index.storage_context.persist(persist_dir="./doc_emb")

# 后续加载
from llama_index.core import load_index_from_storage
storage_context = StorageContext.from_defaults(persist_dir="./doc_emb")
index = load_index_from_storage(storage_context)

Q: 如何更新已构建的索引?

A: 更新策略:

  • 增量更新 :使用index.insert()添加新文档
  • 全量重建:删除旧索引,重新构建(适合文档经常变化的情况)
  • 版本管理:为不同版本的索引创建不同的 collection

Q: 生产环境应该注意什么?

A: 关键点:

  1. 使用持久化存储:避免数据丢失
  2. 错误处理:添加异常捕获和重试机制
  3. 监控和日志:记录 API 调用、检索性能等
  4. 限流和缓存:避免 API 调用过频
  5. 安全性:保护 API key,避免敏感数据泄露
  6. 性能优化:使用异步、批量处理等技术

✨ 10. 总结

本文全面介绍了 LlamaIndex 框架的核心功能和使用方法,帮助读者快速上手构建 RAG 应用。

核心流程回顾

一个完整的 RAG 系统通常包含以下步骤:

  1. 📥数据加载(Loading)
  2. ✂️文本切分与解析(Chunking)
  3. 📊构建索引(Indexing)
  4. 🔍检索(Retrieval)
  5. 💬生成回答(QA & Chat)

关键要点

  • ✂️Chunk 策略:选择合适的 chunk size 和 overlap 是 RAG 系统成功的关键
  • 💾存储选择:根据数据规模和场景选择内存存储或持久化存储
  • 🔍检索策略:向量检索适合语义搜索,关键字检索适合精确匹配,混合检索通常效果最好
  • 🤖模型选择:根据任务需求和数据特点选择合适的 LLM 和 Embedding 模型
  • 🇨🇳中文支持:处理中文文档时,务必使用支持中文的 Embedding 模型和合适的 TextSplitter

最佳实践建议

  1. 🚀开发阶段:使用内存存储和简单配置快速验证想法
  2. 🏭生产环境:使用持久化向量数据库,添加错误处理和监控
  3. 性能优化:合理设置参数,使用异步处理,考虑缓存机制
  4. 💰成本控制:根据任务复杂度选择模型,考虑使用开源模型降低成本

下一步学习

  • 📚深入学习:LlamaIndex 官方文档
  • 🔌探索连接器:LlamaHub 上找到更多 Data Connectors
  • 🤖高级功能:了解 Agent 和 Workflow 的用法
  • 💡实践项目:尝试构建自己的 RAG 应用,解决实际问题
  • 🎯社区支持:加入 LlamaIndex 社区,获取帮助和分享经验

希望本文能帮助你快速掌握 LlamaIndex,构建出强大的知识增强应用!如有问题,欢迎参考上面的常见问题部分,或查阅官方文档。

🎉END

如果你觉得本文有帮助,欢迎点赞👍、在看👀、转发📤,也欢迎留言💬分享你的经验!

相关推荐
用户2190326527352 小时前
配置中心 - 不用改代码就能改配置
后端·spring cloud·微服务
python开发笔记2 小时前
can(6) canopen python库使用
服务器·网络·python
rgeshfgreh2 小时前
Python变量与类型:从入门到精通
python
杨超越luckly2 小时前
HTML应用指南:利用GET请求获取网易云热歌榜
前端·python·html·数据可视化·网易云热榜
汤姆yu2 小时前
基于深度学习的火焰烟雾识别系统
人工智能·深度学习·目标跟踪
快手技术2 小时前
打破信息茧房!快手搜索多视角正样本增强引擎 CroPS 入选 AAAI 2026 Oral
后端·算法·架构
灯下夜无眠2 小时前
sklearn中fit、transform、fit_transform用法详解
人工智能·python·sklearn
张彦峰ZYF2 小时前
多模态大模型、混合专家模型与云端协同架构
人工智能·计算机视觉·多模态大模型·混合专家架构·大小模型协同架构