# docparse-rs · 项目状态 / 记分牌 / 经验

> 当前进度、记分牌、待办与跨阶段经验教训的**单一真源**。CLAUDE.md §8 只留指针指向本文件。
> 战略见 [roadmap.md](roadmap.md)；**系统架构**见 [architecture.md](architecture.md)；**功能/能力清单**见 [capabilities.md](capabilities.md)；
> 执行里程碑历史见 [devlogs/](devlogs/)；
> **接入 Agent/RAG** 见 [agent-integration.md](agent-integration.md)（CLI/MCP/REST/库 + chunk schema + 引用/增强模式）。

## 1. 完成里程碑

**全部完成**（截至 2026-06-11，见 devlogs/ 双部会话总结）：M1–M7、N1–N5、Phase 4 可自主项：

- **G1** 格式 3→12（含 AsciiDoc）；
- **G2/G4/G8a**；
- **G3-R**：`--table-model` 内嵌 UniRec-0.1B×tract 0.23 重抽表结构，合并格语义端到端正确；
- **G6**：Python 客户端 + LangChain 验收；
- **G7**：压测 1847 输入 + fuzz ~1020 万次零 panic；
- **G8b/G8c**：`--vlm-describe/--vlm-tables` mock 验收、`--formula-model` 公式→LaTeX、`--transcribe-model` 整页转写（中英域；韩文域外被退化守卫安全拦截）、MCP/REST 全增强透传；
- **G9** 全部（G9d TEDS 验收门过）；
- IR 0.7.0（Cell span 语义 + 图片 base64 内嵌）。

**Phase 5（H1–H7）** 收官（2026-06-12）：CCITT 扫描解码、cls 旋转、读序异常分/路由收口、双栏左列重排、APTED 尺子、隐藏文本盲区（架构边界=不做）、打磨篮子。

**Phase 6（OmniDocBench 提分，2026-06-12，主负结论）**：逐一证伪所有便宜旋钮——学术表 0.52 是 **UniRec-0.1B 固定 960×1408 输入在大/宽/密表上的真实天花板**（行列诊断:主因大表 UNDER-segment + 超宽表 NO-PRED；**看图核对**把 #4/#16 初判的"口径反噬"推翻=实为模型 over-split）；落地 B1（e2e 表评测 strip_math 对称）+ B2（UniRec EOS 门控失控退化抢救，零回归）；B3 area 下采样 / 抬 max_tokens（0.517→0.466）/ 抬分辨率 1280×1792（净≈0 但救饥饿表=扭曲合身表 + 3× 慢破速度门）实测 no-op/反降/破门；真杠杆=更大/多分辨率表模型 / `--vlm-tables` 服务（均在内嵌+保速域外）。

**Phase 7（PP-DocLayoutV2 内嵌版面，2026-06-14/15）**：把质量更优的 OpenDoc 版面模型 **PP-DocLayoutV2(RT-DETR, 25 类 + 原生阅读顺序)** 内嵌为 `--layout` 第二后端,与 DocLayout-YOLO **共存(按 ONNX 输入数自动识别)**。突破:之前判的"RT-DETR 跑不了 tract"实为 **tract 0.23.1 两个具体 bug**——`GatherNd` 推断规则引用错张量(一行)+ `TopK` 不收 TDim(`Cast(INT64)→TDim`);各一处 vendored 补丁(`vendor/`,`[patch.crates-io]`,见 [vendor/PATCHES.md](../../vendor/PATCHES.md))即 **eval 端到端通、tract==ONNX Runtime 5/5 页逐框一致**,速度 1.38× YOLO(过门),`GridSample×18` 虚惊(tract 能 optimize)。补丁对现有模型**可证明零影响**(PP-OCR/UniRec/SLANet/TATR 不用这两算子;YOLO 的 TopK 走非-TDim 原路径;149 单测 + 三件套 + 双后端回归通过)。`RegionKind` 统一 YOLO/PPV2 语义,formula/transcribe 顺带支持 PPV2。**记分牌 A/B 已跑**(同页同打分器,唯一变量=后端):**General 端到端表 TEDS_X 0.206→0.654(≈3×)**——PPV2 检得准救回 YOLO 在杂版面漏检的表;学术表持平(0.670 vs 0.661,YOLO 本就检得到)、文本两者平(OCR 主导)。结论:PPV2 赢在"检测是瓶颈"的杂文档,故 `--layout-model ppv2` 与 YOLO 共存按需启用。**决定(2026-06-15)**:两处 tract 补丁以 vendored 形式长期留 main,**不发上游 PR**;理由/维护(bump tract 时重打补丁)/回退条件见 [vendor/README.md](../../vendor/README.md)。[devlogs/2026-06-14-ppv2-tract-integration.md](devlogs/2026-06-14-ppv2-tract-integration.md)、analysis [why-tract-cant-run](analysis/2026-06-14-why-tract-cant-run-pp-doclayoutv2.md)/[vendored-on-main](analysis/2026-06-14-vendored-tract-patch-on-main.md)。用法:`--layout --layout-model models/layout-ppv2/PP-DoclayoutV2_simp.onnx`(模型由 `scripts/spike/ppv2/prepare.py` 一次性生成,gitignored)。

**Phase 8（PP-OCRv6 内嵌 OCR,2026-06-17,Gate 0+1 通过,已翻默认）**：把 PaddleOCR **PP-OCRv6 tiny(2026-06-11 发布,LCNetV4+RepLKFPN+EncoderWithLightSVTR,DB+CTC 接口与 v4/v5 同构)** 接入并**设为 `--ocr` 新默认**(`models/ppocr-v6`)。曾判的唯一风险(tract 吃不下新算子)**spike 证伪**:原始 ONNX 把符号 batch 维烙进 `value_info` 致 `Impossible to unify Sym with Val(1)`(与 PPV2 同类动态图,**非算子缺失**),`scripts/spike/ppocrv6/prepare.py` 静态化(钉 batch=1+strip value_info+infer_shapes,空间/宽度保持动态)即跑通,**算子全标准件(无 GatherND/GridSample/TopK)、无须任何 vendored 补丁**(比 PPV2 干净),tract==onnxruntime 逐值对齐。**真图 chinese_scan 端到端零代码改动跑通**(复用 `find_file`+维度消毒+字典长泛化 CTC):比 v4-mobile **更准(修正顿号 `丶`→`、`)+ 快 2.06×(0.47s vs 0.97s)+ 体积减半(~6MB vs 16MB)**;BGR/RGB 疑虑证伪(RGB 序照对)。**默认已翻**:`main.rs` 三处(CLI/MCP/serve)`default_value` v4→`models/ppocr-v6`,bare `--ocr` 直走 v6;`models/ppocr`(v4)留回退档。**UX 突破(同日)**:发现 tract `Onnx::with_ignore_value_info(true)` 等价 Python 的 strip value_info——**raw HF ONNX 可直载,整条 Python 静态化链(prepare.py/onnx/onnxsim/pyyaml)删除**;字典改 Rust 从 rec `inference.yml` 的 `character_dict` 解析(`load_dict`/`parse_yml_char_dict`,无 yaml 依赖)。flag 全局开**对 v4/v5 安全**(v4 chinese_scan 逐字不变);v6 raw 输出与旧静态化版逐字一致(含 `、`);provenance 诚实化 `ocr:ppocr-v4`→`ocr:ppocr`(generation 无关)。`fetch-models.sh ppocr-v6` 简化为只下 4 raw 文件(det/rec onnx+rec yml+v4 cls,~7MB,无 prep)。**自动下载已落地**(`crates/docparse-ocr/src/fetch.rs`,ureq 带 3 次重试抗 HF CDN 中断+原子 rename):缺 `models/ppocr-v6` 时 CLI `ensure_ocr_models` 在 **TTY 下 y/N 确认**自动拉、**非 TTY(MCP/REST/管道/CI)报清晰错**、`DOCPARSE_OCR_DOWNLOAD=1` 预确认;实测空目录自动拉 ~7MB 后端到端 OCR 正确。**待办**:Gate 2(更多扫描回归、阈值/`DET_SIDE` A/B、medium 量化)。见 [devlogs/2026-06-17-ppocr-v6-integration.md](devlogs/2026-06-17-ppocr-v6-integration.md)、[refer/ppocr-v6-evaluation.md](refer/ppocr-v6-evaluation.md)。

**Phase 8b（OCR 增强遍页级并行,2026-06-17,已实施）**:研究"识别速度还能不能提升"时实测发现——确定性解析早已 rayon 页并行,但 **OCR `enhance::apply` 这一遍是串行的**,且 `recognize` 的 `rec_cache` `MutexGuard` **持有到 `model.run` 结束**把 rec 推理**全局串行化**。朴素并行只到 3×(det 能并行、rec 被锁死);**取出 `Arc` 句柄→释放锁→再 run** 后,par×2=完美 2.0×/100%(证明 tract 单次 rec 推理本就近单线程,封顶纯属那把锁),**18 核多页扫描吞吐 10.2×**(spike:chinese_scan 内存复制 18 页)。落地:`apply` 页循环改受限 `par_iter`(scoped pool `min(cores,8)`,扫描 buffer ~100MB/页 → 内存是闸,非核数;`MAX_PAGE_PARALLELISM=8` 在效率拐点)+ 索引收集保序(输出字节一致)+ rec 锁修复;`core` 加 `rayon` 依赖(通用并行库,不破"core 不依赖 PDF 库"分层)。新单测守"并行==串行+确定性";三件套字节不变、chinese_scan `--ocr` 逐字不变。**续(第二 commit):页内 rec 并行攻单页延迟**——单页拆分实测 det 57%(单次推理拆不动)/ rec 41%(14 框可并行);`ocr_boxes` 改**自适应并行**(`rayon::current_thread_index` 判是否已在页级池:单页并行 box、多页不嵌套),**单页 1.31×(0.275→0.211s)+ 多页零回归**。[devlogs/2026-06-17-ocr-page-parallel.md](devlogs/2026-06-17-ocr-page-parallel.md)。

**Phase 9（CLI 体验:进度可视化 + 批量解析,2026-06-18,已实施）**:补两块 CLI 基础设施缺口。**① 处理速度可视化**——让"速度快"卖点看得见:引 `indicatif`,新增 [cli/progress.rs](../crates/docparse-cli/src/progress.rs)(`Reporter`/`PhaseGuard`/`ProgressMode{auto,always,never}`),每相位(parse/ocr/...)计时 + spinner/页条,结束打 `✓ <file> · N pages · X MB · T s · P pages/s · M MB/s` + 相位拆分;**全程 stderr、TTY 自动开、非 TTY/管道/`--quiet` 静默零污染 stdout**。`core/enhance.rs` 抽 `process_page` + 加 `apply_with(on_page 回调)`,回调每页触发喂进度条(**core 不引 indicatif**,回调是 CLI 传入 trait object,守分层)。**② 文件夹批量**——新增 [cli/batch.rs](../crates/docparse-cli/src/batch.rs):给文件夹/多输入/`--out-dir` 即批量,`collect_files`(默认顶层、`-r` 递归、按 `supports` 筛扩展名、显式文件必纳入)→ 逐文件跑管线 → 落 `--out-dir/<原名>.<后缀>`(保全名免撞)→ 聚合报告(人读表→stderr、`--report-json`、`--report-csv`)。**失败隔离**:坏文件=一行 `ERROR`,不中断整批(延伸"坏页→空页不 panic"到文件级)。**串行**处理(每文件内部已页并行 + OCR 内存受限池,叠文件级并行会爆内存)。从 `main` 抽 `parse_and_enhance`/`render_doc` 共用,**单文件路逐字节不变**(`inputs: Vec<PathBuf>`、子命令 match 改借用免部分移动、`Format` 补 `Copy`)。新单测:core `apply_with` 回调一次/页且等价 `apply`;cli progress 开关逻辑 + batch `collect_files`/CSV 转义/报告渲染(core 64 + cli 21,clippy 净)。**续(同日收口)**:① 批量模型复用——新增 `RunModels`(持服务端同款惰性 `OcrState` + 3 个 `LazyUniRec`),OCR/UniRec(~700MB)整批只载一次并复用(惰性,数字零成本不变),`parse_and_enhance` 收 `&RunModels`;实测 batch `--ocr` 两份 scan,scan2 比 scan1 快 ~2.3s=省掉的重载。② 递归落盘镜像——`collect_files` 携 `rel`,落 `--out-dir/<rel>.<后缀>` 建父目录,同名异目录不再互覆。**剩余待办**(成体系排进 ):~~M1 版面模型复用~~(✅ 已落地:改 3 个 docparse-ocr 公有签名收预载 `&LayoutModel`,CLI `RunModels.layout` + 服务端 `EnhanceState.loaded_layout` 各只载一次,**服务端每请求重载顺带修掉**;OCR/UniRec/版面三类模型现都整批/整服务只载一次=模型复用彻底收尾)+ ~~符号链接环防爆栈~~(✅ `-r` 不跟随符号链接目录)+ ~~落盘路径硬化~~(✅ `safe_rel` 防越界)= **M1 全完成**;M2 ~~`--progress=json` 事件流~~(✅ 单文件 summary + 批量流式 file/summary,stdout 不污染,schema 同 `--report-json`)+ ~~报告显相对路径~~(✅)+ ~~`--stats` CPU/内存用量~~(✅ 新需求:`getrusage` 取 peak RSS+CPU 时间+util%,libc 本就在树=零新供应链面,human/json 双形态)= M2 主体完成(余 I6 报告小料);M3 文件级并行 `--jobs`(需 spike);M4 基础解析页内进度(触 trait,候)。见 [cli-batch-and-progress.md](cli-batch-and-progress.md)、[devlogs/2026-06-18-cli-progress-and-batch.md](devlogs/2026-06-18-cli-progress-and-batch.md)。

**Phase 10（速度/质量三杠杆，2026-06-19，已实施）**：用户问"速度/质量还能不能提升",实测盘点后确定性快路径已到天花板(born-digital sub-20ms、CPU util 364%–453%),真杠杆只剩三处,全部落地。**S1 OCR 页并行自适应内存**:`enhance.rs` 固定 `MAX_PAGE_PARALLELISM=8` 改为 `desired_parallelism(cores,ram)`——按"总物理内存一半 / 100MB每页"定线程数、上限核数(`total_physical_ram` 走 libc：macOS `hw.memsize`/Linux `_SC_PHYS_PAGES`,本就在树零新供应链面),`DOCPARSE_OCR_PARALLELISM=N` 显式覆盖;**固定 8→18 核机吃满核**(对齐 Phase 8b 的 10× headroom),小内存高核机反而被 memory 闸住更安全。3 纯函数单测 + "并行==串行"守门 + 各 override 值产物逐字节一致。**S2 图像解码盲区**:补 `images.rs` 四处 position-only=丢内容的 `// TODO`——JPX(`hayro-jpeg2000`,Gray/RGB→Gray8/Rgb8,CMYK/alpha 仍 position-only)、16-bit 高字节降采样、2/4-bit DeviceGray 行对齐解包、1-bit Indexed 2-entry 调色板按 Rec601 亮度定极性折入 `invert`;新分支只接管原 `None` 档=零回归(三件套字节不变)、5 单测。`hayro-jpeg2000` 同 ccitt/jbig2 先例(已传递依赖)。**S3 批量 `--jobs N` 文件级并行**:`batch.rs` 默认 1=原串行(逐字节不变),`effective_jobs(req,cores,files,has_model)` 纯函数——**模型 flag 任一开→强制 1**(OCR/扫描 buffer ~100MB/页 + 模型 ~700MB 叠文件级会爆内存,被闸时 stderr 提示一行),否则 clamp 到 cores∧files,走有界 rayon 池索引保序收集。实测 40 个小数字 PDF `--jobs 8` vs `1` **~3× 吞吐**(0.341s→0.115s、CPU 346%→680%)、产物逐字节一致。排除项:`target-cpu=native`/`lto=fat`(破单二进制分发)、大表/CJK 模型质量(模型天花板,信封外)。

**Phase 11（文档结构树 → agentic 检索,2026-06-19,Phase A 主体已落地）**：调研发现结构树是"差最后一公里的免费奖品"——标题层级早已算出(`assign_heading_levels` tagged H1–H6 + 字号档位)、Tagged StructTree 早已读,但都被拍扁;IR 逐页扁平无 Section 节点、`/Outlines` 没读、chunk 面包屑用字号栈(非单调时错)。**落地**(、调研 [refer/document-structure-tree-for-agentic-retrieval.md](refer/document-structure-tree-for-agentic-retrieval.md) 含 12 个相似 OSS 产品横评):**T1** 新 `core/outline.rs`——`Section{id,title,level,page,bbox,children}` + 级别栈建树(id=标题出现序,**派生 on-demand 不入 IR** 故 `-f json` 字节不变)+ `build`/`get`/`breadcrumb`/`pruned`/`to_json`;**T3** `chunk.rs` 加 `section_id` + `heading_path` 改走真实 `level` 栈(弃字号栈、修偏差),跨模块单测证 chunk `section_id` 精确索引进树、面包屑与树一致;**T4** `-f outline` + MCP `outline` 工具(`id` 取子树/`max_depth` 限深)+ REST `?format=outline`(三接口字节一致)——把树暴露成 **agent 导航接口**(列目录→钻章节,调研指出是当前 OSS 真空);**T5** 34 套件绿 + clippy 0 + 三件套 json/md/text 逐字节不变。**设计偏离回写**:原拟在 IR 加 `Document.outline`,改派生 on-demand 换 `-f json` 字节不变 + 单一真源。**T2 续(同日,/Outlines 书签)**:新 `pdf/outlines.rs` 读 catalog `/Outlines` 树(Title UTF-16BE 解码、`/Dest` 及 `/A`→GoTo、**命名目标**含 `/Names`→`/Dests` 名树 `Kids` 递归),把书签**号码容错锚定**(剥前导节号+折空白/大小写,解 "Introduction" vs "1 Introduction")到目标页标题并打 `tag="H<level>"`——复用 tagged-heading 通道,**零 IR/core 改动**、书签嵌套→层级;安全降级(锚不上不打 tag、不覆盖已有 tag、无书签文档字节不变,三件套实测含带书签 1901 仍逐字节不变);6 单测;2408 真实小节经书签获正确层级。**决策**:原拟"IR 加 bookmarks 字段+core 校正"因 `Document{}` 字面量 29 处遂改 PDF 后端 H-tag 注入。**不做(诚实)**:StructTree 显式父子遍历(tag_level 已正确嵌套 tagged 文档=冗余)、`source` provenance(tag-annotation 下同形=低价值)、致密学术 PDF 几何标题误检(既有检测质量问题,非结构树引入,根治属 Phase B)。**待续**:ToC 页解析 + 版面模型层级深度(Phase B)、RAPTOR 语义树(Phase C,外接信封外)。对标:Docling/llmsherpa/GROBID 有真树但全 Python/JVM+模型+起服务;docparse 差异=确定+每节点可溯源 bbox+零依赖单二进制+树即 agent 工具。

**Phase 12（OKF 导出,2026-06-19,Phase 1 已落地）**：把结构树(Section)+ 带 `section_id` 的 chunks 序列化成 **Open Knowledge Format v0.1 bundle**(Google Cloud 2026-06-12 发布的厂商中立、git 原生、可溯源 RAG 交付格式:目录 markdown + YAML frontmatter)。**判断同结构树**:不改 IR、不新模型、不新抽取——树给骨架、chunks 给血肉,**emit 期 join**,`-f json` 等既有输出**字节不变**。落地(、调研已核 OKF SPEC §9):新 `core/okf.rs` 纯函数 emitter(`Bundle{files}`/`build`/`write_to`;一 Section 一 concept `.md`,frontmatter `type`(必填非空)/`title`/`resource`(page+bbox 可溯源)/`description`(首句)/`timestamp` + 扩展键 `section_id`/`page`/`bbox`;目录嵌套镜像树;根 `index.md` 带 `okf_version:"0.1"`,子 `index.md` 无 frontmatter);slug 剥前导节号 + **截断 64 字符**(防致密 PDF 误检长标题撑爆文件名);**确定性 timestamp 用源 mtime**(`iso8601_utc` 自实现 days-from-civil,无 chrono,绝不 wall-clock)。CLI `-f okf`:`-o` 或**自动派生 `<stem>-okf/`**(stderr 提示)、派生非空报错(除非 `--force`)、`--okf-resource-base` 前缀;批量每文件 `<rel-fullname>-okf/`。验收:8 单测(§9 conformance 自校验 + `diff -r` 确定性 + 无标题优雅退化 + 无 wall-clock);三件套 json/md/text/chunks/outline 逐字节不变;HTML/PDF e2e 嵌套 bundle 合法;clippy 0、34 套件绿。**局限(诚实)**:bundle 质量受上游标题检测限制(致密学术 PDF 几何误检噪声=既有问题,非 OKF 引入,根治属结构树 Phase B)。**Phase 2 续(同日,分发+agent 接入)**:`Bundle::to_tar` 确定性 ustar(固定 mode/uid/**mtime 0**,**手写无 tar 依赖**——tar crate 烙真实 mtime 破确定性);CLI `--okf-tar`→stdout(实测 `|tar x` 还原==目录写出字节一致);MCP `export_okf` 工具(返 `{okf_version,files:[{path,content}]}`);REST `?format=okf`→`application/x-tar`(实测 `curl|tar t` 列条目)。共用 `okf::build`+`okf_options_for`(三面同源)。tar 确定性单测 + MCP/REST e2e;三件套 5 格式仍字节不变、clippy 0、34+ 套件绿。**Phase 3 待续**:OKF consumer(读 bundle 回 IR,非承诺)。差异化:确定 + 可溯源 bbox + 零依赖单二进制,踩中 llmsherpa/Docling(重依赖/起服务)空位。

**Phase 13（机器可读契约 + MCP 自描述,2026-06-20,已落地）**：调研"如何让其它 agent 项目更方便接入"——现状四接口/OKF/Python client/skill/预编译 installer 都已齐,真短板是**没有机器可读契约**(REST 无 OpenAPI、输出无 JSON Schema,外部只能读 markdown 手抄类型)+ **MCP 不自描述**(无 resources/prompts、tool 无 outputSchema)。**落地全纯叠加,既有 `-f json/chunks/outline/okf` 与三面文本输出字节不变**。**① Schema 单一真源=代码**:新依赖 `schemars`(core `schema` feature,CLI 启用,pure-Rust;库嵌入者不开 feature 零拉取),contract 类型(ir/chunk/outline/quality)加 `#[cfg_attr(feature="schema",derive(JsonSchema))]`,新 `core/schema.rs` `all` 生成 draft-2020-12 schema(`#[serde(skip)]` 的 `ImageChunk.data` 正确不入契约);`schemas/` 六文件入库(document/chunk/outline/quality/profile/okf-bundle);CLI `docparse schema [--name N] [--write]` + **golden 防漂移测试**(`schemas/*.json` 不等于代码生成即 fail,提示 `--write` 重生)。**② REST 自描述**:`GET /openapi.json`(OpenAPI 3.1 手拼骨架 + 注入 component schema,不引 utoipa)+ `GET /schema/{name}`。**③ MCP 自描述**:协议 `2025-03-26`→`2025-06-18`,capabilities 加 resources/prompts;4 个结构化工具(get_chunks/outline/export_okf/locate)加 `outputSchema` + 返回 `structuredContent`(与文本同源,parse_document 因 format 可变不加);`resources/*` 暴露 6 schema + 2 份指南(`agent-integration.md` + 新 `agent-enhancement-decisions.md`,via `include_str!` 零漂移);`prompts/*` 两模板(parse-for-rag 带质量自检循环 / navigate-document 按结构钻取)。**字节不变保证**:tool 文本块 `content[0].text` 不变,structuredContent 是 06-18 新增额外字段。验收:core schema 3 + CLI schema 2 + MCP 16 + REST openapi 1 + 既有全套绿、clippy 0、§1 三件套不变、REST 路由 live smoke 通过。**续(同日,TS client 已落地)**:`clients/typescript/`(docparse-client,零运行时依赖)——子进程包 CLI + `fetch`/`FormData` 包 REST(`DocparseClient`/`DocparseHttpClient`,两传输同形,Node ≥18),`HttpClient.schema(name)` 直取服务端 JSON Schema;**LangChain.js `DocumentLoader`**(`docparse-client/langchain`)+ **Vercel AI SDK 工具**(`docparse-client/ai`,`docparseTools`→get_chunks/outline/parse_markdown),host 为惰性可选 peer(`as string` 动态 import 不在 build 期解析,base 零依赖)。类型 `Chunk`/`Section`/`BBox` 镜像契约(权威仍是 `schemas/`/`openapi.json`,可 quicktype 全量生成)。`tsc` 严格模式编译通过;`node:test` 烟测 3 项对真实 binary 绿(json/chunks/outline/text + 错误路径);HTTP 传输对 live server 烟测(chunks+bbox / schema 取回 / outline 全通)。镜像 Python client 形态。**未做(留独立方向)**:`pip`/`npm` 自带预编译二进制、模型下载契约改造。

**Phase 14（文档内嵌图片 → 可被 RAG 召回与引用,2026-06-21,已落地）**：缺口=`-f chunks` 把 `Element::Image` 整个丢了(图对检索不可见)、DOCX 完全不抽图、VLM caption 注入成游离文本块。落地(、[devlogs/2026-06-21-images-for-rag.md](devlogs/2026-06-21-images-for-rag.md))。**① 图片成为一等 chunk**(`chunk.rs`):`ChunkKind::Image` + `Chunk.image{file,data_base64,media_type,caption,caption_source}`;page coverage ≥1% 门控(与 VLM 图门一致,滤图标/分隔线);按 bbox splice 进阅读顺序(与表格同 `follows`,统一 `Item::bbox`);caption 解析=VLM/enhancer caption 优先,否则就近匹配文档内 "Figure/Fig./图N/Abbildung" 行(邻接 ≤40pt + 水平重叠),被绑定的 caption 行从正文流剔除不重复成段落;context=图周边水平重叠正文(≤300 字);`text`=caption ⊕ context(可检索字段)。**② VLM caption 写回**(`vlm/lib.rs`):`annotate_pictures` 不再 push `[figure]` 文本块,改写回对应 `ImageChunk.caption`(`caption_source:"vlm:<model>"`),经 ① 自动进 image chunk;`output.rs` 用 caption 作 markdown `![alt]`、text 输出补 caption 行(`PageContent.images` 放宽含 caption-only)。**③ DOCX 抽图**(`docx/lib.rs`):段落 run 的 `w:drawing→a:blip r:embed` 经 `docx.images`(rId→字节)落 `PageBuilder::image`(`synth.rs` 新增,合成 PDF 用户空间 bbox,EMU→pt `/12700`);新增 `ImageKind::Encoded`(已编码 PNG/JPEG 透传,DOCX/PPTX/HTML 媒体共用),`export_images`/`embed_images` 支持(字节原样写盘/base64,扩展名/mime 由源定)。**字节兼容**:`SCHEMA_VERSION` 0.7.0→0.8.0,新增字段/枚举变体均可选向后兼容;`-f json/text` 既有输出与三件套回归不变。验收:core/docx/output 新单测 + 真实 PDF(1901.03003,11 image chunk,caption 由 "Figure 2./3." 绑定)+ 真实 DOCX(python-docx 造,caption "Figure 1" 绑定 + PNG 经 Encoded 导出)端到端;34 套件绿、clippy 0。**范围**:已覆盖 **PDF / DOCX / PPTX / HTML** 图片。**续(同日,commit 4)**:PPTX 抽图——`load_media` 预载 `ppt/media/*` + 解析每 slide `_rels`(rId→media 路径,`resolve_target` 处理 `.`/`..`/绝对),`parse_slide` 在 `<p:pic>` 捕获 `<a:ext>`(EMU)+`<a:blip r:embed>` 于 `</p:pic>` 内联调 `PageBuilder::image`;顺带把 `emu_to_pt`/`image_mime_from_path` 提为 `core::synth` 共享 helper(DOCX 去重)。**续(commit 5)**:HTML 抽图——`PageBuilder::image` 加 `caption` 参数(HTML `alt` 直接作图说,`caption_source="alt"`);`parse` 透 base dir,`walk` 加 `<img>` 分支,`image_bytes` 解 `data:` URI(base64 解码)/ 文件相对路径(读盘),远程 `http(s)://`/`//` 不抓取;尺寸 px→pt(×0.75)。单测(data:URI+alt、远程跳过、相对路径经 base dir、无 base 跳过 + resolve_target)+ 真实端到端(含图 html → image chunk + PNG 导出,caption=alt、context=周边段落)。**续(commit 6,版面 Caption 绑定)**:之前图说只靠文本 pattern("Figure N");`--layout`/tagged-PDF 能精确给 Caption 区域但到 chunk 层丢了(`tag_role` 对 Caption 返 None、Block 不带 tag)。补链:`tag_role` Caption→`"Caption"`;`Line`/`Acc`/`Block` 加 `caption: bool` 透传(仿 `tag_level`/`tagged_body`);`chunk.rs` `block_is_caption` = 版面/tagged caption **或** 文本 pattern,`find_caption` 返来源(`layout-caption` vs `caption-line`)。**默认零回归**(无 layout/tagged 时 `b.caption` 恒 false 走原 pattern);单测证无 "Figure" 前缀的 `tag=Caption` 图说被正确绑定(`caption_source=layout-caption`)。**caption 三档来源**:`caption-line`(文本 pattern)/ `layout-caption`(版面/tagged Caption 区域)/ `alt`(HTML)/ `vlm:<model>`(神经)。**待续**:DOCX/PPTX 表格单元格内图片(窄场景)。

## 2. 记分牌（两套互补）

① **OmniDocBench**（人工真值，模型路径，第一参考）——文本/公式（UniRec）各 ~0.87（论文子集近论文级）、表结构 TEDS_X 0.810（median 0.895，80 表）、套官方公式 Overall ≈75（对标 OpenDoc-0.1B 90.67 / Docling ~80–85 / Marker 78.44）；**短板=学术难表**（端到端 0.52，模型天花板）+ 轻量 `--ocr` mobile（0.42–0.44，用 `--transcribe-model` 提质）。

② **一致度**（born-digital，vs ODL/Docling，压扁口径会反噬合并格）——NID 0.792/0.822、MHS 0.685/0.643、TEDS_X 0.477/0.526。

## 3. 待办

- **候外部输入**：PyPI/crates.io/MCP-registry 发布（账号，已暂缓）、arXiv 千份压测与 fuzz 24h（资源/排期）；
- **候设计/按需**：行内公式、G3b 确定性 span 推断、JATS/METS-ALTO、RTL（韩文 CJK 缺口属 VLM 服务域）、Markdown-span 输出；难表→`--vlm-tables` 真实服务实测；
- （OCR 档已定案=`--transcribe-model`；AsciiDoc 已落地。）

## 4. 跨阶段经验教训

1. **记分牌大跳几乎全是评测/输出管线 bug**——分数可疑先怀疑管线；
2. **参照系口径会反噬更忠实的输出**（span vs 压扁），产品价值用语义样例验收；
3. **依赖版本本身可以是性能特性**（tract 0.21→0.23 = 17×）；
4. **管道退出码会掩盖失败**；
5. **新格式 e2e 是共享层的免费测试矩阵**；
6. **提分前先验"瓶颈是模型还是尺子"**——便宜旋钮（max_tokens/重采样/分辨率）对模型天花板无效，抬 token 上限反让难表退化更久；
7. **口径判断必须看图坐实**（#4/#16 初判"口径反噬"裁图后推翻=实为模型 over-split）；
8. **"显然该有用"的杠杆也要量**——分辨率确是真杠杆（饥饿表 0→0.97）但脆模型对它净≈0 且破速度门，**真杠杆≠能用**。
