文档切割策略有哪些?怎么规避语义被切割掉的问题
1️⃣ 考察意图
面试官想看你是否真正理解文档切分(chunking)在 RAG 系统中的核心地位——它不是一个“切就完事”的预处理步骤,而是直接影响检索召回率和下游任务质量的工程决策。考察类型是工程取舍 + 系统设计。刁钻点在于:候选人常只背出“固定大小、递归、语义”三种策略,却说不清每种策略在什么场景下失效、以及如何用工程手段(如 overlap、多粒度索引)弥补语义断裂。答好了能展示你对 RAG pipeline 的端到端理解,以及从“检索效率 vs 语义完整性” trade-off 中做权衡的实战能力。
2️⃣ 标准答
文档切割策略可以按“粒度”和“智能程度”分为三个梯队,每个梯队都有其适用场景和必须规避的坑。
第一梯队:固定大小切分(Fixed-size Chunking)
- 做法:按字符数或 token 数硬切,比如 512 tokens 一个 chunk,无 overlap。
- 优点:实现简单,计算开销低,适合对延迟敏感的场景(如实时聊天机器人)。
- 坑:语义断裂最严重。比如一个句子的后半部分被切到下一个 chunk,导致检索时“上下文丢失”。例如“苹果公司发布了新款 iPhone,其价格是……”被切成“苹果公司发布了新款 iPhone”和“其价格是……”,检索“iPhone 价格”时第一个 chunk 不包含价格信息,召回失败。
- 解法:加 overlap(重叠窗口)。通常 overlap 设为 chunk 大小的 10%-20%(如 512 tokens 的 chunk 用 64 tokens overlap)。但 overlap 会引入冗余,增加存储和检索成本,需要根据业务容忍度调整。
第二梯队:递归切分(Recursive Chunking)
- 做法:按文档结构递归切分,优先按段落(
\n\n),再按句子(.!?),最后按固定长度回退。LangChain 的RecursiveCharacterTextSplitter是典型实现。 - 优点:尊重自然边界,语义完整性比固定切分好很多。比如一篇技术博客,按段落切分后,每个 chunk 通常是一个完整论点。
- 坑:段落长度差异大。一个段落可能只有 50 tokens,另一个可能 2000 tokens。如果设置 max_chunk_size=512,长段落会被强制截断,再次引入语义断裂。
- 解法:结合“软边界”和“硬边界”。软边界是段落/句子,硬边界是 max_chunk_size。当软边界长度超过硬边界时,用语义边界检测(见第三梯队)或二次递归(在段落内按句子再切)。实际落地中,我遇到过 PDF 解析后段落标记丢失的情况,此时需要先用
unstructured库做文档结构恢复(layout detection),再递归切分。
第三梯队:语义切分(Semantic Chunking)
- 做法:基于 embedding 相似度或 LLM 判断来检测语义边界。例如:
- Embedding-based:对文档滑动窗口计算相邻句子的 cosine 相似度,当相似度低于阈值(如 0.7)时认为语义断裂,在此处切分。论文
Semantic Chunking for RAG有详细实验。 - LLM-based:用 LLM 判断两个句子是否属于同一主题,比如调用 GPT-4 的
chat.completions接口,prompt 为“以下两个句子是否在讨论同一件事?请回答 yes/no”。 - 优点:最大程度保留语义完整性,适合对质量要求高的场景(如法律文档分析、医疗报告检索)。
- 坑:计算成本高。Embedding-based 需要为每个句子生成向量,LLM-based 更贵且延迟高。此外,阈值选择敏感:阈值太高导致 chunk 过小(碎片化),阈值太低导致 chunk 过大(包含无关信息)。
- 解法:采用多粒度索引(Multi-granularity Indexing)。同时维护两种索引:细粒度(如句子级)用于精确检索,粗粒度(如段落级)用于上下文补充。检索时先召回细粒度 chunk,再通过父文档 ID 拉取粗粒度 chunk 作为上下文。这借鉴了
Parent Document Retriever的思想,在 LlamaIndex 中有现成实现。
实际落地的坑 + 解法:
- 坑:中文文档的句子边界检测不准。英文用
spaCy的sentencizer效果不错,但中文依赖jieba或pkuseg,遇到“某某公司成立于1998年。其产品……”时,句号后可能被误判为句子边界,但实际是同一段落。 - 解法:先用正则或
re库做硬规则(如句号+空格+大写字母),再结合spaCy的sentencizer做软规则。对于中文,可以先用zh_core_web_sm模型做依存分析,但更实用的方案是直接用unstructured库的partition_pdf或partition_text,它内置了多语言文档结构解析。
总结:没有万能策略。固定切分适合低延迟场景,递归切分是默认选择,语义切分用于高质量需求。关键是在工程上做 trade-off:用 overlap 缓解语义断裂,用多粒度索引平衡检索效率和完整性。
3️⃣ 答题模板(30 秒电梯版)
“这个问题我从三个层面回答:第一,常见策略包括固定大小切分、递归切分和语义切分,各有优劣;第二,规避语义切割的核心手段是重叠窗口、基于文档结构的递归切分,以及语义边界检测;第三,进阶方案是多粒度索引,同时维护细粒度和粗粒度 chunk。总结一句:没有万能策略,需要根据延迟、成本和召回率做 trade-off,并用 overlap 和父文档检索兜底。”
4️⃣ 高频追问 & 应对
追问 1:你提到多粒度索引,具体怎么实现?索引存储和检索的复杂度如何?
实现上,用两个向量数据库集合:一个存细粒度 chunk(如句子级),另一个存粗粒度 chunk(如段落级)。细粒度 chunk 的 metadata 中记录 parent_id 指向粗粒度 chunk。检索时,先用细粒度集合做 top-k 召回,然后根据 parent_id 去重并拉取对应的粗粒度 chunk 作为上下文。复杂度方面,存储量是单粒度索引的 1.5-2 倍(因为细粒度 chunk 数量多),但检索延迟只增加一次 ID 查询(O(1))。实际中,可以用 LlamaIndex 的
ParentDocumentRetriever开箱即用。
追问 2:语义切分中 embedding 相似度的阈值怎么确定?有没有自适应方法?
阈值通常通过验证集调参确定。比如在 NQ 数据集上,对每个文档计算相邻句子相似度分布,取中位数或 75 分位数作为初始阈值,然后网格搜索(0.5-0.9,步长 0.05)看检索召回率。自适应方法可以用动态阈值:对每个文档计算相似度序列的局部最小值,在这些最小值处切分。论文
Semantic Chunking via Gradient-Based Boundary Detection有详细方案,但实现复杂,工业界更常用固定阈值 + 人工校验。
追问 3:如果文档是 PDF 或扫描件,切分策略需要做什么调整?
PDF 需要先做 OCR 和布局分析。用
unstructured库的partition_pdf可以提取标题、段落、表格等结构。表格内容建议单独切分,因为表格的语义边界是行/列,不是自然语言。扫描件需要先用Tesseract或PaddleOCR做 OCR,然后按文本块(text block)切分,而不是按字符。坑是 OCR 后的文本可能丢失顺序,需要做版面还原(layout reconstruction),比如用layoutparser库。
5️⃣ 避坑 · 常见错误答法
- ❌ 只背策略名称,不解释 trade-off:“有固定切分、递归切分、语义切分三种。” → ✅ 必须补充每种策略的适用场景和坑,比如“固定切分简单但语义断裂严重,需要用 overlap 缓解;递归切分尊重结构但段落长度不均时仍需截断。”
- ❌ 认为语义切分是银弹:“用 LLM 判断语义边界,完美解决所有问题。” → ✅ 指出语义切分的成本高、阈值敏感,并给出替代方案(如多粒度索引)和工程取舍(“在延迟敏感场景下,递归切分 + overlap 比语义切分更实用”)。
6️⃣ 简历呼应
- 如果你有 RAG 项目:从“我在项目中对比了固定切分和递归切分,发现递归切分 + 10% overlap 在检索召回率上提升 15%,但存储增加了 20%”切入,展示你做过实验并理解 trade-off。
- 如果你只做过传统 NLP:用“文档切分类似于文本摘要中的句子分割,但 RAG 场景下需要兼顾检索效率”类比,然后提到你熟悉
spaCy的句子边界检测,可以迁移到 chunking 中。 - 如果你是校招无项目:聚焦“我在论文复现中实现了基于 embedding 相似度的语义切分,在 NQ 数据集上验证了多粒度索引的有效性”,展示你对前沿方法的理解和动手能力。
- LangChain 文档:
RecursiveCharacterTextSplitter源码与参数调优 - LlamaIndex 博客:
Parent Document Retriever实现原理 - 论文:
Semantic Chunking for RAG: A Comparative Study(2024) - 工具:
unstructured库的 PDF/文档解析文档 - 博客:
Chunking Strategies for RAG: From Fixed to Semantic(Medium, 2024)