RAG 分块Chunking 重排序Remark 双塔架构 Bi-Encoder Cross-Encoder ANN算法

什么是RAG

RAG的全称是Retrieval-Augmented Generation,直译为"检索增强生成"。

它的工作流程就像一次"开卷考试":

  1. 检索(Retrieval):当用户提出一个问题时,系统会先去一个外部的知识库(比如公司的内部文档、最新的法律条文、产品手册等)中,通过向量搜索等方式,快速找到与问题最相关的几个内容片段。

  2. 增强(Augmented):将检索到的这些内容片段,连同用户的原始问题,一起整合成一个更丰富的提示词(Prompt)。

  3. 生成(Generation):将这个整合后的提示词交给大模型,让大模型基于这些提供的"参考资料"来组织语言,生成最终的回答。

RAG技术巧妙地避开了重新训练大模型的巨大成本,让模型能够在不更新自身参数的情况下,"临时抱佛脚"去获取最新、最准确的外部知识。

为什么需要RAG

为什么不是将"公司的规章制度"训练到大模型中呢?版本跟新再继续训练呢,这样不是直接让大模型自己就可以实现问答了。

  1. 训练大模型成本过高:包括:算力,人工,时间。费用在亿的级别。

  2. 多版本知识冲突 :规章制度会更新,训练的知识会在大模型冲突

所以最终的形态是一个通用大模型+实时更新的外部数据库结合最划算。

RAG质量核心

大模型首先是基础和保证,一个垃圾大模型及时给全部的数据作为提示词也输出质量不高的场景排除。这种情况下,直接优化大模型去。

其次就是输入提示词的质量。给大模型的参考资料的质量,包括:跟问题的相关👌系,是否全面。最终可以归结为一句行业共识:"垃圾进,垃圾出"(Garbage In, Garbage Out)

提高提示词的质量在于对参考资料的存储和检索怎么做,尽量将最全面的相关信息提取出来,并组装为提示词。

RAG 工作流程概述

文档摄取和分块 拿到大文档(PDF,Word,HTML,Text )→ 切分成小块 → 算嵌入向量 → 扔进向量数据库。

查询检索 用户提问 → 把问题转成向量 → 用余弦相似度找出最相关的 top-k 个块

上下文增强 把检索到的块和元数据塞进 LLM 的 prompt 里,通常会用模板做些处理和筛选。

答案生成 LLM 根据检索内容加上自己的知识生成回答。

生成器只能看到你喂给它的东西,所以检索质量直接决定了最终效果。块切得不好或者根本检索不到相关内容,再强的 LLM 也救不回来。这也是为什么业内普遍认为 RAG 大概 70% 靠检索,30% 靠生成。

综述:对于大文件(PDF,Word,HTML,Text) 首先需要进行预处理,即:分块Chunking 。 然后存入向量库检索 使用向量比对的方法查询出小的分片,最后根据分片扩展和封装prompt提示词,并发送给LLM大模型生成答案。

RAG和分块

RAG处理程涉及:分块、存储、搜索,组装 几个环节。只要分块方式不同,后边的几个环节处理模型也不同。所以大家喜欢用分块的方式为依据对RAG方案进行分类,介绍几种常见的分块Chunking方案。

分别是:固定分块、递归分块、语义分块、结构化分块、延迟分块。

每种方法在优化上下文理解和检索准确性上都有各自的价值。用对了方法,检索质量能提升一大截,幻觉问题也会少很多。

在讲具体技术之前,得先明白为什么分块这么重要:

嵌入模型和 LLM 都有上下文长度限制 ,没法直接塞整个文档进去;块必须语义完整。要是在句子中间或者一个完整意思的中间切断,嵌入向量就会带噪声甚至产生误导;块太大的话,相关的细节信息容易被淹没;反过来块太小或者重叠太多,就会存大量冗余内容,浪费算力和存储。

下面按从简单到复杂的顺序,聊聊五种主流的分块方法。

固定分块

最直接的做法:按固定长度(token、词或字符)切文本,块与块之间留一些重叠部分。

这是 RAG 项目的常见起点,特别适合文档结构未知或者内容比较单调的场景(比如日志、纯文本)。算是一个不错的 baseline。

递归分块

先按高层级的边界拆(段落或章节),如果某个块还是超长了,就继续往下拆(比如按句子),直到所有块都符合大小限制。

适合有一定结构的文档(带段落、章节的那种),既想保持语义边界完整,又要控制块的大小。

好处是能尽量保留段落这种逻辑单元,不会在奇怪的地方切断,而且能根据内容自动调整块的大小。

语义分块

根据语义变化来切分文本。用句子级别的嵌入来判断哪里该结束一个块,哪里该开始下一个。相邻句子如果语义相近就放一起,相似度明显下降了就切开。

在检索精度要求高的场景特别有用(法律文本、科研论文、技术支持文档之类的)。不过计算嵌入和相似度有成本,而且相似度阈值的设定需要反复调试。

基于结构的分块

利用文档本身的结构------标题、子标题、HTML 标签、表格、列表这些------作为天然的分块边界。比如每个章节或标题自成一块(必要时也可以继续递归拆分)。最适合处理 HTML 页面、技术文档、维基类内容,或者任何有明确语义标记的材料。

从实际经验来看,这种策略效果最好,尤其是配合递归分块一起用的时候。

但前提是得先解析文档格式,而且对于特别长的章节,可能还是会超出 token 限制,这时候就需要混合使用递归拆分了。

实现要点:

  • 用专门的库解析 HTML / Markdown / PDF 结构
  • 把章节标题(<h1>、<h2> 之类的)当作块的根节点
  • 某个章节太长的话,退回到递归拆分
  • 表格和图片可以单独成块,或者做一下摘要处理

延迟分块(也叫动态分块或查询时分块)

什么是延迟分块:把文档拆分这件事推迟到查询时再做

不是一开始就把所有东西切碎,而是存储比较大的片段甚至整份文档。等查询来了,只对相关部分做动态拆分或筛选。核心思路是在做嵌入的时候保留完整上下文,只在确实需要的时候才切分。

Weaviate 的说法是把这叫做"反转传统的嵌入和分块顺序"。

先用支持长上下文的模型对整个文档(或大段落)做嵌入。然后基于 token 范围或边界标记,池化生成块级别的嵌入。

具体流程

  1. 在索引里存储大段落或完整文档。
  2. 查询来了,先检索出 1-2 个最相关的大段。
  3. 在这些段落内部,围绕匹配查询的部分动态切分(可以用语义或重叠的方式)成更细的块。
  4. 对这些细块做过滤或排序,最后喂给生成器。

有点像编程里的延迟绑定,等有了更多上下文信息再做决定。

适用场景

  • 大型文档集(技术报告、长文)里,跨段落的上下文关联很重要的情况。
  • 文档经常更新的系统,不用每次都完整重新分块,能省不少时间。
  • 高精度要求或高风险的应用(法律、医疗、监管类),代词或引用理解错了代价很大的那种。

听起来挺理想,但代价也摆在那。嵌入整个文档或大段内容,计算开销相当大,而且需要支持长 token 的模型。查询时的计算成本也会增加,可能影响响应速度。

分块总结

分块这件事看着不起眼,但实际上直接决定了 RAG 系统的上限。固定分块简单粗暴适合快速验证;递归分块在保持语义完整性上更有优势;语义分块精度高但成本也高;结构化分块对特定类型文档效果最好;延迟分块则是在计算资源充足时的高级玩法。

没有哪种方法是银弹。实际项目里往往需要根据文档类型、查询特点、资源限制来组合使用。比如技术文档用结构化分块打底,长篇报告考虑延迟分块,日常问答系统可能固定或递归分块就够了。

组装(重排序(Rerank))

什么是重排序

上面主要按照拆分Chunking角度来分,现在关注一个组装问题:重排序(Rerank) 。从向量数据库中查询中TOP K数据后,怎么怎么组装体。这里有个潜藏的问题:向量检索的相似度计算是"粗筛",它能在几百万条数据里快速圈定一个候选范围,但这个范围里的块,排序未必准。

比如:根据相似度查询出10条,语义上是两组数据。那么就得将5 5 为一组语义上一致的放在一起,而不是相似度倒序 直接拼接起来。这样可能会导致语义不完整。

天下没有免费的午餐。Rerank模型的推理速度比向量检索慢一到两个数量级(因为要逐对计算分析语义上的相似性),所以实际工程中常见两种策略:

  1. 级联式:向量库先召回Top-50,Rerank只处理这50条,再精筛出Top-5。

  2. 分阶段阈值:相似度得分特别高的块直接跳过Rerank,只有得分处于模糊地带的块才走精排流程,算是在质量和延迟之间做个折中。

向量检索负责"找得快"(粗排),Rerank负责"找得准"(精排)

为什么需要重排序

说到这里得提一下向量数据库的搜索算法了,为什么不能再向量数据库直接解决问题,还得查询出来后再来一次排序。

答案是:检索速度准确度的平衡

举个实际的例子。假设用户问**"Python 中怎么处理大文件的内存溢出问题"**

向量检索可能同时召回了这么几个文档片段:

  1. Python内存管理机制的文章(高度相关)

  2. Java 大文件处理的文章(主题相似但语言不对)

  3. Python基础语法的文章(语言对但主题偏了)

  4. Java怎么处理堆外内存溢出问题 (语言不对)

如果再Milvus这样的数据库就进行这样的语义判断,花费时间不可想象。所以先,根据关键字(可以这么理解)匹配的方式,类似ES进行查询,实际是进行向量化处理后的余弦算法比对。这样速度快,但问题是出来的内容可能语义上跟问题没关系。

如果不加判断全部给放在Prompt中则有:浪费TOKEN,提示词中垃圾信息多等问题。

说白了,Rerank 就是对初检索拿回来的 Top-K 文档做一次精细化的二次排序。排序的依据不再是向量,而是语义分析了。使用到的工具是模型。

这一步要的是召回率 ------宁可多捞一些不太相关的,也不能漏掉真正相关的 。然后 Rerank 作为第二道精筛,用更强大的模型对这个小规模候选集逐一做精细评估,重新打分排序,把真正最相关的文档排到前面。这一步要的是精确度------在候选集里挑出最好的那几条。最终排在最前面的 Top-3 或 Top-5 文档,才会被拼接到 Prompt 里给 LLM 生成答案。

双塔(Bi-Encoder)架构

为了兼顾海选,检索速度的方案。

Query 和 Document 在编码时完全看不到对方。它们各自被压缩成一个固定长度的向量(通常是 768 维或 1024 维),一篇几百字的文档的所有语义信息全部被塞进这一个向量里,这种压缩不可避免地会丢失细粒度的语义信息,特别是对于那些需要理解 Query 和 Document 之间词级别交互关系才能判断相关性的场景。

Cross-Encoder

Cross-Encoder 重排是目前效果最好、也最主流的方案。Cross-Encoder 和 Bi-Encoder 最本质的区别在于:它不是把 Query 和 Document 分开编码,而是把两者拼接成一个序列一起送进 Transformer 模型,让 Query 中的每个 token 和 Document 中的每个 token 之间做全注意力交互(Full Attention),最终输出一个标量的相关性分数。这种深度交互让模型能够捕捉到非常细粒度的语义匹配关系------比如"Python 内存溢出"和"Python 内存管理"的区别,"大文件处理"和"文件基本操作"的区别,这些 Bi-Encoder 容易混淆的 case,Cross-Encoder 通常都能区分开。

代价就是慢。因为每一对 (Query, Document) 都要独立过一遍完整的 Transformer 前向推理,无法像 Bi-Encoder 那样预计算文档向量。如果候选集有 50 条文档,就要做 50 次推理。所以 Cross-Encoder 绝对不能用来做全库检索,只适合对一个小规模候选集做重排------这也是为什么 Rerank 必须放在初检索之后。

Remark怎么实现

1. 决策是候选集大小 。初检索召回多少条交给 Rerank?这是一个召回率和延迟之间的权衡。召回太少(比如只拿 Top-10),可能真正相关的文档压根没进候选集,Rerank 再精准也无能为力------巧妇难为无米之炊召回太多 (比如 Top-200),Cross-Encoder 要做 200 次推理,延迟直接飙升。工程经验是 Top-20 到 Top-50 是一个比较好的平衡点,具体数字取决于你的文档库特征和延迟预算。一个实用的调优方法是:先在离线评估集上跑一组实验,观察 Top-K 召回率随 K 增大的边际收益,找到那个收益开始变得平缓的拐点。

**2. 决策是模型选择。**目前主流的 Rerank 模型可以大致分为三个梯队。

第一梯队是商业 API:Cohere Rerank(开箱即用,效果稳定)和各云厂商提供的重排服务。

第二梯队是开源的 Cross-Encoder 模型:BGE-Reranker 系列(智源出品,中英文效果都很好,有 base/large/v2 多个版本可选)、bce-reranker(网易有道出品,在中文场景表现突出)、Jina Reranker。

第三梯队是通用的 sentence-transformers Cross-Encoder(如 cross-encoder/ms-marco-MiniLM-L-12-v2),体积小速度快但精度稍逊。选型时要综合考虑语言覆盖(如果业务涉及中文,BGE 和 bce 是优先选择)、模型大小和推理延迟、以及是否能本地部署。

**3. 决策是集成方式。**好消息是主流的 RAG 框架都已经内置了 Rerank 的支持。

LlamaIndex 提供了 SentenceTransformerRerank、CohereRerank 等开箱即用的 NodePostprocessor;

LangChain 也有 CohereRerank、CrossEncoderReranker 等组件可以直接插入检索链。

如果用的是自研的 RAG 框架,集成也不复杂------本质上就是在检索之后、生成之前加一个函数调用:输入是 (query, documents) 列表,输出是重新排序后的 documents 列表。

向量数据库搜索算法

最后补充一下在Bi-Encoder架构下即使挨个比对向量相似性也会比较慢,需要根据**近似最近邻(ANN)**算法进行搜索。

在RAG系统里,向量数据库里可能存了几百万甚至上亿 个文档块。每个块都是一个几百维的向量。用户提问 → 转成一个向量 → 问题变成了:**在这几百万个向量里,找出离我最近的那Top-K个。**如果老老实实一个一个比(精确搜索),计算量是几百万 × 几百维,一次提问可能耗时几秒钟甚至更久,用户早就关页面走人了。

所以向量数据库用的都是ANN算法------它不是把整个数据库翻个底朝天,而是事先给向量建好"索引"(类似书的目录),检索时顺着索引快速定位到最有可能的几个区域,只在这些区域里做精确比较。

几种常见的ANN算法(简单带过),如果你去翻向量数据库的文档,会看到这些名词:

算法 一句话解释
HNSW(分层可导航小世界图) 类似社交网络里的人脉链------你想找某个陌生人,不需要认识全城的人,只需要通过几个中间朋友就能快速找到。当前最流行,效果最好。
IVF(倒排文件索引) 把向量空间划分成很多"桶",检索时只查离问题最近的几个桶,别的桶直接跳过。
PQ(乘积量化) 把高维向量压缩成更小的"指纹",比较时比的是压缩后的版本,省内存、速度快。

实际应用中,向量数据库(如Milvus、Qdrant、Pinecone)通常会把这些算法组合使用 ,比如 IVF + HNSWHNSW + PQ,在速度、精度、内存之间找平衡。

查询侧的优化

上面主要讲了**"怎么存和怎么搜"** ,但其实搜索的起点------用户的问题,也有很大的优化空间。

  • HyDE (Hypothetical Document Embeddings) :这是一种很巧妙的技术。它不是直接用用户的问题去搜,而是先用大模型根据问题"假想"出一份答案,再用这份答案去向量库里找相似的文档。这样能有效缩小问题和文档在表达方式上的"语义差距"。

  • 多路召回与混合检索 (Hybrid Search) :只依赖向量检索有时候会漏掉一些包含精确关键词的文档。更稳健的做法是**"多路召回"** :同时用向量检索(找语义相似)和基于关键词的检索(如BM25算法,找精确匹配),再把两边的结果合并起来。工程上通常用RRF (Reciprocal Rank Fusion) 算法来合并排序,能同时兼顾语义理解和精确匹配。

高级RAG模式(介绍)

上述描述的是最经典的"基础RAG"流程(检索一次,生成一次)。但实际上,RAG还有很多进阶玩法,能解决更复杂的问题。

  • Agentic RAG :让大模型变成一个"智能代理",它自己决定要不要搜、什么时候搜、以及搜什么。比如,它能先自己思考,发现知识不足时再去调用工具检索,甚至进行多轮搜索来获取完整信息。

  • 多跳检索 (Multi-hop Retrieval):专门用来回答需要多步推理的复杂问题。比如,回答"A公司的CEO毕业于哪所大学?"这个问题,需要先搜出"A公司的CEO是谁",再搜出"这个CEO的教育背景"。

  • 迭代式RAG:模型在生成答案的过程中,如果发现信息不够,可以再次触发检索,把新获取的知识补充进去,让答案越来越完善。

RAG系统评估

对工程落地来说恰恰至关重要:怎么判断你的RAG系统做得好不好?

  • 评估框架 :目前最流行的是RAGAS (RAG Assessment) 这个评估框架。它不再只依赖人工打分,而是用大模型从多个维度自动评估RAG系统的表现。

  • 核心指标:RAGAS定义了几个关键指标,正好对应RAG的核心环节:

    • Context Relevancy 上下文相关性:检索回来的信息,有多少是真正有用的?废话多不多?

    • Faithfulness 忠实度:模型生成的答案,是不是严格基于检索回来的资料?有没有胡说八道(幻觉)?

    • Answer Relevancy 答案相关性:最终的答案,有没有答非所问?和用户的问题相关吗?

评价体系 RAGAS 翻译三个指标内容**:**

忠实度指的是答案应基于给定上下文。这对于避免幻觉以及确保检索到的上下文可以作为生成答案的依据非常重要。实际上,RAG系统通常用于生成文本相对于其依据来源的事实一致性至关重要的应用中,例如在法律等知识不断演变的领域。

答案相关性指的是生成的答案应恰当地解决所提供的问题。最后,

上下文相关性指的是检索到的上下文应聚焦,包含尽可能少的不相关信息。考虑到将长上下文段落输入LLM的相关成本,这一点很重要。此外,当上下文段落过长时,LLM在利用该上下文方面通常效果较差,尤其是对于位于上下文段落中间的信息

参考博客

RAG检索质量差?这5种分块策略帮你解决70%的问题

RAG重排序(Rerank)入门基础教程(非常详细),收藏这一篇就够了!_重排模型rerank-CSDN博客

RAGAS: Automated Evaluation of Retrieval Augmented Generation