langchain-md-chunker
把 markdown / MDX 文档切成 chunk,并为每个 chunk 记录源文件路径 + 字符偏移,方便回溯原文。
核心是可复用的 Python 包(纯标准库、零运行时依赖);demo/ 里的网页只是演示,不参与打包。
API 参考见 docs/api/index.md。
特点
- 偏移精确:chunk 是原文的精确切片,
source[start:end] == chunk.text恒成立(切分只挑断点,不改写、不拼接、不重复内容)。 - MDX 感知:跳过 frontmatter 与
export const ...代码块;把<Tabs>、<CodeGroup>、<Accordion>等组件当作整体;代码围栏不会被腰斩。 - 标题感知:按标题层级分节,chunk 携带该节的标题路径(如
LangChain > Agents > Middleware)作为上下文。 - 标题可写入正文(可选):
--include-headings会把标题路径按原层级拼进content,更适合 embedding;text仍保持精确切片。 - 可调参数:目标长度、要丢弃的块类型/样板前缀均可配置。
快速开始
作为依赖安装(PyPI):
uv add langchain-md-chunker
仓库内开发:
uv sync # 安装(含 dev 依赖)
uv run python data/download_docs.py # 下载语料,约 180 篇 / 5 MB
uv run chunk data/langchain-docs --verify # 切分并校验偏移(目录需显式指定)
uv run python demo/server.py # 打开 http://127.0.0.1:8000 浏览
切分命令
uv run chunk <INPUT> [-o OUTPUT] [--max-chars 1500] [-p '*.md'] [--include-headings] [--verify] [--quiet]
| 参数 | 默认值 | 说明 |
|---|---|---|
INPUT |
必填 | 源文档目录(不内置任何默认语料) |
-o, --output |
data/chunks |
chunk 输出目录 |
--preset |
langchain-docs |
语料预设:langchain-docs 丢弃每页样板,generic 不丢弃 |
--max-chars |
1500 |
单 chunk 目标上限(字符,针对原文) |
-p, --pattern |
*.md, *.mdx |
文件匹配模式,可重复 |
--include-headings |
关闭 | 把标题路径写进 content(见下节) |
--verify |
关闭 | 写盘前回读源文件,逐条校验偏移 |
--quiet |
关闭 | 只输出错误与警告 |
输出:
data/chunks/
├── chunks.jsonl # 每行一个 chunk,保持源文件顺序
└── manifest.json # 源目录、配置、统计信息
两个文件都是“临时文件 + 原子替换”写入:切分时刷新演示页面,不会读到写了一半的索引。
data/langchain-docs/ 与 data/chunks/ 都在 .gitignore 中:语料和派生产物可随时重建,不入库。
语料预设
核心包本身不知道任何具体语料,默认配置不做语料相关的丢弃。CLI 默认启用 langchain-docs 预设(丢弃每页重复的索引横幅与页脚);换成别的文档集时用 --preset generic,或在 Python 里显式选择:
from langchain_md_chunker import Chunker, langchain_docs
chunker = Chunker(langchain_docs(max_chars=1200)) # 或 Chunker() 走通用默认
自有语料可以自己组合配置:ChunkConfig(skip_kinds=(...), skip_prefixes=(...));预设只是返回 ChunkConfig 的工厂函数,见 presets.py。分层与扩展点见 ARCHITECTURE.md。
数据格式
chunks.jsonl 单行示例:
{
"id": "c32352242f27",
"doc": "oss/python/concepts/context.md",
"start": 181,
"end": 1541,
"start_line": 5,
"end_line": 27,
"chars": 1360,
"headings": ["Context overview", "Context engineering"],
"context": "Context overview > Context engineering",
"text": "## Context engineering\n\n...",
"content": "# Context overview\n\n## Context engineering\n\n..."
}
| 字段 | 说明 |
|---|---|
id |
由 doc + start + end 派生的稳定 ID |
doc |
相对 manifest.source_root 的源文件路径 |
start / end |
半开区间 [start, end),单位是 Unicode 码点 |
start_line / end_line |
1-based 行号,闭区间 |
chars |
end - start |
headings |
标题路径(由外到内) |
context |
headings 用 > 拼接,可直接拼到 embedding 输入前 |
text |
原文切片,逐字符等于 source[start:end] |
content |
用于 embedding / 展示的文本:默认等于 text,开启 include_headings 后是“标题路径 + 空行 + text” |
偏移以码点计(Python
str下标)。JavaScript 字符串是 UTF-16,跨码点时需Array.from(text)后再切片——demo/static/app.js已这样做。
标题前缀(可选)
默认关闭。开启后,每个 chunk 的 content 会按原文层级带上它所在的标题路径:
输入:
# 1
## 2
### 3
xxxxx
输出(该 chunk 只有 xxxxx 一个正文块):
text = "### 3\n\nxxxxx"
content = "# 1\n## 2\n\n### 3\n\nxxxxx"
规则:
- chunk 已经包含自身标题行时不会重复,只补祖先标题;
- 层级按原文保留(
# A下直接### C就输出# A+### C,不臆造层级); - 空标题跳过;无标题的文档
content == text; start / end / chars / 行号始终描述text(原文切片),--max-chars也只约束原文长度; 实测语料上content平均比text多 43 字符(最大 137)。
uv run chunk data/langchain-docs --include-headings --verify # 产物带标题前缀
# 或
Chunker(ChunkConfig(include_headings=True))
切分策略
- 分块:逐行扫描,识别标题(ATX 与 Setext)、代码围栏(``` / ~~~)、表格、引用、MDX 组件、列表、段落、分隔线、frontmatter、
export块。 - 分节:按标题层级构造标题栈,每个 chunk 归属唯一一节,并带上该节标题路径。
- 打包:同节内按块贪心累积到
max_chars,超出才断点;标题行始终与其正文首块同行。 - 超大块:单个块超过上限时按行切分,优先落在子元素起始行(如
<Tab>、<Step>),其次空行,最后才选普通行;代码/组件可能因此被切开,但偏移依然精确。 - 丢弃:
skip_kinds(frontmatter / export)与skip_prefixes(LangChain 文档每页重复的索引横幅与页脚)不产出 chunk;它们会强制断开,避免混进相邻 chunk。export块按大括号配平整段跳过(文档里的PatternEmbed组件有 45 KB),并以标题/围栏为安全边界,绝不会吞掉正文。
已知限制与设计取舍:
- 标题行会与正文首块绑定;若正文是单个不可拆的巨型块(超长单行代码、大组件),该 chunk 可能超过
max_chars。默认语料中约占 0.1%。 max_chars按字符而非 token 计量:中英文密度不同,需要 token 预算时请按语料换算后调整。- 相邻 chunk 不重叠:这是“偏移精确、可回溯”的刻意取舍;需要 overlap 的检索策略请在消费端处理。
- 不支持 4 空格缩进代码块:文档中的缩进更常表示 MDX 组件或列表续行,误判代价高于收益;代码请使用围栏。
- 列表项内的围栏代码会与列表分成相邻块(内容不丢失,通常仍落在同一个 chunk)。
split_directory是单线程同步 IO;5 MB 语料约 0.5 s,更大规模建议按目录分片。- 偏移按 Unicode 码点;浏览器/JS 定位需按 UTF-16 换算(
demo/static/app.js有示例)。
Python API
from pathlib import Path
from langchain_md_chunker import Chunker, ChunkConfig
chunker = Chunker(ChunkConfig(max_chars=1200, include_headings=True))
chunks = chunker.split_directory("data/langchain-docs") # 或 split_file / split_text
source = Path("data/langchain-docs", chunks[0].doc).read_text(encoding="utf-8")
assert source[chunks[0].start : chunks[0].end] == chunks[0].text
print(chunks[0].context, len(chunks))
持久化与读取:
from langchain_md_chunker import build_manifest, read_chunks, read_manifest, write_chunks
manifest = build_manifest(chunks, source_root="data/langchain-docs", config=chunker.config)
write_chunks(chunks, "data/chunks", manifest=manifest)
chunks = read_chunks("data/chunks") # 目录或 .jsonl 均可
manifest = read_manifest("data/chunks")
与 LangChain 集成(可选,自行安装 langchain-core):
from langchain_md_chunker import to_langchain_documents
documents = to_langchain_documents(chunks) # page_content 默认取 content
documents = to_langchain_documents(chunks, page_content="text") # 精确原文
公开 API 即 langchain_md_chunker.__all__:Chunker、ChunkConfig、Chunk、langchain_docs、
to_langchain_documents、verify_chunks、write_chunks、read_chunks、build_manifest、read_manifest 及文件常量。
解析层(blocks.py)与切分层(splitter.py)属于内部实现,不作为兼容性承诺;
完整的公开接口清单见 docs/api/index.md。
演示前端
uv run python demo/server.py --port 8000
- 左侧按源文件顺序列出
[序号] 预览,支持按路径 / 标题 / 预览片段过滤;预览取自content,开启--include-headings后可直接看到标题前缀; - 点击任意
[序号],右侧直接渲染源文件并高亮该 chunk,自动滚动到高亮位置; - 顶部显示文档路径、字符偏移、行号、标题路径,可另开原始文件;
- URL 形如
http://127.0.0.1:8000/#c32352242f27,可直接分享某个 chunk。
前端没有构建步骤(原生 ES modules + HTML/CSS),服务端只用标准库,不导入 chunker 包,因此不会污染核心。修改语料后重新 uv run chunk data/langchain-docs,服务会自动重载。
目录结构
src/langchain_md_chunker/
├── __init__.py # 公开 API(__all__)
├── blocks.py # 内部:行 → Block(围栏/表格/MDX/引用…)
├── splitter.py # 内部:Block → 分节打包 → RawChunk(偏移)
├── chunker.py # 编排:文件 / 目录 + 标题前缀 → Chunk
├── models.py # 输出模型 Chunk
├── config.py # ChunkConfig(与语料无关的默认值)
├── presets.py # 语料预设(langchain-docs)
├── storage.py # JSONL + manifest 读写
├── verify.py # 偏移一致性校验
└── cli.py # `uv run chunk <INPUT>`
demo/
├── server.py # 演示服务(stdlib)
└── static/ # index.html / app.js / styles.css
tests/
├── conftest.py # 语料 fixture(全量切分只跑一次)
├── test_parser.py # 围栏/表格/MDX/引用等解析边界
├── test_splitter.py # 打包、标题上下文、超长块、样板剔除
├── test_chunker.py # 文件/目录 API、编码(Unicode、CRLF)、校验、幂等
├── test_storage.py # JSONL / manifest 往返
├── test_cli.py # 命令行端到端与退出码
├── test_package.py # 包入口、公开 API 与语料预设
├── test_fuzz.py # 固定种子的随机文档不变量测试
├── test_docs.py # 公开 API 文档覆盖与链接检查
└── test_demo.py # 演示服务的数据装载、重载容错与路径安全
docs/api/ # API 参考(纯 Markdown,无需构建)
CHANGELOG.md # 版本变更记录
RELEASING.md # PyPI 发布流程与检查清单
data/download_docs.py
测试
uv run pytest # 142 项,秒级
uv run pytest --cov # 覆盖率(已配置分支覆盖)
uv run ruff check src demo tests # 静态检查(ruff 已固定为 dev 依赖)
当前覆盖率:核心包 100% 行 / 100% 分支(uv run pytest --cov 可复现)。
测试关注的是不变量而不是实现细节:
- chunk 切片等于源文件
[start, end),且行号、标题路径、上下文自洽; - 代码围栏、表格、MDX 组件不会被“半个标签”切断,围栏内的
#不当标题; - Unicode(非 BMP 字符)、CRLF 文件的偏移按码点而非字节;
- 文件/目录 API 的
doc路径、排序稳定性与verify_chunks的负例; - CLI 退出码、
--verify失败、--max-chars/-p生效; - 语料存在时对全量 4400+ chunk 校验:不重叠、不越界、ID 唯一、超长块占比 < 1%。
发布
PyPI 包名 langchain-md-chunker。版本号、构建、Trusted Publishing 配置与回滚流程见
RELEASING.md,
变更记录见 CHANGELOG.md。
uv build # 产物:dist/*.whl、dist/*.tar.gz
uvx --from twine twine check dist/*
版权
代码以 MIT License 发布。
data/langchain-docs/ 中的文档版权归 LangChain 所有,仅用于本地学习与测试。
Metadata
Release files for langchain-md-chunker 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| langchain_md_chunker-0.2.0.tar.gz | 21.7 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| langchain_md_chunker-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 48.4 kB
Release files / langchain_md_chunker-0.2.0.tar.gz
| Download URL | langchain_md_chunker-0.2.0.tar.gz |
|---|---|
| Size | 21.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e896795683ce24e3fa87fd5166e585d10029ad8e6527a40405b3776611b7ae73
|
|
BLAKE2b-256 checksum How to use checksums |
dab558da271dabe8605b8503b4dd5016ef8974f641198dc658977efed4a803bb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / langchain_md_chunker-0.2.0-py3-none-any.whl
| Download URL | langchain_md_chunker-0.2.0-py3-none-any.whl |
|---|---|
| Size | 26.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b15def2182aec06ce22dee4f0fcfceeb4cc05a84bf6d6a6bebaa116600a02384
|
|
BLAKE2b-256 checksum How to use checksums |
89eafdda6e905bafc475de9f4688872588af5cf5e66d57a5eb6a11989dc0fb4a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for langchain-md-chunker 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.