基于大模型的RAG应用,一个普遍的认识是:做原型很简单,投入生产很难
为什么我的RAG应用很难按预期工作?在之前的文章中我们曾经陆续的对RAG应用优化做过零星与局部的探讨,如融合检索、查询转换、多模态处理、Agentic RAG等。接下来我们将结合之前的方法与实践,总结形成更完整的企业级RAG应用的优化建议以供参考。
一、让数据更容易被拆分成可理解的知识块
在经典的RAG应用中,最后的LLM能看到的参考上下文只能是原始数据的一部分,即检索出来、与输入问题相关的知识块(除非你把所有知识都交给LLM,但在企业应用中并不现实)。因此当这些知识块中存在以下问题时,就会给LLM的理解与生成带来障碍:
- 过多的上下文指代词、或对其他知识块的引用
- 知识内容的语义上存在前后矛盾与不一致
当然,对于连贯的知识内容来说,很难从绝对意义上做到拆分后的内容能够完全独立,因此这只能是一个“尽量”、“相对完善”的策略。比如你的知识库中的某个软件操作教程有下面这段内容:
由于内容中携带的图片指代与知识的外部链接,LLM显然无法理解。那么你可能需要对其进行完善:**补充细节的错误提示描述,并嵌入引用的知识内容或摘要。**这样处理后,尽管你可能仍需要在生成环节考虑外部链接的输出,但至少很好的提升了这一段内容的独立性,有助于后续的检索与理解生成。
另一个需要注意的是知识内容语义的一致性与连贯性,减少知识内部的前后矛盾。这个问题常常出现在一些会频繁变更的行业知识内容(比如某个频繁变更的法规),容易导致前后不一致的知识,并带来输出的随机性。你需要考虑其在向量知识库中的变更维护机制,一个常见的方法是:
借助元数据来“标记”这些知识块(比如原始文档编号、版本号、时间戳、Hash值等),从而在后续原始知识反生变化时,及时的更新到向量存储,或实现多版本的管理与检索时的排序。
二、处理简称、缩写、专有名词
知识文档中的一些简称、缩写、特定的行业或公司内部的术语,可能会让LLM在缺乏足够的上下文的情况下难以理解其完整含义。
比如:如果缺乏足够的知识背景与说明,大模型可能不会把“BD”理解成“Business Developent”;也可能会对“PR”是代表产品经理(Product Manager)还是项目经理(Project Manager)产生困扰;还有很多容易造成理解困难的高度领域特定的专有名词,比如医疗健康、科研、金融、IT等领域的大量简称或缩写词;或者一些需要特定文化背景理解的表述,比如一些俚语、双关语、新兴的网络用语等。
因此,在构建RAG应用的索引阶段,可以尝试在对原始数据处理时,识别出这些容易产生理解困难的名词,并对其进行处理,可以考虑两种方法:
- 在索引阶段对原知识进行补充或修复,如使用翻译词表做替换或说明。
- 另一种方法则是在检索阶段将相关的词表作为独立上下文输入到LLM协助理解。如果这个上下文过大,也可以通过检索来过滤掉无关内容。
这个问题在实现Text-to-SQL的数据库查询方案中会比较常见:由于LLM很可能无法理解数据库表中的每个字段的含义(特别字段名比较奇怪时),容易生成错误的SQL,因此你需要给出字段的含义说明,并最好能过滤掉无关字段。
三、对多模态文档的特别处理
多模态文档的处理是一个棘手的问题。一方面来自文档自身的复杂性,比如混合各种形态与结构的PDF;另一方面来自多模态模型的技术成熟度、输出性能与质量。虽然从索引的复杂性、检索性能、生成质量来说,目前建议知识库以文本为主,但实际企业应用中的多模态文档是无法逃避的问题,在当前的技术条件下,一般的处理方式仍然是:
尽量把多模态文档中隐藏在图片、图表、表格中的知识“文本化”并进行向量存储后用于检索。 在之前曾经较详细的介绍过相关的处理方法与工具:
这里做简单的解释:
- 借助解析工具从PDF中分类提取Text(文本)、Table(表格)、Image(图片)等不同形态内容,并对文本直接做向量化索引。
- 借助LLM生成表格内容描述与摘要,用于嵌入与检索。在检索阶段,可再递归检索出原始的表格内容用于生成。
- 借助多模态视觉大模型比如qwen-vl,GPT-4V等结合OCR技术对图片进行理解。纯文字图片可利用OCR直接转换成普通的文本处理;而其他图片则借助模型理解并生成摘要信息用于索引与检索(检索时也可借助递归检索出原始图片用于后续生成)。
多模态文档解析可以借助Unstructured、OmniParse、LlamaParse、国内的RAGFlow平台等。
四、优化文本块(chunk)的分割策略
在对原始知识进行足够的清洗与完善后,就需要考虑如何将知识分割成多个块(chunk)。因为Chunk分割的策略(大小、算法等)会从多个方面影响到最终的应用效果:
- 检索的精准度
- 上下文的完整性与丰富性
- 响应时间
- tokens使用成本
因此需要在分割原始知识时充分考虑不同的策略与算法,期望的最终效果应该是:既能提供足够的文本嵌入以便有效精准的执行检索,也能够有足够的上下文提供给LLM进行推理。在索引阶段的一些优化建议有:
如果你简单的按固定大小进行内容分割,注意评估测试不同尺寸(chunk_size)下的性能与质量。
尝试更多的分块策略与算法来对不同的内容进行处理:
- 基于文件结构感知的分割。比如按照句子或段落;对Excel/CSV文件的按行分割等,以提高chunk的独立性和完整性
- 基于文件内容感知的风格。比如识别Markdown文件中的标题、HTML中的标签、代码段落中的注释等感知内容
- 基于语义理解的分割。比如LlamaIndex中的SemanticSpitterNodeParser解析器,可以借助嵌入模型来识别chunk文本间的相关性并做合并,尽量确保一个chunk内容在语义上紧密相关
借助一些高级分割策略,并结合检索机制,在不牺牲语义丰富性的前提下,尽可能提高索引的精准性。如:
- 使用带“滑动窗口”的、但chunk_size较小的分割器(如LlamaIndex的SentenceWindowNodeParser),在拆分时自动把一个chunk“周围窗口内的chunk”带入元数据,这些“周围的chunk”在检索时会自动带入以丰富上下文
- 使用多个chunk_size大小的分割器将文本分割成不同大小但又相互关联的chunk,较小的chunk用来实现更精准的检索能力;但在检索后又能关联获得较大的chunk,从而获得更丰富的上下文。(比如Langchain的多向量检索器、LlamaIndex的HierarchicalNodeParser)
需要注意这些复杂的分割策略一般需要对应的检索方法的配合。
五、对知识的语义进行丰富
索引是决定RAG应用最终输出质量的基础设施。如果说之前描述的优化chunk分割的策略,比如尽量让chunk的语义完整独立、用较小的chunk做索引等都是为了提升索引的质量(语义检索的精准性);那么另外一份维度就是提升索引的数量,通常的方法是:对分割后的chunk创建或生成更多用于索引的数据内容并进行嵌入,从而使得索引能够包含更丰富的语义信息,更有利于在检索时召回对应的chunk。
这些新的数据内容可以是:
- 自定义数据:针对拆分的chunk自行定义用于索引的数据内容
- 假设性问题:借助LLM生成针对chunk内容的假设性提问
- 相似性问题:借助LLM生成已有QA问答的相似性问题(如更多口语化问法)
- 摘要信息:对于较长内容的chunk生成精简的摘要信息
当然,针对这些生成的用于索引的数据内容,在具体向量化时也可以有不同的策略:
- 把生成的数据内容(如假设性问题)与原chunk内容一起做向量化
- 仅对生成的数据内容进行向量化,并在检索时关联检索到原chunk
具体需要根据实际应用场景进行选择。
六、设置并利用元数据
目前的向量数据库都可以支持给向量数据添加与设置元数据,这些元数据通常来自于原始知识内容分割成chunk时的自动生成或者人工设置,比如原始知识文档的名称、时间戳、编号等。这些附加的属性可以在未来发挥巨大的作用,除了在前面说到的可以利用元数据进行知识的更新维护、检索后的排序等;更重要的是可以在企业级的大知识库环境下,利用元数据进行向量空间的预过滤(向量搜索之前),往往可以很好的提升生成的性能与质量。
假设我们构建一个法律法规的智能案例知识库,其中有关联到不同法律种类、针对不同地区的各种条款与案例。当接收到自然语言的问题时,如果你不想搜索整个向量数据库,你可以针对法律种类、地区甚至时间等设置元数据字段,并在执行相似性语义搜索之前针对这些元数据字段进行预先过滤,然后你就可以在某个特定的向量空间(比如“刑法”)中搜索。
在实际应用中,元数据也并非一定是完全预确定的参数(比如地区)。你可以结合实际用户提问的场景与检索的需求,借助LLM来推理与生成某种类型的元数据并用于过滤,比如识别商品评论的正面与负面的属性。
七、尝试不同的索引类型
向量索引(Vector Index)是RAG应用最常用的、也是讨论最多的一种索引类型。但很多时候你还可以结合其他的索引类型来提供更多样与强大的检索能力。这里介绍两种:
【关键词索引】
从每个chunk中的内容提取关键词,然后建立起关键词到各个chunk之间的映射关系并作为索引结构。在检索时,则根据输入问题的关键词来提取最相关的多个chunk返回。当然,由于关键词索引缺乏语义的理解,因此往往只能作为一种辅助索引。
关键词索引需要考虑的一个策略是如何提取关键词,常见的方法有:
- 借助LLM提取知识内容与输入问题的关键词。
- 借助RAKE(一种轻量级的自然语言内容关键词提取工具库)工具来提取内容关键词。
【知识图谱索引】
知识图谱索引是另外一种完全不同于向量索引的形式,它通过基于图(Graph)结构的语义网络来组织与表示知识,更适合需要精确表示实体之间复杂关系并推理的场景。在底层存储与检索能力上也不再依赖于向量数据库,而是图数据库(Graph Database)。
知识图谱本质上一种结构化的知识表示。无论原始知识是结构化内容(比如存储在关系型数据库中的表),还是非结构化文档,都可以借助技术手段(比如通过LLM推理抽取)转化成知识图谱的表示形式。
除此之外,在一些开发框架中还有更多的索引类型,比如LlamaIndex中的文档摘要索引(DocumentSummaryIndex)、树索引(TreeIndex)、对象索引(ObjectIndex)等,可以按需选择与使用。
八、测试并选择合适的嵌入模型
嵌入模型(Embedding Model)是贯穿RAG应用的重要组件,用来将文本转化为固定大小的数值向量以用来在后续进行相似性匹配与搜索,在实际应用时通常需要索引与检索阶段使用一致的嵌入模型。因此选择合适的嵌入模型进行向量化,是关系到后期检索精准度的重要环节。
不同的嵌入模型有着不同的模型架构、训练数据、是否上下文相关、表示维度、应用场景、计算资源要求、支持的语言 等,需要根据具体的应用需求和计算资源做出选择。比如你需要决定嵌入的维度,更高的维度可以让你捕获更多语义,但同时它需要更多的存储空间和更多的计算时间。
如果您不确定要使用哪种模型,推荐查看huggingface上的MTEB(海量文本嵌入基准)测试结果进行选择与测试
另外一个可以考虑的选项是微调嵌入模型。由于嵌入模型通常基于一般知识进行训练,这限制了它们在特定企业或行业领域的有效性。你可以使用特定领域的数据(例如法律与金融)来微调自己的嵌入模型,以提高RAG应用的检索性能。