Skip to content

智能分片

文档转换完成后,需要进行分片处理。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 — 向量化失败

手动管理分片

系统支持手动新增或编辑分片,用于手工优化检索结果:

  • 新增分片 — 手工补充特殊知识片段
  • 编辑分片 — 优化分片内容,提升检索质量
  • 删除分片 — 移除过时或错误的分片

下一步