deepdoc-lib 0.2.2__tar.gz → 0.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. deepdoc_lib-0.2.4/.github/workflows/ci.yml +92 -0
  2. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/PKG-INFO +2 -2
  3. deepdoc_lib-0.2.4/UPSTREAM_SYNC.md +86 -0
  4. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/__init__.py +1 -0
  5. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/_version.py +3 -3
  6. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/depend/simple_cv_model.py +8 -3
  7. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/__init__.py +2 -0
  8. deepdoc_lib-0.2.4/deepdoc/parser/epub_parser.py +139 -0
  9. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/excel_parser.py +78 -25
  10. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/html_parser.py +77 -14
  11. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/markdown_parser.py +240 -34
  12. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/mineru_parser.py +42 -22
  13. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/pdf_parser.py +388 -41
  14. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/ppt_parser.py +9 -3
  15. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/tcadp_parser.py +43 -23
  16. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/utils.py +1 -1
  17. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/vision/layout_recognizer.py +35 -24
  18. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/vision/ocr.py +8 -2
  19. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/vision/operators.py +1 -1
  20. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/vision/table_structure_recognizer.py +6 -1
  21. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc_lib.egg-info/PKG-INFO +2 -2
  22. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc_lib.egg-info/SOURCES.txt +2 -0
  23. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc_lib.egg-info/requires.txt +1 -1
  24. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc_lib.egg-info/scm_file_list.json +71 -67
  25. deepdoc_lib-0.2.4/deepdoc_lib.egg-info/scm_version.json +8 -0
  26. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/pyproject.toml +1 -1
  27. deepdoc_lib-0.2.2/.github/workflows/ci.yml +0 -31
  28. deepdoc_lib-0.2.2/deepdoc_lib.egg-info/scm_version.json +0 -8
  29. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/.env.example +0 -0
  30. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/.github/workflows/publish.yml +0 -0
  31. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/LICENSE +0 -0
  32. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/MANIFEST.in +0 -0
  33. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/README.md +0 -0
  34. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/README.md +0 -0
  35. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/README_zh.md +0 -0
  36. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/common/__init__.py +0 -0
  37. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/common/config_utils.py +0 -0
  38. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/common/connection_utils.py +0 -0
  39. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/common/file_utils.py +0 -0
  40. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/common/misc_utils.py +0 -0
  41. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/common/model_store.py +0 -0
  42. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/common/settings.py +0 -0
  43. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/common/tiktoken_cache.py +0 -0
  44. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/common/token_utils.py +0 -0
  45. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/config.py +0 -0
  46. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/depend/find_codec.py +0 -0
  47. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/depend/nltk_manager.py +0 -0
  48. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/depend/prompts/vision_llm_describe_prompt.md +0 -0
  49. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/depend/prompts/vision_llm_figure_describe_prompt.md +0 -0
  50. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/depend/prompts.py +0 -0
  51. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/depend/rag_tokenizer.py +0 -0
  52. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/depend/surname.py +0 -0
  53. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/depend/timeout.py +0 -0
  54. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/depend/vision_llm_chunk.py +0 -0
  55. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/dict/README.md +0 -0
  56. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/dict/huqie.txt +0 -0
  57. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/download_models.py +0 -0
  58. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/llm_adapter/__init__.py +0 -0
  59. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/llm_adapter/adapter.py +0 -0
  60. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/llm_adapter/utils.py +0 -0
  61. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/llm_adapter/vision.py +0 -0
  62. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/docling_parser.py +0 -0
  63. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/docx_parser.py +0 -0
  64. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/figure_parser.py +0 -0
  65. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/json_parser.py +0 -0
  66. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/resume/__init__.py +0 -0
  67. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/resume/entities/__init__.py +0 -0
  68. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/resume/entities/corporations.py +0 -0
  69. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/resume/entities/degrees.py +0 -0
  70. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/resume/entities/industries.py +0 -0
  71. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/resume/entities/regions.py +0 -0
  72. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/resume/entities/res/corp.tks.freq.json +0 -0
  73. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/resume/entities/res/corp_baike_len.csv +0 -0
  74. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/resume/entities/res/corp_tag.json +0 -0
  75. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/resume/entities/res/good_corp.json +0 -0
  76. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/resume/entities/res/good_sch.json +0 -0
  77. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/resume/entities/res/school.rank.csv +0 -0
  78. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/resume/entities/res/schools.csv +0 -0
  79. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/resume/entities/schools.py +0 -0
  80. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/resume/step_one.py +0 -0
  81. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/resume/step_two.py +0 -0
  82. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/parser/txt_parser.py +0 -0
  83. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/vision/__init__.py +0 -0
  84. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/vision/postprocess.py +0 -0
  85. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/vision/recognizer.py +0 -0
  86. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/vision/seeit.py +0 -0
  87. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/vision/t_ocr.py +0 -0
  88. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc/vision/t_recognizer.py +0 -0
  89. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc_config.example.yaml +0 -0
  90. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc_lib.egg-info/dependency_links.txt +0 -0
  91. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc_lib.egg-info/entry_points.txt +0 -0
  92. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/deepdoc_lib.egg-info/top_level.txt +0 -0
  93. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/scripts/download_models.py +0 -0
  94. {deepdoc_lib-0.2.2 → deepdoc_lib-0.2.4}/setup.cfg +0 -0
@@ -0,0 +1,92 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ pytest:
10
+ name: Pytest (${{ matrix.python-version }})
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up Python
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+ cache: "pip"
24
+
25
+ - name: Install dependencies
26
+ run: |
27
+ python -m pip install --upgrade pip
28
+ python -m pip install -e . pytest
29
+
30
+ - name: Resolve the built wheel with current PyJWT
31
+ run: |
32
+ python -m pip install build
33
+ python -m build --wheel
34
+ python -m pip install --dry-run --ignore-installed dist/*.whl 'PyJWT[crypto]>=2.15.1,<3'
35
+
36
+ # Preprovision immutable official data instead of asking the NLTK
37
+ # downloader to traverse the runner's proxy. Do not disable pathsec.
38
+ - name: Check out pinned NLTK test resources
39
+ uses: actions/checkout@v4
40
+ with:
41
+ repository: nltk/nltk_data
42
+ ref: 550b6625bcef1f2abff2ff770a5a0d272c9c6b2a
43
+ path: .ci-nltk-data
44
+ persist-credentials: false
45
+ sparse-checkout-cone-mode: false
46
+ sparse-checkout: |
47
+ packages/tokenizers/punkt.zip
48
+ packages/tokenizers/punkt_tab.zip
49
+ packages/corpora/wordnet.zip
50
+ packages/taggers/averaged_perceptron_tagger.zip
51
+ packages/taggers/averaged_perceptron_tagger_eng.zip
52
+
53
+ - name: Prepare and validate offline NLTK test resources
54
+ env:
55
+ DEEPDOC_NLTK_DATA_DIR: ${{ runner.temp }}/deepdoc-nltk-data
56
+ NLTK_DATA: ${{ runner.temp }}/deepdoc-nltk-data
57
+ run: |
58
+ for package in tokenizers/punkt tokenizers/punkt_tab corpora/wordnet \
59
+ taggers/averaged_perceptron_tagger taggers/averaged_perceptron_tagger_eng; do
60
+ python -m zipfile -e ".ci-nltk-data/packages/$package.zip" "$NLTK_DATA/$(dirname "$package")"
61
+ done
62
+ python -c 'from deepdoc.depend.nltk_manager import ensure_nltk_data; ensure_nltk_data(offline=True)'
63
+
64
+ - name: Run tests
65
+ env:
66
+ DEEPDOC_NLTK_DATA_DIR: ${{ runner.temp }}/deepdoc-nltk-data
67
+ NLTK_DATA: ${{ runner.temp }}/deepdoc-nltk-data
68
+ run: python -m pytest tests
69
+
70
+ minimum-sdk:
71
+ name: Minimum SDK contract (3.10)
72
+ runs-on: ubuntu-latest
73
+ steps:
74
+ - uses: actions/checkout@v4
75
+
76
+ - name: Set up Python
77
+ uses: actions/setup-python@v5
78
+ with:
79
+ python-version: "3.10"
80
+ cache: "pip"
81
+
82
+ - name: Build and resolve the wheel with the minimum SDK and current PyJWT
83
+ run: |
84
+ python -m pip install --upgrade pip build
85
+ python -m build --wheel
86
+ python -m pip install --dry-run --ignore-installed dist/*.whl 'zai-sdk==0.0.4' 'PyJWT[crypto]>=2.15.1,<3'
87
+
88
+ - name: Install and test the minimum SDK contract
89
+ run: |
90
+ python -m pip install dist/*.whl 'zai-sdk==0.0.4' 'PyJWT[crypto]>=2.15.1,<3' pytest
91
+ python -m pip check
92
+ python -m pytest tests/test_zhipu_sdk_compatibility.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepdoc_lib
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: [RAGFlow](https://ragflow.io/) is an open-source RAG (Retrieval-Augmented Generation) engine based on deep document understanding. It offers a streamlined RAG workflow for businesses of any scale, combining LLM (Large Language Models) to provide truthful question-answering capabilities, backed by well-founded citations from various complex formatted data.
5
5
  Author-email: Zhichang Yu <yuzhichang@gmail.com>
6
6
  Requires-Python: <3.14,>=3.10
@@ -42,7 +42,7 @@ Requires-Dist: tencentcloud-sdk-python>=3.0.1215
42
42
  Requires-Dist: tiktoken>=0.7.0
43
43
  Requires-Dist: xgboost<3.0.0,>=2.1.0
44
44
  Requires-Dist: xpinyin>=0.7.6
45
- Requires-Dist: zhipuai>=2.0.1
45
+ Requires-Dist: zai-sdk>=0.0.4
46
46
  Requires-Dist: google-generativeai<0.9.0,>=0.8.1
47
47
  Requires-Dist: trio>=0.29.0
48
48
  Requires-Dist: setuptools<76.0.0,>=75.2.0
@@ -0,0 +1,86 @@
1
+ # Upstream Sync Record
2
+
3
+ This library is a standalone, pip-installable extraction of the `deepdoc/`
4
+ module from [infiniflow/ragflow](https://github.com/infiniflow/ragflow),
5
+ restructured around explicit configuration injection
6
+ (`PdfModelConfig` / `TokenizerConfig`, `DEEPDOC_*` env vars) and
7
+ ModelScope-based model distribution instead of ragflow's global settings and
8
+ HuggingFace runtime downloads.
9
+
10
+ Upstream changes are **ported by behavior, never by wholesale file copy**,
11
+ so the library's public API (`from deepdoc import PdfParser, ...`) and
12
+ packaging stay stable.
13
+
14
+ ## Sync history
15
+
16
+ ### 2026-07-23 — synced to ragflow `18ea0fb7` (2026-07-22)
17
+
18
+ Ported fixes (upstream PR references):
19
+
20
+ | Area | Fix | Upstream |
21
+ |---|---|---|
22
+ | vision/operators | `StandardizeImag` → `StandardizeImage` rename; the dynamic `getattr(operators, "StandardizeImage")` dispatch silently skipped image standardization before | [#16785](https://github.com/infiniflow/ragflow/pull/16785) |
23
+ | mineru/tcadp | zip extraction hardening: reject symlink / encrypted / absolute-path / `..`-traversal / zip-slip entries; streaming download for TCADP results | [#12527](https://github.com/infiniflow/ragflow/pull/12527) |
24
+ | html_parser | parse bodyless HTML fragments (`soup.body or soup`) | [#16423](https://github.com/infiniflow/ragflow/pull/16423) |
25
+ | html_parser | switch `html5lib` → `html.parser` (aligns with upstream #14486-era behavior the later fixes assume) | [#14486](https://github.com/infiniflow/ragflow/pull/14486) |
26
+ | html_parser | h4 heading mapping `#####` → `####` | [#13833](https://github.com/infiniflow/ragflow/pull/13833) |
27
+ | html_parser | oversized-block splitting preserves original text (CJK-aware atom splitting) | [#16052](https://github.com/infiniflow/ragflow/pull/16052) |
28
+ | excel_parser | keep `0` / `False` cells; skip fully-empty rows | [#16287](https://github.com/infiniflow/ragflow/pull/16287) |
29
+ | excel_parser | no spurious header-only chunk when data rows are an exact multiple of `chunk_rows` | [#15490](https://github.com/infiniflow/ragflow/pull/15490) |
30
+ | excel_parser | binary-search actual row count for sheets with abnormal `max_row` | [#13018](https://github.com/infiniflow/ragflow/pull/13018) |
31
+ | markdown_parser | full sync to upstream state: protected ranges (fenced code / MD tables / HTML tables) never split by delimiters; GFM separators with 1+ dashes; lone-header attachment | #10896 #11018 #11520 #13892 #15630 #15632 #16109 #16319 |
32
+ | ppt_parser | cached, None-safe shape sorting (group shapes with `top=None` no longer crash) | [#13054](https://github.com/infiniflow/ragflow/pull/13054) |
33
+ | parser/utils | `get_text`: `if binary:` → `if binary is not None:` | [#13196](https://github.com/infiniflow/ragflow/pull/13196) |
34
+ | table_structure_recognizer | English caption patterns (`Figure N` / `Table N` / `Fig. N`) | [#15481](https://github.com/infiniflow/ragflow/pull/15481) |
35
+ | vision/ocr | env-configurable ONNX thread counts (`OCR_INTRA_OP_NUM_THREADS` / `OCR_INTER_OP_NUM_THREADS`) and opt-in GPU arena shrinkage (`OCR_GPUMEM_ARENA_SHRINKAGE=1`) | [#12777](https://github.com/infiniflow/ragflow/pull/12777) |
36
+ | layout_recognizer | figure/equation placeholder boxes get per-type layoutno namespaces (`figure-N` / `equation-N`), preventing unrelated figure and equation crops from merging on scientific PDFs | [#15873](https://github.com/infiniflow/ragflow/pull/15873) |
37
+ | layout_recognizer | re-enable CID-pattern garbage filter for text boxes | [#13404](https://github.com/infiniflow/ragflow/pull/13404) |
38
+ | pdf_parser | garbled-text detection → OCR fallback (PUA/CID chars, subset-font encoding garbling, OCR-alphabet coverage check) | [#13404](https://github.com/infiniflow/ragflow/pull/13404) |
39
+ | pdf_parser | geometry-based word-space recovery, CJK-aware (`_insert_word_spaces`) | [#16958](https://github.com/infiniflow/ragflow/pull/16958) |
40
+ | pdf_parser | remove 299-page hardcoded limit (`MAXIMUM_PAGE_NUMBER = 100000`) | [#14382](https://github.com/infiniflow/ragflow/pull/14382) |
41
+ | pdf_parser | chunked `parse_into_bboxes` for large PDFs (`PDF_PARSER_PAGE_BATCH_SIZE`, default 50) with window→global box remapping; lazy `img_np` in `__ocr` | [#14385](https://github.com/infiniflow/ragflow/pull/14385) |
42
+ | pdf_parser | out-of-range page-index guards in `cropout` / table-figure insertion | [#12938](https://github.com/infiniflow/ragflow/pull/12938) [#12848](https://github.com/infiniflow/ragflow/pull/12848) |
43
+ | pdf_parser (VisionParser) | `pdf_page_num = from_page + idx` (page numbering was wrong when `from_page > 0`); fixed broken relative import of `llm_adapter` | [#12938](https://github.com/infiniflow/ragflow/pull/12938) + fork-local fix |
44
+ | parser (new) | EPUB parser (spine-order XHTML extraction, delegates to HtmlParser), exported as `deepdoc.EpubParser` | [#13650](https://github.com/infiniflow/ragflow/pull/13650) |
45
+
46
+ Already present before this sync (no action needed): `ast.literal_eval`
47
+ security fix (#12236), XLS/calamine loading (#10660), chartsheet guards
48
+ (#10819), KMeans multi-column detection (#11415 + #12534 cleanup),
49
+ `is_english` regex fix (#11432), OCR GPU memory limit env (#10407),
50
+ `vision_figure_parser_docx_wrapper` guard (#12104).
51
+
52
+ **Deliberately NOT ported** (decisions):
53
+
54
+ - **Go + HTTP microservice pipeline** (#16323, `deepdoc/server/`, `DEEPDOC_URL`
55
+ DLA client): conflicts with this library's pure-Python, in-process design.
56
+ - **Automatic table orientation detection/rotation** (#12719 feature +
57
+ #12981 / #17016 follow-ups): feature not in our baseline; runs 4× OCR per
58
+ table; adopt separately if ever needed.
59
+ - **`rag.utils.lazy_image` memory refactors** (docx/excel lazy image loading,
60
+ #13329 / #13558): optimization only; deferred until vendoring lazy_image is
61
+ justified.
62
+ - **3-tuple outlines** (`extract_pdf_outlines` returning
63
+ `(title, depth, page)`, #10456 TOC feature): our `outlines` stays
64
+ `(title, depth)` to avoid breaking downstream consumers.
65
+ - **`model_speciess` → `model_species` rename** (#13929): our attribute name
66
+ is part of the public surface; renaming would break existing callers.
67
+ - **paddleocr / opendataloader / somark parsers**: cloud-API clients, not
68
+ currently needed; revisit on demand.
69
+ - **MinerU feature evolution** (API-only backends, VLM descriptions, etc.):
70
+ only the security fix was taken; feature sync deferred until MinerU usage
71
+ is confirmed.
72
+ - **Pure refactors** (moving functions between ragflow modules, lefthook
73
+ reformatting): not applicable to our layout.
74
+
75
+ Regression tests: `tests/test_upstream_sync_regressions.py`.
76
+
77
+ ## How to run the next sync
78
+
79
+ ```bash
80
+ # in a ragflow clone
81
+ git log --reverse --format="%h %ad %s" --date=short 18ea0fb7..origin/main -- deepdoc
82
+ ```
83
+
84
+ Then for each commit: understand the behavior change, find the corresponding
85
+ code in this repo, and re-implement it in this library's style (config
86
+ injection, relative imports, vendored `common/` + `depend/`). Update this file.
@@ -27,6 +27,7 @@ __all__ = [
27
27
  "PlainParser",
28
28
  "DocxParser",
29
29
  "DoclingParser",
30
+ "EpubParser",
30
31
  "ExcelParser",
31
32
  "PptParser",
32
33
  "HtmlParser",
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.2.2'
22
- __version_tuple__ = version_tuple = (0, 2, 2)
21
+ __version__ = version = '0.2.4'
22
+ __version_tuple__ = version_tuple = (0, 2, 4)
23
23
 
24
- __commit_id__ = commit_id = 'g2c122018a'
24
+ __commit_id__ = commit_id = 'g7403fae36'
@@ -11,7 +11,7 @@ import requests
11
11
  from openai import OpenAI
12
12
  from openai.lib.azure import AzureOpenAI
13
13
  from PIL import Image
14
- from zhipuai import ZhipuAI
14
+ from zai import ZhipuAiClient
15
15
 
16
16
  # 可选导入 ollama
17
17
  try:
@@ -162,7 +162,12 @@ class Zhipu4V(Base):
162
162
 
163
163
  def __init__(self, key, model_name="glm-4v", lang="Chinese", **kwargs):
164
164
  super().__init__(key, model_name, lang)
165
- self.client = ZhipuAI(api_key=key)
165
+ # Preserve the legacy override and China default instead of allowing
166
+ # the maintained SDK's ZAI_BASE_URL to redirect this provider adapter.
167
+ self.client = ZhipuAiClient(
168
+ api_key=key,
169
+ base_url=kwargs.get("base_url") or os.getenv("ZHIPUAI_BASE_URL") or "https://open.bigmodel.cn/api/paas/v4/",
170
+ )
166
171
 
167
172
  def describe_with_prompt(self, image, prompt=None):
168
173
  """使用智谱AI视觉模型描述图片"""
@@ -466,4 +471,4 @@ def create_vision_model(config=None):
466
471
  return VisionModelFactory.create_model(config)
467
472
 
468
473
  else:
469
- raise ValueError(f"不支持的配置类型: {type(config)}")
474
+ raise ValueError(f"不支持的配置类型: {type(config)}")
@@ -16,6 +16,7 @@
16
16
 
17
17
  from .docx_parser import RAGFlowDocxParser as DocxParser
18
18
  from .docling_parser import DoclingParser
19
+ from .epub_parser import RAGFlowEpubParser as EpubParser
19
20
  from .excel_parser import RAGFlowExcelParser as ExcelParser
20
21
  from .html_parser import RAGFlowHtmlParser as HtmlParser
21
22
  from .json_parser import RAGFlowJsonParser as JsonParser
@@ -31,6 +32,7 @@ __all__ = [
31
32
  "PlainParser",
32
33
  "DocxParser",
33
34
  "DoclingParser",
35
+ "EpubParser",
34
36
  "ExcelParser",
35
37
  "PptParser",
36
38
  "HtmlParser",
@@ -0,0 +1,139 @@
1
+ #
2
+ # Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ import logging
18
+ import warnings
19
+ import zipfile
20
+ from io import BytesIO
21
+ from xml.etree import ElementTree
22
+
23
+ from ..config import TokenizerConfig
24
+ from .html_parser import RAGFlowHtmlParser
25
+
26
+ # OPF XML namespaces
27
+ _OPF_NS = "http://www.idpf.org/2007/opf"
28
+ _CONTAINER_NS = "urn:oasis:names:tc:opendocument:xmlns:container"
29
+
30
+ # Media types that contain readable XHTML content
31
+ _XHTML_MEDIA_TYPES = {"application/xhtml+xml", "text/html", "text/xml"}
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+
36
+ class RAGFlowEpubParser:
37
+ """Parse EPUB files by extracting XHTML content in spine (reading) order
38
+ and delegating to RAGFlowHtmlParser for chunking."""
39
+
40
+ def __init__(self, tokenizer_cfg: TokenizerConfig | None = None):
41
+ self.html_parser = RAGFlowHtmlParser(tokenizer_cfg=tokenizer_cfg)
42
+
43
+ def __call__(self, fnm, binary=None, chunk_token_num=512):
44
+ if binary is not None:
45
+ if not binary:
46
+ logger.warning(
47
+ "RAGFlowEpubParser received an empty EPUB binary payload for %r",
48
+ fnm,
49
+ )
50
+ raise ValueError("Empty EPUB binary payload")
51
+ zf = zipfile.ZipFile(BytesIO(binary))
52
+ else:
53
+ zf = zipfile.ZipFile(fnm)
54
+
55
+ try:
56
+ content_items = self._get_spine_items(zf)
57
+ all_sections = []
58
+
59
+ for item_path in content_items:
60
+ try:
61
+ html_bytes = zf.read(item_path)
62
+ except KeyError:
63
+ continue
64
+ if not html_bytes:
65
+ logger.debug("Skipping empty EPUB content item: %s", item_path)
66
+ continue
67
+ with warnings.catch_warnings():
68
+ warnings.filterwarnings("ignore", category=UserWarning)
69
+ sections = self.html_parser(item_path, binary=html_bytes, chunk_token_num=chunk_token_num)
70
+ all_sections.extend(sections)
71
+
72
+ return all_sections
73
+ finally:
74
+ zf.close()
75
+
76
+ @staticmethod
77
+ def _get_spine_items(zf):
78
+ """Return content file paths in spine (reading) order."""
79
+ # 1. Find the OPF file path from META-INF/container.xml
80
+ try:
81
+ container_xml = zf.read("META-INF/container.xml")
82
+ except KeyError:
83
+ return RAGFlowEpubParser._fallback_xhtml_order(zf)
84
+
85
+ try:
86
+ container_root = ElementTree.fromstring(container_xml)
87
+ except ElementTree.ParseError:
88
+ logger.warning("Failed to parse META-INF/container.xml; falling back to XHTML order.")
89
+ return RAGFlowEpubParser._fallback_xhtml_order(zf)
90
+
91
+ rootfile_el = container_root.find(f".//{{{_CONTAINER_NS}}}rootfile")
92
+ if rootfile_el is None:
93
+ return RAGFlowEpubParser._fallback_xhtml_order(zf)
94
+
95
+ opf_path = rootfile_el.get("full-path", "")
96
+ if not opf_path:
97
+ return RAGFlowEpubParser._fallback_xhtml_order(zf)
98
+
99
+ # Base directory of the OPF file (content paths are relative to it)
100
+ opf_dir = opf_path.rsplit("/", 1)[0] + "/" if "/" in opf_path else ""
101
+
102
+ # 2. Parse the OPF file
103
+ try:
104
+ opf_xml = zf.read(opf_path)
105
+ except KeyError:
106
+ return RAGFlowEpubParser._fallback_xhtml_order(zf)
107
+
108
+ try:
109
+ opf_root = ElementTree.fromstring(opf_xml)
110
+ except ElementTree.ParseError:
111
+ logger.warning("Failed to parse OPF file '%s'; falling back to XHTML order.", opf_path)
112
+ return RAGFlowEpubParser._fallback_xhtml_order(zf)
113
+
114
+ # 3. Build id->href+mediatype map from <manifest>
115
+ manifest = {}
116
+ for item in opf_root.findall(f".//{{{_OPF_NS}}}item"):
117
+ item_id = item.get("id", "")
118
+ href = item.get("href", "")
119
+ media_type = item.get("media-type", "")
120
+ if item_id and href:
121
+ manifest[item_id] = (href, media_type)
122
+
123
+ # 4. Walk <spine> to get reading order
124
+ spine_items = []
125
+ for itemref in opf_root.findall(f".//{{{_OPF_NS}}}itemref"):
126
+ idref = itemref.get("idref", "")
127
+ if idref not in manifest:
128
+ continue
129
+ href, media_type = manifest[idref]
130
+ if media_type not in _XHTML_MEDIA_TYPES:
131
+ continue
132
+ spine_items.append(opf_dir + href)
133
+
134
+ return spine_items if spine_items else RAGFlowEpubParser._fallback_xhtml_order(zf)
135
+
136
+ @staticmethod
137
+ def _fallback_xhtml_order(zf):
138
+ """Fallback: return all .xhtml/.html files sorted alphabetically."""
139
+ return sorted(n for n in zf.namelist() if n.lower().endswith((".xhtml", ".html", ".htm")) and not n.startswith("META-INF/"))
@@ -74,6 +74,14 @@ class RAGFlowExcelParser:
74
74
 
75
75
  return df.apply(lambda col: col.map(clean_string))
76
76
 
77
+ @staticmethod
78
+ def _fill_worksheet_from_dataframe(ws, df: pd.DataFrame):
79
+ for col_num, column_name in enumerate(df.columns, 1):
80
+ ws.cell(row=1, column=col_num, value=column_name)
81
+ for row_num, row in enumerate(df.values, 2):
82
+ for col_num, value in enumerate(row, 1):
83
+ ws.cell(row=row_num, column=col_num, value=value)
84
+
77
85
  @staticmethod
78
86
  def _dataframe_to_workbook(df):
79
87
  # if contains multiple sheets use _dataframes_to_workbook
@@ -84,30 +92,19 @@ class RAGFlowExcelParser:
84
92
  wb = Workbook()
85
93
  ws = wb.active
86
94
  ws.title = "Data"
87
-
88
- for col_num, column_name in enumerate(df.columns, 1):
89
- ws.cell(row=1, column=col_num, value=column_name)
90
-
91
- for row_num, row in enumerate(df.values, 2):
92
- for col_num, value in enumerate(row, 1):
93
- ws.cell(row=row_num, column=col_num, value=value)
94
-
95
+ RAGFlowExcelParser._fill_worksheet_from_dataframe(ws, df)
95
96
  return wb
96
-
97
+
97
98
  @staticmethod
98
99
  def _dataframes_to_workbook(dfs: dict):
99
100
  wb = Workbook()
100
101
  default_sheet = wb.active
101
102
  wb.remove(default_sheet)
102
-
103
+
103
104
  for sheet_name, df in dfs.items():
104
105
  df = RAGFlowExcelParser._clean_dataframe(df)
105
106
  ws = wb.create_sheet(title=sheet_name)
106
- for col_num, column_name in enumerate(df.columns, 1):
107
- ws.cell(row=1, column=col_num, value=column_name)
108
- for row_num, row in enumerate(df.values, 2):
109
- for col_num, value in enumerate(row, 1):
110
- ws.cell(row=row_num, column=col_num, value=value)
107
+ RAGFlowExcelParser._fill_worksheet_from_dataframe(ws, df)
111
108
  return wb
112
109
 
113
110
  @staticmethod
@@ -156,6 +153,55 @@ class RAGFlowExcelParser:
156
153
  continue
157
154
  return raw_items
158
155
 
156
+ @staticmethod
157
+ def _get_actual_row_count(ws):
158
+ max_row = ws.max_row
159
+ if not max_row:
160
+ return 0
161
+ if max_row <= 10000:
162
+ return max_row
163
+
164
+ max_col = min(ws.max_column or 1, 50)
165
+
166
+ def row_has_data(row_idx):
167
+ for col_idx in range(1, max_col + 1):
168
+ cell = ws.cell(row=row_idx, column=col_idx)
169
+ if cell.value is not None and str(cell.value).strip():
170
+ return True
171
+ return False
172
+
173
+ if not any(row_has_data(i) for i in range(1, min(101, max_row + 1))):
174
+ return 0
175
+
176
+ left, right = 1, max_row
177
+ last_data_row = 1
178
+
179
+ while left <= right:
180
+ mid = (left + right) // 2
181
+ found = False
182
+ for r in range(mid, min(mid + 10, max_row + 1)):
183
+ if row_has_data(r):
184
+ found = True
185
+ last_data_row = max(last_data_row, r)
186
+ break
187
+ if found:
188
+ left = mid + 1
189
+ else:
190
+ right = mid - 1
191
+
192
+ for r in range(last_data_row, min(last_data_row + 500, max_row + 1)):
193
+ if row_has_data(r):
194
+ last_data_row = r
195
+
196
+ return last_data_row
197
+
198
+ @staticmethod
199
+ def _get_rows_limited(ws):
200
+ actual_rows = RAGFlowExcelParser._get_actual_row_count(ws)
201
+ if actual_rows == 0:
202
+ return []
203
+ return list(ws.iter_rows(min_row=1, max_row=actual_rows))
204
+
159
205
  def html(self, fnm, chunk_rows=256):
160
206
  from html import escape
161
207
 
@@ -171,7 +217,7 @@ class RAGFlowExcelParser:
171
217
  for sheetname in wb.sheetnames:
172
218
  ws = wb[sheetname]
173
219
  try:
174
- rows = list(ws.rows)
220
+ rows = RAGFlowExcelParser._get_rows_limited(ws)
175
221
  except Exception as e:
176
222
  logging.warning(f"Skip sheet '{sheetname}' due to rows access error: {e}")
177
223
  continue
@@ -184,7 +230,12 @@ class RAGFlowExcelParser:
184
230
  tb_rows_0 += f"<th>{escape(_fmt(t.value))}</th>"
185
231
  tb_rows_0 += "</tr>"
186
232
 
187
- for chunk_i in range((len(rows) - 1) // chunk_rows + 1):
233
+ # rows[0] is the header; split the remaining data rows into
234
+ # ceil(n_data / chunk_rows) chunks. Using +1 here over-counts by one
235
+ # when the data-row count is an exact multiple of chunk_rows and emits
236
+ # a spurious header-only chunk.
237
+ n_data_rows = len(rows) - 1
238
+ for chunk_i in range((n_data_rows + chunk_rows - 1) // chunk_rows):
188
239
  tb = ""
189
240
  tb += f"<table><caption>{sheetname}</caption>"
190
241
  tb += tb_rows_0
@@ -223,7 +274,7 @@ class RAGFlowExcelParser:
223
274
  for sheetname in wb.sheetnames:
224
275
  ws = wb[sheetname]
225
276
  try:
226
- rows = list(ws.rows)
277
+ rows = RAGFlowExcelParser._get_rows_limited(ws)
227
278
  except Exception as e:
228
279
  logging.warning(f"Skip sheet '{sheetname}' due to rows access error: {e}")
229
280
  continue
@@ -233,11 +284,13 @@ class RAGFlowExcelParser:
233
284
  for r in list(rows[1:]):
234
285
  fields = []
235
286
  for i, c in enumerate(r):
236
- if not c.value:
287
+ if c.value is None or str(c.value).strip() == "":
237
288
  continue
238
289
  t = str(ti[i].value) if i < len(ti) else ""
239
290
  t += (":" if t else "") + str(c.value)
240
291
  fields.append(t)
292
+ if not fields:
293
+ continue
241
294
  line = "; ".join(fields)
242
295
  if sheetname.lower().find("sheet") < 0:
243
296
  line += " ——" + sheetname
@@ -251,12 +304,12 @@ class RAGFlowExcelParser:
251
304
  total = 0
252
305
 
253
306
  for sheetname in wb.sheetnames:
254
- try:
255
- ws = wb[sheetname]
256
- total += len(list(ws.rows))
257
- except Exception as e:
258
- logging.warning(f"Skip sheet '{sheetname}' due to rows access error: {e}")
259
- continue
307
+ try:
308
+ ws = wb[sheetname]
309
+ total += RAGFlowExcelParser._get_actual_row_count(ws)
310
+ except Exception as e:
311
+ logging.warning(f"Skip sheet '{sheetname}' due to rows access error: {e}")
312
+ continue
260
313
  return total
261
314
 
262
315
  if fnm.split(".")[-1].lower() in ["csv", "txt"]: