返回首页

📝 文本分块算法

将长文本分割为语义完整的片段,RAG 系统预处理的关键步骤

📖 技术概述

🎯 什么是文本分块?

文本分块(Text Chunking/Segmentation)是将长文档分割成较小、语义完整的文本片段的过程。这是 RAG(检索增强生成)、语义搜索等系统的核心预处理步骤。

为什么需要分块?

  • LLM 上下文限制 - 大模型有最大输入长度限制(如 4K、8K、128K tokens)
  • 检索精度 - 小片段更容易精确匹配查询,减少噪声
  • 计算效率 - 小片段向量化和相似度计算更高效
  • 成本控制 - 只检索相关片段,减少 token 消耗

💡 核心挑战

  • 语义完整性 - 避免在句子或概念中间切断
  • 块大小平衡 - 太大包含噪声,太小丢失上下文
  • 重叠处理 - 相邻块之间需要适当重叠以保持上下文连贯
  • 多语言支持 - 不同语言的句子边界识别规则不同
  • 特殊格式 - 代码、表格、公式等特殊内容的处理

🔧 主流算法

📏 固定长度分块

最简单的方法,按固定字符数或 token 数分割文本。

实现方式
  1. 设定块大小 chunk_size(如 500 字符或 500 tokens)
  2. 设定重叠大小 chunk_overlap(如 50 字符)
  3. 按固定步长(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 推荐的通用分块策略,按优先级顺序尝试多种分隔符。

分隔符优先级
  1. \n\n - 段落分隔(双换行)
  2. \n - 单换行
  3. 空格 - 单词/词语边界
  4. 空 - 字符级别
查看代码示例
# 递归字符分块实现(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)
	}
}

🧠 语义分块

基于文本语义相似度进行分块,确保每块内部语义连贯。

实现方式
  1. 将文本按句子分割
  2. 计算相邻句子的语义相似度
  3. 在相似度最低处切断(话题转换点)
  4. 合并相似句子为块
查看代码示例
# 语义分块实现
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 系统最佳实践

  1. 预处理 - 清理 HTML、特殊字符、标准化格式
  2. 选择分块策略 - 根据文档类型选择合适算法
  3. 调整参数 - 基于检索效果调优 chunk_size
  4. 添加元数据 - 记录来源、标题、位置等信息
  5. 质量评估 - 人工抽查分块质量和检索效果