From 72c99c452c5048abe29d749a368b87065000bf35 Mon Sep 17 00:00:00 2001 From: altair823 Date: Wed, 3 Jun 2026 04:59:23 +0000 Subject: [PATCH] =?UTF-8?q?feat(config,app):=20embedding=20provider=3Dolla?= =?UTF-8?q?ma=20=EB=B0=B0=EC=84=A0=20+=20endpoint,=20version=200.26.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit kebab-config: EmbeddingModelCfg.endpoint: Option(serde default, ollama용, None→models.llm.endpoint 폴백) + provider 문서에 ollama + env KEBAB_MODELS_EMBEDDING_ENDPOINT. kebab-app embedder(): provider match 에 ollama 분기(facade 경유). workspace member += kebab-embed-ollama, app dep 추가. version 0.25.0 → 0.26.0(minor, +Cargo.lock) — 신규 임베딩 백엔드/모델은 CLAUDE.md §Release 의 surface 변경 트리거. Co-Authored-By: Claude Opus 4.8 (1M context) --- Cargo.lock | 63 +++++++++++++++++++++------------- Cargo.toml | 3 +- crates/kebab-app/Cargo.toml | 1 + crates/kebab-app/src/app.rs | 18 ++++++---- crates/kebab-config/src/lib.rs | 21 ++++++++++-- 5 files changed, 73 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4e8a1a8..860e41a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4724,7 +4724,7 @@ dependencies = [ [[package]] name = "kebab-app" -version = "0.25.0" +version = "0.26.0" dependencies = [ "anyhow", "base64 0.22.1", @@ -4739,6 +4739,7 @@ dependencies = [ "kebab-embed", "kebab-embed-candle", "kebab-embed-local", + "kebab-embed-ollama", "kebab-llm", "kebab-llm-local", "kebab-nli", @@ -4771,7 +4772,7 @@ dependencies = [ [[package]] name = "kebab-chunk" -version = "0.25.0" +version = "0.26.0" dependencies = [ "anyhow", "blake3", @@ -4789,7 +4790,7 @@ dependencies = [ [[package]] name = "kebab-cli" -version = "0.25.0" +version = "0.26.0" dependencies = [ "anyhow", "clap", @@ -4810,7 +4811,7 @@ dependencies = [ [[package]] name = "kebab-config" -version = "0.25.0" +version = "0.26.0" dependencies = [ "anyhow", "dirs 5.0.1", @@ -4826,7 +4827,7 @@ dependencies = [ [[package]] name = "kebab-core" -version = "0.25.0" +version = "0.26.0" dependencies = [ "anyhow", "blake3", @@ -4840,7 +4841,7 @@ dependencies = [ [[package]] name = "kebab-embed" -version = "0.25.0" +version = "0.26.0" dependencies = [ "anyhow", "blake3", @@ -4854,7 +4855,7 @@ dependencies = [ [[package]] name = "kebab-embed-candle" -version = "0.25.0" +version = "0.26.0" dependencies = [ "anyhow", "candle-core", @@ -4864,6 +4865,7 @@ dependencies = [ "kebab-config", "kebab-core", "kebab-embed-local", + "kebab-embed-ollama", "rayon", "serde_json", "tempfile", @@ -4873,7 +4875,7 @@ dependencies = [ [[package]] name = "kebab-embed-local" -version = "0.25.0" +version = "0.26.0" dependencies = [ "anyhow", "fastembed", @@ -4884,9 +4886,24 @@ dependencies = [ "tracing", ] +[[package]] +name = "kebab-embed-ollama" +version = "0.26.0" +dependencies = [ + "anyhow", + "kebab-config", + "kebab-core", + "reqwest 0.12.28", + "serde", + "serde_json", + "tokio", + "tracing", + "wiremock", +] + [[package]] name = "kebab-eval" -version = "0.25.0" +version = "0.26.0" dependencies = [ "anyhow", "kebab-app", @@ -4905,7 +4922,7 @@ dependencies = [ [[package]] name = "kebab-llm" -version = "0.25.0" +version = "0.26.0" dependencies = [ "anyhow", "kebab-core", @@ -4914,7 +4931,7 @@ dependencies = [ [[package]] name = "kebab-llm-local" -version = "0.25.0" +version = "0.26.0" dependencies = [ "anyhow", "kebab-config", @@ -4931,7 +4948,7 @@ dependencies = [ [[package]] name = "kebab-mcp" -version = "0.25.0" +version = "0.26.0" dependencies = [ "anyhow", "kebab-app", @@ -4949,7 +4966,7 @@ dependencies = [ [[package]] name = "kebab-nli" -version = "0.25.0" +version = "0.26.0" dependencies = [ "anyhow", "hf-hub", @@ -4964,7 +4981,7 @@ dependencies = [ [[package]] name = "kebab-parse-code" -version = "0.25.0" +version = "0.26.0" dependencies = [ "anyhow", "gix", @@ -4987,7 +5004,7 @@ dependencies = [ [[package]] name = "kebab-parse-image" -version = "0.25.0" +version = "0.26.0" dependencies = [ "ab_glyph", "anyhow", @@ -5011,7 +5028,7 @@ dependencies = [ [[package]] name = "kebab-parse-md" -version = "0.25.0" +version = "0.26.0" dependencies = [ "anyhow", "kebab-core", @@ -5028,7 +5045,7 @@ dependencies = [ [[package]] name = "kebab-parse-pdf" -version = "0.25.0" +version = "0.26.0" dependencies = [ "anyhow", "blake3", @@ -5043,7 +5060,7 @@ dependencies = [ [[package]] name = "kebab-rag" -version = "0.25.0" +version = "0.26.0" dependencies = [ "anyhow", "blake3", @@ -5065,7 +5082,7 @@ dependencies = [ [[package]] name = "kebab-search" -version = "0.25.0" +version = "0.26.0" dependencies = [ "anyhow", "globset", @@ -5084,7 +5101,7 @@ dependencies = [ [[package]] name = "kebab-source-fs" -version = "0.25.0" +version = "0.26.0" dependencies = [ "anyhow", "blake3", @@ -5102,7 +5119,7 @@ dependencies = [ [[package]] name = "kebab-store-sqlite" -version = "0.25.0" +version = "0.26.0" dependencies = [ "anyhow", "blake3", @@ -5122,7 +5139,7 @@ dependencies = [ [[package]] name = "kebab-store-vector" -version = "0.25.0" +version = "0.26.0" dependencies = [ "anyhow", "arrow", @@ -5146,7 +5163,7 @@ dependencies = [ [[package]] name = "kebab-tui" -version = "0.25.0" +version = "0.26.0" dependencies = [ "anyhow", "crossterm", diff --git a/Cargo.toml b/Cargo.toml index b5e04f8..dfccea5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ members = [ "crates/kebab-embed", "crates/kebab-embed-local", "crates/kebab-embed-candle", + "crates/kebab-embed-ollama", "crates/kebab-llm", "crates/kebab-llm-local", "crates/kebab-rag", @@ -31,7 +32,7 @@ edition = "2024" rust-version = "1.85" license = "MIT OR Apache-2.0" repository = "https://github.com/altair823/kebab" -version = "0.25.0" # v0.25.0 — doc-side expansion(별칭) 기능 완전 제거: Chunk.aliases / expansion.rs / IngestExpansionCfg / alias lexical arm / expansion_progress wire kind 제거, 신규 마이그레이션 V013 이 chunk_aliases_fts + chunks.aliases DROP. AssetTimings.expansion_ms 는 wire 호환 위해 값 0 유지. 별칭 default-off 였어 사용자 체감 0. — CLAUDE.md §Release +version = "0.26.0" # v0.26.0 — arctic-embed-l-v2.0 임베더 통합: kebab-embed-candle 다중 모델 레지스트리(e5 mean + arctic CLS, 모델별 pooling/prefix 분기) + 신규 kebab-embed-ollama 크레이트(provider="ollama", POST /api/embed, L2 정규화, batch+fail-soft). config models.embedding.provider 에 "ollama" 추가 + endpoint: Option. 기본 동작 불변(provider=fastembed e5), arctic 은 opt-in, embedding_version cascade(arctic-cls / ollama:{model} 태그). — CLAUDE.md §Release # pre-v0.18 workspace-wide cleanup: enable clippy::pedantic group with # intentional allow-list. The allowed lints are either cosmetic (doc style), diff --git a/crates/kebab-app/Cargo.toml b/crates/kebab-app/Cargo.toml index d8dea8a..6db0722 100644 --- a/crates/kebab-app/Cargo.toml +++ b/crates/kebab-app/Cargo.toml @@ -19,6 +19,7 @@ kebab-search = { path = "../kebab-search" } kebab-embed = { path = "../kebab-embed" } kebab-embed-local = { path = "../kebab-embed-local" } kebab-embed-candle = { path = "../kebab-embed-candle" } +kebab-embed-ollama = { path = "../kebab-embed-ollama" } kebab-llm = { path = "../kebab-llm" } kebab-llm-local = { path = "../kebab-llm-local" } kebab-rag = { path = "../kebab-rag" } diff --git a/crates/kebab-app/src/app.rs b/crates/kebab-app/src/app.rs index 7860f70..0bd4650 100644 --- a/crates/kebab-app/src/app.rs +++ b/crates/kebab-app/src/app.rs @@ -45,6 +45,7 @@ use kebab_core::{ }; use kebab_embed_candle::CandleEmbedder; use kebab_embed_local::FastembedEmbedder; +use kebab_embed_ollama::OllamaEmbedder; use kebab_llm_local::OllamaLanguageModel; use kebab_parse_code::{ CAstExtractor, CppAstExtractor, GoAstExtractor, JavaAstExtractor, JavascriptAstExtractor, @@ -834,11 +835,13 @@ impl App { if let Some(e) = self.embedder.get() { return Ok(Some(e.clone())); } - // Provider branch (Track 1 spec §3). `embeddings_disabled()` above - // already handled `"none"`; here we route the live providers. - // `fastembed`/`onnx`/(empty) keep the default onnxruntime path - // (vectors unchanged — `embedding_version` is preserved); `candle` - // selects the pure-Rust NUMA-safe backend. + // Provider branch (Track 1 spec §3 + arctic-embedder spec). The + // `embeddings_disabled()` check above already handled `"none"`; here we + // route the live providers. `fastembed`/`onnx`/(empty) keep the default + // onnxruntime path (vectors unchanged — `embedding_version` is + // preserved); `candle` selects the pure-Rust NUMA-safe backend (e5 or + // arctic via its model registry); `ollama` offloads to a remote + // `/api/embed` daemon. let provider = self.config.models.embedding.provider.as_str(); let emb: Arc = match provider { "fastembed" | "onnx" | "" => Arc::new( @@ -847,10 +850,13 @@ impl App { "candle" => Arc::new( CandleEmbedder::new(&self.config).context("kb-app: load CandleEmbedder")?, ), + "ollama" => Arc::new( + OllamaEmbedder::new(&self.config).context("kb-app: load OllamaEmbedder")?, + ), other => { return Err(anyhow!( "kb-app: unknown embedding provider {other:?}; expected one of \ - `fastembed` (default), `candle`, or `none` (lexical-only)" + `fastembed` (default), `candle`, `ollama`, or `none` (lexical-only)" )); } }; diff --git a/crates/kebab-config/src/lib.rs b/crates/kebab-config/src/lib.rs index 49c47f9..cec773e 100644 --- a/crates/kebab-config/src/lib.rs +++ b/crates/kebab-config/src/lib.rs @@ -155,9 +155,10 @@ impl NliCfg { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct EmbeddingModelCfg { - /// `fastembed` (default, onnxruntime) or `candle` (pure-Rust, - /// NUMA-safe). `none` disables embeddings (lexical-only). Unknown - /// values error at embedder construction. + /// `fastembed` (default, onnxruntime), `candle` (pure-Rust, NUMA-safe), + /// or `ollama` (remote HTTP embedding endpoint). `none` disables + /// embeddings (lexical-only). Unknown values error at embedder + /// construction. pub provider: String, pub model: String, pub version: String, @@ -170,6 +171,13 @@ pub struct EmbeddingModelCfg { /// provider. Defaulted on load so pre-0.22 config files still parse. #[serde(default)] pub num_threads: u32, + /// HTTP endpoint for the `ollama` embedding provider (e.g. + /// `"http://127.0.0.1:11434"`). `None` (or a missing key in TOML) means + /// "fall back to `models.llm.endpoint`" — same convention as the OCR / + /// vision endpoints. Ignored by the `fastembed` / `candle` providers. + /// Defaulted on load so pre-0.26 config files still parse. + #[serde(default)] + pub endpoint: Option, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -688,6 +696,7 @@ impl Config { dimensions: 1024, batch_size: 64, num_threads: 0, + endpoint: None, }, llm: LlmCfg { provider: "ollama".to_string(), @@ -950,6 +959,12 @@ impl Config { self.models.embedding.num_threads = n; } } + "KEBAB_MODELS_EMBEDDING_ENDPOINT" => { + // Empty value → None (= fall back to models.llm.endpoint), + // mirroring the OCR endpoint override semantics. + self.models.embedding.endpoint = + if v.is_empty() { None } else { Some(v.clone()) }; + } // models.llm "KEBAB_MODELS_LLM_PROVIDER" => self.models.llm.provider = v.clone(),