☰
RAG落地实战:分块、召回、重排的6个优化结论
2026/10/1 23:49:18 网站建设 项目流程

RAG项目真正落地,从来不是把LangChain或LlamaIndex的demo跑起来就算完事。我在过去一年多里做过几个知识问答类RAG项目,从分块、召回到重排,每一条链路都踩过不少坑,也沉淀了一些能直接复用的结论。这篇文章就把围绕“分块、召回、重排”三个核心环节的6个实战结论整理出来,适合已经跑通基础demo、但觉得准确率差强人意、不知道下一步往哪优化的团队。这里没有花哨的概念包装,只有我踩出来的取舍经验,希望能帮你少绕几个弯。

1. 别急着调Prompt,三个环节决定RAG效果天花板

1.1 分块、召回、重排为什么是地基

一个典型的RAG流程是:把文档切成若干块(分块),为每个块生成向量并建索引;用户提问时从索引里找回相关内容(召回),再把召回结果按相关性排序、挑选出最佳片段(重排);最终把精选内容交给LLM生成答案。很多团队把大量精力放在调Prompt上,觉得最后一步写得好,答案就漂亮。但我在项目里见过太多“Prompt怎么调都没用”的case,追根溯源,往往是分块没切好,或者召回环节根本没把该有的上下文找回来。我的体感是:RAG效果的上限在分块,下限在召回,重排决定你能不能把召回到的好东西稳定发挥出来。这三个环节彼此牵制,任何一个掉链子,后面的优化都事倍功半。

这里有个容易被忽视的点:分块和召回其实在LLM介入前就已经决定了答案的候选范围。LLM再聪明,也只能在你喂给它的上下文里做推理。所以RAG质量和模型聪明程度的关系,远没有很多人想得那么大。与其花时间研究更复杂的prompt模板,不如先把分块、召回、重排当成一个整体系统来看。

1.2 六条实战结论速览

先把结论亮出来,方便你有个整体框架,后续每一节都会展开细节和依据。

序号结论一句话概括影响环节
1分块大小必须结合模型上下文、文档结构动态决定,默认值只能起步不能上线分块
2重叠能救回跨块信息,但比例失控会让索引膨胀、召回噪声变大分块
3纯向量检索对专有名词和长尾不友好,混合召回是生产环境标配召回
4Top-K不是越大越好,后面没有好的重排,召回再多也是负担召回/重排
5cross-encoder重排效果好,但必须做延迟与成本的取舍重排
6用Hit Rate和MRR评估,而不是只看单次问答的对错评估

这6条结论的先后顺序,基本也就是我调优时从头到尾的排查顺序。先检查数据进索引之前的分块,再检查召回策略,最后才考虑要不要加重排,否则很容易出现“重排模型换了四五个,问题却出在分块把信息切断了”的情况。我自己就干过这种蠢事,重排模型换了一圈没效果,最后发现是分块把表格从中间劈开了。

1.3 为什么我只聊这三个环节

有人可能会问:RAG不是还包括query改写、路由、多跳检索这些吗?当然,进阶玩法很多,比如agentic RAG、GraphRAG这些概念现在也很热。但我的观点是:如果你的分块、召回、重排这三层地基没打好,上再多复杂机制都是往沙地上盖楼。很多被包装成“智能体RAG”的失败项目,拆开看问题依然是“召回结果不对”或“上下文信息缺失”。所以这篇文章刻意收窄范围,只聊三个基础但决定成败的环节。

2. 分块:很多人第一步就把知识库切坏了

2.1 分块大小别拍脑袋,要从上下文窗口倒推

我第一次做RAG时,分块参数直接抄开源项目默认值,大概是text-embedding-ada-002时代比较流行的800 token,结果业务文档里的产品参数表被切得稀碎。用户问“这个型号的防护等级是IP65吗”,系统死活答不出来。后来才意识到,分块大小不能只盯embedding模型的输入限制,也不能只盯大模型窗口,而是要从你想要的“最终答案形态”倒推。

具体算一笔账。假设你用的是128K上下文窗口的模型,看起来块切大点没事,但你要考虑一次问答会往Prompt里塞几个块。如果答案需要来自5个不同文档片段,每块1500 token,光上下文就占了7500 token;如果再把历史对话加进去,总量其实很容易接近限制。反过来,如果把块切到100 token,定位精度是高了,但语义完整性被破坏,一段连贯的技术描述被拦腰截断,召回回来的只有半句话,模型再强也没法补全上下文。所以分块大小本质上是一个“定位精度”和“语义完整性”之间的权衡。

我现在的经验是:通用文本初始分块放在512到1024 token之间,但一定要根据文档类型调整。产品手册这类信息密度高的工具类文本,每个小节、每个参数表最好单独成块;技术博客、长篇叙事,块可以适当放大,用段落边界做切分,保留前后文逻辑。之前踩过的坑就是“一刀切512”,导致代码示例和说明文字被拆开,召回倒是命中率高,答案却总是东拼西凑。

2.2 重叠和分隔符:两个最容易被忽略的参数

分块里的overlap(重叠)参数,很多人直接设为0或者随便填个数字。但我实测下来,重叠能明显提升召回质量,尤其是当一个知识点恰好跨在分块边界上的时候。文档里的“安装步骤”经常写到一半换页,标题在上一块,关键步骤在下一块,没有重叠就只能靠运气。比较实用的起始值是块大小的10%到20%,比如512 token的块,重叠给64到128 token,大部分跨块信息都能被兜住。

但重叠也不是越高越好。我试过把重叠开到30%以上,索引体积明显膨胀,embedding的存储成本变高,而且召回时经常返回好几块几乎一样的文本,占用了大量上下文额度,答案反而被冗余信息带偏。所以重叠解决的是“边界断裂”,不是“内容冗余”,这一点分清楚,参数就容易定。另外,重叠还会直接影响后续重排的候选质量,如果候选列表里前几名全是高度相似的重叠块,重排模型也很难从里面挑出更优答案。

另一个容易被忽略的是分隔符。固定按token数切是最省事的做法,但在很多场景下效果并不好。更好的方案是先按结构化边界切:Markdown的标题、代码块、表格、换行明显的段落,都可以作为优先分隔符。我在用LangChain的RecursiveCharacterTextSplitter时,一般会把separators配置成“标题->段落->句子->字符”的优先级顺序,这样能最大程度保留语义边界。

from langchain.text_splitter import RecursiveCharacterTextSplitter text_splitter = RecursiveCharacterTextSplitter( chunk_size=512, chunk_overlap=64, separators=["\n## ", "\n### ", "\n\n", "\n", ". ", " "], )

这个配置看着简单,但实际跑起来,比默认分隔符的效果稳定很多。尤其是Markdown文档,标题本身就是天然的语义边界,先用标题切,再往下一级细化,比直接按字符数硬切合理得多。

2.3 结构感知分块好在哪,语义分块能不能用

结构感知分块,说白了就是让切块尊重文档本身的骨架。比如一页公司内部运维手册,一级标题是“故障处理”,二级标题是“服务重启”,底下每个小节是完整操作步骤。如果按固定token切,很容易把步骤2和步骤3切开。用结构感知分块之后,每个二级标题下的内容作为一个独立块,检索时用户问“服务重启要几步”,直接命中完整步骤块,回答质量自然好。

语义分块是另一条路:不对文本做固定切分,而是用embedding去计算相邻句子之间的语义距离,距离变化大的地方当作边界。这种方式的优点是块边界更符合人的阅读直觉,缺点也很明显:计算成本高、需要先跑一次embedding模型、实时性差,而且语义距离阈值并不好调,线上稳定性不如结构感知分块。我在实际项目里,语义分块主要用在说明书、合同这类没有明确格式、但每段主题分明的长文本上;凡是有明确标题层级或表格结构的文档,直接用结构感知法就够了。

做知识库清洗时,我还会把页眉页脚、模板字段先去干净,不然这些噪声会被当成正文一起切块,检索时疯狂命中一些没价值的块。清不清洗,同一个分块参数跑出来的效果能差好几个点,这个后面在问题排查小节里会详细说。

3. 召回:你的搜索不只是向量检索

3.1 纯向量检索为什么覆盖不了专有名词和长尾

纯向量检索的做法是把用户query和每个候选块都embedding成向量,然后算余弦相似度。听起来自然,但实际跑起来会发现一个很核心的问题:稠密向量擅长语义相似,对精确匹配一点都不敏感。比如电子行业文档里出现“CADENCE位号重排”,这是一种很具体的EDA工具操作,embedding很容易把它泛化成“layout中元件标号调整顺序”,当你问“cadence位号重排的约束规则”时,向量检索反而会把别的相似说法拉进来,精确的原文片段却没进候选。

我再举个例子:之前做设备维修知识库,文档里全是型号编码,像“S7-1200”这种词,向量检索经常把S7-1200和S7-1500混在一起,因为它们语义太接近了。而用户的问题是“S7-1200的CPU日志怎么导”,模型返回一堆S7-1500相关的内容,看似相关,实际全错。纯向量检索解决不了长尾精确匹配,尤其是产品型号、工单号、报错码这类字符串,要解决,只能把稀疏检索加回来。

3.2 混合召回怎么做,权重怎么调

混合召回就是用BM25为代表的稀疏检索,配合向量检索一起找候选。BM25看的是词频和文档长度,能精确匹配“S7-1200”这类关键词;向量检索看语义,能召回“怎么导出CPU日志”这种换了说法的表达。两者互补性非常强,我用下来的效果是:单独向量检索Hit Rate可能只有60%到70%,加了BM25之后普遍能到80%以上。

工程上怎么落地?如果用的是Elasticsearch或OpenSearch,可以直接配好hybrid query,把BM25得分和vector得分做加权求和;用Qdrant、Milvus这类向量库,可以配合BM25插件,或者单独上一个ES做精排。之前用LangChain的EnsembleRetriever最省事,把BM25Retriever和VectorStoreRetriever拼在一起,通过weights参数分配权重。

from langchain.retrievers import EnsembleRetriever from langchain_community.retrievers import BM25Retriever from langchain.vectorstores import FAISS bm25_retriever = BM25Retriever.from_documents(documents, k=50) vector_retriever = FAISS.from_documents(documents, embedding).as_retriever( search_kwargs={"k": 50} ) ensemble_retriever = EnsembleRetriever( retrievers=[bm25_retriever, vector_retriever], weights=[0.4, 0.6], )

权重我一般从0.6/0.4(向量/BM25)开始调,纯文档型业务把向量权重调高,代码、配置、日志这类精确内容,BM25权重反而要高些。调参别靠感觉,最好准备一个几百条真实问题的评估集,在评估集上跑不同权重,看Hit Rate和MRR的变化,选最高分组合。

3.3 用Hit Rate和MRR判断召回好不好

线上RAG问答效果差,很多团队第一反应是改Prompt,结果改完发现没变化。问题常常在检索引擎本身。为了判断召回好不好,我建议指标只用两个:Hit Rate和MRR。

Hit Rate是所有测试问题里,能召回相关文档块的比例。比如100个问题,有80个问题能在Top-5里看到正确答案,Hit Rate就是80%。这个指标直观反映“该找的内容找没找回来”。MRR稍微复杂一点,是算第一个正确答案排在第几位:每个问题取1/排名,然后平均。假设某个问题正确答案排在第一位,得1分;排在第二位,得0.5分,排得越靠后分越低。MRR衡量的是“排序质量”,它和你后续重排能不能发挥作用强相关。

举个例子。A方案Hit Rate很高但MRR很低,说明正确答案总在很靠后的位置,如果重排能力不强,这些正确答案根本到不了LLM手里,回答该错还是错;B方案Hit Rate不如A,但MRR高,说明只要命中了基本排在前两位,这种场景用重排救起来效果会更快。所以召回优化阶段,两个指标都要看,缺一个都可能走偏。

4. 重排:最后一公里能救回不少准确率

4.1 从bi-encoder到cross-encoder:为什么重排有效

向量召回阶段用的是bi-encoder,query和文档块分别编码成向量,最后只在向量空间里算个相似度,速度快但缺乏深层的交互。重排阶段用的通常是cross-encoder,模型把query和文档块拼在一起共同编码,能捕捉到它们之间更细的语义交互。简单说:bi-encoder快但糙,cross-encoder慢但准。

我在项目里用的比较多的是bge-reranker系列,小模型用base,追求效果就上large。跑起来之后,效果提升是肉眼可见的:一段query“无法启动设备”,向量召回可能把“设备启动失败的处理步骤”排到很后面,cross-encoder会因为在“启动”和“失败”之间建立更细的关联,把这个块排到前面。上线重排后,最终答案的准确率提升通常在5到10个点,远超你花同样时间调Prompt的收益。

4.2 重排的工程取舍:速度与准确率怎么平衡

cross-encoder准是准,但慢也是真慢。我之前在一台没有GPU的服务器上跑bge-reranker-large,对一个Top-50候选池重排一次要2到3秒,这个延迟加在问答链路里,显然不可接受。所以重排必须分层设计。

我的做法是:第一阶段先用轻量策略把候选池从50压缩到10,比如混合召回已经能排得比较好的Top-10,直接用RRF排序结果;第二阶段才对最终的10到20个候选做cross-encoder重排,只取前5个喂给LLM。如果没有GPU,建议换小模型,比如bge-reranker-base或者FlashRank,实测在延迟敏感场景下性价比更高。还有一种玩法是缓存:相同query的检索和重排结果缓存一定时间,把热门问题压到个位数毫秒,日常很多重复问题就直接命中缓存了。

这里给一个简单的耗时参考。同样在CPU环境下,Top-50候选全部做cross-encoder重排可能要2秒以上,但先压缩到Top-20再重排,耗时可以控制在500毫秒左右。做决策时不要只看离线效果,还要看线上P95延迟,否则系统上线后会被用户不断投诉“太慢”。

4.3 一个完整可复用的Pipeline:混合召回 + RRF + 重排

聊完单独环节,给一个完整可落地的流程做参考:

  1. 先用BM25和向量分别从索引里各取Top-N(比如各50),得到两个候选集。
  2. 用RRF(Reciprocal Rank Fusion)把两边的候选合并排序。RRF的核心思路是:同一个文档在多个检索结果里排名都靠前,融合分就越高。公式为score(d) = sum(1 / (k + rank_i(d))),k常见取60。这一步比直接加权求分更稳,因为不用反复调权重。
  3. 从RRF结果里取前20作为精排候选,交给cross-encoder重排。
  4. 重排后取前5,按得分排序塞进Prompt。

这个流程跑下来,比单纯做加权混合检索再直接进LLM,效果明显稳定。现在主流的LangChain、LlamaIndex也都支持类似组件,比如EnsembleRetriever和各类Rerank模块。需要留意的只有一点:整个链路会多一些计算开销,最终要用延迟监控说话,不能让“多环节”变成“慢查询”。我见过有的团队为了炫技,把链路搭得很长,最后发现大部分时间都花在内部调用上,真实收益反而不大。

def rrf_score(doc_id, ranks, k=60): score = 0 for rank in ranks[doc_id]: score += 1 / (k + rank) return score

把RRF和重排放在一起用,最大的好处是:重排模型不必处理太多低质量候选,它只需要在一批“已经相当靠谱”的候选中做最后筛选,效果自然更稳定。

5. 常见问题与调优实录

5.1 召回率高但答案不对,先查数据清洗

有段时间我很困惑:Hit Rate到85%了,用户还是觉得问答结果不靠谱。后来一条条看上下文才发现,召回是对的,但块里有大量模板信息——“本文档由系统自动生成”“版本号V1.0.3”这些文本占了半个块,真正有用的内容被挤到末尾,模型生成的答案自然被稀释。所以数据清洗这一步别省:把页眉页脚、固定模板、水印、乱码、无关链接都处理掉,再做分块和embedding。清洗前后同样参数跑下来,Hit Rate可能只多3到5个点,但问答质量提升是质的。

我一般会先跑一遍文档统计脚本,看每个文档前几行和后几行是不是重复模板,如果是,直接做正则删除。对于PDF文档,还要检查OCR是否引入了乱码或换行错乱,这些噪声不清理,分块边界会被严重干扰。很多团队觉得清洗数据不是RAG的事,结果把大量时间耗在后续调参上,实际上大部分“玄学问题”都出在数据没洗干净。

5.2 问答响应越来越慢,重排往往是元凶

RAG变慢,链路每个环节都可能是瓶颈。我把排查顺序固定下来:先看LLM生成时间,再看重排时间,再看向量检索和索引构建。大多数情况下你会发现,问题不在LLM,而在重排。如果重排占整条链路一半以上的时间,果断降级:把候选池从50减到20,或换成更小的rerank模型。

另一个容易被忽略的点是embedding生成。如果整个索引库非常大,每次查询时使用一些实时计算逻辑,也可能拖慢链路。不过大多数RAG项目的响应瓶颈,最终都落在“候选集太大+重排太慢”这个组合上。建议在监控面板里把每个阶段的耗时都打点,不然只能靠猜。

5.3 从62%到91%:一套知识库调优的完整过程

最后放一个真实案例。某次给一个内部运维知识库做RAG,初始状态Hit Rate只有62%,MRR大概0.4,用户反馈“答案经常文不对题”。我梳理出来三个核心问题:第一,文档统一按512 token切块,表格和代码全都被截断;第二,只用了向量检索,S7-1200这类型号匹配全靠缘分;第三,没有重排,Top-5里经常混进低相关内容。

针对性地改了三步:

步骤调整内容效果
分块改成“Markdown二级标题优先切分,表格和代码块单独保留”,重叠10%跨块信息被切断的问题得到改善
召回换混合召回,BM25和向量权重先用0.5/0.5Hit Rate明显上升
重排加cross-encoder base版,候选池Top-50压缩到Top-20再精排到Top-5MRR显著提升

调整之后,同一批评估集上Hit Rate升到91%,MRR升到0.78。整体链路延迟从原来的3.2秒降到2.4秒,多花在重排上的时间被候选集压缩抵消了。这个过程里最值钱的思路是:先解决数据进索引之前的问题,再动检索策略,最后才考虑拿模型做重排。顺序反了,会很反复。

5.4 一些可以照抄的参数经验

平时被问到最多的就是“你用的参数到底是多少”。我把通用起点整理成一张表,但记得这只是起点,不是标准答案:

参数建议起点说明
chunk_size512信息密度高的文档用256到512,长文用768到1024
chunk_overlap64约为块大小的10%到20%
混合召回权重0.6向量 / 0.4 BM25精确类内容可以反过来
召回Top-K50混合检索各取50,再压缩
重排候选数20压缩到20后做cross-encoder重排
最终喂给LLM的块数5超过5块内容容易稀释,答案反而发散

这些参数不是拍脑袋定的,都是从评估集上一个一个试出来的。你手里的业务数据不一样,参数一定会有变化,但起点和排查思路可以复用。

6. 最后说点个人体会

这几年做RAG,我最大的体会是:大模型本身不是效果瓶颈,数据管道才是。你在Prompt上纠结半天,不如回头看看分块是否合理、召回是否混了路、重排是否筛掉了噪声。这篇文章里列的6条结论,没有一条是凭空想出来的,都是业务问题和真实评估集一条条喂出来的。如果现在的RAG效果不太行,建议先从指标和评估集开始,把Hit Rate和MRR拉起来,再决定要不要花钱升级模型或重排服务。如果让我给一句最朴素的建议:RAG落地的第一步不是选模型,而是建一套能反映真实业务的评估集,然后把分块、召回、重排当成一个整体系统来调。这一步走稳了,后面的优化才有方向。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询