RAG 文档切片实战:国标知识库篇(一)——基础切片

RAG 文档切片实战:国标知识库篇(一)——基础切片 面向场景将 GB/T 50001-2017、GB/T 18229-2000、GB/T 50104-2010 等国标文档接入 RAG 系统时如何选择切片策略本文通过真实国标条文示例对比固定长度切片与递归切片的效果并给出可直接落地的 Python 实现。一、固定长度切片Fixed-Length Chunking1.1 核心思想按字符数或 token 数硬切比如每 1000 字符一块。不管内容边界在哪到了长度就切。1.2 国标示例假设从 GB/T 18229-2000 提取出这样一段原始文本4.2.1 CAD工程制图应根据专业、用途、内容等划分图层并采用统一的图层命名规则。图层名宜采用英文字母、数字和连字符组成。4.2.2 图层命名宜采用以下格式专业代码-功能代码-序号。专业代码应符合表 4.2.2 的规定。表 4.2.2 专业代码对照表 | 专业代码 | 含义 | A | 建筑 | S | 结构 | M | 暖通 | P | 给排水 | E | 电气 | 4.2.3 图层颜色应与专业代码对应便于识图和管理...设chunk_size60overlap0切出来Chunk 1: 4.2.1 CAD工程制图应根据专业、用途、内容等划分图层并采用统一的图层命名规则。图层名宜采用英文字母、数字和连字符组成。 Chunk 2: 4.2.2 图层命名宜采用以下格式专业代码-功能代码-序号。专业代码应符合表 4.2.2 的规定。表 4.2.2 专 Chunk 3: 业代码对照表 | 专业代码 | 含义 | A | 建筑 | S | 结构 | M | 暖通 | P | 给排水 | Chunk 4: E | 电气 | 4.2.3 图层颜色应与专业代码对应便于识图和管理...1.3 用户提问暴露的缺陷用户问“表 4.2.2 里 P 代表什么专业”问题表现条文编号被切断4.2.2 和它的表格被拆到不同 chunk表格被切碎表头在 Chunk 2内容在 Chunk 3尾在 Chunk 4上下文丢失Chunk 3 里的 “A | 建筑”不知道这是表 4.2.2 的专业代码引用关系断裂Chunk 1 提到应符合表 4.2.2 的规定但表格内容在别的 chunk检索结果系统可能命中 Chunk 3“P | 给排水”但 LLM 看到的是一个没有表头、没有条文引用的孤立片段无法确认 P 的含义甚至不知道这是哪张表。1.4 Python 实现 fixed_length_chunker.py 固定长度切片器 —— 最基础的实现 fromtypingimportListclassFixedLengthChunker:def__init__(self,chunk_size:int1000,overlap:int0): Args: chunk_size: 每块最大字符数 overlap: 相邻块之间的重叠字符数 self.chunk_sizechunk_size self.overlapoverlapdefsplit(self,text:str)-List[dict]: 将文本切成固定长度的块 Returns: 包含 chunk 文本和元数据的字典列表 chunks[]start0text_lenlen(text)chunk_idx0whilestarttext_len:endmin(startself.chunk_size,text_len)chunk_texttext[start:end]chunks.append({chunk_id:fchunk_{chunk_idx},chunk_type:fixed_length,content:chunk_text,start:start,end:end,metadata:{}})# 步进chunk_size - overlap保证重叠窗口startself.chunk_size-self.overlap chunk_idx1returnchunks# 使用示例 if__name____main__:text(4.2.1 CAD工程制图应根据专业、用途、内容等划分图层并采用统一的图层命名规则。图层名宜采用英文字母、数字和连字符组成。4.2.2 图层命名宜采用以下格式专业代码-功能代码-序号。专业代码应符合表 4.2.2 的规定。表 4.2.2 专业代码对照表 | 专业代码 | 含义 | A | 建筑 | S | 结构 | M | 暖通 | P | 给排水 | E | 电气 | 4.2.3 图层颜色应与专业代码对应便于识图和管理。)chunkerFixedLengthChunker(chunk_size60,overlap10)chunkschunker.split(text)forcinchunks:print(f[{c[chunk_id]}] ({len(c[content])}chars))print(c[content])print(-*50)输出[chunk_0] (60 chars) 4.2.1 CAD工程制图应根据专业、用途、内容等划分图层并采用统 -------------------------------------------------- [chunk_1] (60 chars) 图层并采用统一的图层命名规则。图层名宜采用英文字母、数字和 -------------------------------------------------- [chunk_2] (60 chars) 、数字和连字符组成。4.2.2 图层命名宜采用以下格式专业代码- -------------------------------------------------- ...1.5 加入 Overlap 后的改善与局限chunkerFixedLengthChunker(chunk_size60,overlap20)Overlap 能缓解句子被拦腰切断的问题但对国标结构性问题无能为力问题Overlap 能改善原因短句被切到边界✅ 可以简单重复检索不受影响表格被切碎❌ 不行只能挪断裂点表格还是两半条文和引用分离❌ 不行引用是语义依赖不是字数问题层级结构丢失❌ 不行overlap 不恢复标题层级邻接条文逻辑链断裂⚠️ 看运气刚好落在 overlap 区能救不保证二、递归切片Recursive Character Text Splitting2.1 核心思想按分隔符的优先级层级逐层切分而不是硬按字数切。优先级队列第一优先段落分隔符\n\n 第二优先换行符\n 第三优先句子分隔符。、.、!、? 第四优先其他标点、、 最后单字符逻辑先用大的分隔符切如果块还太大再用下一级分隔符继续切直到每块小于chunk_size。2.2 国标示例原始文本已按换行格式化4 图线 4.1 一般规定 4.1.1 图线宽度应根据图样的复杂程度和比例确定并应从下列线宽系列中选取0.18mm、0.25mm、0.35mm、0.5mm、0.7mm、1.0mm、1.4mm、2.0mm。 4.1.2 图线分为粗线、中粗线、细线。 4.2 图线宽度 4.2.1 粗线宽度宜为0.7mm或1.0mm。 4.2.2 中粗线宽度宜为0.5mm或0.35mm。 4.2.3 细线宽度宜为0.25mm或0.18mm。 表 4.2.3 图线宽度系列 | 线型 | 宽度(mm) | 用途 | | 粗线 | 0.7, 1.0 | 主要可见轮廓线 | | 中粗线 | 0.5, 0.35 | 可见轮廓线、尺寸线 | | 细线 | 0.25, 0.18 | 填充线、索引符号 | 4.3 图线画法 4.3.1 虚线、点画线应保持线段长度一致间距均匀。设chunk_size120递归切片后的结果Chunk 1: 4 图线 (5 chars) Chunk 2: 4.1 一般规定 (8 chars) Chunk 3: 4.1.1 图线宽度应根据图样的复杂程度和比例确定... (78 chars) Chunk 4: 4.1.2 图线分为粗线、中粗线、细线。 (22 chars) Chunk 5: 4.2 图线宽度 (7 chars) Chunk 6: 4.2.1 粗线宽度宜为0.7mm或1.0mm。 (25 chars) Chunk 7: 4.2.2 中粗线宽度宜为0.5mm或0.35mm。 (27 chars) Chunk 8: 4.2.3 细线宽度宜为0.25mm或0.18mm。 (27 chars) Chunk 9: 表 4.2.3 图线宽度系列\n| 线型 | 宽度(mm) |... (115 chars) Chunk 10: 4.3 图线画法 (7 chars) Chunk 11: 4.3.1 虚线、点画线应保持线段长度一致间距均匀。 (29 chars)2.3 改善了什么✅ 4.2.1 / 4.2.2 / 4.2.3 各自独立成块✅ 表 4.2.3 完整地在一个 chunk 里✅ 每个 chunk 边界都是自然语义边界✅ 章节标题单独成块2.4 用户提问暴露的缺陷用户问“第 4 章关于图线有哪些具体规定”问题表现章节标题成了孤儿 chunkChunk 1 只有4 图线5 个字没有内容检索时语义空虚层级关系完全丢失4.2.1 属于4.2 图线宽度、属于4 图线——这些信息没有记录表格和引用它的条文分离4.2.3 说细线宽度宜为 0.25 或 0.18表 4.2.3 列出了用途但两者只是相邻 chunk没有显式关系编号体系未被利用无法按chapter_no做 metadata filter无法回答列出第 4 章所有条文检索结果命中 4.2.1 时系统无法自动带上它所在的章节是图线宽度这个上下文。LLM 看到一条孤零零的条文不知道它在整个标准里的位置。2.5 Python 实现 recursive_chunker.py 递归切片器 —— 按分隔符优先级层级切分 fromtypingimportListclassRecursiveChunker: 递归字符切片器 分隔符优先级段落 换行 句子 标点 字符 # 默认分隔符优先级从大到小DEFAULT_SEPARATORS[\n\n,# 段落\n,# 换行。,# 中文句号,# 中文分号,# 中文冒号,# 中文逗号 ,# 空格,# 单字符兜底]def__init__(self,chunk_size:int1000,separators:List[str]None):self.chunk_sizechunk_size self.separatorsseparatorsorself.DEFAULT_SEPARATORSdefsplit(self,text:str)-List[dict]:入口递归切分文本returnself._split_recursive(text,0)def_split_recursive(self,text:str,sep_idx:int)-List[dict]: 递归切分核心逻辑 Args: text: 待切分文本 sep_idx: 当前使用的分隔符在优先级列表中的索引 # 如果文本已经够短直接返回iflen(text)self.chunk_size:return[{content:text}]iftext.strip()else[]# 如果已经用尽所有分隔符按字符硬切兜底ifsep_idxlen(self.separators):returnself._hard_split(text)separatorself.separators[sep_idx]chunks[]ifseparator:# 单字符兜底returnself._hard_split(text)# 按当前分隔符切分partstext.split(separator)current_chunkfori,partinenumerate(parts):# 还原分隔符最后一个后面不加candidatepartseparatorifilen(parts)-1elsepartiflen(current_chunk)len(candidate)self.chunk_size:current_chunkcandidateelse:# 当前 chunk 已经满了先存起来ifcurrent_chunk.strip():iflen(current_chunk)self.chunk_size:# 即使按当前分隔符切还是太长递归到下一级分隔符chunks.extend(self._split_recursive(current_chunk,sep_idx1))else:chunks.append({content:current_chunk.strip()})current_chunkcandidate# 处理最后剩余的文本ifcurrent_chunk.strip():iflen(current_chunk)self.chunk_size:chunks.extend(self._split_recursive(current_chunk,sep_idx1))else:chunks.append({content:current_chunk.strip()})returnchunksdef_hard_split(self,text:str)-List[dict]:按字符硬切最后兜底chunks[]foriinrange(0,len(text),self.chunk_size):chunktext[i:iself.chunk_size].strip()ifchunk:chunks.append({content:chunk})returnchunks# 使用示例 if__name____main__:text4 图线 4.1 一般规定 4.1.1 图线宽度应根据图样的复杂程度和比例确定并应从下列线宽系列中选取0.18mm、0.25mm、0.35mm、0.5mm、0.7mm、1.0mm、1.4mm、2.0mm。 4.1.2 图线分为粗线、中粗线、细线。 4.2 图线宽度 4.2.1 粗线宽度宜为0.7mm或1.0mm。 4.2.2 中粗线宽度宜为0.5mm或0.35mm。 4.2.3 细线宽度宜为0.25mm或0.18mm。 表 4.2.3 图线宽度系列 | 线型 | 宽度(mm) | 用途 | | 粗线 | 0.7, 1.0 | 主要可见轮廓线 | | 中粗线 | 0.5, 0.35 | 可见轮廓线、尺寸线 | | 细线 | 0.25, 0.18 | 填充线、索引符号 | 4.3 图线画法 4.3.1 虚线、点画线应保持线段长度一致间距均匀。chunkerRecursiveChunker(chunk_size120)chunkschunker.split(text)fori,cinenumerate(chunks):contentc[content]print(f[chunk_{i}] ({len(content)}chars))print(content[:80]...iflen(content)80elsecontent)print(-*50)输出[chunk_0] (5 chars) 4 图线 -------------------------------------------------- [chunk_1] (8 chars) 4.1 一般规定 -------------------------------------------------- [chunk_2] (78 chars) 4.1.1 图线宽度应根据图样的复杂程度和比例确定并应从下列线宽系列中选取... -------------------------------------------------- ...2.6 与 LangChain 的对应上述实现就是 LangChainRecursiveCharacterTextSplitter的核心逻辑简化版fromlangchain.text_splitterimportRecursiveCharacterTextSplitter text_splitterRecursiveCharacterTextSplitter(chunk_size120,chunk_overlap0,separators[\n\n,\n,。,,,, ,])chunkstext_splitter.split_text(text)三、两种基础切片方式对比维度固定长度递归切片核心逻辑按字数硬切按分隔符优先级切条文完整性❌ 容易切断✅ 自然边界保护表格完整性❌ 切碎✅ 通常完整若表格 chunk_size标题层级关系❌ 完全丢失❌ 仍然丢失章节-内容关联❌ 无❌ 无条文-表格引用❌ 分离⚠️ 相邻但无显式关系实现难度极简10 行代码简单LangChain 内置国标适用性❌ 不推荐⚠️ 可做预处理不建议做主方案四、下篇预告基础切片虽然实现简单但对国标这种强结构文档来说最大的痛点是层级关系完全丢失。下一篇将介绍两种结构化切片方案标题感知切片按章 节 条 表层级切保留完整路径父子切片Parent 保上下文 Child 做精确检索解决精确性 vs 完整性的矛盾系列文章索引一基础切片固定长度与递归切片 ← 本文二结构化切片标题感知与父子切片三特殊切片与最终方案表格感知、语义切片与国标场景推荐