dsh-doc-router

docstested: passnpmMIT

Document intake for agents: doc_route sniffs a file's real format by magic bytes and measures a PDF's text layer and page column layout before any conversion, and pdf_markdown converts PDFs to Markdown in column reading order with PyMuPDF. The PDF path needs a Python 3 interpreter with PyMuPDF.

下载量 / Downloads
1,000
依赖体积 / node_modules
13.11 MB
测试时间 / Tested
2026-11-06
DSH 版本 / Version
0.2.2-rc.1

Install

dsh plugin --profile web add dsh-doc-router

Sandbox test results

StageResult
1 install✓
2 list✓
3 mount✓
4 boot—

Four stages run in an isolated sandbox: install (network on) → list → mount → boot (last three with no network).

README

dsh-doc-router

![npm](https://www.npmjs.com/package/dsh-doc-router)

![CI](https://github.com/Dantwz7/dsh-doc-router/actions/workflows/ci.yml)

![license: MIT](#许可证)

![node](package.json)

English | 中文

先路由,再读取。 一个 DeepSeek Harness
插件:先探明文件的真实格式与 PDF 的页面版面,再把它交给该用的那条管线;
对多栏论文做版面感知转换,而不是把正文切碎。

[为什么](#为什么需要它) · [提供什么](#提供什么) · [安装](#安装) · [快速开始](#快速开始) · [路由是怎么判的](#路由是怎么判的) · [配置](#配置) · [实测证据](#实测证据) · [故障排查](#故障排查) · [开发](#开发) · [路线图](#路线图) · [许可证](#许可证)


为什么需要它

默认转换器会把多栏论文做错,而且错得不轻。以一篇 Nature Letter

(nmat4749.pdf,双栏,7 页)为例,在 PyMuPDF 1.28.2 / MarkItDown 0.2.0 下实测:

管线 · 输出 · 多栏处理

pymupdf4llm(版面感知) · 第 1 页 6 533 字符 · ✅ 两栏按阅读顺序合并;标题→#、上标→<sup>、_in situ_ 斜体全部保留

pypdf · 全 7 页 30 632 字符 · ⚠️ 正文可读,但结构全丢

MarkItDown · 全 7 页 45 266 字符 · ❌ 正文碎成数百行 \ · … \ · 表格

同一失败可以在 clone 后自己复现。用本仓库的

two-column-reading-order.pdf

与 three-column.pdf

(两栏 / 三栏内容各自带标记的合成页),MarkItDown 0.1.5 会把每一栏同时竖着读下去:

夹具 · pdf_markdown · MarkItDown 0.1.5

two-column-reading-order.pdf · L01…L15,然后 R01…R15 · L01 R01 L02 R02 … —— 29 次跨栏跳变

three-column.pdf · L01…L15、M01…M15,然后 R01…R15 · L01 M01 R01 L02 … —— 44 次跨栏跳变

整个论点就这一行:一条管线保住了栏,另一条把它们交错在一起。

npm test 断言的是上表两个 pdf_markdown 单元格里的阅读顺序。

所以让模型「随便挑个转换器」的后果,是拿到一份被悄悄弄坏的原文,而且看起来像是成功了。

解法不是找一个更好的转换器,而是路由:先看文件,再选管线。

提供什么

产物 · 作用

doc_route 工具 · 判类并给出该用的管线:格式、页数、有无文字层、单栏/多栏,以及判据背后的逐页证据

pdf_markdown 工具 · 用 PyMuPDF 做版面感知的 PDF→Markdown;支持页码区间与落盘

doc-routing 技能 · 注册进 runtime 技能层,因此对所有工作区生效,不依赖工作区里的 skills/ 目录;它告诉模型「读之前先路由」

所有判据都是确定性的:零模型调用、零网络、零 token。

安装

版本要求

DSH · >=0.2.0-rc.1 <0.3.0-0 · · >=0.2.1-rc.1 <0.3.0-0

Node · ^22.19.0 · · >=24.0.0

Python · 3.9+ —— 只有 PDF 版面探测与 pdf_markdown 需要

PyMuPDF · pip install -U pymupdf pymupdf4llm —— 可选,见[许可证](#许可证)

解释器查找顺序,命中即止:

1. config.pythonPath

2. 环境变量 DOC_ROUTER_PYTHON

3. DSH 打包的运行时

4. ~/.dsh/dsh-runtimes 下的运行时

5. PATH 上的 python

显式指定的解释器原样采用,所以裸命令名(如 python3)也能用;

而写错路径时,报错会明确报出你指定的那个路径,不会变成一个谜。

从 npm 安装(推荐)

plugin_manager action=install_bundle target="dsh-doc-router@0.6.1"

请写精确版本。 DSH 的包管理器有最小发布年龄策略,只写包名可能悄悄解析到旧版本。

装完请重启 DSH。

从 DSH 命令行安装

dsh plugin --profile <你的 profile> add dsh-doc-router@0.6.1

<你的 profile> 必须是 DSH 实际启动的那个。拿不准就用上面的 plugin_manager,

它会自动作用于当前 profile。这个子命令有两个反直觉之处:

  • --profile 写错不会报错,而是新建一个。 该名字会被初始化成一个全新的空

profile,包装进那里,于是插件永远到不了你正在跑的那个 profile。当前 profile 就是

$DSH_HOME/profiles 下那个 package.json 里列着你的插件的目录。

  • --help 不是空跑。 dsh plugin --profile <名字> --help 会先把那个 profile

初始化出来,再打印 pnpm 的帮助。

从本地源码目录安装

plugin_manager action=install_bundle target="file:<仓库绝对路径>"

file: 安装是拷贝,所以改源码不会生效:必须升版本号 → 移除 bundle →

重装 → 重启 DSH。详见

CONTRIBUTING.md。

启用版面探测

<插件解析到的 python> -m pip install -U pymupdf pymupdf4llm

没有 PyMuPDF 时插件照常加载,doc_route 仍能按格式判类;

只有版面探测与 pdf_markdown 会停用,并且两者都会明确说明原因,而不是含糊地失败。

快速开始

先路由,再转换。下面的例子全部取自本仓库自带的合成夹具,因此每一行都可以在 clone 后复现。

1. 先路由

doc_route({ path: "tests/fixtures/two-column.pdf" })

→ Routed tests/fixtures/two-column.pdf — format: pdf, pages: 1, text layer: yes, columns: multi-column (est 2)
  Recommended pipeline: pdf_markdown

  多栏正文(估 2 栏)→ markitdown 会把正文切成表格碎片,pypdf 会丢结构
  需精确核对公式/上下标/数字时,再对该页渲染成图校验

  Probe detail: {"1":"0.91/2col"}

  dsh-doc-router v0.6.1
上面两行提示语是中文,因为这一段是在 noteLanguage: 'zh' 下跑的。**0.4.0 起
默认是英文**——见[配置](#配置)。判据行、recommend、probe 三个字段与语言无关。

2. 再转换

pdf_markdown({ path: "tests/fixtures/two-column.pdf" })

→ Converted tests/fixtures/two-column.pdf (all 1 pages) — 3391 characters of Markdown. …

pdf_markdown({ path: "tests/fixtures/two-column.pdf", pages: "1", output: "paper.md" })

→ Converted tests/fixtures/two-column.pdf (pages 1) — wrote 3391 characters to paper.md.

两个工具都接受相对工作区的路径。当内联输出会超过 maxChars 时,

pdf_markdown 会截断并明确告知,而不是灌爆上下文——要完整文档就传 output 落盘。

真实判据(来自本仓库的夹具)

下表每一行都是在仓库自带的夹具上跑 doc_route 得到的:

夹具 · 判据 · 管线

two-column.pdf · pdf · 1 页 · 有文字层 · 多栏(估 2 栏) · pdf_markdown

three-column.pdf · pdf · 1 页 · 有文字层 · 多栏(估 3 栏) · pdf_markdown

two-column-watermark.pdf · pdf · 1 页 · 有文字层 · 多栏(估 2 栏——不是 3) · pdf_markdown

unknown-columns.pdf · pdf · 3 页 · 有文字层 · unknown · pdf_markdown(安全侧)

dense-script-column.pdf · pdf · 1 页 · 有文字层 · 多栏(估 2 栏) · pdf_markdown

single-column.pdf · pdf · 1 页 · 有文字层 · 单栏 · markitdown → pypdf

scanned.pdf · pdf · 1 页 · 无文字层 · render_to_png → read_image

sample.docx · docx · markitdown

sample.png · png · read_image

sample.txt · text · read

pdf_markdown 会拒绝非 PDF,并直接告诉你该用哪条管线:

pdf_markdown({ path: "tests/fixtures/sample.docx" })
→ pdf_markdown only converts PDFs — detected docx. Recommended pipeline: markitdown
  (run doc_route for the full verdict)
[!NOTE]
关于提示语的语言。 判据行、recommend、probe 三个字段与语言无关。
所有给人或给模型看的文本都由 [noteLanguage](#配置) 选择:判据之后的
自由文本提示语、pdf_markdown 的拒绝信息、以及 doc-routing 技能正文。
0.4.0 起默认 en,zh 保持旧行为。

路由是怎么判的

所有判据都是确定性的:零模型调用

…(truncated)

Rendered from the README published on npm. Formatted only, not edited.

Versions

Published on npm::9,最后更新 2026-10-26

Links

GitHub · Homepage · npm

Same category