惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The GitHub Blog
The GitHub Blog
S
SegmentFault 最新的问题
MyScale Blog
MyScale Blog
有赞技术团队
有赞技术团队
V
Visual Studio Blog
T
The Blog of Author Tim Ferriss
爱范儿
爱范儿
Vercel News
Vercel News
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Y
Y Combinator Blog
Blog — PlanetScale
Blog — PlanetScale
D
DataBreaches.Net
美团技术团队
Microsoft Security Blog
Microsoft Security Blog
大猫的无限游戏
大猫的无限游戏
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
酷 壳 – CoolShell
酷 壳 – CoolShell
GbyAI
GbyAI
A
About on SuperTechFans
云风的 BLOG
云风的 BLOG
The Cloudflare Blog
宝玉的分享
宝玉的分享
V
V2EX
Microsoft Azure Blog
Microsoft Azure Blog

博客园 - 立体风

autohotkey2.0 Send keys参数分析 autohotkey2.0 盲从模式 windows 10 下 vscode 编写编译一个 nana c++ 库的示例程序 vscode 插件 cmake-tools 输出内容解读 Claude Code 处理中文乱码问题 python 启动器命令 py 答惑 windows 下 python 3.11 的版本问题 pip 安装 老版本 pytorch 的一个容易迷惑的错误 sentence transformer 例子及说明 Sentence Transformers 介绍 Jina Reranker 替代方案:改用 ModelScope 模型 Webnovel Writer 6.2.1 项目分析 windows 10 LTSC 版 安装 Terminal autohotkey2.0 脚本运行机制 windows wsl2 安装 gentoo 的步骤 windows 10 LTSC版 打开 wsl2 autohotkey 提示词 编译安装最新版 perl rust 做到“快并且安全” 的逻辑思路 rust 和 go 语言的核心目的 用 vim 查看文件的格式和编码 throughput 和 efficient 词义 python 以字符数量分割文档 (二) python 以字符数量分割文档 (一) GNU Bash 参考手册(中文版)(六) GNU Bash 参考手册(中文版)(五) GNU Bash 参考手册(中文版)(四) GNU Bash 参考手册(中文版)(三) GNU Bash 参考手册(中文版)(二) GNU Bash 参考手册(中文版)(一)
python 以字符数量分割文档 (三)
立体风 · 2026-08-07 · via 博客园 - 立体风

针对 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基础"
}

主要增加三个能力:

  1. 标题路径追踪

例如:

原文:

# Linux基础

## 文件系统

### inode

内容

分片 metadata:

section_path:
  - Linux基础
  - 文件系统
  - inode

  1. 自动生成 JSON 索引

生成:

index.json

内容:

[
  {
    "id": "001",
    "file": "001.md",
    "title": "Linux基础",
    "characters": 87342
  }
]

方便程序读取。


  1. 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 工作流可以直接衔接。