【大模型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利用ElementTree实现快速解析XML文件

《Python利用ElementTree实现快速解析XML文件》ElementTree是Python标准库的一部分,而且是Python标准库中用于解析和操作XML数据的模块,下面小编就来和大家详细讲讲... 目录一、XML文件解析到底有多重要二、ElementTree快速入门1. 加载XML的两种方式2.

Python列表去重的4种核心方法与实战指南详解

《Python列表去重的4种核心方法与实战指南详解》在Python开发中,处理列表数据时经常需要去除重复元素,本文将详细介绍4种最实用的列表去重方法,有需要的小伙伴可以根据自己的需要进行选择... 目录方法1:集合(set)去重法(最快速)方法2:顺序遍历法(保持顺序)方法3:副本删除法(原地修改)方法4:

使用Python构建一个Hexo博客发布工具

《使用Python构建一个Hexo博客发布工具》虽然Hexo的命令行工具非常强大,但对于日常的博客撰写和发布过程,我总觉得缺少一个直观的图形界面来简化操作,下面我们就来看看如何使用Python构建一个... 目录引言Hexo博客系统简介设计需求技术选择代码实现主框架界面设计核心功能实现1. 发布文章2. 加

在Spring Boot中浅尝内存泄漏的实战记录

《在SpringBoot中浅尝内存泄漏的实战记录》本文给大家分享在SpringBoot中浅尝内存泄漏的实战记录,结合实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 目录使用静态集合持有对象引用,阻止GC回收关键点:可执行代码:验证:1,运行程序(启动时添加JVM参数限制堆大小):2,访问 htt

利用Python快速搭建Markdown笔记发布系统

《利用Python快速搭建Markdown笔记发布系统》这篇文章主要为大家详细介绍了使用Python生态的成熟工具,在30分钟内搭建一个支持Markdown渲染、分类标签、全文搜索的私有化知识发布系统... 目录引言:为什么要自建知识博客一、技术选型:极简主义开发栈二、系统架构设计三、核心代码实现(分步解析

springboot filter实现请求响应全链路拦截

《springbootfilter实现请求响应全链路拦截》这篇文章主要为大家详细介绍了SpringBoot如何结合Filter同时拦截请求和响应,从而实现​​日志采集自动化,感兴趣的小伙伴可以跟随小... 目录一、为什么你需要这个过滤器?​​​二、核心实现:一个Filter搞定双向数据流​​​​三、完整代码

Spring Security基于数据库的ABAC属性权限模型实战开发教程

《SpringSecurity基于数据库的ABAC属性权限模型实战开发教程》:本文主要介绍SpringSecurity基于数据库的ABAC属性权限模型实战开发教程,本文给大家介绍的非常详细,对大... 目录1. 前言2. 权限决策依据RBACABAC综合对比3. 数据库表结构说明4. 实战开始5. MyBA

使用Python实现快速搭建本地HTTP服务器

《使用Python实现快速搭建本地HTTP服务器》:本文主要介绍如何使用Python快速搭建本地HTTP服务器,轻松实现一键HTTP文件共享,同时结合二维码技术,让访问更简单,感兴趣的小伙伴可以了... 目录1. 概述2. 快速搭建 HTTP 文件共享服务2.1 核心思路2.2 代码实现2.3 代码解读3.

Spring Boot + MyBatis Plus 高效开发实战从入门到进阶优化(推荐)

《SpringBoot+MyBatisPlus高效开发实战从入门到进阶优化(推荐)》本文将详细介绍SpringBoot+MyBatisPlus的完整开发流程,并深入剖析分页查询、批量操作、动... 目录Spring Boot + MyBATis Plus 高效开发实战:从入门到进阶优化1. MyBatis

MyBatis 动态 SQL 优化之标签的实战与技巧(常见用法)

《MyBatis动态SQL优化之标签的实战与技巧(常见用法)》本文通过详细的示例和实际应用场景,介绍了如何有效利用这些标签来优化MyBatis配置,提升开发效率,确保SQL的高效执行和安全性,感... 目录动态SQL详解一、动态SQL的核心概念1.1 什么是动态SQL?1.2 动态SQL的优点1.3 动态S