RAG 的缺陷

论文:Seven Failure Points When Engineering a Retrieval Augmented Generation System

  • Index Process(文本向量化构建索引的过程):
    • 文档加载准确性和效率:如 PDF 文件的加载,如何提取其中的有用文字信息和图片信息等
    • 文档切分的粒度:文本切分的大小和位置会影响后面检索出来的上下文完整性和与大模型交互的 token 数量,怎么控制好文档切分的度,是个难题
    • 内容缺失 (MIssing Content):原本的文本中就没有问题的答案
  • Query Process(检索增强回答的过程中):
    • Missed Top Ranked:错过排名靠前的文档
    • Not in Context:提取上下文与答案无关
    • 格式错误 (Wrong Format):例如需要 JSON,给了 MarkDown
    • 答案不完整 (Incomplete):答案只回答了问题的一部分
    • 未提取到答案 (Not Extracted):提取的上下文中有答案,但大模型没有提取出来
    • Incorrect Specificity:答案不够具体或过于具体
  • 性能问题:
    • 引入向量数据库中间件,当数据量或并发量足够大时,向量数据库可能成为性能瓶颈

展望:将来 LLM 能够输入非常长的上下文时,就不需要 RAG 了,直接把所有数据灌给 LLM 就行了

文档加载准确性和效率  

优化文档读取器

​一般知识库中的文档格式都不尽相同,HTML、PDF、MarkDown、TXT、CSV 等,每种格式文档都有其都有的数据组织方式。怎么在读取这些数据时将干扰项去除(如一些特殊符号等),同时还保留原文本之间的关联关系(如 CSV 文件保留其原有的表格结构),是主要的优化方向

​目前针对这方面的探索为:针对每一类文档,设计一个专门的读取器。如 LangChain 中提供的 WebBaseLoader 专门用来加载 HTML 文本等

数据清洗与增强

输入垃圾,那也必定输出垃圾。如果源数据质量低劣,比如包含互相冲突的信息,那不管 RAG 工作构建得多么好,它都不可能用输入的垃圾神奇地输出高质量结果。这个解决方案不仅适用于这个痛点,任何 RAG 工作流程想要获得优良表现,都必须先清洁数据

文档切分的粒度

​粒度太大可能导致检索到的文本包含太多不相关的信息,降低检索准确性,粒度太小可能导致信息不全面,导致答案的片面性。问题的答案可能跨越两个甚至多个片段

固定长度的分块

直接设定块中的字数,每个文本块有多少字

内容重叠分块

在固定大小分块的基础上,为了保持文本块之间语义上下文的连贯性,在分块时,保持文本块之间有一定的内容重叠

基于结构的分块

基于结构的分块方法利用文档的固有结构,如 HTML 或 MarkDown 中的标题和段落,以保持内容的逻辑性和完整性

基于递归的分块

重复的利用分块规则不断细分文本块。在 LangChain 中会先通过段落换行符(\n\n)进行分割。然后,检查这些块的大小。如果大小不超过一定阈值,则该块被保留。对于大小超过标准的块,使用单换行符(\n)再次分割。以此类推,不断根据块大小更新更小的分块规则(如空格,句号)

分块大小的选择

不同的嵌入模型有其最佳输入大小。比如 OpenAI 的 text-embedding-ada-002 的模型在 256 或 512 大小的块上效果更好

文档的类型和用户查询的长度及复杂性也是决定分块大小的重要因素。处理长篇文章或书籍时,较大的分块有助于保留更多的上下文和主题连贯性;而对于社交媒体帖子,较小的分块可能更适合捕捉每个帖子的精确语义。如果用户的查询通常是简短和具体的,较小的分块可能更为合适;相反,如果查询较为复杂,可能需要更大的分块

内容缺失

​准备的外挂文本中没有回答问题所需的知识。这时候,RAG 可能会提供一个自己编造的答案

增加相应知识库

将相应的知识文本加入到向量知识库中

数据清洗与增强

输入垃圾,那也必定输出垃圾。如果源数据质量低劣,比如包含互相冲突的信息,那不管 RAG 工作构建得多么好,它都不可能用输入的垃圾神奇地输出高质量结果。这个解决方案不仅适用于这个痛点,任何 RAG 工作流程想要获得优良表现,都必须先清洁数据

更好的 Prompt 设计

通过 Prompts 让大模型在找不到答案的情况下,输出“根据当前知识库,无法回答该问题”等提示。这样的提示,就能鼓励模型承认自己的局限,并更透明地向用户传达它的不确定。虽然不能保证 100% 准确度,但在清洁数据之后,精心设计 prompt 是最好的做法之一

错过排名靠前的文档

外挂知识库中存在回答问题所需的知识,但是可能这个知识块与问题的向量相似度排名并不是靠前的,导致无法召回该知识块传给大模型,导致大模型始终无法得到正确的答案

增加召回数量

增加召回的 topK 数量,例如原来召回前 3 个知识块,修改为召回前 5 个知识块。不推荐单独使用此方法,因为知识块多了,不光会增加 token 消耗,也会增加大模型回答问题的干扰。需要结合重排

重排(Reranking)

该方法的步骤是,首先检索出 topN 个知识块(N > K)过召回,然后再对这 topN 个知识块进行重排序,取重排序后的 K 个知识块当作上下文。重排是利用另一个排序模型或排序策略,对知识块和问题之间进行关系计算与排序

提取上下文与答案无关

内容缺失错过排名靠前的文档的具体体现

格式错误

Prompt 调优

优化 Prompt 逐渐让大模型返回正确的格式

答案不完整

将问题分开提问

一方面引导用户精简问题,一次只提问一个问题。另一方面,针对用户的问题进行内部拆分处理,拆分成数个子问题,等子问题答案都找到后,再总结起来回复给用户

未提取到答案

提示压缩技术

使用提示压缩技术削减 RAG 成本及加速推理

性能问题

构建分布式系统

RAG 评估

RAG 效果评估的必要性

  • 评估出 RAG 对大模型能力改善的程度
  • RAG 优化过程,通过评估可以知道改善的方向和参数调整的程度

RAG 评估方法

人工评估

​最 Low 的方式是进行人工评估:邀请专家或人工评估员对 RAG 生成的结果进行评估。他们可以根据预先定义的标准对生成的答案进行质量评估,如准确性、连贯性、相关性等。这种评估方法可以提供高质量的反馈,但可能会消耗大量的时间和人力资源

自动化评估

​自动化评估肯定是 RAG 评估的主流和发展方向

LangSmith

需要准备测试数据集。不仅可以评估 RAG 效果,对于 LangChain 中的 Prompt 模板等步骤都可进行测试评估

RAGAs

​RAGAs(Retrieval-Augmented Generation Assessment)是一个评估框架,考虑检索系统识别相关和重点上下文段落的能力,LLM 以忠实方式利用这些段落的能力,以及生成本身的质量

数据集格式:

  • question:作为 RAG 管道输入的用户查询
  • answer:从 RAG 管道生成的答案
  • contexts:从用于回答 question 外部知识源中检索的上下文
  • ground_truths:question 的基本事实答案。这是唯一人工注释的信息

评估指标

评估检索质量:

  • 上下文相关性 (context_relevancy),也叫上下文精度 (context_precision)
  • 上下文召回率 (context_recall):越高表示检索出来的内容与标准答案越相关

评估生成质量:

  • 忠实度 (faithfulness):越高表示答案的生成使用了越多的参考文档(检索出来的内容)
  • 答案相关性 (answer_relevancy):侧重于评估生成的答案与给定提示的相关性
from datasets import Dataset
from ragas.metrics import context_precision, context_recall, faithfulness, answer_relevancy
from ragas import evaluate
from dotenv import load_dotenv
import os
 
load_dotenv()
 
data_samples = {
    'question': ['When was the first super bowl?', 'Who won the most super bowls?'],
    'answer': ['The first superbowl was held on Jan 15, 1967',
               'The most super bowls have been won by The New England Patriots'],
    'contexts': [
        ['The First AFL–NFL World Championship Game was an American football game played on January 15, 1967, at the Los Angeles Memorial Coliseum in Los Angeles,'],
        ['The Green Bay Packers...Green Bay, Wisconsin.', 'The Packers compete...Football Conference']
    ],
    'ground_truth': ['The first superbowl was held on January 15, 1967',
                     'The New England Patriots have won the Super Bowl a record six times']
}
dataset = Dataset.from_dict(data_samples)
 
# 上下文相关性:评估是否所有和标准答案相关的 chunk 被检索到而且排名很靠前
context_precision_score = evaluate(dataset, metrics=[context_precision])
 
# 上下文召回率:衡量检索到的上下文与标准答案的匹配程度
context_recall_score = evaluate(dataset, metrics=[context_recall])
 
# 忠实度:衡量生成答案与给定上下文之间的事实一致性
faithfulness_score = evaluate(dataset, metrics=[faithfulness])
 
# 答案相关性:侧重于评估生成的答案与给定提示的相关性
answer_relevancy_score = evaluate(dataset, metrics=[answer_relevancy])
 
print(context_precision_score.to_pandas())
print(context_recall_score.to_pandas())
print(faithfulness_score.to_pandas())
print(answer_relevancy_score.to_pandas())

输出:

Python 3.9.13 (tags/v3.9.13:6de2ca5, May 17 2022, 16:36:42) [MSC v.1929 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.18.1 -- An enhanced Interactive Python. Type '?' for help.
PyDev console: using IPython 8.18.1
Python 3.9.13 (tags/v3.9.13:6de2ca5, May 17 2022, 16:36:42) [MSC v.1929 64 bit (AMD64)] on win32
runfile('E:\\PythonWorkSpace\\LLMProject\\demo4.py', wdir='E:\\PythonWorkSpace\\LLMProject')
Evaluating: 100%|██████████| 2/2 [00:05<00:00,  2.74s/it]
Evaluating: 100%|██████████| 2/2 [00:03<00:00,  1.76s/it]
Evaluating: 100%|██████████| 2/2 [00:07<00:00,  3.55s/it]
Evaluating: 100%|██████████| 2/2 [00:36<00:00, 18.45s/it]
                       user_input  ... context_precision
0  When was the first super bowl?  ...               1.0
1   Who won the most super bowls?  ...               0.0
[2 rows x 5 columns]
                       user_input  ... context_recall
0  When was the first super bowl?  ...            1.0
1   Who won the most super bowls?  ...            0.0
[2 rows x 5 columns]
                       user_input  ... faithfulness
0  When was the first super bowl?  ...          1.0
1   Who won the most super bowls?  ...          0.0
[2 rows x 5 columns]
                       user_input  ... answer_relevancy
0  When was the first super bowl?  ...         0.980736
1   Who won the most super bowls?  ...         0.943014
[2 rows x 5 columns]