背景

目前工作中正在研发RAG相关产品,产品形态上类似于垂直领域的ChatPDF。在此过程中遇到了一些坑,在此记录一下备忘。

正文

RAG相关技术照理应该是比较成熟了,从LangChain到LLamaIndex,各种框架也陆续推出,并且各大厂商也有类似的产品。再加上RAG的概念也很简单,无非就是将文件切成若干小块,做向量化存入向量数据库;然后用户问问题,根据用户问题从向量数据库中找到相关的若干小块,将小块和用户问题一起拼装成提示词问大模型,最后大模型给出答案。这些就给人一种错觉,觉得RAG与大模型相比没啥技术含量,是谁都行。但是,当实际研发一个RAG可落地的产品时,就会发现每一步都是坑,对说的就是上面RAG的每个环节都是坑,更可怕的是,只要有一步不行,那整体效果就极有可能不行。

下面就一步一步来说。

首先是文档的解析(这是文件切分的前一步)。我们的输入文本都是PDF(幸好基本都是文档型的PDF,而不是扫描件类型的)。在解析PDF时,主要的难点在于:

  1. 能准确识别文档段落,尽量将同一段的文本放在一起;
  2. 表格的识别和提取,这里面还要考虑识别表格的策略、合并单元格、跨页表格、跨页单元格、跨页后表头会有重复或不重复;
  3. 页眉、页脚的的识别;
  4. 统一去除一些无用、重复内容(如上面的页眉页脚、目录中的点)。

然后是文档切片。文档的切片方式有很多种,如:固定长度、滑动窗口、父子切片(auto-merging)等。切片的目标是尽量将相同语义的内容放在一个切片中,这样在召回后大模型可以更全面准确的回答问题。

接下来是切片的存储和召回,这两个一起说,因为存储和召回一般需要使用相同的技术。现在主流的就是做词嵌入Embedding,使用Embedding模型+向量数据库的方式。但是从我们实际使用的情况看,效果很差(很有可能是我们使用的姿势不对),压根无法实际使用。另外还有一种朴素的方式:关键词搜索(本质就是全文检索)。技术上就是将用户问题抽取出若干关键词(这个可以用大模型做),根据这些关键词,与切片文本做BM25文本相似度比对并排序。这种方式我们最早是在《使用Qwen-Agent将上下文记忆扩展到百万量级》这篇博客中看到的,从实际使用效果看,效果很好。另外在召回方面,还从Qwen-Agent中学到了一招:默认将文档的前几页放到召回列表中。这种做法简单暴力,但对应我们的场景,确实效果立竿见影。并且也可以基于这个进行发散,是否可以识别文档中的特定部分,并默认放到召回列表中呢。

之后是用户问题。这一步乍一看,就用户问个问题,能有啥花头精?目前我们至少会遇到两个问题:

  1. 前面也说了,我们采用的是关键词搜索,这就需要从用户问题中提取出关键词,单纯提取关键词还不够,为了提高泛化能力,还需要从关键词引申出同义词、近义词等
  2. 对于多轮对话,还需要将问题进行改写或扩写,才能使问题更明确。如:问:推荐一个旅游地点?答:南京。问:那边有什么好吃的?此时,需要将“那边”替换为“南京”才能准确的从知识库中召回想要的内容。

最后是大模型回答。这一步需要考虑提示词应该怎么写?召回的内容放那好(放用户消息还是系统消息)?选用哪个模型最合适(考虑效果和成本)?

经验总结

  1. 目前试下来,效果较好的技术路线为:滑动窗口切片+关键词搜索+qweb2-7b。另外PDF解析是基于tabula-java自研的,各种魔改。
  2. 召回时可以考虑多召回点,因为现在的大模型上下文都长了,基本都有32k,那完全可以召回4k长度的内容