Files
kebab/docs/components/embed
altair823 5e6d977281 docs: CLAUDE 3구역 정책 + DOCS.md 지도 + component README task-spec 링크 정리
doc-reorg(C) Phase 4b+5:
- CLAUDE.md: doc-list 에 DOCS.md/CHANGELOG 추가 + zones(living/계약/증거) 정책 +
  "새 plan/handoff/per-feature spec 누적 금지(결정→HOTFIXES, 구조→ARCHITECTURE,
  릴리스→CHANGELOG)" 명문화. §Spec contract 재작성(task spec 삭제 반영: 유일
  frozen 계약 = 설계 doc, 구현 진실 = 코드). stale per-task-spec 참조 정정.
- docs/components/*/README.md (12): 삭제될 tasks/p*/ task-spec markdown 링크
  제거/plain-text 화 + 흡수된 crate(kebab-normalize→parse-md::normalize,
  kebab-parse-types→parse-md::types) 참조 정정.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Mc6W1fgsrbFKTsqA6P8La
2026-06-27 12:46:29 +00:00
..

Embed

Chunk text → 단위 정규화된 벡터. trait + impl 패턴으로 future swap (candle / ollama-embed) 가능.

구성 crate

Crate 역할
kebab-embed-local FastembedEmbedder — fastembed-rs 위 ONNX-backed local 임베더. default multilingual-e5-small 384d.

구조

classDiagram
    class Embedder {
        <<trait kebab-core>>
        model_id() EmbeddingModelId
        model_version() EmbeddingVersion
        dimensions() usize
        embed(inputs) Vec~Vec~f32~~
    }
    class EmbeddingInput {
        text: &str
        kind: EmbeddingKind
    }
    class EmbeddingKind {
        <<enum>>
        Document
        Query
    }
    class FastembedEmbedder {
        +new(config) Result~Self~
        -inner: Mutex~TextEmbedding~
        -model_id, version, dimensions, batch_size
    }
    class MockEmbedder {
        feature = "mock"
        deterministic test double
    }
    Embedder <|.. FastembedEmbedder
    Embedder <|.. MockEmbedder
    Embedder ..> EmbeddingInput
    EmbeddingInput ..> EmbeddingKind

Data flow

flowchart LR
    Chunks["Vec~Chunk~<br/>(kebab-chunk)"]
    Inputs["EmbeddingInput<br/>{text, kind}"]
    Prefix["E5 prefix<br/>Document → 'passage: '<br/>Query → 'query: '"]
    Batch["batch by config.batch_size"]
    Onnx["fastembed TextEmbedding<br/>(ONNX session, Mutex)"]
    L2["L2 정규화<br/>(fastembed 내장)"]
    Vec["Vec~Vec~f32~~<br/>unit norm, finite"]
    Inputs --> Prefix --> Batch --> Onnx --> L2 --> Vec
    Chunks -.text.-> Inputs
    Query["사용자 query string<br/>(kebab-search)"] -.text+Query.-> Inputs
    Vec --> VStore["kebab-store-vector"]
    Vec --> Search["kebab-search<br/>(query 경로)"]

주요 type / trait / 함수

Trait (kebab-core):

  • Embedder::embed(&self, inputs: &[EmbeddingInput<'_>]) -> Result<Vec<Vec<f32>>> — 출력 shape inputs.len() × dimensions(). 결과 벡터 모두 L2 = 1 + finite.
  • EmbeddingInput { text: &str, kind: EmbeddingKind } — kind = Document / Query (E5 prefix 분기).
  • EmbeddingModelId(String), EmbeddingVersion(String)model_id × version × dim 으로 vector store 테이블 분리.

FastembedEmbedder (kebab-embed-local):

  • FastembedEmbedder::new(config: &kebab_config::Config) -> Result<Self> — 모델 파일 캐시 위치 = {model_dir}/fastembed/. 첫 호출 시 ONNX + tokenizer 다운로드. config.models.embedding.dimensions 가 실제 모델 차원과 다르면 즉시 Err (런타임 silent mismatch 회피).
  • Mutex<TextEmbedding> 으로 inner 세션 직렬화 — fastembed 4.9 가 &self 지만 보수적 lock. kebab-app 의 indexer 가 어차피 순차 batch 라 contention 없음.
  • E5 prefix 자동 적용: Document"passage: ", Query"query: " (§11.3).
  • L2 정규화 = fastembed 내장 (transformer_with_precedence). 별도 정규화 안 함, 단 assert_unit_norm 테스트로 invariant pin.

테스트 도구 (kebab-core, feature mock):

  • assert_vector_shape(&[Vec<f32>], expected_dims) — 길이 + finite 검증.
  • assert_unit_norm(&[Vec<f32>], tolerance) — L2 norm 이 1.0 ± tolerance. f32 384d 권장 tol = 5e-4.
  • MockEmbedder (feature mock, default OFF) — 테스트용 deterministic double. 실 어댑터는 kebab-embed-local 또는 future P+ adapter 가 담당.

외부 의존

  • kebab-embed-localkebab-core + kebab-config, fastembed, anyhow.
  • 외부 lib: fastembed-rs (ONNX wrapper, Hugging Face 모델 다운로드 포함). 로컬 ORT runtime.
  • 외부 서비스: 첫 호출 시 모델 다운로드 (Hugging Face). 그 후 오프라인.

핵심 결정

  • Embedder trait + 테스트 도구가 kebab-core 에 직접 거주 (mock feature). : kebab-store-vector, kebab-search 등 downstream 은 use kebab_core::Embedder 로 의존 — 별도 re-export shim 불필요. MockEmbedder / assert_vector_shape / assert_unit_norm 은 default-OFF mock feature 뒤에 둠. 과거의 순수 facade kebab-embedkebab-core 로 fold-in 되어 삭제됨 (crate 그래프는 docs/ARCHITECTURE.md 참조). 어댑터 코드는 kebab-embed-local 또는 future kebab-embed-<provider> 로.

  • multilingual-e5-small 384d default. : 한국어 + 영어 동시 강함, ONNX 작음 (~120MB), 384d 가 retrieval 정확도/저장 비용 균형 좋음. e5 prefix 컨벤션 ("passage: " / "query: ") 으로 같은 모델이 doc + query 두 모드 cover.

  • L2 정규화 = fastembed 내장에 위임. : fastembed 4.x 가 transformer_with_precedence 에서 이미 L2. 두 번 정규화 = 비용 + numerical drift. invariant 가 깨지면 assert_unit_norm 테스트가 즉시 실패 — fastembed 가 default 바꾸면 회귀 잡힘.

  • Mutex<TextEmbedding> 보수적 직렬화. : fastembed &self API 라 in principle 병렬 가능, 그러나 ORT Session 의 thread-safety 가 backend 별로 다름. indexer 가 어차피 순차 batch 라 contention 없음. profiling 에서 병목 보이면 그때 풀음.

  • dim mismatch = 생성자에서 즉시 fail. : config.models.embedding.dimensions = 384 가 실제 모델 차원과 다르면 첫 embed 호출에서야 발견 → 운영 시 ingest 절반 진행 후 죽음. 생성자에서 검증 = early exit, 사용자가 즉시 config 수정.

  • 모델 캐시 = {model_dir}/fastembed/ 고정 서브디렉토리. : spec literal. model_dir{data_dir}/models default → 사용자가 한 곳에 모든 모델 캐시. fastembed 외 어댑터 (candle / ggml / ...) 는 자기 서브디렉토리 사용해서 충돌 회피.

  • MockEmbedder feature gate (default OFF). : production binary 가 mock 코드를 포함 안 함. test crate 가 features = ["mock"] 로 명시 opt-in.

관련 spec / HOTFIXES

  • frozen 설계 §7.1 (helper input types EmbeddingInput/EmbeddingKind), §7.2 (Embedder trait), §11.3 (E5 prefix), §6.4 (models.embedding), §9 (versioning): docs/superpowers/specs/2026-04-27-kebab-final-form-design.md
  • task specs: 삭제됨(2026-06-27 doc-reorg) — 설계는 frozen 계약, 동작은 tasks/HOTFIXES.md, 상세 git history.
  • HOTFIXES: 이 그룹은 머지 후 deviation 없음.