LlamaIndex学习4-2:RAG索引存储
存储介绍
经过上一篇的测试,不管是长文档还是短文档,都会每次先进行向量化和建立索引,这里使用一下官方介绍的索引存储功能,来减少每次创建索引的步骤和模型消耗:
LlamaIndex 支持使用 HuggingFace 的 Optimum 库创建和使用 ONNX 嵌入。简单创建并保存 ONNX 嵌入,然后使用它们。
- 使用HuggingFace的Optimum库创建ONNX嵌入模型
- 实现索引的持久化存储和加载
- 避免重复创建索引,提高系统效率
完整代码:
import sys
import os
# 添加项目根目录到Python路径
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, StorageContext, load_index_from_storage
from llama_index.llms.openai_like import OpenAILike
from llama_index.embeddings.huggingface_optimum import OptimumEmbedding
from llama_index.core import Settings
import asyncio
import shutil
from utils.logger import get_logger
# 获取封装好的日志工具
logger = get_logger()
# 定义目录和路径
ONNX_MODEL_DIR = "./onnx_model"
INDEX_STORAGE_DIR = "./storage"
DATA_DIR = "data"
HF_MODEL_NAME = "BAAI/bge-large-zh-v1.5"
def setup_llm():
"""设置LLM模型"""
llm = OpenAILike(
model="Qwen/Qwen2.5-7B-Instruct",
api_key=os.getenv("SILICONFLOW_API_KEY"),
api_base=os.getenv("SILICONFLOW_BASE_URL"),
is_chat_model=True,
)
Settings.llm = llm
logger.info("已设置全局LLM模型")
return llm
def setup_optimum_embedding():
"""
设置并获取OptimumEmbedding模型
如果本地没有ONNX模型,则会创建并保存
"""
# 检查是否已经有ONNX模型
if not os.path.exists(ONNX_MODEL_DIR):
logger.info(f"正在创建ONNX模型: {HF_MODEL_NAME} -> {ONNX_MODEL_DIR}")
# 创建并保存ONNX模型
OptimumEmbedding.create_and_save_optimum_model(
HF_MODEL_NAME,
ONNX_MODEL_DIR
)
logger.info(f"ONNX模型已创建完成: {ONNX_MODEL_DIR}")
else:
logger.info(f"找到已有的ONNX模型: {ONNX_MODEL_DIR}")
# 初始化OptimumEmbedding
embed_model = OptimumEmbedding(folder_name=ONNX_MODEL_DIR)
Settings.embed_model = embed_model
logger.info("已设置全局Embedding模型")
return embed_model
def get_or_create_index(embed_model, force_rebuild=False):
"""
获取或创建向量索引
Args:
embed_model: 嵌入模型
force_rebuild: 是否强制重建索引
Returns:
创建的索引
"""
# 检查是否存在索引存储
if os.path.exists(INDEX_STORAGE_DIR) and not force_rebuild:
try:
logger.info(f"正在从本地加载索引: {INDEX_STORAGE_DIR}")
# 从存储加载索引
storage_context = StorageContext.from_defaults(persist_dir=INDEX_STORAGE_DIR)
index = load_index_from_storage(storage_context)
logger.info("索引加载成功")
return index
except Exception as e:
logger.error(f"加载索引失败: {e}")
# 如果加载失败,删除可能已损坏的存储并重新创建
logger.info("将重新创建索引")
shutil.rmtree(INDEX_STORAGE_DIR, ignore_errors=True)
# 创建新索引
logger.info("正在创建新索引...")
# 加载文档
documents = SimpleDirectoryReader(DATA_DIR).load_data()
logger.info(f"已加载 {len(documents)} 个文档")
# 创建索引
index = VectorStoreIndex.from_documents(
documents,
embed_model=embed_model,
show_progress=True
)
# 持久化保存索引
index.storage_context.persist(persist_dir=INDEX_STORAGE_DIR)
logger.info(f"索引已创建并保存到: {INDEX_STORAGE_DIR}")
return index
async def query_index(index, query_text):
"""使用索引查询文档"""
logger.info(f"查询: {query_text}")
# 创建查询引擎
query_engine = index.as_query_engine()
# 执行查询
start_time = asyncio.get_event_loop().time()
response = await query_engine.aquery(query_text)
query_time = asyncio.get_event_loop().time() - start_time
logger.info(f"查询完成,耗时: {query_time:.2f}秒")
return response
async def main():
"""主函数"""
# 设置LLM和嵌入模型
setup_llm()
embed_model = setup_optimum_embedding()
# 获取或创建索引
# 要强制重建索引,可以将force_rebuild设置为True
index = get_or_create_index(embed_model, force_rebuild=False)
# 执行测试查询
test_query = "夏栖飞的真名叫什么?"
response = await query_index(index, test_query)
print("\n查询结果:")
print(str(response))
logger.info('查询结果:',str(response))
if __name__ == "__main__":
logger.info("程序开始运行")
asyncio.run(main())
logger.info("程序运行结束")
注意事项
- 首次运行:首次运行时会下载模型并创建ONNX优化版本,可能需要一些时间
- 索引更新:当文档内容变更时,需要设置
force_rebuild=True
来重建索引 - 存储路径:确保
ONNX_MODEL_DIR
和INDEX_STORAGE_DIR
目录有写入权限 - 异常处理:代码中包含了索引加载失败时的异常处理逻辑
查询效果展示
这里直接把《庆余年》这本原著小说进行了向量化和建立索引并存储起来,第一次访问会需要等待ONNX的进度,后续再重新执行py脚本,会直接从模型里进行查询:
问:夏栖飞的真名叫什么?
RAG回答
查询结果:
夏栖飞的真名叫明青城。
问:范闲的箱子里藏着什么武器?
RAG回答
范闲的箱子里藏着一把名为M82A1的重狙。这是一把不属于这个世界、极其恐怖的武器,它曾导致庆国两位亲王的离奇死亡,帮助诚王爷登基,并让现在的庆国陛下有机会坐上龙椅。
什么是鱼肠?
RAG回答
查询结果: 鱼肠是一种特殊的剑,它被藏在鱼腹之中,可能永远不被发现,但如果被使用,就会刺入某个人的胸膛。范闲将自己和荆戈以及身边的影子比作鱼肠,表示他们虽然已经显露身份,但自己身边的鱼肠仍然隐藏未露。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!