一篇讲透 Chunk 切分:RAG 知识库为什么不是“随便切一刀”?
做 RAG 知识库很多人第一步就做错了。他们以为把 Word、PDF、网页、Markdown 转成文本然后每 500 字切一段丢进向量库就完成了知识入库。但真正上线后会发现用户问得很具体系统召回不到用户问一个流程答案只拿到中间一步用户问制度条款模型只看到半句话用户问长文档总结检索出来的内容前后断裂用户问“这个操作怎么做”模型把第 3 步当成第 1 步回答。问题往往不是模型不行也不是向量库不行而是Chunk 切分策略太粗糙。Chunk 不是简单分段而是 RAG 系统的“知识颗粒度设计”。一、什么是 Chunk为什么它这么重要Chunk 可以理解为把一篇大文档切成一块一块可检索、可召回、可喂给大模型的小知识片段。比如一篇 30 页的产品手册大模型不可能每次都读全文向量库也不适合直接存整篇文章。所以要先切分成多个 Chunk原始文档 ↓ 章节 ↓ 小节 ↓ 段落 ↓ Chunk ↓ Embedding ↓ 向量库用户提问时系统不是找整篇文档而是先找最相关的 Chunk。所以 Chunk 切得好不好直接决定三个东西第一能不能召回到正确内容。第二召回内容上下文是否完整。第三大模型回答时会不会断章取义。LangChain 文档也提到过类似矛盾检索时希望文档块足够小这样 Embedding 更精准但又希望文档块足够大这样上下文不丢失。Parent Document Retriever 就是为了解决“小块精准召回、大块恢复上下文”的问题。二、为什么不能直接按固定字数切很多初学者会这样切每 500 字切一块 每块重叠 50 字这确实简单但问题也很明显。比如原文是一、开户流程 第一步用户提交身份证、银行卡、手机号。 第二步系统进行实名校验。 第三步风控系统判断用户是否命中黑名单。 第四步校验通过后生成账户。 第五步发送开户成功通知。如果机械切分可能变成Chunk 1 第一步用户提交身份证、银行卡、手机号。 第二步系统进行实名校验。 Chunk 2 第三步风控系统判断用户是否命中黑名单。 第四步校验通过后生成账户。 Chunk 3 第五步发送开户成功通知。用户问“开户完整流程是什么”系统可能只召回 Chunk 2因为里面有“风控”“账户”等关键词。大模型拿到的上下文只有第 3 步和第 4 步于是回答就不完整。这就是典型的切分破坏流程结构。固定长度切分适合兜底但不适合直接作为企业级知识库的主策略。三、Chunk 切分的核心原则不是按长度切而是按知识单元切好的 Chunk 应该满足四个要求1、语义完整一个 Chunk 最好表达一个完整意思。比如差 “用户提交材料后系统会进行实名校验。如果校验失败……” 好 “实名认证规则用户提交身份证、银行卡、手机号后系统会校验三要素是否一致。校验失败时返回失败原因并提示用户重新提交。”前者被切断了后者是完整知识点。2、边界清晰Chunk 最好有明确来源文档名称开户操作手册 章节二、开户注册流程 小节2.1 实名认证 段落实名认证规则这样召回后大模型知道这段内容来自哪里。3、颗粒度适中太小召回精准但上下文不足。太大上下文完整但 Embedding 语义变稀释。所以企业级知识库通常不会只保留一种 Chunk而是会有Small Chunk用于精准召回 Parent Chunk用于上下文恢复 Section Chunk用于章节级理解 Document Metadata用于过滤和溯源4、结构信息不能丢尤其是流程类、制度类、合同类、产品手册类文档不能只存纯文本还要存元数据{ docId: doc_001, title: 开户操作手册, sectionTitle: 开户注册流程, chunkType: process_step, stepNo: 3, totalSteps: 5, parentId: parent_001 }这样系统才知道这是第 3 步不是孤立的一句话。四、按语义切分让一个 Chunk 表达一个完整意思语义切分的核心是不要让一句话、一段逻辑、一个规则被切断。比如下面这段制度用户连续输错密码 5 次后系统会临时锁定账户 30 分钟。 如果用户在锁定期间再次尝试登录系统不会重新计算锁定时间。 用户可以通过短信验证码、人脸识别或客服人工审核方式解锁账户。比较好的切法是Chunk 账户锁定规则用户连续输错密码 5 次后系统会临时锁定账户 30 分钟。锁定期间再次尝试登录不会重新计算锁定时间。用户可通过短信验证码、人脸识别或客服人工审核方式解锁账户。不要切成Chunk 1 用户连续输错密码 5 次后系统会临时锁定账户 30 分钟。 Chunk 2 如果用户在锁定期间再次尝试登录系统不会重新计算锁定时间。 Chunk 3 用户可以通过短信验证码、人脸识别或客服人工审核方式解锁账户。因为用户问“账户被锁怎么办”系统可能只召回 Chunk 3却不知道为什么被锁、锁多久。语义切分更适合这些内容制度条款 业务规则 FAQ 问答 产品说明 异常处理说明 接口字段说明五、按标题切分标题是天然的知识边界很多文档本身就有标题结构一、账户管理 1.1 开户 1.2 销户 1.3 冻结账户 二、交易管理 2.1 转账 2.2 充值 2.3 提现这类文档不能只按字数切而应该优先按标题切。比如标题路径 账户管理 开户 实名认证每个 Chunk 都带上标题路径{ docTitle: 账户管理手册, heading1: 账户管理, heading2: 开户, heading3: 实名认证, content: 用户开户时需要完成三要素实名认证…… }这样用户问开户时实名认证失败怎么办系统不仅能召回正文还能带上标题上下文账户管理 开户 实名认证LangChain 的 MarkdownHeaderTextSplitter 就是典型的标题切分思路它会根据 Markdown 标题拆分文档并把标题作为元数据保留下来拆完后还可以继续用其他 splitter 控制 Chunk 大小。六、按章节切分长文档必须保留章节结构长文档最大的问题是局部内容有用但离开章节背景就容易误解。比如一份风控规则文档三、黑名单规则 3.1 命中身份证黑名单 …… 3.2 命中手机号黑名单 …… 3.3 命中设备指纹黑名单 ……用户问什么情况下会被风控拦截如果只召回 3.2答案就不完整。所以长文档一般要设计多层结构Document ├── Section三、黑名单规则 │ ├── Chunk3.1 身份证黑名单 │ ├── Chunk3.2 手机号黑名单 │ └── Chunk3.3 设备指纹黑名单小 Chunk 用于精准召回。Section 用于恢复章节上下文。这就是 Section Retrieval 的核心思想用户问具体问题时先召回小 Chunk如果发现多个小 Chunk 都属于同一个章节就把整个章节或章节摘要一起带给大模型。七、按流程切分流程类文档必须记录 stepNo 和 totalSteps流程类文档是最容易切坏的。比如一、退款流程 第一步用户提交退款申请。 第二步系统校验订单状态。 第三步判断是否超过退款期限。 第四步调用支付渠道退款。 第五步更新订单状态。 第六步发送退款结果通知。如果普通切分系统只知道每个 Chunk 是一句话。但它不知道这是流程也不知道这是第几步。企业级做法应该是{ chunkType: process_step, processName: 退款流程, stepNo: 4, totalSteps: 6, stepTitle: 调用支付渠道退款, content: 第四步调用支付渠道退款。系统根据原支付渠道发起退款请求并记录渠道返回码。, prevStep: 3, nextStep: 5, parentProcessId: refund_process_001 }这样用户问退款流程第四步失败怎么办系统能精准召回第 4 步。用户问退款完整流程是什么系统可以根据processName 退款流程 stepNo 1 到 6 totalSteps 6把完整流程拼回来。这就是流程类文档切分的关键流程不是普通文本流程必须结构化。八、Small Chunk用于精准召回Small Chunk 的作用是让系统更容易命中用户问题。比如用户问退款失败后订单状态怎么处理如果 Chunk 太大里面同时包含退款申请、风控校验、渠道退款、订单状态、通知等内容Embedding 会变得很“泛”不够精准。Small Chunk 可以只保留一个知识点订单状态处理规则支付渠道退款失败时订单状态保持为“退款处理中”系统记录失败原因并进入重试队列。这种小块非常适合向量检索。但 Small Chunk 也有缺点上下文不完整。所以不能只用 Small Chunk还要配合 Parent Chunk。九、Parent Chunk用于完整上下文恢复Parent Chunk 可以理解为 Small Chunk 的上级内容。例如Parent Chunk 退款流程完整章节 Small Chunk 1 用户提交退款申请 Small Chunk 2 系统校验订单状态 Small Chunk 3 调用支付渠道退款 Small Chunk 4 退款失败后的订单状态处理检索时先用 Small Chunk 命中问题用户问题退款失败后订单状态怎么处理 命中 Small Chunk 4然后系统根据 Small Chunk 里的 parentId 找到 Parent ChunkparentId refund_section_001最后把 Parent Chunk 或 Parent Chunk 中相关部分交给大模型。LangChain 的 ParentDocumentRetriever 就是这个思路先检索小块再根据小块找到它所属的父文档或大块内容并返回从而平衡精准召回和上下文完整性。一句话总结Small Chunk 负责“找得准” Parent Chunk 负责“讲得全”十、Window Retrieval召回命中点附近的上下文Window Retrieval 适合解决一种常见问题召回到的句子是对的但前后文不够。比如命中句子是系统会进入人工审核。单看这句话大模型不知道为什么进入人工审核什么情况下进入人工审核人工审核之后做什么所以需要把它前后几句话一起取出来上一句如果用户提交的身份证信息与银行卡信息不一致系统会判定为高风险。 命中句系统会进入人工审核。 下一句人工审核通过后用户可继续完成开户流程。这就是 Window Retrieval。LlamaIndex 的 Sentence Window 思路就是检索时可以命中一个句子但通过 metadata replacement 把该句子替换成它周围的上下文窗口从而让模型看到更完整的语境。企业里可以这样设计{ chunkId: chunk_1001, content: 系统会进入人工审核。, windowContent: 如果用户提交的身份证信息与银行卡信息不一致系统会判定为高风险。系统会进入人工审核。人工审核通过后用户可继续完成开户流程。, windowSize: 1 }检索用 content。生成答案用 windowContent。这就能做到召回精准 上下文完整 回答不容易断章取义十一、Parent Retrieval小块召回大块回答Parent Retrieval 和 Window Retrieval 有点像但范围更大。Window Retrieval 通常取命中点前后几句。Parent Retrieval 通常取命中 Chunk 所属的父级段落、章节或文档片段。比如Small Chunk 支付渠道退款失败时订单状态保持为“退款处理中”。 Parent Chunk 退款流程章节包括退款申请、订单校验、渠道退款、失败重试、状态更新、通知用户。用户问退款失败后系统怎么处理系统流程1、用用户问题检索 Small Chunk 2、命中“退款失败状态处理” 3、根据 parentId 找到退款流程章节 4、把相关 Parent Chunk 给大模型 5、大模型生成完整答案这样回答会更完整退款失败后系统不会立即关闭退款单而是将订单状态保持为“退款处理中”记录渠道失败原因进入重试队列。如果多次重试失败则转人工处理并向用户展示处理中状态。如果只给 Small Chunk模型可能只能回答一句话。十二、Section Retrieval按章节恢复完整语境Section Retrieval 适合处理长文档。比如用户问黑名单规则有哪些如果只召回一个 Chunk可能只看到“手机号黑名单”。但实际上完整答案应该包含身份证黑名单 手机号黑名单 银行卡黑名单 设备指纹黑名单 IP 黑名单 账户行为黑名单Section Retrieval 的做法是1、先召回多个 Small Chunk 2、判断它们属于哪个 section 3、如果同一个 section 命中数量较多 4、返回整个 section 或 section 摘要比如命中结果chunk_01身份证黑名单 chunk_02手机号黑名单 chunk_03设备指纹黑名单它们都属于sectionId risk_blacklist_rules sectionTitle 黑名单规则那系统就可以把整个“黑名单规则”章节拿出来。这比单纯 topK 检索更稳定。十三、长文档如何处理不能一次切到底长文档处理要分层。不要这样一篇 100 页 PDF ↓ 每 500 字切一块 ↓ 全部丢向量库更好的方式是文档级 ↓ 章节级 ↓ 段落级 ↓ 语义 Chunk ↓ 句子窗口可以设计成{ docId: doc_001, docTitle: 风控规则说明书, sectionId: sec_003, sectionTitle: 黑名单规则, parentChunkId: parent_003_001, chunkId: chunk_003_001_004, chunkType: rule, content: 手机号命中黑名单时系统会拒绝开户申请。, windowContent: 用户提交开户申请后系统会校验手机号。如果手机号命中黑名单系统会拒绝开户申请并提示用户联系客服。, metadata: { businessType: 开户, ruleType: 黑名单, sourcePage: 12 } }这样每个 Chunk 都不是孤立文本而是带着完整身份。十四、推荐的企业级 Chunk 入库流程一个比较完整的流程可以这样做1、文档上传 ↓ 2、格式解析Word / PDF / HTML / Markdown ↓ 3、版面识别标题、表格、段落、图片、页码 ↓ 4、结构化解析章节、标题、列表、流程、表格 ↓ 5、清洗去页眉页脚、去重复、修复断行 ↓ 6、标题切分按一级、二级、三级标题建立层级 ↓ 7、语义切分把章节内容切成完整知识点 ↓ 8、流程识别提取 stepNo、totalSteps、processName ↓ 9、生成 Small Chunk ↓ 10、生成 Parent Chunk ↓ 11、生成 Section Chunk 或章节摘要 ↓ 12、Embedding 向量化 ↓ 13、写入向量库 元数据索引 ↓ 14、检索时执行 Small Chunk Parent / Window / Section 恢复这套流程的核心不是“切得越碎越好”而是检索时要精准 回答时要完整 溯源时要清楚 流程时要有顺序十五、Chunk 元数据应该怎么设计一个生产级 Chunk 不应该只有 content。建议至少包含这些字段{ chunkId: 唯一 Chunk ID, docId: 文档 ID, docTitle: 文档标题, sectionId: 章节 ID, sectionTitle: 章节标题, headingPath: 一级标题 二级标题 三级标题, parentChunkId: 父 Chunk ID, chunkType: rule / faq / process_step / table / paragraph, content: 用于向量化的小块内容, windowContent: 命中点前后窗口内容, parentContent: 父级上下文, pageNo: 来源页码, sourceType: pdf / word / markdown / html, businessTag: 业务标签, stepNo: 流程第几步, totalSteps: 流程总步数, createdAt: 创建时间, version: 文档版本 }为什么要这么多字段因为检索不只是向量相似度。真实业务里经常需要按文档过滤 按业务线过滤 按版本过滤 按章节过滤 按流程顺序恢复 按页码溯源 按 chunkType 做不同策略没有元数据后面很难做精细化检索。十六、不同类型文档切分策略不一样1、FAQ 文档FAQ 最好按问答对切Q账户被冻结怎么办 A用户可以通过 App 提交解冻申请……不要把多个问答切在一个 Chunk 里。推荐{ chunkType: faq, question: 账户被冻结怎么办, answer: 用户可以通过 App 提交解冻申请…… }2、制度规则文档按规则点切规则名称 适用范围 触发条件 处理方式 例外情况3、流程文档按流程步骤切并记录processName stepNo totalSteps prevStep nextStep4、接口文档按接口或字段切接口名称 请求参数 返回参数 错误码 调用示例5、表格文档表格不能简单转成一堆文本。要保留表头 行列关系 单位 备注 表格标题比如产品 | 费率 | 生效时间 A产品 | 0.3% | 2025-01-01 B产品 | 0.5% | 2025-02-01不能切成A产品 0.3% B产品 0.5%否则大模型不知道 0.3% 是什么。十七、Chunk 大小怎么选没有一个绝对标准但可以按场景理解。1、短 FAQ200500 字适合精准问答。2、业务规则300800 字保证规则完整不要切断条件和处理方式。3、流程步骤每一步一个 Chunk同时保留整个流程的 Parent Chunk。4、长章节Small Chunk300600 字 Parent Chunk10002000 字 Section Chunk整个章节或章节摘要LlamaIndex 的层级解析也体现了类似思路可以把文档解析成多层节点比如较大的 2048 级别、中等的 512 级别、更小的 128 级别子节点会关联到父节点。重点不是数字本身而是层级思想小块用于检索 大块用于上下文 章节用于完整理解十八、Overlap 重叠要不要加要加但不能乱加。Overlap 的作用是防止边界处信息丢失。比如Chunk 1 结尾用户连续输错密码 5 次后系统会锁定账户。 Chunk 2 开头锁定时间为 30 分钟。如果没有重叠用户问“锁定多久”可能只召回 Chunk 1答案不完整。加一点重叠后Chunk 1 用户连续输错密码 5 次后系统会锁定账户锁定时间为 30 分钟。 Chunk 2 锁定时间为 30 分钟。用户可以通过短信验证码解锁。但 Overlap 太大也不好存储变多 召回重复 上下文浪费 答案容易啰嗦一般建议普通段落10%20% 重叠 流程步骤不建议靠 overlap应该靠 stepNo 关联 章节文档靠 parentId 和 sectionId 恢复上下文十九、检索时应该怎么组合一个成熟的检索链路可以这样设计用户问题 ↓ Query Rewrite问题改写 ↓ 向量召回 Small Chunk ↓ 关键词召回补充 ↓ 元数据过滤 ↓ Rerank 精排 ↓ 判断 Chunk 类型 ↓ 如果是流程按 stepNo 补全前后步骤 ↓ 如果是长文档走 Parent Retrieval ↓ 如果是句子命中走 Window Retrieval ↓ 如果多个 Chunk 属于同一章节走 Section Retrieval ↓ 组装上下文 ↓ 大模型生成答案 ↓ 返回答案 引用来源这里的关键是召回阶段不要给太大内容 生成阶段不要只给小碎片二十、一个完整案例退款流程文档怎么切原文退款流程 第一步用户提交退款申请填写退款原因。 第二步系统校验订单状态只有已支付订单允许退款。 第三步系统判断是否超过退款期限。 第四步调用支付渠道发起退款。 第五步如果退款成功更新订单状态为已退款。 第六步如果退款失败订单状态保持退款处理中并进入重试队列。 第七步系统通过短信和站内信通知用户退款结果。1、Small Chunk{ chunkType: process_step, processName: 退款流程, stepNo: 6, totalSteps: 7, content: 第六步如果退款失败订单状态保持退款处理中并进入重试队列。 }2、Parent Chunk{ chunkType: process_parent, processName: 退款流程, content: 退款流程包括用户提交退款申请、系统校验订单状态、判断退款期限、调用支付渠道、成功后更新为已退款、失败后保持退款处理中并进入重试队列、通知用户退款结果。 }3、Window Content{ stepNo: 6, windowContent: 第五步如果退款成功更新订单状态为已退款。第六步如果退款失败订单状态保持退款处理中并进入重试队列。第七步系统通过短信和站内信通知用户退款结果。 }4、用户提问退款失败后订单是什么状态5、检索结果命中 Small Chunk第六步如果退款失败订单状态保持退款处理中并进入重试队列。6、上下文恢复补充 Window第五步退款成功更新为已退款。 第六步退款失败保持退款处理中并进入重试队列。 第七步通知用户退款结果。7、最终回答退款失败后订单不会直接变成已退款也不会关闭而是保持“退款处理中”状态并进入系统重试队列。系统后续会继续尝试退款并通过短信或站内信通知用户结果。这个答案就比单句回答更完整。二十一、常见错误为什么很多 RAG 系统回答不好1、只按字数切结果流程断了规则断了标题丢了。2、只存 content不存 metadata结果无法按文档、章节、业务线、版本过滤。3、Chunk 太大结果召回不精准Embedding 表达变模糊。4、Chunk 太小结果召回片段太碎大模型回答缺上下文。5、流程文档不记录步骤结果大模型不知道前后顺序容易乱答。6、长文档没有 Parent / Section结果只能回答局部不能回答完整章节问题。7、表格直接转纯文本结果行列关系丢失费率、时间、条件对应不上。二十二、企业级推荐方案多粒度 Chunk 元数据 上下文恢复最终推荐架构是Small Chunk 负责精准召回 Window Chunk 负责补充前后文 Parent Chunk 负责恢复段落级上下文 Section Chunk 负责恢复章节级上下文 Metadata 负责过滤、排序、溯源、流程恢复可以理解为Small Chunk 是“搜索命中点” Parent Chunk 是“上下文说明书” Section Chunk 是“章节地图” Metadata 是“导航系统”只要这四层做好RAG 的准确率、完整性、可解释性都会明显提升。二十三、总结Chunk 切分不是简单的文本切割而是知识库建设里最核心的工程能力之一。普通系统会问一段切多少字成熟系统会问这个文档是什么类型 它的标题结构是什么 它有没有流程 它有没有表格 它的最小知识单元是什么 它的父级上下文是什么 用户问具体问题时怎么精准召回 用户问完整流程时怎么恢复全貌真正好用的 RAG 系统一定不是只靠大模型硬答而是在入库阶段就把知识结构设计好。最后用一句话总结Small Chunk 解决“找得准”Parent Chunk 解决“讲得全”Window Retrieval 解决“不断章取义”Section Retrieval 解决“长文档完整理解”流程类文档必须记录 stepNo 和 totalSteps。Chunk 切分做得越细RAG 系统上线后越稳。切分阶段偷懒后面就只能靠 Prompt、Rerank、模型能力不断补坑。