RAG:如何从0到1搭建一个RAG应用

通过本文你可以了解到:

  • 什么是RAG?
  • 如何搭建一个RAG应用?
  • 目前开源的RAG应用有哪些?

大模型学习参考:
1.大模型学习资料整理:大模型学习资料整理:如何从0到1学习大模型,搭建个人或企业RAG系统,如何评估与优化(更新中…)
2.streamlit入门和简单使用:streamlit:如何快速构建一个应用,不会前端也能写出好看的界面

欢迎大家访问个人博客网址:https://www.maogeshuo.com,博主努力更新中…

RAG简介

在这里插入图片描述

检索增强生成Retrieval Augmented Generation,RAG)是一种强大的工具,整合了从庞大知识库中检索到的相关信息,并以此为基础,指导大型语言模型生成更为精准的答案,从而显著提升了回答的准确性与深度。

2020 年,Meta AI 研究人员提出了RAG的方法,用于提高 LLM 在特定任务上的性能。LLM 擅长语言理解、推理和生成等任务,但也存在一些问题:

  • 信息滞后:LLM 的知识是静态的,来源于当时训练时的数据,也就是 LLM 无法直接提供最新的信息。
  • 模型幻觉:实践表明,当前的生成式 AI 技术存在一定的幻觉,而在一些常见的业务应用中,我们是希望保证事实性的。
  • 私有数据匮乏:LLM 的训练数据主要来源于互联网公开的数据,而垂类领域、企业内部等有很多专属知识,这部分是 LLM 无法直接提供的。
  • 内容不可追溯: LLM 生成的内容往往缺乏明确的信息来源,影响内容的可信度。RAG 将生成内容与检索到的原始资料建立链接,增强了内容的可追溯性,从而提升了用户对生成内容的信任度。
  • 长文本处理能力较弱: LLM 在理解和生成长篇内容时受限于有限的上下文窗口,且必须按顺序处理内容,输入越长,速度越慢。RAG 通过检索和整合长文本信息,强化了模型对长上下文的理解和生成,有效突破了输入长度的限制,同时降低了调用成本,并提升了整体的处理效率。

RAG 通过将检索到的相关信息提供给 LLM,让 LLM 进行参考生成,可以较好地缓解上述问题。因此,合理使用 RAG 可以拓展 LLM 的知识边界,使其不仅能够访问专属知识库,还能动态地引入最新的数据,从而在生成响应时提供更准确、更新的信息。

RAG组成部分

自定义知识库,用于RAG检索的知识来源:

  • 结构化的数据库形态:比如MySQL
  • 非结构化的文档体系:比如文件、图片、音频、视频

RAG 是一个完整的系统,其工作流程可以简单地分为数据处理、检索、增强和生成四个阶段:

数据处理阶段

对原始数据进行清洗和处理。
将处理后的数据转化为检索模型可以使用的格式。
将处理后的数据存储在对应的数据库中。

检索阶段

将用户的问题输入到检索系统中,从数据库中检索相关信息。

增强阶段

对检索到的信息进行处理和增强,以便生成模型可以更好地理解和使用。

生成阶段

将增强后的信息输入到生成模型中,生成模型根据这些信息生成答案。

搭建RAG应用

数据处理

数据清洗和处理

数据处理阶段,一般需要对知识库中的数据进行数据清洗,比如去掉多余的换行、特殊符号,然后加载处理后的文件和分块:

  • 加载文件:使用langchain下的document_loaders加载pdf、docs、txt、md等格式文件
  • 文本分块:分块的方式有很多,选择不同的分块方法、分块大小、chunk_overlap,对最后的检索结果有影响,这一阶段也有RAG的优化点之一
import osfrom langchain_community.document_loaders import PyPDFLoader, Docx2txtLoader, UnstructuredFileLoader
from langchain_text_splitters import RecursiveCharacterTextSplitterdef load_document(file):"""加载PDF、DOC、TXT文档:param file::return:"""name, extension = os.path.splitext(file)if extension == '.pdf':print(f'Loading {file}')loader = PyPDFLoader(file)elif extension == '.docx':print(f'Loading {file}')loader = Docx2txtLoader(file)elif extension == '.txt':loader = UnstructuredFileLoader(file)else:print('Document format is not supported!')return Nonedata = loader.load()return datadef chunk_data(data, chunk_size=256, chunk_overlap=150):"""将数据分割成块:param data::param chunk_size: chunk块大小:param chunk_overlap: 重叠部分大小:return:"""text_splitter = RecursiveCharacterTextSplitter(chunk_size=chunk_size, chunk_overlap=chunk_overlap)chunks = text_splitter.split_documents(data)return chunks

embedding模型存储

将分块后的文本,使用embedding模型持久化存储,目前常用的中文模型bge-large-zh-v1.5。持久化存储后,避免每次都去embedding一次,消耗很长的时间。下次使用时,直接加载模型就可以了。

import osfrom langchain_community.embeddings import HuggingFaceBgeEmbeddings, OpenAIEmbeddings
from langchain_community.vectorstores import Chroma, FAISSdef get_embedding(embedding_name):"""根据embedding名称去加载embedding模型:param embedding_name: 路径或者名称:return:"""if embedding_name == "bge":embedding_path = os.environ[embedding_name]model_kwargs = {'device': 'cpu'}return HuggingFaceBgeEmbeddings(model_name=embedding_path, model_kwargs=model_kwargs)if embedding_name == "bce":return None# create embeddings using OpenAIEmbeddings() and save them in a Chroma vector store
def create_embeddings_chroma(chunks):embeddings = OpenAIEmbeddings()vector_store = Chroma.from_documents(chunks, embeddings)# if you want to use a specific directory for chromadb# vector_store = Chroma.from_documents(chunks, embeddings, persist_directory='./mychroma_db')return vector_storedef create_embeddings_faiss(vector_db_path, embedding_name, chunks):"""使用FAISS向量数据库,并保存:param vector_db_path: 向量:param embedding_name::param chunks::return:"""embeddings = get_embedding(embedding_name)db = FAISS.from_documents(chunks, embeddings)if not os.path.isdir(vector_db_path):os.mkdir(vector_db_path)db.save_local(folder_path=vector_db_path)return dbdef load_embeddings_faiss(vector_db_path, embedding_name):"""加载向量库:param vector_db_path::param embedding_name::return:"""embeddings = get_embedding(embedding_name)db = FAISS.load_local(vector_db_path, embeddings, allow_dangerous_deserialization=True)return db

构建模型

采用了函数和类两种方式定义模型:

  • 函数:get_llm_model定义了基本的参数,model、prompt、temperature、max_tokens、n_ctx
  • 自定义类:
import os
import sys
import time
from abc import ABCfrom langchain_core.callbacks import CallbackManagerForLLMRun
from llama_cpp import Llama
from langchain.llms.base import LLM
from pydantic import Field
from typing import Dict, Any, Mapping, Optional, ListBASE_DIR = os.path.dirname(__file__)
# PRJ_DIR上层目录
# PRJ_DIR = os.path.abspath(os.path.join(BASE_DIR, ".."))
sys.path.append(BASE_DIR)def get_llm_model(prompt: str = None,model: str = None,temperature: float = 0.0,max_token: int = 2048,n_ctx: int = 512):"""根据模型名称去加载模型,返回response数据:param prompt::param model::param temperature::param max_token::param n_ctx::return:"""if model in ['Qwen_q2']:model_path = os.environ[model]llm = Llama(model_path=model_path, n_ctx=n_ctx)start = time.time()response = llm.create_chat_completion(messages=[{"role": "system","content": "你是一个智能超级助手,请用专业的词语回答问题,整体上下文带有逻辑性,如果不知道,请不要乱说",},{"role": "user","content": "{}".format(prompt)},],temperature=temperature,max_tokens=max_token,stream=False)cost = time.time() - startprint(f"模型生成时间:{cost}")print(f"大模型回复:\n{response}")return response['choices'][0]['message']['content']class QwenLLM(LLM):"""自定义QwenLLM"""model_name: str = "Qwen_q2"# 访问时延上限request_timeout: float = None# 温度系数temperature: float = 0.1# 窗口大小n_ctx = 2048# token大小max_tokens = 1024# 必备的可选参数model_kwargs: Dict[str, Any] = Field(default_factory=dict)def _call(self, prompt: str, stop: Optional[List[str]] = None,run_manager: Optional[CallbackManagerForLLMRun] = None,**kwargs: Any):qwen_path = os.environ[self.model_name]print("qwen_path:", qwen_path)llm = Llama(model_path=qwen_path, n_ctx=self.n_ctx)response = llm.create_chat_completion(messages=[{"role": "system","content": "你是一个智能超级助手,请用[中文]专业的词语回答问题,整体上下文带有逻辑性,并以markdown格式输出",},{"role": "user","content": "{}".format(prompt)},],temperature=self.temperature,max_tokens=self.max_tokens,stream=False)# prompt工程提示# print(f"Qwen prompt: \n{prompt}")# response = lla(#     prompt=prompt,#     temperature=self.temperature,#     max_tokens=self.max_tokens# )print(f"Qwen response: \n{response}")# return response['choices'][0]['text']return response['choices'][0]['message']['content']@propertydef _llm_type(self) -> str:return "Llama3"# 定义一个返回默认参数的方法@propertydef _default_params(self) -> Dict[str, Any]:"""获取调用默认参数。"""normal_params = {"temperature": self.temperature,"request_timeout": self.request_timeout,"n_ctx": self.n_ctx,"max_tokens": self.max_tokens}# print(type(self.model_kwargs))return {**normal_params}@propertydef _identifying_params(self) -> Mapping[str, Any]:"""Get the identifying parameters."""return {**{"model_name": self.model_name}, **self._default_params}

构建应用

import sysimport streamlit as st
import os
from langchain.chains import RetrievalQA
from langchain.chat_models import ChatOpenAI
import tiktoken
from dotenv import load_dotenv, find_dotenv
from langchain_core.prompts import PromptTemplateBASE_DIR = os.path.dirname(__file__)
PRJ_DIR = os.path.abspath(os.path.join(BASE_DIR, ".."))
sys.path.append(PRJ_DIR)from streamlit_demo.custom_llm import QwenLLM
from streamlit_demo.embedding_oper import create_embeddings_faiss, create_embeddings_chroma, load_embeddings_faiss
from streamlit_demo.prepare_data import load_document, chunk_data_ = load_dotenv(find_dotenv(), override=True)
vector_db_path = os.path.join(BASE_DIR, "vector_db")
print(f"vector_db_path: {vector_db_path}")DEFAULT_TEMPLATE = """你是一个聪明的超级智能助手,请用专业且富有逻辑顺序的句子回复,并以中文形式且markdown形式输出。检索到的信息:{context}问题:{question}
"""def ask_and_get_answer_from_local(model_name, vector_db, prompt, top_k=5):"""从本地加载大模型:param model_name: 模型名称:param vector_db::param prompt::param top_k::return:"""docs_and_scores = vector_db.similarity_search_with_score(prompt, k=top_k)print("docs_and_scores: ", docs_and_scores)# knowledge = [doc.page_content for doc in docs_and_scores]# print("检索到的知识:", knowledge)if model_name == "Qwen_q2":llm = QwenLLM(model_name=model_name, temperature=0.4)prompt_template = PromptTemplate(input_variables=["context", "question"], template=DEFAULT_TEMPLATE)retriever = vector_db.as_retriever(search_type='similarity', search_kwargs={'k': top_k})chain = RetrievalQA.from_chain_type(llm=llm,chain_type="stuff",retriever=retriever,chain_type_kwargs={"prompt": prompt_template},return_source_documents=True)answer = chain({"query": prompt, "top_k": top_k})print(f"answers: {answer}")# answer = chain.run(prompt)# answer = answer['choices'][0]['message']['content']answer = answer['result']return answerdef ask_and_get_answer(vector_store, q, k=3):llm = ChatOpenAI(model='gpt-3.5-turbo', temperature=1)retriever = vector_store.as_retriever(search_type='similarity', search_kwargs={'k': k})chain = RetrievalQA.from_chain_type(llm=llm, chain_type="stuff", retriever=retriever)answer = chain.run(q)return answer# calculate embedding cost using tiktoken
def calculate_embedding_cost(texts):enc = tiktoken.encoding_for_model('text-embedding-ada-002')total_tokens = sum([len(enc.encode(page.page_content)) for page in texts])# print(f'Total Tokens: {total_tokens}')# print(f'Embedding Cost in USD: {total_tokens / 1000 * 0.0004:.6f}')return total_tokens, total_tokens / 1000 * 0.0004# clear the chat history from streamlit session state
def clear_history():if 'history' in st.session_state:del st.session_state['history']if __name__ == "__main__":# st.image('img.png')st.subheader('LLM Question-Answering Application 🤖')with st.sidebar:# text_input for the OpenAI API key (alternative to python-dotenv and .env)api_key = st.text_input('OpenAI API Key:', type='password')if api_key:os.environ['OPENAI_API_KEY'] = api_keyllm = st.selectbox(label="请选择本地大模型",options=('Qwen_q2', 'Qwen_q6'))# 向量数据库embedding = st.selectbox("请选择向量数据库",('FAISS', 'Chroma'))# file uploader widgetuploaded_file = st.file_uploader('上传文件:', type=['pdf', 'docx', 'txt'])# chunk size number widgetchunk_size = st.number_input('chunk_size:', min_value=100, max_value=2048, value=512, on_change=clear_history)# chunk overlapchunk_overlap = st.number_input(label="chunk_overlap", min_value=0, max_value=1024, value=150,on_change=clear_history)# k number input widgetk = st.number_input('top_k', min_value=1, max_value=20, value=3, on_change=clear_history)# add data button widgetadd_data = st.button('添加数据', on_click=clear_history)# 输出方式output_type = st.selectbox("选择输出方式", ('普通输出', '流式输出'))if uploaded_file and add_data:  # if the user browsed a filewith st.spinner('Reading, chunking and embedding file ...'):# writing the file from RAM to the current directory on diskbytes_data = uploaded_file.read()file_name = os.path.join('./', uploaded_file.name)with open(file_name, 'wb') as f:f.write(bytes_data)data = load_document(file_name)chunks = chunk_data(data, chunk_size=chunk_size, chunk_overlap=chunk_overlap)st.write(f'Chunk size: {chunk_size}, chunk_overlap: {len(chunks)} Chunks: {len(chunks)}')tokens, embedding_cost = calculate_embedding_cost(chunks)st.write(f'Embedding cost: ${embedding_cost:.4f}')# creating the embeddings and returning the Chroma vector store# 指定选择向量库和embedding类型,还可改进if embedding == "FAISS":vector_store = create_embeddings_faiss(vector_db_path=vector_db_path, embedding_name="bge",chunks=chunks)elif embedding == "Chroma":vector_store = create_embeddings_chroma(chunks)# saving the vector store in the streamlit session state (to be persistent between reruns)st.session_state.vs = vector_storest.success('File uploaded, chunked and embedded successfully.')# 初始化historyif "messages" not in st.session_state:st.session_state.messages = []# 展示对话for msg in st.session_state.messages:with st.chat_message(msg['role']):st.markdown(msg["content"])# React to user inputif prompt := st.chat_input("Say something"):# Display user message in chat message containerwith st.chat_message("user"):st.markdown(prompt)# Add user message to chat historyst.session_state.messages.append({"role": "user", "content": prompt})# load local vector dbif 'vs' not in st.session_state:# st.warning(body='正在努力加载模型中...', icon="⚠️")vector_store = load_embeddings_faiss(vector_db_path, "bge")st.session_state.vs = vector_storest.toast('Load vector store db success!', icon='😍')# 普通方式输出if prompt is not None:vector_store = st.session_state.vs# if vector_store is None:#     st.warning(body='正在努力加载模型中,稍后再试', icon="⚠️")if output_type == "普通输出" and vector_store is not None:response = ""if llm == "GPT":response = ask_and_get_answer(vector_store, prompt, k)elif llm == "Qwen_q2":response = ask_and_get_answer_from_local(model_name="Qwen_q2", vector_db=vector_store, prompt=prompt, top_k=k)# Display assistant response in chat message containerwith st.chat_message("assistant"):st.markdown(response)# Add assistant response to chat historyst.session_state.messages.append({"role": "assistant", "content": response})else:# 流式输出# stream_res = get_llm_model_with_stream(prompt=prompt, model="Qwen_q2")# with st.chat_message("assistant"):#     content = st.write_stream(stream_res)# print("流式输出:", content)# st.session_state.messages.append({"role": "assistant", "content": content})print("流式输出")# run the app: streamlit run ./chat_doc.py

结果展示

使用步骤:

  1. 选择参数,然后上传本地的文件
  2. 开始添加数据,用于数据处理和embedding持久化存储在这里插入图片描述
    在这里插入图片描述

开源的RAG应用

QAnything: https://github.com/netease-youdao/QAnything

AnythingLLM:https://github.com/Mintplex-Labs/anything-llm

ragflow:https://github.com/infiniflow/ragflow/blob/main/README_zh.md

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/346455.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

手机连接ESP8266的WIFI,进入内置网页,输入要显示的内容,在OLED显示屏上显示文本

在这篇技术博客中,我们将探讨如何使用ESP8266 Wi-Fi 模块和SSD1306 OLED显示屏,构建一个简易的信息显示和交互系统。此系统能够让用户通过一个简单的Web界面输入信息,并将其显示在OLED屏幕上。这种设备的应用非常广泛,可以用于智能…

vue实现pdf下载——html2canvas

html2canvas 官方文档https://html2canvas.hertzen.com/getting-started html2canvas 的原理是通过遍历DOM树,将每一个HTML元素转化为Canvas对象,并叠加到一起形成一张完整的图片或者PDF文件。 1. 安装插件 npm install html2canvas jspdf --save 2.使用(页面已经…

Leetcode刷题笔记9

1. 两数之和 1. 两数之和 - 力扣(LeetCode) 解法一:暴力枚举 没什么好说的,直接使用两个for循环,i从第一个元素开始,j从第二个元素开始遍历并寻找 时间复杂度:O(N^2) 空间复杂度&#xff1a…

4_机械臂位姿求逆理论及代码计算

1、aubo arcs sdk poseInverse 使用例子 auto cur_pose rpc_cli->getRobotInterface(robot_name)->getRobotState()->getTcpPose();// 2.288083 0.035207 1.550335auto pose_inv rpc_cli->getMath()->poseInverse(cur_pose);//结果:0.118611 -0.57…

blender

通用设置: 仅显示/取消隐藏:数字键盘/ 移动视角:shift鼠标中键 Blender如何给场景添加参考图片-百度经验 (baidu.com) 进入编辑模式:Tab 编辑模式:点-线-面 两个视图 法向显示:就能变成恶心的蓝红色 显…

自动驾驶---Perception之视觉点云雷达点云

1 前言 在自动驾驶领域,点云技术的发展历程可以追溯到自动驾驶技术的早期阶段,特别是在环境感知和地图构建方面。 在自动驾驶技术的早期技术研究中,视觉点云和和雷达点云都有出现。20世纪60年代,美国MIT的Roberts从2D图像中提取3D…

解锁ChatGPT:从GPT-2实践入手解密ChatGPT

⭐️我叫忆_恒心,一名喜欢书写博客的研究生👨‍🎓。 如果觉得本文能帮到您,麻烦点个赞👍呗! 近期会不断在专栏里进行更新讲解博客~~~ 有什么问题的小伙伴 欢迎留言提问欧,喜欢的小伙伴给个三连支…

LabVIEW开发实验室超导体电流特性测试系统

本系统旨在为学校实验室提供一个基于LabVIEW的超导体电流特性测试平台,通过精确测量超导体在不同温度和电流条件下的电学特性,帮助学生和研究人员深入理解超导体的物理性质。本文将从背景、目标、工作原理、使用方法、操作流程和注意事项等方面详细介绍该…

现代x86汇编-环境安装

今天端午节,独自在家,翻阅了张银奎老师编写的《现代x86汇编语言程序设计》一书,前言部分说明书中示例代码都是用微软visual C工具编写并使用微软宏汇编(著名的MASM)编译的,好久没有用微软vc了,假…

【UML用户指南】-13-对高级结构建模-包

目录 1、名称 2、元素 3、可见性 4、引入与引出 用包把建模元素安排成可作为一个组来处理的较大组块。可以控制这些元素的可见性,使一些元素在包外是可见的,而另一些元素要隐藏在包内。也可以用包表示系统体系结构的不同视图。 狗窝并不复杂&#x…

排序题+贪心

排序力扣题 一&#xff1a;合并区间 56. 合并区间 方法一&#xff1a;先排序再合并 如图&#xff0c;把区间按照起点从小到达排序&#xff0c;如果起点相同那么按照终点小的优先排序 然后每次记录一个区间&#xff0c;访问下一个区间&#xff1a; 如果下一个区间的起点<前…

Hexo+Github搭建个人博客教程

hexo官网&#xff1a;https://hexo.io/zh-cn/ butterfly 主题设置&#xff1a;https://butterfly.js.org/ GitHub地址&#xff1a;https://github.com/jerryc127/hexo-theme-butterfly 基础命令 初始化博客命令&#xff1a;hexo init “文件名” 开启本地服务&#xff08;本…

支付交易——在线支付系统基本概念

摘要 本文聚集于实战&#xff0c;只讲解最实用的知识点&#xff0c;至于支付起源、在线支付发展历程等科普知识&#xff0c;感兴趣的读者可参考其它优秀的支付类书籍或网络上其它优秀的文章。本章内容对大部分专业概念进行了极致简化&#xff0c;以便更好地帮助读者入门。实际…

XSS(跨站脚本攻击)

1.什么是xss XSS全称&#xff08;Cross Site Scripting&#xff09;跨站脚本攻击&#xff0c;为了避免和CSS层叠样式表名称冲突&#xff0c;所以改为了 XSS&#xff0c;是最常见的Web应用程序安全漏洞之一,XSS是指攻击者在网页中嵌入客户端脚本&#xff0c;通常是JavaScript编写…

Word中插入Mathtype右编号,调整公式与编号的位置

当你已经将mathtype内置于word后&#xff0c;可以使用右编号快速插入公式 但是往往会出现公式和编号出现的位置或之间的距离不合适 比如我在双栏下插入公式&#xff0c;会发现插入的公式与编号是适用于单栏的 解决办法&#xff1a; 开始->样式->MTDisplayLquation -&g…

【Python Cookbook】S02E04 文本模式的匹配和查找 match()、search()、findall() 以及 捕获组和 + 的含义

目录 问题解决方案讨论 问题 本文讨论一些按照特定的文本模式进行的查找和匹配。 解决方案 如果想要匹配的只是简单文字&#xff0c;通常我们使用一些内置的基本字符串方法即可&#xff0c;如&#xff1a;str.find()&#xff0c;str.startwith()&#xff0c;str.endswith() …

电脑存储设备,固态硬盘介绍,usb接口

简介 存储设备分为两大类主存和辅存&#xff0c;另外还有专门提供存储服务的网络存储 主存储器 随机存取存储器&#xff08;RAM, Random Access Memory&#xff09; 特点&#xff1a;高速、易失性存储器&#xff0c;断电后数据丢失。用途&#xff1a;临时存储正在使用的数据…

【Oracle生产运维】数据库服务器负载过高异常排查处理

说明 在Oracle数据库运维工作中&#xff0c;经常会遇到Oracle数据库服务器平均负载&#xff08;load average&#xff09;突然异常升高&#xff0c;如果放任不管&#xff0c;严重的情况下会出现数据库宕机、服务器重启等重大故障。因此&#xff0c;当发现数据库服务器平均负载…

log4j日志打印导致OOM问题

一、背景 某天压测&#xff0c;QPS压到一定值后机器就开始重启&#xff0c;出现OOM&#xff0c;好在线上机器配置了启动参数-XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/**/**heapdump.hprof。将dump文件下载到本地&#xff0c;打开Java sdk bin目录下的jvisualvm工具&a…

事业单位——被逆袭篇

目录 一、结果 二、考试 三、时间 四、复习 五、总结 一、结果 图1&#xff1a;2024年浙江广播电视集团下属浙江省中波发射管理中心公开招聘笔面试结果 准考证号笔试面试总成绩排名备注107016070.866.48310702416555.44107134390.871.681入围107146869.869.08210715406454.…