译者 | 朱先忠
审校 | 重楼
引言
当下,所有大型语言模型(LLM)都存在一个知识截止日期的问题,即它们无法回答针对其知识库中不存在的特定数据的查询。例如,LLM无法回答有关公司去年会议纪要数据的查询。另一方面,LLM还容易产生幻觉,并提供看似合理的错误答案。
为了克服这个问题,检索增强生成(RAG)解决方案越来越受欢迎。RAG的主要思想是将外部文档整合到大型语言模型中,并指导其行为仅从外部知识库中回答问题。具体地说,这是通过将文档分块为更小的块,计算每个块的嵌入(数值表示),然后将嵌入作为索引存储在专门的向量数据库中来实现的。
RAG工作流程示意图——查询被转换为嵌入,通过检索模型与向量数据库匹配,并与检索到的数据相结合,最终通过大型语言模型产生响应。
上下文检索RAG
将用户的查询与向量数据库中的小块进行匹配的过程通常效果良好;然而,它还存在以下问题:
- 一个问题的答案可能需要多个彼此相距甚远的块。由于上下文丢失,无法找到所有相关的块。例如,考虑一个法律文件的问题:“阿尔法和贝塔公司之间终止合伙关系的条件是什么?”文件中的一个部分可能是“协议可能会在特定条件下终止”。然而,由于缺乏任何上下文信息(没有公司名称),在检索过程中无法选择此块。
- 对于某些问题,传统的最佳匹配搜索比语义搜索更有效,尤其是对于精确匹配而言。例如,在电子商务文档中,通过语义搜索方法对查询“什么是产品ID ZX-450?”的答案可能会带来有关多个产品的信息,而缺少确切的“ZX-450”产品。
- 从向量数据库检索到的信息被转发到LLM,LLM根据查询生成最终答案。在此过程中,LLM必须确定最合适的块来生成最终答案。检索到的块太多可能会导致响应中出现不相关的信息。因此,LLM必须有一个排序机制。
为了应对这些问题,Anthropic公司最近引入了一种向每个块添加上下文的方法;与原始RAG相比,该方法的性能有了显著提高。在将文档拆分为块后,该方法首先将块与整个文档作为上下文一起发送到LLM,为每个块分配一个简短的上下文。随后,上下文附加的块被保存到向量数据库中。它们进一步使用bm25检索器将上下文分块与最佳匹配相结合,该检索器使用bm25方法搜索文档,并使用一个重新排序模型,该模型根据相关性为每个检索到的块分配评分。
具有上下文检索的多模态RAG
尽管性能有了显著提高,但Anthropic公司仅证明了这些方法对文本类型数据的适用性。但当今世界中,许多文档中丰富的信息的来源包括图像(图形、图形)和复杂的表格,等等。如果我们只解析文档中的文本,我们将无法深入了解文档中的其他模式。因此,包含图像和复杂表格的文档需要高效的解析方法,这不仅需要从文档中正确提取它们,还需要理解它们。
使用Anthropic公司的最新模型(claude-3-5-connect-20240620)为文档中的每个块分配上下文在大型文档的情况下可能会涉及高成本,因为它涉及将整个文档与每个块一起发送。尽管Claude模型的提示缓存技术可以通过在API调用之间缓存频繁使用的上下文来显著降低这一成本,但其成本仍远高于OpenAI公司的成本高效模型,如gpt-4o-mini。
本文旨在探讨针对上述Anthropic公司方法的进一步扩展,如下所示:
- 使用LlamaParse将所有内容(从文本到表格再到图像)提取到结构良好的markdown格式的文档中。
- 通过节点解析器将文档解析为节点,而不是使用文本拆分器将文档拆分为块。这不仅涉及拆分文本,还涉及理解文档的结构、语义和元数据等任务。
- OpenAI公司极具成本效益的大型语言模型gpt-4o-mini和嵌入模型text-embedding-3-small用于为每个节点分配上下文、生成最终响应和计算节点的嵌入。
在了解了Anthropic公司关于上下文检索的博客文章之后,我在GitHub链接上找到了OpenAI公司的部分实现。然而,它使用传统的分块和LlamaParse方法,没有最近推出的高级模式。我发现Llamaparse的高级模式在提取文档中的不同结构方面非常有效。
Anthropic公司的上下文检索实现也可以在GitHub上找到,它使用了LlamaIdex抽象;然而,它没有实现多模态解析。在撰写本文时,LlamaIdex提供了一个更新的实现,它使用了多模态解析和上下文检索。该实现使用了Anthropic公司的LLM(claude-3–5-connect-2024062)和Voyage公司的嵌入模型(Voyage-3)。然而,它们并没有像Anthropic公司的博客文章中提到的那样探索BM25(Best Matching 25)排序算法和重排序(Reranking)技术。
本文讨论的上下文检索实现是一种低成本、多模态的RAG解决方案,通过BM25搜索和重新排序提高了检索性能。还将这种基于上下文检索的多模态RAG(CMRAG)的性能与基本RAG和LlamaIdex的上下文检索实现进行了比较。
下面4个链接中重新使用了这其中的一些功能,并进行了必要的修改。
4.https://github.com/lesteroliver911/contextual-doc-retrieval-opneai-reranker?tab=readme-ov-file
此实现的源代码可在GitHub上获得。
本文中用于实现基于上下文检索的多模态RAG(以下简称“CMRAG”)的总体方法示意图如下所示:
解析后的节点在保存到向量数据库之前会被分配上下文。上下文检索涉及结合嵌入(语义搜索)和TF-IDF向量(最佳匹配搜索),然后通过重新排序器模型进行重新排序,最后由LLM生成响应。
接下来,让我们深入研究一下CMRAG的分步实现。
多模态解析
首先,需要安装以下依赖库才能运行本文中讨论的代码。
GitHub笔记本文件中也提到了所有需要导入才能运行整个代码的依赖库。在这篇文章中,我使用了芬兰移民关键数据(根据CC By 4.0许可,允许重复使用),其中包含几个图表、图像和文本数据。
LlamaParse使用商业性质的多模态模型(如gpt-4o)提供多模态解析来处理文档提取。
在这种模式下,会对文档的每一页进行截图,然后将截图发送到多模态模型,并附上提取标记的指令。每页的标记结果被合并到最终输出中。
最近的LlamaParse高级模式提供了先进的多模态文档解析支持,能够将文本、表格和图像提取到结构良好的标记中,同时显著减少了缺失的内容和幻觉。它可以通过在Llama云平台创建一个免费账号并获得API密钥来使用。免费计划提供每天解析1000个页面。
LlamaParse高级模式的使用方式如下:
在上述代码中,我们首先从指定目录读取文档,使用解析器的get_json_result()方法解析文档,并使用解析器的get_images()方法获取图像字典。随后,提取节点并将其发送到LLM,以使用retrieve_nodes()方法根据整个文档分配上下文。解析这份文档(60页),包括获取图像词典等内容,共计耗时5分34秒(一次性过程)。
报告第四页(来源:芬兰移民关键数据)
报告中的第四页由JSON结果的第一个节点表示(按作者排列的图像)
上下文检索
通过retrieve_nodes()函数从解析的josn_results中提取单个节点和相关图像(屏幕截图)。每个节点与所有节点(以下代码中的doc变量)一起被发送到_assign_context()函数。_assign_context()函数使用提示模板context_prompt_TMPL(来自链接,并经过修改后采用)为每个节点添加简洁的上下文。通过这种方式,我们将元数据、标记文本、上下文和原始文本集成到节点中。
以下代码显示了retrieve_nodes()函数的实现。两个辅助函数_get_sorted_image_files()和get_img_page_number()分别按页面和图像的页码获取排序后的图像文件。总体目标不是像简单的RAG那样仅依赖原始文本来生成最终答案,而是考虑元数据、标记文本、上下文和原始文本,以及检索到的节点的整个图像(屏幕截图)(节点元数据中的图像链接)来生成最终响应。
下面给出的是与报告第一页对应的节点的描述。
添加了上下文和元数据的节点(图片由作者提供)
用BM25增强上下文检索并重新排序
所有具有元数据、原始文本、标记文本和上下文信息的节点都被索引到向量数据库中。节点的BM25索引被创建并保存在pickle文件中,用于查询推理。处理后的节点也会被保存,以供以后使用(text_node_with_context.pkl)。
现在,我们可以初始化一个查询引擎,使用以下管道进行查询。但在此之前,设置以下提示以指导LLM生成最终响应的行为。初始化多模态LLM(gpt-4o-mini)以生成最终响应。此提示可根据需要进行调整。
在查询引擎中集成整个管道流程
本节中要介绍的QueryEngine类实现了上述完整的工作流程。BM25搜索中的节点数量(top_n_BM25)和重新排序器重新排序的结果数量(top_name)可以根据需要进行调整。通过切换GitHub代码中的best_match_25和re_ranking变量,可以选择或取消选择BM25搜索和重排序。
下面给出的是QueryEngine类实现的整体工作流程:
1. 查找查询嵌入。
2. 使用基于向量的检索从向量数据库中检索节点。
3. 使用BM25搜索检索节点(如果选择使用该方法的话)。
4. 结合BM25和基于向量的检索中的节点。查找节点的唯一数量(删除重复的节点)。
5. 应用重排序对组合结果进行重排序(如果选中该方法的话)。在这里,我们使用Cohere公司的rerank-english-v2.0重新排序模型。您可以在Cohere公司的网站上创建一个账号,以获得试用版API密钥。
6. 从与节点关联的图像创建图像节点。
7. 根据解析的markdown文本创建上下文字符串。
8. 将节点图像发送到多模态LLM进行解释。
9. 通过将文本节点、图像节点描述和元数据发送到LLM来生成最终响应。
使用OpenAI公司提供的模型,特别是gpt-4o-mini的一个优点是上下文分配和查询推理运行的成本要低得多,上下文分配时间也要短得多。虽然OpenAI公司和Anthropic公司的基本层确实很快达到API调用的最大速率限制,但Anthropc公司的基本层中的重试时间各不相同,可能太长。使用claude-3–5-connect-20240620对本文档的前20页进行上下文分配过程,使用提示缓存大约需要170秒,成本为20美分(输入+输出词元)。然而,与Claude 3.5 Sonnet相比,gpt-4o-mini的输入词元大约便宜20倍,输出词元大约便宜25倍。OpenAI公司声称为重复内容实现了提示缓存,这对所有API调用都自动起作用。
相比之下,通过gpt-4o-mini向整个文档(60页)中的节点分配上下文大约在193秒内完成,没有任何重试请求。
实现QueryEngine类后,我们可以按如下方式运行查询推理:
这是对此查询的markdown响应。
对查询的响应(图片由作者提供)
查询响应中引用的页面如下:
上述查询中引用的一页(第9页)。提取的信息显示在红色矩形中(来源:移民关键数据)
现在,让我们比较一下基于gpt-4o-mini模型的RAG(LlamaParse高级模式+上下文检索+BM25+重排序)和基于Claude模型的RAG。我还实现了一个简单的基础级别的RAG,可以在GitHub的笔记本中找到。以下是要比较的三个RAG。
1. LlamaIndex中的简单RAG使用SentenceSplitter将文档分割成块(chunk_size=800,chunk_overlap=400),创建向量索引和向量检索。
2. CMRAG(claude-3–5-connect-20240620,voya-3)——LlamaParse高级模式+上下文检索。
3. CMRAG(gpt-4o-mini,text-embedding-3-small)——LlamaParse高级模式+上下文检索+BM25+重排序。
为了简单起见,我们将这些RAG分别称为RAG0、RAG1和RAG2。以下是报告中的三页,我向每个RAG提出了三个问题(每页一个问题)。红色矩形突出显示的区域显示了基本事实或正确答案的来源。
文件第4页(来源:移民关键数据)
文件第12页(来源:移民关键数据)
文件第20页(来源:移民关键数据)
以下是对每个问题的三个RAG的回答。
基本RAG、基于Claude模型的CMRAG和基于gpt-4o-mini模型的CMRAG的比较(图片由作者提供)
可以看出,RAG2的表现非常好。对于第一个问题,RAG0提供了错误的答案,因为该问题是从图像中提出的。RAG1和RAG2都提供了这个问题的正确答案。对于另外两个问题,RAG0无法提供任何答案。然而,RAG1和RAG2都为这些问题提供了正确的答案。
总结
总体而言,由于集成了BM25方法、重排序和更好的提示,RAG2的性能在许多情况下与RAG1相当,甚至更好。它为上下文、多模态RAG提供了一种经济高效的解决方案。该管道方案中可能的集成技术包括假设的文档嵌入(简称“HyDE”)或查询扩展等。同样,也可以探索开源嵌入模型(如all-MiniLM-L6-v2模型)和/或轻量级的LLM(如gemma2或phi3-small),使其更具成本效益。
有关本文示例中完整的源代码参考,请查看我的github代码仓库:https://github.com/umairalipathan1980/Multimodal-contextual-RAG.git?source=post_page-----d1965b8ab00c--------------------------------
译者介绍
朱先忠,51CTO社区编辑,51CTO专家博客、讲师,潍坊一所高校计算机教师,自由编程界老兵一枚。
原文标题:Integrating Multimodal Data into a Large Language Model,作者:Umair Ali Khan