RAG实战篇(1)——项目优化最佳实践

171 阅读10分钟

背景

在过去两年中,检索增强生成(RAG,Retrieval-Augmented Generation)技术逐渐成为提升智能体的核心组成部分。通过结合检索与生成的双重能力,RAG 能够引入外部知识,从而为大模型在复杂场景中的应用提供更多可能性。但是在实际落地场景中,往往会存在检索准确率低,噪音干扰多,召回完整性,专业性不够,导致 LLM 幻觉严重的问题。本次分享会聚焦 RAG 在实际落地场景中的知识加工和检索细节,如何去优化 RAG Pineline 链路,最终提升召回准确率。

一、知识检索优化思路

目前 RAG 智能问答应用几个痛点:

  • 知识库文档越来越多以后,检索噪音大,召回准确率不高
  • 召回不全,完整性不够
  • 召回和用户问题意图相关性不大
  • 只能回答静态数据,无法动态获取知识,导致答疑应用比较呆,比较笨。

知识处理优化

非结构化 / 半结构化 / 结构化数据的处理,准备决定着 RAG 应用的上限,因此首先需要在知识处理,索引阶段做大量的细粒度的 ETL 工作,主要优化的思路方向:

  • 非结构化 -> 结构化:有条理地组织知识信息。
  • 提取更加丰富的, 多元化的语义信息。

知识加载

目的:需要对文档进行精确的解析,更多元化的识别到不同类型的数据。

优化建议:

  • 建议将 docx、txt 或者其他文本事先处理为 pdf 或者 markdown 格式,这样可以利用一些识别工具更好的提取文本中的各项内容。
  • 提取文本中的表格信息。
  • 保留 markdown 和 pdf 的标题层级信息,为接下来的层级关系树等索引方式准备。
  • 保留图片链接,公式等信息,也统一处理成 markdown 的格式。

切片 Chunk 尽量保持完整

目的:保存上下文完整性和相关性,这直接关乎回复准确率。

保持在大模型的上下文限制内,分块保证输入到 LLMs 的文本不会超过其 token 限制。

优化建议:

  • 图片 + 表格单独抽取成 Chunk,将表格和图片标题保留到 metadata 元数据里。
  • 文档内容尽量按照标题层级或者 Markdown Header 进行拆分,尽可能保留 chunk 的完整性。
  • 如果有自定义分隔符可以按照自定义分割符切分。

多元化的信息抽取

除了对文档进行 Embedding 向量抽取外,其他多元化的信息抽取能够对文档进行数据增强,显著提升 RAG 召回效果。

  • 知识图谱

  • 优点:1. 解决 NativeRAG 的完整性缺失,依然存在幻觉问题,知识的准确性,包括知识边界的完整性、知识结构和语义的清晰性,是对相似度检索的能力的一种语义补充。

  • 适用场景:适用于严谨的专业领域 (医疗,运维等),知识的准备需要受到约束的并且知识之间能够明显建立层级关系的。

  • 如何实现:

    • 依赖大模型提取 (实体, 关系, 实体) 三元组关系。
    • 依赖前期高质量,结构化的知识准备,清洗,抽取,通过业务规则通过手动或者自定义 SOP 流程构建知识图谱。

  • Doc Tree
  • 适用场景:解决了上下文完整性不足的问题,也能匹配时完全依据语义和关键词,能够减少噪音
  • 如何实现:以标题层级构建 chunk 的树形节点,形成一个多叉树结构,每一层级节点只需要存储文档标题,叶子节点存储具体的文本内容。这样利用树的遍历算法,如果用户问题命中相关非叶子标题节点,就可以将相关的子节点数据进行召回。这样就不会存在 chunk 完整性缺失的问题。

  • 提取 QA 对,需要前置通过预定义或者模型抽取的方式提取 QA 对信息
  • 适用场景:
  • 能够在检索中命中问题并直接进行召回,直接检索到用户想要的答案,适用于一些 FAQ 场景,召回完整性不够的场景。
  • 如何实现:
  • 预定义: 预先为每个 chunk 添加一些问题
  • 模型抽取: 通过给定一下上下文,让模型进行 QA 对抽取
  • 元数据抽取
  • 如何实现:根据自身业务数据特点,提取数据的特征进行保留,比如标签,类别,时间,版本等元数据属性。
  • 适用场景:检索时候能够预先根据元数据属性进行过滤掉大部分噪音。
  • 总结提取
  • 适用场景:解决这篇文章讲了个啥,总结一下等全局问题场景。
  • 如何实现:通过 mapreduce 等方式分段抽取,通过模型为每段 chunk 提取摘要信息。

知识处理 工作流

目前 DB-GPT 知识库提供了文档上传 -> 解析 -> 切片 -> Embedding -> 知识图谱三元组抽取 -> 向量数据库存储 -> 图数据库存储等知识加工的能力,但是不具备对文档进行复杂的个性化的信息抽取能力,因此希望通过构建知识加工工作流模版来完成复杂的,可视化的,用户可自定义的知识抽取,转换,加工流程。

2.2 RAG 流程优化

RAG 流程的优化我们又分为了静态文档的 RAG 和动态数据获取的 RAG,目前大部分涉及到的 RAG 只覆盖了非结构化的文档静态资产,但是实际业务很多场景的问答是通过工具获取动态数据 + 静态知识数据共同回答的场景,不仅需要检索到静态的知识,同时需要 RAG 检索到工具资产库里面工具信息并执行获取动态数据。

静态知识 RAG 优化

原始问题处理

目的:澄清用户语义,将用户的原始问题从模糊的,意图不清晰的查询优化为含义更丰富的一个可检索的 Query

  • 原始问题分类,通过问题分类可以
  • LLM 分类 (LLMExtractor)
  • 构建 embedding + 逻辑回归实现双塔模型,text2nlu DB-GPT-Hub/src/dbgpt-hub-nlu/README.zh.md at main · eosphoros-ai/DB-GPT-Hub
  • tip: 需要高质量的 Embedding 模型,推荐 bge-v1.5-large
  • 反问用户,如果语义不清晰将问题再抛给用户进行问题澄清,通过多轮交互
  • 通过热搜词库根据语义相关性给用户推荐他想要的问题候选列表
  • 槽位提取,目的是获取用户问题中的关键 slot 信息,比如意图,业务属性等等
  • LLM 提取 (LLMExtractor)
  • 问题改写
  • 热搜词库进行改写
  • 多轮交互
元数据过滤

当我们把索引分成许多 chunks 并且都存储在相同的知识空间里面,检索效率会成为问题。比如用户问 "浙江我武科技公司" 相关信息时,并不想召回其他公司的信息。因此,如果可以通过公司名称元数据属性先进行过滤,就会大大提升效率和相关度。

async def aretrieve(
    self, query: str, filters: Optional[MetadataFilters] = None
) -> List[Chunk]:
    """Retrieve knowledge chunks.
        Args:
            query (str): async query text.
            filters: (Optional[MetadataFilters]) metadata filters.
        Returns:
            List[Chunk]: list of chunks
        """
    return await self._aretrieve(query, filters)

多策略混合召回
  • 按照优先级召回,分别为不同的检索器定义优先级,检索到内容后立即返回
  • 定义不同检索,比如 qa_retriever, doc_tree_retriever 写入到队列里面, 通过队列的先进先出的特性实现优先级召回。
  • 多知识索引 / 空间并行召回
  • 通过知识的不同索引形式,通过并行召回方式获取候选列表,保证召回完整性。
后置过滤

经过粗筛候选列表后,怎么通过精筛过滤噪音呢

  • 无关的候选分片剔除
  • 时效性剔除
  • 业务属性不满足剔除
  • topk 去重
  • 重排序 仅仅靠粗筛的召回还不够,这时候我们需要有一些策略来对检索的结果做重排序,比如把组合相关度、匹配度等因素做一些重新调整,得到更符合我们业务场景的排序。因为在这一步之后,我们就会把结果送给 LLM 进行最终处理了,所以这一部分的结果很重要。
  • 使用相关重排序模型进行精筛,可以使用开源的模型,也可以使用带业务语义微调的模型。
  • 根据不同索引召回的内容进行业务 RRF 加权综合打分剔除
显示优化 + 兜底话术 / 话题引导
  • 让模型使用 markdown 的格式进行输出
基于以下给出的已知信息, 准守规范约束,专业、简要回答用户的问题.
规范约束:
    1.如果已知信息包含的图片、链接、表格、代码块等特殊markdown标签格式的信息,确保在答案中包含原文这些图片、链接、表格和代码标签,不要丢弃不要修改,
如:图片格式:![image.png](xxx), 链接格式:[xxx](xxx), 表格格式:|xxx|xxx|xxx|, 代码格式:```xxx```.
    2.如果无法从提供的内容中获取答案, 请说: "知识库中提供的内容不足以回答此问题" 禁止胡乱编造.
    3.回答的时候最好按照1.2.3.点进行总结, 并以markdwon格式显示.

动态知识 RAG 优化

文档类知识是相对静态的,无法回答个性化以及动态的信息, 需要依赖一些第三方平台工具才可以回答,基于这种情况,我们需要一些动态 RAG 的方法,通过工具资产定义 -> 工具选择 -> 工具校验 -> 工具执行获取动态数据

工具资产库

构建企业领域工具资产库,将散落到各个平台的工具 API,工具脚本进行整合,进而提供智能体端到端的使用能力。比如,除了静态知识库以外,我们可以通过导入工具库的方式进行工具的处理。

工具召回

工具召回沿用静态知识的 RAG 召回的思路,再通过完整的工具执行生命周期来获取工具执行结果。

  • 槽位提取:通过传统 nlp 获取 LLM 将用户问题进行解析,包括常用的业务类型,标签,领域模型参数等等。
  • 工具选择:沿用静态 RAG 的思路召回,主要有两层,工具名召回和工具参数召回。
  • 工具参数召回,和 TableRAG 思路类似,先召回表名,再召回字段名。
  • 参数填充:需要根据召回的工具参数定义,和槽位提取出来的参数进行 match
  • 可以代码进行填充,也可以让模型进行填充。
  • 优化思路:由于各个平台工具的同样的参数的参数名没有统一,也不方便去治理,建议可以先进行一轮领域模型数据扩充,拿到整个领域模型后,需要的参数都会存在。
  • 参数校验
  • 完整性校验:进行参数个数完整性校验
  • 参数规则校验:进行参数名类型,参数值,枚举等等规则校验。
  • 参数纠正 / 对齐,这部分主要是为了减少和用户的交互次数,自动化完成用户参数错误纠正,包括大小写规则,枚举规则等等。eg: