Semantica Split 模块实战指南:11 种文本分块策略与知识图谱保真切分
【免费下载链接】semanticaGraph-Native Infrastructure for Context and Accountable AI Systems项目地址: https://gitcode.com/GitHub_Trending/sema/semantica
semantica.split是 Semantica 的文档切分模块,提供从通用递归切分、语义切分到实体感知、关系感知、结构感知等一整套分块策略,目标是让每个 chunk 在切分后依然保留完整语义上下文。本文将完整讲解模块的导出类、TextSplitter 统一入口、全部切分方法的参数与适用场景、Chunk 数据结构、配置管理方式,并结合仓库源码(semantica/split/目录)说明各方法的底层实现与自动降级机制,帮助你为 RAG 检索、知识图谱构建和 LLM 上下文准备选对切分策略。
为什么切分(Chunking)如此关键
大多数 LLM 与嵌入模型都有固定的上下文窗口,超过窗口长度的文档必须被切分。但朴素的切分方式(例如每 500 个字符一刀切,完全无视结构)会破坏语义上下文,带来三类典型故障:
- 实体被切断:像 "Apple Inc." 这样的实体名被切到两个 chunk 里,两边都会丢失完整上下文;
- 关系三元组悬空:关系三元组 "Steve Jobs founded Apple" 若在 "Steve Jobs" 处被切断,主体就被悬空,后续知识图谱构建会得到残缺的关系;
- 混合主题产生质心向量:一个 chunk 同时混杂两个无关主题,嵌入后得到的质心向量与任何一个主题都不相似,检索时两边都匹配不上。
Semantica 的切分方法正是围绕避免这些故障模式设计的:实体感知切分保证实体跨度不越界,关系感知切分保证三元组落在同一 chunk 内,语义切分则只在主题真正变化时才切分。
模块导出的核心类
semantica.split通过init.py 对外导出统一分块器、各专用 chunker 以及若干底层切分函数:
| Class | 作用 |
|---|---|
TextSplitter | 统一入口:通过method=切换策略,无需改动下游代码 |
Chunk | 输出数据结构{text, start_index, end_index, metadata, id} |
SemanticChunker | 基于嵌入的主题漂移检测:只在内容真正变化时切分 |
StructuralChunker | 基于标题/章节的结构化文本切分 |
EntityAwareChunker | 防止命名实体跨 chunk 边界被切断 |
RelationAwareChunker | 保证 subject–predicate–object 三元组完整落在单个 chunk 内 |
HierarchicalChunker | 多层级切分,产生父子级 chunk 关系 |
SlidingWindowChunker | 固定尺寸滑窗切分,支持可配置重叠与步长 |
GraphBasedChunker | 基于图结构(社区检测/中心性)的切分 |
OntologyAwareChunker | 基于本体概念的切分(底层复用实体感知逻辑) |
此外__init__.py还导出了Splitter(TextSplitter的向后兼容别名)、SplitConfig/split_config配置对象、ProvenanceTracker溯源跟踪器,以及split_recursive、split_by_tokens、split_entity_aware等一组可直接调用的底层切分函数。
TextSplitter 支持的 method 值
| Method | 适用场景 |
|---|---|
recursive | 通用文本:按段落 → 句子 → 单词依次切分 |
sentence | 对话式文本、问答 |
paragraph | 长篇文本,段落完整性重要 |
token | LLM 上下文窗口限制 |
semantic_transformer | 存在主题转移的长文档 |
entity_aware | 知识图谱抽取流水线 |
relation_aware | 三元组完整性重要的 KG 流水线 |
structural | 带标题/段落结构的文本 |
sliding_window | 为 bi-encoder 检索提供高密度重叠 |
hierarchical | 多粒度检索 |
word/character/embedding_semantic/llm/graph_based/ontology_aware/community_detection/centrality_based/subgraph/topic_based/nltk/huggingface | 见下文"方法速查表"与源码_SPLIT_METHODS注册表 |
从源码看,methods.py 中的_SPLIT_METHODS字典共注册了 22 个内置方法,覆盖标准切分、KG/本体/图分析切分与专用切分三类。
快速开始
第一步:选择切分方法
from semantica.split import TextSplitter splitter = TextSplitter( method="recursive", # 见 Splitting Methods 表格 chunk_size=1000, chunk_overlap=200, )第二步:切分纯文本
chunks = splitter.split(text) for chunk in chunks: print(f" Start: {chunk.start_index}, End: {chunk.end_index}") print(f" Method: {chunk.metadata.get('method')}") print(f" Preview: {chunk.text[:80]}...")第三步:切分文档对象
split_documents()接受任何带.text属性的对象,或直接传字符串,不要求特定的文档类:
class Doc: def __init__(self, text, metadata=None): self.text = text self.metadata = metadata or {} doc = Doc(text="Annual report content...", metadata={"source": "annual_report.pdf"}) splitter = TextSplitter(method="structural") chunks = splitter.split_documents([doc]) for chunk in chunks: print(f" {chunk.text[:80]}...")第四步:批量切分文档列表
# split_documents() 返回一个跨所有输入的扁平 List[Chunk] all_chunks = splitter.split_documents(docs) for chunk in all_chunks: print(chunk.text[:80])从 splitter.py 的实现看,split_documents采用鸭子类型从对象上依次尝试text、page_content、content属性取文本,content为字节时会按 UTF-8 解码,content缺失但对象带path时还会尝试直接从文件路径读取;切分后文档级metadata会与 chunk 级元数据合并(文档元数据在前,chunk 元数据在后覆盖)。
切分方法速查表
| Method | 如何切分 | 适用场景 |
|---|---|---|
recursive | 段落 → 句子 → 单词(级联回退) | 通用默认值 |
semantic_transformer | 对句子做嵌入,在余弦相似度下降处切分 | RAG:主题一致性重要 |
entity_aware | 调整边界,保证实体跨度不被切断 | NER 流水线 |
relation_aware | 保证 subject–predicate–object 三元组落在同一 chunk | KG 构建 |
sentence | 句子边界检测(正则、NLTK、spaCy) | 短文档、问答 |
paragraph | 段落边界切分 | 长篇文章、报告 |
token | 通过 tiktoken 或 transformers 统计 token 数,硬性截止 | LLM 上下文窗口准备 |
word | 按单词数切分并带重叠 | 简单的 token 近似切分 |
character | 固定字符数切分并带重叠;最快,无需 NLP | 简单批处理任务 |
sliding_window | 固定尺寸窗口按步长推进;重叠可配置 | 密集检索(ColBERT、DPR) |
structural | 标题/段落结构检测 | 带明确标题层级的文本 |
embedding_semantic | 嵌入相似度边界(semantic_transformer的别名) | RAG 嵌入一致性 |
hierarchical | 多层级:章节 → 段落 → 句子 | 多粒度检索 |
此外,methods.py 还注册了llm(用 LLM 提示词寻找最优切分点,失败时回退递归切分)、graph_based(社区检测 Louvain 或中心性分析)、ontology_aware、community_detection、centrality_based、subgraph(k 跳邻域)、topic_based、nltk、huggingface等方法。
如何选择切分策略
在决定使用哪个方法前,可以参考这份决策树:
- 正在构建知识图谱?→
relation_aware(保持三元组完整);纯 NER 场景再用entity_aware - RAG 系统且检索质量优先?→
semantic_transformer - 需要为 bi-encoder 检索(ColBERT、DPR)提供密集重叠?→
sliding_window - 为固定窗口 LLM 准备提示词?→
token - 带标题的结构化文本?→
structural - 需要段落级连贯性?→
paragraph或sentence - 追求快速切分、无 NLP 开销?→
recursive或character
TextSplitter 构造函数参数
from semantica.split import TextSplitter splitter = TextSplitter( method="semantic_transformer", # 切分策略:见 Splitting Methods 表格 chunk_size=1000, # 目标尺寸(字符数) chunk_overlap=200, # 相邻 chunk 之间的字符重叠 similarity_threshold=0.7, # 余弦相似度阈值(仅 semantic_transformer) model="all-MiniLM-L6-v2", # sentence-transformers 模型名(仅 semantic_transformer) ner_method="ml", # NER 方法(仅 entity_aware) relation_method="ml", # 关系抽取方法(仅 relation_aware) )| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
method | str \| list[str] | "recursive" | 切分策略,或一个方法列表作为回退链 |
chunk_size | int | 1000 | 目标尺寸,单位是字符(不是 token:如果之前用 token 计尺寸,大约乘以 4 来近似相同边界) |
chunk_overlap | int | 200 | 相邻 chunk 之间的字符重叠 |
similarity_threshold | float | 0.7 | semantic_transformer的余弦相似度阈值:越低切分越多 |
model | str | "all-MiniLM-L6-v2" | semantic_transformer使用的 sentence-transformers 模型名 |
ner_method | str | "ml" | entity_aware的 NER 方法:"pattern"|"regex"|"ml"|"huggingface"|"llm" |
relation_method | str | "ml" | relation_aware的关系抽取方法:"ml"|"llm"|"huggingface" |
tokenizer | str | "gpt-4" | token方法的 tiktoken 模型名:无法识别的名称自动回退到cl100k_base |
关于chunk_overlap过小的警告。没有重叠时,跨越 chunk 边界的事实会在两个 chunk 里都不可见。相对于chunk_size的 10%~20% 重叠是安全下限:例如chunk_size=1000时,chunk_overlap应设置为100到200。
从 splitter.py 的实现看,method参数既支持单个字符串也支持列表——传列表即构成回退链;chunk_size与chunk_overlap缺省时会从全局split_config读取;其余参数存入self.options,并在构造时调用_load_method_configs()将每个方法在配置中的专有参数合并进来(方法配置优先)。
各切分方法的详细行为
Recursive(默认)
先尝试段落分隔,再尝试句子边界,最后是单词边界:仅在 chunk 超过chunk_size时才逐级回退:
splitter = TextSplitter(method="recursive", chunk_size=1000, chunk_overlap=200) chunks = splitter.split(text)关键行为:
- 尽可能保留段落与句子结构;
- 优雅降级:产出的 chunk 永远不会大于
chunk_size; - 重叠保证跨 chunk 边界的上下文连续;
- 不确定用哪个方法时,它是很好的起点。
源码佐证:在 methods.py 中,split_recursive的默认分隔符优先级为["\n\n", "\n", ". ", " ", ""],并要求切分点至少达到目标尺寸的 50% 才会被采用,否则继续尝试下一级分隔符,最后兜底按字符边界切分。
Semantic(语义切分)
用 sentence-transformers 模型为每个句子生成嵌入,当相邻句子之间的余弦相似度低于similarity_threshold时切分。每个 chunk 覆盖一个连贯主题:
from semantica.split import TextSplitter splitter = TextSplitter( method="semantic_transformer", model="all-MiniLM-L6-v2", # 任何 sentence-transformers 模型名 similarity_threshold=0.7, # 0.6 = 更多切分,0.8 = 更少切分 chunk_size=800, chunk_overlap=0, # 不需要:chunk 本身已经连贯 ) chunks = splitter.split(text)关键行为:
- 依赖
sentence-transformers包:默认使用all-MiniLM-L6-v2,可通过model=配置; - 产出变长 chunk:有的主题短、有的主题长;
- 若未安装
sentence-transformers,自动回退到句子切分; - 因需要计算嵌入,比
recursive慢;重复切分同一批文本时应缓存嵌入。
提示:语义切分需要足够的句子。semantic_transformer需要多个句子才能检测出主题转移。对于约 300 词以下的短文档,它的行为退化为句子切分,此时建议改用recursive。
源码佐证:split_semantic_transformer(methods.py)先通过正则把文本切成句子,再用SentenceTransformer(model)批量编码,逐对计算余弦相似度(_cosine_similarity使用 numpy),低于阈值或超过chunk_size即产生 chunk;任何异常或缺少依赖时都会记录 warning 并回退到split_by_sentences。
Entity-Aware(实体感知)
内部运行 NER,然后调整 chunk 边界,保证任何实体提及都不会被切到两个 chunk:
from semantica.split import TextSplitter # ner_method 会被透传给内部的 NERExtractor。 # 追求最高准确率用 "llm",追求速度用 "ml"(默认)。 splitter = TextSplitter( method="entity_aware", chunk_size=512, chunk_overlap=50, ner_method="ml", # "pattern" | "regex" | "ml" | "huggingface" | "llm" ) chunks = splitter.split(text) for chunk in chunks: print(f" entities in chunk: {chunk.metadata.get('entity_count', 0)}") print(f" preview: {chunk.text[:80]}...")关键行为:
- NER 在内部运行:实体抽取在切分器内部自动完成;
- 每个 chunk 的实体对象可通过
chunk.metadata["entities"]获取; - chunk 尺寸会在
chunk_size附近略有浮动:边界调整不超过一个句子; - 支持所有实体类型:PERSON、ORGANIZATION、LOCATION、DATE 及自定义类型。
源码佐证:split_entity_aware(methods.py)先实例化NERExtractor(method=ner_method)提取全部实体,收集所有start_char/end_char构成实体边界集合,逐句拼接时若某句包含实体边界且preserve_entities=True,即使超过尺寸也不在此处切断。ner_method="ml"时还支持通过kwargs传入 NER 抽取器的附加选项。
Relation-Aware(关系感知)
保证 subject–predicate–object 三元组落在同一个 chunk 内——这对知识图谱流水线至关重要:
from semantica.split import TextSplitter # relation_method 会被透传给内部的 RelationExtractor。 # 追求最高准确率用 "llm",追求速度用 "ml"(默认)。 splitter = TextSplitter( method="relation_aware", chunk_size=512, relation_method="ml", # "ml" | "llm" | "huggingface" ) chunks = splitter.split(text) for chunk in chunks: print(f" relations in chunk: {chunk.metadata.get('relation_count', 0)}") for rel in chunk.metadata.get("relationships", []): print(f" {rel}")关键行为:
- 关系抽取在内部运行:无需预计算实体或三元组;
- 关系对象可通过
chunk.metadata["relationships"]获取; - 隐含实体感知行为:三元组中的两个实体同样保持完整;
- 最适合作为
Parse → Split → Extract → Build KG流水线中的切分步骤。
源码佐证:split_relation_aware(methods.py)先运行 NER(ner_method可通过 kwargs 传入,默认"ml"),再实例化RelationExtractor(method=relation_method)抽取关系;每个关系取min(subject.start_char, object.start_char)到max(subject.end_char, object.end_char)作为三元组边界区间,逐句扫描时只要句子与某个三元组区间相交且preserve_triplets=True,就继续累积当前 chunk 而不切断。输出的 metadata 同时包含relation_count、relationships与triplets三个键。
Structural(结构感知)
基于标题与段落边界的结构分析切分文本,每个标题或段落组形成一个 chunk 边界:
from semantica.split import TextSplitter splitter = TextSplitter(method="structural") chunks = splitter.split(text) for chunk in chunks: print(f" {chunk.text[:80]}...") print(f" start: {chunk.start_index}, end: {chunk.end_index}")关键行为:
- 作用于纯文本:不要求结构化文档格式;
- 尊重标题层级(以
#开头的行或全大写标题)与段落分隔; - 使用
max_chunk_size=参数(而非标准的chunk_size=)控制最大尺寸; - 若
StructuralChunker不可用,自动回退到recursive。
源码佐证:split_structural(methods.py)在StructuralChunker可用时实例化 structural_chunker.py 中的类。StructuralChunker._extract_structure逐行识别五类结构元素——heading(Markdown#前缀或全大写短行、冒号结尾短行)、list(-/*/+/数字序号)、code_block(```或缩进块)、paragraph与表格;_group_elements按max_chunk_size分组,并在遇到 level ≤ 2 的标题且respect_headers=True时提前断块。每个 chunk 的 metadata 含element_count、element_types和structure_preserved: True。
Sliding Window(滑窗切分)
固定尺寸窗口按步长推进,重叠可配置,适合需要高密度重叠的 bi-encoder 检索场景:
from semantica.split import SlidingWindowChunker chunker = SlidingWindowChunker(chunk_size=1000, overlap=200) chunks = chunker.chunk(text, preserve_boundaries=True)关键行为:
stride默认取chunk_size - overlap,也可显式指定;preserve_boundaries=True时优先在句子边界(.、!、?、换行)处回退切断,其次在单词边界(空格)处回退,且至少保留目标尺寸的 50%;- 参数校验严格:
chunk_size必须为正数、overlap必须非负且小于chunk_size、stride必须为正数,否则抛出ValidationError(见 sliding_window_chunker.py); - 每个 chunk 的 metadata 含
chunk_index、has_overlap,边界保留模式下还有boundary_preserved标记。
Hierarchical(层级切分)
多层级切分(章节 → 段落 → 句子),为多粒度检索提供父子级 chunk 关系:
from semantica.split import HierarchicalChunker chunker = HierarchicalChunker( levels=["section", "paragraph", "sentence"], chunk_sizes=[2000, 1000, 500], ) chunks = chunker.chunk(text)源码佐证:split_hierarchical(methods.py)先用正则\n#{1,6}\s+检测 Markdown 章节,若存在多个章节则对每个章节递归调用自身并降级到下一层;否则回退到段落级(split_by_paragraphs)或句子级(split_by_sentences)。HierarchicalChunker.chunk还会为每个 chunk 的 metadata 写入hierarchical: True与levels字段(见 kg_chunkers.py)。
Chunk 数据结构
Chunk dataclass
@dataclass class Chunk: text: str # 该 chunk 的文本内容 start_index: int # 在源文本中的起始字符偏移 end_index: int # 在源文本中的结束字符偏移 metadata: Dict[str, Any] # 方法特定字段:见下表 id: Optional[str] = None # 可选 chunk 标识符该定义位于 semantic_chunker.py,metadata默认值为空字典。
Chunk metadata 字段
Metadata 键因方法而异,下表只列出实现中确实会写入的键:
| Field | Type | Set by | Description |
|---|---|---|---|
method | str | 所有方法 | 生成该 chunk 的切分方法 |
chunk_size | int | 大多数方法 | 该 chunk 的字符长度 |
sentence_count | int | sentence、semantic_transformer、spaCy 路径 | 该 chunk 中的句子数 |
paragraph_count | int | paragraph | 该 chunk 中的段落数 |
word_count | int | word | 该 chunk 中的单词数 |
token_count | int | token;当 spaCy 可用时的sentence/semantic_transformer | token 数:并非始终存在 |
entity_count | int | entity_aware | 边界落在这个 chunk 内的实体数 |
entities | list | entity_aware | 边界落在这个 chunk 内的实体对象 |
relation_count | int | relation_aware | 该 chunk 中的关系三元组数 |
relationships | list | relation_aware | 该 chunk 中的关系对象 |
triplets | list | relation_aware | 该 chunk 中的三元组对象(与relationships相同内容) |
element_count | int | structural | 分组到该 chunk 的结构元素数 |
element_types | list[str] | structural | 元素类型:"heading"、"paragraph"、"list"等 |
structure_preserved | bool | structural | 结构是否被完整保留 |
chunk_index/has_overlap | int/bool | sliding_window | 滑窗序号与是否携带重叠 |
Token 方法的 Tokenizer 选项
token方法接受tokenizer=关键字参数,该参数会传给tiktoken.encoding_for_model()。值应为 tiktoken 模型名,无法识别的名称会自动回退到cl100k_base。
| Value | Encoding used |
|---|---|
"gpt-4"(默认) | cl100k_base |
"gpt-3.5-turbo" | cl100k_base |
"text-embedding-ada-002" | cl100k_base |
| 任何无法识别的字符串 | 回退到cl100k_base |
错误的 tokenizer 警告。token方法会把tokenizer=值传给tiktoken.encoding_for_model()。若 tiktoken 不识别该模型名,会静默回退到cl100k_base。要获得确定性的行为,请传入有效的 tiktoken 模型名(如"gpt-4"、"gpt-3.5-turbo")。
源码佐证:split_by_tokens(methods.py)优先使用 tiktoken,异常时回退cl100k_base;tiktoken 不可用则尝试 HuggingFaceAutoTokenizer(transformers包);两者都不可用时按空白分词近似,并把chunk_size乘以 4 以接近真实边界——这也解释了文档中"token 尺寸约等于字符数除以 4"的经验法则。
配置管理:从环境变量到 YAML/JSON/TOML
semantica.split提供集中的配置管理,支持三种配置来源(见 config.py):
- 环境变量:
SPLIT_CHUNK_SIZE、SPLIT_CHUNK_OVERLAP、SPLIT_DEFAULT_METHOD、SPLIT_MAX_CHUNK_SIZE、SPLIT_MIN_CHUNK_SIZE等; - 配置文件:YAML、JSON、TOML 格式,读取顶层
split段作为全局配置、split_methods段作为方法专有配置; - 编程式 API:
split_config.set("chunk_size", 2000)、split_config.get("chunk_size", default=1000)、split_config.set_method_config("recursive", ...)、split_config.get_method_config("recursive")。
解析优先级遵循"配置文件 → 环境变量 → 默认值"的回退链,环境变量值会自动做类型转换(int等)。TextSplitter构造时会通过split_config.get("chunk_size", 1000)读取默认值,并把get_method_config(method)返回的方法专有配置合并进self.options,因此你可以在不改动调用代码的情况下,通过 YAML 配置或环境变量全局调整切分参数。
回退链与自定义方法注册
TextSplitter的method参数支持传入方法列表构成回退链(见 splitter.py):逐个尝试列表中的方法,某个方法未找到、返回空结果或抛异常时,记录 warning 并继续尝试下一个;全部失败才抛出ProcessingError,错误信息包含失败方法列表与最后一个异常。
同时,registry.py 提供了MethodRegistry注册表,支持运行时注册自定义切分方法:
from semantica.split.registry import method_registry def my_splitter(text, chunk_size=1000, **kwargs): # 自定义切分逻辑,返回 List[Chunk] ... method_registry.register("split", "my_custom_method", my_splitter) available = method_registry.list_all("split") # 查看已注册方法get_split_method在解析方法名时优先查注册表,其次查内置_SPLIT_METHODS字典(methods.py),因此注册的自定义方法可以像内置方法一样通过TextSplitter(method="my_custom_method")使用;list_available_methods()会合并内置与注册方法并去重排序。
与解析、抽取、流水线的集成
TextSplitter既可以独立使用,也可以与其他 Semantica 模块手动组合。下面是顺序模式示例:解析文件 → 切分文本 → 从每个 chunk 抽取实体:
from semantica.parse import DocumentParser from semantica.split import TextSplitter from semantica.semantic_extract import NERExtractor # 解析 parser = DocumentParser() parsed = parser.parse("data/report.pdf") # 返回带 "full_text" 键的 dict # 切分 splitter = TextSplitter(method="semantic_transformer", chunk_size=512) chunks = splitter.split(parsed["full_text"]) # 逐 chunk 抽取 ner = NERExtractor(method="ml") for chunk in chunks: entities = ner.extract(chunk.text) print(f" {len(entities)} entities in chunk starting at {chunk.start_index}")需要完整的流水线编排 API 时,可参考 Pipeline 参考文档,将切分作为命名流水线步骤集成。
模块在 Semantica 全家桶中的定位:
- Parse — 切分前解析文档:产出章节与元数据;
- Embeddings — 为向量检索与语义切分嵌入 chunk;
- Semantic Extract — 从单个 chunk 中抽取实体与关系;
- Pipeline — 将切分集成到命名流水线步骤。
更详细的模块使用说明(含TextSplitter、各 chunker 与配置示例)还可以参考仓库内的 split_usage.md 使用文档,以及 tests/split 目录下的切分测试用例。
小结
semantica.split的核心设计理念是"切分必须保真":recursive提供无 NLP 开销的通用默认值,semantic_transformer在主题真正变化时才切分,entity_aware与relation_aware分别保证实体与三元组不被拦腰切断,structural尊重标题层级,sliding_window为密集检索提供可控重叠,hierarchical支持多粒度检索。统一的TextSplitter入口配合方法回退链、注册表扩展、配置管理与逐级降级机制,让你可以在不改变下游代码的前提下,为 RAG 检索、知识图谱构建和 LLM 上下文准备灵活切换切分策略——这正是保证后续嵌入质量与实体抽取质量的第一道关口。
【免费下载链接】semanticaGraph-Native Infrastructure for Context and Accountable AI Systems项目地址: https://gitcode.com/GitHub_Trending/sema/semantica
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考