chunk重叠overlap设多少:切断上下文的坑

一句话先说清:RAG 切文档的时候,相邻两块之间要不要留一段重叠(overlap),留多少,这事比很多人想的重要。重叠太小,一句关键的话被生生切两半,两边都召回不全;重叠太大,向量库里全是冗余,检索还容易把好几块几乎一样的段落一起捞回来。我拿一份运维手册实测过一组,今天把怎么定 overlap 讲透。

不留重叠会怎样

先看反面教材。我最早图省事,按固定 500 字硬切,零重叠。结果有个问题死活答不对:「数据库主从切换后,缓存要不要手动刷新」。扒日志发现,手册里这句话的答案------「主从切换后需手动执行 flush,否则会读到旧数据」------刚好被切在两个 chunk 的接缝上。前半块「主从切换后需手动」,后半块「执行 flush 否则读到旧数据」,单看哪一块向量都跟问题不够贴,两块都没进 top5。

这就是零重叠的典型坑:语义被物理切割截断。一个完整的因果、一组步骤、一个定义,被字数硬生生劈开,两半各自残缺。

overlap 怎么定,给个实测参考

我固定 chunk 大小 500 字,只动 overlap,拿同一份标注集量 recall@5:

|----------|----------|-------|-----------|
| overlap | recall@5 | 向量库膨胀 | 备注 |
| 0 | 0.78 | 基准 | 接缝处的答案捞不全 |
| 50(10%) | 0.88 | +11% | 性价比最高 |
| 100(20%) | 0.91 | +25% | 还在涨但变缓 |
| 200(40%) | 0.92 | +60% | 几乎不涨了,纯浪费 |

规律很清楚:从 0 加到 10%~20%,召回明显往上走;再往上加,收益迅速摊平,存储和检索成本却线性涨。所以我现在的默认值就是 chunk 大小的 15% 左右,比如 500 字的块留 75 字重叠,按句子边界对齐,别从字中间切。

复制代码
def split_with_overlap(text, size=500, overlap=75):
    chunks, start = [], 0
    while start < len(text):
        end = start + size
        chunks.append(text[start:end])
        start = end - overlap   # 回退 overlap,制造重叠
    return chunks

重叠太大的反噬

overlap 拉到 40% 那次,我还碰上个意外问题:检索回来的 top5 里,有三段内容高度雷同,因为它们本就大面积重叠。等于五个名额被同一坨信息占了三个,真正多样的资料反而挤不进来,模型看到的视野变窄了。后来我在重排那步加了个去重,把相似度过高的块合并,才缓过来。

所以重叠不是越大越保险。它解决的是「接缝截断」,代价是「冗余和重复召回」,得卡在一个平衡点。

还有个更省心的法子

如果你的文档结构清楚(有标题层级、有明确的步骤编号),与其纠结字数重叠,不如按语义边界切------一个小节一块、一个步骤一块,天然不会从句子中间断开,overlap 的需求就小很多。我现在的策略是:结构化文档优先按标题切,实在是大段连续正文才退回固定长度+重叠。

这些切法我没自己写一堆解析代码。智能体配在一个零代码就能搭 RAG 的平台上,文档传上去能选切分方式、调 chunk 大小和重叠比例,向量化它自动做,我主要精力花在调参和量召回上。当然它给的切分策略也就那几档,真遇到特别刁钻的版式,还得自己预处理过一遍再喂进去,别指望它全自动搞定。

(模型和知识库我都走讯飞星辰 MaaS,托管的现成大模型和 RAG,直接调没自部署)

你们 overlap 一般留多少?有没有也遇到过答案卡在接缝上死活召回不到的?评论区说说你的切分配方。