📝 文本分块算法
将长文本分割为语义完整的片段,RAG 系统预处理的关键步骤
📖 技术概述
🎯 什么是文本分块?
文本分块(Text Chunking/Segmentation)是将长文档分割成较小、语义完整的文本片段的过程。这是 RAG(检索增强生成)、语义搜索等系统的核心预处理步骤。
为什么需要分块?
- LLM 上下文限制 - 大模型有最大输入长度限制(如 4K、8K、128K tokens)
- 检索精度 - 小片段更容易精确匹配查询,减少噪声
- 计算效率 - 小片段向量化和相似度计算更高效
- 成本控制 - 只检索相关片段,减少 token 消耗
💡 核心挑战
- 语义完整性 - 避免在句子或概念中间切断
- 块大小平衡 - 太大包含噪声,太小丢失上下文
- 重叠处理 - 相邻块之间需要适当重叠以保持上下文连贯
- 多语言支持 - 不同语言的句子边界识别规则不同
- 特殊格式 - 代码、表格、公式等特殊内容的处理
🔧 主流算法
📏 固定长度分块
最简单的方法,按固定字符数或 token 数分割文本。
实现方式
- 设定块大小 chunk_size(如 500 字符或 500 tokens)
- 设定重叠大小 chunk_overlap(如 50 字符)
- 按固定步长(chunk_size - chunk_overlap)滑动窗口
原文:这是一段很长的文本内容,包含了多个句子和段落。我们需要将它分割成适当大小的片段...
这是一段很长的文本内容,包含了多个句子
包含了多个句子和段落。我们需要将它分割
我们需要将它分割成适当大小的片段。每个
查看代码示例
# 固定长度分块实现
def fixed_size_chunk(text, chunk_size=500, chunk_overlap=50):
"""
固定长度文本分块
Args:
text: 输入文本
chunk_size: 每块大小(字符数)
chunk_overlap: 块间重叠大小
Returns:
分块列表
"""
chunks = []
start = 0
while start < len(text):
end = start + chunk_size
chunk = text[start:end]
# 如果不是最后一块,尝试在句子边界处切断
if end < len(text):
# 查找最近的句子结束符
for sep in ['。', '!', '?', '.', '!', '?']:
last_sep = chunk.rfind(sep)
if last_sep > chunk_size * 0.5: # 至少在一半位置之后
chunk = chunk[:last_sep + 1]
end = start + last_sep + 1
break
chunks.append(chunk.strip())
start = end - chunk_overlap
return chunks
# 使用示例
text = """
人工智能是计算机科学的一个重要分支,它试图理解智能的实质,
并生产出一种新的能以人类智能相似的方式做出反应的智能机器。
该领域的研究包括机器人、语言识别、图像识别、自然语言处理和专家系统等。
人工智能从诞生以来,理论和技术日益成熟,应用领域也不断扩大。
"""
chunks = fixed_size_chunk(text, chunk_size=100, chunk_overlap=20)
for i, chunk in enumerate(chunks):
print(f"块 {i+1} ({len(chunk)}字符): {chunk[:50]}...")
// 固定长度分块实现
/**
* 固定长度文本分块
* @param {string} text - 输入文本
* @param {number} chunkSize - 每块大小(字符数)
* @param {number} chunkOverlap - 块间重叠大小
* @returns {string[]} 分块列表
*/
function fixedSizeChunk(text, chunkSize = 500, chunkOverlap = 50) {
const chunks = [];
let start = 0;
while (start < text.length) {
let end = start + chunkSize;
let chunk = text.slice(start, end);
// 如果不是最后一块,尝试在句子边界处切断
if (end < text.length) {
// 查找最近的句子结束符
const separators = ['。', '!', '?', '.', '!', '?'];
for (const sep of separators) {
const lastSep = chunk.lastIndexOf(sep);
if (lastSep > chunkSize * 0.5) { // 至少在一半位置之后
chunk = chunk.slice(0, lastSep + 1);
end = start + lastSep + 1;
break;
}
}
}
chunks.push(chunk.trim());
start = end - chunkOverlap;
}
return chunks;
}
// 使用示例
const text = `
人工智能是计算机科学的一个重要分支,它试图理解智能的实质,
并生产出一种新的能以人类智能相似的方式做出反应的智能机器。
该领域的研究包括机器人、语言识别、图像识别、自然语言处理和专家系统等。
人工智能从诞生以来,理论和技术日益成熟,应用领域也不断扩大。
`;
const chunks = fixedSizeChunk(text, 100, 20);
chunks.forEach((chunk, i) => {
console.log(`块 ${i+1} (${chunk.length}字符): ${chunk.slice(0, 50)}...`);
});
// 固定长度分块实现
package main
import (
"fmt"
"strings"
)
// FixedSizeChunk 固定长度文本分块
// text: 输入文本
// chunkSize: 每块大小(字符数)
// chunkOverlap: 块间重叠大小
// returns: 分块列表
func FixedSizeChunk(text string, chunkSize int, chunkOverlap int) []string {
var chunks []string
start := 0
for start < len(text) {
end := start + chunkSize
if end > len(text) {
end = len(text)
}
chunk := text[start:end]
// 如果不是最后一块,尝试在句子边界处切断
if end < len(text) {
// 查找最近的句子结束符
separators := []string{"。", "!", "?", ".", "!", "?"}
for _, sep := range separators {
lastSep := strings.LastIndex(chunk, sep)
if lastSep > chunkSize/2 { // 至少在一半位置之后
chunk = chunk[:lastSep+1]
end = start + lastSep + 1
break
}
}
}
chunks = append(chunks, strings.TrimSpace(chunk))
start = end - chunkOverlap
}
return chunks
}
func main() {
text := `
人工智能是计算机科学的一个重要分支,它试图理解智能的实质,
并生产出一种新的能以人类智能相似的方式做出反应的智能机器。
该领域的研究包括机器人、语言识别、图像识别、自然语言处理和专家系统等。
人工智能从诞生以来,理论和技术日益成熟,应用领域也不断扩大。
`
chunks := FixedSizeChunk(text, 100, 20)
for i, chunk := range chunks {
display := chunk
if len(display) > 50 {
display = display[:50]
}
fmt.Printf("块 %d (%d字符): %s...\n", i+1, len(chunk), display)
}
}
🔤 句子分割
按句子边界分割文本,保持句子完整性。
实现方式
- 规则-based - 使用正则表达式匹配句子结束符
- NLP 工具 - NLTK、spaCy、HanLP 等现成工具
- 合并策略 - 多个短句可合并为一块
查看代码示例
# 句子分割实现
import re
from typing import List
def sentence_chunk(text: str,
min_sentences_per_chunk: int = 1,
max_sentences_per_chunk: int = 5,
max_chars: int = 500) -> List[str]:
"""
句子级别文本分块
Args:
text: 输入文本
min_sentences_per_chunk: 每块最小句子数
max_sentences_per_chunk: 每块最大句子数
max_chars: 每块最大字符数
Returns:
分块列表
"""
# 句子分割(支持中英文)
sentence_endings = r'[。!?.!?]'
sentences = [s.strip() for s in re.split(sentence_endings, text) if s.strip()]
# 添加回句子结束符
chunks = []
current_chunk = []
current_length = 0
for sentence in sentences:
# 恢复句子结束符(简化处理)
sentence_with_end = sentence + '。'
sentence_len = len(sentence_with_end)
# 检查是否需要开始新块
if (len(current_chunk) >= max_sentences_per_chunk or
current_length + sentence_len > max_chars):
if current_chunk:
chunks.append(' '.join(current_chunk))
current_chunk = [sentence_with_end]
current_length = sentence_len
else:
current_chunk.append(sentence_with_end)
current_length += sentence_len
# 添加最后一块
if current_chunk:
chunks.append(' '.join(current_chunk))
return chunks
# 使用示例
text = """
机器学习是人工智能的核心技术之一。它通过算法让计算机从数据中学习规律。
深度学习是机器学习的一个重要分支。它使用多层神经网络来学习数据的层次化表示。
自然语言处理让计算机理解和生成人类语言。这是大语言模型的基础技术。
计算机视觉让计算机能够"看懂"图像和视频内容。
"""
chunks = sentence_chunk(text, max_sentences_per_chunk=2)
for i, chunk in enumerate(chunks):
print(f"块 {i+1}: {chunk}")
// 句子分割实现
/**
* 句子级别文本分块
* @param {string} text - 输入文本
* @param {number} minSentencesPerChunk - 每块最小句子数
* @param {number} maxSentencesPerChunk - 每块最大句子数
* @param {number} maxChars - 每块最大字符数
* @returns {string[]} 分块列表
*/
function sentenceChunk(text, minSentencesPerChunk = 1, maxSentencesPerChunk = 5, maxChars = 500) {
// 句子分割(支持中英文)
const sentenceEndings = /[。!?.!?]/g;
const sentences = text.split(sentenceEndings)
.map(s => s.trim())
.filter(s => s.length > 0);
// 构建分块
const chunks = [];
let currentChunk = [];
let currentLength = 0;
for (const sentence of sentences) {
// 恢复句子结束符(简化处理)
const sentenceWithEnd = sentence + '。';
const sentenceLen = sentenceWithEnd.length;
// 检查是否需要开始新块
if (currentChunk.length >= maxSentencesPerChunk ||
currentLength + sentenceLen > maxChars) {
if (currentChunk.length > 0) {
chunks.push(currentChunk.join(' '));
}
currentChunk = [sentenceWithEnd];
currentLength = sentenceLen;
} else {
currentChunk.push(sentenceWithEnd);
currentLength += sentenceLen;
}
}
// 添加最后一块
if (currentChunk.length > 0) {
chunks.push(currentChunk.join(' '));
}
return chunks;
}
// 使用示例
const text = `
机器学习是人工智能的核心技术之一。它通过算法让计算机从数据中学习规律。
深度学习是机器学习的一个重要分支。它使用多层神经网络来学习数据的层次化表示。
自然语言处理让计算机理解和生成人类语言。这是大语言模型的基础技术。
计算机视觉让计算机能够"看懂"图像和视频内容。
`;
const chunks = sentenceChunk(text, 1, 2, 500);
chunks.forEach((chunk, i) => {
console.log(`块 ${i+1}: ${chunk}`);
});
// 句子分割实现
package main
import (
"fmt"
"regexp"
"strings"
)
// SentenceChunk 句子级别文本分块
// text: 输入文本
// minSentencesPerChunk: 每块最小句子数
// maxSentencesPerChunk: 每块最大句子数
// maxChars: 每块最大字符数
// returns: 分块列表
func SentenceChunk(text string, minSentencesPerChunk int, maxSentencesPerChunk int, maxChars int) []string {
if minSentencesPerChunk == 0 {
minSentencesPerChunk = 1
}
if maxSentencesPerChunk == 0 {
maxSentencesPerChunk = 5
}
if maxChars == 0 {
maxChars = 500
}
// 句子分割(支持中英文)
re := regexp.MustCompile(`[。!?.!?]`)
parts := re.Split(text, -1)
var sentences []string
for _, s := range parts {
trimmed := strings.TrimSpace(s)
if trimmed != "" {
sentences = append(sentences, trimmed)
}
}
// 构建分块
var chunks []string
currentChunk := []string{}
currentLength := 0
for _, sentence := range sentences {
// 恢复句子结束符(简化处理)
sentenceWithEnd := sentence + "。"
sentenceLen := len(sentenceWithEnd)
// 检查是否需要开始新块
if len(currentChunk) >= maxSentencesPerChunk || currentLength+sentenceLen > maxChars {
if len(currentChunk) > 0 {
chunks = append(chunks, strings.Join(currentChunk, " "))
}
currentChunk = []string{sentenceWithEnd}
currentLength = sentenceLen
} else {
currentChunk = append(currentChunk, sentenceWithEnd)
currentLength += sentenceLen
}
}
// 添加最后一块
if len(currentChunk) > 0 {
chunks = append(chunks, strings.Join(currentChunk, " "))
}
return chunks
}
func main() {
text := `
机器学习是人工智能的核心技术之一。它通过算法让计算机从数据中学习规律。
深度学习是机器学习的一个重要分支。它使用多层神经网络来学习数据的层次化表示。
自然语言处理让计算机理解和生成人类语言。这是大语言模型的基础技术。
计算机视觉让计算机能够"看懂"图像和视频内容。
`
chunks := SentenceChunk(text, 1, 2, 500)
for i, chunk := range chunks {
fmt.Printf("块 %d: %s\n", i+1, chunk)
}
}
🔄 递归字符分块
LangChain 推荐的通用分块策略,按优先级顺序尝试多种分隔符。
分隔符优先级
\n\n- 段落分隔(双换行)\n- 单换行- 空格 - 单词/词语边界
- 空 - 字符级别
查看代码示例
# 递归字符分块实现(LangChain 风格)
from typing import List, Optional
class RecursiveCharacterTextSplitter:
"""递归字符文本分割器"""
def __init__(
self,
chunk_size: int = 1000,
chunk_overlap: int = 200,
separators: Optional[List[str]] = None,
keep_separator: bool = True,
):
self.chunk_size = chunk_size
self.chunk_overlap = chunk_overlap
self.keep_separator = keep_separator
# 默认分隔符优先级
self._separators = separators or ["\n\n", "\n", " ", ""]
def split_text(self, text: str) -> List[str]:
"""分割文本"""
return self._split_text(text, self._separators)
def _split_text(self, text: str, separators: List[str]) -> List[str]:
"""递归分割文本"""
final_chunks = []
# 获取当前分隔符
separator = separators[-1]
new_separators = []
if len(separators) > 1:
new_separators = separators[1:]
# 按当前分隔符分割
if separator:
if self.keep_separator:
# 保留分隔符
splits = self._split_with_inclusive_separator(text, separator)
else:
splits = text.split(separator)
else:
# 字符级别分割
splits = list(text)
# 处理每个片段
for split in splits:
if not split:
continue
# 如果片段仍然太大,递归分割
if len(split) > self.chunk_size:
sub_chunks = self._split_text(split, new_separators)
final_chunks.extend(sub_chunks)
else:
final_chunks.append(split)
# 合并小片段
return self._merge_chunks(final_chunks)
def _split_with_inclusive_separator(self, text: str, separator: str) -> List[str]:
"""分割但保留分隔符"""
parts = []
current = ""
for char in text:
current += char
if char == separator or (len(separator) > 1 and current.endswith(separator)):
parts.append(current)
current = ""
if current:
parts.append(current)
return parts
def _merge_chunks(self, chunks: List[str]) -> List[str]:
"""合并小片段以达到目标大小"""
if not chunks:
return []
merged = []
current_chunk = chunks[0]
for chunk in chunks[1:]:
# 检查合并后是否超过大小限制
if len(current_chunk) + len(chunk) + self.chunk_overlap <= self.chunk_size:
# 合并(带重叠)
overlap = current_chunk[-self.chunk_overlap:] if self.chunk_overlap > 0 else ""
current_chunk = current_chunk + chunk
else:
merged.append(current_chunk.strip())
current_chunk = chunk
merged.append(current_chunk.strip())
return merged
# 使用示例
splitter = RecursiveCharacterTextSplitter(
chunk_size=200,
chunk_overlap=50,
separators=["\n\n", "\n", "。", ".", " ", ""]
)
text = """
这是第一段内容,包含多个句子。这是第一段的第二句。这是第一段的第三句。
这是第二段内容,同样包含多个句子。这是第二段的第二句。
这是第三段,比较短。
"""
chunks = splitter.split_text(text)
for i, chunk in enumerate(chunks):
print(f"\n块 {i+1} ({len(chunk)}字符):")
print(chunk[:100]}...")
// 递归字符分块实现(LangChain 风格)
/**
* 递归字符文本分割器
*/
class RecursiveCharacterTextSplitter {
constructor(chunkSize = 1000, chunkOverlap = 200, separators = null, keepSeparator = true) {
this.chunkSize = chunkSize;
this.chunkOverlap = chunkOverlap;
this.keepSeparator = keepSeparator;
// 默认分隔符优先级
this._separators = separators || ["\n\n", "\n", " ", ""];
}
splitText(text) {
return this._splitText(text, this._separators);
}
_splitText(text, separators) {
const finalChunks = [];
// 获取当前分隔符
const separator = separators[separators.length - 1];
let newSeparators = [];
if (separators.length > 1) {
newSeparators = separators.slice(1);
}
// 按当前分隔符分割
let splits = [];
if (separator) {
if (this.keepSeparator) {
// 保留分隔符
splits = this._splitWithInclusiveSeparator(text, separator);
} else {
splits = text.split(separator);
}
} else {
// 字符级别分割
splits = text.split('');
}
// 处理每个片段
for (const split of splits) {
if (!split) continue;
// 如果片段仍然太大,递归分割
if (split.length > this.chunkSize) {
const subChunks = this._splitText(split, newSeparators);
finalChunks.push(...subChunks);
} else {
finalChunks.push(split);
}
}
// 合并小片段
return this._mergeChunks(finalChunks);
}
_splitWithInclusiveSeparator(text, separator) {
const parts = [];
let current = "";
for (const char of text) {
current += char;
if (char === separator || (separator.length > 1 && current.endsWith(separator))) {
parts.push(current);
current = "";
}
}
if (current) {
parts.push(current);
}
return parts;
}
_mergeChunks(chunks) {
if (chunks.length === 0) return [];
const merged = [];
let currentChunk = chunks[0];
for (let i = 1; i < chunks.length; i++) {
const chunk = chunks[i];
// 检查合并后是否超过大小限制
if (currentChunk.length + chunk.length + this.chunkOverlap <= this.chunkSize) {
// 合并(带重叠)
currentChunk = currentChunk + chunk;
} else {
merged.push(currentChunk.trim());
currentChunk = chunk;
}
}
merged.push(currentChunk.trim());
return merged;
}
}
// 使用示例
const splitter = new RecursiveCharacterTextSplitter(200, 50, ["\n\n", "\n", "。", ".", " ", ""]);
const text = `
这是第一段内容,包含多个句子。这是第一段的第二句。这是第一段的第三句。
这是第二段内容,同样包含多个句子。这是第二段的第二句。
这是第三段,比较短。
`;
const chunks = splitter.splitText(text);
chunks.forEach((chunk, i) => {
console.log(`\n块 ${i+1} (${chunk.length}字符):`);
console.log(chunk.slice(0, 100) + "...");
});
// 递归字符分块实现(LangChain 风格)
package main
import (
"fmt"
"strings"
)
// RecursiveCharacterTextSplitter 递归字符文本分割器
type RecursiveCharacterTextSplitter struct {
ChunkSize int
ChunkOverlap int
KeepSeparator bool
Separators []string
}
// NewRecursiveCharacterTextSplitter 创建分割器
func NewRecursiveCharacterTextSplitter(chunkSize int, chunkOverlap int, separators []string, keepSeparator bool) *RecursiveCharacterTextSplitter {
if separators == nil {
separators = []string{"\n\n", "\n", " ", ""}
}
return &RecursiveCharacterTextSplitter{
ChunkSize: chunkSize,
ChunkOverlap: chunkOverlap,
KeepSeparator: keepSeparator,
Separators: separators,
}
}
// SplitText 分割文本
func (s *RecursiveCharacterTextSplitter) SplitText(text string) []string {
return s.splitText(text, s.Separators)
}
func (s *RecursiveCharacterTextSplitter) splitText(text string, separators []string) []string {
finalChunks := []string{}
// 获取当前分隔符
separator := separators[len(separators)-1]
newSeparators := []string{}
if len(separators) > 1 {
newSeparators = separators[1:]
}
// 按当前分隔符分割
var splits []string
if separator != "" {
if s.KeepSeparator {
// 保留分隔符
splits = s.splitWithInclusiveSeparator(text, separator)
} else {
splits = strings.Split(text, separator)
}
} else {
// 字符级别分割
splits = strings.Split(text, "")
}
// 处理每个片段
for _, split := range splits {
if split == "" {
continue
}
// 如果片段仍然太大,递归分割
if len(split) > s.ChunkSize {
subChunks := s.splitText(split, newSeparators)
finalChunks = append(finalChunks, subChunks...)
} else {
finalChunks = append(finalChunks, split)
}
}
// 合并小片段
return s.mergeChunks(finalChunks)
}
func (s *RecursiveCharacterTextSplitter) splitWithInclusiveSeparator(text string, separator string) []string {
parts := []string{}
current := ""
for _, char := range text {
current += string(char)
if string(char) == separator || (len(separator) > 1 && strings.HasSuffix(current, separator)) {
parts = append(parts, current)
current = ""
}
}
if current != "" {
parts = append(parts, current)
}
return parts
}
func (s *RecursiveCharacterTextSplitter) mergeChunks(chunks []string) []string {
if len(chunks) == 0 {
return []string{}
}
merged := []string{}
currentChunk := chunks[0]
for i := 1; i < len(chunks); i++ {
chunk := chunks[i]
// 检查合并后是否超过大小限制
if len(currentChunk)+len(chunk)+s.ChunkOverlap <= s.ChunkSize {
// 合并(带重叠)
currentChunk = currentChunk + chunk
} else {
merged = append(merged, strings.TrimSpace(currentChunk))
currentChunk = chunk
}
}
merged = append(merged, strings.TrimSpace(currentChunk))
return merged
}
func main() {
splitter := NewRecursiveCharacterTextSplitter(200, 50, []string{"\n\n", "\n", "。", ".", " ", ""}, true)
text := `
这是第一段内容,包含多个句子。这是第一段的第二句。这是第一段的第三句。
这是第二段内容,同样包含多个句子。这是第二段的第二句。
这是第三段,比较短。
`
chunks := splitter.SplitText(text)
for i, chunk := range chunks {
display := chunk
if len(display) > 100 {
display = display[:100]
}
fmt.Printf("\n块 %d (%d字符):\n%s...\n", i+1, len(chunk), display)
}
}
🧠 语义分块
基于文本语义相似度进行分块,确保每块内部语义连贯。
实现方式
- 将文本按句子分割
- 计算相邻句子的语义相似度
- 在相似度最低处切断(话题转换点)
- 合并相似句子为块
查看代码示例
# 语义分块实现
import numpy as np
from typing import List, Tuple
class SemanticChunker:
"""基于语义相似度的文本分块器"""
def __init__(
self,
embedding_model=None,
threshold: float = 0.5,
min_chunk_size: int = 1,
max_chunk_size: int = 10,
):
"""
Args:
embedding_model: 句向量模型(如 sentence-transformers)
threshold: 相似度阈值,低于此值切断
min_chunk_size: 每块最小句子数
max_chunk_size: 每块最大句子数
"""
self.threshold = threshold
self.min_chunk_size = min_chunk_size
self.max_chunk_size = max_chunk_size
self.embedding_model = embedding_model
def _get_embedding(self, text: str) -> np.ndarray:
"""获取文本向量"""
if self.embedding_model is None:
# 简化示例:使用随机向量
return np.random.randn(384)
return self.embedding_model.encode(text)
def _cosine_similarity(self, a: np.ndarray, b: np.ndarray) -> float:
"""计算余弦相似度"""
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
def split(self, text: str) -> List[str]:
"""分割文本"""
# 1. 句子分割
sentences = self._split_into_sentences(text)
if len(sentences) <= self.min_chunk_size:
return [' '.join(sentences)]
# 2. 计算句子向量
embeddings = [self._get_embedding(s) for s in sentences]
# 3. 计算相邻句子相似度
similarities = []
for i in range(len(embeddings) - 1):
sim = self._cosine_similarity(embeddings[i], embeddings[i + 1])
similarities.append(sim)
# 4. 在低相似度处切断
chunks = []
current_chunk = [sentences[0]]
for i, sim in enumerate(similarities):
if sim < self.threshold or len(current_chunk) >= self.max_chunk_size:
if len(current_chunk) >= self.min_chunk_size:
chunks.append(' '.join(current_chunk))
current_chunk = []
current_chunk.append(sentences[i + 1])
if current_chunk:
chunks.append(' '.join(current_chunk))
return chunks
def _split_into_sentences(self, text: str) -> List[str]:
"""句子分割"""
import re
pattern = r'[。!?.!?]'
sentences = [s.strip() for s in re.split(pattern, text) if s.strip()]
return sentences
# 使用示例(需要 sentence-transformers)
# from sentence_transformers import SentenceTransformer
# model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
# chunker = SemanticChunker(embedding_model=model, threshold=0.6)
# 简化示例(无实际模型)
chunker = SemanticChunker(threshold=0.6)
text = """
机器学习是人工智能的重要分支。它让计算机从数据中学习规律。
深度学习使用神经网络进行表示学习。卷积神经网络在图像识别中表现优异。
自然语言处理研究计算机与人类语言的交互。大语言模型是 NLP 的重要成果。
计算机视觉让机器能够理解图像内容。目标检测是 CV 的核心任务之一。
"""
chunks = chunker.split(text)
for i, chunk in enumerate(chunks):
print(f"\n块 {i+1}:")
print(chunk)
// 语义分块实现
/**
* 基于语义相似度的文本分块器
*/
class SemanticChunker {
constructor(embeddingModel = null, threshold = 0.5, minChunkSize = 1, maxChunkSize = 10) {
this.embeddingModel = embeddingModel;
this.threshold = threshold;
this.minChunkSize = minChunkSize;
this.maxChunkSize = maxChunkSize;
}
/**
* 获取文本向量(简化版本,实际使用需要接入句向量模型)
* @param {string} text - 输入文本
* @returns {number[]} 文本向量
*/
_getEmbedding(text) {
if (!this.embeddingModel) {
// 简化示例:使用随机向量(384 维)
return Array.from({ length: 384 }, () => Math.random() * 2 - 1);
}
return this.embeddingModel.encode(text);
}
/**
* 计算余弦相似度
* @param {number[]} a - 向量 a
* @param {number[]} b - 向量 b
* @returns {number} 余弦相似度
*/
_cosineSimilarity(a, b) {
let dotProduct = 0;
let normA = 0;
let normB = 0;
for (let i = 0; i < a.length; i++) {
dotProduct += a[i] * b[i];
normA += a[i] * a[i];
normB += b[i] * b[i];
}
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
}
/**
* 分割文本
* @param {string} text - 输入文本
* @returns {string[]} 分块列表
*/
split(text) {
// 1. 句子分割
const sentences = this._splitIntoSentences(text);
if (sentences.length <= this.minChunkSize) {
return [sentences.join(' ')];
}
// 2. 计算句子向量
const embeddings = sentences.map(s => this._getEmbedding(s));
// 3. 计算相邻句子相似度
const similarities = [];
for (let i = 0; i < embeddings.length - 1; i++) {
const sim = this._cosineSimilarity(embeddings[i], embeddings[i + 1]);
similarities.push(sim);
}
// 4. 在低相似度处切断
const chunks = [];
let currentChunk = [sentences[0]];
for (let i = 0; i < similarities.length; i++) {
const sim = similarities[i];
if (sim < this.threshold || currentChunk.length >= this.maxChunkSize) {
if (currentChunk.length >= this.minChunkSize) {
chunks.push(currentChunk.join(' '));
currentChunk = [];
}
}
currentChunk.push(sentences[i + 1]);
}
if (currentChunk.length > 0) {
chunks.push(currentChunk.join(' '));
}
return chunks;
}
/**
* 句子分割
* @param {string} text - 输入文本
* @returns {string[]} 句子列表
*/
_splitIntoSentences(text) {
const pattern = /[。!?.!?]/g;
return text.split(pattern)
.map(s => s.trim())
.filter(s => s.length > 0);
}
}
// 使用示例(需要接入句向量模型)
// const model = new SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2');
// const chunker = new SemanticChunker(model, 0.6);
// 简化示例(无实际模型)
const chunker = new SemanticChunker(0.6);
const text = `
机器学习是人工智能的重要分支。它让计算机从数据中学习规律。
深度学习使用神经网络进行表示学习。卷积神经网络在图像识别中表现优异。
自然语言处理研究计算机与人类语言的交互。大语言模型是 NLP 的重要成果。
计算机视觉让机器能够理解图像内容。目标检测是 CV 的核心任务之一。
`;
const chunks = chunker.split(text);
chunks.forEach((chunk, i) => {
console.log(`\n块 ${i+1}:`);
console.log(chunk);
});
// 语义分块实现
package main
import (
"fmt"
"math"
"math/rand"
"regexp"
"strings"
"time"
)
// SemanticChunker 基于语义相似度的文本分块器
type SemanticChunker struct {
Threshold float64
MinChunkSize int
MaxChunkSize int
EmbeddingDim int
}
// NewSemanticChunker 创建语义分块器
func NewSemanticChunker(threshold float64, minChunkSize int, maxChunkSize int, embeddingDim int) *SemanticChunker {
if minChunkSize == 0 {
minChunkSize = 1
}
if maxChunkSize == 0 {
maxChunkSize = 10
}
if embeddingDim == 0 {
embeddingDim = 384
}
return &SemanticChunker{
Threshold: threshold,
MinChunkSize: minChunkSize,
MaxChunkSize: maxChunkSize,
EmbeddingDim: embeddingDim,
}
}
// getEmbedding 获取文本向量(简化版本)
func (s *SemanticChunker) getEmbedding(text string) []float64 {
// 简化示例:使用随机向量
embedding := make([]float64, s.EmbeddingDim)
for i := range embedding {
embedding[i] = rand.NormFloat64()
}
return embedding
}
// cosineSimilarity 计算余弦相似度
func (s *SemanticChunker) cosineSimilarity(a, b []float64) float64 {
dotProduct := 0.0
normA := 0.0
normB := 0.0
for i := range a {
dotProduct += a[i] * b[i]
normA += a[i] * a[i]
normB += b[i] * b[i]
}
return dotProduct / (math.Sqrt(normA) * math.Sqrt(normB))
}
// Split 分割文本
func (s *SemanticChunker) Split(text string) []string {
// 1. 句子分割
sentences := s.splitIntoSentences(text)
if len(sentences) <= s.MinChunkSize {
return []string{strings.Join(sentences, " ")}
}
// 2. 计算句子向量
embeddings := make([][]float64, len(sentences))
for i, sentence := range sentences {
embeddings[i] = s.getEmbedding(sentence)
}
// 3. 计算相邻句子相似度
similarities := make([]float64, len(embeddings)-1)
for i := 0; i < len(embeddings)-1; i++ {
similarities[i] = s.cosineSimilarity(embeddings[i], embeddings[i+1])
}
// 4. 在低相似度处切断
var chunks []string
currentChunk := []string{sentences[0]}
for i, sim := range similarities {
if sim < s.Threshold || len(currentChunk) >= s.MaxChunkSize {
if len(currentChunk) >= s.MinChunkSize {
chunks = append(chunks, strings.Join(currentChunk, " "))
currentChunk = []string{}
}
}
currentChunk = append(currentChunk, sentences[i+1])
}
if len(currentChunk) > 0 {
chunks = append(chunks, strings.Join(currentChunk, " "))
}
return chunks
}
// splitIntoSentences 句子分割
func (s *SemanticChunker) splitIntoSentences(text string) []string {
re := regexp.MustCompile(`[。!?.!?]`)
parts := re.Split(text, -1)
var sentences []string
for _, part := range parts {
trimmed := strings.TrimSpace(part)
if trimmed != "" {
sentences = append(sentences, trimmed)
}
}
return sentences
}
func main() {
// 初始化随机数种子
rand.Seed(time.Now().UnixNano())
// 简化示例(无实际模型)
chunker := NewSemanticChunker(0.6, 1, 10, 384)
text := `
机器学习是人工智能的重要分支。它让计算机从数据中学习规律。
深度学习使用神经网络进行表示学习。卷积神经网络在图像识别中表现优异。
自然语言处理研究计算机与人类语言的交互。大语言模型是 NLP 的重要成果。
计算机视觉让机器能够理解图像内容。目标检测是 CV 的核心任务之一。
`
chunks := chunker.Split(text)
for i, chunk := range chunks {
fmt.Printf("\n块 %d:\n%s\n", i+1, chunk)
}
}
📄 特定格式分块
针对 Markdown、代码、表格等特殊格式的分块策略。
Markdown 分块
- 按标题层级(#、##、###)分割
- 保持代码块完整性
- 保留表格结构
代码分块
- 按函数/类定义分割
- 保持代码块完整性
- 保留导入语句和上下文
查看代码示例
# Markdown 分块实现
import re
from typing import List, Tuple
class MarkdownChunker:
"""Markdown 文本分块器"""
def __init__(self, max_chunk_size: int = 1000):
self.max_chunk_size = max_chunk_size
def split(self, text: str) -> List[Tuple[str, str]]:
"""
分割 Markdown 文本
Returns:
[(标题,内容), ...] 列表
"""
# 按标题分割
heading_pattern = r'^(#{1,6})\s+(.+)$'
lines = text.split('\n')
chunks = []
current_title = ""
current_content = []
for line in lines:
match = re.match(heading_pattern, line)
if match:
# 保存之前的块
if current_content:
chunks.append((current_title, '\n'.join(current_content)))
# 开始新块
level = len(match.group(1))
current_title = f"{'#' * level} {match.group(2)}"
current_content = []
else:
current_content.append(line)
# 添加最后一块
if current_content:
chunks.append((current_title, '\n'.join(current_content)))
# 如果块太大,进一步分割
final_chunks = []
for title, content in chunks:
if len(content) > self.max_chunk_size:
# 按段落分割大块
paragraphs = content.split('\n\n')
current_para = []
current_len = 0
for para in paragraphs:
if current_len + len(para) > self.max_chunk_size:
final_chunks.append((title, '\n\n'.join(current_para)))
current_para = [para]
current_len = len(para)
else:
current_para.append(para)
current_len += len(para)
if current_para:
final_chunks.append((title, '\n\n'.join(current_para)))
else:
final_chunks.append((title, content))
return final_chunks
# 使用示例
markdown_text = """
# 机器学习概述
机器学习是人工智能的核心技术。
## 监督学习
监督学习从标注数据中学习映射关系。
### 分类
分类任务预测离散标签。
### 回归
回归任务预测连续值。
## 无监督学习
无监督学习从无标注数据中发现模式。
"""
chunker = MarkdownChunker(max_chunk_size=500)
chunks = chunker.split(markdown_text)
for title, content in chunks:
print(f"\n{title}")
print(f"内容长度:{len(content)}")
print(content[:100]}...")
// Markdown 分块实现
/**
* Markdown 文本分块器
*/
class MarkdownChunker {
constructor(maxChunkSize = 1000) {
this.maxChunkSize = maxChunkSize;
}
/**
* 分割 Markdown 文本
* @param {string} text - 输入文本
* @returns {Array<{title: string, content: string}>} 分块列表
*/
split(text) {
// 按标题分割
const headingPattern = /^(#{1,6})\s+(.+)$/m;
const lines = text.split('\n');
const chunks = [];
let currentTitle = "";
let currentContent = [];
for (const line of lines) {
const match = line.match(headingPattern);
if (match) {
// 保存之前的块
if (currentContent.length > 0) {
chunks.push({ title: currentTitle, content: currentContent.join('\n') });
}
// 开始新块
const level = match[1].length;
currentTitle = '#'.repeat(level) + ' ' + match[2];
currentContent = [];
} else {
currentContent.push(line);
}
}
// 添加最后一块
if (currentContent.length > 0) {
chunks.push({ title: currentTitle, content: currentContent.join('\n') });
}
// 如果块太大,进一步分割
const finalChunks = [];
for (const { title, content } of chunks) {
if (content.length > this.maxChunkSize) {
// 按段落分割大块
const paragraphs = content.split('\n\n');
let currentPara = [];
let currentLen = 0;
for (const para of paragraphs) {
if (currentLen + para.length > this.maxChunkSize) {
finalChunks.push({ title, content: currentPara.join('\n\n') });
currentPara = [para];
currentLen = para.length;
} else {
currentPara.push(para);
currentLen += para.length;
}
}
if (currentPara.length > 0) {
finalChunks.push({ title, content: currentPara.join('\n\n') });
}
} else {
finalChunks.push({ title, content });
}
}
return finalChunks;
}
}
// 使用示例
const markdownText = `
# 机器学习概述
机器学习是人工智能的核心技术。
## 监督学习
监督学习从标注数据中学习映射关系。
### 分类
分类任务预测离散标签。
### 回归
回归任务预测连续值。
## 无监督学习
无监督学习从无标注数据中发现模式。
`;
const chunker = new MarkdownChunker(500);
const chunks = chunker.split(markdownText);
chunks.forEach(({ title, content }) => {
console.log(`\n${title}`);
console.log(`内容长度:${content.length}`);
console.log(content.slice(0, 100) + '...');
});
// Markdown 分块实现
package main
import (
"fmt"
"regexp"
"strings"
)
// MarkdownChunk Markdown 分块结构
type MarkdownChunk struct {
Title string
Content string
}
// MarkdownChunker Markdown 文本分块器
type MarkdownChunker struct {
MaxChunkSize int
}
// NewMarkdownChunker 创建 Markdown 分块器
func NewMarkdownChunker(maxChunkSize int) *MarkdownChunker {
if maxChunkSize == 0 {
maxChunkSize = 1000
}
return &MarkdownChunker{
MaxChunkSize: maxChunkSize,
}
}
// Split 分割 Markdown 文本
func (m *MarkdownChunker) Split(text string) []MarkdownChunk {
// 按标题分割
headingPattern := regexp.MustCompile(`^(#{1,6})\s+(.+)$`)
lines := strings.Split(text, "\n")
var chunks []MarkdownChunk
currentTitle := ""
currentContent := []string{}
for _, line := range lines {
match := headingPattern.FindStringSubmatch(line)
if match != nil {
// 保存之前的块
if len(currentContent) > 0 {
chunks = append(chunks, MarkdownChunk{
Title: currentTitle,
Content: strings.Join(currentContent, "\n"),
})
}
// 开始新块
level := len(match[1])
currentTitle = strings.Repeat("#", level) + " " + match[2]
currentContent = []string{}
} else {
currentContent = append(currentContent, line)
}
}
// 添加最后一块
if len(currentContent) > 0 {
chunks = append(chunks, MarkdownChunk{
Title: currentTitle,
Content: strings.Join(currentContent, "\n"),
})
}
// 如果块太大,进一步分割
var finalChunks []MarkdownChunk
for _, chunk := range chunks {
if len(chunk.Content) > m.MaxChunkSize {
// 按段落分割大块
paragraphs := strings.Split(chunk.Content, "\n\n")
currentPara := []string{}
currentLen := 0
for _, para := range paragraphs {
if currentLen+len(para) > m.MaxChunkSize {
finalChunks = append(finalChunks, MarkdownChunk{
Title: chunk.Title,
Content: strings.Join(currentPara, "\n\n"),
})
currentPara = []string{para}
currentLen = len(para)
} else {
currentPara = append(currentPara, para)
currentLen += len(para)
}
}
if len(currentPara) > 0 {
finalChunks = append(finalChunks, MarkdownChunk{
Title: chunk.Title,
Content: strings.Join(currentPara, "\n\n"),
})
}
} else {
finalChunks = append(finalChunks, chunk)
}
}
return finalChunks
}
func main() {
markdownText := `
# 机器学习概述
机器学习是人工智能的核心技术。
## 监督学习
监督学习从标注数据中学习映射关系。
### 分类
分类任务预测离散标签。
### 回归
回归任务预测连续值。
## 无监督学习
无监督学习从无标注数据中发现模式。
`
chunker := NewMarkdownChunker(500)
chunks := chunker.Split(markdownText)
for _, chunk := range chunks {
fmt.Printf("\n%s\n", chunk.Title)
fmt.Printf("内容长度:%d\n", len(chunk.Content))
display := chunk.Content
if len(display) > 100 {
display = display[:100]
}
fmt.Println(display + "...")
}
}
📊 算法对比
⚡ 分块算法对比
| 算法 | 速度 | 语义完整性 | 实现难度 | 适用场景 |
|---|---|---|---|---|
| 固定长度 | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐ | 快速原型、对语义要求不高 |
| 句子分割 | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐ | 通用文本、文档检索 |
| 递归字符 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | RAG 系统、LangChain 推荐 |
| 语义分块 | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | 高质量检索、话题明确文本 |
| 特定格式 | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | Markdown、代码、结构化文档 |
🎯 参数选择指南
块大小(chunk_size)
- 256-512 tokens - 精确检索,适合事实性问题
- 512-1024 tokens - 平衡检索质量和上下文
- 1024-2048 tokens - 需要完整上下文的复杂问题
重叠大小(chunk_overlap)
- 0-10% - 减少冗余,适合长文档
- 10-20% - 推荐默认值
- 20-30% - 保持上下文连贯,适合短文本
💼 实际应用
🔧 主流工具
| 工具 | 分块方法 | 特点 |
|---|---|---|
| LangChain | 递归字符、Markdown、代码 | 功能全面、文档完善 |
| LlamaIndex | 句子、语义、递归 | 专为 RAG 设计 |
| Hugging Face | Token 级别 | 与模型集成 |
| NLTK/spaCy | 句子分割 | NLP 基础工具 |
📋 RAG 系统最佳实践
- 预处理 - 清理 HTML、特殊字符、标准化格式
- 选择分块策略 - 根据文档类型选择合适算法
- 调整参数 - 基于检索效果调优 chunk_size
- 添加元数据 - 记录来源、标题、位置等信息
- 质量评估 - 人工抽查分块质量和检索效果