【大模型LLMs】RAG实战:基于LlamaIndex快速构建RAG链路(Qwen2-7B-Instruct+BGE Embedding)

本文主要是介绍【大模型LLMs】RAG实战:基于LlamaIndex快速构建RAG链路(Qwen2-7B-Instruct+BGE Embedding),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

【大模型LLMs】RAG实战:基于LlamaIndex快速构建RAG链路(Qwen2-7B-Instruct+BGE Embedding)

  • 1. 环境准备
  • 2. 数据准备
  • 3. RAG框架构建
    • 3.1 数据读取 + 数据切块
    • 3.2 构建向量索引
    • 3.3 检索增强
    • 3.4 main函数
  • 参考

基于LlamaIndex框架,以Qwen2-7B-Instruct作为大模型底座,bge-base-zh-v1.5作为embedding模型,构建RAG基础链路。数据集选用cmrc2018数据集(该数据集禁止商用)

1. 环境准备

安装LlamaIndex的相关依赖

pip install llama-index
pip install llama-index-llms-huggingface
pip install llama-index-embeddings-huggingface
pip install datasets

从Hugging Face安装将要使用的LLMs以及embedding model,这里我们选择Qwen/Qwen2-7B-Instruct作为大模型底座,选择BAAI/bge-base-zh-v1.5作为embedding模型,用于对文档进行向量化表征
这里介绍快速下载huggingface模型的命令行方法:

1. 首先安装依赖
pip install -U huggingface_hub
pip install -U hf-transfer 
2. 设置环境变量(设置hf环境变量为1用于提升下载速度;设置镜像站地址)
export HF_HUB_ENABLE_HF_TRANSFER=1
export HF_ENDPOINT="https://hf-mirror.com"
3. 安装相应的模型(以Qwen2-7B-Instruct为例,前面是huggingface上的模型名,后面是本地下载的路径)
huggingface-cli download Qwen/Qwen2-7B-Instruct --local-dir ./Qwen2-7B-Instruct
huggingface-cli download BAAI/bge-base-zh-v1.5 --local-dir ./bge-base-zh-v1.5

2. 数据准备

使用开源数据集 cmrc2018 构建本地知识库,该数据集由人类专家在维基百科段落上标注的近2万个真实问题组成,包含上下文(可以用于构建本地知识库)、问题和答案

在这里插入图片描述

我们读取cmrc的train数据集,并将question,answer以及context存储到本地data目录下的cmrc-eval-zh.jsonl文件下,代码如下:

import json
from tqdm import tqdm
from datasets import load_datasetcmrc = load_dataset("cmrc2018")
with open("../data/cmrc-eval-zh.jsonl", "w") as f:for train_sample in tqdm(cmrc["train"]):qa_context_mp = {"question": train_sample["question"],"reference_answer": train_sample["answers"]["text"][0],"reference_context": train_sample["context"]}f.write(json.dumps(qa_context_mp, ensure_ascii=False) + "\n")

在这里插入图片描述

3. RAG框架构建

在具体构建rag链路之前,我们初始化一个日志记录logger,用于记录运行过程中的信息

import logging# 设置logger
logging.basicConfig(level=logging.DEBUG,filename='../out/output.log',  # 替换成保存日志的本地目录datefmt='%Y/%m/%d %H:%M:%S',format='%(asctime)s - %(name)s - %(levelname)s - %(lineno)d - %(module)s - %(message)s'
)
logger = logging.getLogger(__name__)

3.1 数据读取 + 数据切块

读取第二节中构建的cmrc-eval-zh.jsonl文件,将reference_context字段读取出来保存到list中作为后续知识库的文本,另外将question-answer pair进行保存,用于后续模型的推理。然后构建llama_index.core的Document对象。如果是本地的txt文档知识库,也可以直接使用SimpleDirectoryReader方法

from llama_index.core import Document, SimpleDirectoryReaderdef get_documents_qa_data(documents_path):# 遍历jsonl文件,读取reference_context、question、reference_answer字段text_list = []qa_data_mp = []with open(documents_path, 'r', encoding="utf-8") as f:for line in f:sample = json.loads(line)if sample["reference_context"] not in text_list:text_list.append(sample["reference_context"])qa_data_mp.append({"question": sample["question"],"reference_answer": sample["reference_answer"]})# 构建Document对象列表documents = [Document(text=t) for t in text_list]"""# 如果直接读取本地txt文档documents = SimpleDirectoryReader(input_files='xxx.txt').load_data()"""# 如果documents长度为0,则在日志中记录if len(documents) == 0:logger.warning('documents list length::: 0')logger.info('documents build successfully!')return documents, qa_data_mp

考虑到实际RAG应用场景中,很多文档的长度过长,一方面难以直接放入LLM的上下文中,另一方面在可能导致检索过程忽略一些相关的内容导致参考信息质量不够,一般会采用对文本进行chunk的操作,将一段长文本切分成多个小块,这些小块在LlamaIndex中表示为Node节点。上述过程代码如下,我们将documents传入到下面定义的函数中,通过SimpleNodeParser对文本进行切块,并设置chunk的size为1024

from llama_index.core.node_parser import SimpleNodeParserdef get_nodes_from_documents_by_chunk(documents):# 对文本进行chunknode_paeser = SimpleNodeParser.from_defaults(chunk_size=1024)# [node1,node2,...] 每个node的结构为{'Node_ID':xxx, 'Text':xxx}nodes = node_paeser.get_nodes_from_documents(documents=documents)# 如果nodes长度为0,则在日志中记录if len(nodes) == 0:logger.warning('nodes list length::: 0')logger.info('nodes build successfully!')return nodes

3.2 构建向量索引

在RAG的retrieval模块中,一般采用的检索方式有两种:

  • 基于文本匹配的检索(例如,bm25算法)
  • 基于向量相似度的检索(embedding+relevance computing,例如bge等模型)

本文对前面构建的node节点中的文本进行向量化表征(基于BAAI/bge-base-zh-v1.5),然后构建每个node的索引,这里的embedding模型我们在第一节环境准备过程中已经下载至本地目录
此外,在构建索引后,可以使用StorageContext将index存储到本地空间,后续调用get_vector_index时可以先判断本地是否有存储过storage_context,如果有则直接加载即可(通过load_index_from_storage),如果没有则通过传入的nodes参数再次构建向量索引

from llama_index.core import Settings, VectorStoreIndex, StorageContext, load_index_from_storage
from llama_index.embeddings.huggingface import HuggingFaceEmbedding# 设置embedding model(bge-base-zh-v1.5)
Settings.embed_model = HuggingFaceEmbedding(model_name = "../../../model/bge-base-zh-v1.5"
)def get_vector_index(nodes=None, documents=None, store_path='../store/', use_store=True):# 如果已经进行过持久化数据库的存储,则直接加载if use_store:storage_context = StorageContext.from_defaults(persist_dir=store_path)index = load_index_from_storage(storage_context)# 如果没有存储,则直接构建vectore-store-indexelse:# 构建向量索引vector-indexif nodes is not None:index = VectorStoreIndex(nodes, # 构建的节点,如果没有进行chunk,则直接传入documents即可embed_model=Settings.embed_model # embedding model设置)else:index = VectorStoreIndex(documents, # 没有进行chunk,则直接传入documentsembed_model=Settings.embed_model # embedding model设置)#进行持久化存储index.storage_context.persist(store_path)  logger.info('vector-index build successfully!\nindex stores in the path:::{}'.format(store_path))return index

构建好的index保存到本地’…/store/'目录下:

在这里插入图片描述

3.3 检索增强

接下来,我们在代码中设置将要使用的LLMs,本文选择通义千问的Qwen2-7B-Instruct模型,在第一节中也已经下载至本地,通过HuggingFaceLLM设置。(其他大模型的使用方式也是类似,如果是OpenAI的大模型则不使用该方式,此类教程很多,本文不在赘述)

下面的代码中,首先使用HuggingFaceLLM设置通义千问大模型;同时根据通义千问的官方文档中的LlamaIndex使用demo,完成messages_to_prompts和completion_to_prompt两个函数的设置(新起一个utils.py用于存放着两个函数)

from llama_index.core.llms import ChatMessage
from llama_index.llms.huggingface import HuggingFaceLLM
from utils import messages_to_prompt, completion_to_prompt# 设置llm(Qwen2-7B-Instruct)
# 设置llm(Qwen2-7B-Instruct)
Settings.llm = HuggingFaceLLM(model_name="../../../model/Qwen2-7B-Instruct",tokenizer_name="../../../model/Qwen2-7B-Instruct",context_window=30000,max_new_tokens=2000,generate_kwargs={"temperature": 0.7,"top_k": 50, "top_p": 0.95},messages_to_prompt=messages_to_prompt,completion_to_prompt=completion_to_prompt,device_map="auto"
)def get_llm_answer_with_rag(query, index, use_rag=True):if use_rag:query_engine = index.as_query_engine()resp = query_engine.query(query).responselogger.info('use rag, query:::{}, resp:::{}'.format(query, resp))else:resp = Settings.llm.chat(messages=[ChatMessage(content=query)])logger.info('not use rag, query:::{}, resp:::{}'.format(query, resp))return resp
# utils.py
def messages_to_prompt(messages):prompt = ""for message in messages:if message.role == "system":prompt += f"<|im_start|>system\n{message.content}<|im_end|>\n"elif message.role == "user":prompt += f"<|im_start|>user\n{message.content}<|im_end|>\n"elif message.role == "assistant":prompt += f"<|im_start|>assistant\n{message.content}<|im_end|>\n"if not prompt.startswith("<|im_start|>system"):prompt = "<|im_start|>system\n" + promptprompt = prompt + "<|im_start|>assistant\n"return promptdef completion_to_prompt(completion):return f"<|im_start|>system\n<|im_end|>\n<|im_start|>user\n{completion}<|im_end|>\n<|im_start|>assistant\n"

3.4 main函数

最后,我们在main函数里面讲前面的整个链路打通,同时我们也从cmrc-eval-zh.jsonl中读取qa对

from collections import defaultdictdef main():documents_path = '../data/cmrc-eval-zh.jsonl'store_path = '../store/'# 加载文档documents, qa_data_mp = get_documents_qa_data(documents_path)# 进行chunknodes = get_nodes_from_documents_by_chunk(documents)# 构建向量索引index = get_vector_index(nodes=nodes, store_path=store_path, use_store=False)# 获取检索增强的llm回复for qa_data in qa_data_mp:query = qa_data["question"]reference_answer = qa_data["reference_answer"]llm_resp = get_llm_answer_with_rag(query, index, use_rag=True)print("query::: {}".format(query))print("reference answer::: {}".format(reference_answer))print("answer::: {}".format(llm_resp))print("*"*100)if __name__ == "__main__":main()

运行后结果如下:

在这里插入图片描述

参考

  1. LlamaIndex官方文档
  2. Qwen官方文档
  3. 本项目Github链接

这篇关于【大模型LLMs】RAG实战:基于LlamaIndex快速构建RAG链路(Qwen2-7B-Instruct+BGE Embedding)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1091540

相关文章

从原理到实战深入理解Java 断言assert

《从原理到实战深入理解Java断言assert》本文深入解析Java断言机制,涵盖语法、工作原理、启用方式及与异常的区别,推荐用于开发阶段的条件检查与状态验证,并强调生产环境应使用参数验证工具类替代... 目录深入理解 Java 断言(assert):从原理到实战引言:为什么需要断言?一、断言基础1.1 语

Java MQTT实战应用

《JavaMQTT实战应用》本文详解MQTT协议,涵盖其发布/订阅机制、低功耗高效特性、三种服务质量等级(QoS0/1/2),以及客户端、代理、主题的核心概念,最后提供Linux部署教程、Sprin... 目录一、MQTT协议二、MQTT优点三、三种服务质量等级四、客户端、代理、主题1. 客户端(Clien

在Spring Boot中集成RabbitMQ的实战记录

《在SpringBoot中集成RabbitMQ的实战记录》本文介绍SpringBoot集成RabbitMQ的步骤,涵盖配置连接、消息发送与接收,并对比两种定义Exchange与队列的方式:手动声明(... 目录前言准备工作1. 安装 RabbitMQ2. 消息发送者(Producer)配置1. 创建 Spr

深度解析Spring Boot拦截器Interceptor与过滤器Filter的区别与实战指南

《深度解析SpringBoot拦截器Interceptor与过滤器Filter的区别与实战指南》本文深度解析SpringBoot中拦截器与过滤器的区别,涵盖执行顺序、依赖关系、异常处理等核心差异,并... 目录Spring Boot拦截器(Interceptor)与过滤器(Filter)深度解析:区别、实现

深度解析Spring AOP @Aspect 原理、实战与最佳实践教程

《深度解析SpringAOP@Aspect原理、实战与最佳实践教程》文章系统讲解了SpringAOP核心概念、实现方式及原理,涵盖横切关注点分离、代理机制(JDK/CGLIB)、切入点类型、性能... 目录1. @ASPect 核心概念1.1 AOP 编程范式1.2 @Aspect 关键特性2. 完整代码实

MySQL中的索引结构和分类实战案例详解

《MySQL中的索引结构和分类实战案例详解》本文详解MySQL索引结构与分类,涵盖B树、B+树、哈希及全文索引,分析其原理与优劣势,并结合实战案例探讨创建、管理及优化技巧,助力提升查询性能,感兴趣的朋... 目录一、索引概述1.1 索引的定义与作用1.2 索引的基本原理二、索引结构详解2.1 B树索引2.2

Linux如何快速检查服务器的硬件配置和性能指标

《Linux如何快速检查服务器的硬件配置和性能指标》在运维和开发工作中,我们经常需要快速检查Linux服务器的硬件配置和性能指标,本文将以CentOS为例,介绍如何通过命令行快速获取这些关键信息,... 目录引言一、查询CPU核心数编程(几C?)1. 使用 nproc(最简单)2. 使用 lscpu(详细信

从入门到精通MySQL 数据库索引(实战案例)

《从入门到精通MySQL数据库索引(实战案例)》索引是数据库的目录,提升查询速度,主要类型包括BTree、Hash、全文、空间索引,需根据场景选择,建议用于高频查询、关联字段、排序等,避免重复率高或... 目录一、索引是什么?能干嘛?核心作用:二、索引的 4 种主要类型(附通俗例子)1. BTree 索引(

Java Web实现类似Excel表格锁定功能实战教程

《JavaWeb实现类似Excel表格锁定功能实战教程》本文将详细介绍通过创建特定div元素并利用CSS布局和JavaScript事件监听来实现类似Excel的锁定行和列效果的方法,感兴趣的朋友跟随... 目录1. 模拟Excel表格锁定功能2. 创建3个div元素实现表格锁定2.1 div元素布局设计2.

Redis 配置文件使用建议redis.conf 从入门到实战

《Redis配置文件使用建议redis.conf从入门到实战》Redis配置方式包括配置文件、命令行参数、运行时CONFIG命令,支持动态修改参数及持久化,常用项涉及端口、绑定、内存策略等,版本8... 目录一、Redis.conf 是什么?二、命令行方式传参(适用于测试)三、运行时动态修改配置(不重启服务