• 1.3.11 Stable

    iomgaa released this 2026-10-22 15:55:15 +08:00 | 0 commits to main since this release

    #34: OCR 新增整份文档解析 OcrClient.parse_document——CHSAnalyzer3 要把几百篇医学论文 PDF 整份转成带页码的正文与表格,而库只会返回版面框,且 PDF 被写死的 image.jpg 标签当成图片上传。两个症状同根: 库把 OCR 输入建模成「一张图 + 写死的标签」、输出建模成「只有框」,没有「文档」这个概念。设计 research-wiki/designs/2026-10-21-issue34-ocr-document-design.md。

    新增

    • OcrClient.parse_document(document, *, session_id, parent_call_id, tenant_id, meta, call_deadline_s) -> OcrDocumentResult: PDF 或图片整份交给 MonkeyOCR,返回逐块 OcrDocumentElement(继承 OcrLayoutElement,仅多一个 text:text/title 纯文本、interline_equation 裸 LaTeX、table HTML、image 空串)、page_sizes 与整篇 markdown。治理链路(限流/熔断/重试/换源/遥测/调用方维度/期限)复用 parse_layout 的同一循环。
    • 公共类型 OcrDocumentElement / OcrDocumentResult(包根导出);端口 OcrDocumentPort;transport 协议 OcrDocumentTransport(独立于 OcrTransport,不动旧 runtime_checkable Protocol 的成员,既有实现与测试替身不受影响)。
    • CallOperation / 遥测 operation 列新增取值 parse_document(无 DDL、无新列);占位 <ocr:document image_bytes=N>,response 摘要 <document elements=N pages=M md_chars=K> 永不写入正文。
    • 内容只取自 _middle.json 与配对的 .md,有意不读 _content_list.json(它是展示格式,星号、反引号、波浪线、美元符会被转义,实测 134 个文字块中 23 个含 \$)。

    修复

    • recognize_text(PDF) 的静默错误结果: 此前一切上传都标 image.jpg,而 MonkeyOCR 按扩展名分派。PDF 走图片分支时 /ocr/text 返回 success:true 与幻觉文本(实测 "The image contains a single character: 1"),下游完全无感。上传标签现在按内容判定(图片魔数优先,再看前 1KB 内的 %PDF-),三个方法共用一处;非 PDF 一律沿用 image.jpg(字节级不变)。

    行为变更

    • parse_layout(PDF) 的上传分支改变(图片数据集 → PDF 数据集)。差分实测: 文本型与无文字层的位图型 2 页 PDF,_middle.json / _content_list.json / .md 逐字相同;旋转页、带批注页未测,不构成保证。该路径无已知消费者(医院线只传图片)。

    • parse_document 对坏输入的处置(仅新路径;共享分类不变): 无法识别的字节本地拒绝、不发请求(RequestRejectedError,不带 status_code);服务把一切失败包成 500 "Parsing failed: …",其中已知的确定性坏输入(Failed to open stream / Cannot open empty stream / cannot save with zero pages / is no PDF)归 RequestRejectedError(status_code=500)——不重试、熔断记成功不计窗口。其余 500 仍是瞬时错误。标记集合可经 MonkeyOcrTransport(deterministic_failure_markers=...) 覆盖。

    • 结果包解析的外部数据异常边界统一落 ResultInvalidError(含加密成员、未知压缩方法、巨大整数、深嵌套 JSON),不漏出原生异常——漏出会让客户端既不写终态遥测行、又不归还半开探针。

    • 文档路径流式下载并限制整包 256 MiB;_middle.json ≤ 64 MiB、.md ≤ 16 MiB;解析在线程里执行,取消与 call_deadline_s 基本不被占住(json.loads 持有 GIL:实测 54 MiB 的 middle 解析约 1 秒、事件循环最大停顿约 0.3 秒;取消后线程自然收尾、占用默认 executor,正常大小是毫秒级)。parse_layout 的口径(bbox 整数化退化校验、page_idx 回退、无体积上限)保持原样。

    • 共用的第一段协议新增 download_url 校验: 服务响应里的畸形 download_url(控制字符、坏方括号、越界端口)此前会让 httpx.InvalidURL 等原生异常漏出 OcrClient(不写终态遥测行、半开探针不归还),现归瞬时错误、不发第二段请求。parse_layout 共用这一处,属顺带修复;不限制主机(基线接受绝对地址)。

    • 嗅探副作用: 前 1KB 内含 %PDF- 字样的非 PDF 文本在 recognize_text / parse_layout 上的上传标签由 image.jpg 变为 doc.pdf;BMP 魔数现要求 DIB 头大小字段合法(不再单凭 BM 两字节)。

    • 文档路径读取错误响应体只读前 64 KiB(摘要只需要几 KiB),不再把巨大的错误体整个读进内存。

    已知限制与运维须知(详见 README「1.3.11 OCR 整份文档解析」)

    • 服务端丢弃脚注、页眉页脚、页码,表格脚注里的显著性/缩写说明不会出现在结果里。
    • timeout_s 不是总耗时上界(逐次网络操作的超时);长文档的硬上限靠 call_deadline_s,并应 ≤ lease_ttl_s。库内无基于延迟的熔断,长文档不会被判慢。
    • 若 MonkeyOCR 部署为 sync-only 后端(出厂默认 lmdeploy),/parse 每实例全局串行,长文档会队头阻塞同实例上医院线的请求;请向运维确认 backend。
    • 多行/多 span 文本块、inline_equation span、嵌套 caption/footnote 子块三条路径无真实样本,仅由构造 fixture 测试。
    • 后续: #35(共享分类对确定性坏输入 500 仍归瞬时)、#36(recognize_text 不校验服务响应的 success)、#37(parse_layout 结果包解析仍漏出原生异常、download_url 不限制主机)。
    Downloads