@heripo/pdf-parser 0.1.44 → 0.1.46
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.
- package/README.ko.md +129 -134
- package/README.md +127 -132
- package/dist/index.js +47 -27
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
## Key Features
|
|
38
38
|
|
|
39
39
|
- **Fixed ocrmac OCR**: Docling conversion always uses ocrmac / Apple Vision Framework
|
|
40
|
-
- **Mandatory VLM Correction**: Post-Docling correction always runs
|
|
41
|
-
- **Apple Silicon
|
|
40
|
+
- **Mandatory VLM Correction**: Post-Docling text correction always runs; page gating and structural review can be disabled
|
|
41
|
+
- **Apple Silicon Backend**: Docling conversion requests the `mps` accelerator
|
|
42
42
|
- **Automatic Environment Setup**: Automatic Python virtual environment and docling-serve installation
|
|
43
43
|
- **Image Extraction**: Automatic extraction and saving of images from PDFs
|
|
44
44
|
- **Document Type Validation**: Optional LLM-based validation that a PDF is an archaeological report
|
|
@@ -65,22 +65,23 @@
|
|
|
65
65
|
brew install node
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
#### 2. pnpm >=
|
|
68
|
+
#### 2. pnpm >= 12
|
|
69
69
|
|
|
70
70
|
```bash
|
|
71
|
-
npm install -g pnpm
|
|
71
|
+
npm install -g pnpm@12
|
|
72
72
|
```
|
|
73
73
|
|
|
74
74
|
#### 3. Python 3.9 - 3.12
|
|
75
75
|
|
|
76
|
-
> **Important**: Python 3.
|
|
76
|
+
> **Important**: The current installer accepts Python 3.9–3.12 and rejects 3.13+. This is the repository's version check, not a compatibility statement for every upstream Docling release.
|
|
77
77
|
|
|
78
78
|
```bash
|
|
79
79
|
# Install Python 3.11 (recommended)
|
|
80
80
|
brew install python@3.11
|
|
81
81
|
|
|
82
82
|
# Verify version
|
|
83
|
-
|
|
83
|
+
export PATH="$(brew --prefix python@3.11)/libexec/bin:$PATH"
|
|
84
|
+
python3 --version
|
|
84
85
|
```
|
|
85
86
|
|
|
86
87
|
#### 4. poppler (PDF text extraction)
|
|
@@ -105,9 +106,9 @@ Installed by default on macOS. Verify:
|
|
|
105
106
|
which lsof
|
|
106
107
|
```
|
|
107
108
|
|
|
108
|
-
#### 7. ImageMagick + Ghostscript
|
|
109
|
+
#### 7. ImageMagick + Ghostscript
|
|
109
110
|
|
|
110
|
-
Required
|
|
111
|
+
Required for rendering local PDF pages used by mandatory VLM correction, as well as image-PDF fallback. Install these even when `enableImagePdfFallback` and `forceImagePdf` are disabled.
|
|
111
112
|
|
|
112
113
|
```bash
|
|
113
114
|
brew install imagemagick ghostscript
|
|
@@ -121,25 +122,27 @@ When using `@heripo/pdf-parser` for the first time, it automatically:
|
|
|
121
122
|
2. Installs `docling-serve` and dependencies
|
|
122
123
|
3. Starts docling-serve process on local port
|
|
123
124
|
|
|
124
|
-
|
|
125
|
+
The virtual environment is reused, but local initialization runs dependency setup again. The installer pins docling-serve 1.16.1 and an explicit Docling runtime set; see [python-environment.ts](./src/environment/python-environment.ts). Ensure the `python3` command on PATH is a supported version, not just a separately installed `python3.11`.
|
|
125
126
|
|
|
126
127
|
## Installation
|
|
127
128
|
|
|
128
129
|
```bash
|
|
129
130
|
# Install with npm
|
|
130
|
-
npm install @heripo/pdf-parser @heripo/logger
|
|
131
|
+
npm install @heripo/pdf-parser @heripo/logger @ai-sdk/openai
|
|
131
132
|
|
|
132
133
|
# Install with pnpm
|
|
133
|
-
pnpm add @heripo/pdf-parser @heripo/logger
|
|
134
|
+
pnpm add @heripo/pdf-parser @heripo/logger @ai-sdk/openai
|
|
134
135
|
|
|
135
136
|
# Install with yarn
|
|
136
|
-
yarn add @heripo/pdf-parser @heripo/logger
|
|
137
|
+
yarn add @heripo/pdf-parser @heripo/logger @ai-sdk/openai
|
|
137
138
|
```
|
|
138
139
|
|
|
139
140
|
## Usage
|
|
140
141
|
|
|
141
142
|
### Basic Usage
|
|
142
143
|
|
|
144
|
+
Use an ESM project. `HERIPO_MODEL` is an example-specific environment variable for a model ID supporting image input and structured output; also set `OPENAI_API_KEY`. Other providers can be supplied through their AI SDK adapters.
|
|
145
|
+
|
|
143
146
|
```typescript
|
|
144
147
|
import { openai } from '@ai-sdk/openai';
|
|
145
148
|
import { Logger } from '@heripo/logger';
|
|
@@ -158,33 +161,37 @@ const pdfParser = new PDFParser({
|
|
|
158
161
|
logger,
|
|
159
162
|
});
|
|
160
163
|
|
|
161
|
-
const correctionModel = openai(
|
|
164
|
+
const correctionModel = openai(process.env.HERIPO_MODEL!);
|
|
162
165
|
|
|
163
166
|
// Initialize (environment setup and start docling-serve)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
false, // cleanupAfterCallback
|
|
175
|
-
{
|
|
176
|
-
correction: {
|
|
177
|
-
models: {
|
|
178
|
-
textCorrection: correctionModel,
|
|
179
|
-
pageGate: correctionModel,
|
|
180
|
-
reviewAssistance: correctionModel,
|
|
181
|
-
},
|
|
167
|
+
try {
|
|
168
|
+
await pdfParser.init();
|
|
169
|
+
|
|
170
|
+
// Parse PDF
|
|
171
|
+
const tokenUsageReport = await pdfParser.parse(
|
|
172
|
+
'file:///path/to/report.pdf', // PDF URL (file:// or http://)
|
|
173
|
+
'report-001', // Report ID
|
|
174
|
+
async (outputPath) => {
|
|
175
|
+
// Conversion complete callback
|
|
176
|
+
console.log('PDF conversion complete:', outputPath);
|
|
182
177
|
},
|
|
183
|
-
|
|
184
|
-
|
|
178
|
+
false, // cleanupAfterCallback
|
|
179
|
+
{
|
|
180
|
+
correction: {
|
|
181
|
+
models: {
|
|
182
|
+
textCorrection: correctionModel,
|
|
183
|
+
pageGate: correctionModel,
|
|
184
|
+
reviewAssistance: correctionModel,
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
}, // PDFConvertOptions
|
|
188
|
+
);
|
|
185
189
|
|
|
186
|
-
// Token usage report (null when no LLM usage)
|
|
187
|
-
console.log('Token usage:', tokenUsageReport);
|
|
190
|
+
// Token usage report (null when no LLM usage)
|
|
191
|
+
console.log('Token usage:', tokenUsageReport);
|
|
192
|
+
} finally {
|
|
193
|
+
await pdfParser.dispose();
|
|
194
|
+
}
|
|
188
195
|
```
|
|
189
196
|
|
|
190
197
|
### Advanced Options
|
|
@@ -193,7 +200,7 @@ console.log('Token usage:', tokenUsageReport);
|
|
|
193
200
|
// Option A: Use local server with port
|
|
194
201
|
const pdfParser = new PDFParser({
|
|
195
202
|
logger,
|
|
196
|
-
port: 5001, //
|
|
203
|
+
port: 5001, // Set the local port explicitly
|
|
197
204
|
timeout: 10000000, // Timeout (milliseconds)
|
|
198
205
|
venvPath: '/custom/path/.venv', // Custom venv path (default: CWD/.venv)
|
|
199
206
|
killExistingProcess: true, // Kill existing process on port (default: false)
|
|
@@ -216,13 +223,13 @@ const tokenUsageReport = await pdfParser.parse(
|
|
|
216
223
|
// Mandatory post-Docling correction
|
|
217
224
|
correction: {
|
|
218
225
|
models: {
|
|
219
|
-
textCorrection: openai(
|
|
220
|
-
pageGate: openai(
|
|
221
|
-
reviewAssistance: openai(
|
|
222
|
-
tableCorrection: openai(
|
|
226
|
+
textCorrection: openai(process.env.HERIPO_MODEL!),
|
|
227
|
+
pageGate: openai(process.env.HERIPO_MODEL!),
|
|
228
|
+
reviewAssistance: openai(process.env.HERIPO_MODEL!),
|
|
229
|
+
tableCorrection: openai(process.env.HERIPO_MODEL!),
|
|
223
230
|
reviewAssistanceTasks: {
|
|
224
|
-
text_ocr_hanja: openai(
|
|
225
|
-
tables: openai(
|
|
231
|
+
text_ocr_hanja: openai(process.env.HERIPO_MODEL!),
|
|
232
|
+
tables: openai(process.env.HERIPO_MODEL!),
|
|
226
233
|
},
|
|
227
234
|
},
|
|
228
235
|
concurrency: {
|
|
@@ -246,7 +253,7 @@ const tokenUsageReport = await pdfParser.parse(
|
|
|
246
253
|
onReviewAssistanceProgress: (event) => console.log(event),
|
|
247
254
|
|
|
248
255
|
// Document validation
|
|
249
|
-
documentValidationModel: openai(
|
|
256
|
+
documentValidationModel: openai(process.env.HERIPO_MODEL!),
|
|
250
257
|
|
|
251
258
|
// Chunked conversion for large PDFs
|
|
252
259
|
chunkedConversion: true,
|
|
@@ -265,6 +272,12 @@ const tokenUsageReport = await pdfParser.parse(
|
|
|
265
272
|
);
|
|
266
273
|
```
|
|
267
274
|
|
|
275
|
+
### Artifacts
|
|
276
|
+
|
|
277
|
+
The callback receives the absolute `output/<reportId>/` path. `result.json` is the corrected document and `result_ocr_origin.json` is the initial OCR snapshot. `images/` and `pages/` hold extracted and page images. Structural review also produces `result_review_origin.json`, `review_assistance_page_gate.json`, `review_assistance_checkpoint.json` and `review_assistance.json`. Check page status, issues and call traces for failed review work.
|
|
278
|
+
|
|
279
|
+
With `cleanupAfterCallback: true`, the artifact directory is removed after the callback. Copy required files elsewhere inside it or use `false` to preserve them. The parser does not create `result-processed.json` or a handoff manifest; the demo worker saves those separately.
|
|
280
|
+
|
|
268
281
|
### Resource Cleanup
|
|
269
282
|
|
|
270
283
|
Clean up resources after work is complete:
|
|
@@ -278,15 +291,15 @@ await pdfParser.dispose();
|
|
|
278
291
|
|
|
279
292
|
### Why ocrmac Is Fixed
|
|
280
293
|
|
|
281
|
-
|
|
294
|
+
ocrmac is the fixed OCR backend, using Apple Vision on macOS.
|
|
282
295
|
|
|
283
|
-
`@heripo/pdf-parser` no longer samples OCR strategies or switches to a VLM OCR path. Docling conversion always uses ocrmac.
|
|
296
|
+
`@heripo/pdf-parser` no longer samples OCR strategies or switches to a VLM OCR path. Docling conversion always uses ocrmac. VLM correction runs after Docling conversion. Optional language detection and document validation can also call a model before conversion.
|
|
284
297
|
|
|
285
298
|
### Required Correction Contract
|
|
286
299
|
|
|
287
300
|
Every `parse()` call must provide `correction.models.textCorrection`, `correction.models.pageGate`, and `correction.models.reviewAssistance`. If any required correction model is missing, parsing fails before conversion callback wrapping.
|
|
288
301
|
|
|
289
|
-
|
|
302
|
+
With the default review controls enabled, the correction stage runs as follows:
|
|
290
303
|
|
|
291
304
|
1. Save `result_ocr_origin.json` before mutations.
|
|
292
305
|
2. Run page-level text and table-cell OCR correction with `textCorrection`.
|
|
@@ -299,21 +312,39 @@ Text correction applies to every page with text or table content. The page gate
|
|
|
299
312
|
|
|
300
313
|
### Local Model Execution
|
|
301
314
|
|
|
302
|
-
The correction pipeline is optimized for local VLMs: small contexts, many calls, deterministic validation, retry loops, bounded concurrency, and resumable checkpoints. For local models, start with `concurrency.pages: 1`, `concurrency.
|
|
315
|
+
The correction pipeline is optimized for local VLMs: small contexts, many calls, deterministic validation, retry loops, bounded concurrency, and resumable checkpoints. For local models, start with `concurrency.pages: 1`, `concurrency.reviewTasks: 1`, `modelConcurrency: 1`, `temperature: 0`, and a generous `workItemTimeoutMs`. Increase concurrency only after the model is stable.
|
|
303
316
|
|
|
304
|
-
###
|
|
317
|
+
### Validation
|
|
305
318
|
|
|
306
|
-
|
|
319
|
+
Run the defined package check with `pnpm --filter @heripo/pdf-parser test:coverage`. Tests mock external model calls; correction quality on real documents requires separate evaluation.
|
|
307
320
|
|
|
308
|
-
|
|
309
|
-
pnpm --filter @heripo/pdf-parser smoke:correction
|
|
310
|
-
```
|
|
321
|
+
### Correction Controls and Defaults
|
|
311
322
|
|
|
312
|
-
|
|
323
|
+
All three required models must still be supplied when structural review is disabled. `reviewAssistanceEnabled: false` runs only text/table-cell OCR correction, skipping the page gate and structural review. `tableCorrectionEnabled: false` skips structural table work items, but does not disable initial table-cell OCR correction.
|
|
324
|
+
|
|
325
|
+
`forceAutoApply` defaults to `false`. When true, validated commands are applied without routing to manual review based on confidence thresholds or structural block reasons. Command validation still runs.
|
|
326
|
+
|
|
327
|
+
| Option | Default |
|
|
328
|
+
| ---------------------------------------------------- | ---------------------- |
|
|
329
|
+
| `concurrency.pages` | `1` |
|
|
330
|
+
| `concurrency.reviewTasks` | `6` |
|
|
331
|
+
| `modelConcurrency` | `1` |
|
|
332
|
+
| `maxRetries.*` | `3` |
|
|
333
|
+
| `workItemTimeoutMs` | `1800000` (30 minutes) |
|
|
334
|
+
| `outputLanguage` | `en-US` |
|
|
335
|
+
| `autoApplyThreshold` / `proposalThreshold` | `0.85` / `0.5` |
|
|
336
|
+
| `reviewAssistanceEnabled` / `tableCorrectionEnabled` | `true` / `true` |
|
|
337
|
+
| `forceAutoApply` / `temperature` | `false` / `0` |
|
|
338
|
+
|
|
339
|
+
Defaults are also exported as `PDF_CORRECTION_DEFAULTS`. `concurrency.tables` and `pageGate.structuralNoiseThreshold` exist in the type but are not forwarded as runner controls by the current top-level pipeline. Use `reviewTasks` and `modelConcurrency` to bound table work.
|
|
340
|
+
|
|
341
|
+
### Language Detection
|
|
342
|
+
|
|
343
|
+
Explicit `ocr_lang` values take precedence. Otherwise local PDF text is inspected, with optional vision-based detection through `languageDetectionModel` and `languageDetectionFallbackModel` when needed. Remote inputs and undetected inputs use `ko-KR`, `en-US` as defaults. This does not select an OCR engine: OCR remains ocrmac.
|
|
313
344
|
|
|
314
345
|
## Review Assistance
|
|
315
346
|
|
|
316
|
-
Review Assistance
|
|
347
|
+
Review Assistance runs after text correction when `correction.reviewAssistanceEnabled` is true (library default), but it does not process every page with the same intensity. The page gate marks covers, chapter covers, barcode/ISBN pages, and decorative pages as low-value for structural review. Skipped pages remain observable in `review_assistance.json` with an info issue and skip reason.
|
|
317
348
|
|
|
318
349
|
Eligible pages are split into small work items for text OCR/Hanja review, text integrity, text role/footnote review, tables, pictures/captions, layout/bbox/order, and table-specific correction. Each call records timing, model id, attempts, target refs, and deterministic validation status in `review_assistance.json`.
|
|
319
350
|
|
|
@@ -330,10 +361,10 @@ const tokenUsageReport = await pdfParser.parse(
|
|
|
330
361
|
{
|
|
331
362
|
correction: {
|
|
332
363
|
models: {
|
|
333
|
-
textCorrection: openai(
|
|
334
|
-
pageGate: openai(
|
|
335
|
-
reviewAssistance: openai(
|
|
336
|
-
tableCorrection: openai(
|
|
364
|
+
textCorrection: openai(process.env.HERIPO_MODEL!),
|
|
365
|
+
pageGate: openai(process.env.HERIPO_MODEL!),
|
|
366
|
+
reviewAssistance: openai(process.env.HERIPO_MODEL!),
|
|
367
|
+
tableCorrection: openai(process.env.HERIPO_MODEL!),
|
|
337
368
|
},
|
|
338
369
|
autoApplyThreshold: 0.85,
|
|
339
370
|
proposalThreshold: 0.5,
|
|
@@ -353,7 +384,7 @@ const tokenUsageReport = await pdfParser.parse(
|
|
|
353
384
|
);
|
|
354
385
|
```
|
|
355
386
|
|
|
356
|
-
|
|
387
|
+
Use a local `file://` PDF for the complete pipeline. HTTP inputs are accepted by Docling, but local page rendering, language detection and document validation are skipped. Missing `pages/page_<index>.png` files cause text correction to be skipped; a successful callback does not prove every page was corrected. Failed correction calls retain the original OCR text and emit warnings. It updates `result.json` with auto-applied fixes, keeps snapshots in `result_review_origin.json` and `result_ocr_origin.json`, writes `review_assistance_page_gate.json`, and writes `review_assistance.json` containing per-page decisions, issues, proposals, call traces, validation status, and summary counts.
|
|
357
388
|
|
|
358
389
|
## Document Type Validation
|
|
359
390
|
|
|
@@ -371,12 +402,12 @@ try {
|
|
|
371
402
|
{
|
|
372
403
|
correction: {
|
|
373
404
|
models: {
|
|
374
|
-
textCorrection: openai(
|
|
375
|
-
pageGate: openai(
|
|
376
|
-
reviewAssistance: openai(
|
|
405
|
+
textCorrection: openai(process.env.HERIPO_MODEL!),
|
|
406
|
+
pageGate: openai(process.env.HERIPO_MODEL!),
|
|
407
|
+
reviewAssistance: openai(process.env.HERIPO_MODEL!),
|
|
377
408
|
},
|
|
378
409
|
},
|
|
379
|
-
documentValidationModel: openai(
|
|
410
|
+
documentValidationModel: openai(process.env.HERIPO_MODEL!),
|
|
380
411
|
},
|
|
381
412
|
);
|
|
382
413
|
} catch (error) {
|
|
@@ -399,18 +430,20 @@ const tokenUsageReport = await pdfParser.parse(
|
|
|
399
430
|
{
|
|
400
431
|
correction: {
|
|
401
432
|
models: {
|
|
402
|
-
textCorrection: openai(
|
|
403
|
-
pageGate: openai(
|
|
404
|
-
reviewAssistance: openai(
|
|
433
|
+
textCorrection: openai(process.env.HERIPO_MODEL!),
|
|
434
|
+
pageGate: openai(process.env.HERIPO_MODEL!),
|
|
435
|
+
reviewAssistance: openai(process.env.HERIPO_MODEL!),
|
|
405
436
|
},
|
|
406
437
|
},
|
|
407
438
|
chunkedConversion: true,
|
|
408
|
-
chunkSize: 50, // Pages per chunk (default:
|
|
409
|
-
chunkMaxRetries: 3, //
|
|
439
|
+
chunkSize: 50, // Pages per chunk (default: 10)
|
|
440
|
+
chunkMaxRetries: 3, // Retries per failed chunk (default: 2)
|
|
410
441
|
},
|
|
411
442
|
);
|
|
412
443
|
```
|
|
413
444
|
|
|
445
|
+
Correction runs after chunk results are merged. The current chunked failure path attempts image-PDF conversion independently of `enableImagePdfFallback`, so ImageMagick/Ghostscript are needed for that recovery path too. For local inputs, the chunked branch takes precedence when `chunkedConversion` and `forceImagePdf` are both set.
|
|
446
|
+
|
|
414
447
|
## Image PDF Fallback
|
|
415
448
|
|
|
416
449
|
When conversion fails, the parser can automatically fall back to converting the PDF to an image-based PDF first, then retrying conversion. This is useful for PDFs with complex or corrupt structures. Requires ImageMagick and Ghostscript.
|
|
@@ -440,9 +473,9 @@ const tokenUsageReport = await pdfParser.parse(
|
|
|
440
473
|
{
|
|
441
474
|
correction: {
|
|
442
475
|
models: {
|
|
443
|
-
textCorrection: openai(
|
|
444
|
-
pageGate: openai(
|
|
445
|
-
reviewAssistance: openai(
|
|
476
|
+
textCorrection: openai(process.env.HERIPO_MODEL!),
|
|
477
|
+
pageGate: openai(process.env.HERIPO_MODEL!),
|
|
478
|
+
reviewAssistance: openai(process.env.HERIPO_MODEL!),
|
|
446
479
|
},
|
|
447
480
|
},
|
|
448
481
|
forceImagePdf: true, // Always convert to image PDF first
|
|
@@ -471,16 +504,16 @@ try {
|
|
|
471
504
|
{
|
|
472
505
|
correction: {
|
|
473
506
|
models: {
|
|
474
|
-
textCorrection: openai(
|
|
475
|
-
pageGate: openai(
|
|
476
|
-
reviewAssistance: openai(
|
|
507
|
+
textCorrection: openai(process.env.HERIPO_MODEL!),
|
|
508
|
+
pageGate: openai(process.env.HERIPO_MODEL!),
|
|
509
|
+
reviewAssistance: openai(process.env.HERIPO_MODEL!),
|
|
477
510
|
},
|
|
478
511
|
},
|
|
479
512
|
},
|
|
480
513
|
controller.signal, // AbortSignal
|
|
481
514
|
);
|
|
482
515
|
} catch (error) {
|
|
483
|
-
if (error.name === 'AbortError') {
|
|
516
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
484
517
|
console.log('Parsing was cancelled');
|
|
485
518
|
}
|
|
486
519
|
}
|
|
@@ -494,35 +527,9 @@ When using a local docling-serve instance (port mode), the parser automatically
|
|
|
494
527
|
|
|
495
528
|
## Why macOS Only?
|
|
496
529
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
### OCR Selection Background
|
|
500
|
-
|
|
501
|
-
Archaeological excavation report PDFs have the following characteristics:
|
|
502
|
-
|
|
503
|
-
- Scanned documents spanning hundreds of pages
|
|
504
|
-
- Layouts containing complex tables, diagrams, photographs
|
|
505
|
-
- Precise text extraction is essential
|
|
506
|
-
|
|
507
|
-
### OCR Option Comparison
|
|
508
|
-
|
|
509
|
-
| Method | Performance | Cost | Description |
|
|
510
|
-
| ----------------------- | ----------- | ---- | ---------------------------------------------------------- |
|
|
511
|
-
| **Docling (Local)** | ★★★★★ | Free | Overwhelming performance on Apple Silicon, GPU utilization |
|
|
512
|
-
| Cloud OCR (Google, AWS) | ★★★★ | $$$ | Tens of dollars per hundreds of pages |
|
|
513
|
-
| Tesseract (Local) | ★★ | Free | Low Korean recognition rate, lacking layout analysis |
|
|
514
|
-
|
|
515
|
-
### Key Advantages
|
|
530
|
+
The package declares `os: ["darwin"]` and checks macOS and local tools in `init()`. Connecting to an external Docling server through `baseUrl` does not remove this client-side restriction. ocrmac uses Apple Vision on macOS.
|
|
516
531
|
|
|
517
|
-
|
|
518
|
-
- **Performance**: Fast processing with GPU acceleration on Apple Silicon M1/M2/M3/M4/M5
|
|
519
|
-
- **Quality**: Accurate recognition even for complex documents
|
|
520
|
-
- **Privacy**: Documents are not sent to external servers
|
|
521
|
-
|
|
522
|
-
### Trade-off
|
|
523
|
-
|
|
524
|
-
- Optimal performance only in macOS + Apple Silicon environment
|
|
525
|
-
- No current plans for Linux/Windows support (see "Linux Support Status" below)
|
|
532
|
+
Docling OCR can run locally, but VLM correction calls the configured models. Cloud models can receive document text and page images and incur API charges. To keep the full pipeline local, configure local models for language detection, document validation, correction, fallbacks and downstream document processing.
|
|
526
533
|
|
|
527
534
|
## System Dependencies Details
|
|
528
535
|
|
|
@@ -534,17 +541,18 @@ Archaeological excavation report PDFs have the following characteristics:
|
|
|
534
541
|
| poppler | Any | `brew install poppler` | PDF page counting (pdfinfo) and text layer extraction (pdftotext) |
|
|
535
542
|
| jq | Any | `brew install jq` | JSON processing (conversion result parsing) |
|
|
536
543
|
| lsof | Any | Included with macOS | docling-serve port management |
|
|
537
|
-
| ImageMagick | Any
|
|
538
|
-
| Ghostscript | Any
|
|
544
|
+
| ImageMagick | Any | `brew install imagemagick` | Image PDF fallback and page rendering |
|
|
545
|
+
| Ghostscript | Any | `brew install ghostscript` | Image PDF fallback (PDF to image conversion) |
|
|
539
546
|
|
|
540
|
-
> **Python 3.13+ is
|
|
547
|
+
> **Python 3.13+ is rejected by the current installer.** See the version check in [python-version.ts](./src/utils/python-version.ts).
|
|
541
548
|
|
|
542
549
|
### Checking Python Version
|
|
543
550
|
|
|
544
551
|
```bash
|
|
545
552
|
# Check installed Python version
|
|
546
553
|
python3 --version
|
|
547
|
-
|
|
554
|
+
export PATH="$(brew --prefix python@3.11)/libexec/bin:$PATH"
|
|
555
|
+
python3 --version
|
|
548
556
|
|
|
549
557
|
# When multiple versions are installed
|
|
550
558
|
ls -la /usr/local/bin/python*
|
|
@@ -569,12 +577,12 @@ which jq
|
|
|
569
577
|
```typescript
|
|
570
578
|
type Options = {
|
|
571
579
|
logger: LoggerMethods; // Logger instance (REQUIRED)
|
|
572
|
-
timeout?: number; // Timeout in milliseconds (default:
|
|
580
|
+
timeout?: number; // Timeout in milliseconds (default: 100000)
|
|
573
581
|
venvPath?: string; // Python venv path (default: CWD/.venv)
|
|
574
582
|
killExistingProcess?: boolean; // Kill existing process on port (default: false)
|
|
575
583
|
enableImagePdfFallback?: boolean; // Enable image PDF fallback (default: false, requires ImageMagick + Ghostscript)
|
|
576
584
|
} & (
|
|
577
|
-
| { port?: number } // Local
|
|
585
|
+
| { port?: number } // Local mode; explicitly provide port (no runtime default)
|
|
578
586
|
| { baseUrl: string } // External server mode
|
|
579
587
|
);
|
|
580
588
|
```
|
|
@@ -614,6 +622,10 @@ Disposes the parser instance, kills the local docling-serve process (if started)
|
|
|
614
622
|
await pdfParser.dispose();
|
|
615
623
|
```
|
|
616
624
|
|
|
625
|
+
##### `isReady(): Promise<boolean>` / `ensureReady(): Promise<void>`
|
|
626
|
+
|
|
627
|
+
`isReady()` checks health without recovery. `ensureReady()` requires initialization and attempts recovery for a local server. External server failures are propagated. `dispose()` also terminates a process on the configured local port, including a reused server; use `baseUrl` for an externally managed lifecycle.
|
|
628
|
+
|
|
617
629
|
### PDFConvertOptions
|
|
618
630
|
|
|
619
631
|
```typescript
|
|
@@ -630,6 +642,8 @@ type PDFConvertOptions = {
|
|
|
630
642
|
|
|
631
643
|
// Document processing
|
|
632
644
|
document_timeout?: number; // Document processing timeout in seconds
|
|
645
|
+
languageDetectionModel?: LanguageModel;
|
|
646
|
+
languageDetectionFallbackModel?: LanguageModel;
|
|
633
647
|
documentValidationModel?: LanguageModel; // LLM for document type validation
|
|
634
648
|
|
|
635
649
|
// Correction progress
|
|
@@ -686,7 +700,7 @@ interface PDFCorrectionOptions {
|
|
|
686
700
|
concurrency?: {
|
|
687
701
|
pages?: number; // Page-level text correction and page gate concurrency
|
|
688
702
|
reviewTasks?: number; // Structural Review Assistance work-item concurrency
|
|
689
|
-
tables?: number; //
|
|
703
|
+
tables?: number; // Accepted but not forwarded by the top-level pipeline
|
|
690
704
|
};
|
|
691
705
|
maxRetries?: {
|
|
692
706
|
textCorrection?: number;
|
|
@@ -800,31 +814,12 @@ brew install imagemagick ghostscript
|
|
|
800
814
|
|
|
801
815
|
## Linux Support Status
|
|
802
816
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
| Platform | Status | Notes |
|
|
806
|
-
| --------------------- | --------- | ----------------------------------------------- |
|
|
807
|
-
| macOS + Apple Silicon | Supported | Optimal performance, GPU acceleration |
|
|
808
|
-
| macOS + Intel | Supported | No GPU acceleration |
|
|
809
|
-
| Linux | TBD | No current plans due to performance/cost issues |
|
|
810
|
-
| Windows | TBD | WSL2 Linux approach possible |
|
|
811
|
-
|
|
812
|
-
### Reason for No Linux Support
|
|
813
|
-
|
|
814
|
-
Docling SDK's local OCR achieves both performance and cost efficiency by utilizing Apple Metal GPU acceleration on macOS. We have not yet found an OCR solution on Linux that provides equivalent performance and cost efficiency.
|
|
815
|
-
|
|
816
|
-
### Ideas Welcome
|
|
817
|
-
|
|
818
|
-
If you have ideas for supporting Linux while maintaining both performance and cost efficiency, please suggest them via [GitHub Discussions](https://github.com/heripo-lab/heripo-engine/discussions) or Issues. The following information is particularly helpful:
|
|
819
|
-
|
|
820
|
-
- Experience with Korean document OCR on Linux
|
|
821
|
-
- OCR solutions capable of handling complex layouts (tables, diagrams)
|
|
822
|
-
- Cost estimates for processing hundreds of pages
|
|
817
|
+
The current `@heripo/pdf-parser` package supports macOS and does not provide a Linux/Windows runtime path. Platform proposals are welcome in [GitHub Discussions](https://github.com/heripo-lab/heripo-engine/discussions).
|
|
823
818
|
|
|
824
819
|
## Related Packages
|
|
825
820
|
|
|
826
|
-
- [@heripo/document-processor](../document-processor) - Document structure analysis and LLM processing
|
|
827
|
-
- [@heripo/model](../model) - Data models and type definitions
|
|
821
|
+
- [@heripo/document-processor](../document-processor/README.md) - Document structure analysis and LLM processing
|
|
822
|
+
- [@heripo/model](../model/README.md) - Data models and type definitions
|
|
828
823
|
|
|
829
824
|
## Sponsor
|
|
830
825
|
|
package/dist/index.js
CHANGED
|
@@ -142,6 +142,15 @@ function detectProvider(model) {
|
|
|
142
142
|
if (providerId.includes("lmstudio")) return "lmstudio";
|
|
143
143
|
return "unknown";
|
|
144
144
|
}
|
|
145
|
+
function cacheWrite1hTokens(steps) {
|
|
146
|
+
let total;
|
|
147
|
+
for (const step of steps ?? []) {
|
|
148
|
+
const raw = step.usage.raw;
|
|
149
|
+
const value = raw?.cache_creation?.ephemeral_1h_input_tokens;
|
|
150
|
+
if (value !== void 0) total = (total ?? 0) + value;
|
|
151
|
+
}
|
|
152
|
+
return total;
|
|
153
|
+
}
|
|
145
154
|
var LLMCaller = class {
|
|
146
155
|
/**
|
|
147
156
|
* Extract model name from LanguageModel object
|
|
@@ -160,6 +169,9 @@ var LLMCaller = class {
|
|
|
160
169
|
* Build usage information from response
|
|
161
170
|
*/
|
|
162
171
|
static buildUsage(config, modelName, response, usedFallback) {
|
|
172
|
+
const usage = response.usage;
|
|
173
|
+
const write1h = cacheWrite1hTokens(response.steps);
|
|
174
|
+
const allWrites = usage?.inputTokenDetails?.cacheWriteTokens;
|
|
163
175
|
return {
|
|
164
176
|
component: config.component,
|
|
165
177
|
phase: config.phase,
|
|
@@ -168,6 +180,13 @@ var LLMCaller = class {
|
|
|
168
180
|
inputTokens: response.usage?.inputTokens ?? 0,
|
|
169
181
|
outputTokens: response.usage?.outputTokens ?? 0,
|
|
170
182
|
totalTokens: response.usage?.totalTokens ?? 0,
|
|
183
|
+
...usage?.inputTokenDetails?.cacheReadTokens != null && {
|
|
184
|
+
cachedInputTokens: usage.inputTokenDetails.cacheReadTokens
|
|
185
|
+
},
|
|
186
|
+
...allWrites != null && {
|
|
187
|
+
cacheWriteTokens: allWrites - (write1h ?? 0)
|
|
188
|
+
},
|
|
189
|
+
...write1h !== void 0 && { cacheWrite1hTokens: write1h },
|
|
171
190
|
metadata: config.metadata
|
|
172
191
|
};
|
|
173
192
|
}
|
|
@@ -210,7 +229,8 @@ var LLMCaller = class {
|
|
|
210
229
|
if (toolCall) {
|
|
211
230
|
return {
|
|
212
231
|
output: toolCall.input,
|
|
213
|
-
usage: lastResult.usage
|
|
232
|
+
usage: lastResult.usage,
|
|
233
|
+
steps: lastResult.steps
|
|
214
234
|
};
|
|
215
235
|
}
|
|
216
236
|
}
|
|
@@ -347,16 +367,32 @@ function emptyUsage() {
|
|
|
347
367
|
return { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
|
|
348
368
|
}
|
|
349
369
|
function addInto(target, src) {
|
|
370
|
+
const hadUsage = target.inputTokens !== 0 || target.outputTokens !== 0 || target.totalTokens !== 0;
|
|
350
371
|
target.inputTokens += src.inputTokens;
|
|
351
372
|
target.outputTokens += src.outputTokens;
|
|
352
373
|
target.totalTokens += src.totalTokens;
|
|
374
|
+
for (const key of CACHE_KEYS) {
|
|
375
|
+
const next = src[key];
|
|
376
|
+
const current = target[key];
|
|
377
|
+
if (next === void 0) {
|
|
378
|
+
if (current !== void 0) target[key] = null;
|
|
379
|
+
} else if (current === void 0) {
|
|
380
|
+
target[key] = hadUsage ? null : next;
|
|
381
|
+
} else {
|
|
382
|
+
target[key] = current === null || next === null ? null : current + next;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
353
385
|
}
|
|
386
|
+
var CACHE_KEYS = [
|
|
387
|
+
"cachedInputTokens",
|
|
388
|
+
"cacheWriteTokens",
|
|
389
|
+
"cacheWrite1hTokens"
|
|
390
|
+
];
|
|
354
391
|
function sumUsage(a, b) {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
};
|
|
392
|
+
const total = emptyUsage();
|
|
393
|
+
if (a) addInto(total, a);
|
|
394
|
+
if (b) addInto(total, b);
|
|
395
|
+
return total;
|
|
360
396
|
}
|
|
361
397
|
function accumulateModel(bucket, modelName, usage) {
|
|
362
398
|
let entry = bucket.get(modelName);
|
|
@@ -471,21 +507,13 @@ var LLMTokenUsageAggregator = class {
|
|
|
471
507
|
components.push({
|
|
472
508
|
component: component.component,
|
|
473
509
|
phases,
|
|
474
|
-
total: {
|
|
475
|
-
inputTokens: component.total.inputTokens,
|
|
476
|
-
outputTokens: component.total.outputTokens,
|
|
477
|
-
totalTokens: component.total.totalTokens
|
|
478
|
-
}
|
|
510
|
+
total: { ...component.total }
|
|
479
511
|
});
|
|
480
512
|
}
|
|
481
513
|
const totalUsage = this.getTotalUsage();
|
|
482
514
|
return {
|
|
483
515
|
components,
|
|
484
|
-
total:
|
|
485
|
-
inputTokens: totalUsage.inputTokens,
|
|
486
|
-
outputTokens: totalUsage.outputTokens,
|
|
487
|
-
totalTokens: totalUsage.totalTokens
|
|
488
|
-
}
|
|
516
|
+
total: totalUsage
|
|
489
517
|
};
|
|
490
518
|
}
|
|
491
519
|
/**
|
|
@@ -494,19 +522,11 @@ var LLMTokenUsageAggregator = class {
|
|
|
494
522
|
* @returns Aggregated token usage totals
|
|
495
523
|
*/
|
|
496
524
|
getTotalUsage() {
|
|
497
|
-
|
|
498
|
-
let totalOutput = 0;
|
|
499
|
-
let totalTokens = 0;
|
|
525
|
+
const total = emptyUsage();
|
|
500
526
|
for (const component of Object.values(this.usage)) {
|
|
501
|
-
|
|
502
|
-
totalOutput += component.total.outputTokens;
|
|
503
|
-
totalTokens += component.total.totalTokens;
|
|
527
|
+
addInto(total, component.total);
|
|
504
528
|
}
|
|
505
|
-
return
|
|
506
|
-
inputTokens: totalInput,
|
|
507
|
-
outputTokens: totalOutput,
|
|
508
|
-
totalTokens
|
|
509
|
-
};
|
|
529
|
+
return total;
|
|
510
530
|
}
|
|
511
531
|
/**
|
|
512
532
|
* Log comprehensive token usage summary
|