MinerU PDF Parsing#
RAGFlow integrates with MinerU as one of several selectable PDF parse backends (alongside deepdoc, docling, opendataloader, and others) . MinerU runs as a separate API server; RAGFlow posts PDF files to it via POST /file_parse and receives a ZIP archive containing content_list.json, middle.json, and page images .
The core implementation lives in deepdoc/parser/mineru_parser.py (MinerUParser class), which extends RAGFlowPdfParser. The entry point for document processing is parse_pdf(), which:
- Accepts OCR language, parse method (
auto/txt/ocr), formula/table flags fromparser_config - Writes the PDF to a temp file if
binaryis provided - Calls
_run_mineru_api()to POST to the MinerU API server and stream back the ZIP - Reads
*_content_list.jsonfrom the ZIP via_read_output() - Converts content blocks to RAGFlow sections via
_transfer_to_sections()
MinerU output bounding boxes are in 0–1000 normalized coordinates. _line_tag() converts these to pixel coordinates by scaling against rendered page image size, and swaps inverted coordinates (x0 > x1, top > bott) that MinerU sometimes emits for rotated pages .
Supported backends are enumerated in MinerUBackend: pipeline (default multimodel), several vlm-* variants (HuggingFace Transformers, vLLM, MLX, LMDeploy), and vlm-http-client for remote VLM servers. The vlm-http-client backend additionally requires MINERU_SERVER_URL .
Page Range Forwarding#
Problem#
RAGFlow's task dispatcher (in api/db/services/task_service.py) splits large PDFs into multiple page-range sub-tasks, each with a from_page/to_page window. These values are passed through to by_mineru() in rag/app/naive.py via the PARSERS registry call in the task executor . However, in the baseline implementation, by_mineru() accepted from_page/to_page in its signature but did not forward them to parse_pdf() or the MinerU API request . The API request payload hardcoded start_page_id: 0 and end_page_id: 99999 , so every page-range sub-task triggered a full-document parse—inflating chunk counts to N× the real count for an N-task document.
Fixes#
Multiple PRs addressed this across successive iterations:
-
PR #16957 threaded
page_from/page_tothrough the call chain:by_mineru()→parse_pdf()→_run_mineru()→_run_mineru_api(), and wired them into the API payload asstart_page_idandend_page_id. RAGFlow uses an exclusiveto_page(Python slice semantics); MinerU'send_page_idis 0-based inclusive, so the conversion isend_page_id = page_to - 1(or99999whenpage_to == MAXIMUM_PAGE_NUMBER) . -
PR #16857 made the same fix as part of a broader cross-parser patch covering Docling and OpenDataLoader as well. It added an additional correction: because MinerU pre-slices the PDF before analysis, returned
page_idxvalues are relative to the slice start. The fix offsets each output'spage_idxbystart_page_idto restore absolute document-level page numbers for downstream position tagging and chunk storage . -
PR #16600 (earlier iteration) also tackled the same gap, adding
start_page_id/end_page_idtoMinerUParseOptionsand updatingby_mineru(). It also handled a MinerU version difference: some versions returnpage_idxrelative to the slice start, others use absolute page numbers—detected by inspecting whether the minimum returnedpage_idx >= start_page_id.
Page Index Semantics#
| System | Convention |
|---|---|
RAGFlow from_page/to_page | 0-indexed, to_page is exclusive |
MinerU start_page_id/end_page_id | 0-indexed, end_page_id is inclusive |
Docling page_range | 1-indexed, inclusive |
OpenDataLoader pages | 1-indexed, human-readable string (e.g. "1-50") |
Deduplication / Redundant Re-parsing#
Before the page range fixes, every page-range task for a given PDF independently called the MinerU API with the full document — O(N) redundant full-document parses for an N-task PDF. There is no output caching in the current architecture: each parse_pdf() invocation creates a fresh tempfile.mkdtemp() directory, and the MinerU API server does not deduplicate requests .
The deduplication strategy adopted by the fix PRs is input narrowing rather than output caching: pass the exact from_page/to_page window to the MinerU API, so the server only parses and returns content for the requested page slice. Each sub-task sends a smaller payload and receives a smaller result—avoiding redundant work by construction. Task-level result reuse (matching chunks from prior runs via task digest) is handled separately at the task executor layer and is not specific to MinerU.
Key Files & References#
| File | Purpose |
|---|---|
deepdoc/parser/mineru_parser.py | MinerUParser — API client, output parsing, coordinate conversion |
rag/app/naive.py — by_mineru() | Adapter called by task executor; maps task params to parse_pdf() |
api/db/services/task_service.py | Task dispatcher that splits PDFs into page-range sub-tasks |
rag/svr/task_executor.py | Calls by_mineru() via PARSERS registry with from_page/to_page |
Related PRs: