RAG 技术入门:核心原理、优化方案与评估体系详解
一、RAG 定义
由于通用的 LLM 预训练数据存在限制,缺乏实时知识或垂直领域知识,而不断微调(Fine-tuning)又存在较高成本。因此,一种解决该问题的方式应运而生。
首先通过 Retrieve(检索)外部知识库的文档来为 LLM 提供补充信息的上下文,并与最初的问题一起被合并成一个 Augmentative(增强)的提示,而后输入 LLM 使 LLM 能够 Generate(生成)更有效的回答。顾名思义这就是 RAG(Retrieve Augmented Generation)。
二、通用 RAG 框架
通用的 RAG 框架流程如下:
- 多文档切分成 chunks(块);
- 将 chunks 索引化并存储,目前基于 LLM 对文本进行 embedding 从而实现向量化储存的方式比较热门;
- query 进入并进行索引匹配,从而检索到相关的 chunks(如果是向量化索引,则通过计算向量间的相似度来进行匹配);
- 将 chunks 内容作为 context 与 query 包装成 prompt 输入 LLM,并生成回答。
目前大部分对 RAG 的优化都是在如下框架的基础上对环节进行优化,从而提高 response 的质量。当然也有将 RAG 进行模块化包装,从而使 RAG 组合更加灵活(Langchain 和 LlamaIndex 就是典型的例子)。下一章将详细介绍关于 RAG 的一些优化方式。
[图:RAG 通用框架示意图]
三、高级 RAG 优化
对于 RAG 的优化,下面将结合自身的实践经验以及参考的论文进行说明。
1. Query 环节优化
- 由于单一的 Query 可能存在噪音和随机性;因此在 query 进行检索前,可以对 query 进行如下操作:
- 使用 LLM 对 query 进行改写,使之更加规范;
- 使用 LLM 理解 query 意图并生成多个 queries 并行检索;
- 使用 LLM 将 query 分解成多个 sub query,并进行并行检索。
- 在查询中加入多轮对话,形成聊天引擎,多次上下文连续查询。
- ContextChatEngine: 首先检索与用户查询相关的上下文,然后将其与内存缓冲区中的聊天历史记录一起发送给 LLM,以便 LLM 在生成下一个答案时了解先前的上下文。
- CondensePlusContextMode: 在每次交互中,聊天历史记录和最后一条消息都会经过 LLM 进行融合判断并压缩为一个新查询(如果不相关则直接查询,如果相关则进行融合),然后该查询转到索引,检索到的上下文与原始用户消息一起传递给 LLM,以生成一个回答。
这一环节有一个很重要的目的是为 rerank 而生,以减少 raw 问题检索造成的弱鲁棒性。
2. Retrieve 环节的优化
以下的优化都是单独说明,可以进行随意组合。
-
总分层级索引(总→细,提高搜索的效率) 创建多层级索引,如第一层索引由摘要组成,另一层由摘要对应的一或多个文档块组成,并分两步检索,首先将 query 与摘要匹配过滤掉不相关的文档,并只在相关组内检索第二层文档块。 这种方式虽然能够通过快速缩小范围的方式来减少检索时间,但是可能考虑到摘要的信息的完整性,这种方式可能会在第一层匹配时忽略掉文档的细节信息,从而导致检索的准确性降低。个人认为,当是知识库巨大,而且文档块的内容较为单一时可以使用。
-
父子层级索引(细→总,提高搜索精确问题的准确性) 同样创建多层级索引。如:先将文档按一定方式切分成较大的文本块,形成 parent chunks;在对每个 parent chunk 按更细的方式切分成 child chunks,每个 child chunk 都有一个其所对应的 parent index。query 检索时,在 sub chunks 中进行检索,后依据被检索到的 sub chunks 所对应的 parent index 回至父段块(目的是为了获得更完整完整的上下文),以父段作为最终的增强信息获得答案。
[图:父子层级索引示意图]
这种方式在匹配时完全依据语义和关键词,能够减少噪音,尤其能够优化当 query 所对应的有效信息(或有效提示)较为简短时 parent 所面临的匹配不敏感问题。


