RAG系统调优:召回、切块、重排与生成全链路优化指南
写 RAG 项目最磨人的阶段往往不是把第一版跑通而是线上回答看起来还行可一追问细节就露馅。这段时间我一直在补 RAG 系统调优的实战经验今天这篇相当于给自己做了一个阶段复盘也正好当作《深入浅出 RAG》系列的第 13 章。这个系列一路聊下来从向量检索的原理讲到知识库的搭建再到多轮对话怎么设计到了这一章终于要把系统放在“真实流量”里按摩擦了。适合正在做 RAG 落地、已经被“召回不准”“回答太官方”“引用对不上”这类问题缠住的朋友阅读。我先把结论放前面RAG 调优的杠杆率分布极不均匀。同样投入一周时间调索引结构和切块策略可能让整体效果提升 30% 以上调 Embedding 模型运气好能提升 10%调生成模型的提示词有时只提 3 个点就能消掉一半幻觉。为什么差距这么大因为 RAG 系统的瓶颈通常不在“模型不够强”而在“知识进不去、查不到、用不好”这三个环节。这篇文章我会沿着一条 Query 从进入系统到生成回答的完整路径把每个环节的调优点、参数选择、评测方法和踩坑经验全部展开。1. 调优之前先把 RAG 的构成拆明白1.1 为什么“调优”往往不是调模型很多第一次接触 RAG 调优的同学下意识会认为回答质量不行就该换更大的 LLM。实际上RAG 本质上是一个“搜索引擎 阅读理解器”的组合系统模型只是里面的阅读器。假如检索环节返回的资料本身是错的、缺的、或者被切得七零八落的那不管模型多聪明都只能在残缺信息里勉强拼凑答案这时候换再大的模型也只是在粉饰错误。我习惯把 RAG 系统分成四层来排查。知识层原始文档怎么清洗、怎么切块、怎么建索引决定了系统“有什么可用”。召回层检索器用稠密向量、稀疏关键词还是混合策略决定了系统“能不能找到”。重排层召回 Top-K 里往往混着噪声重排模型负责把最相关的几个结果顶到前面。生成层提示词是否约束模型忠实于检索结果上下文窗口怎么组织决定了系统“答得对不对”。每次遇到线上问题我先问自己这个问题出现在哪一层如果答案内容正确但是细节缺失那多半是切块粒度太大如果答案完全跑偏先看检索结果里有没有正确答案如果检索结果里有正确答案但模型没用上才是提示词的事。这层判断一旦建立调优就不会东一榔头西一棒子。1.2 一条 Query 的旅程从检索到生成的四个环节为了方便后文展开我先把一次完整的 RAG 请求拆成四个环节后面所有调优手段都会挂在这条链路上。用户输入“我们的退货政策里七天无理由退换货需要满足什么条件”之后系统先做查询预处理包括改写、意图识别、扩展同义词。然后进入召回系统把 Query 转成向量和知识库里的向量做相似度搜索同时可能用 BM25 做关键词匹配拿到候选文档集合。接着是重排用一个更精细的模型对候选文档重新打分把最相关的 3 到 5 段选出来。最后是生成把选中的段落拼进提示词让 LLM 生成带引用的回答。这四个环节里每一个都有独立的调优空间但是它们又不是完全独立的。比如你把切块策略改了重排的效果会跟着变你把元数据过滤加上了召回结果也会立刻变化。所以调优一定要带着评测指标做不然很容易出现“这周调好了 A 类问题下周 B 类问题全崩了”的尴尬局面。2. 检索环节调优召回率和精度怎么平衡2.1 选对检索器从稀疏到稠密再到混合检索器是 RAG 系统的入口它决定候选文档集合的上限。如果这一步就把正确答案漏掉了后面无论怎么重排、怎么约束生成都是白搭。我见过不少项目一开始只用了向量检索结果实体名词、产品型号、政策编号这类精确匹配场景全部翻车因为这些 token 在 Embedding 空间里可能被压缩得毫无区分度。先说三种常见检索器的定位BM25 稀疏检索对关键词匹配极其敏感特别适合“型号、人名、序号”这类精确匹配需求缺点是理解语义弱同义改写基本没戏。稠密向量检索能处理同义改写、口语化表达但对少见实体和长尾问题不敏感而且对 Embedding 模型的领域适配度要求很高。混合检索同时跑两种检索再用 RRFReciprocal Rank Fusion或加权分数融合把两边的优势合并在一起。我现在的默认配置是混合检索。RRF 的融合公式很直观每个文档在两种检索结果里各自有一个排名文档的融合分等于它在各列表里排名倒数之和公式是score Σ 1 / (k rank)k 一般取 60。这样处理有个很好的特性不需要对向量相似度和 BM25 分数做归一化因为排名的绝对值不影响融合结果只影响相对顺序。有同学可能会问那 Embedding 模型要不要换我的建议是先别急着换。先看混合检索在你的数据集上有没有效果提升如果召回结果里正确答案出现的排名已经不错了问题出在重排环节那换模型就不是第一优先级。如果跑了一批测试 Query发现正确答案压根不在 Top 50 里这时候再去换领域微调的 Embedding 模型不迟。2.2 切块策略真的不是越小越好切块是 RAG 里最容易被低估的环节。很多教程会说“chunk_size 设 512overlap 设 128 就行”但真实项目里同一份文档的不同章节对块大小的敏感度完全不一样。比如操作手册里的步骤列表如果一刀切成 300 token每一步的上下文很容易被切断而政策条款按条目切每一块本身就是一个完整语义单位太大反而会把多个无关条目塞进同一个块里。我常用的切块维度有三个固定窗口切分按 token 数硬切实现简单适合结构统一的纯文本。递归字符切分优先按段落、句子边界切Tree-sitter 类工具甚至能按代码结构切适合混合文档。语义切分先按句子拆开再把语义相近的句子聚合效果最好但耗时也最高。实践下来大多数业务文档用 512 到 1024 token 的块比较稳。块越小检索精度越高但上下文越容易不完整块越大上下文越完整但噪声也越多。关键指标是“块内信息密度”一个块只讲一件事是切块的最佳状态。另外overlap 不要盲目复制。overlap 的作用是防止切断语义连续的句子一般设块大小的 10% 到 20% 就够。如果 overlap 设置得太大同一个内容会在多个块里重复出现召回时会带来大量重复片段重排环节还得做一遍去重。2.3 重排序的必要性Top-K 从 5 变 50 再拉回 5很多人搭 RAG 时直接把向量检索的 Top-5 塞给 LLM这样看起来省事实际效果通常不好。因为向量召回 Top-5 里可能只有两条是真正相关的剩下三条是“看起来像但站不住脚”的干扰项。正确做法是召回阶段多取一些候选比如 Top-50再用重排序模型压缩回 Top-5。重排序模型一般用 Cross-Encoder它会把 Query 和文档拼在一起输入模型做深度交互打分比 Bi-Encoder 的向量相似度准得多。缺点是推理速度慢所以它只负责精排不再负责全量检索。选重排模型的时候记得看两件事一是它对中文长文本的支持二是它的输入长度上限。如果块本身已经接近 1000 token很多重排模型会直接截断导致文档尾部信息丢失。我之前踩过这个坑后来改成先对块做摘要再重排或者干脆缩小块大小才把重排效果拉回来。还有一个容易忽略的点重排分数不能跨模型直接比。不同模型的打分尺度完全不一样有的输出 0 到 1 的概率有的输出 logits所以重排分数只能用于内部排序不要拿来设定全局阈值。3. 索引与知识组织让 RAG 知识库真正“好查”3.1 元数据过滤给知识打上结构化标签如果你的知识库只有一堆文本块检索时只能靠语义相似度硬碰硬那系统会很“笨”。实际上大部分业务知识天然带有结构化属性文档来源、发布时间、适用产品线、文档类型、安全级别等等。把这些属性写成元数据存进索引查询时先按元数据过滤再做向量检索效果会稳定很多。比如一个知识库里有产品说明书、售后政策、销售话术三类文档。用户问“这款产品保修多久”你可能希望优先从售后政策里找答案而不是从销售话术里翻。这时候给每个块打上doc_type标签查询时把doc_type售后政策作为硬过滤条件检索结果的质量立刻上升。元数据过滤还有一层价值它能解决“多租户”或者“多产品线”场景下的数据隔离问题。假设你是做本地 ERP 加 LLM 的产品检索不同客户的数据混在一个库里如果不加customer_id过滤检索结果会串味到别的客户头上这已经不是效果问题了而是数据安全红线。所以元数据设计一定要在索引设计阶段就做好不要等线上出了问题再补。3.2 父子块策略用小片段检索、用大片段生成很多时候检索精准和上下文完整是两个互相矛盾的诉求。小片段语义集中、召回准但上下文缺失大片段上下文完整但噪声太大。父子块策略就是为这个矛盾设计的。具体做法是把文档切成两层。父块比较大比如 1500 token负责保存完整上下文子块比较小比如 300 token负责进入向量索引参与检索。用户 Query 先检索子块命中子块后通过映射关系找到它所属的父块然后把父块内容塞给 LLM 生成答案。这样做的收益非常明显子块小向量检索的命中精度高父块大LLM 生成的上下文完整。代价是索引存储量会增加因为同一段内容被存了两份。不过现在向量数据库的存储成本不算高这点代价换来的效果提升是值得的。父子块还有一个进阶玩法父块不一定是原始文本块也可以是这一节内容的摘要。检索子块命中后把摘要和子块一起放入提示词模型可以先看摘要理解上下文再读细节。这个方案在处理超长文档时特别有用。3.3 表格、结构化数据怎么放进知识库关于“系列产品表格怎么存入 RAG 知识库”我的答案是不要直接硬塞。直接把一张 50 行 10 列的 Markdown 表格切成一个块丢进向量库检索时往往什么都查不到因为表格的语义高度浓缩用户问“这个型号的接口支持哪些协议”跟你表格里的列名可能完全对不上。我常用的做法有三种表格改写为描述文本把每一行数据转成一句自然语言描述例如“型号 X200 支持 USB 3.2、Type-C、HDMI 2.1 三种接口”再切块索引。表头与单元格组合索引保留表格结构但把每个单元格和它的表头、所在行关键信息拼在一起建索引提升命中率。表格与上下文分开存表格本身作为父块行内数据作为子块检索子块命中后模型再读完整表格。这三种方案里我最推荐第一种因为它最符合 RAG 的语义检索逻辑。自然语言描述能让“支持哪些协议”这种 Query 和文档内容在语义空间上更接近。表格原文可以作为引用材料保留方便生成答案时提供可溯源的数据。4. 生成环节调优提示词、引用与多轮对话4.1 提示词里的“检索结果使用纪律”模型拿到检索结果之后并不天然知道该怎么用。尤其是当检索结果里既有相关信息又有无关信息时模型可能把无关信息也编进答案。所以提示词里必须明确“使用纪律”。我常用的提示词约束包括三条你可以根据自己的场景调整只依据提供的资料回答不要使用内部知识补全。如果资料不足以回答直接回答“资料不足”不要尝试猜测。引用答案时在句末标注对应的资料编号。听起来非常简单实际效果却立竿见影。很多幻觉问题的根源不是模型不行而是它不知道“不能答时要停”。你可以在提示词里把“资料不足”设定为一个合法答复选项而不是逼着模型硬答。这比给模型喂再多的 few-shot 都管用。不过要注意约束太多也会伤到回答的自然度。比如某些场景下用户问“你们公司地址在哪”如果知识库里没有模型说“资料不足”会很生硬。这时候可以加一个兜底策略资料不足时可以基于通用知识回答但必须声明“该信息不在当前知识库中”。这样既保证了诚实又保留了用户体验。4.2 让答案可溯源引用标注怎么改RAG 系统如果只是把正确答案生成出来但用户无法验证这个系统在严肃场景下还是不达标。我见过很多项目把引用标注做成“参考资料[1] [2]”但用户点开之后看到两段完全一样的文字这种引用等于没有。正确做法是先保证索引每一块都有稳定的文档标识和段落标识。切块时写入doc_id、chunk_id、source_url、page_no等字段。然后提示词要求模型在引用时使用编号标注生成结束之后再用代码逻辑做一步校验检查模型引用的编号是否真的出现在检索结果里。如果模型引用了不存在的编号要么是它幻觉了要么是输出格式乱了。我的处理方案是在提示词里明确列出资料编号例如“资料编号仅为 [1]、[2]、[3]”并告诉模型“只能引用这些编号不要创造新编号”。同时在后端做一次简单 regex 校验把非法引用直接过滤掉或者触发一次重新生成。引用标注这一环看起来是工程小细节实际是 RAG 系统专业度的分水岭。4.3 多轮对话中的检索意图识别与上下文管理RAG 系统一旦进入多轮对话场景事情就变复杂了。用户上一轮问“你们有哪些接口”这一轮直接说“给我看看价格”系统需要知道“价格”指的是上一轮提到的那些接口的价格。如果直接拿“给我看看价格”去检索召回结果大概率是乱七八糟的。解决这个问题有两种主流方案。Query 改写在进入检索之前用 LLM 把多轮上下文压缩成一个独立搜索词。比如把“给我看看价格”改写成“查询上述接口的对外报价”。上下文拼接检索直接把最近几轮问答拼在一起作为检索 Query简单粗暴但检索质量受无关历史干扰比较大。我更推荐 Query 改写。改写时要注意保留关键实体和业务限定词。比如用户前一轮提了“华东区”这个地域限制改写成“查询华东区接口报价”才能保证检索不出圈。多轮对话还有一层管理问题上下文窗口要不要全部保留。我的经验是只保留最近两到三轮的问答更早的信息如果影响当前 Query让改写模型自己去摘要不要把冗余历史全塞进提示词。上下文越长模型越容易“忘记”检索结果里的关键信息。这里顺便回应一个热词RAG 和 MCP 的区别。MCP 本质上是给模型开放工具调用的协议解决的是“模型怎么调用外部工具”而 RAG 解决的是“模型怎么从大段知识里找答案”。两者可以配合使用但在调优思路上完全不是一回事。RAG 调优关注检索和上下文MCP 调优关注工具描述和参数 Schema。这两者在项目里需要分开评估不要混为一谈。5. 评测与观测不量化调优就是盲调5.1 用一套离线指标代替手感我先说一个现实RAG 调优如果没有评测集所有改动都只是“自我感觉变好了”。这个状态在 demo 阶段没问题但一上线就会被真实用户打回原形。所以我建议建立一套至少 50 条问题的离线评测集覆盖业务高频问题、长尾问题、易混淆问题三类。评测指标里我最看重两个维度忠实度Faithfulness生成的答案是否严格基于检索资料有没有幻觉。答案召回Answer Recall标准答案里的关键信息点生成答案覆盖了多少。这两个指标可以先用 LLM 做自动评测再人工抽检 20% 的案例。自动评测比较快适合每次改动后快速回归人工抽检能发现评分模型自己的偏好和盲区。另外不要只评测“最终回答”。我建议分成“检索是否命中正确答案”“重排后正确答案是否进 Top-5”“最终回答是否正确”三个指标分别记录。这样一旦效果变差你能立刻定位是哪一环出了问题而不是从头到尾查一遍。5.2 日志与 Tracing每个失败案例都是调优线索线上系统的调优离不开观测。我每次上线 RAG 服务之前都会在链路里埋一套结构化日志关键信息包括原始 Query、改写后的 Query、召回 Top-20 文档 ID、重排后 Top-5 文档 ID、最终生成的答案、用户反馈。有了这套日志你可以定期做“失败案例复盘”。比如看到某一类 Query 的召回结果里正确答案排在第 18 名说明召回没问题但重排没把它顶上来如果正确答案根本没出现在 Top-20说明召回层漏了。这些定性的结论比任何技术指标都更有指导意义。现在常用的链路追踪工具像 LangSmith、Langfuse 都支持记录这种结构化数据你也可以用自己家的日志系统。关键是记录字段要提前设计好宁可多记也不要少记后面分析起来会省很多事。如果预算有限最简单的方式是把日志写进一张表字段就按我上面说的设计。每两周翻一次你会发现 RAG 调优的下一步方向其实已经藏在日志里了。6. 常见问题速查与实战心得6.1 高频问题排查表我把实际项目里最容易碰到的几类问题整理成一个速查表遇到症状可以直接对照排查症状可能原因优先检查项答案有相关内容但细节缺失块太大、上下文被截断调小 chunk_size改用父子块策略答案偏题答非所问召回结果噪声大检查 Top-5 里是否有正确答案加重排层答案正确但引用对不上切块后原文锚点丢失索引里补充文档 ID、段落 ID后端加引用校验特定实体名词检索不到向量模型对实体不敏感加 BM25 混合检索或微调 Embedding 模型多轮对话指代混乱Query 改写不彻底改写时显式带上历史关键实体同一问题反复跳答案提示词约束不够、随机性太高明确“资料不足”答复选项调低 temperature新增文档后旧问题效果变差索引未重建或元数据冲突确认增量索引任务、检查重复文档合并策略这张表看着简单排查时特别顶用。我每次调优遇到瓶颈先对表找方向比自己空想快得多。6.2 我踩过的三个坑第一个坑一上来就调 Embedding 模型。早期做 RAG 时我觉得向量检索不准就换个更大的模型前前后后折腾了好几种模型最后发现问题的根源是切块切得太碎一通操作回到原点白白浪费了一周。现在的习惯是先把数据清洗、切块、元数据过滤做扎实再考虑换模型。第二个坑重排模型没有考虑长文本截断。当时把 1500 token 的大块直接送进 Cross-Encoder 重排很多文档的后半段信息被截断重排分数完全失真。后来改成先切小块再重排或者用支持长文本的模型问题才缓解。第三个坑评测集只有正例没有反例。一开始的评测集全是“正常提问”系统调优后看起来效果很好一上线就被各种“刁钻问法”打崩。后来我在评测集里加入了模糊提问、指代提问、跨文档提问系统才算真正经住了考验。最后分享一个小技巧无论你用什么 RAG 框架都要保证检索结果里的原始文本能够被完整追溯到最终回答。我会在生成回答时顺手把命中的 doc_id 列表写入响应头或元数据字段这样即使线上出了诡异问题也能最快定位是哪一份文档“带偏”了模型。这个习惯看着不起眼但在排障时救过我好几次命。RAG 调优没有一招通吃的银弹它更像是在检索、索引、生成三个维度之间做平衡。我个人的体会是任何一次调优改动都要问三个问题改之前基线在哪、改之后提升了什么、代价是什么。只要这三笔账算得清楚RAG 系统就越调越顺。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →