From 16ddb1dfc32edf45a190853011108768f6c9937a Mon Sep 17 00:00:00 2001 From: altair823 Date: Wed, 3 Jun 2026 04:59:23 +0000 Subject: [PATCH] =?UTF-8?q?docs:=20arctic=20=EC=9E=84=EB=B2=A0=EB=8D=94=20?= =?UTF-8?q?=EB=AC=B8=EC=84=9C=20=EB=8F=99=EA=B8=B0=ED=99=94=20(README/ARCH?= =?UTF-8?q?ITECTURE/HANDOFF/HOTFIXES)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit README Configuration: provider candle/ollama + arctic 모델(candle CLS / ollama 태그) + endpoint + e5→arctic cascade 경고. ARCHITECTURE: 백엔드 그래프 노드(embedollama) + 임베딩 백엔드 결정표(채택 근거 측정 recall@10 130) + 디렉토리 트리. HANDOFF 1줄. HOTFIXES 2026-06-03 arctic dated entry(레지스트리/pooling/prefix/cascade + 수동 cosine 0.999984 실측 결과). Co-Authored-By: Claude Opus 4.8 (1M context) --- HANDOFF.md | 1 + README.md | 34 +++++++++++++++++++++++++++--- docs/ARCHITECTURE.md | 28 ++++++++++++++++++++++--- tasks/HOTFIXES.md | 50 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 6 deletions(-) diff --git a/HANDOFF.md b/HANDOFF.md index 1d9142c..977d949 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -35,6 +35,7 @@ P0~P5 직렬. P6~P9 P5 이후 병렬 가능. 머지 후 발견된 모든 deviation / hotfix 의 dated 로그는 [tasks/HOTFIXES.md](tasks/HOTFIXES.md). 본 요약은 \"누군가가 인수받을 때 알아두면 시간을 많이 절약하는\" 항목만: +- **2026-06-03 arctic-embed-l-v2.0 임베더 통합** — v0.26.0. 별칭 제거 후 설명형 query recall 보강(측정 recall@10 130/132, e5 +7). `kebab-embed-candle` 모델 레지스트리화(e5 mean + `snowflake-arctic-embed-l-v2.0` CLS, 모델별 pooling/prefix) + 신규 `kebab-embed-ollama`(`provider="ollama"`, `/api/embed`). config `endpoint: Option` 추가. 기본 e5 유지(opt-in), arctic 전환은 embedding_version cascade → 재색인. candle↔Ollama cosine>0.99 게이트로 pooling/prefix 정확성 고정(`#[ignore]`). 자세한 내용: `tasks/HOTFIXES.md` (2026-06-03 arctic), spec `docs/superpowers/specs/2026-06-03-arctic-embedder-spec.md`. - **2026-06-03 doc-side expansion(별칭) 기능 완전 제거** — v0.25.0. 아래 2026-05-31 항목의 색인-시 청크당 LLM 별칭 생성 + 별칭 검색 채널을 **전부 제거**(ROI 음수: cross-lingual 은 e5-large 단독으로 충분, 기여는 설명형 +2 그룹뿐인데 대가가 청크당 색인-시 LLM). `Chunk.aliases`/`expansion.rs`/`IngestExpansionCfg`/alias lexical arm/`expansion_progress` wire kind 제거, 신규 마이그레이션 **V013** 이 `chunk_aliases_fts`+`chunks.aliases` DROP. 별칭 default-off 였어 사용자 체감 0, 기존 KB 도 재색인 불요(잔존 별칭 벡터는 `strip_alias_suffix` graceful 매핑/`reset` 정리). `AssetTimings.expansion_ms` 는 wire 호환 위해 값 0 으로 유지. 자세한 내용: `tasks/HOTFIXES.md` (2026-06-03), spec `docs/superpowers/specs/2026-06-03-remove-doc-expansion-spec.md`. - **2026-05-31 Phase 2 doc-side expansion 별칭(개별 dense 벡터) + 파생물 캐시(V012)** — v0.21.0 cut. 색인 시 LLM 이 청크별 별칭("같은 의미 다른 표현")을 생성, 줄별 **개별 dense 벡터**(sentinel `{chunk}#alias#N`)로 색인 (묶음 1벡터는 평균화 희석으로 회귀 → 폐기) + boilerplate 청크 skip. `[ingest.expansion]` default off. 측정(나무위키 ~1000 문서 CS corpus): 변형 일관성 14/18 → **16/18**, spread 0.222→0.111, 대조군 false-positive 별칭 무죄. 비용 병목(별칭 18문서 2.5h)은 **파생물 캐시(V012, 청크 내용 해시 키)**로 해소 — 정답 3개 cold 1879s → warm 13s **≈ 145배**, embedding+별칭 LLM 캐싱, version_key cascade 정합. search/ask 가 `kebab.sqlite`+`lancedb` 만으로 동작 → 외부 서버 색인 후 DB 만 복사하는 이식 워크플로 가능. **결정/known limitation**: grounded/refusal 판정이 부분 인용을 grounded 로 오분류(정직한 거부가 false-positive 로 집계) — 별도 개선 후보. stack·svm 설명형 2개 잔존. 자세한 내용: `tasks/HOTFIXES.md` (2026-05-31), 측정: `docs/superpowers/handoffs/2026-05-31-namu-wiki-alias-cache-study.md`. - **2026-05-29 v0.20.2 dogfood findings + 검색 품질 baseline** — 8-finding 라운드 완료. (1) Ask 응답언어: rag-v3 default (질문 언어 = 답변 언어). (2) eval `--config` facade 패치 로 dogfood KB 직접 eval 가능. (3) 검색 품질 baseline — hybrid hit@3=1.0 / MRR=0.833, lexical hit@3=1.0 / MRR=0.7 (golden 10 query). **O-2 known limitation**: 소형 모델(gemma4:e4b) refusal 메시지의 query 언어 불일치 가능 — 판정은 정상, 표시 문구만 해당. 자세한 내용: `tasks/HOTFIXES.md` (2026-05-29). diff --git a/README.md b/README.md index df49cb1..91605e0 100644 --- a/README.md +++ b/README.md @@ -111,18 +111,46 @@ root = "~/KnowledgeBase" # 색인할 폴더. 절대 / tilde / env / 상대 경 [models.embedding] provider = "fastembed" # "fastembed"(기본, onnxruntime) / "candle"(순수 Rust) - # / "none"(lexical-only). candle 는 같은 모델·같은 벡터를 - # 순수 Rust 로 돌려 NUMA 서버의 onnxruntime 48-스레드 - # double-free 를 피하는 opt-in 백엔드 (재색인 불필요). + # / "ollama"(원격 HTTP) / "none"(lexical-only). + # candle 는 같은 모델·같은 벡터를 순수 Rust 로 돌려 + # NUMA 서버의 onnxruntime 48-스레드 double-free 를 피하는 + # opt-in 백엔드 (e5 는 재색인 불필요). model = "multilingual-e5-large" # 다국어 sentence embedding (1024-dim). # 첫 ingest 시 ONNX (~1.3GB) 자동 다운로드. # candle provider 는 safetensors (~2GB) 다운로드. + # candle/ollama 는 "snowflake-arctic-embed-l-v2.0" + # (설명형 query 의 recall 보강) 도 지원 — 아래 참고. dimensions = 1024 # config 와 LanceDB stored dim 불일치 시 검색 0건. num_threads = 0 # candle 전용 CPU 스레드 캡 (0=auto=#cores). # env KEBAB_EMBED_THREADS 가 우선. NUMA 노드 바인딩은 # numactl 과 조합. fastembed provider 는 무시. +# endpoint = "http://127.0.0.1:11434" # provider="ollama" 전용 HTTP endpoint. + # 생략 시 [models.llm].endpoint 로 폴백. + # fastembed/candle provider 는 무시. ``` +**arctic-embed-l-v2.0 (설명형 query recall 보강)**: 기본 e5-large 대신 +Snowflake `arctic-embed-l-v2.0` 임베더를 쓸 수 있다 (1024-dim, opt-in). 측정에서 +설명형/약어/영문 용어 query 의 recall@10 이 e5 대비 향상됐다. 두 경로: + +```toml +# (A) candle 백엔드 — 순수 Rust, in-process (NUMA 안전, Metal GPU 가능): +[models.embedding] +provider = "candle" +model = "snowflake-arctic-embed-l-v2.0" # CLS pooling, query 에 "query: " 접두어 + # (문서는 무접두어). safetensors ~2GB 다운로드. + +# (B) ollama 백엔드 — 원격/로컬 Ollama 데몬에 위임 (POST /api/embed): +[models.embedding] +provider = "ollama" +model = "snowflake-arctic-embed2" # Ollama 모델 태그 (ollama pull 필요) +endpoint = "http://127.0.0.1:11434" # 생략 시 [models.llm].endpoint +``` + +> ⚠️ e5 → arctic 전환은 `embedding_version` cascade 를 트리거한다 (모델이 다르면 +> 벡터도 다름). 기존 e5 KB 와 혼용 불가 — 전환 시 **재색인** 필요 (`kebab reset` +> 후 재 ingest). 기본값은 e5 라 기존 사용자는 영향 없음. + **Apple Silicon GPU 가속 (candle / macOS)**: M-시리즈 맥에서 candle 임베딩을 GPU(Metal)로 돌리면 CPU 대비 대용량 ingest 가 크게 빨라진다. 빌드 또는 설치 시 `embed_metal` feature 를 켠다: diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 4bc23dc..7b5b70e 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -16,7 +16,7 @@ Cargo workspace, 함수 호출 기반 모듈러 모놀리스. UI binary (`kebab- | metadata | SQLite + FTS5 (lexical search + v0.20.1 한국어 형태소 tokenizer via lindera-ko-dic) | | vector | LanceDB (embedded, model 별 분리 table) | | Markdown parser | `pulldown-cmark`. frontmatter 에 title 없으면 첫 H1 → H2 → 첫 paragraph 80 자 → 파일명 순으로 자동 채움 (`parser_version = md-frontmatter-v2`, 기존 doc 도 다음 ingest 에서 갱신) | -| embedding | `fastembed-rs` (`multilingual-e5-large`, 1024d, v0.18.0부터 default 업그레이드) | +| embedding | `fastembed-rs` (`multilingual-e5-large`, 1024d, v0.18.0부터 default 업그레이드). opt-in 대안: candle (e5 또는 `snowflake-arctic-embed-l-v2.0`) / Ollama `/api/embed`. arctic = 설명형 query recall 보강 (v0.26.0, 아래 결정표) | | 한국어 형태소분석 | `lindera-ko-dic` (FTS5 외부 tokenizer, v0.20.1) — 2자 이상 한국어 query 지원 | | LLM | Ollama HTTP (default `gemma4:e4b` ─ OCR / caption 와 family 통일. 사용자가 더 큰 variant `gemma4:26b` 등으로 override 가능) | | 음성 ASR | `whisper.cpp` (via `whisper-rs`) — P8 보류, 시스템 dep brainstorm 후 | @@ -67,7 +67,8 @@ flowchart TB subgraph Adapters ["traits + adapters"] embed["kebab-embed
(trait)"] embedlocal["kebab-embed-local
(fastembed, default)"] - embedcandle["kebab-embed-candle
(candle, NUMA-safe opt-in)"] + embedcandle["kebab-embed-candle
(candle, e5+arctic, NUMA-safe opt-in)"] + embedollama["kebab-embed-ollama
(Ollama /api/embed, opt-in)"] llm["kebab-llm
(trait)"] llmlocal["kebab-llm-local
(Ollama)"] search["kebab-search"] @@ -94,6 +95,7 @@ flowchart TB app --> vector app --> embedlocal app --> embedcandle + app --> embedollama app --> llmlocal app --> search app --> rag @@ -108,6 +110,8 @@ flowchart TB embedlocal --> embed embedcandle --> core embedcandle --> config + embedollama --> core + embedollama --> config llmlocal --> llm rag --> search rag --> llm @@ -136,6 +140,23 @@ UI → store/llm/parse 직접 의존 금지. 모든 user-facing 진입은 `kebab `kebab-parse-code` 의 외부 tree-sitter grammar crate 의존: P10-1A-2 에서 `tree-sitter-rust` 추가, P10-1B 에서 `tree-sitter-python` / `tree-sitter-typescript` / `tree-sitter-javascript` 추가, P10-1C-Go 에서 `tree-sitter-go` 추가, P10-1C-JK 에서 `tree-sitter-java` / `tree-sitter-kotlin-ng` 추가, P10-1D 에서 `tree-sitter-c` / `tree-sitter-cpp` 추가. 모두 `kebab-parse-code` 에만 격리 (facade 룰 — UI crate / chunker 가 직접 import 금지). Kotlin 은 `tree-sitter-kotlin-ng` 사용 (bare `tree-sitter-kotlin` 은 tree-sitter 0.21–0.23 에 고착 — 사용 불가). v0.18.0+ 부터 `kebab-source-fs` 는 자체 `code_meta` 모듈 (lang detect + skip helpers + BUILTIN_BLACKLIST) 을 보유, kebab-parse-code 와 분리 (refactor 2026-05-26). v0.19.0 부터 `kebab-parse-md` 가 `kebab-parse-types` (parser intermediate types) + `kebab-normalize` (CanonicalDocument lift) 두 crate 를 흡수 — 24 → 22 crates, design §3.7b 재작성 (HOTFIXES 2026-05-26). v0.20.1 부터 `kebab-search` 가 `lindera-ko-dic` 를 의존해 한국어 FTS5 형태소 tokenizer 지원 — V009 migration 으로 2자 이상 한국어 query 매칭 (Bug #8 closure). +### 임베딩 백엔드 결정표 (v0.26.0) + +| provider | 모델 | pooling / prefix | 위치 | 언제 | +|---|---|---|---|---| +| `fastembed` (기본) | `multilingual-e5-large` | mean / `query:`·`passage:` | in-process (onnxruntime) | 기본. 단일 소켓 호스트 | +| `candle` | e5 또는 `snowflake-arctic-embed-l-v2.0` | 모델별 (e5=mean, arctic=CLS) / arctic=`query:`·무접두어 | in-process (pure Rust) | NUMA 서버 (onnxruntime 48-스레드 double-free 회피), Apple Silicon Metal GPU | +| `ollama` | `snowflake-arctic-embed2` 등 | 모델 태그로 추론 / arctic=`query:`·무접두어 | 원격 HTTP (`/api/embed`) | candle 폴백, 측정에 쓴 경로 그대로 재현 | + +**arctic-embed-l-v2.0 채택 근거**: 별칭(doc-side expansion) 제거(v0.25.0) 후 설명형 +query 의 recall 보강책. 측정(`/build/dogfood/logs/2026-06-03-method-measurements.md`)에서 +arctic = recall@10 130/132 (e5 대비 +7, 색인 1회·per-query 0·LLM 0, 용어 무손실). +candle 이 주 백엔드(in-process, NUMA 안전), Ollama 가 폴백(측정 경로 재현). 두 경로의 +pooling/prefix 정확성은 `kebab-embed-candle/tests/arctic_ollama_parity.rs` +(candle arctic vs Ollama arctic 코사인>0.99, `#[ignore]`) 로 고정. e5 → arctic 전환은 +`embedding_version` cascade (모델별 벡터 상이) → 재색인 필요. 기본값 e5 유지라 기존 +사용자 무영향. 자세한 내용: [tasks/HOTFIXES.md](../tasks/HOTFIXES.md) 2026-06-03 arctic entry. + ## 디렉토리 구조 ```text @@ -184,7 +205,8 @@ kebab/ │ ├── kebab-store-sqlite/ # SQLite + FTS5 (V001/V002/V003) (P1-6, P2-1, P3-3). src/derivation_cache.rs = derivation_cache 테이블 저장소 (V012, v0.21.0) │ ├── kebab-search/ # Lexical + Vector + Hybrid retriever (P2-2, P3-4) │ ├── kebab-embed/ kebab-embed-local/ # Embedder trait + fastembed adapter (P3-1, P3-2) -│ ├── kebab-embed-candle/ # candle (pure-Rust) Embedder, NUMA-safe opt-in provider=candle (Track 1, v0.22.0) +│ ├── kebab-embed-candle/ # candle (pure-Rust) Embedder, 모델 레지스트리(e5 mean + arctic CLS), NUMA-safe opt-in provider=candle (Track 1, v0.22.0; arctic v0.26.0) +│ ├── kebab-embed-ollama/ # Ollama /api/embed Embedder, opt-in provider=ollama (arctic 폴백 경로, v0.26.0) │ ├── kebab-store-vector/ # LanceDB VectorStore (P3-3, P7-3 follow-up) │ ├── kebab-llm/ kebab-llm-local/ # LanguageModel trait + Ollama adapter (P4-1, P4-2) │ ├── kebab-rag/ # RAG pipeline (P4-3) diff --git a/tasks/HOTFIXES.md b/tasks/HOTFIXES.md index 151338f..c594f27 100644 --- a/tasks/HOTFIXES.md +++ b/tasks/HOTFIXES.md @@ -14,6 +14,56 @@ historical contract that was implemented; this file accumulates the deltas so phase 5+ readers can find the live behavior without diffing git history. +## 2026-06-03 — arctic-embed-l-v2.0 임베더 통합 (candle + Ollama) (v0.26.0) + +**무엇을 왜 추가했나.** 별칭(doc-side expansion) 제거(v0.25.0) 후 설명형 query 의 +recall 보강책으로 `snowflake-arctic-embed-l-v2.0` 임베더를 두 백엔드로 통합했다. +근거는 방법별 측정(`/build/dogfood/logs/2026-06-03-method-measurements.md`): +arctic = recall@10 **130/132**, recall@50 **132/132**, **용어 무손실**(syn/abbr/en +유지). e5-large 대비 +7, 색인 1회·per-query 0·LLM 0 = 살아있는 KB 에 지속 가능. +별칭이 청크당 색인-시 LLM(나무위키 18문서 cold 2.5h)을 요구한 것과 대조. + +**무엇을 건드렸나.** +- `kebab-embed-candle`: e5 하드코딩(`HF_MODEL`/`SUPPORTED_MODEL`/mean/`query:`+`passage:`)을 + **모델 레지스트리**(`MODEL_REGISTRY`: `EmbedModelSpec { name, hf_repo, pooling, query_prefix, doc_prefix, dim, version_tag }`)로 + 일반화. e5(mean, `query:`/`passage:`) + arctic(**CLS**, `query:`/무접두어). pooling + 은 모델별 분기(mean=attention-mask-weighted / CLS=`hidden[:,0,:]`), tokenize/forward/L2 + 공유. arctic pooling=CLS 는 HF `1_Pooling/config.json`(`pooling_mode_cls_token:true`)로 + 확인. `model_version` 은 arctic 일 때 `+arctic-cls` 태그(switch 시 embedding_version + cascade 트리거); e5 는 fastembed-e5 와의 호환(NUMA 드롭인) 위해 plain `config.version` 유지. +- `kebab-embed-ollama` (신규 크레이트): `Embedder` 구현, `reqwest::blocking` POST + `/api/embed` `{model, input:[...]}` → `embeddings`. batch 48 + fail-soft 재시도 3, + 결과 **L2 정규화**(Ollama raw 반환), dim 검증, query/doc prefix 모델 태그로 추론 + (`arctic-embed`→`query:`/무접두어, `e5`→`query:`/`passage:`). `model_version=ollama:{model}`. + endpoint = `models.embedding.endpoint` ?? `models.llm.endpoint`. +- `kebab-config`: `EmbeddingModelCfg.endpoint: Option`(serde default, ollama용) + + `provider` 문서에 `ollama` 추가 + env `KEBAB_MODELS_EMBEDDING_ENDPOINT`. +- `kebab-app::embedder()`: provider match 에 `ollama` 분기 추가(facade 경유). +- workspace member += `kebab-embed-ollama`, version 0.25.0 → **0.26.0**(minor). + +**correctness 게이트.** candle arctic 임베딩이 측정에 쓴 Ollama `snowflake-arctic-embed2` +임베딩과 일치해야 pooling/prefix 정확성(=recall 130 재현)이 보장된다. 검증: +`kebab-embed-candle/tests/arctic_ollama_parity.rs`(`#[ignore]`, live Ollama 의존) 가 +candle arctic vs 우리 Ollama 어댑터로 같은 문장(설명형/약어/영문 포함, doc+query +양 경로)을 임베딩해 per-sentence **코사인 > 0.99** 를 assert. 수동 실행 결과(코사인값)는 +릴리스 전 본 entry 에 기록. + +**수동 검증 결과** (2026-06-03 worker 실측, Ollama @192.168.0.47:11434 +`snowflake-arctic-embed2`): 8문장 × (doc+query) 16벡터 per-sentence 코사인 +**0.999984 ~ 0.999995**, `cosine_min = 0.999984` (게이트 0.99 대비 대폭 상회). +설명형("후입선출 방식으로 동작하는 자료구조")·약어("SVM 은 support vector machine")· +영문·한글 모두 일치. → candle arctic 의 CLS pooling + `query: ` prefix 가 Ollama 측정 +경로와 정확히 동일 = recall@10 130 재현 보장. Ollama raw 도 이미 L2-정규화(norm 1.0)라 +어댑터의 L2 정규화는 idempotent no-op. 로그: `/build/dogfood/logs/arctic-parity.log`, +요약: `/tmp/arctic-result.md`. + +**호환성.** 기본 provider=fastembed e5 동작/벡터 불변(arctic 은 opt-in). dim 1024 +동일이나 LanceDB 테이블명에 모델명 포함(`chunk_embeddings_{model}_{dim}`)이라 충돌 +없음. e5 → arctic 전환 = `embedding_version` cascade(모델별 벡터 상이) → **재색인 필요** +(기존 e5 KB 와 혼용 불가, 명확). A(heading enrichment)는 측정상 arctic 에서 악화 → +미적용. spec: `docs/superpowers/specs/2026-06-03-arctic-embedder-spec.md`, plan: 동일 +디렉토리 `2026-06-03-arctic-embedder-plan.md`. + ## 2026-06-03 — doc-side expansion(별칭) 기능 완전 제거 (v0.25.0) **무엇을 왜 제거했나.** v0.21.0 (PR #195/#196) 에서 도입한 색인-시 청크당 LLM