很多 RAG 系统不是“检索不准”,而是“切出来的东西根本没法用”
我见过太多团队做 RAG 的路径几乎一样:
- 第一天:搭向量库、跑通 demo,大家很兴奋
- 第二周:接入真实文档,效果开始变飘
- 第一个月:开始怀疑 embedding、怀疑模型、怀疑 prompt
- 最后:系统能用,但时灵时不灵,团队也说不清到底问题在哪
然后某一天,有人终于把检索出来的 chunk 复制出来,自己从头读一遍,沉默两秒,说:
“这切得也太碎了吧……这段根本不知道在讲什么。”
很多时候,RAG 的失败不是发生在 re-rank、不是发生在生成,更不是发生在模型能力,而是发生在最早那一步:把知识切成模型能吃的块。切分像“地基”,你地基歪了,上面堆什么都显得玄学。

RAG 端到端流程图
一个必须先统一的概念:RAG 的最小知识单位不是“文档”,是“chunk”
很多人脑子里默认的知识单位还是“整篇文档”。但 RAG 系统里,模型几乎永远看不到整篇,它看到的是你切出来的片段。
这意味着一个很关键的事实:
- 你的系统到底能不能答对
- 不取决于“文档里有没有”
- 而取决于:你能不能切出一个可检索、可读、可引用、可生成的 chunk
你可以把 chunk 当成“给模型的证据卡片”。证据卡片写得稀碎,模型再聪明也只能瞎猜。
这里我特别想强调一句很直白的话:
切分的目标不是“方便检索”,而是“让检索结果可直接支撑回答”。
常见误区:切得越小越好,越容易检索
这是最普遍、也最致命的直觉。
你切得很小,确实可能让向量检索更“精确”地命中关键词附近的语义区域,但你同时会制造一个更麻烦的问题:信息不完整。
RAG 不是搜索引擎。搜索引擎给用户一个链接,用户自己点进去看上下文;RAG 是把内容直接塞进模型,让模型当场回答。模型不是没有理解能力,它的问题是:它不能像人一样“点开全文补上下文”。
切得太小,常见后果有这些:
- 条件在上一块,结论在下一块,模型只检索到其中一块
- 表格表头在上一块,字段解释在下一块,检索到的内容失去意义
- 章节标题和正文分离,检索到正文但不知道它属于哪个范围
- “例外情况/注意事项”被切出去,模型只看到主规则,回答就很危险
所以切分不是简单调 chunk_size,而是一个“信息组织”问题。

切得太小导致“条件/结论分离”示意图
切分的真正评判标准:chunk 必须“可独立阅读”,最好“可直接当证据引用”
我一般用两个很土但很有效的检查方法。
- 独立阅读检查:把 chunk 单独贴出来,不看前后文,你能不能看懂它在说什么?
- 证据引用检查:如果模型只能“引用这一段”来回答,它能不能给出一个至少不出大错的答案?
如果这两个检查过不了,chunk 再短再“好检索”也没用。
很多团队做切分时缺的不是算法,而是这个简单的“可用性意识”。
RAG 切分的第一大重灾区:标题、层级和范围感被切没了
很多技术文档、制度文档、产品说明都有层级:
- 一级标题:适用范围
- 二级标题:规则
- 三级标题:例外/注意事项
如果你切分时只按长度截断,很容易把标题扔到上一块,把正文扔到下一块。结果检索出来的正文看起来像一段“无归属的规则”,模型回答时也会变得很武断。
工程上更好的做法通常是:
- chunk 里尽量包含:标题 + 正文(至少包含最近的上级标题)
- chunk 的 metadata 里保存:文档路径/章节路径(用于调试、展示、引用)
这不是花活,这是让知识“带着身份证”。
第二大重灾区:表格、列表、步骤说明,切坏了就是“信息尸体”
RAG 文档里最值钱的内容,往往不是长段落,而是结构化信息:
- 表格:字段含义、参数范围、异常码
- 列表:注意事项、适用条件、限制
- 步骤:操作流程、排障流程
但这些内容最容易被粗暴切分破坏。
典型翻车方式:
- 表头和表格内容分离
- 列表项被拆开,子项跑到下一块
- 步骤 1 在这一块,步骤 2 在下一块,模型只拿到步骤 2,回答像胡扯
- “注意:以下条件满足时不适用”被切走,模型永远看不到
解决这类问题,你通常要做的是“结构感知切分”,而不是单纯按 token 切。
切分不是单点技术,它会连锁影响 embedding、召回、rerank、生成
很多人把切分当成“预处理”,但它更像系统的“输入格式定义”。
切分会影响:
- embedding 的语义表达:碎片文本的向量往往更接近“泛化语义”,更容易误召回
- 召回质量:碎片越多,候选越多,噪声越大
- rerank 压力:rerank 需要从更多噪声里挑对的,成本和误差都上升
- 生成质量:模型看到的上下文更碎,容易补全、脑补、甚至自信胡说
所以很多 RAG 系统“越做越复杂”:加 rerank、加 rewrite、加缓存,但根因可能只是切分切坏了。
一个工程化的切分策略:先按结构切,再按长度二次切,最后做轻量重叠
很多人想找“万能 chunk_size”。说实话,没有。更可靠的是一个分层策略:
- 先按文档结构切:章节、段落、列表、表格块
- 对过长的结构块再做二次切:在句子/小节边界切,不要硬截
- 对边界依赖强的内容做轻量 overlap:让条件与结论更容易同时出现
这里的关键是:先保证语义完整,再控制长度。
下面给一个很简化的示意代码,表达这种思路(不是工业级,但你能一眼看懂为什么这么做)。
import re
def split_by_headings(text: str):
# 以 Markdown 标题为例:把标题行作为分段锚点
parts = re.split(r'\n(?=#{1,6}\s)', text)
return [p.strip() for p in parts if p.strip()]
def split_by_paragraph(text: str):
paras = [p.strip() for p in text.split("\n\n") if p.strip()]
return paras
def secondary_split(sentences, max_chars=1200, overlap_chars=150):
chunks = []
buf = ""
for s in sentences:
if len(buf) + len(s) + 1 <= max_chars:
buf = (buf + " " + s).strip()
else:
chunks.append(buf)
# overlap:保留末尾一小段,减少边界信息断裂
tail = buf[-overlap_chars:] if overlap_chars > 0 else ""
buf = (tail + " " + s).strip()
if buf:
chunks.append(buf)
return chunks
def chunk_document(text: str):
blocks = split_by_headings(text)
final_chunks = []
for b in blocks:
paras = split_by_paragraph(b)
# 句子级切分(简单示意)
sentences = []
for p in paras:
sentences.extend([s.strip() for s in re.split(r'[。!?\n]', p) if s.strip()])
final_chunks.extend(secondary_split(sentences))
return final_chunks
你会发现它体现的不是“一个参数”,而是一个理念:
- 结构优先
- 边界友好
- overlap 克制(别重叠太大,否则重复信息会污染检索)

分层切分流程图
overlap 不是越大越好,它是“止血贴”,不是“治疗方案”
很多人发现信息断裂,就把 overlap 调到很大,甚至 30% 以上。短期看命中率变好了,但长期会带来几个问题:
- chunk 重复度高,向量空间更拥挤,召回更容易“撞车”
- rerank 候选里重复内容多,浪费预算
- 生成上下文重复,模型更容易“复读”和啰嗦
- 评估时难以判断:到底是哪个 chunk 起作用
overlap 更像止血贴,合理范围通常是:
- 只覆盖“最容易断裂”的边界信息
- 只在确实存在跨块依赖的文档类型上使用(流程、规则、FAQ解释)
切分要做“内容类型分治”,不要幻想一套策略吃遍天下
真实文档往往是混合体:制度、FAQ、操作步骤、异常码、产品介绍都在一起。用同一策略切,效果很难稳定。
更靠谱的做法是:先识别内容类型,再选择切法。
- FAQ:问答要绑定在一起,别把 Q 和 A 拆开
- 规则条款:条件 + 结论 + 例外尽量同块
- 流程步骤:连续步骤尽量同块,至少保持局部连续
- 表格:表头与行绑定,必要时把表格转成“可读文本块”
- 长说明:结构切 + 二次切
切分做得好不好,不要只看“最终答案”,要拆层评估
这点特别重要,因为很多团队评估 RAG 只看最终回答,结果所有问题都怪到模型头上。
更工程化的评估拆法是:
- 检索评估:正确 chunk 是否在 TopK 里
- 可用性评估:TopK 的 chunk 是否能支撑答案
- 生成评估:模型是否正确引用与使用 chunk
其中“可用性评估”最容易被忽略,但它直接对应切分质量。
给你一个非常朴素、但在团队里很好落地的评估方式:抽样检查“证据卡片”。
- 随机抽 50 个问题
- 每个问题看 Top5 chunk
- 打标签:可用 / 部分可用 / 不可用
- 统计不可用比例
- 再反推切分问题在哪里(结构?表格?边界?)
你会遇到的真实翻车场景,我建议你提前认识它们
这里我讲几种非常常见的“线上翻车场景”,你会发现它们几乎都和切分有关。
- “答得像,但缺关键条件”:切分把条件切走了
- “引用了一段看似相关的说明,却答错结论”:chunk 太泛,模型只能脑补
- “同一个问题有时答对有时答错”:候选里噪声高,排序略变就换 chunk
- “答案里出现了不该出现的例外条款”:例外被切成独立块,误召回
- “看似检索命中,但回答仍然空泛”:命中的是背景介绍块,不是操作块
如果你把这些症状当成“模型不行”,你就会开始换模型、调 prompt,最后系统更复杂,问题还在。
一个非常现实的建议:不要一开始就全量建库,先做“核心文档小闭环”
切分策略这东西,你不可能一开始就完美。更健康的节奏是:
- 选 20–50 篇最核心、最常被问的文档
- 为它们做 2–3 套切分策略
- 用同一批问题集评估 chunk 可用率
- 先把“可用率”做到一个你能接受的水平
- 再扩到全量文档
这能大幅降低你“切错一刀,全库重建”的成本。
当你在做切分策略对比时,最麻烦的往往不是“写切分代码”,而是“同一批问题在不同切分版本上反复评估、对比输出差异”。这类阶段我更建议用LLaMA-Factory online先把小闭环跑起来:同一评估集、不同切分版本、直接看检索结果与最终回答的差异,会比你在本地反复搭脚手架更省时间,也更容易快速做出“这刀到底切对没”的判断。
总结:RAG 的切分不是预处理,而是知识工程;你切的不是文本,是“模型可用的证据”
如果要用一句话收尾,我会写得直白一点:
RAG 的 80% 问题不是模型问题,是你把知识切成了模型用不了的形态。
切分不是把文档“剁碎”,而是把知识组织成:
- 可被检索到
- 可独立阅读
- 可直接支撑答案
- 可解释、可回溯
你做到这一步,后面的 embedding、rerank、prompt 才有意义;你没做到,后面堆再多模块也像在给地基打补丁。