“无”中生有:基于知识增强的RAG优化实践

图片

阿里妹导读


本文作者基于自身在RAG技术领域长达半年的实践经验,分享了从初识RAG的潜力到面对实际应用挑战的心路历程,以及如何通过一系列优化措施逐步解决这些挑战的过程。

自2023年大模型技术进入大家视线后,业内出现了较多基于大模型技术和RAG对智能问答机器人的探索,利用大模型对产品的相关文档学习,建立个人/平台助手以减少客服的人工作业量。在我做RAG这大半年的时间里,从开始很坚定认为RAG一定是非常有前景的技术方向之一,到中间一度迷茫RAG在知识问答上是否真正能比传统文档检索技术带来更多的实用价值,再到逐渐尝试并摸索到现在的一整套较适合我们应用算法团队训练和部署资源均不富裕情况里的优化方案并在逐步平稳落地中收获了试点平台owner的认可。无论是出于深化思考还是技术成长的角度,需要在这个节点去记录下在这个过程中产生的对RAG技术的疑问及随着一步步探索和思考所形成的自己的解决方案。由于RAG的相关工程全链路及生成微调等相关分享在网络上也较多,单从知识维度综合去讲探索的文章较少。因此本文的行文逻辑没有按RAG整体技术架构去聊,而是单从知识增强的多角度去看RAG问答的难题与优化思路。领域本身较新,在nlp也是半路出家,思考大于经验,有不合理的地方欢迎大家讨论和指正。

RAG背景

检索增强生成(Retrieval-augmented Generation),简称RAG,是一种利用预先检索知识库来增强大模型生成能力的方案;简单来讲,对于大模型在业务上的落地来说,我们用的大模型一般都是较好的开源通用大模型,但通用大模型只有通用的归纳总结能力,并没有对领域问题回答的能力。因此,实际落地时如何让大模型能领域化回答问题,有两种方案,一种是用领域化数据再去训练和微调,另一种则是外接领域化知识库,对用户提问先检索知识库再由大模型生成回答。

图片

我们是做内部的平台答疑助手,在我们的场景下,平台的变动是频繁的,对知识库的更新编辑是非常多的,因此去训练和实时更新大模型的成本很高,所以很自然地开始了基于RAG的这一套技术方案。

这个技术思路确实很直白也很简单,三两句即使没有接触过大模型算法的同学也能对RAG这个技术的概念有一个认知(检索文档+给大模型生成答案),且落地成本很低,无论是通过开源框架还是利用内部的RAG、AGENT平台都能快速上手搭建一套属于自己的知识库问答能力,但这些能力在实际落地效果差异性很大。五月去北京参加AI Con大会,会上听了很多RAG在领域里的实践,一位同行的师兄的感想里写了这么一句话,也是我做RAG过程中逐渐对它的认知:“简单做做很简单,复杂做做很复杂”。在实践中也发现,很多比较先进的技术研究的也是大模型生成的部分,落地上很多做法对于知识构建这一块更是“简单做做”居多。所以这篇文章也是想先从“简单做做”分析目前的RAG中的知识难题,再从为解决这类知识难题我们所做的一些探索的思路。

“简单做做很简单”的RAG知识难题分析

RAG的“简单做做”在于无论是用开源的框架还是一些中心化RAG平台所提供的能力,我们其实可以很容易就搭建属于自己的一套RAG问答能力,这套RAG能力往往是完整的,涵盖【query改写】、【向量嵌入】、【多路检索】、【大模型总结】多个模块;这种能力从0-60分的搭建是快速高效的,大模型能从大量知识库中找到一些相关知识,并做出一些像样的回答(找不到相关知识时候也能作出看似合理的回答)。但随着将该类模型不止用于简单闲聊的场景,而是在实际需要解放人力的场景下落地的时候,这样的能力给用户的实感是空有智能外壳,实际能力并不如传统检索。

图片

上图也是一篇比较全的RAG综述[1]里对RAG发展的框架概述,我们今天想聊的知识增强在这张RAG技术框架图里也只是不显眼的【Documents-Indexing】,简单做做的【文档分割-存储】也是实现上最简单的一个步骤。但简单做做后的效果往往不如人意,以下两个问题也是我们在实践过程中的思考:

从知识角度分析,为什么简单做的RAG看上去会有人工智障感?

人工智障感主要是源于大模型并没有理解灌入模型的知识,大模型去总结很多文档时缺乏一些领域内常识,又在杂乱的文档中进行生成,会有较多幻觉。(一个没有常识的人,看了点看似相关可能无关的只言片语,也很难不胡说八道)因此,核心问题在于【大模型悟性不高且知识质量低】,解决方案也是两类并行,提高大模型的悟性【Prompt工程里尽可能详细引导/参考微软RAFT方法(2)微调/许愿开源大模型越来越牛..】,另一类即是从源头上提高外接给大模型的知识质量,即是我们这篇文章聊的重点。接下来进入正题,灌入大模型的知识质量上存在哪些需要解决的难题?

从知识角度思考,灌入大模型的知识质量上存在哪些需要解决的难题?

这个问题最直接的回答是本身业务侧提供的知识库不够/质量不行,实际上这个原因也确实有影响;但在实践后也可以发现,一些算法上的设计同样影响大模型的知识质量。

1. 知识分块矛盾

一般来说,RAG的第一步就是先对文档进行分块,简单做做的第一步就是先按默认的大小对文档进行分块,overlap设个小点的值以防句子被截断。但这里在实践中容易出问题的点在于RAG中检索和生成所要求的粒度不同:

  • 要想检索器检索的精准,需要小分块;
  • 要想大模型生成时候参考得多,需要大分块;
  • 同时,即使有overlap,但文档语义很可能恰恰在分块处隔断。如下图所示,本身包含的一致性语义信息更多的章节很可能会被分在不同chunk中。

因此,只通过默认大小一致的分块策略及检索生成用同样的分块策略都对RAG的性能损失极大。

图片

2. 知识本身缺失

假设分块问题能解决,另一个知识质量上更难的点在于,知识不存在于任何一个分块里:

  • 其一,答案可能存在在文档中,但不存在确切的某一分块中。从query到document本身的语义鸿沟来说,用户的问题可大可小,和沉淀的文档的语义维度不可能始终一致;

  • 其二,一些先验的领域型知识并不在确切的分块里,但大模型没有该类领域性常识时在回答具体问题时较为困难;

  • 其三,一些过往问答里出现过的知识仅能依靠业务侧人工整理才能进入知识库。

3. 知识相互冲突

假设知识全都call出来,但call出来的知识本身不清晰或者是不同的文档中对相似问题的诠释不同,模棱两可的知识容易让大模型想不通以至于幻觉。

总结归纳到以上三点的时候,也慢慢清晰了:为什么明明知识管理在RAG中重要性极高,但调研时较少算法研究集中这块。因为理想化情况下,知识本身是ok的我们才会让大模型去外接知识;同时,对于人员投入较多的场景来说,由有经验的同学去保证知识的正确是最优解。但在我们的场景下,往往是业务难以投入太多人力配合知识补充,内部知识库更新也并不会有严谨性保证,因此,结构化较好的语雀文档对我们来说已经是最佳知识库源。如何利用智能化算法来让这些文档发挥它最大的作用也是我们在优化中最主要的方向。

“复杂做做很复杂”的RAG知识优化实践

对应于前一章节所带出的三类知识质量上的问题,【知识分块矛盾】【知识本身缺失】和【知识相互冲突】,这一章节将会以思路为主介绍下对这三类难题的优化实践。


一、从【知识分块矛盾】出发的知识检索优化

1. 知识分块优化

一开始做知识分块优化完全源于我们所用的数据基本上都是内部平台的语雀文档数据,内部平台的文档一般都是很规范,语雀文档的markdown属性也是天然包含了很多信息,能把这类结构化的markdown文档里的标题信息用上一定是能比纯文本长度分割提升明显很多。但即使是开源里知名度最高的索引框架LlamaIndex,对于Markdown文本的分割都是十分诡异的思路(相对来说比较新的中心化的框架确实难以集成符合特定场景需求的算法)。

图片

到这里,我们就放弃了直接用开源类的想法:

  • 直接按结构分割可能会造成大的chunk过大、小的chunk过小;
  • 直接按长度分割则会造成结构信息丢失。

因此,在该部分,我们结合了结构分割和长度分割两种,实现了“先结构化分割-对大chunk再长度分割-对小chunk进行结构化合并”的三步分割逻辑,对每个文档进行结构-长度均衡的块分割。

具体步骤如下:

  • step1: 递归分割文档,得到最小标题单元的Chunk(保留了各级标题信息)。

  • step2: 对最小标题单元的chunk进行判断,若chunk长度较长,按长度进行二次分割,获取包含标题信息的最小单元Segments

  • step3: 对Segments进行判断,从最低级标题到高级标题开始依次判断并在长度范围内合并获取包含了segment周围尽可能多语义信息的Blocks【这一步在实际实现时使用的是归并算法的思想,按标题从低级到高级依次归并】。

我们实现的算法所构建的知识Blocks既包含各级标题信息,对大chunk有分割;对小chunk合并又是基于结构的归并,尽可能多地包含了附近的上下文信息。因此给大模型的知识块尽可能实现了语义和长度的平衡。

2. 检索-生成的块粒度解耦

由于检索中文档块越小、检索越精准,而大模型生成阶段则是文档块越大、生成越全面,原有的检索文档块-大模型生成的思路会造成检索和生成两阶段的效果削弱。

因此在对文档进行三步分割过程中,我们分割的不同粒度的知识在最终检索过程中均是有用的。

  • Sentence:对chunk进行句子分割得到,会再拼接标题等结构化信息;细粒度的知识适用于检索阶段。
  • Segment:见上一节,其包含了结构化段落信息;完整的语义知识适用于重排阶段。
  • Block:见上一节,其包含了Segment周围尽可能多的语义信息;更多的上下文信息适用于大模型生成阶段。

我们对检索生成两阶段的文档的块粒度进行了解耦,实现了“先句子粒度检索-分割段粒度重排-结构化块生成”的从小到大索引。

图片


二、从【知识本身缺失】出发的知识增强扩充

知识本身缺失,很自然的思路就是对知识库做增强扩充;扩充知识库,在靠人力补齐外,我们探索了几类借助模型去从多个维度扩充的方法:扩充知识粒度解决Q-D不匹配的问题,扩充领域知识来补充大模型的先验知识缺陷,扩充知识来源丰富本身知识库。

1. 知识粒度扩充-通用性知识增强

针对由于语义粒度上Query-Document匹配困难的问题,现有的解决思路做的尝试都是把让Q-D进行对齐,要不把query也扩充到document粒度,要不把document精炼到query粒度。

将query扩充到document粒度

这类方案参考HyDE[2],当query进来后,预先去生成可能的答案,再由可能的答案去检索相关document。这类方案的动机也很明确,在很多RAG的落地实践中看起来也可行,但由于对query的答案预先生成需要本身的大模型在线服务资源翻倍、等待时间上也大加长,在我们的场景中并没有去尝试。

将document精炼到query粒度

该类方案的思路基本上是借助大模型做文档预先提取,文档摘要,做跨文档的预先关联。方案很多很明确,结合场景里的具体问题分析我们所需要的粒度扩充,再利用大模型辅助即可较快实现离线的一系列抽取。我们通过知识增强,下表中的检索块指检索时用query所匹配的块内容,生成块指匹配到检索块后给大模型进行生成的块内容。

问题类型

扩充粒度

检索块

生成块

细粒度query

从文档中预先抽取用户可能问的3-5个核心问题

用户可能问的核心问题

对应文本块

粗粒度query

对段落/文档粒度进行摘要后存入索引

段落/文档粒度的摘要

对应段落/文档(注意长度分割)

以上无论是细粒度query还是粗粒度query都还是停留在单文档的多粒度知识扩充上,跨文档场景下的query仍然无法得到解决。解决跨文档的query问题本质上还是解决文档间的关联问题,提到关联很自然的也就来到了Graph这项技术。因此,我们尝试了微软的GraphRAG[3]思路来解决跨文档的关联问题。线下跑的代码确实是有效的,不过GraphRAG的检索目前用在线上的成本很高【更新索引所需资源多,在线Graph存储链路有待我们探索】,所以也不符合我们的低成本优化方案。但探索到了有用的技术哪能轻言放弃!虽然GraphRAG全链路尚未在我们的场景的在线链路里落地,但思路用在了下一节先验知识扩充里。

2. 先验知识扩充-领域型知识抽取

这一节想要解决的知识缺失问题也是领域化智能问答里一个老生常谈的话题,许多模型预先并不知道一些领域内的先验知识,单凭检索出来的相关内容进行回答总会有效果不好的问题。因此,也有许多领域化的探索想方设法将领域化知识传入大模型中。这类探索也大致可以归纳成以下两类。

将领域型知识内化进大模型

将领域型知识内化进大模型也就是让大模型在回答前就已经学习了相关的领域类知识,最常用的就是领域化数据微调方案。一开始都会去尝试微调,但我们前期的微调效果不佳,后续没有再走这条路的原因有几个:

  • 数据收集对我们来说难度高,需要有业务的同学帮忙去整理一些领域高质量数据,同时领域化数据也需要配比一些通用型数据进行微调,数据质量也很影响最终的;

  • 不同研发平台的领域化概念可能不同,为每个平台都定制领域化微调模型并部署,这个资源上是完全不可行的;

  • RAG做到后面,其实生成部分用的开源大模型的更迭是很快的,我们不太可能及时去训新开源的模型,换更新的大模型的收益又比用微调后的原有模型增益高。

这个问题也是我们后续一个可能的方向,如果后续能有一些知识编辑的策略,可以低成本地将我们内部的所有的研发文档输入到模型的某个记忆单元,在模型层面也就相当于做了增量的预训练。但短期内,在落地上更有效的方案还是将领域型知识显示地灌入大模型。

将领域型知识显式给大模型

实际落地上,我们暂时没法让模型长期记忆里存储领域型知识,于是选择的方案是对query进行知识检索的同时也去检索一遍相关领域专有知识,类似于MemoRAG[4]的机制去建立我们的领域Memo Model。这套方案的核心也是在于领域的Memo Model如何构建。

图片

如果有专心看到这里的朋友可能会想起来,上一节有个实用但还没用起来的技术是GraphRAG[5]。在这里,我们构建了基于领域图谱的MemoRAG。由于我们在构建领域图谱的时候,大模型遍历了整个知识库,它能获取的领域关联在一次次遍历和总结时候的可用率是非常高的。

构建领域图谱的具体步骤如下:

  • step1: 模型会先多次读取文档分块,从中提取出领域实体(可结合不同场景规定实体类型)。
  • step2: 在遍历完所有文档分块后,模型会再多次遍历提取出的领域实体,将其中的相似相关实体进行合并再总结。
  • step3: 对合并后的实体,模型再根据文本块去提取实体之间的关联。
  • step4: 对抽取的实体和关联做社区发现,并对所聚集的社区做报告摘要

因此,在一整个链路后,模型能够获取跨文档完整的领域实体相关知识。对每个实体,模型也会做社区发现,生成社区报告。这类实体和社区报告,在这里完全可以作为Memo Model的重要组成。query进来后,我们首先对query分词去我们的领域Memo Model中检索属于该租户的相关领域线索,并后续一并给模型,显式地将领域知识灌入模型。

图片

3. 知识来源扩充-经验性知识沉淀

这一节比起前两节,对知识扩充的思路会更直给。说白了就是,知识库里内容不够的话,我们多从其他相关知识源来挖点内容。不过每个场景下可用知识源都不会太相同,为什么还是把这一节放上来了,主要还是在于智能答疑的场景下,一般都是先有人工答疑再有用智能问答替代人工答疑这个过程。所以,人工答疑的数据往往是语雀知识库外、甚至是超越语雀知识库的最有用的知识源。

但历史人工会话如何用在RAG也是一个比较难的命题,信息挖掘存在几个难点,其一为数据源较杂乱,不同于标准化的书面文本,其中掺杂了较多口语化内容,且格式不一,其挖掘难度大;其二为关键信息关联性低,往往用户的问题是通过多轮交互后才能得到解决方案,解决方案较为分散;其三为信息准确度要求高,若挖掘出来的信息准确率较低,在后续的检索中会削弱模型性能,降低智能问答效率。

在历史会话的挖掘上,我们也在调研后考虑了两个方案。

历史会话总结与摘要

初期调研时,针对大部分历史会话的探索基本上还是微调大模型对会话进行总结和摘要的能力。但在我们的场景下,一些研发平台的人工答疑链路会很长,直接利用整个总结数据灌入知识库又回到了Query-Document匹配难的问题。这里要解决也可以再进行细粒度query抽取,从总结里再生成问题和对应答案。但整体方案这样看起来会有个误差累积的风险,所以我们在构思,是不是也可以直接微调一版能够从历史会话中直接抽取有效问答对的模型。

历史会话问答对挖掘

如上述思路,我们将方案转变到历史会话的问答对直接挖掘。下图为利用会话问答对大模型对历史工单进行问答对抽取的推理流程。第一个核心模块是会话问答对大模型,这里我们利用了多尺度的问答对提取策略。针对会话问题中多轮回答的复杂性,单步提示难以让模型同时感知到全局和局部的知识。我们设计了一种分步提示(Multi-Step Prompting)策略,该生成策略用于训练数据准备、训练任务构造及大模型推理三个部分。本策略将针对历史会话的问答对抽取分解为两层子任务,分别构建不同的提示词prompt,包括全局层和局部层。

图片

  • 全局问答对抽取:由于往往一个历史会话工单中用户有最初进入提问的主要问题,而该类主要问题的解决方案往往横跨了整个对话。因此在全局层,首先要求大模型熟悉会话全文并理解全文判断会话中客服是否解决了用户所提出的主要问题,若解决了,则输出全局的问答对。

  • 局部问答对抽取:考虑到内部研发问题的复杂度往往较高,在多轮对话中往往存在多个衍生子问题,该类问题对于知识库建设也起着较为关键的作用。因此在局部层,要求大模型从局部出发,判断用户可能提出的业务问题及客服是否有解决方案,若有解决方案,则输出多个局部问答对。

针对上下两层子任务,我们分别设计了相应的提示词,基于这个策略我们借用开源模型抽取+人工检验标注了基于2000+个会话的问答对数据,最终可用数据有1300条。我们用这批数据lora微调了Qwen-14b的模型。为了保证问答对抽取的完整性,并未限制抽取的问答对的数量,因此局部抽取模块的问答对抽取自由度较高,导致可能会有脏数据产生。我们也利用大模型对抽取的问答对作质量评估,将质量较低的问答对筛选掉。由于前文所述的会话问答对大模型在经过对话数据微调后,对于对话有相应理解能力,此处我们仅用该大模型作简单的评估即可对部分无关问题进行剔除。我们抽取问答对的可用率在试点租户上可达70%。


三、从【知识相互冲突】出发的知识标签策略

前面两章讲的还是知识本身搜不到的问题,这一章节关注的问题是在于,我们之前会有一些case,是在知识库非常大的智能体下面,会发现有这样一种bad case:对同一个query,在不同的文档里确实是都能找到回答,但这两个文档的质量又确实有差异。一方面是在面对这类问题时往往大模型是无法直接判断错对优先,另一方面是本身模糊不清的知识更易让大模型产生幻觉。

因此,我们在构建知识库时,可以考虑运用一些天然的知识标签和低成本构建的知识标签来增强知识库以化解知识冲突问题。

1. 从知识标签到文档优先级

为了前置去解决给大模型的知识文档冲突的问题,我们需要在给大模型前事先判断出文档之间的重要性次序,可以利用文档的一些内容以外的信息作为文档标签来进行文档之间的相对优先级比较。

图片

从天然标签上来说,文档的时间信息及阅读量信息均可作为参考;从人工标签上来说,配合的业务方能从专业的角度进行简单的标记,将一些高保障知识库优先级打高、非常用知识库优先级打低。

标签的用法上,一方面是可以前置做一些规则上的定制化过滤;另一方面,当我们需要的只是在大模型思考时候减少知识冲突带来的混乱时,则是可以将其综合对文档做相对的优先级分数区分。

2. 文档优先级运用思路

从文档标签中获取优先级后,到底如何使用一开始也比较困扰。调研大部分的落地方法里一般在这里会带过说结合场景设计策略。最开始和业务同学聊说希望能加入文档优先级,能够更明确地剔除一些无效文档,把高质量文档的权重调高。我们尝试了在多个环节将文档优先级化成权重,再将这个权重做一些策略加入到检索/重排分数中,但一番尝试后发现这个调权过程非常之没有道理。权重调低了,这个优先级基本没用;调高了,一些高度相关但优先级不高的文档在一些query里又会完全失效。且即使对现有评测集里的case利用权重我们能调出一版最优的结果,但这些权重对后续未知case就全是黑盒,并不是合理的应用方法。后续,我们在实践中改变了做法。

筛除无关文档

文档优先级一个最简单有效的用法是,语雀知识库中大量知识,但很多平台的早期更新文档或者是个人记录其实都是无关紧要的信息。因此,文档优先级可以用在过滤无关文档上。

结合CoT的Ref Selection

而对于都可能有用的文档来说,检索召回阶段的目标是召回相关文档,那这个阶段优先级加权怎么都不合理,因为必然会影响本身文本的相关性。我们不在检索中直接按优先级排序,而是将原有的通过相关性检索出来的文档和文档之间的相对优先级均通过prompt的方式告诉大模型,启发大模型先去选出要参考的文档再从中进行作答。

这类优先级用法要解决的是具体两类问题:1. 一类情况是我们已经知道了哪些是相关的,但这些相关文本里可能有些知识冲突或者是一些重复时候,优先让模型参考优先级高的答;2.还有一类情况是,对于重排后文档整体相似度阈值,如果说检索出来的文档相关性其实都低于某个水位的时候,让模型优先参考优先级高的(至少答的是比较高质量的内容)。

在实践时的一个小trick在于,让模型选出参考文档时让模型给出简单的思考过程会比让模型直接给出参考文档的效果好很多。这个trick的有效性可能在于,作为生成式的模型,大模型确实是在生成过程中进行思考,而辨别文档相关性、知识冲突这些需要思考的指令又十分需要思考过程。

我们也发现,让模型选出参考文档优先级确实是只能给模型辅助,唯一风险是优先级的使用有时候也比较黑盒(有的时候真的不懂模型的想法)。后续我们也会尝试微调的方案,在参考文档的选择时也可以结合RAFT的思想对模型进行微调,让模型具有在一些冲突时候优先选择优先级高的文档作答的能力。

未来规划

我们目前的研发助手业务会持续跟进,除了在线链路上的优化外,后续在知识增强上依然会持续优化。对很多线上case的分析上来说,目前我们对知识增强做了很多方案上的探索,整体思路已大致确立,但仍有一些落地中实用但难做的点需要继续探索:

  • 历史会话的深度挖掘:不止一个业务方反馈非常需要对历史会话内容的挖掘,这块不止包括问答对抽取能力的优化,也包括如何配合进行历史会话的智能化分析、整理高频问题。目前也是在探索中。

  • 多模态知识的扩充:我们目前扩充的知识库也都是基于文本信息,对图片信息和文档链接的处理相同,将其作为url链接,前端上能对模型吐出的图片url链接进行展示。所以,如果大模型从这段话里认为这个图片信息需要展示会吐出,但这个做法也是损失了图片本身的信息。后续我们也需要尝试利用图片信息再扩充知识库。

参考链接:

[1]https://arxiv.org/pdf/2312.10997
[2]https://zilliz.com/learn/improve-rag-and-information-retrieval-with-hyde-hypothetical-document-embeddings
[3]https://arxiv.org/pdf/2404.16130
[4]https://arxiv.org/pdf/2409.05591

[5]https://arxiv.org/pdf/2404.16130


手把手教学构建 RAG 应用


本方案基于AnalyticDB for PostgreSQL的高效向量引擎与阿里云自主研发的通义千问LLM模型,构建一个高性能的检索增强生成(Retrieval-Augmented Generation, RAG)应用,实现企业的AI智能客服,更高效地解决客户问题。