feat(chunk): pdf-page-v1.2 — PDF 페이지 oversize 분할 + 공유 oversize 모듈 #212

Merged
altair823 merged 1 commits from feat/pdf-oversize-split into main 2026-06-24 05:47:48 +00:00
Owner

요약

md-heading-v2(PR #209)의 예산 초과 청크 분할을 PDF 청커에도 적용한다. pdf-page-v1.1chunk_page 는 문장/문단 경계로만 잘라서, 경계 없는 거대 페이지(빽빽한 scanned page 가 한 줄로 OCR 된 경우)가 통째로 한 청크가 되어 strict 임베더(AMD Lemonade)에서 임베드 실패하는 hole 이 PDF 에 잔존했다. pdf-page-v1.2 는 2-tier 로 닫는다: tier-1(문장/문단 greedy + overlap) 후, segment 가 max_chunk_tokens(공유 config, default 4000) 초과면 tier-2 가 공유 text_pieces 로 재분할 → 모든 PDF 청크 ≤ 예산.

설계: docs/superpowers/plans/2026-06-24-pdf-page-v1.2-oversize-split.md

변경

  • 신규 공유 모듈 crate::oversize(text_pieces/char_pieces/BYTES_PER_TOKEN) — md 와 PDF 가 공유(단일 진실 공급원). md-heading-v2 는 호출만 하고 출력 byte-identical(md 라벨·동작 불변, parity 테스트 전부 통과 → md 버전 bump 없음).
  • PdfPageV1Chunker { max_chunk_tokens }(이전 unit struct) + policy_hash budget fold(md 동형) + pdf_chunker_from_config(kebab-app). 신규 config 키 없음(max_chunk_tokens 공유).
  • 분할 조각 chunk_id #c{segment_start}s{i}(미분할 단일 segment 는 bare #c{segment_start} → 공통 경우 hash 컴포넌트 v1.1 동일).
  • chunker_version pdf-page-v1.1pdf-page-v1.2 → 다음 plain ingest 에서 PDF 1회 자동 재청크(markdown/code 무영향).

비범위 / known

  • Page span: 분할 조각은 부모 segment 의 char 범위를 그대로 가짐(segment-granular, md 동형). 최초의 per-piece 정밀 narrowing 은 text_pieces 의 줄 구분자 소실로 drift 하는 버그라 코드 리뷰 후 제거(회귀 테스트로 잠금). citation 은 페이지 영역 단위(절대 drift 없음).
  • 버전 bump 은 follow-up #2/#3 와 함께 배치 릴리스에서 일괄.

검증

  • kebab-chunk lib 93 pass(oversize_pdf_page_splits, oversize_pdf_page_with_newlines_splits_without_span_drift(span drift 회귀), non_oversize_pdf_unchanged, policy_hash_matches_md_heading_v2_for_identical_policy_and_budget, budget_in_policy_hash, crate::oversize roundtrip 2종 + 기존 md parity 전부). kebab-app green. clippy -D warnings 0.
  • 독립 코드 리뷰: span-drift 버그 발견 → segment-granular 로 수정 → 재검증 후 나머지 6개 위험(md 추출 무변경/chunk_id 고유·결정성/budget bound/policy_hash fold/char 경계/tier 상호작용) 전부 PASS.
  • 도그푸딩(실험 KB, scanned PDF + paddle-onnx OCR + arctic@Lemonade, budget 200 으로 tier-2 강제): 625/625 errors=0, scanned_page1.pdf 1→3 청크(max 190)·scanned_page2.pdf 3→7 청크(max 197), 둘 다 pdf-page-v1.2 스탬프. 전 코퍼스 10215 청크 전부 ≤200, 초과 0.
## 요약 md-heading-v2(PR #209)의 예산 초과 청크 분할을 **PDF 청커에도** 적용한다. `pdf-page-v1.1` 의 `chunk_page` 는 문장/문단 경계로만 잘라서, 경계 없는 거대 페이지(빽빽한 scanned page 가 한 줄로 OCR 된 경우)가 통째로 한 청크가 되어 strict 임베더(AMD Lemonade)에서 임베드 실패하는 hole 이 PDF 에 잔존했다. `pdf-page-v1.2` 는 2-tier 로 닫는다: tier-1(문장/문단 greedy + overlap) 후, segment 가 `max_chunk_tokens`(공유 config, default 4000) 초과면 tier-2 가 공유 `text_pieces` 로 재분할 → **모든 PDF 청크 ≤ 예산**. 설계: docs/superpowers/plans/2026-06-24-pdf-page-v1.2-oversize-split.md ## 변경 - 신규 공유 모듈 `crate::oversize`(`text_pieces`/`char_pieces`/`BYTES_PER_TOKEN`) — md 와 PDF 가 공유(단일 진실 공급원). md-heading-v2 는 호출만 하고 **출력 byte-identical**(md 라벨·동작 불변, parity 테스트 전부 통과 → md 버전 bump 없음). - `PdfPageV1Chunker { max_chunk_tokens }`(이전 unit struct) + `policy_hash` budget fold(md 동형) + `pdf_chunker_from_config`(kebab-app). 신규 config 키 없음(`max_chunk_tokens` 공유). - 분할 조각 chunk_id `#c{segment_start}s{i}`(미분할 단일 segment 는 bare `#c{segment_start}` → 공통 경우 hash 컴포넌트 v1.1 동일). - chunker_version `pdf-page-v1.1` → `pdf-page-v1.2` → 다음 plain ingest 에서 PDF 1회 자동 재청크(markdown/code 무영향). ## 비범위 / known - Page span: 분할 조각은 부모 segment 의 char 범위를 그대로 가짐(segment-granular, md 동형). 최초의 per-piece 정밀 narrowing 은 `text_pieces` 의 줄 구분자 소실로 drift 하는 버그라 코드 리뷰 후 제거(회귀 테스트로 잠금). citation 은 페이지 영역 단위(절대 drift 없음). - 버전 bump 은 follow-up #2/#3 와 함께 배치 릴리스에서 일괄. ## 검증 - kebab-chunk lib 93 pass(`oversize_pdf_page_splits`, `oversize_pdf_page_with_newlines_splits_without_span_drift`(span drift 회귀), `non_oversize_pdf_unchanged`, `policy_hash_matches_md_heading_v2_for_identical_policy_and_budget`, `budget_in_policy_hash`, `crate::oversize` roundtrip 2종 + 기존 md parity 전부). kebab-app green. clippy `-D warnings` 0. - 독립 코드 리뷰: span-drift 버그 발견 → segment-granular 로 수정 → 재검증 후 나머지 6개 위험(md 추출 무변경/chunk_id 고유·결정성/budget bound/policy_hash fold/char 경계/tier 상호작용) 전부 PASS. - 도그푸딩(실험 KB, scanned PDF + paddle-onnx OCR + arctic@Lemonade, budget 200 으로 tier-2 강제): 625/625 errors=0, scanned_page1.pdf 1→3 청크(max 190)·scanned_page2.pdf 3→7 청크(max 197), 둘 다 `pdf-page-v1.2` 스탬프. 전 코퍼스 10215 청크 전부 ≤200, 초과 0.
altair823 added 1 commit 2026-06-24 05:47:17 +00:00
md-heading-v2(PR #209)의 oversize 분할을 PDF 청커에도 적용. v1.1 의 chunk_page 는
문장/문단 경계로만 잘라서 경계 없는 거대 페이지(빽빽한 scanned page 한 줄 OCR)가
통째로 한 청크 → strict 임베더 실패. v1.2 는 2-tier: tier-1(문장/문단 greedy +
overlap) 후 segment 가 max_chunk_tokens 초과면 tier-2 가 공유 text_pieces 로
재분할 → 모든 PDF 청크 ≤ 예산.

- 신규 공유 모듈 crate::oversize (text_pieces/char_pieces/BYTES_PER_TOKEN) — md 와
  PDF 가 공유(단일 진실 공급원). md-heading-v2 는 호출만, 출력 byte-identical(md
  라벨·동작 불변, parity 테스트 전부 통과).
- PdfPageV1Chunker { max_chunk_tokens } + policy_hash budget fold(md 동형) +
  pdf_chunker_from_config(kebab-app). 신규 config 키 없음.
- 분할 조각 chunk_id 는 #c{segment_start}s{i}(미분할 단일 segment 는 bare
  #c{segment_start} 유지 → 공통 경우 v1.1 동일).
- Page span: 분할 조각은 부모 segment 의 char 범위를 그대로 가짐(segment-granular,
  md 동형). per-piece narrowing 은 text_pieces 의 줄 구분자 소실로 drift 하는
  버그라 코드 리뷰 후 제거 — 회귀 테스트
  oversize_pdf_page_with_newlines_splits_without_span_drift 로 잠금.
- chunker_version v1.1→v1.2 → 다음 ingest 에서 PDF 1회 자동 재청크(md/code 무영향).

검증: kebab-chunk lib 93 pass(span 회귀 포함), kebab-app green, clippy 0. 도그푸딩
(실험 KB, scanned PDF + arctic@Lemonade, budget 200): 625/625 errors=0,
scanned_page1 1→3 청크·scanned_page2 3→7 청크(둘 다 pdf-page-v1.2), 전 코퍼스
10215 청크 전부 ≤200. 버전 bump 은 follow-up 들과 함께 배치 릴리스에서 일괄.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Mc6W1fgsrbFKTsqA6P8La
altair823 force-pushed feat/pdf-oversize-split from 069c1ae69e to ed8ab7cdbe 2026-06-24 05:47:17 +00:00 Compare
altair823 merged commit 3573cceb4e into main 2026-06-24 05:47:48 +00:00
altair823 deleted branch feat/pdf-oversize-split 2026-06-24 05:47:49 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: altair823-org/kebab#212