@heripo/pdf-parser 0.1.44 → 0.1.45
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/package.json +8 -8
package/README.ko.md
CHANGED
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
## 주요 기능
|
|
38
38
|
|
|
39
39
|
- **ocrmac 고정 OCR**: Docling 변환은 항상 ocrmac / Apple Vision Framework를 사용
|
|
40
|
-
- **필수 VLM 보정**: Docling 이후 텍스트
|
|
41
|
-
- **Apple Silicon
|
|
40
|
+
- **필수 VLM 보정**: Docling 이후 텍스트 보정은 필수이며 page gate와 구조 검토는 비활성화 가능
|
|
41
|
+
- **Apple Silicon 백엔드**: Docling 변환에서 `mps` accelerator 요청
|
|
42
42
|
- **자동 환경 설정**: Python 가상환경 및 docling-serve 자동 설치
|
|
43
43
|
- **이미지 추출**: PDF 내 이미지 자동 추출 및 저장
|
|
44
44
|
- **문서 유형 검증**: LLM 기반 고고학 보고서 여부 검증 (선택)
|
|
@@ -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
|
-
> **중요**: Python 3.13
|
|
76
|
+
> **중요**: 현재 설치 코드는 Python 3.9–3.12만 허용하고 3.13 이상은 거부합니다. 저장소의 버전 검사 기준이며 모든 upstream Docling 버전의 호환성을 뜻하지는 않습니다.
|
|
77
77
|
|
|
78
78
|
```bash
|
|
79
79
|
# Python 3.11 설치 (권장)
|
|
80
80
|
brew install python@3.11
|
|
81
81
|
|
|
82
82
|
# 버전 확인
|
|
83
|
-
|
|
83
|
+
export PATH="$(brew --prefix python@3.11)/libexec/bin:$PATH"
|
|
84
|
+
python3 --version
|
|
84
85
|
```
|
|
85
86
|
|
|
86
87
|
#### 4. poppler (PDF 텍스트 추출)
|
|
@@ -105,9 +106,9 @@ macOS에 기본적으로 설치되어 있습니다. 확인:
|
|
|
105
106
|
which lsof
|
|
106
107
|
```
|
|
107
108
|
|
|
108
|
-
#### 7. ImageMagick + Ghostscript
|
|
109
|
+
#### 7. ImageMagick + Ghostscript
|
|
109
110
|
|
|
110
|
-
이미지 PDF
|
|
111
|
+
필수 VLM 보정에 사용하는 로컬 PDF 페이지 렌더링과 이미지 PDF 폴백에 필요합니다. `enableImagePdfFallback`과 `forceImagePdf`가 꺼져 있어도 설치하세요.
|
|
111
112
|
|
|
112
113
|
```bash
|
|
113
114
|
brew install imagemagick ghostscript
|
|
@@ -121,25 +122,27 @@ brew install imagemagick ghostscript
|
|
|
121
122
|
2. `docling-serve` 및 의존성 설치
|
|
122
123
|
3. 로컬 포트에서 docling-serve 프로세스 시작
|
|
123
124
|
|
|
124
|
-
|
|
125
|
+
가상환경은 재사용하지만 로컬 초기화에서 의존성 설치 단계를 다시 실행합니다. 설치 코드는 docling-serve 1.16.1과 명시적인 Docling 런타임 버전을 사용합니다. [python-environment.ts](./src/environment/python-environment.ts)를 참고하세요. 별도 `python3.11` 설치 여부보다 PATH의 `python3` 버전이 중요합니다.
|
|
125
126
|
|
|
126
127
|
## 설치
|
|
127
128
|
|
|
128
129
|
```bash
|
|
129
130
|
# npm으로 설치
|
|
130
|
-
npm install @heripo/pdf-parser @heripo/logger
|
|
131
|
+
npm install @heripo/pdf-parser @heripo/logger @ai-sdk/openai
|
|
131
132
|
|
|
132
133
|
# pnpm으로 설치
|
|
133
|
-
pnpm add @heripo/pdf-parser @heripo/logger
|
|
134
|
+
pnpm add @heripo/pdf-parser @heripo/logger @ai-sdk/openai
|
|
134
135
|
|
|
135
136
|
# 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
|
## 사용법
|
|
140
141
|
|
|
141
142
|
### 기본 사용법
|
|
142
143
|
|
|
144
|
+
ESM 프로젝트에서 실행하세요. 예제의 `HERIPO_MODEL`은 사용자가 설정하는 환경 변수로, 이미지 입력과 구조화된 출력을 지원하는 모델 ID를 지정합니다. `OPENAI_API_KEY`도 설정하세요. 다른 provider는 해당 AI SDK adapter로 대체할 수 있습니다.
|
|
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
|
// 초기화 (환경 설정 및 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
|
+
// PDF 파싱
|
|
171
|
+
const tokenUsageReport = await pdfParser.parse(
|
|
172
|
+
'file:///path/to/report.pdf', // PDF URL (file:// 또는 http://)
|
|
173
|
+
'report-001', // 리포트 ID
|
|
174
|
+
async (outputPath) => {
|
|
175
|
+
// 변환 완료 콜백
|
|
176
|
+
console.log('PDF 변환 완료:', 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
|
-
// 토큰 사용량 리포트 (LLM 사용이 없으면 null)
|
|
187
|
-
console.log('토큰 사용량:', tokenUsageReport);
|
|
190
|
+
// 토큰 사용량 리포트 (LLM 사용이 없으면 null)
|
|
191
|
+
console.log('토큰 사용량:', tokenUsageReport);
|
|
192
|
+
} finally {
|
|
193
|
+
await pdfParser.dispose();
|
|
194
|
+
}
|
|
188
195
|
```
|
|
189
196
|
|
|
190
197
|
### 고급 옵션
|
|
@@ -193,7 +200,7 @@ console.log('토큰 사용량:', tokenUsageReport);
|
|
|
193
200
|
// 옵션 A: 로컬 서버 (포트 모드)
|
|
194
201
|
const pdfParser = new PDFParser({
|
|
195
202
|
logger,
|
|
196
|
-
port: 5001,
|
|
203
|
+
port: 5001, // Local port must be specified explicitly
|
|
197
204
|
timeout: 10000000, // 타임아웃 (밀리초)
|
|
198
205
|
venvPath: '/custom/path/.venv', // 커스텀 venv 경로 (기본값: CWD/.venv)
|
|
199
206
|
killExistingProcess: true, // 포트의 기존 프로세스 종료 (기본값: false)
|
|
@@ -216,13 +223,13 @@ const tokenUsageReport = await pdfParser.parse(
|
|
|
216
223
|
// Docling 이후 필수 보정
|
|
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
|
// 문서 유형 검증
|
|
249
|
-
documentValidationModel: openai(
|
|
256
|
+
documentValidationModel: openai(process.env.HERIPO_MODEL!),
|
|
250
257
|
|
|
251
258
|
// 대용량 PDF 청크 변환
|
|
252
259
|
chunkedConversion: true,
|
|
@@ -265,6 +272,12 @@ const tokenUsageReport = await pdfParser.parse(
|
|
|
265
272
|
);
|
|
266
273
|
```
|
|
267
274
|
|
|
275
|
+
### 산출물
|
|
276
|
+
|
|
277
|
+
콜백은 `output/<reportId>/`의 절대 경로를 받습니다. `result.json`은 보정 결과이고 `result_ocr_origin.json`은 초기 OCR 스냅샷입니다. `images/`와 `pages/`는 추출 이미지와 페이지 이미지입니다. 구조 검토 활성화 시 `result_review_origin.json`, `review_assistance_page_gate.json`, `review_assistance_checkpoint.json`, `review_assistance.json`도 생성됩니다. 실패한 검토 작업은 리포트의 페이지 상태·이슈·call trace를 확인하세요.
|
|
278
|
+
|
|
279
|
+
`cleanupAfterCallback: true`이면 콜백 이후 산출물 디렉터리가 삭제됩니다. 필요한 파일을 콜백 안에서 다른 위치로 복사하거나 `false`로 보존하세요. 파서는 `result-processed.json`이나 handoff manifest를 만들지 않으며 데모 worker가 별도로 저장합니다.
|
|
280
|
+
|
|
268
281
|
### 리소스 정리
|
|
269
282
|
|
|
270
283
|
작업 완료 후 리소스를 정리합니다:
|
|
@@ -278,15 +291,15 @@ await pdfParser.dispose();
|
|
|
278
291
|
|
|
279
292
|
### ocrmac을 고정하는 이유
|
|
280
293
|
|
|
281
|
-
|
|
294
|
+
ocrmac은 macOS의 Apple Vision을 사용하는 고정 OCR 백엔드입니다.
|
|
282
295
|
|
|
283
|
-
`@heripo/pdf-parser`는 더 이상 OCR strategy를 sampling하거나 VLM OCR 경로로 전환하지 않습니다. Docling 변환은 항상 ocrmac으로 실행하고, VLM
|
|
296
|
+
`@heripo/pdf-parser`는 더 이상 OCR strategy를 sampling하거나 VLM OCR 경로로 전환하지 않습니다. Docling 변환은 항상 ocrmac으로 실행하고, VLM 보정은 Docling 이후 실행합니다. 선택적인 언어 감지와 문서 유형 검증에서도 변환 전에 모델을 호출할 수 있습니다.
|
|
284
297
|
|
|
285
298
|
### 필수 correction 계약
|
|
286
299
|
|
|
287
300
|
모든 `parse()` 호출은 `correction.models.textCorrection`, `correction.models.pageGate`, `correction.models.reviewAssistance`를 제공해야 합니다. 필수 모델이 누락되면 변환 callback을 감싸기 전에 명확히 실패합니다.
|
|
288
301
|
|
|
289
|
-
보정 단계는
|
|
302
|
+
구조 검토가 기본 설정대로 활성화된 경우 보정 단계는 다음과 같습니다.
|
|
290
303
|
|
|
291
304
|
1. 변경 전 `result_ocr_origin.json`을 저장합니다.
|
|
292
305
|
2. `textCorrection` 모델로 페이지 텍스트와 표 셀 OCR을 보정합니다.
|
|
@@ -299,21 +312,39 @@ await pdfParser.dispose();
|
|
|
299
312
|
|
|
300
313
|
### 로컬 모델 권장 실행 방식
|
|
301
314
|
|
|
302
|
-
보정 파이프라인은 로컬 VLM 기준으로 설계되었습니다. 큰 context 하나보다 작은 context를 자주 호출하고, deterministic validator와 retry, timeout, checkpoint/resume으로 안정성을 확보합니다. 처음에는 `concurrency.pages: 1`, `concurrency.
|
|
315
|
+
보정 파이프라인은 로컬 VLM 기준으로 설계되었습니다. 큰 context 하나보다 작은 context를 자주 호출하고, deterministic validator와 retry, timeout, checkpoint/resume으로 안정성을 확보합니다. 처음에는 `concurrency.pages: 1`, `concurrency.reviewTasks: 1`, `modelConcurrency: 1`, `temperature: 0`, 충분한 `workItemTimeoutMs`로 시작하고, 모델이 안정화된 뒤 동시성을 높이는 것을 권장합니다.
|
|
303
316
|
|
|
304
|
-
###
|
|
317
|
+
### 검증
|
|
305
318
|
|
|
306
|
-
|
|
319
|
+
저장소에 정의된 테스트 명령은 `pnpm --filter @heripo/pdf-parser test:coverage`입니다. 테스트는 외부 모델 호출을 모킹하므로 실제 문서의 보정 품질은 별도 확인해야 합니다.
|
|
307
320
|
|
|
308
|
-
|
|
309
|
-
pnpm --filter @heripo/pdf-parser smoke:correction
|
|
310
|
-
```
|
|
321
|
+
### 보정 옵션과 기본값
|
|
311
322
|
|
|
312
|
-
|
|
323
|
+
세 필수 모델은 구조 검토를 꺼도 모두 제공해야 합니다. `reviewAssistanceEnabled: false`이면 텍스트·표 셀 OCR 보정만 수행하고 page gate와 구조 검토를 생략합니다. `tableCorrectionEnabled: false`는 구조 검토의 표 작업만 생략하며 초기 표 셀 OCR 보정에는 영향을 주지 않습니다.
|
|
324
|
+
|
|
325
|
+
`forceAutoApply` 기본값은 `false`입니다. `true`이면 검증을 통과한 명령을 신뢰도 임계값과 구조적 제약에 따른 수동 검토 분기 없이 적용합니다. 명령 검증 자체를 생략하는 옵션은 아닙니다.
|
|
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
|
+
기본값은 내보낸 `PDF_CORRECTION_DEFAULTS`에서도 확인할 수 있습니다. `concurrency.tables`와 `pageGate.structuralNoiseThreshold`는 타입에 있지만 현재 최상위 파이프라인에서 runner 제어값으로 전달되지 않습니다. 표 작업 동시성은 `reviewTasks`와 `modelConcurrency`로 제어하세요.
|
|
340
|
+
|
|
341
|
+
### 언어 감지
|
|
342
|
+
|
|
343
|
+
`ocr_lang`이 있으면 그대로 사용합니다. 없으면 로컬 PDF 텍스트 레이어를 분석하고, 부족한 경우 `languageDetectionModel`로 페이지를 감지합니다. `languageDetectionFallbackModel`도 지정할 수 있습니다. 원격 입력이나 감지할 수 없는 입력은 기본 언어 `ko-KR`, `en-US`를 사용합니다. OCR 엔진 선택 기능은 아니며 OCR은 항상 ocrmac입니다.
|
|
313
344
|
|
|
314
345
|
## Review Assistance
|
|
315
346
|
|
|
316
|
-
Review Assistance는 텍스트 보정 이후
|
|
347
|
+
Review Assistance는 `correction.reviewAssistanceEnabled: true`일 때(라이브러리 기본값) 텍스트 보정 이후 실행되지만 모든 페이지를 같은 강도로 처리하지 않습니다. page gate가 표지, 챕터 표지, 바코드/ISBN 페이지, 장식 중심 페이지를 구조 review 저가치 페이지로 분류합니다. skip된 페이지도 `review_assistance.json`에 info issue와 skip reason으로 남습니다.
|
|
317
348
|
|
|
318
349
|
eligible page는 text OCR/Hanja, text integrity, text role/footnote, tables, pictures/captions, layout/bbox/order, table-specific correction 같은 작은 work item으로 쪼개집니다. 각 호출의 timing, model id, attempts, target refs, deterministic validation status는 `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
|
+
전체 파이프라인에는 로컬 `file://` PDF를 사용하세요. HTTP 입력은 Docling에서 받지만 로컬 페이지 렌더링·언어 감지·문서 검증은 건너뜁니다. `pages/page_<index>.png`가 없으면 텍스트 보정을 생략하므로 콜백 성공이 모든 페이지의 보정 성공을 뜻하지는 않습니다. 보정 호출이 실패한 페이지는 OCR 원문을 유지하고 경고를 기록합니다. 고신뢰도 수정은 `result.json`에 적용하고, 원본 snapshot은 `result_review_origin.json`, `result_ocr_origin.json`에 보존하며, `review_assistance_page_gate.json`과 페이지별 결정, 이슈, proposal, call trace, validation status, 요약 count를 담은 `review_assistance.json`을 기록합니다.
|
|
357
388
|
|
|
358
389
|
## 문서 유형 검증
|
|
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, //
|
|
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
|
+
청크 변환 결과를 합친 뒤 보정을 수행합니다. 현재 구현은 청크 변환 실패 시 `enableImagePdfFallback`과 별개로 이미지 PDF 재시도를 수행하므로 이 경로에도 ImageMagick/Ghostscript가 필요합니다. `chunkedConversion`과 `forceImagePdf`를 함께 지정한 로컬 입력은 청크 분기가 우선합니다.
|
|
446
|
+
|
|
414
447
|
## 이미지 PDF 폴백
|
|
415
448
|
|
|
416
449
|
변환이 실패하면 파서가 PDF를 이미지 기반 PDF로 변환한 후 재시도할 수 있습니다. 복잡하거나 손상된 구조의 PDF에 유용합니다. ImageMagick과 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, // 항상 이미지 PDF로 먼저 변환
|
|
@@ -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('파싱이 취소되었습니다');
|
|
485
518
|
}
|
|
486
519
|
}
|
|
@@ -494,35 +527,9 @@ try {
|
|
|
494
527
|
|
|
495
528
|
## 왜 macOS 전용인가?
|
|
496
529
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
### OCR 선택 배경
|
|
500
|
-
|
|
501
|
-
고고학 발굴조사보고서 PDF는 다음과 같은 특성이 있습니다:
|
|
502
|
-
|
|
503
|
-
- 수백 페이지 분량의 스캔 문서
|
|
504
|
-
- 복잡한 표, 도면, 사진이 포함된 레이아웃
|
|
505
|
-
- 정밀한 텍스트 추출이 필수적
|
|
506
|
-
|
|
507
|
-
### OCR 옵션 비교
|
|
508
|
-
|
|
509
|
-
| 방식 | 성능 | 비용 | 설명 |
|
|
510
|
-
| ----------------------- | ----- | ---- | --------------------------------------- |
|
|
511
|
-
| **Docling (로컬)** | ★★★★★ | 무료 | Apple Silicon에서 압도적 성능, GPU 활용 |
|
|
512
|
-
| Cloud OCR (Google, AWS) | ★★★★ | $$$ | 수백 페이지당 수십 달러 |
|
|
513
|
-
| Tesseract (로컬) | ★★ | 무료 | 한국어 인식률 낮음, 레이아웃 분석 부족 |
|
|
514
|
-
|
|
515
|
-
### 핵심 장점
|
|
530
|
+
패키지는 `os: ["darwin"]`을 선언하고 `init()`에서 macOS와 로컬 도구를 확인합니다. `baseUrl`로 외부 Docling 서버를 연결해도 이 클라이언트 제한은 유지됩니다. ocrmac OCR은 macOS의 Apple Vision을 사용합니다.
|
|
516
531
|
|
|
517
|
-
|
|
518
|
-
- **성능**: Apple Silicon M1/M2/M3/M4/M5에서 GPU 가속으로 빠른 처리
|
|
519
|
-
- **품질**: 복잡한 레이아웃의 문서도 정확히 인식
|
|
520
|
-
- **프라이버시**: 문서가 외부 서버로 전송되지 않음
|
|
521
|
-
|
|
522
|
-
### Trade-off
|
|
523
|
-
|
|
524
|
-
- macOS + Apple Silicon 환경에서만 최적 성능
|
|
525
|
-
- Linux/Windows 지원은 현재 계획 없음 (아래 "Linux 지원 현황" 참고)
|
|
532
|
+
Docling OCR은 로컬에서 실행할 수 있지만 VLM 보정에는 설정한 모델을 호출합니다. 클라우드 모델을 선택하면 문서 텍스트와 페이지 이미지가 해당 provider로 전송될 수 있고 API 비용이 발생합니다. 전체 처리를 로컬에 유지하려면 언어 감지·문서 검증·보정·fallback·후속 문서 처리 모델까지 로컬로 설정해야 합니다.
|
|
526
533
|
|
|
527
534
|
## 시스템 의존성 상세
|
|
528
535
|
|
|
@@ -534,17 +541,18 @@ try {
|
|
|
534
541
|
| poppler | Any | `brew install poppler` | PDF 페이지 수 확인 (pdfinfo) 및 텍스트 레이어 추출 (pdftotext) |
|
|
535
542
|
| jq | Any | `brew install jq` | JSON 처리 (변환 결과 파싱) |
|
|
536
543
|
| lsof | Any | macOS 기본 설치됨 | docling-serve 포트 관리 |
|
|
537
|
-
| ImageMagick | Any
|
|
538
|
-
| Ghostscript | Any
|
|
544
|
+
| ImageMagick | Any | `brew install imagemagick` | 이미지 PDF 폴백 및 페이지 렌더링 |
|
|
545
|
+
| Ghostscript | Any | `brew install ghostscript` | 이미지 PDF 폴백 (PDF를 이미지로 변환) |
|
|
539
546
|
|
|
540
|
-
>
|
|
547
|
+
> **현재 설치 코드는 Python 3.13 이상을 거부합니다.** [python-version.ts](./src/utils/python-version.ts)의 검사를 참고하세요.
|
|
541
548
|
|
|
542
549
|
### Python 버전 확인
|
|
543
550
|
|
|
544
551
|
```bash
|
|
545
552
|
# 설치된 Python 버전 확인
|
|
546
553
|
python3 --version
|
|
547
|
-
|
|
554
|
+
export PATH="$(brew --prefix python@3.11)/libexec/bin:$PATH"
|
|
555
|
+
python3 --version
|
|
548
556
|
|
|
549
557
|
# 여러 버전이 설치된 경우
|
|
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; // 로거 인스턴스 (필수)
|
|
572
|
-
timeout?: number; // 타임아웃 (밀리초, 기본값:
|
|
580
|
+
timeout?: number; // 타임아웃 (밀리초, 기본값: 100000)
|
|
573
581
|
venvPath?: string; // Python venv 경로 (기본값: CWD/.venv)
|
|
574
582
|
killExistingProcess?: boolean; // 포트의 기존 프로세스 종료 (기본값: false)
|
|
575
583
|
enableImagePdfFallback?: boolean; // 이미지 PDF 폴백 활성화 (기본값: false, ImageMagick + Ghostscript 필요)
|
|
576
584
|
} & (
|
|
577
|
-
| { port?: number } // 로컬
|
|
585
|
+
| { port?: number } // 로컬 모드; port를 명시해야 함 (런타임 기본값 없음)
|
|
578
586
|
| { baseUrl: string } // 외부 서버 모드
|
|
579
587
|
);
|
|
580
588
|
```
|
|
@@ -614,6 +622,10 @@ PDF 파일을 파싱합니다.
|
|
|
614
622
|
await pdfParser.dispose();
|
|
615
623
|
```
|
|
616
624
|
|
|
625
|
+
##### `isReady(): Promise<boolean>` / `ensureReady(): Promise<void>`
|
|
626
|
+
|
|
627
|
+
`isReady()`는 복구 없이 상태만 확인합니다. 초기화 후 사용하는 `ensureReady()`는 로컬 서버 복구를 시도하며 외부 서버 오류는 그대로 전달합니다. `dispose()`는 재사용한 서버를 포함해 설정된 로컬 포트의 프로세스를 종료하므로 외부에서 수명을 관리하는 서버는 `baseUrl`로 연결하세요.
|
|
628
|
+
|
|
617
629
|
### PDFConvertOptions
|
|
618
630
|
|
|
619
631
|
```typescript
|
|
@@ -630,6 +642,8 @@ type PDFConvertOptions = {
|
|
|
630
642
|
|
|
631
643
|
// 문서 처리
|
|
632
644
|
document_timeout?: number; // 문서 처리 타임아웃 (초)
|
|
645
|
+
languageDetectionModel?: LanguageModel;
|
|
646
|
+
languageDetectionFallbackModel?: LanguageModel;
|
|
633
647
|
documentValidationModel?: LanguageModel; // 문서 유형 검증용 LLM
|
|
634
648
|
|
|
635
649
|
// 보정 진행 상황
|
|
@@ -686,7 +700,7 @@ interface PDFCorrectionOptions {
|
|
|
686
700
|
concurrency?: {
|
|
687
701
|
pages?: number; // 페이지 단위 text correction/page gate 동시성
|
|
688
702
|
reviewTasks?: number; // 구조 Review Assistance work-item 동시성
|
|
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 지원 현황
|
|
802
816
|
|
|
803
|
-
현재
|
|
804
|
-
|
|
805
|
-
| 플랫폼 | 상태 | 비고 |
|
|
806
|
-
| --------------------- | ---- | ------------------------------- |
|
|
807
|
-
| macOS + Apple Silicon | 지원 | 최적 성능, GPU 가속 |
|
|
808
|
-
| macOS + Intel | 지원 | GPU 가속 없음 |
|
|
809
|
-
| Linux | 미정 | 성능/비용 문제로 현재 계획 없음 |
|
|
810
|
-
| Windows | 미정 | WSL2 통한 Linux 방식 고려 가능 |
|
|
811
|
-
|
|
812
|
-
### Linux 미지원 사유
|
|
813
|
-
|
|
814
|
-
Docling SDK의 로컬 OCR은 macOS에서 Apple Metal GPU 가속을 활용해 성능과 비용 효율성을 모두 달성합니다. Linux에서 동등한 성능과 비용 효율성을 제공하는 OCR 솔루션을 아직 찾지 못했습니다.
|
|
815
|
-
|
|
816
|
-
### 아이디어 제안 환영
|
|
817
|
-
|
|
818
|
-
성능과 비용을 모두 잡으면서 Linux를 지원할 수 있는 아이디어가 있다면, [GitHub Discussions](https://github.com/heripo-lab/heripo-engine/discussions) 또는 Issue로 제안해 주세요. 특히 다음과 같은 정보가 도움이 됩니다:
|
|
819
|
-
|
|
820
|
-
- Linux에서 한국어 문서 OCR 경험
|
|
821
|
-
- 복잡한 레이아웃(표, 도면) 처리 가능한 OCR 솔루션
|
|
822
|
-
- 수백 페이지 처리 시 비용 추산
|
|
817
|
+
현재 `@heripo/pdf-parser`는 macOS 전용이며 Linux/Windows 실행 경로를 제공하지 않습니다. 플랫폼 관련 제안은 [GitHub Discussions](https://github.com/heripo-lab/heripo-engine/discussions)에 남겨주세요.
|
|
823
818
|
|
|
824
819
|
## 관련 패키지
|
|
825
820
|
|
|
826
|
-
- [@heripo/document-processor](../document-processor) - 문서 구조 분석 및 LLM 처리
|
|
827
|
-
- [@heripo/model](../model) - 데이터 모델 및 타입 정의
|
|
821
|
+
- [@heripo/document-processor](../document-processor/README.ko.md) - 문서 구조 분석 및 LLM 처리
|
|
822
|
+
- [@heripo/model](../model/README.ko.md) - 데이터 모델 및 타입 정의
|
|
828
823
|
|
|
829
824
|
## 후원
|
|
830
825
|
|
|
@@ -839,13 +834,13 @@ heripo lab의 오픈소스 연구를 후원하려면 다음 경로를 이용할
|
|
|
839
834
|
|
|
840
835
|
## 기여하기
|
|
841
836
|
|
|
842
|
-
기여는 언제나 환영합니다! [기여 가이드](../../CONTRIBUTING.
|
|
837
|
+
기여는 언제나 환영합니다! [기여 가이드](../../CONTRIBUTING.md)를 참고하세요.
|
|
843
838
|
|
|
844
839
|
## 이슈 및 지원
|
|
845
840
|
|
|
846
841
|
- **버그 리포트**: [GitHub Issues](https://github.com/heripo-lab/heripo-engine/issues)
|
|
847
842
|
- **토론**: [GitHub Discussions](https://github.com/heripo-lab/heripo-engine/discussions)
|
|
848
|
-
- **보안 취약점**: [보안 정책](../../SECURITY.
|
|
843
|
+
- **보안 취약점**: [보안 정책](../../SECURITY.md) 참고
|
|
849
844
|
|
|
850
845
|
## 프로젝트 전체 정보
|
|
851
846
|
|
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/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@heripo/pdf-parser",
|
|
3
3
|
"private": false,
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.45",
|
|
6
6
|
"description": "PDF parsing library using Docling SDK with OCR support for macOS",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"types": "dist/index.d.ts",
|
|
@@ -55,13 +55,13 @@
|
|
|
55
55
|
"access": "public"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@heripo/logger": "0.1.
|
|
59
|
-
"@heripo/model": "0.1.
|
|
60
|
-
"ai": "^7.0.
|
|
58
|
+
"@heripo/logger": "0.1.45",
|
|
59
|
+
"@heripo/model": "0.1.45",
|
|
60
|
+
"ai": "^7.0.107",
|
|
61
61
|
"docling-sdk": "^2.0.6",
|
|
62
62
|
"es-toolkit": "^1.52.0",
|
|
63
63
|
"yauzl": "3.4.0",
|
|
64
|
-
"zod": "^4.5
|
|
64
|
+
"zod": "^4.6.5"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@heripo/shared": "0.0.0",
|
|
@@ -69,10 +69,10 @@
|
|
|
69
69
|
"@heripo/tsup-config": "0.0.0",
|
|
70
70
|
"@heripo/vitest-config": "0.0.0",
|
|
71
71
|
"@types/yauzl": "^3.4.0",
|
|
72
|
-
"@vitest/coverage-v8": "^5.0.
|
|
73
|
-
"@vitest/expect": "^5.0.
|
|
72
|
+
"@vitest/coverage-v8": "^5.0.1",
|
|
73
|
+
"@vitest/expect": "^5.0.1",
|
|
74
74
|
"tsup": "^8.5.1",
|
|
75
|
-
"vitest": "^5.0.
|
|
75
|
+
"vitest": "^5.0.1"
|
|
76
76
|
},
|
|
77
77
|
"scripts": {
|
|
78
78
|
"clean": "rimraf dist",
|