中文RAG分块应该按字符还是Token?长度、重叠与边界完整性实测思路
文章摘要
中文RAG项目经常使用“每500个字符切一段”或“每300个Token切一段”,但字符数与Token数并不是同一个概念,不同模型的Tokenizer也可能给出不同结果。分块过小会丢失条件,过大则降低检索精度并增加上下文成本。本文解释字符、Token、句子和语义边界的区别,并给出中文制度、合同、产品手册和FAQ的分块参数建议与评测方法。
一、为什么字符数和Token数不能混用
字符数是字符串长度。
例如:
平台支持经销商收货与终端动销分析。中文字符、标点和英文字符都可以直接统计。
Token是模型分词器处理后的单位。
同一段内容在不同模型中可能被切成不同数量的Token。
影响因素包括:
- 模型Tokenizer;
- 中文词语;
- 英文缩写;
- 数字;
- 标点;
- JSON;
- 代码;
- 空格和换行。
因此:
500个中文字符 ≠ 固定500个Token如果向量模型和生成模型使用不同Tokenizer,差异还会更大。
二、按字符分块的优点与问题
优点
- 实现简单;
- 不依赖模型Tokenizer;
- 速度快;
- 参数直观;
- 适合预处理和初步切分。
示例:
defsplit_by_chars(text:str,chunk_size:int=500,overlap:int=80)->list[str]:chunks:list[str]=[]start=0whilestart<len(text):end=min(start+chunk_size,len(text))chunks.append(text[start:end])ifend==len(text):breakstart=end-overlapreturnchunks问题
- 可能从句子中间切开;
- 表格被拆断;
- 条款条件和结论分离;
- 代码结构损坏;
- 无法准确控制模型上下文;
- 不同文本密度差异大。
字符切分适合作为最后一道长度限制,不适合成为唯一规则。
三、按Token分块的优点与问题
优点
- 更接近模型真实上下文成本;
- 便于控制Embedding输入上限;
- 便于预算生成上下文;
- 中英文混合内容更可控。
伪代码:
defsplit_by_tokens(text:str,tokenizer,max_tokens:int,overlap_tokens:int)->list[str]:tokens=tokenizer.encode(text)chunks:list[str]=[]start=0whilestart<len(tokens):end=min(start+max_tokens,len(tokens))chunks.append(tokenizer.decode(tokens[start:end]))ifend==len(tokens):breakstart=end-overlap_tokensreturnchunks问题
- 仍可能切断语义;
- 依赖具体Tokenizer;
- 更换Embedding模型后参数可能变化;
- Token解码可能影响空格和格式;
- 处理成本高于字符计数。
Token长度控制的是容量,不等于保证内容完整。
四、真正重要的是语义边界
推荐切分优先级:
文档 → 章节 → 小节 → 条款 → 段落 → 句子 → Token或字符兜底例如制度文件:
第四章 差旅标准 4.1 交通标准 4.2 住宿标准 4.3 餐饮补贴应该先按章节和条款切分,再检查是否超过Token上限。
错误做法:
直接每500字符切开可能把4.2的适用对象留在前一块,把金额留在后一块。
五、重叠区间有什么作用
Overlap用于保留边界上下文。
例如:
Chunk 1:……申请人必须在出差前提交审批。 Chunk 2:提交审批后,由直属负责人审核……如果边界切在中间,重叠可以减少信息损失。
但重叠不是越大越好。
过大重叠会导致:
- 索引体积增加;
- 相似Chunk重复召回;
- 上下文重复;
- Token浪费;
- Reranker结果单一。
常见起点:
Overlap占Chunk的10%—20%但最终应以检索评测为准。
六、不同文档类型的建议
1. FAQ
一问一答天然是Chunk。
建议:
每个FAQ独立 保留分类和关键词 通常不需要重叠2. 企业制度
建议按:
章节 → 条款 → 子条款每个Chunk带:
- 制度名称;
- 版本;
- 章节标题;
- 条款编号;
- 生效日期。
3. 合同
建议按条款切分,不要把不同责任条款合并。
需要保留:
- 合同类型;
- 甲乙方;
- 条款号;
- 定义引用;
- 附件关系。
4. 产品手册
建议按功能或操作任务:
功能说明 前置条件 操作步骤 异常处理不要把多个完全不同功能放在同一Chunk。
5. API文档
建议按接口:
Method+Path 请求参数 响应参数 错误码 示例一个接口可以有父子Chunk。
6. 表格
不要直接按字符切表格。
应该:
- 保留表头;
- 每行带表头语义;
- 大表按业务分组;
- 必要时转为结构化JSON;
- 保留原始页码和表名。
七、推荐的两阶段分块
第一阶段:结构切分
Markdown标题 PDF章节 Word样式 条款编号 列表 表格第二阶段:长度控制
如果结构块超过上限,再按句子和Token切分。
伪代码:
defhierarchical_split(document:Document,tokenizer,max_tokens:int=400)->list[Chunk]:sections=split_by_structure(document)result:list[Chunk]=[]forsectioninsections:ifcount_tokens(section.text,tokenizer)<=max_tokens:result.append(to_chunk(section))continuesentences=split_sentences(section.text)result.extend(merge_sentences_by_token_limit(sentences,tokenizer,max_tokens=max_tokens,overlap_tokens=60))returnresult八、父子分块如何兼顾召回和完整性
小Chunk更容易精准召回,大Chunk更容易提供完整答案。
父子分块:
父Chunk:完整章节 子Chunk:段落或条款检索:
对子Chunk生成Embedding → 找到高相关子Chunk → 返回对应父Chunk或邻接内容适合:
- 制度;
- 合同;
- 长产品手册;
- 技术文档。
要避免父Chunk过大,否则上下文又会膨胀。
九、参数从哪里开始
以下只是起始值,不是通用答案。
| 文档 | 子Chunk起始范围 | Overlap |
|---|---|---|
| FAQ | 一问一答 | 0 |
| 制度条款 | 200—450 Token | 30—60 |
| 产品手册 | 300—600 Token | 50—100 |
| 合同 | 单个完整条款 | 视引用关系 |
| API文档 | 单接口或子模块 | 少量 |
| 代码 | 函数或方法 | 通常不用固定Overlap |
如果Embedding模型对长文本支持更好,也不代表应该无限扩大Chunk。
十、如何评测分块质量
准备真实问题,每个问题标注:
正确文档 正确条款 答案所需最小证据比较不同参数:
300 Token+50重叠 500 Token+80重叠 父子分块 语义分块指标:
- Answer-Bearing Recall@K;
- MRR;
- 重复Chunk比例;
- 平均上下文Token;
- 答案正确率;
- 忠实度;
- 查询延迟。
十一、字符数是否完全没用
不是。
字符数适合:
- 快速预估;
- 输入长度保护;
- 文本清洗;
- 不绑定模型的基础规则;
- 发现异常超长内容。
推荐:
结构边界决定怎么切 Token决定是否超限 字符数负责快速保护和监控十二、常见错误
1. 所有文档使用同一参数
FAQ、合同和代码不应使用相同规则。
2. 只看Chunk平均长度
还要看是否包含完整答案。
3. 重叠设置过大
造成重复召回。
4. 忽略标题和Metadata
Chunk正文短,但缺少章节语义。
5. 更换Embedding模型后不重测
Tokenizer和语义能力都可能变化。
总结
中文RAG分块不应该在“字符还是Token”之间二选一。
更合理的方式是:
先按文档结构和语义边界切分 → 再用Token控制模型上限 → 用字符数做保护 → 通过真实问题评测参数决定效果的核心不是Chunk恰好有多少字,而是答案所需的条件和结论是否完整地留在同一个可检索单元中。
