refactor(core): 빈 re-export shim crate kebab-embed/kebab-llm → kebab-core 흡수
kebab-embed/kebab-llm 은 "새 type 없음"을 자처한 순수 re-export 셸이었다 (trait 은 이미 kebab-core 소유, mock + test helper 만 보유). "kebab-core 재구성 시 안정 surface" 라는 명분은 1인 RAG 엔 speculative YAGNI. 흡수: - kebab-core 에 default-OFF `mock` feature + src/mock.rs (MockEmbedder, MockLanguageModel, assert_vector_shape/assert_unit_norm/assert_finish_chunk 을 kebab_core:: → crate:: import 만 바꿔 verbatim 이동). - production import 2곳(kebab-embed-local, kebab-llm-local) + test import 다수(search/rag/parse-image/embed-local) 를 kebab_core 로 repoint. mock 쓰는 crate 는 dev-dep 에 features=["mock"] (default 빌드 무영향). - shim 자체 테스트: mock 동작 테스트는 kebab-core/tests/ 로 이동, reexports.rs(셸 재수출 테스트)는 폐기. - crates/kebab-embed, crates/kebab-llm 삭제 + workspace member/deps 정리. - ARCHITECTURE/HANDOFF/component README 의 crate 그래프·표·rationale 갱신 (22 → 20 crates). llm-local 의 broken intra-doc link 2건도 정리. trait surface·동작 불변 (test-only + import-rename). workspace build 는 mock default-OFF 라 mock 코드 미컴파일. 적대적 검증 3렌즈(build-test-integrity + behavior-identity[mock byte-identical] + dead-crate-completeness) 통과, clippy --workspace -D warnings 클린. 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:
@@ -95,7 +95,7 @@ flowchart TB
|
||||
|
||||
- **새 미디어 타입 추가** (예: epub) — Parse → Normalize+Chunk → Store (chunker_version) → App facade (라우팅).
|
||||
- **새 retrieval 모드** — Search → App facade (mode dispatch) → UI (--mode flag).
|
||||
- **새 LLM 어댑터** — LLM (trait crate, 새 type 금지) + 새 `kebab-llm-<provider>` crate → App facade (config provider switch).
|
||||
- **새 LLM 어댑터** — `kebab-core` 의 `LanguageModel` trait 구현 + 새 `kebab-llm-<provider>` crate → App facade (config provider switch).
|
||||
- **TUI 신규 pane** — UI 만. Mode + Theme + InputBuffer 재사용.
|
||||
|
||||
## 다이어그램 제약
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
| Crate | 역할 |
|
||||
|-------|------|
|
||||
| `kebab-embed` | `Embedder` trait re-export + 테스트 도구 (`assert_vector_shape`, `assert_unit_norm`) + optional `MockEmbedder` (feature gated). 새 type 추가 **금지** — 순수 facade. |
|
||||
| `kebab-embed-local` | `FastembedEmbedder` — fastembed-rs 위 ONNX-backed local 임베더. default `multilingual-e5-small` 384d. |
|
||||
|
||||
## 구조
|
||||
@@ -64,7 +63,7 @@ flowchart LR
|
||||
|
||||
## 주요 type / trait / 함수
|
||||
|
||||
**Trait** (`kebab-core`, re-export `kebab-embed`):
|
||||
**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 테이블 분리.
|
||||
@@ -75,22 +74,21 @@ flowchart LR
|
||||
- E5 prefix 자동 적용: `Document` → `"passage: "`, `Query` → `"query: "` (§11.3).
|
||||
- L2 정규화 = fastembed 내장 (`transformer_with_precedence`). 별도 정규화 안 함, 단 `assert_unit_norm` 테스트로 invariant pin.
|
||||
|
||||
**테스트 도구** (`kebab-embed`):
|
||||
**테스트 도구** (`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` → `kebab-core` 만 (re-export crate).
|
||||
- `kebab-embed-local` → `kebab-embed` + `kebab-config`, `fastembed`, `anyhow`.
|
||||
- `kebab-embed-local` → `kebab-core` + `kebab-config`, `fastembed`, `anyhow`.
|
||||
- 외부 lib: `fastembed-rs` (ONNX wrapper, Hugging Face 모델 다운로드 포함). 로컬 ORT runtime.
|
||||
- 외부 서비스: 첫 호출 시 모델 다운로드 (Hugging Face). 그 후 오프라인.
|
||||
|
||||
## 핵심 결정
|
||||
|
||||
- **`kebab-embed` = trait re-export only, **새 type 금지****.
|
||||
**왜**: `kebab-store-vector`, `kebab-search` 등 downstream 이 `use kebab_embed::Embedder` 안정 surface 의존. `kebab-core` 재구성 시 trait 이동해도 downstream 안 깨짐. spec 가 명시 — 어댑터 코드는 `kebab-embed-local` 또는 future `kebab-embed-<provider>` 로.
|
||||
- **`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-embed` 는 `kebab-core` 로 fold-in 되어 삭제됨 (crate 그래프는 [`docs/ARCHITECTURE.md`](../../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.
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
| Crate | 역할 |
|
||||
|-------|------|
|
||||
| `kebab-llm` | `LanguageModel` trait re-export + `MockLanguageModel` (feature `mock`, default OFF). 새 type 추가 **금지** — 순수 facade. |
|
||||
| `kebab-llm-local` | `OllamaLanguageModel` — `reqwest::blocking` 기반 Ollama `POST /api/generate` 어댑터. line-delimited JSON streaming 디코드. |
|
||||
|
||||
## 구조
|
||||
@@ -86,7 +85,7 @@ flowchart LR
|
||||
|
||||
## 주요 type / trait / 함수
|
||||
|
||||
**Trait** (`kebab-core`, re-export `kebab-llm`):
|
||||
**Trait** (`kebab-core`):
|
||||
- `LanguageModel::model_ref() -> ModelRef` — provider/model/version 식별. `Answer.model_ref` 으로 흘려서 wire payload 가 자가 식별.
|
||||
- `LanguageModel::context_tokens() -> usize` — 모델 별 max prompt+completion 합. RAG 가 budget 계산에 사용.
|
||||
- `LanguageModel::generate_stream(req: GenerateRequest) -> Result<Box<dyn Iterator<Item = Result<TokenChunk>> + Send>>` — async 안 됨, 매 next() 가 blocking. 모든 stream 이 마지막에 `TokenChunk::Done` 으로 끝남 (error 케이스 포함, §0 Q5).
|
||||
@@ -106,20 +105,19 @@ flowchart LR
|
||||
**`LlmError`** (`kebab-llm-local::error`):
|
||||
- ConnectionRefused / HttpStatus(code) / Decode(json error) / Timeout / Aborted / 그 외 — `Err` 로 first chunk 전 surface 가능.
|
||||
|
||||
**테스트 도구** (`kebab-llm`):
|
||||
**테스트 도구** (`kebab-core`, feature `mock`):
|
||||
- `assert_finish_chunk(chunks: &[TokenChunk])` — 마지막이 `Done` 이어야 — 모든 stream contract pin.
|
||||
- `MockLanguageModel` (feature `mock`, default OFF) — deterministic test double. 실 adapter 만 `Err` 가능, mock 은 항상 stream 시작 후 yield.
|
||||
|
||||
## 외부 의존
|
||||
|
||||
- `kebab-llm` → `kebab-core` 만 (re-export crate).
|
||||
- `kebab-llm-local` → `kebab-llm` + `kebab-config`, `reqwest` (`blocking` feature, JSON), `serde` + `serde_json`, `thiserror`, `anyhow`.
|
||||
- `kebab-llm-local` → `kebab-core` + `kebab-config`, `reqwest` (`blocking` feature, JSON), `serde` + `serde_json`, `thiserror`, `anyhow`.
|
||||
- 외부 서비스: **Ollama HTTP** (default `http://127.0.0.1:11434`). default 모델 `gemma4:e4b` (OCR / caption / RAG 모두 같은 family — 단일 모델 다운로드면 전 시스템 동작).
|
||||
|
||||
## 핵심 결정
|
||||
|
||||
- **`kebab-llm` = trait re-export only, **새 type 금지****.
|
||||
**왜**: `kebab-rag` 등 downstream 이 `use kebab_llm::LanguageModel` 안정 surface 의존. 어댑터 (Ollama/llama.cpp/candle) 는 별 crate. swap config-only.
|
||||
- **`LanguageModel` trait + `MockLanguageModel` 가 `kebab-core` 에 직접 거주 (`mock` feature)**.
|
||||
**왜**: `kebab-rag` 등 downstream 은 `use kebab_core::LanguageModel` 로 의존 — 별도 re-export shim 불필요. 어댑터 (Ollama/llama.cpp/candle) 는 여전히 별 crate 라 swap config-only. 과거의 순수 facade `kebab-llm` 은 `kebab-core` 로 fold-in 되어 삭제됨 (crate 그래프는 [`docs/ARCHITECTURE.md`](../../ARCHITECTURE.md) 참조).
|
||||
|
||||
- **synchronous + blocking + stream iterator**.
|
||||
**왜**: §0 Q5 가 streaming 명시. `async` 가 trait object 와 잘 안 맞음 (Rust async-in-trait 안정성 + Send bound 복잡). `reqwest::blocking` + line-delimited frame 의 `Iterator` 가 caller 코드 단순. RAG 가 동기 소비 + UI thread 가 별도 worker 로 spawn.
|
||||
|
||||
@@ -106,7 +106,7 @@ flowchart LR
|
||||
|
||||
## 외부 의존
|
||||
|
||||
- crate dep: `kebab-core` + `kebab-config` + `kebab-search` (`Retriever` trait 만) + `kebab-llm` (trait 만) + `kebab-store-sqlite` (`DocumentStore` + `put_answer` helper).
|
||||
- crate dep: `kebab-core` (`LanguageModel` trait 포함) + `kebab-config` + `kebab-search` (`Retriever` trait 만) + `kebab-store-sqlite` (`DocumentStore` + `put_answer` helper).
|
||||
- 외부 lib: `serde`/`serde_json`, `regex` (citation marker `[N]` 매칭), `time` (timestamps), `blake3` (`TraceId` 채굴), `thiserror`, `anyhow`.
|
||||
- 외부 서비스: 없음 (concrete adapter 가 가져옴).
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ flowchart LR
|
||||
|
||||
## 외부 의존
|
||||
|
||||
- crate dep: `kebab-core` + `kebab-config` + `kebab-store-sqlite` + `kebab-store-vector` + `kebab-embed` (trait re-export). `kebab-embed-local` 은 caller 가 inject (forbidden direct dep).
|
||||
- crate dep: `kebab-core` (`Embedder` trait 포함) + `kebab-config` + `kebab-store-sqlite` + `kebab-store-vector`. `kebab-embed-local` 은 caller 가 inject (forbidden direct dep).
|
||||
- 외부 lib: `rusqlite` (FTS5 쿼리), `globset` (filter 매칭), `serde_json`, `tracing`.
|
||||
- 외부 서비스: 없음.
|
||||
|
||||
@@ -125,7 +125,7 @@ flowchart LR
|
||||
- **두 측 `index_version` mismatch = warn (not error)**.
|
||||
**왜**: lexical 이 v2, vector 가 v1 (re-embed 안 했음) 같은 stale state 가 운영 시 일어남. 즉시 fail = ingest 끝나기 전 search 막힘. warning 만 띄우고 계속 동작 = 사용자가 인지하고 re-index 결정.
|
||||
|
||||
- **`kebab-embed` (trait crate) 만 의존, `kebab-embed-local` (concrete) **금지****.
|
||||
- **`Embedder` trait (`kebab-core`) 만 의존, `kebab-embed-local` (concrete) **금지****.
|
||||
**왜**: future MVP 의 swap 가능성 (candle, ollama-embed 등). `kebab-search` 가 concrete 어댑터 import 하면 `kebab-embed-local` 의 fastembed dep (큰 ONNX runtime) 이 search 에 강제 → unrelated build 비용. caller 가 runtime inject.
|
||||
|
||||
## 관련 spec / HOTFIXES
|
||||
|
||||
Reference in New Issue
Block a user