跳转至

02_Rag

01 什么是 Rag? ⭐⭐⭐

RAG (Retrieval-Augmented Generation),就是检索增强生成。其实就是给大模型配一个"实时更新的外部知识库" ,在生成答案之前,先去外部知识库里检索相关内容,然后把检索结果和用户的问题一起交给 LLM,让它基于这些上下文来回答。

它解决的核心问题有两个:

一是知识冻结问题,LLM 的知识在训练完成之后就固定了,涉及到私有数据或者最新的信息它就答不上来。而通过 Rag,可以随时把私有文档、实时数据注入到上下文中,让模型无需重新训练就能掌握新知识。

二是幻觉问题,LLM 遇到不知道的东西会"一本正经地胡说八道"。RAG 通过提供检索到的真实上下文,让模型有据可依,并在 prompt 中约束"仅基于检索内容作答",大幅降低幻觉率。

02 什么是 Advanced RAG?

Advanced RAG 可以理解为 Naive RAG 的增强版本。它对 Rag 的在线和离线阶段进行优化,从而提升知识召回的准确率和覆盖率,减少“检索不到”和“检索结果不相关”等问题。

03 什么是 Mudular Rag

Modular RAG 就是把传统 RAG 系统拆成一系列相对独立的功能模块,每个模块各干各的事,由一个统一的编排器负责调度和路由,这样整体系统更加灵活、可插拔、好维护与拓展。

04 Rag 的标准流程 ⭐⭐⭐

离线阶段所做的其实就是把各种各样的知识文档加载为 document,然后对 document 进行切片、向量化,然后存储到向量数据库中。

在线阶段简单来说,其实就是根据用户的提问、去向量数据库查询相关的文档片段,然后将检索到的内容与用户原始提问一并提交给 LLM,让它去生成答案

05 稠密向量、稀疏向量是什么?

稠密向量(Dense Vector) 是由 embedding 模型(如 BGE、text-embedding-ada)将整段文本压缩成的固定维度的连续浮点数数组(如 768 维)。它的优势是语义理解能力强,比如"苹果"和"水果"在向量空间中距离很近,即使字面没有重叠也能召回。缺点是精确的关键词匹配差,比如搜索产品型号"RTX 4090"可能召回不到精确结果。

稀疏向量(Sparse Vector) 是基于词频统计的,维度等于词表大小,但绝大部分位置为 0,只有实际出现的词对应的位置有非零权重(如 BM25 / TF-I DF)。它的优势是精确关键词匹配强,搜"RTX 4090"就能精准命中包含这个词的文档。缺点是无法理解语义,"苹果"搜不到"水果"。

工业上的最佳实践是两者结合做混合检索:稠密向量负责语义召回,稀疏向量负责关键词召回。

06 向量检索和关键字检索的区别⭐⭐⭐

向量检索本质上就是通过稠密向量去做语义召回,而关键字检索则是通过稀疏向量去做精确得关键字匹配的,它们的区别主要是稠密向量和稀疏向量的区别。

07 TF-IDF 和 BM25算法是什么?简单介绍一下

TF-IDF 和 BM25 都是用于衡量文档查询之间相关性的算法,也是关键字检索的核心算法。

TF-IDF(词频-逆文档频率)

TF-IDF 由两部分组成。TF(Term Frequency)是词频,表示一个词在文档中出现的次数,词出现越多次说明当前关键字这个文档越相关。IDF(Inverse Document Frequency)是逆文档频率,表示一个词在整个文档集合中的稀有程度,如果一个词在很多文档中都出现(比如"的"、"是"),它的 IDF 值就低,说明区分能力弱。两者相乘就是 TF-IDF 分数:一个词在当前文档中出现频繁,但在其他文档中很少见,它的权重就高。

BM25(Best Matching 25)

BM25 是 TF-IDF 的改进版,也是目前 Elasticsearch 等搜索引擎默认的相关性打分算法。它在 TF-IDF 的基础上主要做了两个优化:

第一是对词频做了饱和处理。TF-IDF 中词频越高分数就无限增长,但 BM25 认为一个词出现 10 次和出现 100 次,相关性差异没那么大,所以用了一个饱和函数,词频达到一定阈值后分数增长就放缓了。

第二是考虑了文档长度。长文档天然包含更多词,TF-IDF 对长文档不公平。BM25 会根据文档长度做归一化,长文档的词频会被适当惩罚,短文档的词频会被适当奖励。

简单说,BM25 就是更聪明的 TF-IDF,解决了词频饱和和文档长度的问题,所以实际效果更好。

08 向量归一化是什么?有什么用 ⭐⭐

归一化就是把向量缩放到固定长度(通常是单位长度)。 具体来说,L2 归一化就是用向量除以它的模(欧几里得范数):

\[ v_normalized = v / ||v|| \]

它的作用主要有两个:

  1. 统一度量标准。
  2. 消除文本长度偏差。

09 相比直接微调 LLM,RAG 解决了什么问题?微调和 RAG 各自的优劣势是什么? ⭐⭐⭐

首先,微调做的主要是去提升模型在特定任务上的表现,或者对齐人类的偏好,这些是 Rag 做不到的,同时通过微调,也确实可以让 LLM 学习新知识。

但是在注入知识这方面,Rag 成本是更低的、也是更灵活的,微调训练完一轮,知识又截止了,有新的知识还得去微调,但是对于 Rag 来说,只需要更新向量数据库就行了,所以这方面 Rag 是更好的。

优劣势就从成本、各自擅长的事情去回答就可以。

10 为什么要对文档进行切割?⭐⭐⭐

对文档进行切片主要有两个原因:

一是 Embedding 模型有输入长度限制。常见的 BGE、Qwen3-Embedding 最大输入是 8192 tokens,一本几万字的长文档根本塞不进去。

二是把整篇长文档压缩成一个向量,那么这个向量的语义信息是比较广泛的,在后续召回的时候,会导致召回率与精确度差,同时就算召回了,也会存在大量的噪声。比如将一篇介绍 Redis 的文档整个压缩,

11 Rag 中文档的切分策略有哪些?⭐⭐⭐

这个问题从chunk的大小,每个chunk的语义完整性和实现难度去综合回答

首先,对于文档的切分,我自己大概的经验有下面这些:

第一点就是 chunk 的大小不要太大,也不要太小,大了的话语义被稀释,召回率和准确率都会下降,小了,上下文语义容易断裂,最后给 llm 的上下文不完整。

第二点就是 切分的时候,尽可能按照自然的边界去切分,比如标题啊,段落啊,一些短句的标点符号,实在没招了,用逗号切,再没办法了,就硬切,因为我们 chunk 的大小是有个上界的。然后为了进一步保证每个 chunk 的语义完整,还通常会设置一个 10-20% 的 chunk_overlap。

第三年就是注意特殊的格式,比如表格,代码块,这些内容应该用特殊的处理逻辑。

所以,基于上面的切分思想的描述,有这么一些常用的切分方法:固定大小切分、按照自然语义边界递归的切分、父子文档切分,甚至还可以让 llm 来决定每个 chunk 的大小和边界。

12 怎么避免语义被切割掉的问题?⭐⭐⭐

解决思路可以从两个方向入手:

一是"切得巧妙",核心是在切的时候尽量减少语义断裂:使用语义边界切割尽量在自然断点处切,优先段落、再句子,不在一句话中间断掉。

二是"切后补上下文",核心是在检索到 chunk 后,给它补上周围的环境:

句子窗口检索(Sentence Window Retrieval):切的时候只切单个句子,但检索到这个句子后,把前后各取 N 句作为窗口一起提交给 LLM,用小 chunk 精确匹配,用大窗口提供上下文。本质上就是父子文档切割的另类实现。

父子文档切割:小块用于检索匹配,召回时取回大块(父文档)作为上下文。

上下文召回(Contextual Retrieval):在原始 chunk 前面自动补充一段简短的背景说明,解释这个 chunk 在原文中的上下文关系。这种方案是在向量化之前就把chunk缺失的上下文信息补充进去了,并且 通过 Prompt Caching,可以大大的减少token开销,因为为同一个文档的所有chunk去生成上下文信息的时候,提示词中都会有相同的原始文档内容,而这部分就可以复用前缀缓存。

工程上的实践:重叠切割 + 语义边界切割做兜底,对高质量要求的场景用父子文档切割或上下文召回。

13 什么是 Embedding 模型?如何评估和选择一个 Embedding 模型?⭐⭐⭐

嵌入模型就是把一段文本压缩成固定维度向量空间的一个向量的模型,它能够捕捉这段文本在该向量空间的语义特征。并且语义越相近的文本对应的向量在该空间的距离越近。

Embedding 模型的选择可以参考排行榜,但是不能只看通用排行榜,而应该使用真实数据评估召回效果,同时结合嵌入模型擅长的语言、输入长度、向量维度、推理延迟和部署成本,选择综合收益最高的模型

14 排行榜和常见的嵌入模型?

排行榜方面,MTEB 是比较常见的通用评测基准,但只能作为初筛依据。模型方面,BGE 和 E5 生态成熟,BGE-M3 适合多语言和混合检索,Qwen3-Embedding 适合当前的多语言语义检索场景,OpenAI Embedding 接入简单但依赖云端服务。最终还是要结合自己的业务数据部署条件进行选择。

15 Embedding 有几种算法?⭐⭐⭐

Embedding 算法可以按三代演进的逻辑来讲:

第一代:静态词向量(Word2Vec、GloVe)。解决了"词变向量"的问题,但每个词只有一个固定的向量表示,处理不了多义词。比如"苹果"在"苹果很好吃"和"苹果手机"中是同一个向量,无法区分语义。而且它只编码到词级别,对句子级别的语义表达力有限。

第二代:上下文相关向量,典型代表就是 BERT,它本质上就是 transfomer 的编码器,预训练的任务是 MLM 和 NSP,然后用 cls token 来表示这个句子的语义。

第三代:典型代表是 SBERT、SimCSE、BGE 和 E5。这类模型通常采用 bi-encoder 架构,分别将 Query 和 Document 编码成向量,再通过余弦相似度或内积计算二者的相关性。

16 什么是向量数据库、你了解或者用过那些?⭐⭐⭐

向量数据库是专门用来存储和检索向量的数据库。

但这里要区分一个概念:普通数据库(如 MySQL)虽然也能存向量数据,但它的底层靠的是 B-tree 索引,只擅长精确匹配和范围查询,对高维向量的相似性计算几乎无能为力。向量数据库的核心能力是 ANN(近似最近邻搜索)——在百万级、千万级向量中,快速找到与查询向量最相似的那些,而不需要逐一计算所有向量的距离。

17 向量数据库中,常见的向量搜索方法:余弦相似度、欧式距离和曼哈顿距离分别是什么?有什么区别?🚀

无论是余弦相似度、欧式距离,还是曼哈顿距离,本质上都是去衡量 Query 向量和库中向量的相似度的,只不过计算方式不同。余弦相似度更加关注方向,而欧氏距离和曼哈顿距离则更加关注位置信息。

  • 余弦相似度
\[\text{Similarity} = \frac{\mathbf{A} \cdot \mathbf{B}}{\|\mathbf{A}\| \|\mathbf{B}\|}\]
  • 欧氏距离
\[L2 = \|\mathbf{A} - \mathbf{B}\|_2\]
  • 曼哈顿距离
\[L1 = \|\mathbf{A} - \mathbf{B}\|_1\]

常见向量数据库选型

选型主要从三个维度考虑:数据规模、部署方式、是否需要混合检索

Chroma 适合快速原型验证。它是轻量级的嵌入式向量库,安装简单、开箱即用,本地单机就能跑。但数据量大了之后性能跟不上,不建议上生产。

Qdrant 是中小到大规模生产环境的首选推荐。Rust 编写,性能出色,单机就能支撑百万级向量检索,支持混合检索(BM25 + 向量),而且自带向量量化能力,存储效率高。

Milvus 适合超大规模场景,支持分布式部署,数据量上亿的时候才考虑它。但部署运维复杂度也高,如果不是分布式刚需,没必要上 Milvus。

Pinecone 是 SaaS 服务,不想运维的可以直接用,但它不是开源的,数据存在别人的服务器上,合规要求高的项目不能用。

pgvector 是 PostgreSQL 的插件,如果项目本来就在用 PG,直接装插件就够了,不用再引入一个新的数据库,减少了运维负担。

核心索引算法

向量数据库的底层检索能力取决于索引算法。暴力搜索(Brute-Force)要逐个计算所有向量的距离,数据量大时不可用,所以必须用近似最近邻(ANN)算法。最主流的两种是 HNSWIVF

HNSW(Hierarchical Navigable Small World,分层可导航小世界)

本质是一个多层有向图,灵感来自跳表(Skip List)。

一个形象的例子:找路

想象你要在一个大城市里从 A 地走到 B 地,有三种走法:

第一种,挨家挨户问路,每家都问"离 B 地更近的路怎么走"。这就是暴力搜索,要逐个计算每个向量的距离,数据量大时根本不可用。

第二种,先上高速公路,在高速上快速开到目标城市附近,再下高速走省道,最后在小街小巷里精细找到目的地。这就是 HNSW 的核心思想——分层导航

IVF(Inverted File Index,倒排文件索引)

本质是聚类 + 分区,思路比 HNSW 简单得多。

建索引过程:先用 K-Means 把所有向量聚类成 N 个簇(cell),每个簇有一个中心向量(centroid)。然后把每个向量分配到离它最近的簇里。这样就相当于把整个向量空间切成了 N 块,每块包含一部分向量。

检索过程:给定查询向量,先算它离哪些簇中心最近,只搜索最近的 n 个簇(nprobe),在簇内部做暴力搜索。不用遍历所有向量,只搜一部分,所以比暴力搜索快很多。

关键参数

  • nlist:聚类簇的数量,一般取向量总数的 sqrt(N) 到 4*sqrt(N)
  • nprobe:检索时搜索几个簇,越大召回率越高但越慢

向量数据库的核心能力

除了基础的向量相似度检索,一个成熟的向量数据库还需要具备以下核心能力:

Metadata 过滤(混合检索 / Hybrid Search)

向量存入数据库时通常会附带一些业务属性,比如文章分类、作者、发布时间等。检索时可以先按这些属性过滤缩小范围,再在过滤后的结果里做向量相似度匹配。比如"只查技术类的文章",先过滤分类,再做语义搜索,这样结果更精准。

实时更新(增量更新 / Incremental Update)

实际业务中向量数据不是一次导入就固定不变的,文档会新增、修改、删除。向量数据库需要支持高效的单条或小批量增删操作,而不是每次都重建整个索引。这要求底层索引结构(比如 HNSW 的图、IVF 的聚类单元)能够动态调整,同时不影响正在进行的查询请求。

关键词检索融合(混合召回 / Hybrid Recall)

同时支持稠密向量和稀疏向量两种检索方式。稠密向量负责语义理解(搜"汽车"能匹配到"轿车"),稀疏向量负责精确的关键词匹配(搜具体型号能准确命中)。把两者的结果加权融合,既不会漏掉语义相关的结果,也不会放过精确匹配的关键词。

18 为什么要对用户Query进行重写?目的是什么?有哪些方式?

为什么要重写:

用户的问题直接拿去检索效果往往不好,主要有几个原因:一是口语化表达,比如"那个谁谁提的方案",直接检索什么都匹配不到;二是缺少上下文,多轮对话中用户可能会说"那它的性能怎么样",这个"它"指代的是上一轮提到的东西,但检索引擎不知道;三是问题太复杂,一个查询里包含多个意图,比如"分别介绍 LangChain,LangGraph,再说说各自适用的场景",这其实包含了两个孤立的查询需求。

Query Rewrite 的目的就是把用户原始的查询改写成更适合检索的形式,提升召回的准确率。

19 什么是多路召回?具体怎么做?⭐⭐⭐

多路召回就是用多种不同的检索策略并行检索,各自召回一批结果。因为单一检索策略有盲区。纯向量检索对精确关键词不敏感,纯关键词检索不懂语义。所以要多管齐下,尽量不漏掉任何可能相关的内容。


20 RAG 检索的优化策略 ⭐⭐⭐

第一层:索引层优化

检索时用小块(chunk)能提高召回准确率,但小块给大模型时上下文不足、语义容易断裂。所以实际做法是:检索用小块,交给大模型时用大块。具体有两种方案:

一是父子切片:文档先按大块切(父),再把每个父块细分成小块(子)。检索时用小块召回,召回后用对应的大块作为上下文给大模型。

二是小块 + 上下文补充:按小块正常切,但检索召回后,在交给大模型之前,把相邻块的前后文拼上去,补足语义。

第二层:查询优化

用户原始提问直接拿去检索效果往往不好,通过 Query Rewrite 把查询改写成更适合检索的形式,能显著提高召回精度。具体有四种方式:直接改写、多 Query 扩展、HyDE、Step-back(前面已详述)。

第三层:召回优化(多路召回)

单一检索策略有盲区,所以用多种检索策略并行检索:向量检索负责语义匹配,关键字检索负责精确匹配,必要时还可以加上网络检索扩展外部知识源。多路召回的结果通过 RRF(Reciprocal Rank Fusion--倒数排名融合)做初步融合排序,把各路的排名合并成一个统一排序。

第四层:重排序(Rerank)

多路召回的结果用 Cross-Encoder 的 Rerank 模型做精细打分,因为 Cross-Encoder 会把 query 和 document 一起输入模型做交叉注意力计算,比向量检索的 Bi-Encoder 双塔打分准确得多。最后按 Rerank 分数截取最相关的几条作为最终上下文。

21 为什么要进行重排序?🚀🚀

向量检索(Bi-Encoder)计算的是余弦相似度,属于“快而不精”;Rerank 模型(Cross-Encoder)会同时输入问题和文档进行计算,最后得出一个相关性分数,计算慢但是更加准确。因此,当我们通过向量检索从海量的文档中找到了一些候选文档,便可以通过重排序更加精确的从这些文档中找出最相关的文档。

22 RRF 算法是什么? ⭐⭐⭐

RRF 是倒数排名融合算法,用来合并多路检索结果。它不依赖不同召回方式的原始分数,因为不同模型的分数通常不具备可比性,而是根据文档在各路结果中的排名计算综合分。

核心思想:一个文档如果在多个检索方法中都排在靠前的位置,那么它应该在最终的融合排名中获得更高的权重。

RRF(d) = Σ 1 / (k + rank(d))

k 就是一个平滑系数:它的作用是缩小第一名和后面名次的得分差距,确保即使某路召回的文档很少,排在第一名的得分也不会过分突出,从而让不同数量级的召回结果能在同一水平线上公平竞争。

23 什么是 HYDE?它存在什么问题?

HyDE 就是先通过 llm,为当前 query 生成一个假设性的答案,之后将 query 和 答案拼接起来,向量化后去召回相关文档。

HyDE 可以缓解用户问题过短、表达不完整,导致问题向量文档向量语义差距较大的问题。

存在的问题

  • 假答案质量依赖 LLM 能力,易引入噪声和幻觉;专业领域需要微调后才能用
  • 额外调用 LLM,增加延迟和成本开销
  • 对事实性问题可能适得其反(LLM 生成的错误信息会误导检索方向)

24 如何评价 RAG 项目效果的好坏呢? ⭐⭐⭐

分三层,分别为检索层、生成层和上线之后,其中,检索层关注的指标主要有:Recall@K、MRR、Precision@K,生成层主要是 faithfulness、answer_correctness、answer Relevancy、context_precision、context_recall,

  • Faithfulness答案是否能够被检索到的上下文支撑,分数低可能存在幻觉。
  • Answer Relevancy答案是否围绕用户问题,分数低说明回答可能偏题。
  • Context Precision检索到的上下文与当前用户问题以及参考答案是否相关。
  • Context Recall参考答案中有多少相关信息被召回了,分数低说明召回覆盖不足。
  • Answer Correctness最终答案参考答案的一致程度,用来衡量整体回答效果。

25 RAG 有哪些关键评估能力?⭐⭐

除了上述量化指标,还需要验证一些鲁棒性能力,这些通常需要构造特殊测试用例:

能力 说明
抗噪声能力 检索结果混入无关文档时,LLM 能否不被干扰, still 给出正确回答
负向拒绝能力 检索不到任何相关内容时,模型能否直接说"不知道",而不是瞎编
信息整合能力 答案分散在多个文档中,模型能否把它们整合起来给出完整回答
反事实鲁棒性 检索结果中存在错误信息,模型能否识别并忽略,不被带偏

26 RAG 知识库文档更新后,向量库如何动态更新呢? ⭐⭐

首先,向量库的更新不想数据库那么简单,只需要直接更新那一条需要更新的数据即可,这是因为在向量库中,一个文档可能对应几十个,甚至上百个 chunk,也就对应向量库的上百条记录,当文档内容发生改变后,这个文档对应的 chunk 的数量、边界、内容都可能发生变化,所以最好的更新方式就是根据文档 id 将之前的 chunk 都删除掉,然后对跟新后的文档进行切分,向量化和入库。

至于如何能够发现文档内容变更了,一种简单有效的方法就是给每个文档按照内容算一个 hash 值,然后定期的去检查当前文档内容的 hash 值是否等于这个值,如果不同,就说明文档内容被变更了,那么就去触发更新流程(这里选择的方案很多,最简单的就是开个子线程去异步处理,更规范一点,就是通过 rmq 实现异步 + 解耦)

27 Ragas 可以用来评估哪些指标?

我们通过 Ragas 主要评测了 faithfulness、answer_correctness、Answer Relevancy、context_precision、context_recall 这五个指标。

  • Faithfulness:答案是否能够被检索到的上下文支撑,分数低可能存在幻觉。
  • Answer Relevancy:答案是否围绕用户问题,分数低说明回答可能偏题。
  • Context Precision:召回的上下文是否足够相关,分数低说明噪声较多。
  • Context Recall:参考答案中的关键信息是否被召回,分数低说明召回覆盖不足。
  • Answer Correctness:最终答案与参考答案的一致程度,用来衡量整体回答效果。

注意,这些指标都不需要 query 对应的标准 chunk!