From d4e4ad67d5e26239cd4aa606dbc06e4ce43e4d52 Mon Sep 17 00:00:00 2001 From: myhloli Date: Fri, 5 Jun 2026 19:34:19 +0800 Subject: [PATCH] feat: add hybrid-flash processing functions for synchronous and asynchronous analyze stages --- .../hybrid_flash/hybrid_flash_analyze.py | 4 + mineru/cli/common.py | 188 +++++++++++++++--- 2 files changed, 162 insertions(+), 30 deletions(-) diff --git a/mineru/backend/hybrid_flash/hybrid_flash_analyze.py b/mineru/backend/hybrid_flash/hybrid_flash_analyze.py index 212a7c24..3710c606 100644 --- a/mineru/backend/hybrid_flash/hybrid_flash_analyze.py +++ b/mineru/backend/hybrid_flash/hybrid_flash_analyze.py @@ -297,6 +297,8 @@ def doc_analyze( **kwargs, ): """hybrid-flash第一阶段analyze:返回pipeline形态model_list和backend元信息。""" + # hybrid-flash 当前只执行 analyze,客户端输出开关属于最终输出阶段,不能下传到模型初始化。 + kwargs.pop("client_side_output_generation", None) if predictor is None: predictor = ModelSingleton().get_model(backend, model_path, server_url, **kwargs) predictor = _maybe_enable_serial_execution(predictor, backend) @@ -425,6 +427,8 @@ async def aio_doc_analyze( **kwargs, ): """异步hybrid-flash analyze入口,返回pipeline形态model_list和backend元信息。""" + # hybrid-flash 当前只执行 analyze,客户端输出开关属于最终输出阶段,不能下传到模型初始化。 + kwargs.pop("client_side_output_generation", None) if predictor is None: predictor = await _get_model_async(backend, model_path, server_url, **kwargs) predictor = _maybe_enable_serial_execution(predictor, backend) diff --git a/mineru/cli/common.py b/mineru/cli/common.py index f029dd76..130c281a 100644 --- a/mineru/cli/common.py +++ b/mineru/cli/common.py @@ -10,6 +10,7 @@ from typing import Sequence from loguru import logger +from mineru.cli.backend_options import normalize_backend from mineru.data.data_reader_writer import FileBasedDataWriter from mineru.utils.draw_bbox import draw_layout_bbox, draw_span_bbox from mineru.utils.engine_utils import get_vlm_engine @@ -69,9 +70,15 @@ def ensure_backend_dependencies(backend: str) -> None: def _load_hybrid_analyze_entrypoint(entrypoint_name: str, backend: str): + """按 hybrid 后端家族加载普通 hybrid 或 hybrid-flash 的 analyze 入口。""" ensure_backend_dependencies(backend) + module_name = ( + "mineru.backend.hybrid_flash.hybrid_flash_analyze" + if backend.startswith("hybrid-flash-") + else "mineru.backend.hybrid.hybrid_analyze" + ) try: - hybrid_analyze = importlib.import_module("mineru.backend.hybrid.hybrid_analyze") + hybrid_analyze = importlib.import_module(module_name) except (ImportError, ModuleNotFoundError) as exc: raise HybridDependencyError( build_hybrid_dependency_error_message(backend) @@ -498,6 +505,112 @@ def _process_vlm( ) +def _process_hybrid_flash( + output_dir, + pdf_file_names, + pdf_bytes_list, + h_lang_list, + parse_method, + inline_formula_enable, + backend, + f_draw_layout_bbox, + f_draw_span_bbox, + f_dump_md, + f_dump_middle_json, + f_dump_model_output, + f_dump_orig_pdf, + f_dump_content_list, + f_make_md_mode, + table_enable, + server_url=None, + image_analysis=True, + **kwargs, +): + """同步运行 hybrid-flash analyze 阶段,当前只产出 analyze 调试结果。""" + hybrid_flash_doc_analyze = _load_hybrid_analyze_entrypoint( + "doc_analyze", + f"hybrid-flash-{backend}", + ) + + if not backend.endswith("client"): + server_url = None + + for idx, (pdf_bytes, lang) in enumerate(zip(pdf_bytes_list, h_lang_list)): + pdf_file_name = pdf_file_names[idx] + local_image_dir, _local_md_dir = prepare_env( + output_dir, + pdf_file_name, + f"hybrid_{parse_method}", + ) + image_writer = FileBasedDataWriter(local_image_dir) + + hybrid_flash_doc_analyze( + pdf_bytes=pdf_bytes, + image_writer=image_writer, + backend=backend, + parse_method=parse_method, + language=lang, + inline_formula_enable=inline_formula_enable, + table_enable=table_enable, + server_url=server_url, + image_analysis=image_analysis, + **kwargs, + ) + + +async def _async_process_hybrid_flash( + output_dir, + pdf_file_names, + pdf_bytes_list, + h_lang_list, + parse_method, + inline_formula_enable, + backend, + f_draw_layout_bbox, + f_draw_span_bbox, + f_dump_md, + f_dump_middle_json, + f_dump_model_output, + f_dump_orig_pdf, + f_dump_content_list, + f_make_md_mode, + table_enable, + server_url=None, + image_analysis=True, + **kwargs, +): + """异步运行 hybrid-flash analyze 阶段,当前只产出 analyze 调试结果。""" + aio_hybrid_flash_doc_analyze = _load_hybrid_analyze_entrypoint( + "aio_doc_analyze", + f"hybrid-flash-{backend}", + ) + + if not backend.endswith("client"): + server_url = None + + for idx, (pdf_bytes, lang) in enumerate(zip(pdf_bytes_list, h_lang_list)): + pdf_file_name = pdf_file_names[idx] + local_image_dir, _local_md_dir = prepare_env( + output_dir, + pdf_file_name, + f"hybrid_{parse_method}", + ) + image_writer = FileBasedDataWriter(local_image_dir) + + await aio_hybrid_flash_doc_analyze( + pdf_bytes=pdf_bytes, + image_writer=image_writer, + backend=backend, + parse_method=parse_method, + language=lang, + inline_formula_enable=inline_formula_enable, + table_enable=table_enable, + server_url=server_url, + image_analysis=image_analysis, + **kwargs, + ) + + def _process_hybrid( output_dir, pdf_file_names, @@ -684,6 +797,7 @@ def do_parse( client_side_output_generation=False, **kwargs, ): + backend = normalize_backend(backend) need_remove_index = _process_office_doc( output_dir, pdf_file_names=pdf_file_names, @@ -718,10 +832,7 @@ def do_parse( if backend.startswith("vlm-"): backend = backend[4:] - if backend == "vllm-async-engine": - raise Exception("vlm-vllm-async-engine backend is not supported in sync mode, please use vlm-vllm-engine backend") - - if backend == "auto-engine": + if backend == "engine": backend = get_vlm_engine(inference_engine='auto', is_async=False) os.environ['MINERU_VLM_FORMULA_ENABLE'] = str(formula_enable) @@ -737,24 +848,33 @@ def do_parse( elif backend.startswith("hybrid-"): ensure_backend_dependencies(backend) backend = backend[7:] + is_flash = backend.startswith("flash-") - if backend == "vllm-async-engine": - raise Exception( - "hybrid-vllm-async-engine backend is not supported in sync mode, please use hybrid-vllm-engine backend") + if is_flash: + backend = backend[6:] - if backend == "auto-engine": + if backend == "engine": backend = get_vlm_engine(inference_engine='auto', is_async=False) os.environ['MINERU_VLM_TABLE_ENABLE'] = str(table_enable) os.environ['MINERU_VLM_FORMULA_ENABLE'] = "true" - _process_hybrid( - output_dir, pdf_file_names, pdf_bytes_list, p_lang_list, parse_method, formula_enable, backend, - f_draw_layout_bbox, f_draw_span_bbox, f_dump_md, f_dump_middle_json, - f_dump_model_output, f_dump_orig_pdf, f_dump_content_list, f_make_md_mode, - server_url, image_analysis=image_analysis, - client_side_output_generation=client_side_output_generation, **kwargs, - ) + if is_flash: + _process_hybrid_flash( + output_dir, pdf_file_names, pdf_bytes_list, p_lang_list, parse_method, formula_enable, backend, + f_draw_layout_bbox, f_draw_span_bbox, f_dump_md, f_dump_middle_json, + f_dump_model_output, f_dump_orig_pdf, f_dump_content_list, f_make_md_mode, + table_enable, server_url, image_analysis=image_analysis, + client_side_output_generation=client_side_output_generation, **kwargs, + ) + else: + _process_hybrid( + output_dir, pdf_file_names, pdf_bytes_list, p_lang_list, parse_method, formula_enable, backend, + f_draw_layout_bbox, f_draw_span_bbox, f_dump_md, f_dump_middle_json, + f_dump_model_output, f_dump_orig_pdf, f_dump_content_list, f_make_md_mode, + server_url, image_analysis=image_analysis, + client_side_output_generation=client_side_output_generation, **kwargs, + ) async def aio_do_parse( @@ -781,6 +901,7 @@ async def aio_do_parse( client_side_output_generation=False, **kwargs, ): + backend = normalize_backend(backend) # Office 解析是同步且可能耗时的操作,异步入口需要放到线程中避免阻塞事件循环。 need_remove_index = await asyncio.to_thread( _process_office_doc, @@ -818,10 +939,7 @@ async def aio_do_parse( if backend.startswith("vlm-"): backend = backend[4:] - if backend == "vllm-engine": - raise Exception("vlm-vllm-engine backend is not supported in async mode, please use vlm-vllm-async-engine backend") - - if backend == "auto-engine": + if backend == "engine": backend = get_vlm_engine(inference_engine='auto', is_async=True) os.environ['MINERU_VLM_FORMULA_ENABLE'] = str(formula_enable) @@ -837,23 +955,33 @@ async def aio_do_parse( elif backend.startswith("hybrid-"): ensure_backend_dependencies(backend) backend = backend[7:] + is_flash = backend.startswith("flash-") - if backend == "vllm-engine": - raise Exception("hybrid-vllm-engine backend is not supported in async mode, please use hybrid-vllm-async-engine backend") + if is_flash: + backend = backend[6:] - if backend == "auto-engine": + if backend == "engine": backend = get_vlm_engine(inference_engine='auto', is_async=True) os.environ['MINERU_VLM_TABLE_ENABLE'] = str(table_enable) os.environ['MINERU_VLM_FORMULA_ENABLE'] = "true" - await _async_process_hybrid( - output_dir, pdf_file_names, pdf_bytes_list, p_lang_list, parse_method, formula_enable, backend, - f_draw_layout_bbox, f_draw_span_bbox, f_dump_md, f_dump_middle_json, - f_dump_model_output, f_dump_orig_pdf, f_dump_content_list, f_make_md_mode, - server_url, image_analysis=image_analysis, - client_side_output_generation=client_side_output_generation, **kwargs, - ) + if is_flash: + await _async_process_hybrid_flash( + output_dir, pdf_file_names, pdf_bytes_list, p_lang_list, parse_method, formula_enable, backend, + f_draw_layout_bbox, f_draw_span_bbox, f_dump_md, f_dump_middle_json, + f_dump_model_output, f_dump_orig_pdf, f_dump_content_list, f_make_md_mode, + table_enable, server_url, image_analysis=image_analysis, + client_side_output_generation=client_side_output_generation, **kwargs, + ) + else: + await _async_process_hybrid( + output_dir, pdf_file_names, pdf_bytes_list, p_lang_list, parse_method, formula_enable, backend, + f_draw_layout_bbox, f_draw_span_bbox, f_dump_md, f_dump_middle_json, + f_dump_model_output, f_dump_orig_pdf, f_dump_content_list, f_make_md_mode, + server_url, image_analysis=image_analysis, + client_side_output_generation=client_side_output_generation, **kwargs, + ) if __name__ == "__main__":