如果你照着网上的 RAG 教程搭过一次 demo,很容易陷入类似情况:示例语料检索正常,换成本领域文档后,检索结果变得不稳定。于是反复修改 chunk_size,再换向量模型,试了很多组,仍然说不清问题到底出在哪里。这里的真正问题通常不是某个“最佳参数”不存在,而是调参动作没有回答一个更前置的问题:让检索失败的是切分,还是向量匹配?
切分与向量化的职责并不相同
RAG 的召回流程可以简化成:文档被切成多个 chunk;每个 chunk 被向量化;查询向量与所有 chunk 向量做相似度计算;返回分数最高的前 k 个候选。
切分决定的是检索单元的语义边界。如果一个问题的答案被拆进两个 chunk,每个 chunk 都只覆盖部分证据,单独拿任何一个去和查询比较,语义都不完整。向量化再强,也无法补齐被切掉的上下文。
向量化处理的是“已经形成的候选 chunk 与查询的语义接近程度”,它决定的是命中之后的排序质量。因此调参前应先把失败分成两类:
- 支持答案的文本从来没有出现在前 k 个候选中;
- 支持答案的文本出现了,但排序明显靠后。
前者更可能由切分粒度和切分边界引起,后者主要和向量模型、检索返回量或排序策略有关。两类问题混在一起调,结果会很难解释。
先花少量时间建一个小评估集
只靠两三条手工问题和肉眼观察,很难判断一次改动是真实改进还是偶然波动。一个低成本、可复现的评估集可以这样构建:
- 从真实使用场景收集 20~50 条查询;不要为了测试方便而把问题改写成过度规范的表达。
- 在原始文档中标记出能回答每条查询的文本范围。
- 固定检索返回数 k,例如 10。
- 记录 hit@10:前 10 个候选中是否出现能支持答案的文本。
如果还想评估排序能力,可以同时看 MRR。MRR 对每条查询取“第一个相关候选所在位置的倒数”:排第 1 记 1,排第 5 记 0.2,前 k 个没有相关结果记 0,最后对所有查询取平均。
一个小评估集未必能反映所有长尾场景,但已经足够暴露切分和向量化带来的明显差距。重点是:每次调参都用同一组查询、同一个 k,不要边调边改评估口径。
chunk_size 与 overlap 应该按切分粒度对照
更稳定的做法是先固定向量模型,把切分方案按粒度分成三档做基线对比,而不是只把 chunk_size 从一个整数改成另一个整数。
- 细粒度:以自然段为基本单元。如果自然段过长,超过向量模型可处理长度,就在句末、空行或列表边界继续切分。
- 中粒度:把一个标题下的相邻自然段合并成一个小节,作为一个 chunk。
- 粗粒度:直接按章节作为 chunk;若超长,再在节内自然边界二次分割。
这三档的本质不是“数字变大变小”,而是让检索单元尽量贴近文档自身的逻辑结构。对同一份文档,固定 512 token 按句号硬切,和从标题边界处开始切,得到的结果可能完全不同。
拿到三档基线的 hit@10 后,再看失败查询属于哪一种:如果正确证据被切到两个 chunk 的接缝处,细粒度往往会漏掉后半部分信息,这时让 chunk 携带更多相邻上下文可能有效;如果某个 chunk 里混入了多个主题,粗粒度会把与查询不相关的主题也卷进向量里,导致正确内容被稀释。
overlap 可以先设为 0 做基线。只有当失败查询明确表现为“答案的关键句落在两个 chunk 的交界处”时,再逐步增加 overlap,例如把前一个 chunk 末尾的一句话带入下一个 chunk,或者让下一级 chunk 保留上一级的小节标题。这样做的目的是保持上下文连续,而不是让 overlap 本身越大越好。overlap 过大会让同一段文本被多次向量化,不仅增加索引体积,也会给候选集引入大量近似重复内容。
向量模型必须放在切分实验之后再评估
如果切分已经把正确证据分成两段,换更强或更大的向量模型并不能制造出本来就不存在的完整 chunk。因此应先选出明显更好的切分方案和 overlap 方向,再替换向量模型。
替换模型时仍需使用同一份评估集、同一个 k。只看两条样例感受不够,应分别记录 hit@10 和 MRR:如果 hit@10 没有变化,说明模型替换并没有改变“能否找回正确内容”这一层结果;如果 MRR 提升,说明正确内容确实排得更靠前,这对后续只取少量 top 结果的链路是有价值的。
实际选择向量模型时,要特别关注目标文档的语言和领域。不同模型训练语料的侧重点不同,对中文文档、领域缩写、代码和通用英文文档的表现也会有差异。要验证这种差异,不能靠模型总排行榜,只能靠已经建立的本地真实查询评估集。
一套可执行的实验顺序
把上面的逻辑收敛为实际操作,可以按这个顺序执行:
- 建立 20~50 条真实查询的评估集,计算当前基准的 hit@10。
- 固定向量模型,按细粒度、中粒度、粗粒度三档切分做对照,记录每档的 hit@10。
- 选择命中较好的粒度,再对失败查询分类;如果失败集中在 chunk 边界,从 overlap 为 0 开始,按句逐步增加,直到 hit@10 不再提升。
- 固定切分与 overlap,再替换向量模型进行第二轮对照。除非 hit@10 或 MRR 明显改善,否则不必为了换模型而换模型。
- 每轮只改变一个变量,并保留完整的查询集和配置记录,否则后续无法判断是哪一步带来了提升。
这套方法不依赖具体产品,也不预设某个固定 chunk_size。它解决的问题是:当 RAG 检索效果差时,如何用低成本实验找出真正的瓶颈,而不是在参数空间里盲目试错。