智能分片
文档转换完成后,需要进行分片处理。Knowledge 服务采用 结构感知分片 策略,而非简单的字符切分。
设计理念
传统分片的问题
传统方式按固定字符数切分,存在以下问题:
- ❌ 切断语义完整的段落 — 在段落中间切断
- ❌ 丢失章节上下文 — 不知道分片属于哪个章节
- ❌ 检索结果缺乏连贯性 — 返回的分片语义不完整
结构感知分片的优势
Knowledge 的分片策略:
- ✅ 识别章节结构 — 解析 Markdown 标题层级
- ✅ 保留语义边界 — 按章节/段落自然分割
- ✅ 追踪原文位置 — 记录每个分片的字符位置
- ✅ 递归细分 — 大章节自动按字符数细分
分片算法
第一步: 解析 Markdown 结构
系统解析 Markdown 的标题层级,构建章节树:
markdown
# 第一章 概述 ← 一级标题
本章介绍分布式锁的基本概念...
## 1.1 背景 ← 二级标题
分布式系统的核心问题是...
## 1.2 目标 ← 二级标题
分布式锁需要保证...
### 1.2.1 互斥性 ← 三级标题
同一时刻只有一个客户端能获取锁...
# 第二章 设计 ← 一级标题
本章介绍分布式锁的设计原则...解析后的章节树:
第一章 概述
├── 1.1 背景
└── 1.2 目标
└── 1.2.1 互斥性
第二章 设计第二步: 按章节分片
系统按章节结构分割文档,每个章节作为一个候选分片:
分片 1: 第一章 概述 + 1.1 背景 + 1.2 目标 + 1.2.1 互斥性
分片 2: 第二章 设计第三步: 递归细分
如果某个章节内容过大 (超过 chunk-size),递归按子章节细分:
分片 1: 第一章 概述 + 1.1 背景 (字符数: 450)
分片 2: 1.2 目标 + 1.2.1 互斥性 (字符数: 680)
分片 3: 第二章 设计 (字符数: 920)降级方案
如果文档没有 Markdown 标题结构,降级为 按段落分割:
java
// 伪代码
if (sections.isEmpty()) {
// 无章节结构,按段落分割
return splitByParagraphs(documentId, mdContent, config);
}分片示例
输入文档
markdown
# 分布式锁指南
## 什么是分布式锁
分布式锁是分布式系统中用于协调多个节点访问共享资源的机制。它保证在同一时刻只有一个客户端能获取锁并执行操作。
## 分布式锁的特性
### 互斥性
同一时刻只能有一个客户端持有锁。这是分布式锁最基本的特性。
实现方式包括:
- 基于 Redis 的 SETNX 命令
- 基于 ZooKeeper 的临时顺序节点
- 基于数据库的唯一约束
### 可重入性
同一个客户端可以重复获取已持有的锁,不会发生死锁。
### 锁超时
锁需要设置超时时间,防止客户端崩溃后锁无法释放。分片结果
假设 chunk-size = 500,分片结果:
Chunk 1:
sectionPath: "分布式锁指南 > 什么是分布式锁"
content: "分布式锁是分布式系统中用于协调多个节点访问共享资源的机制..."
charStartIndex: 0
charEndIndex: 85
Chunk 2:
sectionPath: "分布式锁指南 > 分布式锁的特性 > 互斥性"
content: "同一时刻只能有一个客户端持有锁。这是分布式锁最基本的特性。实现方式包括..."
charStartIndex: 86
charEndIndex: 258
Chunk 3:
sectionPath: "分布式锁指南 > 分布式锁的特性 > 可重入性"
content: "同一个客户端可以重复获取已持有的锁,不会发生死锁。"
charStartIndex: 259
charEndIndex: 315
Chunk 4:
sectionPath: "分布式锁指南 > 分布式锁的特性 > 锁超时"
content: "锁需要设置超时时间,防止客户端崩溃后锁无法释放。"
charStartIndex: 316
charEndIndex: 370位置追踪
每个分片记录在原文中的精确位置,用于检索结果高亮:
分片数据模型
java
public class DocumentChunk {
private String documentId; // 文档 ID
private int chunkIndex; // 分片序号
private String content; // 分片内容
private String sectionPath; // 章节路径: "第一章 > 1.1 背景"
private String sectionTitle; // 章节标题: "1.1 背景"
private int charStartIndex; // 原文起始字符位置
private int charEndIndex; // 原文结束字符位置
private int wordCount; // 字符数
}高亮定位
检索时返回命中分片的位置信息,前端可据此高亮原文:
json
{
"documentId": "doc-001",
"content": "分布式锁是...",
"sectionTitle": "什么是分布式锁",
"highlightRanges": [
{ "start": 0, "end": 85 }
]
}分片配置
yaml
molandev:
rag:
splitting:
# 分片大小 (字符数), 超过此大小的章节会递归细分
chunk-size: 1000
# 分片重叠大小 (字符数), 避免边界截断
overlap-size: 200
# 最小分片大小, 小于此大小的分片会被过滤
min-chunk-size: 100调优建议
| 场景 | chunk-size | 说明 |
|---|---|---|
| 短文档 (FAQ) | 300-500 | 保持语义完整性 |
| 技术文档 | 800-1200 | 平衡语义与检索精度 |
| 长文档 (书籍) | 1000-1500 | 配合上下文补全 |
分片存储
分片结果存储到 MySQL:
sql
INSERT INTO kl_document_chunk (
document_id,
library_id,
content,
position,
char_start_index,
char_end_index,
word_count,
section_path,
section_title,
status -- PENDING
) VALUES (...);分片状态:
PENDING— 待向量化VECTORIZED— 已向量化FAILED— 向量化失败
手动管理分片
系统支持手动新增或编辑分片,用于手工优化检索结果:
- 新增分片 — 手工补充特殊知识片段
- 编辑分片 — 优化分片内容,提升检索质量
- 删除分片 — 移除过时或错误的分片