专栏名称: 阿里开发者
阿里巴巴官方技术号,关于阿里的技术创新均将呈现于此
目录
相关文章推荐
春江潮起  ·  世间将再无松下电视 ·  昨天  
春江潮起  ·  世间将再无松下电视 ·  昨天  
阿里开发者  ·  提示词工程的十大认知误区 ·  昨天  
白鲸出海  ·  奥特曼率队深夜血战DeepSeek,o3-m ... ·  5 天前  
51好读  ›  专栏  ›  阿里开发者

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

阿里开发者  · 公众号  · 科技公司  · 2024-11-18 08:30

正文

阿里妹导读


本文作者基于自身在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,包括全局层和局部层。 



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

  • 局部问答对抽取:







请到「今天看啥」查看全文