RAG优化策略总结

一、背景

目前LLM虽然已经具备了强大的能力,但是在某些情况下,它们仍可能无法提供准确的答案。目前 LLM 面临的主要问题有:

  1. 信息偏差/幻觉: LLM 有时会产生与客观事实不符的信息,导致用户接收到的信息不准确。
  2. 知识更新滞后性: LLM 基于静态的数据集训练,这可能导致模型的知识更新滞后,无法及时反映最新的信息动态。
  3. 领域专业知识能力欠缺:LLM通常基于公开数据集训练,在处理特定领域的专业知识时,效果不太理想。

对于 Transformer架构类型的大模型来说,想要提高LLM生成内容的准确性,一般只需要 3 个步骤:

  1. 提供更准确的内容:提供准确性更高的内容,会让 LLM 能识别到关联的内容, 生成的内容准确性更高。
  2. 让重要的内容更靠前:GPT 模型的注意力机制会让传递 Prompt中更靠前的内容权重更高,越靠后权重越低。
  3. 不传递不相关内容:缩短每个块的大小,尽可能让每个块只包含关联的内容,缩小不相关内容的比例。

二、RAG流程简介

RAG 是一个完整的系统,其工作流程可以简单地分为数据预处理、检索、增强和生成四个阶段:

  1. 数据处理阶段: 对原始数据进行清洗和处理,然后将处理后的数据转化为检索模型可以使用的格式,最后存储在对应的数据库中。
  2. 检索阶段: 将用户的问题输入到检索系统中,从数据库中检索相关信息。
  3. 增强阶段: 对检索到的信息进行处理和增强,以便生成模型可以更好地理解和使用。
  4. 生成阶段: 将增强后的信息输入到生成模型中,生成模型根据这些信息生成答案。

一个完整的RAG应用开发流程,涉及到文档加载器、向量数据库、检索器、Prompt、记忆、输出解析器、大语言模型、多个功能模块,如下所示:

通常RAG的优化策略主要分为:查询转换、路由、问题构建、索引、检索和生成六个方面进行,如下与所示:

三、RAG优化

3.1 查询转换

3.1.1 查询重写和融合策略

如果直接使用原始问题进行检索,可以因为用户的表述偏差导致检索不到相关的文档。多查询重写策略的核心思想是利用大语言模型(LLM)对原始问题进行扩展、分解或抽象,生成多个语义相关但视角不同的子查询,从而提高检索系统对用户意图的覆盖能力。这种方法能有效解决单一查询可能存在的表述偏差或信息不全问题。整体流程如下所示:

一个简易的prompt如下所示:

xml 复制代码
你的任务是为给定的用户问题生成3 - 5个语义等价但表述差异化的查询变体,目的是帮助用户克服基于距离的相似性搜索的一些局限性,以便从向量数据库中检索相关文档。
以下是原始问题:
<question>
{{question}}
</question>
请生成3 - 5个语义与原始问题等价,但表述不同的查询变体,用换行符分隔这些替代问题。
请在<查询变体>标签内写下你的答案。

由于需要转换问题一般较小,以及生成子问题时对 LLM 的能力要求并不高,在实际的 LLM 应用开发中,通常使用参数较小的本地模型+针对性优化的 prompt 即可完成任务,并将 temperature 设置为 0,确保生成的文本更加有确定性。

调用样例如下:

在多查询重写策略中,每个子问题都会检索出相应的文档片段。针对如何合并这些文档的问题,便延伸出多查询结果融合策略。主要思想对其检索结果进行重新排序(即 reranking)后输出 Top K 个结果,最后再将这 Top K 个结果喂给 LLM 并生成最终答案 。通常使用的算法是RRF(Reciprocal Rank Fusion),即倒排序排名算法。公式如下:

  • 表示检索到的所有相关文档, 则是一个子文档
  • 表示所有子问题检索出来的文档列表, 表示某个子问题检索出来的列表。需要注意的是这个列表除了包含子文档,还表示了子文档的按照相关度的排序结果
  • 表示当前文档子d在其子集中的位置。
  • 是固定常数60,这个是经过实验的最优值。

该算法会对全集 D 进行二重遍历,外层遍历文档全集 D,内层遍历文档子集,在做内层遍历的时候,我们会累计当前文档在其所在子集中的位置并取倒数作为其权重。也就是说如果该子文档在每个子问题检索位置越靠前,则权重越高。 RFF的代码实现如下所示:

python 复制代码
def rrf(results: list[list], k: int = 60) -> list[tuple]:
    """倒数排名融合RRF算法,用于将多个结果生成单一、统一的排名"""

    # 1.初始化一个字典,用于存储每一个唯一文档的得分
    fused_scores = {}

    # 2.遍历每个查询对应的文档列表
    for docs in results:
        # 3.内层遍历文档列表得到每一个文档
        for rank, doc in enumerate(docs):
            # 4.将文档使用langchain提供的dump工具转换成字符串
            doc_str = dumps(doc)
            # 5.检测该字符串是否存在得分,如果不存在则赋值为0
            if doc_str not in fused_scores:
                fused_scores[doc_str] = 0
            # 6.计算多结果得分,排名越小越靠前,k为控制权重的参数
            fused_scores[doc_str] += 1 / (rank + k)

    # 7.提取得分并进行排序
    reranked_results = [
        (loads(doc), score)
        for doc, score in sorted(fused_scores.items(), key=lambda x: x[1], reverse=True)
    ]

    return reranked_results

3.1.2 问题分解策略

当提问的原始问题非常复杂时,无论是使用原始问题进行检索,亦或者生成多个相关联的问题进行检索,往往都很难在向量数据库中找到关联性高的文档,导致 RAG 效果偏差。造成这个问题的原因有几种:

  1. 复杂问题由多个问题按顺序步骤组成,执行相似性搜索时,向量数据库存储的都是基础文档数据,往往相似度低,但是这些数据在现实世界又可能存在很大的关联(文本嵌入模型的限制,一条向量不可能无损记录段落信息)。
  2. 问题复杂度高或者涉及到数学问题,导致 LLM 没法一次性完成答案的生成,一次性传递大量的相关性文档,极大压缩了大语言模型生成内容上下文长度的限制

问题分解策略就是将一个复杂问题分解成多个子问题或者子步骤。问题分解后的子问题跟原始问题是"父子"关系,而查询重写跟原始问题则是"兄弟"关系。

问题分解策略一共有两种分别是串行模式和并行模式。

  • 串行模式适用于逻辑依赖强的问题分解,确保步骤的连贯性。如"RAG都有哪些阶段?",需要先找到都有哪些阶段,然后再询问各个阶段该做什么事情。
  • 并行模式适用于独立子任务的高效处理,提升响应速度。如"如何规划北京到上海的 5 天旅游行程?",需要分解成交通、住宿、景点三个子问题,分别完成。

两种模式的流程如下所示:

串行模式

并行模式

一个简单问题分解的prompt如下所示:

xml 复制代码
你的任务是针对输入的问题生成多个相关的子问题或子查询,将输入问题分解成一组可以独立回答的子问题或子任务。
以下是输入的问题:
<question>
{{question}}
</question>
请生成3 - 5个与该问题相关的搜索查询,并使用换行符进行分割。生成的子问题/子查询应具有明确的主题和可独立回答的特点。
请在<子问题>标签内写下生成的子问题/子查询。

调用样例如下:

3.1.3 问题回退策略

问题回退策略和问题分解策略相反,当用户问题非常具体时,可能无法检索的对应文档,就需要将问题进行抽象。比如"李开复在2000年是在哪个公司工作?",重新抽象成"李开复的工作经历是什么?"。处理流程如下:

下面是一个执行样例:

  1. 原始问题: 如果理想气体的温度增加 2 倍,体积增加 8 倍,压力 P 会如何变化?

    • 直接回答的答案: 如果温度增加 2 倍,体积增加 8 倍,那么压力将减少 16 倍
    • 回溯问题:这个问题背后的物理原理是什么?
    • 回溯答案:理想气体定律: 其中 是压力, 是体积, 是物质的量, 是气体常数, 是温度。
    • 最终答案: 压力减少了 4 倍
  2. 原始问题: 1954 年 8 月至 11 月期间,埃斯特拉・利奥波德就读于哪所学校?

    • 直接回答的答案: 1954 年 8 月至 11 月期间,埃斯特拉・利奥波德就读于威斯康星大学麦迪逊分校。

    • 回溯问题: 埃斯特拉・利奥波德的教育经历是怎样的?

    • 回溯答案

      • 1948 年,威斯康星大学麦迪逊分校,植物学学士;
      • 1950 年,加州大学伯克利分校,植物学硕士;
      • 1955 年,耶鲁大学,植物学博士。
    • 最终答案: 她 1955 年就读于耶鲁大学植物学博士项目。因此,1954 年 8 月至 11 月期间,埃斯特拉・利奥波德最可能就读于耶鲁大学

应用场景

  • 复杂推理任务:如 STEM 问题(需公式应用)、时间敏感问题(需整合时间线)。
  • 多跳推理:通过抽象减少中间步骤的逻辑错误

prompt举例:

xml 复制代码
你的任务是分析给定的问题,忽略具体细节,提炼出问题背后涉及的核心概念、原理、知识范畴或通用逻辑。
这是需要分析的问题:
<question>
{{question}}
</question>
分析时,需要提取出问题的本质,将其转化为对某一概念、原理、知识范畴或通用逻辑的探讨。例如,如果问题是"水加热到 100℃为什么会沸腾?",那么分析后的新问题应该是"分析液体沸腾现象的物理原理(如相变、沸点与气压关系、能量传递机制等)"。
请在<回答>标签内写下你的分析结果。

调用样例如下:

3.1.4 HyDE 混合策略

在数据库中存储的数据一般都是文档层面上的,文档包含的数据会远远比用户的查询数据要大很多,所以 querydoc 之间是不对称检索,能找到的相似性文档相对来说也比较少。

例如:今天回家的路上看到了美丽的风景,非常开心!想学习 python 该怎么办? 这个请求中,前面的风景、开心等词语均为无关信息。会对真实的请求学习 python 产生干扰。如果直接搜索用户的请求,可能会产生不正确或无法回答的 LLM 响应。因此,有必要使得用户查询的语义空间与文档的语义空间保持一致。

整体流程如下:

HyDE 将检索过程分解为两个阶段:

  1. 生成假设文档:利用指令遵循的语言模型(如 InstructGPT)根据查询生成虚构但具有相关性的假设文档。生成过程通过自然语言指令(如 "写一个回答问题的段落")引导,无需标注数据。
  2. 对比编码检索:使用无监督对比学习的编码器(如 Contriever)将生成的假设文档编码为向量,在语料库嵌入空间中检索最相似的真实文档。编码器的密集瓶颈可过滤假设文档中的错误细节,将生成内容与实际语料对齐。

关键优势

  • 零样本能力:无需相关性标签或微调,直接利用预训练语言模型和对比编码器的能力。
  • 跨任务与语言泛化:在 Web 搜索、问答、事实核查等任务及斯瓦希里语、韩语、日语等多语言场景中均表现优异,超越无监督基线 Contriever,并接近有监督微调模型。

prompt参考:

xml 复制代码
你的任务是实现HyDE零样本检索策略,根据用户输入的查询生成假设文档。生成的内容要反映相关性模式,同时允许存在虚构细节。
以下是用户输入的查询:
<查询>
{{QUERY}}
</查询>
在生成假设文档时,请遵循以下要点:
1. 仔细理解查询的核心内容和意图。
2. 围绕查询构建文档,让文档与查询具有明显的相关性。
3. 可以适当添加一些虚构的细节,但不能偏离查询的主题。
4. 输出的文档应具有一定的逻辑性和连贯性。
请在<生成文档>标签内写下你生成的假设文档。
<生成文档>
[在此生成假设文档]
</生成文档>

调用样例如下:

局限性: 对于 doc-doc 类型的检索,虽然在语义空间上保持了一致,但是在 query->doc 的过程中,受限于各种因素,仍然可能产生错误信息。

例如提问 Bel是什么?,在没有执行 HyDE 混合策略而是直接查询得到答案如下:

复制代码
Bel 是由 Paul Graham 在四年的时间里(2015年3月26日至2019年10月12日),用 Arc 语言编写的一种编程语言。它基于 John McCarthy 最初的 Lisp,但添加了额外的功能。它是一个以代码形式表达的规范,旨在成为计算的形式化模型,是图灵机的一种替代方案。

但是执行 HyDE 混合策略生成假设性 doc 如下:

复制代码
Bel 是 Paul Graham 的化名,他是这段信息背后的作者,当时需要种子资金以维持生活,并且参与了一项交易,后来成为 Y Combinator 模式的典范。

在这个例子中,HyDE 在没有文档上下文的情况下错误地解释了 Bel,这会导致完全检索不到相关的文档信息。

3.1.5 混合检索策略

在查询检索中,常见的两种检索方式分别是稀疏检索器密集检索器

  • 稀疏检索器 :基于关键词匹配,利用词频(TF)和逆文档频率(IDF)计算文档与查询的相关性。
    • 优点:非常高效,无需训练、对明确关键词匹配效果好;
    • 缺点:无法捕捉语义(如同义词、上下文相关性)
  • 密集检索器 :使用深度学习模型生成密集向量表示,通过向量相似度(如余弦相似度)衡量相关性。
    • 优点:捕捉语义信息,解决词汇不匹配问题;
    • 缺点:需大量训练数据、计算成本高,对生僻词敏感;

混合检索策略就是将多种检索方式混合起来,可以利用不同算法的优势,从而获得比任何单一算法更好的性能,这也是常用的检索策略。这个流程如下所示:

  • WRRF是RRF的加权版本,通过赋予不同检索器的权重,从而影响检索文档的排序。

在Langchain中,代码实现样例如下:

ini 复制代码
doc_list = [  
    "我喜欢苹果",  
    "我喜欢橙子",  
    "苹果和橙子是水果",  
]  
  
# 初始化 BM25 检索器和 FAISS 检索器  
bm25_retriever = BM25Retriever.from_texts(doc_list)  
bm25_retriever.k = 2  
  
embedding = OpenAIEmbeddings()  
faiss_vectorstore = FAISS.from_texts(doc_list, embedding)  
faiss_retriever = faiss_vectorstore.as_retriever(search_kwargs={"k": 2})  
  
# 初始化集成检索器  
ensemble_retriever = EnsembleRetriever(retrievers=[bm25_retriever, faiss_retriever], weights=[0.5, 0.5])  

docs = ensemble_retriever.get_relevant_documents("苹果")  

Document(page_content='我喜欢苹果', metadata={}), Document(page_content='苹果和橙子是水果', metadata={})

3.2 路由

3.2.1 数据源路由

在 RAG 应用开发中,想根据不同的问题检索不同的向量数据库,其实只需要设定要对应的 Prompt,然后让 LLM 根据传递的问题返回需要选择的向量数据库的名称,然后根据得到的名称选择不同的检索器即可。整体流程如下:

路由数据源的prompt样例如下:

xml 复制代码
你是一位擅长将用户问题路由到适当数据源的专家。你的任务是根据问题涉及的编程语言,将问题路由到相关的数据源。
首先,请仔细阅读以下数据源信息:
<data_sources>
{{DATA_SOURCES}}
</data_sources>
现在,请仔细阅读以下用户问题:
<question>
{{QUESTION}}
</question>
为了将问题路由到合适的数据源,请按照以下步骤操作:
1. 仔细分析问题,识别其中涉及的编程语言。
2. 查看数据源信息,找出与该编程语言相关的数据源。
3. 如果问题涉及多种编程语言,找出与所有涉及语言都相关或与主要语言相关的数据源。
4. 如果没有合适的数据源,指出"没有合适的数据源"。

请在<回答>标签内写下路由结果。
<回答>
[在此输出路由结果]
</回答>

调用样例如下:

image-20250405112959522

3.2.2 prompt路由

在RAG应用开发中,针对不同场景的问题使用特定化的prompt模板 效果一般都会比通用模板会好一些,例如在教培场景,制作一个可以教学物理的授课机器人,如果使用通用的 prompt模板,会导 prompt编写变得非常复杂;反过来如果prompt写的简单,有可能没法起到很好的回复效果。

如果能针对用户的提问,例如用户提问的内容是数学相关的则使用数学的模板,提问的内容是物理相关的则使用物理的模板,针对性选择不同的模板,LLM 生成的内容会比使用通用模板会更好,例如下方有两个 prompt模板:

物理老师:

xml 复制代码
你将扮演一位非常聪明的物理教授,以简洁易懂的方式回答物理问题。当你不知道问题的答案时,要坦率承认自己不知道。
以下是需要你回答的物理问题:
<query>
{{query}}
</query>
在回答问题时,请遵循以下指南:
1. 确保回答简洁易懂。
2. 如果不知道问题的答案,直接表明"我不知道这个问题的答案"。
请在<回答>标签内写下你的答案。

数学老师:

markdown 复制代码
你将扮演一位非常优秀的数学家,专门负责回答数学问题。你需要将复杂的问题分解成多个小步骤,回答这些小步骤,然后将它们整合起来回答更广泛的问题。
这是需要你解答的数学问题:
<问题>
{{query}}
</问题>
在解答问题时,请按照以下步骤进行:
1. 仔细阅读问题,理解问题的核心。
2. 将问题分解成多个小步骤。
3. 依次解答每个小步骤。
4. 最后将小步骤的解答整合起来,给出完整的答案。
请在<回答>标签内写下你的答案,确保答案清晰、全面且包含每一个关键步骤。

基于文本向量模型,可以根据查询问题的语义,查找到相似度更高的prompt模板,语义的prompt路由流程如下所示:

3.3 问题构建

3.3.1 自查询

检索外部数据时,最后在执行检索的时候使用的都是固定的筛选条件(没有附加过滤的相似性搜索)。但是在某些情况下,用户发起的原始提问其实隐式携带了 筛选条件 ,例如提问:请帮我整理下关于2023年全年关于AI的新闻汇总。

在这段 原始提问中,如果执行相应的向量数据库相似性搜索,其实是附加了 筛选条件的,即 year=2023,但是在普通的相似性搜索中,是不会考虑 2023 年这个条件的(因为没有添加元数据过滤器,2022年和2023年数据在高维空间其实很接近),存在很大概率会将其他年份的数据也检索出来。

那么有没有一种策略,能根据用户传递的原始问题构建相应的元数据过滤器呢? 这样在搜索的时候带上对应的元数据过滤器,不仅可以压缩检索范围,还能提升搜索的准确性。这个思想其实就是 查询构建或者称为 自查询。

并且除了 向量数据库,类比映射到 关系型数据库、图数据库也是同样的操作技巧,即:

  1. 关系型数据库自查询: 使用 LLM 将自然语言转换成 SQL 过滤语句。
  2. 图数据库自查询: 使用 LLM 将自然语言转换成图查询语句。
  3. 向量数据库: 使用 LLM 将自然语言转换成元数据过滤器/向量检索器。

在Langchain中,封装了一个自查询检索器(SelfQueryRetriever),执行流程如下:

将对应的prompt翻译后如下所示:

swift 复制代码
你的任务是根据提供的信息,生成一个符合特定结构的JSON对象。该JSON对象将用于查询和过滤文档。
以下是允许使用的比较器和逻辑运算符:
<allowed_comparators>
{{ALLOWED_COMPARATORS}}
</allowed_comparators>
<allowed_operators>
{{ALLOWED_OPERATORS}}
</allowed_operators>
现在,请根据以下信息构建JSON对象:

<< Data Source >>
```json
{{{{
    "content": "{content}",
    "attributes": {attributes}
}}}}
```

在构建JSON对象时,请遵循以下规则:
1. 查询字符串应仅包含预期与文档内容匹配的文本。过滤条件中的任何条件不应在查询中提及。
2. 逻辑条件语句由一个或多个比较和逻辑操作语句组成。
    - 比较语句的形式为:`comp(attr, val)`,其中`comp`为允许的比较器,`attr`为要应用比较的属性名称,`val`为比较值。
    - 逻辑操作语句的形式为:`op(statement1, statement2, ...)`,其中`op`为允许的逻辑运算符,`statement1`, `statement2`, ... 为比较语句或逻辑操作语句。
3. 仅使用上述列出的比较器和逻辑运算符,不使用其他运算符。
4. 过滤条件仅引用数据源中存在的属性。
5. 过滤条件仅使用应用了函数的属性名称及其函数名。
6. 处理日期数据类型的值时,过滤条件仅使用`YYYY - MM - DD`格式。
7. 过滤条件仅在需要时使用。如果没有要应用的过滤条件,`filter`的值应返回 "NO_FILTER"。
8. `limit`必须始终为整数类型的值。如果该参数没有意义,请留空。

请在<回答>标签内输出符合以下格式的JSON对象:
```json
{
    "query": "文本字符串,用于与文档内容进行比较",
    "filter": "用于过滤文档的逻辑条件语句",
    "limit": 要检索的文档数量
}
```

<<样例>>
Data Source:
```json
{{
    "content": "Lyrics of a song",
    "attributes": {{
        "artist": {{
            "type": "string",
            "description": "Name of the song artist"
        }},
        "length": {{
            "type": "integer",
            "description": "Length of the song in seconds"
        }},
        "genre": {{
            "type": "string",
            "description": "The song genre, one of "pop", "rock" or "rap""
        }}
    }}
}}
```

User Query:
What are songs by Taylor Swift or Katy Perry about teenage romance under 3 minutes long in the dance pop genre

Structured Request:
```json
{{
    "query": "teenager love",
    "filter": "and(or(eq(\"artist\", \"Taylor Swift\"), eq(\"artist\", \"Katy Perry\")), lt(\"length\", 180), eq(\"genre\", \"pop\"))"
}}
```

3.4 索引

3.4.1 多表征索引

通常我们会为一个文档生成一个向量信息,并存储到向量数据库中。如果能从多个维度记录该文档块的信息,会大大增加该文档块被检索到的概率,多个维度记录信息等同于为文档块生成多个向量。 通常建立的维度有以下几种:

  1. 把文档切割成更小的块: 将一个文档块继续拆分成更小的块,通过检索小的块,定位父文档。
  2. 存储摘要信息: 将一个文档通过LLM生成摘要信息,将其和原文档一起存到向量数据库中,只返回原文档。
  3. 假设性问题: 使用 LLM 为每个文档块生成适合回答的假设性问题,将其和原文档一起嵌入或者代替,返回时返回原文档。

整体流程如下:

3.4.2 分层索引

在传统的 RAG 中,我们通常依靠检索短的连续文本块来进行检索。但是,当我们处理的是长上下文时,我们就不能仅仅将文档分块嵌入到其中,或者仅仅使用上下文填充所有文档。相反,我们希望为 LLM 的长下文找到一种好的最小化分块方法,这就是 RAPTOR 的用武之地,在 RAPTOR 中,均衡了多文档、超长上下文、高准确性、超低成本等特性。

RAPTOR 其实是一种用树状组织检索的递归抽象处理技术,它采用了一种自下而上的方法,通过对文本片段(块)进行聚类和归纳来形成一种分层结构。

构建过程如下图所示:

  1. 对原始文本进行分块,拆分成合适的大小;
  2. 对拆分的文档块进行嵌入/向量化,向量目前处于高维,并将数据存储到向量数据库;
  3. 将高维向量进行降维,降低运算成本,例如降低成 2 维或者 3 维;
  4. 对降维向量进行聚类,找出同一类的文档组;
  5. 合并文档组的文本,使用 LLM 对合并文档进行摘要汇总得到新的文本,重复2-5的步骤;
  6. 直到最后只剩下一个文档并且该文档的长度符合大小时,结束整个流程;

检索策略分为两种:树遍历检索和折叠树检索。其中树遍历检索流程如下所示:

  1. 从树的根节点开始,检索问题和文档的余弦相似性
  2. 选择最相关的前 k 个节点
  3. 将选择的节点的子节点放入候选集中,重复1-3的步骤

折叠树检索过程如下图所示:

  1. 将所有的节点都存储在一个向量数据库,折叠为一层
  2. 对原始问题进行检索,选择最相关的前 k 个节点

3.4.3 切块优化

3.4.3.1 递归字符分割器

字符分割器就是根据指定分割符,将文档切割成多个文档块。它通常会支持控制文档块的大小,避免超出大模型上下文限制。除此之外,还可以控制块与块之间重叠的内容大小,尽可能保留上下文信息。

但是在划分的过程中,可能会出现文档块过小或者过大的情况,这会让 RAG 变得不可控,例如:

  1. 文档块可能会变得非常大:由于切分依赖找到分隔符,如果说两个分割符的间隔非常大,就会导致文档块的大小超出限制的值,极端的情况下某个块的内容长度可能就超过了 LLM 的上下文长度限制,这样这个文本块永远不会被引用到,相当于存储了数据,但是数据又丢失了。
  2. 文档块可能会远远小于窗口大小:如果两个分割符的间隔非常小,则导致文档块的信息密度太低,块内容即使填充到 Prompt 中,LLM 也无法提取出有用的信息。

递归字符分割器对大文档块会使用更多的分隔符使其变小,对小文档块进行合并使其保留更多的信息,整体流程如下所示:

3.4.3.2 语义文档分割器

文档分割器都是使用特定字符对文本进行拆分,这种拆分模式虽然考虑了文档中的上下文切断的问题,但是并没有考虑句子之间的语义相似性,如果有一篇长文本,需要将其分割成语义相关的块,以便更好地理解和处理,这个时候就需要使用语义文档分割器。整体流程如下所示:

3.4.3.3 文档转换器

除了对文档进行切割的方式,对于结构化数据可以直接使用对应结构化的文档转换器,如HTML文档转换器、代码文档转换器。除此之外,还有两种特殊的转换器:

  • 问答转换器: 使用大语言模型,对文档信息进行提取,被生成该信息可能出现问题,如{'question': '产品发布活动的日期是什么时候?', 'answer': '7月15日'}
  • 文档翻译器: 其实是将文档转换成另外一种形式的文档,比如将英文的论文翻译成中文。

3.5 检索

3.5.1 ReRank 重排序

在完成对问题的改写、不同数据库查询的构建以及路由逻辑、向量数据库索引方面的优化后,我们可以考虑进一步优化 筛选阶段,一般涵盖了 重排序、纠正性RAG 两种策略。其中重排序是使用频率最高,性价比最高,通常与 混合检索一起搭配使用,也是目前主流的优化策略

重排序的核心思想见字知其意,即对检索到的文档 调整顺序,除此之外,重排序 一般还会增加剔除无关/多余数据的步骤,其中RRF就是重排序中最基础的一种。

3.5.2 CRAG

纠正性检索增强生成(Corrective Retrieval-Augmented Generation,CRAG)是一种先进的自然语言处理技术,旨在提高检索的生成方法的鲁棒性和准确性。在 CRAG 中引入了一个轻量级的检索评估器来评估检索到的文档的质量,并根据评估结果触发不同的知识检索动作,以确保生成结果的准确性。整体流程如下图所示:

  1. 检索文档:首先,基于用户的查询,系统执行检索操作以获取相关的文档或信息。
  2. 评估检索质量:CRAG 使用一个轻量级的检索评估器对检索到的每个文档进行质量评估,计算出一个量化的置信度分数。
  3. 触发知识检索动作: 根据置信度分数,CRAG 将触发以下二个动作之一:
    • 正确:如果评估器认为文档与查询高度相关,将采用该文档进行知识精炼。
    • 错误:如果文档被评估为不相关或误导性,CRAG将重写生成一个新的问题,然后利用网络搜索寻找更多知识来源。
  4. 知识精炼:对于评估为正确的文档,CRAG将进行知识精炼,抽取关键信息并过滤掉无关信息。
  5. 问题重写: 通过重写一个新的相似问题,来优化答案的检索。
  6. 网络搜索:在需要时,CRAG会执行网络搜索以寻找更多高质量的知识来源,以纠正或补充检索结果。
  7. 生成文本:最后,利用经过优化和校正的知识,传递给 LLM,生成对应文本。

评估节点prompt:

xml 复制代码
你是一名评分员,负责评估检索到的文档与用户问题的相关性。你的任务是根据给定的标准,判断文档是否与问题相关,并给出"yes"或"no"的二元评分。
以下是用户的问题:
<question>
{{QUESTION}}
</question>
以下是检索到的文档:
<document>
{{DOCUMENT}}
</document>
判断文档是否相关的标准为:如果文档包含与问题相关的关键词或语义含义,则判定为相关。
首先,在<思考>标签中详细分析文档是否包含与问题相关的关键词或语义含义,说明你的分析过程。然后在<回答>标签中给出最终的二元评分("yes"或"no")。
<思考>
[在此详细说明你对文档与问题相关性的分析过程]
</思考>
<回答>
[在此给出"yes"或"no"的评分]
</回答>

问题重写prompt:

markdown 复制代码
你是一个问题改写器,任务是将输入的问题转换为一个更适合网络搜索的优化版本。你需要仔细分析输入问题,挖掘其潜在的语义意图和含义。
以下是需要改写的问题:
<question>
{{QUESTION}}
</question>
在改写问题时,请遵循以下方法:
1. 去除不必要的修饰词和语气词,使问题简洁明了。
2. 提取问题的核心内容,突出关键信息。
3. 调整语序,使问题更符合网络搜索的习惯。
请在<改写后的问题>标签内写下改写后的问题。

知识精炼prompt:

xml 复制代码
你是一位信息精炼专家,负责从给定文档中提取与特定主题直接相关的关键事实、数据、观点和结论,过滤掉不相关的背景信息、示例和解释。
请仔细阅读以下文档:
<document>
{{DOCUMENT}}
</document>
需要围绕的主题是:
<topic_name>
{{TOPIC_NAME}}
</topic_name>
在精炼信息时,请遵循以下要求:
1. 仅提取与主题直接相关的关键事实、数据、观点和结论。
2. 过滤掉所有不相关的背景信息、示例和解释。
3. 输出尽量保持简洁明了。
请在<回答>标签内写下精炼后的信息。

RAG-prompt:

xml 复制代码
你是一个负责回答问题的助手。你的任务是利用提供的检索到的上下文来回答问题。如果不知道答案,就直接表明不知道。回答最多使用三句话,保持简洁。
以下是检索到的上下文:
<retrieved_context>
{{RETRIEVED_CONTEXT}}
</retrieved_context>
这是问题:
<question>
{{QUESTION}}
</question>
请在<回答>标签内写下你的答案。
<回答>
[在此给出答案]
</回答>

3.6 生成

3.6.1 self-RAG

Self-RAG 全称为自我反思 RAG,见名知其意,即对原始查询、检索的内容、生成的内容进行自我反思,根据反思的结果执行不同的操作,例如:直接输出答案、重新检索、剔除不相关的内容、检测生成内容是否存在幻觉、检测生成内容是否有帮助等,可以把 Self-RAG看成是一个拥有自我反思能力的智能体,这个智能体主要用来依据相关知识库回复用户问题,自我迭代,直到输出满意的结果。SELF-RAG训练了一个任意的LLM,使其能够在给定任务输入时反思自己的生成过程,同时生成任务输出和临时的特殊标记(称为反思标记)。这些反思标记分为检索和评论标记,分别表示了是否需要检索以及生成的质量。流程如下图所示:

一个 Self-RAG应用主要有三大步骤组成:

  1. 按需检索(Retrieval as Needed): SELF-RAG在需要的情况下,通过生成一个检索标记,来评估是否需要从相关文本中获取信息。如果需要检索,模型会使用检索器(Retriever)来获取与输入相关的文本段落;相反,当模型被要求写"写一篇关于Python依赖注入的文章"时,大模型会直接生成答案,无需进行检索。
  2. 以并行方式生成内容(Parallel Generation): 模型会同时使用 prompt 和检索到的内容来生成模型输出,在整个过程中,会触发多种类型的反思(Reflection),涵盖了:反思文档是否有关联、反思生成内容是否存在幻觉以及是否能回答问题、。
    • 如果不关联则重新检索;
    • 如果存在幻觉/支持度不够,则重新生成;
  3. 内容的评估和选择: 对步骤 2 中生成的内容进行评估,并选择最佳文档段落作为输出。

判断是否有幻觉prompt:

xml 复制代码
你是一名评分员,负责评估大语言模型(LLM)的生成内容是否有一组检索到的事实作为依据。你的任务是根据给定的事实集,判断生成内容是否能得到事实的支持,并给出"是"或"否"的二元评分。"是"表示答案有事实依据,"否"则表示没有。
首先,请仔细阅读以下检索到的事实集:
<检索到的事实集>
{{RETRIEVED_FACTS}}
</检索到的事实集>
现在,请仔细阅读以下大语言模型的生成内容:
<大语言模型生成内容>
{{LLM_GENERATION}}
</大语言模型生成内容>
评估这份生成内容时,请考虑生成内容中的所有陈述是否都能在检索到的事实集中找到支持。
在<思考>标签中详细分析你的判断依据,然后在<判断>标签中给出"是"或"否"的判断。例如:
<思考>
[在此详细说明你做出判断的依据]
</思考>
<判断>
[在此给出"是"或"否"的判断]
</判断>
请现在开始你的评估。

答案评估prompt:

xml 复制代码
你的任务是评估一个答案是否解决了相应的问题,并给出"yes"或"no"的二元评分。"yes"表示答案解决了问题,"no"表示答案未解决问题。
以下是问题:
<question>
{{QUESTION}}
</question>
以下是答案:
<answer>
{{ANSWER}}
</answer>
在评估时,请仔细对比答案内容与问题,判断答案是否直接回应并解决了问题。
请在<判断>标签内给出你的最终判断,使用"yes"或"no"。
<判断>
[在此给出"yes"或"no"的判断]
</判断>

四、参考资料

相关推荐
jhonroxton6 分钟前
使用golang快速构建你的MCP Server
llm·mcp
dony724735 分钟前
大模型 json格式 修复
llm·json
仙人掌_lz1 小时前
企业年报问答RAG挑战赛冠军方案:从零到SotA,一战封神
python·gpt·ai·llm·rag·问答·年报
GetcharZp17 小时前
SAM2全面解析:Meta新一代“分割一切”大模型,图像视频一键精准分割!
计算机视觉·llm
CoderCaesar1 天前
图解Transfomer(上)
llm·openai
CodeDevMaster1 天前
MCP入门指南:让AI模型与外部世界无缝连接
llm·mcp
GetcharZp2 天前
Gemma3 实现高效OCR:轻量级AI模型如何革新文字识别?
llm·gemini
山花2 天前
最浅显易懂的自注意力机制讲解,杜绝QKV
人工智能·llm
Baihai_IDP2 天前
LLMs.txt:让大模型更懂你的 Web 文档
人工智能·llm·deepseek