dot-parser 2.1.0__tar.gz → 2.1.2__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.
- {dot_parser-2.1.0 → dot_parser-2.1.2}/CHANGELOG.md +20 -1
- {dot_parser-2.1.0 → dot_parser-2.1.2}/PKG-INFO +2 -2
- {dot_parser-2.1.0 → dot_parser-2.1.2}/README.md +1 -1
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/README.md +1 -1
- {dot_parser-2.1.0 → dot_parser-2.1.2}/docs/IMAGE_EXTRACTION.md +2 -2
- {dot_parser-2.1.0 → dot_parser-2.1.2}/pyproject.toml +1 -1
- {dot_parser-2.1.0 → dot_parser-2.1.2}/src/dot_parser/backends/mistral.py +1 -1
- {dot_parser-2.1.0 → dot_parser-2.1.2}/src/dot_parser/chunking.py +35 -25
- {dot_parser-2.1.0 → dot_parser-2.1.2}/tests/test_chunking.py +25 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/tests/test_images.py +3 -3
- {dot_parser-2.1.0 → dot_parser-2.1.2}/tests/test_mistral_batch_logging.py +1 -1
- {dot_parser-2.1.0 → dot_parser-2.1.2}/uv.lock +1 -1
- {dot_parser-2.1.0 → dot_parser-2.1.2}/.gitignore +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/.gitlab-ci.yml +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/.pre-commit-config.yaml +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/.python-version +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/AUTHORS.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/CONTRIBUTING.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/DCO +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/LICENSE.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/input/broken_encoding.pdf +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/input/image_only.pdf +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/input/native_equations.pdf +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/input/native_simple.pdf +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/input/native_tables.pdf +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_docling_broken_encoding.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_docling_image_only.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_docling_native_equations.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_docling_native_simple.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_docling_native_tables.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_llama_broken_encoding.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_llama_image_only.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_llama_native_equations.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_llama_native_simple.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_llama_native_tables.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_mistral_batch_broken_encoding.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_mistral_batch_image_only.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_mistral_batch_native_equations.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_mistral_batch_native_simple.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_mistral_batch_native_tables.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_pymu_broken_encoding.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_pymu_image_only.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_pymu_native_equations.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_pymu_native_simple.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_pymu_native_tables.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_pymu_ocr_broken_encoding.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_pymu_ocr_image_only.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_pymu_ocr_native_equations.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_pymu_ocr_native_simple.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_pymu_ocr_native_tables.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/results.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/run.py +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/docs/DESIGN.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/docs/DEVELOPMENT.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/docs/PUBLISHING.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/docs/VERSIONING.md +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/src/dot_parser/__init__.py +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/src/dot_parser/backends/__init__.py +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/src/dot_parser/backends/_base.py +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/src/dot_parser/backends/docling.py +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/src/dot_parser/backends/llama.py +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/src/dot_parser/backends/pymu.py +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/src/dot_parser/docx_images.py +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/src/dot_parser/docx_markdown.py +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/src/dot_parser/image_utils.py +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/src/dot_parser/images.py +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/src/dot_parser/markdown_utils.py +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/src/dot_parser/models.py +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/src/dot_parser/parsers.py +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/src/dot_parser/pricing.py +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/src/dot_parser/tokens.py +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/src/dot_parser/vlms.py +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/tests/test_docx_markdown.py +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/tests/test_parsers.py +0 -0
- {dot_parser-2.1.0 → dot_parser-2.1.2}/tests/test_tokens.py +0 -0
|
@@ -7,6 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.1.2] - 2026-10-27
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- `chunk()` keeps every chunk within `max_tokens`. A paragraph larger than
|
|
15
|
+
the limit on its own (a long table, text without blank lines) is now split
|
|
16
|
+
instead of kept whole, and an undersized chunk is absorbed into its neighbour
|
|
17
|
+
only when the result still fits: a long run of heading-only sections (a table
|
|
18
|
+
of contents, an index) no longer piles up into a single chunk.
|
|
19
|
+
|
|
20
|
+
## [2.1.1] - 2026-09-30
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- `Mistral` now defaults to `mistral-ocr-4-1`; `mistral-ocr-4-0` retires on
|
|
25
|
+
2026-09-30. Pricing is unchanged.
|
|
26
|
+
|
|
10
27
|
## [2.1.0] - 2026-09-30
|
|
11
28
|
|
|
12
29
|
### Added
|
|
@@ -83,6 +100,8 @@ this release.
|
|
|
83
100
|
- `parse_with_images(backend=...)` accepts DOCX- and PPTX-capable backends in
|
|
84
101
|
its type signature, matching what the function already supported at runtime.
|
|
85
102
|
|
|
86
|
-
[Unreleased]: https://gitlab.com/deepika6190303/deepika-open-toolbox/dot-parser/-/compare/v2.1.
|
|
103
|
+
[Unreleased]: https://gitlab.com/deepika6190303/deepika-open-toolbox/dot-parser/-/compare/v2.1.2...main
|
|
104
|
+
[2.1.2]: https://gitlab.com/deepika6190303/deepika-open-toolbox/dot-parser/-/compare/v2.1.1...v2.1.2
|
|
105
|
+
[2.1.1]: https://gitlab.com/deepika6190303/deepika-open-toolbox/dot-parser/-/compare/v2.1.0...v2.1.1
|
|
87
106
|
[2.1.0]: https://gitlab.com/deepika6190303/deepika-open-toolbox/dot-parser/-/compare/v2.0.0...v2.1.0
|
|
88
107
|
[2.0.0]: https://gitlab.com/deepika6190303/deepika-open-toolbox/dot-parser/-/tags/v2.0.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: dot-parser
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.2
|
|
4
4
|
Summary: Document-to-markdown parser and chunker for RAG pipelines
|
|
5
5
|
Project-URL: Homepage, https://gitlab.com/deepika6190303/deepika-open-toolbox/dot-parser
|
|
6
6
|
Project-URL: Repository, https://gitlab.com/deepika6190303/deepika-open-toolbox/dot-parser
|
|
@@ -154,7 +154,7 @@ described either by a VLM or by OCR annotations from the same call. See
|
|
|
154
154
|
|
|
155
155
|
- **`Pymu(ocr_fallback=True)`** -- pymupdf4llm, default. Fast and CPU-only. The built-in OCR fallback kicks in when an OCR engine (tesseract, rapidocr, paddleocr) is available. Set `ocr_fallback=False` to force the no-OCR code path (matches a runtime image without OCR shipped).
|
|
156
156
|
- **`Docling(force_full_page_ocr=True)`** -- IBM Docling, layout-aware ML pipeline + Tesseract OCR. Robust on image-only and broken-encoding PDFs. Install with `pip install dot-parser[docling]`.
|
|
157
|
-
- **`Mistral(api_key=None, model="mistral-ocr-4-
|
|
157
|
+
- **`Mistral(api_key=None, model="mistral-ocr-4-1")`** -- Mistral OCR cloud API. Reads `MISTRAL_API_KEY` from env. Implements `parse_pdfs` via the Batch API. Install with `pip install dot-parser[mistral]`.
|
|
158
158
|
- **`Llama(api_key=None, tier="cost_effective")`** -- LlamaCloud parsing API. Reads `LLAMA_CLOUD_API_KEY` from env. Tiers: `fast`, `cost_effective`, `agentic`, `agentic_plus`. Install with `pip install dot-parser[llama]`.
|
|
159
159
|
|
|
160
160
|
#### Mistral OCR enrichments
|
|
@@ -116,7 +116,7 @@ described either by a VLM or by OCR annotations from the same call. See
|
|
|
116
116
|
|
|
117
117
|
- **`Pymu(ocr_fallback=True)`** -- pymupdf4llm, default. Fast and CPU-only. The built-in OCR fallback kicks in when an OCR engine (tesseract, rapidocr, paddleocr) is available. Set `ocr_fallback=False` to force the no-OCR code path (matches a runtime image without OCR shipped).
|
|
118
118
|
- **`Docling(force_full_page_ocr=True)`** -- IBM Docling, layout-aware ML pipeline + Tesseract OCR. Robust on image-only and broken-encoding PDFs. Install with `pip install dot-parser[docling]`.
|
|
119
|
-
- **`Mistral(api_key=None, model="mistral-ocr-4-
|
|
119
|
+
- **`Mistral(api_key=None, model="mistral-ocr-4-1")`** -- Mistral OCR cloud API. Reads `MISTRAL_API_KEY` from env. Implements `parse_pdfs` via the Batch API. Install with `pip install dot-parser[mistral]`.
|
|
120
120
|
- **`Llama(api_key=None, tier="cost_effective")`** -- LlamaCloud parsing API. Reads `LLAMA_CLOUD_API_KEY` from env. Tiers: `fast`, `cost_effective`, `agentic`, `agentic_plus`. Install with `pip install dot-parser[llama]`.
|
|
121
121
|
|
|
122
122
|
#### Mistral OCR enrichments
|
|
@@ -72,5 +72,5 @@ dropped equations, wrong table cells), so we keep it purely manual.
|
|
|
72
72
|
| `pymu` | $0.00 | pymupdf4llm with OCR fallback **disabled** — reproduces le-lab production where no OCR engine is shipped in the runtime image |
|
|
73
73
|
| `pymu_ocr` | $0.00 | pymupdf4llm with built-in OCR fallback **enabled** — uses tesseract (or rapidocr/paddleocr) automatically when text extraction looks suspect |
|
|
74
74
|
| `docling` | $0.00 | layout-aware ML pipeline + Tesseract OCR with `force_full_page_ocr=True` |
|
|
75
|
-
| `mistral` | $4.00 | `mistral-ocr-4-
|
|
75
|
+
| `mistral` | $4.00 | `mistral-ocr-4-1` cloud API. With `--mistral-batch`: $2.00 per 1k pages (50% off) via the Mistral Batch API. |
|
|
76
76
|
| `llama` | $3.00 | LlamaCloud `cost_effective` tier |
|
|
@@ -30,7 +30,7 @@ parse_with_images("doc.docx", backend=Mistral())
|
|
|
30
30
|
|
|
31
31
|
The `.docx` is uploaded to Mistral and `ocr.process` is called with `document_url`
|
|
32
32
|
plus `bbox_annotation_format`: a single call returns markdown, images and
|
|
33
|
-
annotations. Model: `mistral-ocr-
|
|
33
|
+
annotations. Model: `mistral-ocr-4-1`.
|
|
34
34
|
|
|
35
35
|
- **+** One API call, no per-image round trip.
|
|
36
36
|
- **+** Handles natively the formats markitdown cannot pass through (the document is rasterised, so SmartArt / EMF / SVG render correctly).
|
|
@@ -68,7 +68,7 @@ parse_with_images("doc.pdf") # backend=Mistral() implied
|
|
|
68
68
|
parse_with_images("doc.pdf", backend=Mistral()) # explicit, identical
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
Same `bbox_annotation_format` flow as DOCX path B. Model: `mistral-ocr-
|
|
71
|
+
Same `bbox_annotation_format` flow as DOCX path B. Model: `mistral-ocr-4-1`.
|
|
72
72
|
|
|
73
73
|
- **+** One call returns markdown, images and annotations; robust on scanned or broken-encoding PDFs; exact image positions.
|
|
74
74
|
- **−** Billed per page; fixed annotation schema (same caveat as DOCX path B).
|
|
@@ -217,7 +217,7 @@ class Mistral:
|
|
|
217
217
|
self,
|
|
218
218
|
*,
|
|
219
219
|
api_key: str | None = None,
|
|
220
|
-
model: str = "mistral-ocr-4-
|
|
220
|
+
model: str = "mistral-ocr-4-1",
|
|
221
221
|
language: str | None = None,
|
|
222
222
|
extract_headers_footers: bool = False,
|
|
223
223
|
confidence_scores: Literal["page", "word"] | None = None,
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import re
|
|
5
5
|
import statistics
|
|
6
|
+
from typing import cast
|
|
6
7
|
|
|
7
8
|
import semchunk
|
|
8
9
|
|
|
@@ -63,17 +64,16 @@ def _split_oversized(section: dict, max_tokens: int) -> list[dict]:
|
|
|
63
64
|
body = section["body"]
|
|
64
65
|
path = section["section_path"]
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
if count_tokens(
|
|
67
|
+
prefix = f"{heading}\n\n" if heading else ""
|
|
68
|
+
if count_tokens(prefix + body) <= max_tokens:
|
|
68
69
|
return [section]
|
|
69
70
|
|
|
70
71
|
# Try paragraph splitting first
|
|
71
72
|
paragraphs = re.split(r"\n\n+", body)
|
|
72
73
|
if len(paragraphs) > 1:
|
|
73
|
-
sub_chunks = _group_paragraphs(paragraphs, max_tokens,
|
|
74
|
+
sub_chunks = _group_paragraphs(paragraphs, max_tokens, prefix)
|
|
74
75
|
else:
|
|
75
|
-
|
|
76
|
-
sub_chunks = semchunk.chunk(body, chunk_size=max_tokens, token_counter=count_tokens)
|
|
76
|
+
sub_chunks = _split_text(body, max_tokens, prefix)
|
|
77
77
|
|
|
78
78
|
results = []
|
|
79
79
|
for i, text in enumerate(sub_chunks):
|
|
@@ -96,31 +96,37 @@ def _split_oversized(section: dict, max_tokens: int) -> list[dict]:
|
|
|
96
96
|
return results
|
|
97
97
|
|
|
98
98
|
|
|
99
|
-
def
|
|
100
|
-
"""
|
|
99
|
+
def _split_text(text: str, max_tokens: int, prefix: str) -> list[str]:
|
|
100
|
+
"""Split text semantically (semchunk) into pieces that fit within max_tokens
|
|
101
|
+
even with `prefix` (the heading the first piece carries) before them.
|
|
102
|
+
semchunk returns a bare list unless asked for offsets."""
|
|
103
|
+
pieces = semchunk.chunk(
|
|
104
|
+
text, chunk_size=max_tokens, token_counter=lambda t: count_tokens(prefix + t)
|
|
105
|
+
)
|
|
106
|
+
return cast("list[str]", pieces)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _group_paragraphs(paragraphs: list[str], max_tokens: int, prefix: str) -> list[str]:
|
|
110
|
+
"""Group paragraphs into chunks that fit within max_tokens, the first one
|
|
111
|
+
with `prefix` (its heading) before it. A paragraph too large on its own is
|
|
112
|
+
split semantically first."""
|
|
101
113
|
groups: list[str] = []
|
|
102
114
|
current: list[str] = []
|
|
103
|
-
current_tokens = 0
|
|
104
115
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
116
|
+
def fits(texts: list[str]) -> bool:
|
|
117
|
+
lead = "" if groups else prefix
|
|
118
|
+
return count_tokens(lead + "\n\n".join(texts)) <= max_tokens
|
|
108
119
|
|
|
109
120
|
for para in paragraphs:
|
|
110
121
|
para = para.strip()
|
|
111
122
|
if not para:
|
|
112
123
|
continue
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
current
|
|
119
|
-
current_tokens = para_tokens
|
|
120
|
-
first_group = False
|
|
121
|
-
else:
|
|
122
|
-
current.append(para)
|
|
123
|
-
current_tokens += para_tokens
|
|
124
|
+
pieces = [para] if fits([para]) else _split_text(para, max_tokens, prefix)
|
|
125
|
+
for piece in pieces:
|
|
126
|
+
if current and not fits([*current, piece]):
|
|
127
|
+
groups.append("\n\n".join(current))
|
|
128
|
+
current = []
|
|
129
|
+
current.append(piece)
|
|
124
130
|
|
|
125
131
|
if current:
|
|
126
132
|
groups.append("\n\n".join(current))
|
|
@@ -168,15 +174,19 @@ def _merge_small(sections: list[dict], max_tokens: int) -> list[dict]:
|
|
|
168
174
|
|
|
169
175
|
|
|
170
176
|
def _absorb_undersized(sections: list[dict], max_tokens: int) -> list[dict]:
|
|
171
|
-
"""Absorb chunks smaller than 15% of max_tokens into neighbors
|
|
177
|
+
"""Absorb chunks smaller than 15% of max_tokens into neighbors, when the
|
|
178
|
+
result still fits within max_tokens."""
|
|
172
179
|
threshold = max_tokens * 0.15
|
|
173
180
|
result: list[dict] = []
|
|
174
181
|
|
|
182
|
+
def fits(first: str, second: str) -> bool:
|
|
183
|
+
return count_tokens(first + "\n\n" + second) <= max_tokens
|
|
184
|
+
|
|
175
185
|
for section in sections:
|
|
176
186
|
text = _section_text(section)
|
|
177
187
|
tokens = count_tokens(text)
|
|
178
188
|
|
|
179
|
-
if tokens < threshold and result:
|
|
189
|
+
if tokens < threshold and result and fits(_section_text(result[-1]), text):
|
|
180
190
|
# Append to previous
|
|
181
191
|
prev = result[-1]
|
|
182
192
|
prev_text = _section_text(prev)
|
|
@@ -192,7 +202,7 @@ def _absorb_undersized(sections: list[dict], max_tokens: int) -> list[dict]:
|
|
|
192
202
|
# Check if first chunk is undersized and can be prepended to next
|
|
193
203
|
if len(result) >= 2:
|
|
194
204
|
first_text = _section_text(result[0])
|
|
195
|
-
if count_tokens(first_text) < threshold:
|
|
205
|
+
if count_tokens(first_text) < threshold and fits(first_text, _section_text(result[1])):
|
|
196
206
|
second = result[1]
|
|
197
207
|
second_text = _section_text(second)
|
|
198
208
|
result[1] = {
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
# SPDX-FileCopyrightText: Kannon For Deep Tech
|
|
2
2
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
3
|
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
4
6
|
from dot_parser import Chunk, chunk
|
|
7
|
+
from dot_parser.tokens import count_tokens
|
|
5
8
|
|
|
6
9
|
|
|
7
10
|
class TestChunkBasic:
|
|
@@ -62,6 +65,28 @@ class TestSplitOversized:
|
|
|
62
65
|
assert c.section_path == ["# Big"]
|
|
63
66
|
|
|
64
67
|
|
|
68
|
+
class TestMaxTokensBound:
|
|
69
|
+
@pytest.mark.parametrize("merge", [True, False])
|
|
70
|
+
def test_an_oversized_paragraph_among_others_is_split(self, merge):
|
|
71
|
+
md = "# Big\n\nIntro.\n\n" + "word " * 2000 + "\n\nOutro."
|
|
72
|
+
chunks = chunk(md, max_tokens=64, merge=merge)
|
|
73
|
+
assert all(count_tokens(c.content) <= 64 for c in chunks)
|
|
74
|
+
assert chunks[0].heading == "# Big"
|
|
75
|
+
|
|
76
|
+
@pytest.mark.parametrize("merge", [True, False])
|
|
77
|
+
def test_a_table_without_blank_lines_is_split(self, merge):
|
|
78
|
+
table = "\n".join(f"| row {i} | 0.{i:04d} | 12.5 |" for i in range(500))
|
|
79
|
+
md = f"# Results\n\nThe table below.\n\n{table}\n\nEnd."
|
|
80
|
+
chunks = chunk(md, max_tokens=64, merge=merge)
|
|
81
|
+
assert all(count_tokens(c.content) <= 64 for c in chunks)
|
|
82
|
+
|
|
83
|
+
def test_a_run_of_heading_only_sections_stays_within_the_bound(self):
|
|
84
|
+
md = "# Index\n\n" + "\n\n".join(f"## Entry {i}" for i in range(300))
|
|
85
|
+
chunks = chunk(md, max_tokens=64)
|
|
86
|
+
assert all(count_tokens(c.content) <= 64 for c in chunks)
|
|
87
|
+
assert "## Entry 299" in chunks[-1].content
|
|
88
|
+
|
|
89
|
+
|
|
65
90
|
class TestMerge:
|
|
66
91
|
def test_small_sections_merged(self):
|
|
67
92
|
md = "# A\n\n## B\n\nShort.\n\n## C\n\nAlso short."
|
|
@@ -129,7 +129,7 @@ def _make_mistral_with_fake_client(pages):
|
|
|
129
129
|
constructing the instance manually.
|
|
130
130
|
"""
|
|
131
131
|
instance = Mistral.__new__(Mistral)
|
|
132
|
-
instance._model = "mistral-ocr-4-
|
|
132
|
+
instance._model = "mistral-ocr-4-1"
|
|
133
133
|
instance._language = None
|
|
134
134
|
|
|
135
135
|
uploaded = SimpleNamespace(id="file-xyz")
|
|
@@ -261,7 +261,7 @@ class TestMistralParsePdfWithImages:
|
|
|
261
261
|
]
|
|
262
262
|
|
|
263
263
|
instance = Mistral.__new__(Mistral)
|
|
264
|
-
instance._model = "mistral-ocr-4-
|
|
264
|
+
instance._model = "mistral-ocr-4-1"
|
|
265
265
|
instance._language = None
|
|
266
266
|
uploaded = SimpleNamespace(id="file-xyz")
|
|
267
267
|
signed = SimpleNamespace(url="https://signed.example/doc.pdf")
|
|
@@ -297,7 +297,7 @@ class TestMistralParsePdfWithImages:
|
|
|
297
297
|
def test_retry_failure_propagates(self):
|
|
298
298
|
"""If the no-annotation retry also fails, the exception escapes."""
|
|
299
299
|
instance = Mistral.__new__(Mistral)
|
|
300
|
-
instance._model = "mistral-ocr-4-
|
|
300
|
+
instance._model = "mistral-ocr-4-1"
|
|
301
301
|
instance._language = None
|
|
302
302
|
ocr_calls: list[dict] = []
|
|
303
303
|
|
|
@@ -63,7 +63,7 @@ def _backend(job, downloads: dict[str, bytes]) -> Mistral:
|
|
|
63
63
|
no ``mistralai`` import / real key is needed."""
|
|
64
64
|
m = Mistral.__new__(Mistral)
|
|
65
65
|
m._client = _FakeClient(job, downloads) # ty: ignore[invalid-assignment]
|
|
66
|
-
m._model = "mistral-ocr-4-
|
|
66
|
+
m._model = "mistral-ocr-4-1"
|
|
67
67
|
return m
|
|
68
68
|
|
|
69
69
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_mistral_batch_broken_encoding.md
RENAMED
|
File without changes
|
|
File without changes
|
{dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_mistral_batch_native_equations.md
RENAMED
|
File without changes
|
{dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_mistral_batch_native_simple.md
RENAMED
|
File without changes
|
{dot_parser-2.1.0 → dot_parser-2.1.2}/benchmark/output/md/out_mistral_batch_native_tables.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|