一.RAG技术原理1.什么是RAGRetrieval-Augmented Generation (RAG-检索增强生成) 是帮助大模型更好地回答问题的一种方式,在大模型的应用场景中一个非常常见的需求是手里有一堆文档当遇到一个问题时希望通过其中的一段文本来找到答案,这时RAG检索增强生成技术就能派上用场,它的作用就是从这些文档中找到最相关的信息然后利用大模型生成精准的回答,简单来说RAG就是帮助大模型‘找到’并‘利用’最相关的信息来回答你的问题就像一个聪明的助手快速翻阅资料找到最合适的答案,如下图所示能这样实现的原因在于已经有非常多的实验论文能够证明:当为大模型提供一定的上下文信息后其输出会变得更稳定。那么将知识库中的信息或掌握的信息先输送给大模型再由大模型服务用户就是大家普遍达成共识的一个结论和方法。传统的对话系统、搜索引擎等核心依赖于检索技术如果将这一检索过程融入大模型应用的构建中既可以充分利用大模型在内容生成上的能力也能通过引入的上下文信息显著约束大模型的输出范围和结果同时还实现了将私有数据融入大模型中的目的达到了双赢的效果。从技术实现细节上看,RAG的实现是包括两个阶段检索阶段和生成阶段,在检索阶段从知识库中找出与问题最相关的知识为后续的答案生成提供素材,在生成阶段RAG会将检索到的知识内容作为输入与问题一起输入到语言模型中进行生成,这样生成的答案不仅考虑了问题的语义信息还考虑了相关私有数据的内容,如下图所示为什么要搞得这个复杂呢还是在于大模型本身的以下两个特性1.如果用户提出的问题其对应的答案出现在一篇文章中去知识库中找到一篇与用户输入相关的文章是很容易的但是将检索到的这整篇文章直接放入Prompt中并不是最优的选择因为其中一定会包含非常多无关的信息而无效信息越多对大模型后续的推理影响越大2.任何一个大模型都存在最大输入的Token限制如果将一整个文本去全部传入大模型无法容纳如此多的信息而RAG构建的关键则是由检索组件通常由Embedding模型和向量数据库组成和生成组件大模型组成, 在推理时用户查询用于对索引文档运行相似性搜索以检索与查询最相似的文档并为大模型提供额外的上下文,因此在构建RAG的过程中也往往是遵循着这种流程去做定制化的开发,那么每个环节中应该掌握哪些知识呢来看下面的这个思维导图[Embedding 模型选择]在Hugging Face上有一个榜单(MTEB-Massive Text Embedding Benchmark),是目前最权威的嵌入模型评估基准之一目前该榜单主要分为新版和旧版可以根据需要访问以下地址新版 MTEB 排行榜支持更多自定义筛选https://huggingface.co/spaces/mteb/leaderboard旧版 MTEB 排行榜包含中文等语言筛选https://huggingface.co/spaces/mteb/leaderboard_legacy查看榜单的实用建议1.按需筛选MTEB 总分是所有子任务的均分但如果是做 RAG 应用建议重点查看Retrieval检索子项的得分而不是盲目追求总分第一2.多维度过滤在新版榜单中可以结合语言(如中文)、任务类型如 BEIR 检索、模型可用性开源或闭源以及是否经过指令微调Instruction-tuned来进行精准筛选3.结合实际榜单分数存在微小差距或过拟合可能建议结合模型的参数量大小、上下文长度限制以及实际的业务场景(如是否需要处理长文本、特定专业领域等)来综合选择在实现RAG系统时主要有三种方式手动实现、框架实现和手动框架结合的方式,通常企业中不会选择完全手动实现因为这样不仅工作量大而且维护复杂,而手动框架结合的方式因其灵活性和高效性会是90%以上开发者的首选2.RAG技术必要性介绍大型语言模型LLM在生成语言时可能会产生幻觉或不准确的结果,这些问题包括信息误导模型生成的内容可能基于不准确或过时的信息知识更新滞后模型无法访问最新的信息尤其是在专业领域推理能力不足LLM 在面对复杂推理任务时难以提供高效且准确的结果为了解决这些问题检索增强生成Retrieval-Augmented Generation, RAG应运而生,RAG 通过先从数据库中检索与问题相关的信息再基于检索到的内容进行回答生成极大地提升了模型输出的准确性和相关性,RAG 不仅提高了知识更新的效率还显著增强了生成内容的可追溯性使其在实际应用中更具实用性和可信度3.一个RAG系统的核心组件说明为了实现 RAG 模型需要以下几个核心模块(1).向量化模块用于将文档片段转换为向量表示以便后续检索(2).文档加载与切分模块负责加载文档并将其切分为若干易于处理的文档片段(3).数据库模块用于存储文档片段及其对应的向量表示(4).检索模块根据用户输入的查询检索与其相关的文档片段(5).生成模块调用语言模型生成基于检索信息的回答基本构成如下图所示而这些组件也是接下来手动构建一个RAG系统的基础据此也可以看出一个RAG系统的基本流程索引将文档库分割成较短的 Chunk并通过编码器构建向量索引检索根据问题和 chunks 的相似度检索相关文档片段生成以检索到的上下文为条件生成问题的回答这些模块共同构成了一个简易 RAG 的核心架构,每个模块在模型的不同阶段发挥作用确保查询的处理从检索到生成都有条不紊地进行二.文本向量化模块创建正如此前所说在自然语言处理和机器学习领域中Embedding 是将文本转化为数值向量的常用方法,通过这种方式模型可以衡量不同文本之间的相似性,进而应用于如搜索、分类、推荐等多个领域,OpenAI 发布的其第三代 Embedding 模型这些模型具有更高的性能、更低的成本,且在多语言处理上表现出色,以下是关于OpenAI Embedding模型的详细介绍和调用方法1.OpenAI Embedding模型简介Embedding是将文本字符串表示为向量浮点数列表通过计算向量之间的距离来衡量文本之间的相关性.向量距离越小表示文本之间的相关性越高距离越大相关性越低,常见的 Embedding 应用包括搜索根据文本查询的相关性对结果进行排序聚类根据文本相似性将其分组推荐根据相关文本字符串推荐项目异常检测识别与其他内容相关性较低的异常点多样性测量分析相似性分布分类将文本字符串根据其最相似的标签进行分类OpenAI 最新的 Embedding 模型包括text-embedding-3-small和text-embedding-3-large它们比以往的模型具有更高的性能且支持更多语言,这两个模型分别生成长度为1536 和 3072的向量,此外用户可以通过设置维度参数来减少向量的维度而不损失其表示概念的能力2.OpenAI Embedding模型获取要获取文本的 Embedding 向量可以将文本字符串发送到 OpenAI 的 Embedding API 端点并指定所使用的模型例如text-embedding-3-small.响应结果将包含 Embedding 向量以及一些额外的元数据信息截止目前OpenAI 提供了两个第三代 Embedding 模型定价基于输入的 token 数量。以下是关于模型性能与定价的概览模型每美元支持的页面数量在 MTEB 测试中的表现最大输入 token 数量text-embedding-3-small62,50062.3%8191text-embedding-3-large9,61564.6%8191text-embedding-ada-00212,50061.0%8191Embedding 向量可以直接用于多种应用场景例如存储在向量数据库中进行文本相似度搜索等,默认情况下text-embedding-3-small生成的向量长度为 1536text-embedding-3-large的向量长度为 3072余弦相似度介绍与效果简介欧氏距离与余弦相似度计算公式假设现有a、b两个向量例如余弦相似度可以通过如下方式进行计算和呈现import matplotlib.pyplot as plt import numpy as np # 创建两个向量 a np.array([0, 1]) b np.array([1, 1]) # 计算两个向量的余弦相似度 cosine_similarity np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)) # 绘制向量 plt.quiver(0, 0, a[0], a[1], anglesxy, scale_unitsxy, scale1, colorr) plt.quiver(0, 0, b[0], b[1], anglesxy, scale_unitsxy, scale1, colorb) # 设置图表属性 plt.xlim(-0.5, 1.5) plt.ylim(-0.5, 1.5) plt.grid() plt.title(fCosine Similarity: {cosine_similarity:.2f}) plt.xlabel(X axis) plt.ylabel(Y axis) # 添加图例 plt.legend([Vector a, Vector b]) # 显示图表 plt.show()这幅图展示了二维空间中两个向量的方向:红色向量代表\(a→\) 蓝色向量代表 \(b→\),它们之间的夹角表示了两个向量的余弦相似度,余弦相似度是通过计算两个向量的点积并除以它们各自的范数即长度来得到的,在这个示例中这两个向量的余弦相似度大约为 0.71意味着它们在方向上有一定程度的相似性。这个值越接近 1表示两个向量的方向越相似为了实现 RAG 模型的功能首先需要一个向量化Embedding模块,向量化是 RAG 的基础它的作用是将文档片段转化为向量表示便于后续的检索操作,在这个过程中将实现一个向量化类用来将文本片段映射成向量为了便于扩展和未来可能使用不同的模型首先编写一个Embedding 基类,该基类定义了获取文本向量表示的方法同时包含一个计算两个向量之间余弦相似度的功能,这样如果未来使用不同的向量化模型只需继承该基类并重写向量获取的逻辑而不需要重复编写相似度计算部分class BaseEmbeddings: 向量化的基类用于将文本转换为向量表示。不同的子类可以实现不同的向量获取方法。 def __init__(self, path: str, is_api: bool) - None: 初始化基类。 参数 path (str) - 如果是本地模型path 表示模型路径如果是API模式path可以为空 is_api (bool) - 表示是否使用API调用如果为True表示通过API获取Embedding self.path path self.is_api is_api def get_embedding(self, text: str, model: str) - List[float]: 抽象方法用于获取文本的向量表示具体实现需要在子类中定义。 参数 text (str) - 需要转换为向量的文本 model (str) - 所使用的模型名称 返回 list[float] - 文本的向量表示 raise NotImplementedError classmethod def cosine_similarity(cls, vector1: List[float], vector2: List[float]) - float: 计算两个向量之间的余弦相似度用于衡量它们的相似程度。 参数 vector1 (list[float]) - 第一个向量 vector2 (list[float]) - 第二个向量 返回 float - 余弦相似度值范围从 -1 到 1越接近 1 表示向量越相似 dot_product np.dot(vector1, vector2) # 向量点积 magnitude np.linalg.norm(vector1) * np.linalg.norm(vector2) # 向量的模 if not magnitude: return 0 return dot_product / magnitude # 计算余弦相似度在这个基类基础上可以通过继承它来实现具体的模型,例如可以使用 OpenAI 的 API 来生成文本的向量表示只需重写get_embedding方法即可class OpenAIEmbedding(BaseEmbeddings): 使用 OpenAI 的 Embedding API 来获取文本向量的类继承自 BaseEmbeddings。 def __init__(self, path: str , is_api: bool True) - None: 初始化类设置 OpenAI API 客户端如果使用的是 API 调用。 参数 path (str) - 本地模型的路径使用API时可以为空 is_api (bool) - 是否通过 API 获取 Embedding默认为 True super().__init__(path, is_api) if self.is_api: # 初始化 OpenAI API 客户端 from openai import OpenAI self.client OpenAI() self.client.api_key os.getenv(OPENAI_API_KEY) # 从环境变量中获取 API 密钥 self.client.base_url os.getenv(OPENAI_BASE_URL) # 从环境变量中获取 API 基础URL def get_embedding(self, text: str, model: str text-embedding-3-large) - List[float]: 使用 OpenAI 的 Embedding API 获取文本的向量表示。 参数 text (str) - 需要转化为向量的文本 model (str) - 使用的 Embedding 模型名称默认为 text-embedding-3-large 返回 list[float] - 文本的向量表示 if self.is_api: # 去掉文本中的换行符保证输入格式规范 text text.replace(\n, ) # 调用 OpenAI API 获取文本的向量表示 return self.client.embeddings.create(input[text], modelmodel).data[0].embedding else: raise NotImplementedError # 如果不是 API 模式这里未实现本地模型的处理这样设计的结构让我们可以轻松替换或扩展向量化模型而不需要改变整体框架三.文档加载与切分模块创建在实现了向量化之后接下来需要编写一个文档加载与切分模块用于处理不同格式的文档并将其切分为小片段,为什么要进行切分呢这是为了确保每个文档片段都尽量保持简短且信息集中以便于后续的向量化和检索1.文档格式处理函数目标是支持多种格式的文档例如 PDF、Markdown、TXT 等,每种文件格式都有不同的读取方式下面展示一个支持多种格式的简单实现:def read_file_content(cls, file_path: str): # 根据文件扩展名选择读取方法 if file_path.endswith(.pdf): return cls.read_pdf(file_path) elif file_path.endswith(.md): return cls.read_markdown(file_path) elif file_path.endswith(.txt): return cls.read_text(file_path) else: raise ValueError(Unsupported file type)2.文档切分函数这里考虑将文档按Token长度进行切分设置一个最大的 Token 长度然后按这个长度进行切分,在这个过程中也会确保每个片段之间有一定的重叠避免重要信息被切掉def get_chunk(cls, text: str, max_token_len: int 600, cover_content: int 150): chunk_text [] curr_len 0 curr_chunk lines text.split(\n) # 以换行符为单位切分文本 for line in lines: line line.replace( , ) line_len len(enc.encode(line)) # 计算当前行的 Token 长度 if line_len max_token_len: print(warning line_len , line_len) if curr_len line_len max_token_len: curr_chunk line \n curr_len line_len 1 else: chunk_text.append(curr_chunk) curr_chunk curr_chunk[-cover_content:] line curr_len line_len cover_content if curr_chunk: chunk_text.append(curr_chunk) return chunk_text四.词向量数据库与向量检索模块接下来将继续构建向量数据库以及检索模块这是RAG 模型中的核心功能之一,向量数据库用于存储文档片段及其对应的向量表示而检索模块则根据用户提出的问题Query在数据库中检索相关的文档片段,通过这些功能创建的简易 RAG 能够根据输入的查询快速找到最相关的文档片段为了构建这个向量数据库需要以下几个关键功能持久化存储persist将数据库存储到本地便于下次加载使用加载数据库load_vector从本地文件加载已经存储的向量和文档获取向量表示get_vector将文档转化为向量表示并存储检索query根据用户的 Query检索数据库中的相关文档片段五.大模型问答模块编写现在已经构建了向量数据库和检索模块接下来将实现大模型模块用于根据检索到的相关文档片段生成对用户问题的回答,为了简化和便于扩展会先实现一个基类BaseModel然后再以GPT4o模型为例展示如何使用大语言模型来完成这个任务1.大模型模块的基类这里先编写一个基类BaseModel它包含两个主要方法chat负责处理用户的输入并生成回答load_model如果是使用本地模型这个方法负责加载模型,如果使用 API 模型如 OpenAI可以不用实现这个方法class BaseModel: 基础模型类作为所有模型的基类。 包含一些通用的接口如加载模型、生成回答等。 def __init__(self, path: str ) - None: self.path path # 用于存储模型文件的路径默认为空。 def chat(self, prompt: str, history: List[dict], content: str) - str: 使用模型生成回答的抽象方法。 :param prompt: 用户的提问内容 :param history: 之前的对话历史字典列表 :param content: 提供的上下文内容 :return: 模型生成的答案 pass # 具体的实现由子类提供 def load_model(self): 加载模型的方法通常用于本地模型。 pass # 如果是 API 模型可能不需要实现2.借助GPT4o模型进行对话class GPT4oChat(BaseModel): 基于 GPT-4o 模型的对话类继承自 BaseModel。 主要用于通过 OpenAI API 来生成对话回答。 def __init__(self, api_key: str, base_url: str https://ai.devtool.tech/proxy/v1) - None: 初始化 GPT-4o 模型。 :param api_key: OpenAI API 的密钥 :param base_url: 用于访问 OpenAI API 的基础 URL默认为代理 URL super().__init__() self.client OpenAI(api_keyapi_key, base_urlbase_url) # 初始化 OpenAI 客户端 def chat(self, prompt: str, history: List [], content: str ) - str: 使用 GPT-4o 生成回答。 :param prompt: 用户的提问 :param history: 之前的对话历史可选 :param content: 可参考的上下文信息可选 :return: 生成的回答 # 构建包含问题和上下文的完整提示 full_prompt PROMPT_TEMPLATE[GPT4o_PROMPT_TEMPLATE].format(questionprompt, contextcontent) # 调用 GPT-4o 模型进行推理 response self.client.chat.completions.create( modelgpt-4o-mini, # 使用 GPT-4o 小型模型 messages[ {role: user, content: full_prompt} ] ) # 返回模型生成的第一个回答 return response.choices[0].message.content3.提示模板为了方便维护和复用提示语可以使用一个字典来保存不同模型的提示模板,在这里为GPT4o定义了一个模板PROMPT_TEMPLATE dict( GPT4o_PROMPT_TEMPLATE 下面有一个或许与这个问题相关的参考段落若你觉得参考段落能和问题相关则先总结参考段落的内容。 若你觉得参考段落和问题无关则使用你自己的原始知识来回答用户的问题并且总是使用中文来进行回答。 问题: {question} 可参考的上下文 ··· {context} ··· 有用的回答: )六.RAG Demo完整流程演示接下来展示一个完整的 RAG Demo物流助手结合前面实现的向量检索和大模型模块展示如何在实际应用中使用 RAG 模型来回答问题# coding:utf-8 # 导入必备的工具包 import time from langchain_core.prompts import PromptTemplate from langchain_ollama import OllamaLLM from langchain_ollama import OllamaEmbeddings from langchain_community.vectorstores import FAISS # 向量数据库 # 加载FAISS向量库 embeddings OllamaEmbeddings(modelmxbai-embed-large, temperature0) db FAISS.load_local(faiss/wuliu, embeddings, allow_dangerous_deserializationTrue) start_time time.time() def get_related_content(related_docs): related_content [] for doc in related_docs: related_content.append(doc.page_content.replace(\n\n, \n)) return \n.join(related_content) def define_prompt(): question 我的快递出发地是哪预计几天的时间到达 docs db.similarity_search(question, k2) related_content get_related_content(docs) PROMPT_TEMPLATE 基于以下已知信息简洁和专业的来回答用户的问题。不允许在答案中添加编造成分。 已知内容: {context} 问题: {question} prompt PromptTemplate( input_variables[context, question], templatePROMPT_TEMPLATE, ) my_pmt prompt.format(contextrelated_content, questionquestion) return my_pmt def main(): model OllamaLLM(modelqwen2.5:7b) my_pmt define_prompt() print(f提示词模板{my_pmt}) result model.invoke(my_pmt) return result if __name__ __main__: result main() print(* * 80) print(result) print(* * 80) end_time time.time() print(f推理耗时{end_time - start_time:.4f}s)RAG相关项目:【AI大模型应用开发】【项目实战】13.RAG智慧问答项目-(一)项目介绍项目架构项目环境配置【AI大模型应用开发】【项目实战】8.物流行业信息咨询RAG系统