feat(chunk): md-heading-v2 — 예산 초과 청크 일반 분할 (oversize-chunk split)
v1 의 "블록 미분할" 한계를 일반화. 거대 list/code/table/paragraph 가 한 청크로
임베더 컨텍스트를 초과해 임베딩이 통째로 실패하던 문제를 해소한다. v2 는 v1 과
모든 출력이 동일하되, 청크의 실제 임베드 text 크기(`text.len()/3`)가
`max_chunk_tokens`(신규 config, byte/3, default 4000)를 넘는 청크만 줄(`\n`)
경계로, 단일 거대 줄은 UTF-8 char 경계로 잘라 각 조각이 예산 이하가 되게 한다.
- 판정 기준은 저장 `token_estimate` 가 아니라 실제 `text` 길이: ImageRef/AudioRef
청크는 image-only 규약으로 token_estimate=0 이지만 OCR/caption text 는 클 수
있다(빽빽한 스크린샷). 도그푸딩 일치 재테스트에서 이 image-OCR 구멍 발견·수정.
- 분할 조각 chunk_id 는 동일 block_ids 를 공유하므로 id-input 해시에 `#seg{i}`
접미사로 충돌 회피(저장 policy_hash 는 bare — pdf-page-v1 의 `#L` 레시피 동형).
- `max_chunk_tokens` 는 v2 의 policy_hash 에만 fold(공유 ChunkPolicy 미변경 →
코드/PDF 청커 cascade 무영향). 값 변경 시 markdown 만 재청크.
- 미분할 청크는 v1 과 byte-identical. `chunker_version` v1→v2 → 다음 plain
`kebab ingest` 에서 markdown 1회 자동 재청크(--force 불필요, 코드/PDF 무영향).
동기: strict 임베더(AMD Lemonade `/api/embed`)는 oversize 입력을 truncate 아닌
거부(`500 too large`) — ollama 가 조용히 truncate 하던 걸 청커가 애초에 안 만들게.
검증(실험 KB, arctic-embed-l-v2 @ Lemonade): v2 전 620 중 2 doc(거대 jira list
블록) 임베드 실패 → v2 후 620/620 errors=0, 7114 청크 전부 ≤ 4000. 사용자 실
config 일치 재테스트(이미지 OCR + PDF OCR paddle-onnx ON)에서 image-OCR 구멍
발견·수정 후 dense 이미지 OCR 텍스트가 budget 초과 시 분할(token_estimate=0 →
1청크였던 것이 실제 text 기준 다중 청크로) 실증. frozen 설계 doc / frozen p1-5
spec 미변경(설계 §9 가 md-heading-v2 라벨 bump 를 변경 메커니즘으로 명시).
known limitation: PDF 는 별도 청커 pdf-page-v1.1 이라 이 split 미적용(후속 후보).
Cargo.toml 0.29.0 → 0.30.0 (신규 config 키 + 청커 동작 변경 = pre-1.0 minor +
도그푸딩 트리거). docs cascade: HOTFIXES / release-notes-v0.30.0-draft /
plan(2026-06-24) / normalize-chunk README / README / SMOKE / HANDOFF.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Mc6W1fgsrbFKTsqA6P8La
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
| Crate | 역할 |
|
||||
|-------|------|
|
||||
| `kebab-normalize` | `ParsedBlock` (markdown only) → `CanonicalDocument` lift. NFC + heading-path ordinal + provenance 합성 + title fallback chain (p9-fb-07). |
|
||||
| `kebab-chunk` | `CanonicalDocument` → `Vec<Chunk>`. v1 두 변종: `md-heading-v1` (markdown + image), `pdf-page-v1` (PDF). |
|
||||
| `kebab-chunk` | `CanonicalDocument` → `Vec<Chunk>`. markdown 기본 `md-heading-v2` (v1 + 예산 초과 청크 일반 분할; v0.30.0), `pdf-page-v1` (PDF). `md-heading-v1` 은 historical 변종으로 잔존. |
|
||||
|
||||
## 구조
|
||||
|
||||
@@ -30,6 +30,11 @@ classDiagram
|
||||
BYTES_PER_TOKEN = 3
|
||||
POLICY_HASH_HEX_LEN = 16
|
||||
}
|
||||
class MdHeadingV2Chunker {
|
||||
VERSION = "md-heading-v2"
|
||||
max_chunk_tokens = 4000
|
||||
split_oversize_chunk(line→char)
|
||||
}
|
||||
class PdfPageV1Chunker {
|
||||
VERSION = "pdf-page-v1"
|
||||
BYTES_PER_TOKEN = 3
|
||||
@@ -42,11 +47,20 @@ classDiagram
|
||||
chunker_version
|
||||
}
|
||||
Chunker <|.. MdHeadingV1Chunker
|
||||
Chunker <|.. MdHeadingV2Chunker
|
||||
Chunker <|.. PdfPageV1Chunker
|
||||
MdHeadingV1Chunker ..> ChunkPolicy
|
||||
MdHeadingV2Chunker ..> ChunkPolicy
|
||||
PdfPageV1Chunker ..> ChunkPolicy
|
||||
```
|
||||
|
||||
`md-heading-v2` (기본, v0.30.0) 는 v1 과 모든 출력이 동일하되, 마지막에
|
||||
`token_estimate > max_chunk_tokens` 인 청크만 줄(`\n`) 경계로 — 단일 거대 줄은
|
||||
UTF-8 char 경계로 — 잘라 각 조각이 예산 이하가 되도록 한다. 분할 조각은 동일
|
||||
`block_ids` 를 공유하므로 chunk_id 충돌을 막기 위해 id-input 해시에 `#seg{i}`
|
||||
접미사를 붙이고(저장 `policy_hash` 는 bare), `max_chunk_tokens` 는 v2 의
|
||||
`policy_hash` 에 fold 된다(공유 `ChunkPolicy` 미변경). 분할 조각의
|
||||
`source_spans` 는 원 블록 범위를 그대로 보존(블록 단위 citation).
|
||||
|
||||
## Data flow
|
||||
|
||||
```mermaid
|
||||
|
||||
Reference in New Issue
Block a user