构建Agents框架|LlamaIndex使用实战之RAG

本文较长,建议点赞收藏。更多AI大模型应用开发学习视频及资料,在智泊AI

01 前言

RAG(Retrieval Augmented Generation:检索增强生成)是LlamaIndex的核心功能模块,覆盖了从数据加载索引构建存储管理及检索的全流程。其数据连接与加载能力,更是LlamaIndex早期开源阶段的一大亮点。通过连接和检索特定数据片段,RAG不仅有效解决了大语言模型(LLM)面临的上下文长度限制,也保障了私有领域数据的安全。

02 RAG介绍

RAG(Retrieval Augmented Generation:检索增强生成) 是一种增强大模型上下文来提高大模型回答准确性的有效且重要手段。通过检索特定内容也保障了企业数据的安全。

其流程如下(图来自官方):

核心步骤包括:

  • Loading:

    数据加载,通过加载不同的数据源进行数据获取,如数据库、PDF、API接口等等方式,LlamaIndex目前支持上百种数据源的连接。

  • Indexing:

    索引构建,包括对加载数据分块、向量化以及元数据提取,向量化通常被用于语义匹配。

  • Storing:

    存储,对构建的向量数据和元数据进行存储,避免对源数据进行多次向量化操作。

  • Querying:

    检索,根据用户问题对数据进行检索召回,并加入到大模型的上下文中。常见检索的方式包括向量化语义检索同时伴有元数据过滤、全文关键词检索、混合检索并应用重排序等。

  • Evaluating: 评估效果,RAG的效果评估是对结果的检验,也是进行优化索引和检索的依据。

03 LlamaIndex的RAG实战

数据加载

LlamaIndex提供了上百种连接数据的方式,包括:本地文件加载集成连接器 (可以通过llamahub搜索)、通过文本创建Document。加载后的数据都统一转换成Document类对象。

1、本地文件加载

python 复制代码
from llama_index.core import SimpleDirectoryReader
documents = SimpleDirectoryReader("../data").load_data()
print(f"document length:{len(documents)}, document:{documents}")

LlamaIndex中定义了SimpleDirectoryReader类,是内置的最简单的方式,对本地文件进行加载。示例中会加载当前目录的上级data目录下所有文件。

2、集成连接器

以DB为例,通过连接DB,执行Sql查询的每一条数据都是一个Document。

ini 复制代码
# pip3 install llama-index-readers-database
from llama_index.readers.database import DatabaseReader
# 使用PyMySQL:pip3 install pymysql
# {root}换成密码
reader_media = DatabaseReader(
uri="mysql+pymysql://root:{root}@localhost:3306/demo",
schema="demo",  # optional namespace
)
# 加载
docs = reader_media.load_data(
query="SELECT id,username,age,sex,register_time,address FROM xudj.user;",
# 元数据列
metadata_cols=[
("id", "user_id"), # 参数转换,用user_id替代id存储
"username",
],  # map / include in metadata
excluded_text_cols=["address"],  # 剔除字段
document_id=lambda row: f"xudj-user-{row['id']}",  # custom document id
)
print(docs)

示例首先导入数据库相关依赖,这里使用的是Mysql,可以通过llamahub.ai/?tab=readers搜索使用方式,然后定义DatabaseReader配置连接信息,再调用load_data执行sql完成数据加载。

3、通过文本创建Document

python 复制代码
from llama_index.core import Document
doc = Document(doc_id="1", text="文本创建", metadata={"id":"1", "type":"custom"})
print(doc)
print(doc.metadata)

有了文本,这是最直接的方式。

索引构建

数据加载后,有了一系列的Document,便可以进行索引的构建。包括长文本按特定规则分块、提取元数据、向量化处理。

ini 复制代码
from llama_index.core.node_parser import TokenTextSplitter
from llama_index.core import VectorStoreIndex
from llama_index.core import Settings
from llama_index.embeddings.dashscope import DashScopeEmbedding
from llama_index.core import StorageContext
# pip install chromadb
# pip install llama-index-vector-stores-chroma
import chromadb
from llama_index.vector_stores.chroma import ChromaVectorStore
# 1、分块 - chunk_size每块的token数
text_splitter = TokenTextSplitter(chunk_size=200, chunk_overlap=5)
# 2、向量化模型,使用千问的模型
dashscope_embed_model = DashScopeEmbedding(
model_name="text-embedding-v2",
api_key="sk-...")
# 向量化存储,默认使用基于内存的SimpleVectorStore类
# 初始化客户端,设置保存数据的路径
db = chromadb.PersistentClient(path="./chroma_db")
# 3、创建向量存储上下文
chroma_collection = db.get_or_create_collection("quickstart")
vector_store = ChromaVectorStore(chroma_collection=chroma_collection)
storage_context = StorageContext.from_defaults(vector_store=vector_store)
# 4、创建index
index = VectorStoreIndex.from_documents(
documents=documents,
transformations=[text_splitter],
embed_model=dashscope_embed_model,
insert_batch_size=5,  # 限制每次插入的节点数量
storage_context=storage_context,
)

示例包括4部分(下面序号对应代码注释序号):

1、首先定义了分块逻辑,示例使用Token计数的分块方式,默认是按完整句子拆分的SentenceSplitter类,它们都属于NodeParser的子类,用于将Document拆分成Node(Node表示Document的一个分块"chunk",可以是文本或图片等);在LlamaIndex中,不管是分块XxxSplitter类、提取元数据XxxExtractor类(示例未体现)、向量化Node数据XxxEmbedding类,它们都属于transformations,在解析Document时,可以指定多个,transformations是一个列表。

2、定义向量化模型为千问的text-embedding-v2,LlamaIndex默认使用的OpenAI的text-embedding-ada-002模型,这和框架的起源有关,一开始就是为了解决OpenAI系列模型相关问题。

3、创建向量化存储库,示例使用的是开源向量库chroma,指定存储目录和集合名。

4、定义Index(由Document组成的数据结构) ,并指定文档Documents、转换列表transformations、向量化模型及每批次数量、向量化存储库。VectorStoreIndex是LlamaIndex中最常见的索引类型,通过将Document拆分成Node,然后进行向量化每个Node,为后续语义检索做准备。

示例代码中定义的所有组件,在LlamaIndex中都有默认的实现,我们可以通过Settings查询,同时也可以通过Settings进行设置默认值,如下所示:

python 复制代码
from llama_index.core import Settings
import os
# 随便设置值,不然会报错找不到apiKey
os.environ["OPENAI_API_KEY"] = "sk-..."
# 默认使用OpenAI的text-embedding-ada-002模型
print(f"Settings.embed_model :{Settings.embed_model}")
# 默认使用的SentenceSplitter,这三个取得都是相同的默认node_parser
print(f"Settings.transformations :{Settings.transformations}")
print(f"Settings.text_splitter :{Settings.text_splitter}")
print(f"Settings.node_parser :{Settings.node_parser}")
# global可以通过这种方式设置
# Settings.text_splitter = text_splitter

上面我们已经把数据进行了向量化存储,如果想要基于存储的向量数据构建Index索引,可以使用如下方式,不用再次经过一遍上面的流程处理数据了:

ini 复制代码
# 从向量库中创建index,指定向量库和模型
index = VectorStoreIndex.from_vector_store(
vector_store=vector_store,
embed_model=dashscope_embed_model,
)

1、有关Document和Node介绍:developers.llamaindex.ai/python/fram..._guides/loading/documents_and_nodes/

类定义见:llama_index.core.schema

2、chroma开源向量库:

github.com/chroma-core...

3、Index不同的索引类型和检索介绍:

developers.llamaindex.ai/python/fram..._guides/indexing/index_guide/#vector-store-index

检索查询

有了数据也进行了向量化存储,并得到了Index索引对象,现在就可以进行检索并加入大模型上下文,回答问题。LlamaIndex的检索查询包括三步骤:检索Node -> 后置处理过滤Node -> 响应合成

1、仅检索

ini 复制代码
# 上文得到的index索引对象
retriever = index.as_retriever()
nodes = retriever.retrieve("育儿方式")
print(f"len:{len(nodes)} nodes:{nodes[0].text}")
# 另一种方式:直接定义VectorIndexRetriever,指定index
retriever = VectorIndexRetriever(
index=index,
similarity_top_k=1, # 召回一条数据
)
nodes = retriever.retrieve("育儿方式")
print(f"len:{len(nodes)} nodes:{nodes[0].text}")

示例使用index的as_retriever()方法得到VectorIndexRetriever,然后调用retrieve方法来检索node列表。直接定义VectorIndexRetriever效果一样。

2、后置处理过滤Node

ini 复制代码
# 导入postprocessor
from llama_index.core.postprocessor import KeywordNodePostprocessor, SimilarityPostprocessor
node_postprocessors=[
# 相似性
SimilarityPostprocessor(
similarity_cutoff=0.1
)
]
query = index.as_query_engine(llm=llm,
node_postprocessors=node_postprocessors)

示例使用了相似性分数过滤Node,过滤后把更少的Node信息作为上下文传递给大模型,减少大模型的Token数量以及降低响应时间。更多后置处理器见下面地址。

3、响应合成-检索后发起大模型对话

ini 复制代码
# 查询
from llama_index.llms.deepseek import DeepSeek
llm = DeepSeek(model="deepseek-chat",
api_key="sk-...")
from llama_index.core.response_synthesizers import ResponseMode
from llama_index.core import get_response_synthesizer
# 定义响应合成逻辑,默认是ResponseMode.COMPACT
# "compact": 合并Node作为上下文,如果超长,则使用refine提炼,减少模型的请求次数,最终借助生成一个答案
response_synthesizer = get_response_synthesizer(
response_mode=ResponseMode.COMPACT
)
query = index.as_query_engine(llm=llm,
response_synthesizer=response_synthesizer)
print(query.query("育儿方式?"))

示例通过使用index的as_query_engine()方法得到query引擎,并指定响应合成模式"compact" ,这个也是默认模式,含义见上代码注释,最后调用query方法借助LLM生成答案。

as_query_engine会获取无状态的查询引擎,另有as_chat_engine支持多轮对话的有状态查询引擎。

compact是其中一种合成模式,让ChatGPT基于官方文档生成的不同模式的对照表,供参考:

为什么会有不同的响应合成模式呢?

是因为 RAG 里拿到的文档往往是多个 chunk,而 LLM 的 prompt 有长度限制。

所以 LlamaIndex 提供了多种"怎样合成答案"的策略。

不同的后置过滤器:

developers.llamaindex.ai/python/fram..._guides/querying/node_postprocessors/node_postprocessors/

不同响应合成模式:

developers.llamaindex.ai/python/fram..._guides/querying/response_synthesizers/

及对应代码类型定义:llama_index.core.response_synthesizers.type.ResponseMode

04 总结

从整体来看,LlamaIndex 已经构建出一个覆盖 数据接入 → 文本解析 → 向量化与存储 → 检索策略 → 响应生成 的完整 RAG 技术栈,提供了高度模块化、可自由组合的能力。

本文展示的示例仅是 LlamaIndex 功能体系中的一小部分。更多丰富的索引类型、检索模式、数据连接器,以及企业级场景的模板与实践,可以在官方文档或 LlamaHub 中学习和阅读。

值得一提的是,除了 RAG 相关组件外,LlamaIndex 还提供了 基于事件驱动的 Workflow 工作流 与 Agent 智能体 能力,能够进一步支持复杂任务编排、多模型协作等更高级的应用模式。这些在后续文章中逐一展开。

学习资源推荐

如果你想更深入地学习大模型,以下是一些非常有价值的学习资源,这些资源将帮助你从不同角度学习大模型,提升你的实践能力。

本文较长,建议点赞收藏。更多AI大模型应用开发学习视频及资料,在智泊AI

相关推荐
大模型教程2 小时前
LangChain×Qwen3:高性能RAG系统实战项目
程序员·langchain·llm
AI大模型2 小时前
打造生产级复杂 RAG 系统:LangChain, LangGraph 与 RAGAS 实战指南
langchain·llm·agent
AI大模型2 小时前
写给小白看的使用LangChain构建基于知识图谱的RAG系统实战教程
langchain·llm·agent
EdisonZhou4 小时前
MAF快速入门(2)Agent的花样玩法
llm·aigc·agent·.net core
程序员鱼皮5 小时前
Gemini 3.0 炸裂发布!前端又死了???
前端·ai·程序员·互联网·代码
FreeCode5 小时前
LangGraph1.0智能体开发:Graph API概念与设计
python·langchain·agent
howcode6 小时前
女友去玩,竟带回一道 “虐哭程序员” 的难题
后端·设计模式·程序员
用户6600676685396 小时前
大模型调用外部工具获取实时天气的实现方法
llm·openai
AI-Frontiers6 小时前
谷歌重磅出品!揭秘21种Agentic设计模式,AI从业者必备
agent