【大模型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

相关文章

Python版本信息获取方法详解与实战

《Python版本信息获取方法详解与实战》在Python开发中,获取Python版本号是调试、兼容性检查和版本控制的重要基础操作,本文详细介绍了如何使用sys和platform模块获取Python的主... 目录1. python版本号获取基础2. 使用sys模块获取版本信息2.1 sys模块概述2.1.1

使用EasyPoi快速导出Word文档功能的实现步骤

《使用EasyPoi快速导出Word文档功能的实现步骤》EasyPoi是一个基于ApachePOI的开源Java工具库,旨在简化Excel和Word文档的操作,本文将详细介绍如何使用EasyPoi快速... 目录一、准备工作1、引入依赖二、准备好一个word模版文件三、编写导出方法的工具类四、在Export

Python爬虫HTTPS使用requests,httpx,aiohttp实战中的证书异步等问题

《Python爬虫HTTPS使用requests,httpx,aiohttp实战中的证书异步等问题》在爬虫工程里,“HTTPS”是绕不开的话题,HTTPS为传输加密提供保护,同时也给爬虫带来证书校验、... 目录一、核心问题与优先级检查(先问三件事)二、基础示例:requests 与证书处理三、高并发选型:

使用Node.js和PostgreSQL构建数据库应用

《使用Node.js和PostgreSQL构建数据库应用》PostgreSQL是一个功能强大的开源关系型数据库,而Node.js是构建高效网络应用的理想平台,结合这两个技术,我们可以创建出色的数据驱动... 目录初始化项目与安装依赖建立数据库连接执行CRUD操作查询数据插入数据更新数据删除数据完整示例与最佳

Linux五种IO模型的使用解读

《Linux五种IO模型的使用解读》文章系统解析了Linux的五种IO模型(阻塞、非阻塞、IO复用、信号驱动、异步),重点区分同步与异步IO的本质差异,强调同步由用户发起,异步由内核触发,通过对比各模... 目录1.IO模型简介2.五种IO模型2.1 IO模型分析方法2.2 阻塞IO2.3 非阻塞IO2.4

Oracle Scheduler任务故障诊断方法实战指南

《OracleScheduler任务故障诊断方法实战指南》Oracle数据库作为企业级应用中最常用的关系型数据库管理系统之一,偶尔会遇到各种故障和问题,:本文主要介绍OracleSchedul... 目录前言一、故障场景:当定时任务突然“消失”二、基础环境诊断:搭建“全局视角”1. 数据库实例与PDB状态2

Git进行版本控制的实战指南

《Git进行版本控制的实战指南》Git是一种分布式版本控制系统,广泛应用于软件开发中,它可以记录和管理项目的历史修改,并支持多人协作开发,通过Git,开发者可以轻松地跟踪代码变更、合并分支、回退版本等... 目录一、Git核心概念解析二、环境搭建与配置1. 安装Git(Windows示例)2. 基础配置(必

Docker多阶段镜像构建与缓存利用性能优化实践指南

《Docker多阶段镜像构建与缓存利用性能优化实践指南》这篇文章将从原理层面深入解析Docker多阶段构建与缓存机制,结合实际项目示例,说明如何有效利用构建缓存,组织镜像层次,最大化提升构建速度并减少... 目录一、技术背景与应用场景二、核心原理深入分析三、关键 dockerfile 解读3.1 Docke

MyBatis分页查询实战案例完整流程

《MyBatis分页查询实战案例完整流程》MyBatis是一个强大的Java持久层框架,支持自定义SQL和高级映射,本案例以员工工资信息管理为例,详细讲解如何在IDEA中使用MyBatis结合Page... 目录1. MyBATis框架简介2. 分页查询原理与应用场景2.1 分页查询的基本原理2.1.1 分

使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解

《使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解》本文详细介绍了如何使用Python通过ncmdump工具批量将.ncm音频转换为.mp3的步骤,包括安装、配置ffmpeg环... 目录1. 前言2. 安装 ncmdump3. 实现 .ncm 转 .mp34. 执行过程5. 执行结