1概览
本 notebook 对 Semantica 的 split 模块进行了全面讲解,演示了所有分块策略和方法,以实现最优的文档处理。你将学习使用 15 种以上的切分方法,包括标准、语义和知识图谱感知的方法。
学习目标
学完本 notebook 后,你将能够:
- 使用
TextSplitter的多种方法 - 应用标准切分方法(递归、token、句子、段落)
- 使用语义分块以实现主题连贯性
- 应用 KG 感知的分块(实体感知、关系感知、基于图)
- 使用专用分块器(结构化、滑动窗口、表格、层级)
- 使用
ProvenanceTracker跟踪溯源 - 为你的用例选择合适的方法
你将学到什么
| 组件 | 用途 | 何时使用 |
|---|---|---|
TextSplitter |
统一切分器 | 所有分块需求 |
SemanticChunker |
语义边界 | 基于主题的分块 |
EntityAwareChunker |
保留实体 | GraphRAG 工作流 |
RelationAwareChunker |
保留三元组 | KG 构建 |
StructuralChunker |
文档结构 | 格式化文档 |
HierarchicalChunker |
多层级分块 | 大型文档 |
2安装
从 PyPI 安装 Semantica:
pip install semantica
# 或安装所有可选依赖:
pip install semantica[all]
# 安装 Semantica
!pip install -q semantica
3步骤 1:使用 TextSplitter 进行基础分块
让我们从统一的 TextSplitter 接口开始,它提供了对所有分块方法的访问。
什么是 TextSplitter?
TextSplitter 是一个统一接口,支持 15 种以上的分块方法:
- 标准:recursive、token、sentence、paragraph、character、word
- 语义:semantic_transformer、llm、huggingface、nltk
- KG/本体:entity_aware、relation_aware、graph_based、ontology_aware
- 高级:hierarchical、structural、sliding_window、table
from semantica.split import TextSplitter
# 示例长文本
text = """
Apple Inc. is a technology company founded by Steve Jobs, Steve Wozniak, and Ronald Wayne
in Cupertino, California on April 1, 1976. The company's current CEO is Tim Cook, who took
over from Steve Jobs in August 2011. Apple is headquartered at One Apple Park Way in Cupertino.
Apple develops and sells consumer electronics, computer software, and online services. The company's
hardware products include the iPhone smartphone, the iPad tablet computer, the Mac personal computer,
the iPod portable media player, the Apple Watch smartwatch, the Apple TV digital media player, and the
HomePod smart speaker.
Apple's software includes the macOS and iOS operating systems, the iTunes media player, the Safari web
browser, and the iLife and iWork creativity and productivity suites. Its online services include the
iTunes Store, the iOS App Store and Mac App Store, Apple Music, and iCloud.
"""
# 基础递归切分
splitter = TextSplitter(
method="recursive",
chunk_size=200,
chunk_overlap=50
)
chunks = splitter.split(text)
print(f"Split into {len(chunks)} chunks using recursive method\n")
print("=" * 80)
for i, chunk in enumerate(chunks, 1):
print(f"\nChunk {i}:")
print(f" Length: {len(chunk.text)} characters")
print(f" Start: {chunk.start_index}, End: {chunk.end_index}")
print(f" Text: {chunk.text[:100]}...")
print("\n" + "=" * 80)
4步骤 2:标准切分方法
让我们比较不同的标准切分方法。
方法对比
| 方法 | 最适合 | 速度 | 准确性 |
|---|---|---|---|
| recursive | 通用文本 | 快 | 良好 |
| sentence | 连贯的分块 | 中等 | 非常好 |
| token | LLM 上下文 | 中等 | 优秀 |
| paragraph | 自然断点 | 快 | 良好 |
# 比较不同方法
methods = ["recursive", "sentence", "paragraph"]
print("Comparing Standard Splitting Methods:\n")
print("=" * 80)
for method in methods:
splitter = TextSplitter(
method=method,
chunk_size=200,
chunk_overlap=50
)
chunks = splitter.split(text)
print(f"\nMethod: {method.upper()}")
print("-" * 40)
print(f" Chunks created: {len(chunks)}")
print(f" Avg chunk size: {sum(len(c.text) for c in chunks) / len(chunks):.0f} chars")
print(f" First chunk: {chunks[0].text[:80]}...")
print("\n" + "=" * 80)
5步骤 3:基于 Token 的切分
基于 token 的切分对于需要遵守 token 限制的 LLM 应用至关重要。
为什么基于 Token?
- LLM 上下文窗口:GPT-4 有 8K/32K 的 token 限制
- 准确计数:字符数 ≠ token 数
- 成本优化:token 决定 API 成本
from semantica.split import split_by_tokens
# 基于 token 的切分
chunks = split_by_tokens(
text,
chunk_size=100, # 100 个 token
chunk_overlap=20,
tokenizer="tiktoken",
model="gpt-4"
)
print("Token-Based Splitting Results:\n")
print("=" * 80)
for i, chunk in enumerate(chunks, 1):
token_count = chunk.metadata.get('token_count', 'N/A')
print(f"\nChunk {i}:")
print(f" Tokens: {token_count}")
print(f" Characters: {len(chunk.text)}")
print(f" Ratio: {len(chunk.text)/token_count if token_count != 'N/A' else 'N/A':.2f} chars/token")
print("\n" + "=" * 80)
6步骤 4:语义分块
语义分块使用嵌入,基于语义边界创建分块。
工作原理
- 将文本切分为句子
- 为每个句子生成嵌入
- 计算相邻句子之间的相似度
- 在相似度低于阈值处创建边界
from semantica.split import SemanticChunker
# 语义分块
semantic_chunker = SemanticChunker(
chunk_size=200,
chunk_overlap=50,
embedding_model="all-MiniLM-L6-v2",
similarity_threshold=0.7
)
chunks = semantic_chunker.chunk(text)
print("Semantic Chunking Results:\n")
print("=" * 80)
for i, chunk in enumerate(chunks, 1):
coherence = chunk.metadata.get('coherence_score', 'N/A')
print(f"\nChunk {i}:")
print(f" Length: {len(chunk.text)} chars")
print(f" Coherence: {coherence}")
print(f" Text: {chunk.text[:100]}...")
print("\n" + "=" * 80)
7步骤 5:面向 GraphRAG 的实体感知分块
实体感知分块保留实体边界,这对 GraphRAG 工作流至关重要。
为什么实体感知?
- 保留实体:不要把 "Steve Jobs" 切分到不同分块中
- 更好的抽取:完整的实体可提升 NER 准确性
- GraphRAG:对知识图谱构建至关重要
from semantica.split import EntityAwareChunker
# 实体感知分块
entity_chunker = EntityAwareChunker(
chunk_size=200,
chunk_overlap=50,
ner_method="ml", # "ml"(spaCy)、"pattern" 或 "llm"
preserve_entities=True
)
chunks = entity_chunker.chunk(text)
print("Entity-Aware Chunking Results:\n")
print("=" * 80)
for i, chunk in enumerate(chunks, 1):
entities = chunk.metadata.get('entities', [])
print(f"\nChunk {i}:")
print(f" Length: {len(chunk.text)} chars")
print(f" Entities: {len(entities)}")
if entities:
# 同时处理 Entity 对象和字典
entity_texts = [e.get('text', e.get('entity', '')) if isinstance(e, dict) else str(e) for e in entities[:3]]
print(f" Sample entities: {entity_texts}")
print("\n" + "=" * 80)
8步骤 6:关系感知分块
关系感知分块在分块内保留关系三元组。
为什么关系感知?
- 保留三元组:将(主语、谓语、宾语)保持在一起
- KG 构建:更适合构建知识图谱
- 上下文:关系需要完整的上下文
from semantica.split import RelationAwareChunker
# 关系感知分块
relation_chunker = RelationAwareChunker(
chunk_size=200,
chunk_overlap=50,
preserve_triplets=True
)
chunks = relation_chunker.chunk(text)
print("Relation-Aware Chunking Results:\n")
print("=" * 80)
for i, chunk in enumerate(chunks, 1):
triplets = chunk.metadata.get('triplets', [])
relationships = chunk.metadata.get('relationships', [])
print(f"\nChunk {i}:")
print(f" Length: {len(chunk.text)} chars")
print(f" Triplets: {len(triplets)}")
print(f" Relationships: {len(relationships)}")
print("\n" + "=" * 80)
9步骤 7:结构化分块
结构化分块尊重文档结构,如标题、段落和列表。
何时使用?
- 格式化文档:Markdown、HTML、结构化文本
- 保留层级:将章节保持在一起
- 更好的上下文:标题提供上下文
```python from semantica.split import StructuralChunker
带结构的 Markdown 文本
markdown_text = """
Apple Inc.
10History
Apple Inc. was founded by Steve Jobs, Steve Wozniak, and Ronald Wayne in 1976.
11Products
Hardware
- iPhone
- iPad
- Mac
Software
- macOS
- iOS
- Safari """
结构化分块
structural_chunker = StructuralChunker( respect_headings=True, respect_paragraphs=True, respect_lists=True, max_chunk_size=500 )
chunks = structural_chunker.chunk(markdown_text)
print("Structural Chunking Results:\n") print("=" * 80)
for i, chunk in enumerate(chunks, 1): section = chunk.metadata.get('section_title', 'N/A') level = chunk.metadata.get('heading_level', 'N/A')
print(f"\nChunk {i}:")
print(f" Section: {section}")
print(f" Level: {level}")
print(f" Text: {chunk.text[:80]}...")
print("\n" + "=" * 80) ```
12步骤 8:层级分块
层级分块为大型文档创建多层级分块。
优势
- 多粒度:文档 → 章节 → 段落
- 更好的导航:父子关系
- 灵活的检索:在不同层级进行查询
from semantica.split import HierarchicalChunker
# 层级分块
hierarchical_chunker = HierarchicalChunker(
chunk_sizes=[400, 200, 100], # 3 个层级
chunk_overlaps=[80, 40, 20],
create_parent_chunks=True
)
chunks = hierarchical_chunker.chunk(text)
print("Hierarchical Chunking Results:\n")
print("=" * 80)
for i, chunk in enumerate(chunks, 1):
level = chunk.metadata.get('level', 'N/A')
parent_id = chunk.metadata.get('parent_id', None)
child_ids = chunk.metadata.get('child_ids', [])
print(f"\nChunk {i}:")
print(f" Level: {level}")
print(f" Length: {len(chunk.text)} chars")
print(f" Parent: {parent_id if parent_id else 'None (root)'}")
print(f" Children: {len(child_ids)}")
print("\n" + "=" * 80)
13步骤 9:滑动窗口分块
滑动窗口创建重叠的固定大小分块。
用例
- 密集检索:确保不遗漏任何信息
- 固定上下文:一致的分块大小
- 重叠控制:精确的重叠管理
from semantica.split import SlidingWindowChunker
# 滑动窗口分块
sliding_chunker = SlidingWindowChunker(
chunk_size=150,
overlap=50
)
chunks = sliding_chunker.chunk(text)
print("Sliding Window Chunking Results:\n")
print("=" * 80)
for i, chunk in enumerate(chunks, 1):
# 手动计算重叠
overlap = 0
if i > 1:
prev_chunk = chunks[i-2]
overlap = max(0, prev_chunk.end_index - chunk.start_index)
print(f"\nWindow {i}:")
print(f" Position: {chunk.start_index}-{chunk.end_index}")
print(f" Length: {len(chunk.text)} chars")
print(f" Overlap with previous: {overlap} chars")
print("\n" + "=" * 80)
14步骤 10:表格分块
表格分块在切分大型表格时保留表格结构。
特性
- 保留表头:在每个分块中保留列标题
- 按行切分:按行切分,而非按字符
- 上下文包含:包含周围文本
from semantica.split import TableChunker
# 带表格的文本
text_with_table = """
Apple's product lineup includes:
| Product | Category | Release Year |
|---------|----------|-------------|
| iPhone | Smartphone | 2007 |
| iPad | Tablet | 2010 |
| Mac | Computer | 1984 |
| Apple Watch | Wearable | 2015 |
| AirPods | Audio | 2016 |
These products have revolutionized their respective categories.
"""
# 表格分块
table_chunker = TableChunker(
preserve_headers=True,
max_rows_per_chunk=3,
include_context=True,
table_format="markdown"
)
chunks = table_chunker.chunk(text_with_table)
print("Table Chunking Results:\n")
print("=" * 80)
for i, chunk in enumerate(chunks, 1):
is_table = chunk.metadata.get('is_table', False)
print(f"\nChunk {i}:")
print(f" Type: {'Table' if is_table else 'Text'}")
if is_table:
rows = chunk.metadata.get('row_count', 'N/A')
cols = chunk.metadata.get('column_count', 'N/A')
print(f" Rows: {rows}, Columns: {cols}")
print(f" Content: {chunk.text[:100]}...")
print("\n" + "=" * 80)
15步骤 11:溯源跟踪
跟踪分块来源,用于数据血缘和调试。
为什么跟踪溯源?
- 数据血缘:知道分块来自哪里
- 调试:将问题追溯到来源
- 合规:某些用例需要
import sys
import os
import importlib
# 1. 确保本地包在路径中
project_root = os.path.abspath(os.path.join(os.getcwd(), "../.."))
if project_root not in sys.path:
sys.path.insert(0, project_root)
# 2. 强制卸载模块以确保干净地重新加载
modules_to_unload = [
'semantica.split.semantic_chunker',
'semantica.split.splitter',
'semantica.split.provenance_tracker',
'semantica.split'
]
for module in modules_to_unload:
if module in sys.modules:
del sys.modules[module]
# 3. 导入全新的模块
import semantica.split.semantic_chunker
import semantica.split.splitter
import semantica.split.provenance_tracker
from semantica.split import ProvenanceTracker, TextSplitter
# 4. 验证 Chunk 类具有 id 字段
from semantica.split.semantic_chunker import Chunk
print(f"Chunk class fields: {Chunk.__annotations__}")
if 'id' not in Chunk.__annotations__:
print("WARNING: Chunk class still missing 'id' field. Kernel restart required.")
# 创建分块
splitter = TextSplitter(method="recursive", chunk_size=200, chunk_overlap=50)
chunks = splitter.split(text)
# 跟踪溯源
tracker = ProvenanceTracker()
for chunk in chunks:
tracker.track_chunk(
chunk=chunk,
source_document="apple_doc_001",
source_path="data/apple.txt",
timestamp="2024-01-01T00:00:00Z",
method="recursive"
)
print("Provenance Tracking Results:\n")
print("=" * 80)
# 获取第一个分块的血缘
if chunks:
# 使用分块的 ID 获取溯源信息
chunk_id = getattr(chunks[0], 'id', None)
print(f"Chunk ID: {chunk_id}")
if chunk_id:
prov_info = tracker.get_provenance(chunk_id)
if prov_info:
print(f"\nLineage for Chunk 1:")
print(f" Source Document: {prov_info.source_document}")
print(f" File Path: {prov_info.source_path}")
print(f" Method: {prov_info.metadata.get('method')}")
print(f" Timestamp: {prov_info.timestamp}")
else:
print("Error: Chunk ID not found. The Chunk class definition might still be cached.")
print("Please click 'Kernel' -> 'Restart Kernel' in the menu and run all cells again.")
print("\n" + "=" * 80)
16步骤 13:方法对比
让我们并排比较所有方法,以帮助你选择合适的方法。
对比标准
- 分块数量:创建的分块数量
- 平均大小:平均分块大小
- 处理时间:分块速度
import time
# 要比较的方法
methods_to_compare = [
("recursive", {}),
("sentence", {}),
("paragraph", {}),
("token", {"tokenizer": "tiktoken"}),
]
print("Method Comparison:\n")
print("=" * 80)
print(f"{'Method':<15} {'Chunks':<10} {'Avg Size':<12} {'Time (ms)':<12}")
print("-" * 80)
for method, kwargs in methods_to_compare:
try:
start_time = time.time()
splitter = TextSplitter(
method=method,
chunk_size=200,
chunk_overlap=50,
**kwargs
)
chunks = splitter.split(text)
elapsed = (time.time() - start_time) * 1000
avg_size = sum(len(c.text) for c in chunks) / len(chunks) if chunks else 0
print(f"{method:<15} {len(chunks):<10} {avg_size:<12.0f} {elapsed:<12.2f}")
except Exception as e:
print(f"{method:<15} Error: {str(e)[:40]}")
print("=" * 80)
17步骤 14:最佳实践
选择合适的方法
- 通用文档:使用
recursive以获得速度和简洁性 - LLM 应用:使用
token以遵守上下文窗口 - 语义检索:使用
semantic_transformer以实现主题连贯性 - GraphRAG:使用
entity_aware或relation_aware - 结构化文档:对格式化文档使用
structural - 大型文档:使用
hierarchical以实现多层级访问
分块大小指南
| 用例 | 推荐大小 | 重叠 |
|---|---|---|
| 语义检索 | 512-1024 字符 | 20% |
| LLM 上下文 | 2000-4000 字符 | 10-20% |
| 实体抽取 | 500-1500 字符 | 15-25% |
| 问答 | 1000-2000 字符 | 20% |
重叠建议
- 10-15%:快速处理,冗余较少
- 20-25%:均衡(推荐)
- 30-40%:最大程度保留上下文
18小结
你学到了什么
在本 notebook 中,你学会了如何:
- 使用
TextSplitter的多种方法 - 应用标准切分(recursive、token、sentence、paragraph)
- 使用语义分块以实现主题连贯性
- 应用 KG 感知的分块(实体感知、关系感知)
- 使用专用分块器(结构化、层级、滑动窗口、表格)
- 跟踪溯源
- 为你的用例选择合适的方法
关键要点
- 方法选择很重要:不同需求使用不同方法
- 分块大小至关重要:在上下文和处理之间取得平衡
- 重叠有帮助:20% 的重叠是一个不错的默认值
- 跟踪溯源:对调试和合规很重要
- 面向 GraphRAG 的 KG 感知:对知识图谱使用实体/关系感知
下一步
下一个 Notebook:12-embedding-generation.html
学习如何为你的分块生成嵌入!
延伸阅读: - Split Module API Reference - Advanced Chunking Strategies - GraphRAG Pipeline
有问题或疑问? 查看我们的 GitHub repository 或 documentation。