@heripo/pdf-parser 0.1.29 → 0.1.31

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 CHANGED
@@ -20,7 +20,7 @@
20
20
  - [사전 요구사항](#사전-요구사항)
21
21
  - [설치](#설치)
22
22
  - [사용법](#사용법)
23
- - [OCR 전략 시스템](#ocr-전략-시스템)
23
+ - [필수 보정 파이프라인](#필수-보정-파이프라인)
24
24
  - [Review Assistance](#review-assistance)
25
25
  - [문서 유형 검증](#문서-유형-검증)
26
26
  - [대용량 PDF 청크 변환](#대용량-pdf-청크-변환)
@@ -36,15 +36,16 @@
36
36
 
37
37
  ## 주요 기능
38
38
 
39
- - **고품질 OCR**: Docling SDK를 활용한 문서 인식 (ocrmac / Apple Vision Framework)
40
- - **한국어 보고서 VLM 보정**: 한국어 보고서를 자동 감지하고 모든 페이지에 VLM 텍스트 보정을 적용
39
+ - **ocrmac 고정 OCR**: Docling 변환은 항상 ocrmac / Apple Vision Framework를 사용
40
+ - **필수 VLM 보정**: Docling 이후 텍스트 보정, page gate, 구조 review 모델을 항상 실행
41
41
  - **Apple Silicon 최적화**: M1/M2/M3/M4/M5 칩에서 GPU 가속 지원
42
42
  - **자동 환경 설정**: Python 가상환경 및 docling-serve 자동 설치
43
43
  - **이미지 추출**: PDF 내 이미지 자동 추출 및 저장
44
44
  - **문서 유형 검증**: LLM 기반 고고학 보고서 여부 검증 (선택)
45
45
  - **청크 변환**: 대용량 PDF를 청크로 분할하여 안정적으로 처리
46
46
  - **이미지 PDF 폴백**: 변환 실패 시 이미지 기반 PDF로 자동 재시도
47
- - **Review Assistance**: 선택적 page-level VLM review로 audit proposal을 기록하고 고신뢰도 수정만 자동 적용
47
+ - **Review Assistance**: page-level VLM review로 audit proposal을 기록하고, 의미 낮은 페이지는 관찰 가능한 사유와 함께 skip하며, 고신뢰도 수정만 자동 적용
48
+ - **표 보정 강화**: 표별 work item으로 셀, span, header, 단위, 각주, 인접 페이지 연속표를 검증
48
49
  - **AbortSignal 지원**: 진행 중인 파싱 작업 취소
49
50
  - **서버 크래시 복구**: ECONNREFUSED 발생 시 docling-serve 자동 재시작
50
51
 
@@ -84,7 +85,7 @@ python3.11 --version
84
85
 
85
86
  #### 4. poppler (PDF 텍스트 추출)
86
87
 
87
- PDF 페이지 수 확인(`pdfinfo`)과 텍스트 레이어 추출(`pdftotext`)에 필요하며, OCR 전략 시스템의 텍스트 레이어 사전 검사에 사용됩니다.
88
+ PDF 페이지 수 확인(`pdfinfo`)과 텍스트 레이어 추출(`pdftotext`)에 필요하며, Docling 이후 보정의 참조 텍스트로 사용됩니다.
88
89
 
89
90
  ```bash
90
91
  brew install poppler
@@ -140,6 +141,7 @@ yarn add @heripo/pdf-parser @heripo/logger
140
141
  ### 기본 사용법
141
142
 
142
143
  ```typescript
144
+ import { openai } from '@ai-sdk/openai';
143
145
  import { Logger } from '@heripo/logger';
144
146
  import { PDFParser } from '@heripo/pdf-parser';
145
147
 
@@ -156,6 +158,8 @@ const pdfParser = new PDFParser({
156
158
  logger,
157
159
  });
158
160
 
161
+ const correctionModel = openai('gpt-5.1');
162
+
159
163
  // 초기화 (환경 설정 및 docling-serve 시작)
160
164
  await pdfParser.init();
161
165
 
@@ -168,7 +172,15 @@ const tokenUsageReport = await pdfParser.parse(
168
172
  console.log('PDF 변환 완료:', outputPath);
169
173
  },
170
174
  false, // cleanupAfterCallback
171
- {}, // PDFConvertOptions
175
+ {
176
+ correction: {
177
+ models: {
178
+ textCorrection: correctionModel,
179
+ pageGate: correctionModel,
180
+ reviewAssistance: correctionModel,
181
+ },
182
+ },
183
+ }, // PDFConvertOptions
172
184
  );
173
185
 
174
186
  // 토큰 사용량 리포트 (LLM 사용이 없으면 null)
@@ -201,23 +213,41 @@ const tokenUsageReport = await pdfParser.parse(
201
213
  async (outputPath) => console.log(outputPath),
202
214
  false,
203
215
  {
204
- // OCR 전략 옵션
205
- strategySamplerModel: openai('gpt-5.1'),
206
- vlmProcessorModel: openai('gpt-5.1'),
207
- vlmConcurrency: 3,
208
-
209
- // 문서 유형 검증
210
- documentValidationModel: openai('gpt-5.1'),
211
-
212
- // 선택적 page-level review assistance
213
- reviewAssistance: {
214
- enabled: true,
216
+ // Docling 이후 필수 보정
217
+ correction: {
218
+ models: {
219
+ textCorrection: openai('gpt-5.1'),
220
+ pageGate: openai('gpt-5.1'),
221
+ reviewAssistance: openai('gpt-5.1'),
222
+ tableCorrection: openai('gpt-5.1'),
223
+ reviewAssistanceTasks: {
224
+ text_ocr_hanja: openai('gpt-5.1'),
225
+ tables: openai('gpt-5.1'),
226
+ },
227
+ },
228
+ concurrency: {
229
+ pages: 1,
230
+ reviewTasks: 4,
231
+ tables: 1,
232
+ },
233
+ localModelConcurrency: 1,
234
+ workItemTimeoutMs: 900000,
235
+ maxRetries: {
236
+ textCorrection: 3,
237
+ pageGate: 3,
238
+ reviewAssistance: 3,
239
+ tableCorrection: 3,
240
+ },
215
241
  autoApplyThreshold: 0.85,
216
242
  proposalThreshold: 0.5,
243
+ temperature: 0,
217
244
  outputLanguage: 'ko-KR',
218
245
  },
219
246
  onReviewAssistanceProgress: (event) => console.log(event),
220
247
 
248
+ // 문서 유형 검증
249
+ documentValidationModel: openai('gpt-5.1'),
250
+
221
251
  // 대용량 PDF 청크 변환
222
252
  chunkedConversion: true,
223
253
  chunkSize: 50,
@@ -244,58 +274,52 @@ const tokenUsageReport = await pdfParser.parse(
244
274
  await pdfParser.dispose();
245
275
  ```
246
276
 
247
- ## OCR 전략 시스템
277
+ ## 필수 보정 파이프라인
248
278
 
249
- ### 왜 이 전략인가?
279
+ ### ocrmac을 고정하는 이유
250
280
 
251
281
  **ocrmac(Apple Vision Framework)은 매우 우수한 OCR 엔진입니다** -- 무료이고, GPU 가속을 지원하며, 고품질 결과를 제공합니다. 수천~수백만 권의 고고학 보고서를 처리할 때 이만한 솔루션이 없습니다.
252
282
 
253
- **그러나 한국어 고고학 보고서는 문자 체계 인식 보정이 자주 필요합니다.** 한자 복원, CJK mojibake, 음독 치환, 기관명 등은 표준 OCR만으로 안정적으로 처리하기 어렵습니다. VLM을 기본 OCR 엔진으로 쓰는 대신, 빠른 ocrmac 파이프라인을 먼저 실행한 뒤 한국어 보고서에 VLM 텍스트 보정을 적용합니다.
283
+ `@heripo/pdf-parser`는 더 이상 OCR strategy를 sampling하거나 VLM OCR 경로로 전환하지 않습니다. Docling 변환은 항상 ocrmac으로 실행하고, VLM은 Docling 이후 필수 보정 단계에서만 사용합니다.
254
284
 
255
- ### 2단계 한국어 감지 (`OcrStrategySampler`)
285
+ ### 필수 correction 계약
256
286
 
257
- 1. **텍스트 레이어 사전 검사** (비용 없음): `pdftotext`로 문서의 텍스트 레이어를 추출하여 한글을 확인합니다. 한글이 있으면 즉시 한국어 문서로 판별합니다.
258
- 2. **VLM 샘플링** (필요 시에만): 최대 15페이지를 샘플링(앞뒤 10%는 표지·부록으로 제외)하여 Vision LLM으로 언어를 판정합니다. `ko-KR` 감지 시 즉시 종료합니다.
287
+ 모든 `parse()` 호출은 `correction.models.textCorrection`, `correction.models.pageGate`, `correction.models.reviewAssistance`를 제공해야 합니다. 필수 모델이 누락되면 변환 callback을 감싸기 전에 명확히 실패합니다.
259
288
 
260
- ### 전체 문서 보정 (`VlmTextCorrector`)
289
+ 보정 단계는 다음 순서로 실행됩니다.
261
290
 
262
- 한국어 보고서가 감지되면, 모든 페이지를 VLM에 전송하여 보정합니다:
291
+ 1. 변경 전 `result_ocr_origin.json`을 저장합니다.
292
+ 2. `textCorrection` 모델로 페이지 텍스트와 표 셀 OCR을 보정합니다.
293
+ 3. `pageGate` 모델로 Review Assistance page gate를 실행하고 `review_assistance_page_gate.json`을 기록합니다.
294
+ 4. task별 모델로 구조 Review Assistance work item을 실행합니다.
295
+ 5. 감지된 표마다 table-specific correction work item을 실행합니다.
296
+ 6. 실행 중 `review_assistance_checkpoint.json`을 기록하고, 완료 시 `review_assistance.json`을 기록합니다.
263
297
 
264
- - 각 페이지의 OCR 텍스트 요소와 표 셀을 추출
265
- - `pdftotext` 참조 텍스트를 품질 기준으로 활용
266
- - VLM이 치환 기반 보정(find -> replace)을 반환
267
- - 실패한 페이지는 원본 OCR 텍스트를 유지하며 건너뜀
298
+ 텍스트 보정은 텍스트나 표가 있는 모든 페이지에 적용됩니다. page gate는 구조적 Review Assistance 노이즈만 제어하며, OCR 텍스트 보정 범위를 줄이지 않습니다.
268
299
 
269
- ### 전략 옵션
300
+ ### 로컬 모델 권장 실행 방식
270
301
 
271
- ```typescript
272
- const tokenUsageReport = await pdfParser.parse(
273
- 'file:///path/to/input.pdf',
274
- 'report-001',
275
- async (outputPath) => console.log(outputPath),
276
- false,
277
- {
278
- // OCR 전략 샘플링 활성화 (Vision LLM 모델 제공)
279
- strategySamplerModel: openai('gpt-5.1'),
302
+ 보정 파이프라인은 로컬 VLM 기준으로 설계되었습니다. 큰 context 하나보다 작은 context를 자주 호출하고, deterministic validator와 retry, timeout, checkpoint/resume으로 안정성을 확보합니다. 처음에는 `concurrency.pages: 1`, `concurrency.tables: 1`, `localModelConcurrency: 1`, `temperature: 0`, 충분한 `workItemTimeoutMs`로 시작하고, 모델이 안정화된 뒤 동시성을 높이는 것을 권장합니다.
280
303
 
281
- // VLM 텍스트 보정 모델 (한국어 보고서 감지 시 필요)
282
- vlmProcessorModel: openai('gpt-5.1'),
304
+ ### 롤아웃 smoke test
283
305
 
284
- // VLM 페이지 처리 동시성 (기본값: 1)
285
- vlmConcurrency: 3,
306
+ 레포 contributor는 로컬 demo 고고학 보고서 산출물 2종으로 correction rollout smoke test를 실행할 수 있습니다.
286
307
 
287
- // 샘플링을 건너뛰고 특정 OCR 방식 강제
288
- forcedMethod: 'ocrmac', // 또는 'vlm'
289
- },
290
- );
308
+ ```bash
309
+ pnpm --filter @heripo/pdf-parser smoke:correction
291
310
  ```
292
311
 
312
+ 이 smoke test는 기존 demo artifact를 `/private/tmp/heripo-pdf-parser-correction-smoke`로 복사하고, deterministic local fake model로 correction을 실행한 뒤 `review_assistance.json`, 표 work-item trace, validation status, checkpoint resume 동작을 검증합니다. 파이프라인 기계적 경로를 검증하는 용도이며, 의미론적 표 보정 품질은 실제로 설정한 로컬 VLM 품질에 의존합니다.
313
+
293
314
  ## Review Assistance
294
315
 
295
- `reviewAssistance`는 Docling 변환 후 선택적으로 실행되는 page-level VLM
296
- review입니다. 렌더링된 페이지 이미지, 텍스트, 테이블, 캡션, 그림, reading
297
- order, role, bounding box, 도메인 패턴을 점검하고, 고신뢰도 수정만
298
- `result.json`에 적용하면서 audit report를 남깁니다.
316
+ Review Assistance는 텍스트 보정 이후 항상 실행되지만 모든 페이지를 같은 강도로 처리하지 않습니다. page gate가 표지, 챕터 표지, 바코드/ISBN 페이지, 장식 중심 페이지를 구조 review 저가치 페이지로 분류합니다. skip된 페이지도 `review_assistance.json`에 info issue와 skip reason으로 남습니다.
317
+
318
+ 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
+
320
+ ### 표 보정 전략
321
+
322
+ 표는 page 부속물이 아니라 1급 보정 대상으로 처리합니다. scheduler는 표마다 독립 table-specific work item을 만들기 때문에 한 페이지에 표가 여러 개 있어도 bbox, crop, context, validation 상태가 섞이지 않습니다. table validator는 target identity, 같은 페이지 다른 표 내용 혼입, cell, span, header, unit, footnote, empty-cell expansion, adjacent-page continuation ref를 확인한 뒤에야 auto-apply 또는 proposal 결정을 허용합니다.
299
323
 
300
324
  ```typescript
301
325
  const tokenUsageReport = await pdfParser.parse(
@@ -304,13 +328,21 @@ const tokenUsageReport = await pdfParser.parse(
304
328
  async (outputPath) => console.log(outputPath),
305
329
  false,
306
330
  {
307
- strategySamplerModel: openai('gpt-5.1'),
308
- vlmProcessorModel: openai('gpt-5.1'),
309
- reviewAssistance: {
310
- enabled: true,
331
+ correction: {
332
+ models: {
333
+ textCorrection: openai('gpt-5.1'),
334
+ pageGate: openai('gpt-5.1-mini'),
335
+ reviewAssistance: openai('gpt-5.1'),
336
+ tableCorrection: openai('gpt-5.1'),
337
+ },
311
338
  autoApplyThreshold: 0.85,
312
339
  proposalThreshold: 0.5,
313
- maxRetries: 3,
340
+ maxRetries: {
341
+ textCorrection: 3,
342
+ pageGate: 3,
343
+ reviewAssistance: 3,
344
+ tableCorrection: 3,
345
+ },
314
346
  temperature: 0,
315
347
  outputLanguage: 'ko-KR',
316
348
  },
@@ -321,10 +353,7 @@ const tokenUsageReport = await pdfParser.parse(
321
353
  );
322
354
  ```
323
355
 
324
- Review Assistance는 로컬 `file://` PDF와 `vlmProcessorModel` 또는
325
- `strategySamplerModel`이 필요합니다. 고신뢰도 수정은 `result.json`에 적용하고,
326
- 원본 snapshot은 `result_review_origin.json`, `result_ocr_origin.json`에 보존하며,
327
- 페이지별 결정, 이슈, proposal, 요약 count는 `review_assistance.json`에 기록합니다.
356
+ Review Assistance는 page image와 text-layer reference를 위해 로컬 `file://` PDF를 필요로 합니다. 고신뢰도 수정은 `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`을 기록합니다.
328
357
 
329
358
  ## 문서 유형 검증
330
359
 
@@ -340,6 +369,13 @@ try {
340
369
  async (outputPath) => console.log(outputPath),
341
370
  false,
342
371
  {
372
+ correction: {
373
+ models: {
374
+ textCorrection: openai('gpt-5.1'),
375
+ pageGate: openai('gpt-5.1'),
376
+ reviewAssistance: openai('gpt-5.1'),
377
+ },
378
+ },
343
379
  documentValidationModel: openai('gpt-5.1'),
344
380
  },
345
381
  );
@@ -361,6 +397,13 @@ const tokenUsageReport = await pdfParser.parse(
361
397
  async (outputPath) => console.log(outputPath),
362
398
  false,
363
399
  {
400
+ correction: {
401
+ models: {
402
+ textCorrection: openai('gpt-5.1'),
403
+ pageGate: openai('gpt-5.1'),
404
+ reviewAssistance: openai('gpt-5.1'),
405
+ },
406
+ },
364
407
  chunkedConversion: true,
365
408
  chunkSize: 50, // 청크당 페이지 수 (기본값: 상수에서 설정)
366
409
  chunkMaxRetries: 3, // 실패한 청크의 최대 재시도 횟수 (기본값: 상수에서 설정)
@@ -395,6 +438,13 @@ const tokenUsageReport = await pdfParser.parse(
395
438
  async (outputPath) => console.log(outputPath),
396
439
  false,
397
440
  {
441
+ correction: {
442
+ models: {
443
+ textCorrection: openai('gpt-5.1'),
444
+ pageGate: openai('gpt-5.1'),
445
+ reviewAssistance: openai('gpt-5.1'),
446
+ },
447
+ },
398
448
  forceImagePdf: true, // 항상 이미지 PDF로 먼저 변환
399
449
  },
400
450
  );
@@ -418,7 +468,15 @@ try {
418
468
  'report-001',
419
469
  async (outputPath) => console.log(outputPath),
420
470
  false,
421
- {},
471
+ {
472
+ correction: {
473
+ models: {
474
+ textCorrection: openai('gpt-5.1'),
475
+ pageGate: openai('gpt-5.1'),
476
+ reviewAssistance: openai('gpt-5.1'),
477
+ },
478
+ },
479
+ },
422
480
  controller.signal, // AbortSignal
423
481
  );
424
482
  } catch (error) {
@@ -560,12 +618,8 @@ await pdfParser.dispose();
560
618
 
561
619
  ```typescript
562
620
  type PDFConvertOptions = {
563
- // OCR 전략 옵션
564
- strategySamplerModel?: LanguageModel; // OCR 전략 샘플링용 Vision LLM
565
- vlmProcessorModel?: LanguageModel; // 텍스트 보정용 Vision LLM
566
- vlmConcurrency?: number; // 병렬 페이지 처리 (기본값: 1)
567
- skipSampling?: boolean; // 전략 샘플링 건너뛰기
568
- forcedMethod?: 'ocrmac' | 'vlm'; // 특정 OCR 방식 강제
621
+ // Docling 이후 필수 보정
622
+ correction: PDFCorrectionOptions;
569
623
 
570
624
  // 이미지 PDF 옵션
571
625
  forceImagePdf?: boolean; // 이미지 기반 PDF 사전 변환 강제
@@ -578,8 +632,7 @@ type PDFConvertOptions = {
578
632
  document_timeout?: number; // 문서 처리 타임아웃 (초)
579
633
  documentValidationModel?: LanguageModel; // 문서 유형 검증용 LLM
580
634
 
581
- // Review Assistance
582
- reviewAssistance?: boolean | ReviewAssistanceOptions; // 선택적 page-level review
635
+ // 보정 진행 상황
583
636
  onReviewAssistanceProgress?: (event: ReviewAssistanceProgressEvent) => void; // 진행 상황 callback
584
637
 
585
638
  // 청크 변환 (대용량 PDF)
@@ -594,27 +647,47 @@ type PDFConvertOptions = {
594
647
  };
595
648
  ```
596
649
 
597
- ### ReviewAssistanceOptions
650
+ ### PDFCorrectionOptions
598
651
 
599
652
  ```typescript
600
- interface ReviewAssistanceOptions {
601
- enabled?: boolean; // page-level review assistance 활성화
653
+ interface PDFCorrectionOptions {
654
+ models: {
655
+ textCorrection: LanguageModel; // 필수: 페이지 텍스트와 표 셀 OCR 보정
656
+ pageGate: LanguageModel; // 필수: 구조 Review Assistance page gate
657
+ reviewAssistance: LanguageModel; // 필수: 기본 구조 review 모델
658
+ tableCorrection?: LanguageModel; // 선택: 표 전용 보정 override
659
+ reviewAssistanceTasks?: Partial<
660
+ Record<
661
+ | 'text_ocr_hanja'
662
+ | 'text_integrity'
663
+ | 'text_role_footnote'
664
+ | 'tables'
665
+ | 'pictures_captions'
666
+ | 'layout_bbox_order',
667
+ LanguageModel
668
+ >
669
+ >;
670
+ };
671
+ concurrency?: {
672
+ pages?: number; // 페이지 단위 text correction/page gate 동시성
673
+ reviewTasks?: number; // 구조 Review Assistance work-item 동시성
674
+ tables?: number; // 표 전용 보정 동시성
675
+ };
676
+ maxRetries?: {
677
+ textCorrection?: number;
678
+ pageGate?: number;
679
+ reviewAssistance?: number;
680
+ tableCorrection?: number;
681
+ };
682
+ localModelConcurrency?: number; // 로컬 모델 요청 동시성 상한
683
+ workItemTimeoutMs?: number; // work item별 timeout
684
+ outputLanguage?: string; // 사람이 읽는 review reason 언어 (기본값: en-US)
685
+ pageGate?: {
686
+ structuralNoiseThreshold?: number;
687
+ };
602
688
  autoApplyThreshold?: number; // 자동 적용 최소 confidence (기본값: 0.85)
603
689
  proposalThreshold?: number; // sidecar proposal 최소 confidence (기본값: 0.5)
604
- maxRetries?: number; // page-level VLM 호출별 최대 재시도 횟수 (기본값: 3)
605
690
  temperature?: number; // VLM 생성 temperature (기본값: 0)
606
- outputLanguage?: string; // 사람이 읽는 review reason 언어 (기본값: en-US)
607
- }
608
- ```
609
-
610
- ### ConvertWithStrategyResult
611
-
612
- ```typescript
613
- interface ConvertWithStrategyResult {
614
- /** 결정된 OCR 전략 */
615
- strategy: OcrStrategy;
616
- /** 샘플링 및/또는 VLM 처리의 토큰 사용량 리포트 (LLM 사용이 없으면 null) */
617
- tokenUsageReport: TokenUsageReport | null;
618
691
  }
619
692
  ```
620
693