RAG开发的零散笔记

检索增强生成(Retrieval Augmented Generation)

解决了什么问题:

  • 知识局限性,大模型只知道通用,公开的数据。 一些实时性、非公开的数据没有
  • 幻觉问题: 所有的深度学习模型的底层原理都是基于数学概率,模型输出实质上是一系列数值运算,大模型也不例外,所以它经常会一本正经地胡说八道,尤其是在大模型自身不具备某一方面的知识或不擅长的任务场景。
  • 数据安全性: 企业的数据至关重要。RAG为企业构建私有知识库存储提供了方案

Q: 啥时候适合用RAG 当你的知识库少于20w个Token(大概500页材料),你可以将整个知识库包含在你给模型的提示中,而无需使用RAG或类似方法

Pasted image 20260630110148.png

数据预处理

RAG的源数据需要进行严格的数据预处理,保留与业务核心相关的内容。包括:

  • 内容清洗:去除文档中所有和核心知识无关的内容,如网页导航栏,页眉页脚,广告,版权声明等
  • 格式转换和规范化: 将复杂的格式(如PDF的多栏布局,word的表格)转换成更简洁利于llm理解的格式(如markdown),统一处理文档中的空格,换行和特殊字符
  • 修复或增强: 修正明显的拼写错误或OCR识别错误。

文本分块

将原始资料分割成片段chunks的过程,这些chunks是RAG系统信息处理的基本单元,他们将被送入Embedding模型进行向量化,然后存入向量库进行索引

文本分块的核心是一个Trade-Off,检索精度和上下文完整性的平衡。

小块(Smaller Chunks):

  • 优点 :信息更聚焦,语义更集中。这使得查询向量更容易匹配到包含特定关键词或高度相关语义的小块,从而提高检索的精度。更容易命中“靶心”。
  • 缺点 :可能丢失重要的上下文信息。单个小块可能只包含一个事实片段,缺乏必要的背景、前提或后续解释,导致 LLM 无法理解完整的语境或回答需要综合信息的复杂问题。

大块 (Larger Chunks):

  • 优点 :能保留更丰富的上下文信息,包含更长的逻辑链条或更完整的背景描述。这有助于 LLM 理解更复杂的概念、事件的前因后果或不同信息点之间的关系。
  • 缺点 :可能包含较多与当前查询不直接相关的信息(噪音),从而稀释了核心相关性信号,可能降低检索精度(匹配到包含相关词但整体主题跑偏的块)。同时,更大的块也增加了 LLM 处理的负担和潜在的幻觉风险。

分块的方式有如下几种:

  1. 固定大小分块,这是最懒人的方法。很容易破坏语义完整性,造成上下文严重割裂

  2. 基于句子分块。首先使用句子分割算法(如.。?!等)将文本分割成独立的句子,然后将一个或多个连续的句子组合成一个chunk,使其大小接近目标范围。句子长度差异可能比较大,有的长,有的短,导致chunk不均匀。影响后续处理和检索稳定性

  3. 递归字符分块,这是langchain等框架中常用的一种更智能的策略,它会先预设一个“分隔符”优先级列表来递归分割文本,例如,优先尝试按 \n\n (段落) 分割,如果分割后的块仍然太大,再尝试按 \n (换行符) 分割,然后按空格分割,最后如果还太大,就按字符 "" 分割。目标是在保持较大语义块(如段落)的同时,确保最终块大小不超过限制。

  4. 基于文档分块: 这种策略利用文档本身的结构信息进行分割,例如 HTML 的 <div>, <p>, <li> 标签,Markdown 的标题 #, ##, 列表 -, *,或者 JSON/YAML 的层级结构。

向量化

检索系统召回率的上限,从数据被向量化的一刻,就已经被基本确定了, 后续的所有优化,都只是在逼近这个上限。

Embedding 的真正意义在于,它产生的向量不是随机数值的堆砌,而是对数据语义的数学编码。

  • 核心原则:在 Embedding 构建的向量空间中,语义上相似的对象,其对应的向量在空间中的距离会更近;而语义上不相关的对象,它们的向量距离会更远。

  • 利用数学公式计算问题向量和库中所有文本向量的距离, 常见的计算公式有:

    • 余弦相似度 (Cosine Similarity) :计算两个向量夹角的余弦值。值越接近 1,代表方向越一致,语义越相似。这是最常用的度量方式。
    • 点积 (Dot Product) :计算两个向量的乘积和。在向量归一化后,点积等价于余弦相似度。
    • 欧氏距离 (Euclidean Distance) :计算两个向量在空间中的直线距离。距离越小,语义越相似。

嵌入模型

嵌入模型做的事情是把输入文本变成向量——一串代表语义含义的浮点数。这个向量存在于高维空间,距离远近反映相似程度。

嵌入的特性:

嵌入具有不同特性,会影响其工作方式与适用场景:

稠密嵌入(Dense):稠密嵌入向量几乎每个位置都有值,每个数都携带信息,稠密向量紧凑高效,在较小空间承载丰富细节。便于快速相似度对比

稀疏嵌入(Sparse):稀疏嵌入的相当多位置=0,只有少数位置有值,0不代表信息。 稀疏表示 有助于突出最关键的特征,便于识别与众不同之处。

嵌入模型的关键参数:

  • 输入序列长度、输出维度、归一化方式、批处理能力

输入序列长度: 决定了模型一次能处理多少token,分块超过这个限制就得切小。 也暗示了最优分块大小。分块太长被截断了,上下文丢失会影响检索质量。

输出维度

每个嵌入向量里数值的个数,常见的有 384,768,1024,2048等。

高维嵌入能捕捉更丰富细腻的语义关系,检索准确率会提升,代价是存储成本高、向量搜索变慢。低维嵌入在大规模检索时更快更省资源,但语义深度和精度会打折扣。

归一化 和 相似性度量 有些模型本身已经归一化了,直接算余弦相似度很方便,也有模型输出未归一化的向量,索引前得手动归一化。不归一化的话,向量数据库可能把向量大小差异当成语义距离,导致相似度分数不准。

批处理能力

支持批量推理的模型能同时处理多个分块,对大规模RAG流程的吞吐量提升明显。

HNSW 算法

向量库里可能有几十万的数据,如果每次检索都要都所有向量算一遍距离(暴力搜索)太慢了,hnsw这些算法就是来解决问题的

https://zilliz.com.cn/blog/learn-hnswlib-graph-based-library-for-fast-anns

HNSW(Hierarchical Navigable Small World,分层可导航小世界)是==一种高效的近似最近邻(ANN)搜索算法,通过将高维向量构建为多层跳表式的“小世界”图,实现快速的近邻检索==。它在顶层通过粗略搜索快速跨越长距离,随着层级下降逐层细化查找,结合了NSW的导航特性与跳表结构,在保证高召回率的同时,具有对数级

  1. 核心原理

HNSW的核心在于分层可导航的小世界网络

  • 多层图结构: 类似于 跳表 ,底层(Layer 0)包含所有数据点,上层则是通过采样构建的子图。层数越高,节点越稀疏,长距离连接越多。
  • 小世界特性: 图中的节点与其邻居连接,且图中任意两点之间可以通过少量跳数(Steps)到达,类似于“六度分隔”理论。
  • 查询方式: 从顶层入口点开始,在当前层进行贪婪搜索,找到最近邻,然后将此节点作为下一层的入口点,层层向下查找,直至最底层。

 

  1. 核心算法流程
  • 索引构建(插入向量):
    1. 确定新元素的最大层数(使用指数衰减的概率分布)。
    2. 从顶层开始查找,直到找到该层中离插入节点最近的节点(寻找入口点)。
    3. 插入节点并在当前层及以下各层连接临近节点。
    4. 如果节点度数超过上限,进行邻居节点裁剪(Heuristic-based selection)。
  • 查询过程(搜索邻居):
    1. 从最高层的预定入口点开始。
    2. 在当前层采用贪婪策略移动到距离查询向量更近的节点,直至达到局部最优。
    3. 利用该局部最优节点作为下层的入口点。
    4. 最后在第0层(包含全部数据)精细搜索,返回个最近邻。

 

  1. HNSW的关键优势与参数
  • 高检索性能: 搜索复杂度近似为 O(logN),适用于高维大数据量场景。
  • 高召回率: 分层结构避免了在稠密图中陷入局部最小值的陷阱。
  • 关键配置参数:
    • M:每个节点的最大邻居数(层数越高,M越大)。M越大,召回率越高但索引构建越慢。
    • efConstruction:构建时控制近邻扫描区域大小。值越大,索引质量越好,构建时间越长。
    • efSearch:查询时控制进入层级的节点数。值越大,召回率越好,但搜索速度越慢
  1. 应用场景 HNSW被广泛应用于需要极高查询速度和高召回率的场景,如:
  • 推荐系统: 物品相似度召回(如电商、内容推荐)。
  • 向量数据库/RAG:  知识库检索(如Milvus, Faiss, Elasticsearch)
  • 以图搜图: 计算机视觉特征匹配。

 

HNSW因其优异的权衡能力,是目前工业界最流行、综合性能最强的近邻搜索算法之一。

召回

RAG的性能很大程度依赖召回阶段的质量,核心问题是如果检索的文档片段不包含回答问题所需要的信息,那么再强大的模型也会 “垃圾进,垃圾出”

初级的RAG系统召回也会遇到很多问题和瓶颈:

  • 词汇不匹配:例如用户问 “如何解决电脑无法启动”,而文档写的是 “PC开机故障排除指南”
  • 语义不匹配: 查询的意图和文档侧重点可能难以通过简单嵌入对齐
  • 信息分散: 答案所需要的信息可能分散在多个文档中,单一片段无法提供完整上下文
  • 块大小权衡: 小块检索精度高但是上下文不足。大块上下文丰富但是检索精度低,会引入噪声。

召回率 = 检索到的相关内容数量 ÷ 所有相关内容的总数量

向量召回

模型 : BGE-M3 (支持稠密和稀疏向量),产出1024维向量 检索逻辑: 用户Query向量化,在milivus中通过IVF索引(倒排 索引)快速计算余弦相似度。 输出:取出top100

BM25检索(关键字召回)

BM25是TF-IDF的改良版,对于一个查询词:

  • TF(词频): 这个词在文档里出现越多越相关,但出现太多会被惩罚
  • IDF(逆文档频率):这个词越罕见,区分度越高,越重要

RRF融合算法:把两路结果合并排序

Reciprocal Rank Fusion 倒数排名融合

精排阶段

为啥需要ReRank, 得先理解向量检索哪里不足:

RAG的第一阶段检索,通常是双塔Bi-Encoder架构的Embedding模型,他的工作方式是把Query和每个文档独立地编码成一个向量,然后通过余弦相似度或点积来计算相关性。

但是有个根本性的弱点,就是Query和Document在编码时完全看不到对方,他们各自被压缩成一个固定长度的向量。一篇几百字的文档的所有语义都塞在向量里,这种压缩不可避免地会丢失细粒度的语义信息。 特别是对于那些需要理解Query和Document之间词级别交互关系才能判断相关性的场景,Bi-Encoder经常判断不准

举个实际的例子。假设用户问"Python中怎么处理大几个文档片段:
一篇讲Python内存管理机制的文章(高度相关)、 一篇讲Java大文件处理的文章(主题相Lm似但语言不对)、 一篇讲Python基础语法的文章(语言对但主题偏了)。

Bi-Encoder算出来的向量相似度,本来就离得不远。但一个真正理解问题的人,一眼就能排。三篇可能都差不多,因为它们在语义空间中的向量出高下--这就是Rerank要做的事。

https://golangstar.cn/backend_series/llm_interview/rag_rerank.html

Pasted image 20260707142358.png

Rerank模型的输入到底长什么样?

  • 不只是Query+文本。输入格式为:[CLS] 用户问题 [SEP] 文档标题 [SEP] 文档正文前500字符 [SEP]
  • 为什么加标题?因为标题往往概括主旨,加入后能显著提升Rerank对文档核心主题的捕获能力。