LlamaIndex:用索引和检索重新定义RAG系统
·
LlamaIndex本地Demo学习笔记
这是我学习LlamaIndex框架的完整记录。通过9个递进式的本地demo,从零开始理解RAG系统的核心概念。
前言
最近在学习大模型应用开发,发现LlamaIndex是一个很有意思的框架。相比LangChain的通用性,LlamaIndex更专注于"索引"和"检索",特别适合构建RAG系统。
本文记录了我从零开始学习LlamaIndex的完整过程,包括9个本地demo和对应的讲解。希望能帮助和我一样的初学者快速上手。
第一部分:核心概念
LlamaIndex的5个核心概念
| 概念 | 作用 | 类比 |
|---|---|---|
| Document | 原始数据 + 元数据 | 书籍 |
| Node | 文档的分块 | 书籍的章节 |
| Index | 向量化的索引 | 书籍的目录 |
| QueryEngine | 单轮查询工具 | 查字典 |
| ChatEngine | 多轮对话工具 | 和朋友聊天 |
RAG系统的完整流程
原始数据 → 分块 → 向量化 → 索引 → 检索 → LLM生成答案
第二部分:9个本地Demo
Demo 1: 最简单的RAG系统(01_hello_llamaindex.py)
目标:用10行代码实现一个完整的RAG系统
from llama_index.core import Document, VectorStoreIndex, Settings
from llama_index.llms.dashscope import DashScope
from llama_index.embeddings.dashscope import DashScopeEmbedding
import os
# 配置
Settings.llm = DashScope(model="qwen-plus", api_key=os.getenv("DASHSCOPE_API_KEY"))
Settings.embed_model = DashScopeEmbedding(model="text-embedding-v1", api_key=os.getenv("DASHSCOPE_API_KEY"))
# 创建文档
doc = Document(text="北京朝阳区有一套三居室,价格600万")
# 创建索引
index = VectorStoreIndex.from_documents([doc])
# 查询
query_engine = index.as_query_engine()
response = query_engine.query("这套房子多少钱?")
print(f"回答:{response}")
关键点:
- Settings全局配置LLM和Embedding模型
- 一行代码创建索引:
VectorStoreIndex.from_documents() - 一行代码创建查询工具:
as_query_engine()
Demo 2: LlamaIndex vs LangChain(02_langchain_comparison.py)
目标:对比两个框架的差异
LlamaIndex方式:
index = VectorStoreIndex.from_documents(docs)
query_engine = index.as_query_engine()
response = query_engine.query("问题")
LangChain方式:
vectorstore = FAISS.from_texts(texts, embeddings)
retriever = vectorstore.as_retriever()
prompt = ChatPromptTemplate.from_template("...")
chain = prompt | llm | StrOutputParser()
docs = retriever.get_relevant_documents("问题")
response = chain.invoke({"context": context, "question": "问题"})
结论:
- LlamaIndex代码更简洁,专注RAG
- LangChain更灵活,适合复杂链式逻辑
Demo 3: 核心差异分析(03_core_difference.py)
LlamaIndex的思路:
文档 → 向量化 → 索引 → 查询 → 生成答案
LangChain的思路:
文本 → 向量化 → 向量存储 → 检索 → 提示词 → LLM → 答案
选择建议:
- 主要做RAG → 选LlamaIndex
- 需要复杂链式逻辑 → 选LangChain
Demo 4: Document详解(04_document.py)
简单文档:
doc = Document(text="北京朝阳区三居室,600万")
带元数据的文档:
doc = Document(
text="北京朝阳区三居室,600万",
metadata={
"location": "朝阳区",
"price": 600,
"bedrooms": 3
}
)
为什么需要元数据?
- 用于过滤和排序
- 提高检索精度
- 保存结构化信息
Demo 5: Node详解(05_node.py)
问题:长文档不能直接向量化,怎么办?
解决方案:分块成多个Node
from llama_index.core.node_parser import SimpleNodeParser
# 创建切割机
parser = SimpleNodeParser.from_defaults(
chunk_size=300, # 每个Node最多300个字符
chunk_overlap=50 # 相邻Node重叠50个字符
)
# 执行切割
nodes = parser.get_nodes_from_documents([long_doc])
工作流程:
Document(583字符)
↓
切割机按照chunk_size=300切割
↓
Node 1: 前300个字符 + 50个重叠
Node 2: 从250字符开始 + 后面的字符
↓
返回所有Node
为什么需要chunk_overlap?
- 保持上下文连贯
- 避免信息丢失
- 提高检索准确度
Demo 6: Index详解(06_index.py)
目标:理解索引的创建过程
# 创建文档
docs = [
Document(text="房子1:朝阳区,600万"),
Document(text="房子2:海淀区,800万"),
Document(text="房子3:东城区,500万"),
]
# 创建索引
index = VectorStoreIndex.from_documents(docs)
这一步做了什么?
- 把每个Document分成Node
- 对每个Node调用Embedding模型
- 把向量存储在内存中
Index的作用:
- 快速搜索相关文档
- 支持向量相似度计算
- 是RAG系统的核心
Demo 7: QueryEngine详解(07_query_engine.py)
目标:实现单轮查询
# 创建查询引擎
query_engine = index.as_query_engine(similarity_top_k=2)
# 查询
response = query_engine.query("朝阳区有什么房子?")
similarity_top_k=2的含义:
- 返回最相关的2个文档
- 用这2个文档作为上下文
- 让LLM基于这些上下文生成答案
查询流程:
问题:"朝阳区有什么房子?"
↓
向量化问题
↓
搜索最相关的2个文档
↓
构建提示词
↓
调用LLM生成答案
Demo 8: ChatEngine详解(08_chat_engine.py)
目标:实现多轮对话
from llama_index.core.memory import ChatMemoryBuffer
chat_engine = index.as_chat_engine(
chat_mode="condense_question",
memory=ChatMemoryBuffer.from_defaults(token_limit=3000)
)
# 第1轮
response1 = chat_engine.chat("我想在朝阳区买房")
# 第2轮
response2 = chat_engine.chat("预算600万")
# 第3轮
response3 = chat_engine.chat("推荐哪个?")
chat_mode="condense_question"的工作原理:
用户第3轮:推荐哪个?
↓
读取对话历史:
- 第1轮:我想在朝阳区买房
- 第2轮:预算600万
↓
压缩成完整问题:
"用户想在朝阳区买房,预算600万,现在问推荐哪个?"
↓
用这个完整问题去搜索和生成答案
memory=ChatMemoryBuffer.from_defaults(token_limit=3000):
- 保存最近3000个token的对话历史
- 避免内存爆炸
- 保持对话连贯性
Demo 9: 完整的RAG系统(09_complete_rag.py)
目标:整合所有概念,构建完整的RAG系统
from llama_index.core import Document, VectorStoreIndex, Settings
from llama_index.llms.dashscope import DashScope
from llama_index.embeddings.dashscope import DashScopeEmbedding
import os
from dotenv import load_dotenv
# 加载环境变量
load_dotenv()
# 配置
Settings.llm = DashScope(model="qwen-plus", api_key=os.getenv("DASHSCOPE_API_KEY"))
Settings.embed_model = DashScopeEmbedding(model="text-embedding-v1", api_key=os.getenv("DASHSCOPE_API_KEY"))
# 步骤1:准备数据
docs = [
Document(text="房子1:朝阳区CBD,600万,三居室,精装修"),
Document(text="房子2:海淀区中关村,800万,四居室,毛坯房"),
Document(text="房子3:东城区王府井,500万,两居室,简装修"),
]
# 步骤2:创建索引
index = VectorStoreIndex.from_documents(docs)
# 步骤3:创建查询引擎
query_engine = index.as_query_engine(similarity_top_k=2)
# 步骤4:查询
question = "我想在朝阳区买一套三居室,预算600万,有什么推荐?"
response = query_engine.query(question)
print(f"问题:{question}")
print(f"回答:{response}")
完整流程:
┌─────────────────────────────────────────────────────────┐
│ RAG系统完整流程 │
└─────────────────────────────────────────────────────────┘
1. 数据准备阶段
├─ 收集原始数据(Document)
├─ 添加元数据(metadata)
└─ 清洗和预处理
2. 索引构建阶段
├─ 分块(Node Parser)
├─ 向量化(Embedding Model)
└─ 存储索引(Vector Store)
3. 查询阶段
├─ 用户输入问题
├─ 向量化问题
├─ 搜索相关文档
└─ 返回Top-K结果
4. 生成阶段
├─ 构建提示词
├─ 调用LLM
└─ 返回答案
第三部分:实战技巧
技巧1:调整相似度阈值
# 返回最相关的3个文档
query_engine = index.as_query_engine(similarity_top_k=3)
# 返回最相关的5个文档(更多上下文)
query_engine = index.as_query_engine(similarity_top_k=5)
技巧2:调整chunk_size
# 小chunk_size(100):更精细的分块,检索精度高
parser = SimpleNodeParser.from_defaults(chunk_size=100)
# 大chunk_size(1000):粗粒度分块,保留更多上下文
parser = SimpleNodeParser.from_defaults(chunk_size=1000)
# 推荐值:256-512
parser = SimpleNodeParser.from_defaults(chunk_size=512)
技巧3:多轮对话的内存管理
# 保存最近3000个token的对话历史
chat_engine = index.as_chat_engine(
chat_mode="condense_question",
memory=ChatMemoryBuffer.from_defaults(token_limit=3000)
)
# 保存最近10000个token的对话历史(更长的记忆)
chat_engine = index.as_chat_engine(
chat_mode="condense_question",
memory=ChatMemoryBuffer.from_defaults(token_limit=10000)
)
第四部分:常见问题
Q1: 为什么我的查询结果不准确?
可能原因:
- chunk_size太小,上下文不足
- similarity_top_k太小,没有返回足够的相关文档
- 文档质量差,包含噪音
解决方案:
# 增加chunk_size
parser = SimpleNodeParser.from_defaults(chunk_size=512)
# 增加similarity_top_k
query_engine = index.as_query_engine(similarity_top_k=5)
# 清洗文档数据
Q2: 如何处理超长文档?
# 方案1:使用更小的chunk_size
parser = SimpleNodeParser.from_defaults(chunk_size=256)
# 方案2:使用更大的chunk_overlap
parser = SimpleNodeParser.from_defaults(
chunk_size=512,
chunk_overlap=100
)
Q3: 如何提高查询速度?
# 1. 减少similarity_top_k
query_engine = index.as_query_engine(similarity_top_k=2)
# 2. 使用更小的chunk_size
parser = SimpleNodeParser.from_defaults(chunk_size=256)
Q4: 环境变量配置问题
# 确保.env文件中有DASHSCOPE_API_KEY
# 并且在代码中加载了.env文件
from dotenv import load_dotenv
load_dotenv()
下一步计划
创建房产推荐系统的LlamaIndex版本,用LlamaIndex替换RAG部分
这是我的学习笔记,希望能帮助你快速上手LlamaIndex。如果有任何问题,欢迎讨论!
更多推荐



所有评论(0)