python 以字符数量分割文档 (三)
针对 RAG / ChromaDB / book-to-skill / Claude Code Skill 工作流,建议给每个分片增加 metadata。
目标输出:
split/
├── 001.md
├── 001.yaml
├── 002.md
├── 002.yaml
├── 003.md
├── 003.yaml
└── index.json
例如:
001.yaml
id: 001
source: linux_book.md
title: Linux基础
section_path:
- Linux基础
- 文件系统
level: 2
characters: 87342
created_by: markdown_splitter
这样导入 ChromaDB 时:
collection.add(
documents=[content],
metadatas=[metadata]
)
可以直接过滤:
where={
"title": "Linux基础"
}
主要增加三个能力:
- 标题路径追踪
例如:
原文:
# Linux基础
## 文件系统
### inode
内容
分片 metadata:
section_path:
- Linux基础
- 文件系统
- inode
- 自动生成 JSON 索引
生成:
index.json
内容:
[
{
"id": "001",
"file": "001.md",
"title": "Linux基础",
"characters": 87342
}
]
方便程序读取。
- Markdown Front Matter 注入
每个 md 文件开头增加:
---
id: 001
source: linux_book.md
title: Linux基础
characters: 87342
---
# Linux基础
正文...
这样:
- 人读 Markdown 正常
- AI 读取自动获得上下文
完整增强版:
#!/usr/bin/env python3
import sys
import re
import json
from pathlib import Path
from datetime import datetime
INPUT = sys.argv[1] if len(sys.argv) > 1 else "book.md"
OUTPUT = Path("split")
MAX_CHARS = 90000
OUTPUT.mkdir(exist_ok=True)
def is_fence(line):
return re.match(
r"^\s*(`{3,}|~{3,})",
line
)
def scan_markdown(lines):
"""
扫描 Markdown:
返回:
[
{
text:"",
path:[标题],
level:1
}
]
"""
sections=[]
path=[]
start=0
current_level=0
in_fence=False
fence=None
for i,line in enumerate(lines):
f=is_fence(line)
if f:
c=f.group(1)[0]
if not in_fence:
in_fence=True
fence=c
elif c==fence:
in_fence=False
fence=None
continue
if not in_fence:
m=re.match(
r"^(#{1,3})\s+(.+)",
line
)
if m:
level=len(m.group(1))
title=m.group(2).strip()
if i>start:
sections.append(
{
"text":
"".join(lines[start:i]),
"path":
path.copy(),
"level":
current_level
}
)
path=path[:level-1]
path.append(title)
current_level=level
start=i
sections.append(
{
"text":
"".join(lines[start:]),
"path":
path.copy(),
"level":
current_level
}
)
return sections
def hard_split(text):
result=[]
while len(text)>MAX_CHARS:
cut=text.rfind(
"\n\n",
0,
MAX_CHARS
)
if cut==-1:
cut=MAX_CHARS
result.append(
text[:cut]
)
text=text[cut:]
if text:
result.append(text)
return result
def build_chunks(sections):
chunks=[]
current=""
meta=None
for sec in sections:
parts=hard_split(
sec["text"]
)
for part in parts:
if (
current
and
len(current)+len(part)
>
MAX_CHARS
):
chunks.append(
{
"text":current,
"path":meta["path"],
"level":meta["level"]
}
)
current=part
meta=sec
else:
current+=part
meta=sec
if current:
chunks.append(
{
"text":current,
"path":meta["path"],
"level":meta["level"]
}
)
return chunks
def save_chunks(chunks):
index=[]
source=Path(INPUT).name
for i,c in enumerate(chunks,1):
sid=f"{i:03d}"
md_file=OUTPUT/f"{sid}.md"
chars=len(c["text"])
metadata={
"id":sid,
"source":source,
"title":
c["path"][0]
if c["path"]
else "",
"section_path":
c["path"],
"level":
c["level"],
"characters":
chars,
"created":
datetime.now().isoformat(),
"generator":
"markdown_splitter"
}
front="\n".join(
[
"---",
f"id: {sid}",
f"source: {source}",
f"title: {metadata['title']}",
f"characters: {chars}",
"---",
""
]
)
md_file.write_text(
front+c["text"],
encoding="utf-8"
)
yaml_file=OUTPUT/f"{sid}.json"
yaml_file.write_text(
json.dumps(
metadata,
ensure_ascii=False,
indent=2
),
encoding="utf-8"
)
index.append(
metadata
)
print(
md_file,
chars
)
(OUTPUT/"index.json").write_text(
json.dumps(
index,
ensure_ascii=False,
indent=2
),
encoding="utf-8"
)
def main():
lines=Path(INPUT).read_text(
encoding="utf-8"
).splitlines(
keepends=True
)
sections=scan_markdown(lines)
chunks=build_chunks(sections)
save_chunks(chunks)
if __name__=="__main__":
main()
现在输出结构
例如:
输入:
linux.md
运行:
python split_md.py linux.md
生成:
split/
001.md
001.json
002.md
002.json
003.md
003.json
index.json
对 ChromaDB 非常友好
导入:
import json
from pathlib import Path
docs=[]
metas=[]
for f in Path("split").glob("*.md"):
docs.append(
f.read_text()
)
metas.append(
json.loads(
f.with_suffix(".json")
.read_text()
)
)
collection.add(
documents=docs,
metadatas=metas
)
查询:
collection.query(
query_texts=[
"Linux inode是什么"
],
n_results=5
)
返回:
001.md
section_path:
[
"Linux基础",
"文件系统",
"inode"
]
这个版本已经接近一个小型 Markdown-aware Document Loader,后续可以继续扩展:
- 自动生成 embedding
- 直接写入 ChromaDB
- 生成 Claude Code
SKILL.md - 根据章节生成摘要和关键词
这几个方向与你之前研究的 book-to-skill + Agent Skill + RAG 工作流可以直接衔接。

浙公网安备 33010602011771号