RAG

RAG

1. Indexing索引构建

1.1 文档分块chunking

将一个大文档切成小段,每段包含一个相对独立的语义。分块的关键参数:

  • 块大小chunk size:太小会导致语义不完整,太大会导致检索不精准
  • 重叠overlap:防止语义信息在切块边界被硬生生地切断

如何选择合适的chunk size?

  • 根据embedding模型的能力:embedding会有其最大输入token的限制,切块大小应小于这个限制。很多embedding在处理中等长度的文本时效果最好
  • 根据数据的类型和结构
    • 对于结构化、段落分明的文档(如论文、markdown格式),可以采用按段落来切分
    • 对于非结构化的长文本(如小说),更多依赖固定长度切块
    • 对于代码,应按照函数或类来切块
  • 根据预期的查询类型:如果用户的问题通常很具体,需要精确定位到某一句话,那么较小的切块(如句子级别)可能更有效。如果用户的问题很宽泛,需要综合多个段落的信息,那么较大的切块会更好

如何选择合适的overlap?

常见的经验法则是设置overlap为chunk size的10%-20%

大chunk和小chunk的考量

大chunk优点:包含更丰富的上下文,有助于回答需要广泛背景知识的复杂问题

大chunk缺点:

  • 噪声增加:大chunk可能会包含大量与用户查询不相关的信息,稀释了关键信息
  • 检索精度下降: 嵌入向量代表的是整个大块的平均语义,可能无法精确匹配非常具体的问题
  • 成本更高:送入LLM的上下文更长
  • Lost in the Middle问题:“Lost in the Middle” 是指LLM在处理一个长上下文时,倾向于更好地回忆和利用位于上下文开头和结尾的信息,而忽略或遗忘位于中间部分的信息的一种现象

小chunk优点:信息密度高,与具体问题的相关性强,检索更精确

小chunk缺点:

  • 上下文不足:单个小chunk可能不包含回答问题所需的全部信息,需拼接多个chunk
  • 语义割裂:容易将原本连续的上下文信息切断

1.2 向量化Embedding

分块后的文本还是人类语言,机器不懂。我们需要把它转换成向量,相似的文字会得到相似的向量。常见的Embedding模型有:

模型 维度 优势 适用场景
OpenAI text-embedding-3-small 1536 效果好,速度快 通用场景
OpenAI text-embedding-3-large 3072 效果最好 高精度需求
BGE-large-zh 1024 中文效果好 中文为主
Sentence-BERT 768 开源免费 资源有限

如何选择一个合适的Embedding模型?

  • 参考公开排行榜:MTEB (Massive Text Embedding Benchmark)、C-MTEB(针对中文)
  • 考虑具体应用场景:
    • 领域特异性: 如果你的知识库是某个专业领域(如医疗、法律、金融),可以考虑使用在该领域数据上预训练或微调过的嵌入模型,它们通常比通用模型表现更好。
    • 语言支持: 确保模型支持你的业务所涉及的语言,特别是对于多语言场景。
    • 模型大小与速度: 模型越大通常效果越好,但推理速度也越慢,成本越高。需要在效果和性能之间做出权衡。对于需要低延迟的实时应用,可能需要选择一个更小的模型。
  • 私有模型 or 开源模型:根据对数据的安全性考量选择

评估Embedding模型的指标

  1. 检索(Retrieval): 这是对RAG最重要的评估任务。

    • nDCG@k (Normalized Discounted Cumulative Gain):综合衡量了检索结果的相关性和排名。是检索任务中最核心和最全面的指标。

    • Recall@k:衡量在前k个结果中,召回了多少比例的相关文档。

    • MRR (Mean Reciprocal Rank):衡量第一个相关文档出现在第几位。适用于那些只需要找到一个正确答案的场景。

  2. 语义文本相似度(Semantic Textual Similarity, STS):

    • 指标: Spearman或Pearson相关系数。
    • 评估方式: 衡量模型计算出的向量余弦相似度,与人类判断的两句话的语义相似度分数之间的相关性。一个好的模型,其相似度计算结果应该与人类的直觉高度一致。
  3. 分类(Classification):

    • 指标: 准确率(Accuracy)。
    • 评估方式: 将文本嵌入向量作为特征,训练一个简单的逻辑回归分类器,看其在文本分类任务上的表现。这衡量了嵌入向量作为“特征”的质量。
  4. 聚类(Clustering):

    • 指标: V-measure。
    • 评估方式: 看模型生成的嵌入向量能否在无监督的情况下,将语义相似的文本自然地聚集在一起。

1.3 向量数据库Vector Database

普通数据库擅长精确查询:”找ID=123的记录”

但向量搜索是相似性查询:”找和[0.1, 0.3, 0.5]最相似的10个向量”

常用向量数据库:Pinecone、Milvus、FAISS

2. Retrieval检索

Retrieval检索流程如下:

  • 用户查询,根据需要对用户查询进行优化
  • 向量检索,将用户查询也向量化,然后在向量数据库中搜索,找到最相关的几个chunk

2.1 检索策略

基础的向量检索虽然有效,但在处理复杂查询和多样化文档时会遇到瓶颈。检索策略主要可以分为多路召回重排序Re-ranking两大策略,其中多路召回指的是在 RAG 系统的检索阶段,不依赖单一的查询或检索策略,而是同时使用两种或更多不同的方法、模型或查询方式从知识库中获取相关文档片段,常见的多路召回策略有:

  • 基于不同查询表示的多路召回:如多查询检索、HyDE
  • 基于不同索引类型的多路召回:如混合搜索Hybrid Search
  • 基于不同粒度的多路召回:如小块引用大块

为了提升检索质量,可采用以下几种方式:

增强检索器

  • 混合搜索Hybrid Search:将稀疏索引和密集索引相结合。
    • 稀疏索引(如BM25):基于关键词匹配,对于包含特定术语、缩写、ID的查询非常有效
    • 密集索引(向量搜索):基于语义相似度,擅长理解口语化的查询
    • 优点:兼顾了关键词精确匹配和语义模糊匹配的能力,效果通常远超单一检索方法
  • 重排序Re-ranking:采用一个两阶段的检索流程
    • 召回recall:先用一个快速但相对粗糙的方法,从海量文档中召回一个较大的候选集
    • 重排rerank:再使用一个更强大、更复杂的模型,对这个候选集进行精细化的重排序,选出最终top-N作为上下文
    • 优点:重排模型常用Cross-Encoder,可以直接比较query和文档的文本,捕捉更细粒度的相关性,精度远大于单纯的向量相似度

优化查询

查询扩展与转换:不直接使用用户的原始query进行检索,而是先用LLM对原始query进行“加工”

  • query改写:对于用户的原始query,可能会偏向于口语化,直接用来检索效果可能不好,可以先使用LLM将query改写为逻辑清晰明确的提问,再用于检索。(如用户在首次查询时提到了一样东西,后续query都指代为 “它”,如果直接使用原始query可能会有指代不明确的情况,这时候可以对query进行改写)
  • 多查询检索:让LLM针对原始query,从不同角度生成多个不同的查询,然后对所有查询的检索结果进行合并
  • HyDE:让LLM先针对问题生成一个“假设性”的答案,然后用这个假设性答案的embedding去检索,因为答案的文本和目标文档的文本在形式上更相似
  • 子问题查询Decomposition:对于复杂问题,先将其分解为多个简单的子问题,分别检索,再进行合并
  • Step Back问答回退:不直接回答具体问题,而是先生成一个更抽象、更通用的”回退问题”,从更高层次理解用户意图,用这个抽象问题去检索,获得更广泛的信息;结合回退问题的答案和原问题,生成更准确的回答

优化索引结构

  • 小块引用大块:在索引时,将文档切成小的、用于检索的“摘要块”,但每个小块都保留对它所属的、更大的“父块”的引用。检索时,用query匹配小块以获得高精度,但最终送给LLM的是包含更丰富上下文的父块,兼顾了小块检索的精确性和大块上下文的完整性

  • 层级检索:将文档组织成树状,叶节点为原始文档块,中间节点为多个文档块的摘要,根结点为整个文档集的总摘要。RAPTOR即为层级检索的经典实现

  • 图索引:用LLM提取文档中的实体和关系,构建一个知识图谱。检索时先在图谱中进行结构化查询,找到相关的实体和子图,再结合向量检索进行补充。对于需要多跳推理、理解实体关系的query非常有效

    多跳推理是指通过多个关系链条来得出结论,而非直接的单跳关系。举个实际场景来说明:假设图谱中有”周杰伦-配偶-昆凌”和”昆凌-国籍-澳大利亚”这两条关系,当有人问”周杰伦的配偶来自哪里”时,系统需要沿着这两跳关系才能得出答案,这就是典型的多跳推理。

2.2 多路召回结果的融合与排序

多路召回生成了多个结果集,关键在于如何有效地将它们合并并排序,以便将最有价值的信息送入 LLM

排序融合方法(Reciprocal Rank Fusion, RRF)

RRF 是一种常用的无参数排序融合方法。它基于文档在不同召回列表中的排名,通过以下公式计算其融合得分:

$$RRF_{score}(d) = \sum \frac{1}{k + rank_r(d)}$$

其中:

  • $rank_r(d)$:文档d在第r个检索结果列表中的排名位置
  • $k$:一个平滑常数,用于控制排名靠后的文档对总分的贡献。60为广泛应用的经验值

如果一个文档在多个召回列表中都获得了较高的排名,其 RRF 得分将非常高

3. Generation生成

把检索到的文档和用户问题组合成一个完整的Prompt,发给LLM,调用LLM生成答案后,可能还需要对答案进行一些后处理,如:引用标注、安全过滤、格式美化等。生成效果评估参考:

指标 评估内容 评估方法
Faithfulness 答案是否忠实于检索文档 LLM评判 / 人工标注
Relevance 答案是否回答了问题 LLM评判 / 相似度计算
Coherence 答案是否流畅连贯 语言模型困惑度
Groundedness 答案是否有依据 检查是否有引用

3.1 生成时会遇到的”Lost in the Middle”问题

“Lost in the Middle” 是指LLM在处理一个长上下文时,倾向于更好地回忆和利用位于上下文开头和结尾的信息,而忽略或遗忘位于中间部分的信息的一种现象。这个现象对RAG系统有直接且重要的影响。在RAG的生成阶段,我们通常会将检索到的Top-K个文档块与用户的原始问题拼接起来,形成一个长长的prompt。例如:

1
[原始问题] + [文档1] + [文档2] + [文档3] + ... + [文档K]

如果LLM存在“Lost in the Middle”的问题,那么:

  • 文档1文档K 的内容会得到LLM的充分关注。
  • 而位于中间的文档2、文档3…等,即使它们包含了回答问题的关键信息,也有很大概率被LLM忽略,导致最终生成的答案信息不完整或不准确。
  • 这会使得我们精心设计的检索环节(如重排序)的效果大打折扣,因为即使我们把最相关的文档排在了前面,只要它不是第一个或最后一个,就可能被“遗忘”。

Lost in the Middle的缓解方法

  1. 文档重排序
    • 核心思想: 不再按照检索分数的顺序简单地拼接文档,而是有策略地放置它们
    • 具体做法: 在将检索到的K个文档送入LLM之前,进行一次重排序。将最相关的文档放置在上下文的开头和结尾,而将次要相关的文档放在中间。这样可以确保关键信息处于LLM的“注意力甜点区”
  2. 减少检索的文档数量
    • 核心思想: 与其送入大量可能包含噪声的文档,不如只送入少数几个最关键的文档
    • 具体做法: 严格控制Top-K中的K值,例如只取Top-3或Top-5。这需要前端的检索和重排序步骤有更高的精度,确保召回的文档质量足够高
  3. 指令化提示
    • 核心思想: 在prompt中明确指示模型要关注所有提供的上下文
    • 具体做法: 在prompt的末尾加入类似这样的指令:“请确保你的回答完全基于以上提供的所有上下文信息,不要忽略任何一份文档。” 虽然这不能完全解决问题,但在一定程度上可以引导模型的注意力。
  4. 对LLM进行微调
    • 核心思想: 训练LLM更好地处理长上下文
    • 具体做法: 构建一个特定的微调数据集,其中的任务要求模型必须利用位于上下文中间部分的信息才能正确回答。通过这种方式,可以“强迫”模型学会不忽略中间内容。这是最根本但成本也最高的解决方案

4. 一些更复杂的RAG范式

4.1 迭代式检索 (Iterative Retrieval) - 例如 Self-RAG, Corrective-RAG

  • 核心思想: 将RAG从一个单向的流水线,变成一个循环、自我修正的过程。
  • 工作流程:
    1. 首次检索与生成: 像传统RAG一样,进行检索并生成一个初步的答案。
    2. 反思与评估(Reflection): LLM会对初步生成的答案和检索到的上下文进行“反思”。它会评估:当前的信息是否足够支撑答案?答案是否还有不确定或缺失的部分?
    3. 二次检索: 如果认为信息不足,LLM会主动生成一个新的、更具针对性的查询,进行新一轮的检索。例如,如果初步答案是“A公司的CEO是张三”,模型可能会反思“这个信息是否最新?”,然后生成一个新的查询“A公司2025年的CEO是谁?”
    4. 整合与精炼: LLM会整合新旧检索到的所有信息,生成一个更完善、更准确的最终答案。

4.2 自适应检索 (Adaptive Retrieval) - 例如 FLARE, Self-Ask

  • 核心思想: 不在生成前一次性检索所有信息,而是在生成过程中“按需”检索,实现“即时”(just-in-time)的信息获取。
  • 工作流程:
    1. 开始生成: LLM根据问题开始直接生成答案。
    2. 预测不确定性: 它会一边生成,一边预测接下来的内容。当它预测到即将生成一个事实性信息(如人名、日期、地点),但对此不确定(表现为下一个词的概率分布很平坦)时,它会暂停生成。
    3. 主动提问与检索: 在暂停处,LLM会插入一个特殊的占位符(如 [SEARCH]),并主动提出一个需要查询的问题(例如,“法国的首都是哪里?”)。
    4. 获取信息并继续: 系统执行这个查询,将检索到的答案(“巴黎”)填入,然后LLM基于这个新信息继续向下生成。
  • 优势: 这种方法非常高效,只在需要时才进行检索,避免了预先检索大量无关信息。

4.3 多源数据RAG (Multi-Source RAG)

  • 核心思想: 让Agent能够智能地从多种不同类型的数据源中进行检索和整合。
  • 工作流程: Agent首先对问题进行分解,判断回答这个问题需要哪些信息。然后,它可能会决定:
    • 向量数据库中检索相关的非结构化文档。
    • 知识图谱中查询结构化的实体关系。
    • 调用SQL数据库来获取精确的统计数据。
    • 甚至调用搜索引擎API来获取实时信息。
  • 最后,Agent会将从不同来源获取的所有信息进行综合,生成一个全面的答案。这本质上是一种Agent驱动的RAG

5. RAG在生产环境中面临的挑战

将一个RAG原型系统部署到生产环境中,会面临一系列从数据到模型、再到工程和运维的实际挑战。

  1. 数据处理与维护的复杂性 (Data Pipeline Complexity):

    • 分块策略的泛化性: 一个在PDF上效果很好的分块策略,可能在处理HTML或JSON数据时效果很差。为异构数据源设计和维护一套鲁棒的分块策略非常困难。
    • 知识库的实时更新: 如何高效地保持向量索引与源数据的同步?当源文档被修改或删除时,需要有可靠的机制来更新或废弃对应的向量,这涉及到复杂的ETL(Extract, Transform, Load)流程。
  2. 性能瓶颈:延迟与成本 (Performance Bottlenecks: Latency & Cost):

    • 延迟: RAG的“检索+生成”两步天然比直接调用LLM要慢。在实时交互场景下,检索和LLM生成的延迟都必须被极致优化。
    • 成本:
      • 计算成本: 大规模文档的嵌入、向量数据库的运行、LLM的API调用,都是持续的成本支出。
      • 存储成本: 向量索引本身会占用大量的存储空间,尤其是高维度的嵌入。
  3. 端到端的评估与监控 (End-to-End Evaluation & Monitoring):

    • 评估困难: 在生产环境中,很难有带标准答案的数据集。如何有效地评估线上RAG系统的表现(如检索质量、答案忠实度)是一个巨大挑战。
    • 性能衰退监控: 如何发现并诊断问题?是检索模块的性能下降了(例如,因为数据分布变化),还是生成模块开始产生更多幻觉?需要建立一套完善的监控和报警系统。
  4. 处理“无答案”和“上下文外”问题 (Handling “No Answer” and “Out-of-Context” Questions):

    • 挑战: 当知识库中不包含用户所提问题的答案时,系统很容易会基于不相关的检索结果强行生成一个错误的、具有误导性的答案。
    • 解决方案: 系统需要具备判断检索结果相关性的能力。如果判断所有检索到的内容都与问题无关,它应该拒绝回答或明确告知用户“根据现有资料无法回答此问题”,而不是胡乱作答。
  5. 安全与隐私 (Security & Privacy):

    • 访问控制: 在企业环境中,不同的用户对不同的文档有不同的访问权限。RAG系统必须能够集成这套权限体系,确保用户只能检索到他们有权查看的文档内容。
    • 提示注入: 恶意用户可能会在查询中嵌入恶意指令,或者被索引的文档本身可能包含恶意内容,这些都可能用来攻击或操纵RAG系统。

6. RAG常见面试题

6.1 RAG向量数据库如何实现去重

入库前:基于 Hash 的硬去重(最常用)

这是最简单、性能损耗最低的方法。在将文本切块(Chunking)后、转化为向量(Embedding)前进行。

  • 原理:对每一个文本切块计算一个唯一摘要(如 MD5SHA-256)。
  • 操作
    1. 建立一个 布隆过滤器 (Bloom Filter) 或在缓存(如 Redis)中记录已处理文本的 Hash 值。
    2. 如果新切块的 Hash 已存在,则直接跳过,不再调用 Embedding API(省钱省时)。
  • 局限性:只能识别“完全一模一样”的文本。哪怕只多了一个空格或标点,Hash 就会完全不同。

语义维度的“近似去重”(进阶)

有时候文本并不完全相同,但语义高度重合(如:“如何重置密码”和“怎样更改我的密码”)。这种情况下,Hash 就失效了

  • 方案 A:向量相似度预检

    在插入新向量 $V_{new}$ 之前,先在库里做一次 Top-1 检索。如果最高相似度超过了阈值(例如 $0.98$),则认为重复,拒绝入库。

  • 方案 B:分布式离线清洗

    如果是处理海量存量数据,通常会配合大数据工具(如 Spark/Flink)使用 LSH (Locality Sensitive Hashing)。它能将相似的向量映射到同一个桶中,从而高效识别并剔除语义重复项。

检索后的“动态去重”

即使数据库里有少量重复,我们也可以在检索出来后、交给模型前做一次清洗。

  • Rerank 过滤:在使用重排序模型时,设定阈值,剔除掉那些得分极高且内容高度相似的后续节点。
  • 最大边界关联 (MMR, Maximal Marginal Relevance):这是一种经典的算法。它在选择检索结果时,不仅考虑“与问题的相关性”,还考虑“结果之间的多样性”。
    • 公式逻辑:$MMR = \arg\max_{D_i \in R \setminus S} [\lambda \cdot Sim(D_i, Q) - (1-\lambda) \cdot \max_{D_j \in S} Sim(D_i, D_j)]$
    • 通俗解释:如果一个切块虽然和问题很像,但它和已经选入结果集的切块太像了,它的优先级就会被大幅降低

6.2 RAG文档发生局部更新时,如何做增量索引而不是全量重建?

系统之所以能“只处理变动部分”,是因为它在后台(可以是关系型数据库如mysql)维护了一张极其详细的清单。这张清单记录了每个文档切分后的每一个分片的情况。假设你有一个文档 A.txt,内容是“第一段、第二段、第三段”。第一次索引时,清单长这样:

文档 ID 分片序号 分片内容哈希 (指纹) ⚠️关键! 向量库 ID
Doc_A Chunk_1 h1 (对应“第一段”) Vec_001
Doc_A Chunk_2 h2 (对应“第二段”) Vec_002
Doc_A Chunk_3 h3 (对应“第三段”) Vec_003

当把第二段进行了修改,其他没变,系统会对修改后的 A.txt 重新切片,并计算每个新分片的哈希值(指纹):

  • 新分片 1 的哈希:h1(没变)
  • 新分片 2 的哈希:h2_new变了
  • 新分片 3 的哈希:h3(没变)

系统拿着这些新指纹去查那张清单:

  1. 分片 1 (h1):清单里有 h1 且属于 Doc_A结论: 完全一样,跳过,不调 Embedding 接口。
  2. 分片 2 (h2_new):清单里没有这个指纹。结论: 这是新内容。
  3. 分片 3 (h3):清单里有 h3结论: 没变,跳过。

系统只对“分片 2”执行两个操作:

  1. 调用 Embedding 模型,把 h2_new 变成向量。
  2. 去向量数据库里,把 Vec_002 的位置更新为新向量,或者删掉 Vec_002 插入新向量。
  3. 更新清单,把 h2 替换为 h2_new

为什么有时候“局部修改”会失效?

你可能会问:“我只改了一个字,为什么系统还是重新处理了整个文档?”

这通常是因为分片偏移(Chunk Shifting): 如果你在文档的第一行插入了一个词,导致后面所有的文字都往后挪了。如果你使用的是“固定长度分片”(比如每 500 字一丢),那么:

  • 旧的 Chunk 1 变成了 [1-500] 字
  • 新的 Chunk 1 变成了 [新增词 + 1-499] 字
  • 导致后面所有分片的指纹全部变了

这就是为什么“智能分片”(如按段落、按标题切分)对增量索引至关重要的原因——它能保证局部变动不影响其他部分的指纹。

6.3 向量相似度计算

  • 余弦相似度(Cosine Similarity):$cosine(A, B) = \frac{A \cdot B}{\lVert A \rVert \cdot \lVert B \rVert}$
  • 点积(Dot Product):$dot(A, B) = A \cdot B = \sum\limits_{i=1}^n A_iB_i$
  • 欧氏距离(Euclidean Distance):$euclidean(A, B) = \sqrt{\sum\limits_{i=1}^n(A_i-B_i)^2}$
  • 曼哈顿距离(Manhattan Distance):$manhattan(A, B) = \sum\limits_{i=1}^n \vert A_i - B_i \vert$
  • 雅卡尔指数(Jaccard):$jaccard(A, B) = \frac{\vert A \cap B \vert}{\vert A \cup B \vert}$
  • 皮尔逊相关系数(Pearson Correlation):$pearson(A, B) = \frac{Cov(A, B)}{\sigma_A \cdot \sigma_B}$
方法 适用场景 计算效率 鲁棒性 特点
余弦相似度(Cosine Similarity) 文本、高维稀疏数据 忽略向量长度,专注方向一致性
点积(Dot Product) 推荐系统(显式评分 受向量长度影响,需归一化
欧氏距离(Euclidean Distance) 图像、聚类 衡量绝对距离,对尺度敏感
曼哈顿距离(Manhattan Distance) 异常检测、稀疏特征 对异常值鲁棒
雅卡尔指数(Jaccard) 集合、标签匹配 适用于二进制数据或集合
皮尔逊相关系数(Pearson Correlation) 评分预测、连续变量相关性分析 消除均值影响,衡量线性相关性(范围[-1,1])

RAG
http://example.com/2026/03/21/RAG/
作者
Kon4tsu
发布于
2026年3月21日
许可协议