非常感谢RAG(检索增强生成)技术详解:基于垂直领域专有数据的 Chatbots 是如何实现的,这篇文章对 RAG 技术进行了详细的描述。我根据自己的理解,并且按照代码思路重新进行整理。
RAG 技术看似神奇,其本质是结合了检索和生成两个子任务的一种系统工程,其中每个子任务有明确的技术原理支撑。检索模型充当“图书馆员”,扫描大型数据库以获取相关信息,生成模型充当“作家”,将这些信息合成为与任务更相关的文本。它用途广泛,适用于实时新闻摘要、自动化客户服务和复杂研究任务等多种领域。
具体来说,首先,对知识库进行索引,使用加载器从知识库中获取文档并分割成文档片段,经过嵌入后得到向量数据库;然后是检索,通过相似性算法匹配与用户输入相关的文档片段;最后是生成,通过检索到的文档片段和 system prompt 进行检索增强。
RAG代码实现
from langchain_community.document_loaders import DirectoryLoader
from langchain.indexes import VectorstoreIndexCreator
loader = DirectoryLoader('../', glob="**/*.txt")
index = VectorstoreIndexCreator().from_loaders([loader])
index.query("你觉得黑神话·悟空怎么样?")
零、嵌入
当我们和 chatbot 进行对话时,用的是自然语言,那系统又该如何理解这些自然语言呢?在 LLM 的世界中,任何一段人类语言都可以用数字向量来表示(Embedding Machine),而这个向量就是嵌入。
通过 Embedding Machine,自然语言将会变成向量。如果在一个向量空间中表示所有的语言,当两个点越相近,那么他们就越相似。
嵌入和语义近似是搜索的核心原理,它为检索步骤提供了动力。
一、索引
为知识库创建索引的过程就是为知识库建立一个对应关系,方便用户读取到。分为两个高层次的步骤:加载和分割,最终目的是:得到向量数据库。
1. 加载
加载(Loader)就是对知识库进行处理并获取其内容。这是一个很复杂的步骤,它需要将知识库中不同格式的数据,统一成一个输出格式,并输出一个列表数据。比如:如果知识库是一个文档站点,则加载器需要抓取每个页面的内容,然后将HTML格式化为可用的文本;如果是PDF或Google Drive,则需要不同加载器。
2. 分割
分割(Splitter)就是将将知识库中提取出的知识分割成适合嵌入搜索的片段大小。
在第一步中,我们知道了如何将自然语言转换为 LLM 可以理解的向量。而加载的目的就是为了将格式统一,便于转化为向量数据。那为什么需要分割器呢?原因在于:加载的列表数据中,每个文档中包含的知识越多,越考验语义近似算法的性能,相似性计算难以保证。为了让用户问题的主题与文档中的文本相吻合,便有了分割器,即将单个文档分割成适合检索的文档片段。
注意:当我们希望LLM在回答问题时引用其来源的场景,格式保持一致十分非常重要。
3. 向量数据库
最后,将文档片段经过 Embedding Machine后,保存到向量数据库中。
二、检索
检索这一步,是在嵌入和索引的基础上,在向量空间中,找到与查询嵌入相关的文档片段嵌入。通过相似度算法计算相关性,通常使用余弦距离进行计算。
三、生成
通过上面几步,我们成功检索出了与输入查询相关的文档片段。加下来,将输入查询和检索到的文档片段一起送入 LLM 中,得到的就是检索增强后生成的回复。
同时,我们需要注意,在使用 LLM 时,我们需要提供 system prompt 作为自定义指令,它会为 LLM 提供整体指导。对于RAG来说,可以描述为:“嘿,AI,我们将给你一些东西阅读。阅读后回答我们的问题,好吗?谢谢。
一旦有了 system prompt 检索到的文档片段,我们就只需将它们与用户输入查询一起发送给LLM即可。
参考文献:
RAG(检索增强生成)技术详解:基于垂直领域专有数据的Chatbots是如何实现的
LangChain学习笔记:文档加载器
索增强生成 (RAG)的原理——传统检索+LLM生成相结合
RAG 2.0架构详解:构建端到端检索增强生成系统