Skip to content

支持 MinerU convert 与 Processor 链路,缓解 Docling bad_alloc 转换失败 #151

Description

@noho

背景

当前 PDF 转换链路主要依赖 Docling:上传和 CN/HK 下载会生成 *_docling.json,后续由 DoclingProcessor / FinsDoclingProcessor 供 LLM 读取。部分用户反馈 Docling convert 经常出现 std::bad_alloc / bad_alloc 类错误,导致财报转换失败。

动机判断

问题真实存在,且严重性不应低估:

  • 代码本身已经把 Docling Windows std::bad_alloc 作为已知故障写进 dayu/docling_runtime.py 的策略说明,并做了 pypdfium2 / CPU 回退链;这说明当前 Docling runtime 已经在处理同类稳定性问题,但仍未覆盖用户遇到的全部失败面。
  • 对 CN/HK 财报下载来说,Docling 转换失败会直接让单份 filing 失败,无法进入 ingest_complete=True 的完成态。
  • 只换 convert 命令不够,因为当前 LLM 读取链路依赖 Docling JSON schema;MinerU 输出 schema 与 Docling 不兼容,必须同时落 convert 与 processor。

当前 Docling 使用链路

1. 依赖与 runtime 真源

  • pyproject.toml 当前把 docling>=2.90.0,<3.0.0docling-core>=2.74.0,<3.0.0 放在生产依赖。
  • dayu/docling_runtime.py 是 Docling SDK 调用真源:
    • convert_pdf_bytes_with_docling(raw_bytes, stream_name=...) 把 PDF bytes 包成 DocumentStream,避免路径编码问题。
    • run_docling_pdf_conversion(...) 负责 backend × device 尝试链。
    • build_docling_pdf_converter(...) 固定 Docling DocumentConverter / PdfFormatOption / backend 策略。
  • dayu/fins/docling_export.py 是 fins 侧唯一出口:
    • convert_pdf_bytes_to_docling_payload(...) -> dict[str, Any]
    • convert_pdf_bytes_to_docling_json_bytes(raw_data, stream_name) -> bytes
    • PdfToDoclingJsonBytes = Callable[[bytes, str], bytes] 是 CN/HK 下载注入点。

2. 上传链路

  • dayu/fins/pipelines/docling_upload_service.pyDoclingUploadService 读取原文件后,对每个文件调用 _convert_with_docling
  • 转换产物固定命名为 <stem>_docling.jsonsource="docling"content_type="application/json"
  • primary_document_pick_primary_docling_file(...) 选择,要求存在 _docling.json
  • SEC/CN pipeline 上传 filing/material 都复用这个 service。

3. CN/HK 下载链路

  • dayu/fins/pipelines/cn_pipeline.py 构造时默认注入 convert_pdf_bytes_to_docling_json_bytes
  • dayu/fins/pipelines/cn_download_workflow.py 把 host 的 convert_pdf_to_docling_json 传给单 filing workflow。
  • dayu/fins/pipelines/cn_download_filing_workflow.py
    • PDF 下载/复用后,若无可复用 Docling JSON,就在线程里调用 converter。
    • 产物固定为 ${document_id}_docling.json
    • commit source meta 时把 primary_document 切到该 _docling.json
  • 多处硬编码需要处理:
    • cn_download_source_upsert.py 校验完成态 primary_document.endswith("_docling.json")
    • cn_download_rebuild.py / cn_download_staging.py 使用 _docling.json 判断可复用与重建。
    • cn_download_models.py / README 文案把完成态定义成 Docling JSON。

4. Processor / LLM 读取链路

  • Engine 注册表 dayu/engine/processors/registry.py 默认注册 DoclingProcessorMarkdownProcessorBSProcessor
  • Fins 注册表 dayu/fins/processors/registry.pyFinsDoclingProcessor 覆盖 engine 的 docling_processor
  • DoclingProcessor.supports(...) 通过 _docling.json 后缀或 sniff Docling JSON 判断支持。
  • DoclingProcessor 依赖 docling_core.types.doc.document.DoclingDocument.load_from_json(...),然后构建 sections/tables/page_content/search。
  • FinsDoclingProcessor 继承 DoclingProcessor,再对 _tables 做金融语义 relabel。

MinerU 使用方式调研

基于 2026-05-04 查阅的官方资料:

  • 官方 README 说明 MinerU 面向 LLM/RAG/Agent,可把 PDF、图片、DOCX、PPTX、XLSX 转为 Markdown / JSON;README release 区显示最新 release 为 mineru-3.1.6(2026-04-28)。来源:https://github.com/opendatalab/MinerU
  • 安装:官方 Quick Start 推荐 uv pip install -U "mineru[all]"mineru[all] 覆盖 Windows / Linux / macOS 的核心能力。来源:https://github.com/opendatalab/MinerU#install-mineru
  • CLI 基本用法:mineru -p <input_path> -o <output_path>;无 GPU 或希望纯 CPU 时可指定 -b pipeline。来源:https://opendatalab.github.io/MinerU/usage/quick_usage/
  • CLI 当前是 mineru-api 之上的 orchestration client:不传 --api-url 会启动临时本地 mineru-api;传 --api-url 会连接已有 FastAPI 服务。来源:https://opendatalab.github.io/MinerU/usage/cli_tools/
  • 关键 CLI 参数:
    • -p/--path 输入文件或目录。
    • -o/--output 输出目录。
    • -m/--method [auto|txt|ocr]
    • -b/--backend [pipeline|hybrid-auto-engine|hybrid-http-client|vlm-auto-engine|vlm-http-client],默认 hybrid-auto-engine
    • -l/--lang 指定 OCR 语言。
    • -f/--formula-t/--table 控制公式/表格。
    • -s/--start-e/--end 控制页码。
  • 服务端方式:mineru-api --host 0.0.0.0 --port 8000,提供 /healthPOST /tasksPOST /file_parseGET /tasks/{task_id}GET /tasks/{task_id}/result。来源:https://opendatalab.github.io/MinerU/usage/quick_usage/
  • 输出文件:MinerU 会生成 Markdown、layout/span 可视化文件,以及 structured JSON。对 Dayu 最有用的是:
    • {original_filename}_content_list.json:扁平阅读序内容,适合后续处理。
    • {original_filename}_middle.json:包含 pdf_info,适合二次开发与 page/block 级信息。
    • {original_filename}_content_list_v2.json:3.0 起新增,按页分组、统一 type + content,但官方标注为 development version,需谨慎作为稳定契约。
      来源:https://opendatalab.github.io/MinerU/reference/output_files/
  • content_list.json 里 table block 包含 table_body HTML,caption/footnote/page_idx/bbox 等字段;这适合在 Processor 中用结构化 HTML 表格解析成 records/markdown。
  • 环境变量:MINERU_MODEL_SOURCE 可切换模型源;MINERU_PDF_RENDER_TIMEOUTMINERU_PDF_RENDER_THREADSMINERU_PROCESSING_WINDOW_SIZEMINERU_API_MAX_CONCURRENT_REQUESTS 等可控制渲染和服务并发。来源:https://opendatalab.github.io/MinerU/usage/cli_tools/
  • 资源约束:官方表格显示 pipeline backend 支持纯 CPU,但仍建议 16GB+ RAM;hybrid/vlm 精度高但硬件要求更高。来源:https://github.com/opendatalab/MinerU#local-deployment

架构建议

不建议的做法

不要把 MinerU 输出伪装成 _docling.json 或让 DoclingProcessor 读取 MinerU JSON。两者 schema 不同,伪装会把根因隐藏到 processor 内部,后续表格/章节/page_content 都会变成脆弱分支。

建议方向

把当前 Docling 专有链路抽象成“文档转换产物”链路,但转换器与处理器保持成对实现:

  1. 新增 provider 概念:docling / mineru
  2. Converter 层输出强类型 ConvertedDocumentAsset,至少包含:
    • name: str
    • data: bytes
    • content_type: str
    • source_label: str,如 docling / mineru
    • parser_format: str,如 docling_json / mineru_content_list
    • 可选 converter_version: str | None
  3. 上传 service 不应继续叫 DoclingUploadService 如果它开始支持多个 provider;建议改名为 provider-neutral 的 DocumentConversionUploadService 或同类名字,并更新调用点。
  4. CN/HK download host 不应继续暴露 convert_pdf_to_docling_json;建议替换为 convert_pdf_to_primary_documentdocument_converter,让 workflow 不关心 provider 具体 suffix。
  5. Processor 层新增:
    • dayu/engine/processors/mineru_processor.py
    • dayu/fins/processors/fins_mineru_processor.py
    • 在 engine/fins registry 注册,优先级与 Docling/Markdown 同级;靠 supports() 通过后缀与 JSON sniff 判定。
  6. 元数据层建议显式记录转换 provider:
    • source meta 增加 conversion_providerconversion_parser_formatconverter_version
    • file entry 的 source 从当前 docling 扩展为 mineru
    • 如果这被视为 schema 变更,需要按项目约束补 workspace_migrations 插件进入 dayu-cli init,为既有 _docling.json 元数据填充 conversion_provider="docling"

MinerU 产物选择建议

第一阶段建议把 *_content_list.json 作为 primary document:

  • 它是官方定位的“简化、阅读序、适合后续处理”的内容列表。
  • section/list/search 可以直接基于阅读序构建。
  • table 有 HTML table_body,可复用现有 HTML/table 工具或 pandas.read_html 解析。
  • page_idx 可映射到 Dayu 的 1-based page_no

*_middle.json 可以作为附加文件存储,不建议第一阶段作为 primary:它更适合二次开发和调试,结构更重,processor 实现复杂度高。content_list_v2 虽然更规整,但官方标注 development version,第一阶段不宜把它作为稳定读取契约。

代码实施建议

  1. 新增 MinerU converter runtime:

    • 新模块建议 dayu/mineru_runtime.pydayu/fins/mineru_export.py
    • 第一阶段优先走 CLI/subprocess 或本地 mineru-api,不要直接绑定不稳定的内部 Python API;CLI/API 是官方文档主路径。
    • 输入 bytes 时需要写入 workspace/tmp/ 或临时目录,并确保清理;项目约束要求临时脚本/临时文件在 workspace/tmp/
    • 输出目录解析应定位 <output>/<stem>/<method_or_backend>/..._content_list.json;需要单测覆盖不同 backend 目录名。
    • 支持配置项:backend 默认建议 pipeline(稳定、CPU 可用),lang 默认可按市场设 ch/en,method 默认 auto,table/formula 默认开启。
  2. 重构转换注入边界:

    • 用 provider-neutral Protocol 替代 PdfToDoclingJsonBytes
    • CN/HK download workflow 只消费 ConvertedDocumentAsset,不拼 _docling.json
    • 上传 service 对每个原始文件追加 provider 产物,并用统一 _pick_primary_converted_file(...) 选择 primary。
  3. 清理 Docling 硬编码:

    • DOCLING_FILE_SUFFIX 改成 provider suffix 配置,Docling 可继续是 _docling.json,MinerU 建议 _mineru_content_list.json_mineru.json
    • cn_download_source_upsert.py_docling.json 校验改为“primary 指向受支持的转换产物”。
    • cn_download_rebuild.py / cn_download_staging.py 改成按 provider/suffix 查找可复用产物。
    • 文案中的“Docling JSON 完成态”改成“转换 JSON 完成态”。
  4. 实现 MinerUProcessor

    • supports():后缀命中 _mineru_content_list.json / _mineru.json,或 JSON sniff 到 list 且元素包含 MinerU content block 字段(type + page_idx + text/table_body/...)。
    • list_sections():把 text_level >= 1 的 text/title 作为 section header;无标题时构造单一全文 section。
    • read_section():按 section range 拼正文;遇到 table 插入 [TABLE: table_1] 风格占位,保持与现有工具语义一致。
    • list_tables():扫描 type == "table",caption 来自 table_caption,context_before 从前序文本窗口取,page_no=page_idx + 1
    • read_table():优先把 table_body HTML 解析为 records;失败则返回 HTML/markdown 字符串。
    • get_page_content():按 page_idx 收集 sections/tables/text_preview。
    • search() / get_full_text() 可复用 run_titled_section_search 与现有 text_utils。
  5. 实现 FinsMinerUProcessor

    • 继承 MinerUProcessor
    • 复用 relabel_tables(self._tables) 的金融表格标注思路;如果内部 table block 字段不同,先定义与 financial_enhancer 兼容的 table block 或抽出 provider-neutral table semantic 输入。
  6. 依赖策略:

    • 不建议直接把 mineru[all] 放入默认生产依赖,体量和硬件依赖都较重。
    • 建议新增 optional extra,例如 mineru = ["mineru[pipeline]>=3.1,<4"] 或文档要求用户自行安装;具体 extras 名称以验证后的 pip 解析结果为准。
    • 如果默认配置允许 DAYU_PDF_CONVERTER=mineru,启动时必须给出明确缺依赖错误,不要在深层 subprocess 抛不可读异常。

测试建议

  • Converter 单测:fake subprocess / fake output directory,验证能从 MinerU 输出目录读取 content_list.json 并返回 ConvertedDocumentAsset
  • Processor 单测:用最小 MinerU content_list.json fixture 覆盖 section/table/search/page_content。
  • Fins processor 单测:验证金融表格 relabel 不回退。
  • Upload service 单测:同一原始文件在 provider 不变且 fingerprint 相同时 convert 前跳过;provider 变化时应重新转换并更新 primary。
  • CN/HK download workflow 单测:转换失败 reason 不再写死 docling_convert_failed,建议改为 document_convert_failed 或带 provider 的 mineru_convert_failed
  • Rebuild/staging 单测:能复用 MinerU primary,也能识别 Docling primary(如果迁移策略保留历史数据)。
  • Pyright:新增 Protocol/dataclass/TypedDict 后必须全量跑。

README 触发范围

如果落地实现修改以下目录,应按项目规则同步:

  • dayu/fins/:更新 dayu/fins/README.md,说明转换 provider、两条执行路径、产物选择。
  • dayu/engine/:更新 dayu/engine/README.md,说明 MinerUProcessor 与 Processor registry。
  • CLI/config 入口变更:更新根目录 README.mddayu/config/README.md
  • 测试结构变更:更新 tests/README.md

验收标准

  • 可通过配置选择 Docling 或 MinerU 作为 PDF convert provider。
  • MinerU convert 成功后,source meta 的 primary_document 指向 MinerU JSON 产物。
  • FinsToolService 对 MinerU primary document 能正常执行:list_sectionsread_sectionlist_tablesget_tableget_page_contentsearch_document
  • Docling bad_alloc 不再阻断选择 MinerU 的用户。
  • 受影响测试和 pyright 通过;相关 README 已同步。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions