公司动态
RAG-技术笔记 (一)
RAGRAGRAG 检索增强生成Retrieval Augmented Generation已经成为当下最火热的LLM应用方案和打开方式了。理解起来不难就是通过自有垂域数据库检索相关信息然后合并成为提示模板给大模型润色生成回答。每当将大模型应用于实际业务场景时发现通用的基础大模型基本无法满足实际业务需求主要有以下几方面原因知识的局限性大模型自身的知识完全源于训练数据而现有的主流大模型deepseek、文心一言、通义千问…的训练集基本都是构建于网络公开的数据对于一些实时性的、非公开的或私域的数据是没有。幻觉问题所有的深度学习模型的底层原理都是基于数学概率模型输出实质上是一系列数值运算大模型也不例外所以它经常会一本正经地胡说八道尤其是在大模型自身不具备某一方面的知识或不擅长的任务场景。数据安全性对于企业来说数据安全至关重要没有企业愿意承担数据泄露的风险尤其是大公司没有人将私域数据上传第三方平台进行训练会推理。这也导致完全依赖通用大模型自身能力的应用方案不得不在数据安全和效果方面进行取舍。而RAG就是解决上述问题的有效方案。一句话总结RAG中文为检索增强生成 检索技术 LLM 提示。例如向 LLM 提问一个问题qustionRAG 从各种数据源检索相关的信息并将检索到的信息和问题answer注入到 提示词模板中LLM 最后给出答案。许多产品基于 RAG 构建从基于 web 搜索引擎和 LLM 的问答服务到使用私有数据的应用程序。其实早在2019年Faiss 就实现了基于嵌入的向量搜索技术现在 RAG 推动了向量搜索领域的发展。后来比如 chroma、http://weaviate.io 和 pinecone 这些基于开源搜索索引引擎主要是 faiss 和 nmslib向量数据库初创公司增加了输入文本的额外存储和其他工具。在这个过程中有两个主要步骤语义搜索和生成输出。在语义搜索步骤中从知识库中找到与要回答的查询最相关的部分内容。然后在生成步骤中将使用这些内容来生成响应。有3个最著名的基于 LLM 的管道和应用程序的开源库——LangChain 和 LlamaIndexDify。本文主要参考 LlamaIndex的实现来系统讲解高级 RAG 技术以方便大家深入研究。如果想了解dify一文入门智能体dify 超快速构建AI agent481 赞同 · 33 评论文章如果想了解langchainLangChain指南打造LLM的垂域AI框架62 赞同 · 5 评论文章RAG实现过程目前已经知道RAG融合是一种用于可能提升RAG应用检索阶段的技术。下面这张图片展示了大概的工作流程。基本上主要思路就是利用LLM来生成多个查询期望能够通过这些查询让问题的各个方面在上下文中显现出来。之后你可以使用生成的查询进行向量搜索如本系列之前的部分所述并且基于其在结果集中的显示方式来对内容进行重新排序。可以用下面提示词生成额外问题You are a helpful assistant that generates multiple search queries based on a single input query.Generate multiple search queries related to: {USER_INPUT}OUTPUT (4 queries):如上所述LLM能够生成覆盖原问题多个方面的查询。这样可以在数据库中找到包含各个相关方面的信息从而潜在地提高从RAG应用得到的结果。RAG架构RAG的架构如图中所示简单来讲RAG就是通过检索获取相关的知识并将其融入Prompt让大模型能够参考相应的知识从而给出合理回答。因此可以将RAG的核心理解为“检索生成”前者主要是利用向量数据库的高效存储和检索能力召回目标知识后者则是利用大模型和Prompt工程将召回的知识合理利用生成目标答案。RAG架构完整的RAG应用流程主要包含两个阶段数据准备阶段数据提取——文本分割——向量化embedding——数据入库应用阶段用户提问——数据检索召回——注入Prompt——LLM生成答案下面详细介绍一下各环节的技术细节和注意事项数据准备阶段数据准备一般是一个离线的过程主要是将私域数据向量化后构建索引并存入数据库的过程。主要包括数据提取、文本分割、向量化、数据入库等环节。数据准备数据提取数据加载包括多格式数据加载、不同数据源获取等根据数据自身情况将数据处理为同一个范式。数据处理包括数据过滤、压缩、格式化等。元数据获取提取数据中关键信息例如文件名、Title、时间等 。文本分割文本分割主要考虑两个因素1embedding模型的Tokens限制情况2语义完整性对整体的检索效果的影响。一些常见的文本分割方式如下句分割以”句”的粒度进行切分保留一个句子的完整语义。常见切分符包括句号、感叹号、问号、换行符等。固定长度分割根据embedding模型的token长度限制将文本分割为固定长度例如256/512个tokens这种切分方式会损失很多语义信息一般通过在头尾增加一定冗余量来缓解。向量化embedding向量化是一个将文本数据转化为向量矩阵的过程该过程会直接影响到后续检索的效果。目前常见的embedding模型如表中所示这些embedding模型基本能满足大部分需求但对于特殊场景例如涉及一些罕见专有词或字等或者想进一步优化效果则可以选择开源Embedding模型微调或直接训练适合自己场景的Embedding模型。模型名称描述获取地址ChatGPT-EmbeddingChatGPT-Embedding由OpenAI公司提供以接口形式调用。https://platform.openai.com/docs/guides/embeddings/what-are-embeddingsERNIE-Embedding V1ERNIE-Embedding V1由百度公司提供依赖于文心大模型能力以接口形式调用。https://cloud.baidu.com/doc/WENXINWORKSHOP/s/alj562vvuM3EM3E是一款功能强大的开源Embedding模型包含m3e-small、m3e-base、m3e-large等多个版本支持微调和本地部署。https://huggingface.co/moka-ai/m3e-baseBGEBGE由北京智源人工智能研究院发布同样是一款功能强大的开源Embedding模型包含了支持中文和英文的多个版本同样支持微调和本地部署。数据入库数据向量化后构建索引并写入数据库的过程可以概述为数据入库过程适用于RAG场景的数据库包括FAISS、Chromadb、ES、milvus等。一般可以根据业务场景、硬件、性能需求等多因素综合考虑选择合适的数据库。应用阶段在应用阶段可以根据用户的提问通过高效的检索方法召回与提问最相关的知识并融入Prompt大模型参考当前提问和相关知识生成相应的答案。关键环节包括数据检索、注入Prompt等。数据检索数据检索常见的数据检索方法包括相似性检索、全文检索等根据检索效果一般可以选择多种检索方式融合提升召回率。相似性检索即计算查询向量与所有存储向量的相似性得分返回得分高的记录。常见的相似性计算方法包括余弦相似性、欧氏距离、曼哈顿距离等。全文检索全文检索是一种比较经典的检索方式在数据存入时通过关键词构建倒排索引在检索时通过关键词进行全文检索找到对应的记录。注入PromptLLM生成Prompt作为大模型的直接输入是影响模型输出准确率的关键因素之一。在RAG场景中Prompt一般包括任务描述、背景知识检索得到、任务指令一般是用户提问等根据任务场景和大模型性能也可以在Prompt中适当加入其他指令优化大模型的输出。一个简单知识问答场景的Prompt如下所示【任务描述】假如你是一个专业的客服机器人请参考【背景知识】回【背景知识】{content} // 数据检索得到的相关文本【问题】石头扫地机器人P10的续航时间是多久Prompt的设计只有方法、没有语法比较依赖于个人经验在实际应用过程中往往需要根据大模型的实际输出进行针对性的Prompt调优。原始 RAG本文 RAG 管道从一个文本文档语料库开始直接跳过如何通过数据加载器从Youtube等数据源获取步骤。标准的 RAG 流程简介将文本分块然后使用一些 Transformer Encoder 模型将这些块嵌入到向量中将所有向量放入索引中最后创建一个 LLM 提示告诉模型根据搜索步骤中找到的上下文回答用户的查询。在运行时通过使用同一编码器模型对用户的查询进行向量化然后搜索该查询向量的索引找到 top-k 个结果从数据库中检索相应的文本块并将它们作为上下文输入到 LLM 提示中。提示与下边内容类似def question_answering(context, query):prompt f“”Give the answer to the user query delimited by triple backticks{query}using the information given in context delimited by triple backticks{context}.If there is no relevant information in the provided context, try to answer yourself,but tell user that you did not have any relevant context to base your answer on.Be concise and output the answer of size less than 80 tokens.“”response get_completion(instruction, prompt, model“gpt-3.5-turbo”)answer response.choices[0].message[“content”]return answer提示工程是提升 RAG 流程性能的一种简便有效的方法。可以查阅 OpenAI 提供的详尽的提示工程指南。虽然 OpenAI 是 LLM 提供商的领头羊但还有其他不少选择例如deepseekqwen Anthropic 的 ClaudeMistral 的小型但功能强大的模型MixtralMicrosoft 的Phi-2以及如Llama2OpenLLaMAFalcon等众多开源模型可以选择最合适的作为 RAG 管道大脑。高级 RAG接下来深入讲解高级 RAG 技术。包括所涉及的核心步骤和算法的方案但是省略了一些逻辑循环和复杂的多步代理行为以保持方案的可读性。上图中绿色部分是接下来详细探讨的核心 RAG 技术。一张图并不能全部展示所有的高级 RAG 技术比如这里省略了上文扩展技术。1分块 (Chunking) 向量化 (Vectorisation)首先需要为文档内容创建向量索引然后在运行时搜索与查询向量余弦距离最近的向量索引这样就可以找到与查询内容最接近语义的文档。1.1 分块 (Chunking)Transformer 模型具有固定的输入序列长度即使输入上下文窗口很大一个句子或几个句子的向量也比几页文本的向量更能代表其语义含义因此对数据进行分块—— 将初始文档拆分为一定大小的块而不会失去其含义。有许多文本拆分器实现能够完成此任务。块的大小是一个需要重点考虑的问题。块的大小取决于所使用的嵌入模型以及模型需要使用 token 的容量。如基于 BERT 的句子转换器最多需要 512 个 tokenOpenAI ada-002 能够处理更长的序列如 8191 个 token但这里的折衷是 LLM 有足够的上下文来推理而不是足够具体的文本嵌入以便有效地执行搜索。有一项关于块大小选择的研究。在 LlamaIndex 中NodeParser 类很好支持解决这个问题其中包含一些高级选项例如定义自己的文本拆分器、元数据、节点/块关系等。1.2 向量化 (Vectorisation)下一步是选择一个搜索优化的模型来嵌入块。有很多选项比如 bge-large 或 E5 嵌入系列。只需查看 MTEB 排行榜以获取最新更新即可。有关分块和向量化步骤的 end2end 实现请查看 LlamaIndex 中完整数据摄取管道的示例。2. 搜索索引2.1 向量存储索引RAG 管道的关键部分是搜索索引它存储了在上一步中获得的向量化内容。最原始的实现是使用平面索引 — 查询向量和所有块向量之间的暴力计算距离。为了实现1w元素规模的高效检索搜索索引应该采用向量索引比如 faiss、nmslib 以及 annoy。这些工具基于近似最近邻居算法如聚类、树结构或HNSW算法。此外还有一些托管解决方案如 OpenSearch、ElasticSearch 以及向量数据库它们自动处理上面提到的数据摄取流程例如Pinecone、Weaviate和Chroma。取决于索引选择、数据和搜索需求还可以存储元数据并使用元数据过滤器来按照日期或来源等条件进行信息检索。LlamaIndex 支持多种向量存储索引同时也兼容其他简单的索引类型如列表索引、树索引和关键词表索引。2.2 分层索引在大型数据库的情况下一个有效的方法是创建两个索引——一个由摘要组成另一个由文档块组成然后分两步进行搜索首先通过摘要过滤掉相关文档然后只在这个相关组内搜索。2.3 假设性问题和 HyDE另一种方法是让 LLM 为每个块生成一个问题并将这些问题嵌入到向量中在运行时对这个问题向量的索引执行查询搜索将块向量替换为索引中的问题向量然后在检索后路由到原始文本块并将它们作为 LLM 获取答案的上下文发送。这种方法提高了搜索质量因为与实际块相比查询和假设问题之间的语义相似性更高。还有一种叫做 HyDE 的反向逻辑方法——你要求 LLM 在给定查询的情况下生成一个假设的响应然后将其向量与查询向量一起使用来提高搜索质量。2.4 内容增强这里的内容是将相关的上下文组合起来供 LLM 推理以检索较小的块以获得更好的搜索质量。有两种选择一种是围绕较小的检索块的句子扩展上下文另一种是递归地将文档拆分为多个较大的父块其中包含较小的子块。2.4.1 语句窗口检索器在此方案中文档中的每个句子都是单独嵌入的这为上下文余弦距离搜索提供了极大的查询准确性。为了在获取最相关的单个句子后更好地推理找到的上下文将上下文窗口扩展为检索到的句子前后的 k 个句子然后将这个扩展的上下文发送到 LLM。绿色部分是在索引中搜索时发现的句子嵌入整个黑色 绿色段落被送到 LLM 以扩大其上下文同时根据提供的查询进行推理。2.4.2 自动合并检索器或父文档检索器)这里的思路与语句窗口检索器非常相似——搜索更精细的信息片段然后在在LLM 进行推理之前扩展上下文窗口。文档被拆分为较小的子块这些子块和较大的父块有引用关系。