feat(embedding): 임베딩 모델 불일치 가드 — 교차모델 벡터 silent 저하 방지 - #142
Conversation
기본 임베딩이 bge-m3 → qwen(#120)으로 바뀌면서, 기존 bge-m3 벡터를 가진 사용자가 업그레이드하면 쿼리는 qwen 으로 임베딩돼 교차모델 불일치가 발생한다. 차원이 같아(1024) 에러는 안 나고 유사도만 무의미해져 시맨틱 검색이 조용히 저하된다(#121 위키 버그와 동일 메커니즘). 이를 감지·경고한다. - `DEFAULT_OLLAMA_EMBED_MODEL` 상수화 (embedding.rs, magic string 제거). - `EmbeddingConfig::embedding_identity()` — `backend:model` 정규화 식별자. - `Database::get/set_embedding_model()` — config KV 에 벡터 생성 모델 마커 저장. - `secall embed`: 재임베딩(--all)/최초 embed 후 마커 저장. incremental 시 마커와 현재 모델이 다르거나(불일치) 마커가 없는데 벡터가 있으면(v0.7.0 이전 DB) 경고. - `secall status`: 동일 불일치/미표시 경고 출력. - `do_status`(REST/MCP): `embedding_model` 마커 노출 (web UI 활용). 마이그레이션: 기존 사용자는 `secall embed --all`(qwen 재임베딩) 또는 `secall config set embedding.ollama_model bge-m3`(기존 유지). BM25 키워드는 무관. 검증: cargo test 744(신규 2: identity·marker roundtrip), clippy -Dwarnings clean. 후속: sync 의 ingest embed 경로에도 동일 경고 연결(현재는 embed/status 만). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LVEL1SnCQBtUMeWVy99ebj
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
✅ Files skipped from review due to trivial changes (1)
📝 WalkthroughWalkthrough임베딩 모델 식별자를 정규화해 계산하고, DB에 저장된 마커와 비교해 Changes임베딩 모델 identity 추적
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant CLI as secall embed/status
participant Config as EmbeddingConfig
participant DB as Database
participant MCP as mcp::server
CLI->>Config: embedding_identity()
CLI->>DB: get_embedding_model()
CLI->>DB: count embedded vectors
CLI->>DB: set_embedding_model(identity)
MCP->>DB: get_embedding_model()
MCP->>Config: embedding_identity()
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces embedding model mismatch detection to prevent semantic search degradation caused by cross-model embeddings. It adds database functions to store and retrieve the embedding model marker, a normalized identifier generator (embedding_identity), and warnings in the embed and status commands when a mismatch is detected. The review feedback highlights three key areas for improvement: avoiding platform/user-dependent paths in the model identifier by using only the file name, and properly propagating database errors using the ? operator in both the MCP server and the status command instead of silently ignoring them.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| "ort" | "openvino" => format!( | ||
| "{}:{}", | ||
| self.backend, | ||
| self.model_path | ||
| .as_ref() | ||
| .map(|p| p.display().to_string()) | ||
| .unwrap_or_else(|| "BAAI/bge-m3".to_string()) | ||
| ), |
There was a problem hiding this comment.
p.display().to_string()를 사용하여 전체 경로를 식별자로 사용하면 다음과 같은 문제가 발생할 수 있습니다:
- 플랫폼 의존성: Windows(역슬래시)와 Unix(슬래시) 간 경로 구분자가 달라 동일한 상대 경로라도 식별자가 다르게 생성됩니다.
- 사용자/기기 의존성: 절대 경로(예:
~/.cache/...)를 사용할 경우, 사용자 홈 디렉토리 경로가 달라져 기기 간 동기화 시 불일치 경고가 발생합니다.
seCall은 여러 기기나 OS 간에 볼트(Vault) 및 DB를 동기화하여 사용하는 경우가 많으므로, 경로 전체 대신 p.file_name()을 통해 모델 디렉토리명(예: bge-m3-onnx)만 추출하여 식별자로 사용하는 것이 훨씬 안전하고 견고합니다.
| "ort" | "openvino" => format!( | |
| "{}:{}", | |
| self.backend, | |
| self.model_path | |
| .as_ref() | |
| .map(|p| p.display().to_string()) | |
| .unwrap_or_else(|| "BAAI/bge-m3".to_string()) | |
| ), | |
| "ort" | "openvino" => format!( | |
| "{}:{}", | |
| self.backend, | |
| self.model_path | |
| .as_ref() | |
| .and_then(|p| p.file_name().map(|n| n.to_string_lossy().into_owned())) | |
| .unwrap_or_else(|| "BAAI/bge-m3".to_string()) | |
| ), |
| .map_err(|e| anyhow::anyhow!("DB lock: {e}"))?; | ||
| let stats = db.get_stats()?; | ||
| // 벡터가 생성된 임베딩 모델 마커 (없으면 null — v0.7.0 이전 DB 이거나 벡터 없음). | ||
| let embedding_model = db.get_embedding_model().ok().flatten(); |
There was a problem hiding this comment.
db.get_embedding_model()이 반환하는 데이터베이스 에러를 .ok().flatten()으로 무시하면, 실제 DB 에러가 발생했을 때 원인을 파악하기 어렵고 단순히 마커가 없는 것으로 오인될 수 있습니다. 앞선 db.get_stats()?와 마찬가지로 ? 연산자를 사용하여 에러를 상위로 전파하는 것이 안전합니다.
| let embedding_model = db.get_embedding_model().ok().flatten(); | |
| let embedding_model = db.get_embedding_model()?; |
| match db.get_embedding_model() { | ||
| Ok(Some(stored)) if stored != embed_identity => { | ||
| println!(" ⚠ 임베딩 모델 불일치: 기존 벡터={stored} / 현재={embed_identity}"); | ||
| println!(" → `secall embed --all` 로 재임베딩 권장 (BM25 키워드 검색은 무관)"); | ||
| } | ||
| Ok(None) if stats.vector_count > 0 => { | ||
| println!( | ||
| " ⚠ 임베딩 모델 마커 없음 (v0.7.0 이전 DB 일 수 있음). 벡터가 현재 모델({embed_identity})과 다르면 `secall embed --all` 권장" | ||
| ); | ||
| } | ||
| _ => {} | ||
| } |
There was a problem hiding this comment.
db.get_embedding_model() 호출 시 발생할 수 있는 데이터베이스 에러(Err)가 _ => {} 패턴 매칭에 의해 아무런 경고나 에러 전파 없이 무시되고 있습니다. run() 함수가 Result<()>를 반환하므로, ? 연산자를 사용하여 에러를 명시적으로 전파하는 것이 안전합니다.
| match db.get_embedding_model() { | |
| Ok(Some(stored)) if stored != embed_identity => { | |
| println!(" ⚠ 임베딩 모델 불일치: 기존 벡터={stored} / 현재={embed_identity}"); | |
| println!(" → `secall embed --all` 로 재임베딩 권장 (BM25 키워드 검색은 무관)"); | |
| } | |
| Ok(None) if stats.vector_count > 0 => { | |
| println!( | |
| " ⚠ 임베딩 모델 마커 없음 (v0.7.0 이전 DB 일 수 있음). 벡터가 현재 모델({embed_identity})과 다르면 `secall embed --all` 권장" | |
| ); | |
| } | |
| _ => {} | |
| } | |
| match db.get_embedding_model()? { | |
| Some(stored) if stored != embed_identity => { | |
| println!(" ⚠ 임베딩 모델 불일치: 기존 벡터={stored} / 현재={embed_identity}"); | |
| println!(" → `secall embed --all` 로 재임베딩 권장 (BM25 키워드 검색은 무관)"); | |
| } | |
| None if stats.vector_count > 0 => { | |
| println!( | |
| " ⚠ 임베딩 모델 마커 없음 (v0.7.0 이전 DB 일 수 있음). 벡터가 현재 모델({embed_identity})과 다르면 `secall embed --all` 권장" | |
| ); | |
| } | |
| _ => {} | |
| } |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
crates/secall-core/src/vault/config.rs (2)
387-400: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winort/openvino/openai 기본값 하드코딩 중복
"BAAI/bge-m3","text-embedding-3-large"가 각 임베더 구현체의 실제 기본값과 별도로 여기 하드코딩되어 있습니다. 한쪽만 변경되면 identity 계산이 실제 벡터 생성 모델과 어긋나게 됩니다. 공용 상수로 추출해 양쪽에서 참조하도록 리팩터링을 권장합니다.♻️ 제안 리팩터링 방향
- "ort" | "openvino" => format!( - "{}:{}", - self.backend, - self.model_path - .as_ref() - .map(|p| p.display().to_string()) - .unwrap_or_else(|| "BAAI/bge-m3".to_string()) - ), - "openai" => format!( - "openai:{}", - self.openai_model - .as_deref() - .unwrap_or("text-embedding-3-large") - ), + "ort" | "openvino" => format!( + "{}:{}", + self.backend, + self.model_path + .as_ref() + .map(|p| p.display().to_string()) + .unwrap_or_else(|| DEFAULT_ORT_MODEL_PATH.to_string()) + ), + "openai" => format!( + "openai:{}", + self.openai_model.as_deref().unwrap_or(DEFAULT_OPENAI_MODEL) + ),실제 임베더 구현체가 정의한 상수를 재사용하도록
DEFAULT_ORT_MODEL_PATH/DEFAULT_OPENAI_MODEL을 해당 모듈에서 노출해 주세요.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/secall-core/src/vault/config.rs` around lines 387 - 400, The identity formatting in the config logic hardcodes default model values for ort/openvino/openai instead of reusing the defaults defined by the embedder implementations. Update the identity-building code in config.rs to reference shared exported constants from the relevant embedder modules, such as DEFAULT_ORT_MODEL_PATH and DEFAULT_OPENAI_MODEL, so both identity calculation and actual vector generation stay aligned when defaults change.
670-682: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winidentity 테스트 커버리지 확장 제안
ollama_cloud/openvino/openai/none/other분기는 테스트되지 않았습니다. 정합성 감지의 근간이 되는 함수이므로 각 분기별 기대값을 검증하는 테스트 추가를 권장합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/secall-core/src/vault/config.rs` around lines 670 - 682, The embedding_identity test currently covers only the default ollama case and one backend switch, leaving several EmbeddingConfig branches unverified. Expand the existing test_embedding_identity in EmbeddingConfig to add assertions for the ollama_cloud, openvino, openai, none, and other paths, using embedding_identity() so each variant’s expected identity string is explicitly checked. Keep the test grouped with the current identity coverage so changes to backend/model selection remain easy to validate.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/secall-core/src/vault/config.rs`:
- Around line 381-386: The ollama_cloud identity fallback in
embedding_identity() is out of sync with the runtime model selection in vector
search. Update the identity construction so it follows the same order as the
model resolution logic in search/vector.rs: prefer cloud_model, then
ollama_model, then DEFAULT_OLLAMA_EMBED_MODEL. This ensures the marker matches
the actual embedded model when cloud_model is unset but ollama_model is
configured.
In `@crates/secall/src/commands/embed.rs`:
- Around line 78-100: 임베딩이 비활성화된 backend = "none" 상태에서도 embedding_identity()가
"none"으로 비교되어 불필요한 불일치 경고와 --all 재임베딩 권고가 나오는 문제입니다. embed 명령의 기존 모델 비교 블록에서
config.embedding.backend != "none"일 때만 stored_model와 existing_vectors를 검사하도록
감싸고, 동일한 비교 로직이 복제된 status 명령의 관련 블록도 같은 조건으로 맞춰 일관되게 처리하세요.
- Around line 78-100: `embed` command의 `get_stats()` 실패를 `unwrap_or(0)`으로 숨기지 말고
오류를 전파하도록 바꾸세요. `existing_vectors`를 계산하는 부분에서 `db.get_stats()`가 실패하면 `?`로 즉시
반환하게 하여, 이후 `stored_model.is_none() && existing_vectors == 0` 같은 마커 확정 로직이 조회
오류를 0으로 오인하지 않도록 수정하세요. 이 변경은 `embed_identity`, `stored_model`,
`existing_vectors`를 사용하는 경고/확정 흐름 전체에 적용해 안전하게 유지하세요.
---
Nitpick comments:
In `@crates/secall-core/src/vault/config.rs`:
- Around line 387-400: The identity formatting in the config logic hardcodes
default model values for ort/openvino/openai instead of reusing the defaults
defined by the embedder implementations. Update the identity-building code in
config.rs to reference shared exported constants from the relevant embedder
modules, such as DEFAULT_ORT_MODEL_PATH and DEFAULT_OPENAI_MODEL, so both
identity calculation and actual vector generation stay aligned when defaults
change.
- Around line 670-682: The embedding_identity test currently covers only the
default ollama case and one backend switch, leaving several EmbeddingConfig
branches unverified. Expand the existing test_embedding_identity in
EmbeddingConfig to add assertions for the ollama_cloud, openvino, openai, none,
and other paths, using embedding_identity() so each variant’s expected identity
string is explicitly checked. Keep the test grouped with the current identity
coverage so changes to backend/model selection remain easy to validate.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 790fd750-6d45-4135-b3c6-cbb37e7e9980
📒 Files selected for processing (6)
crates/secall-core/src/mcp/server.rscrates/secall-core/src/search/embedding.rscrates/secall-core/src/store/db.rscrates/secall-core/src/vault/config.rscrates/secall/src/commands/embed.rscrates/secall/src/commands/status.rs
| "ollama_cloud" => format!( | ||
| "ollama_cloud:{}", | ||
| self.cloud_model | ||
| .as_deref() | ||
| .unwrap_or(DEFAULT_OLLAMA_EMBED_MODEL) | ||
| ), |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# ollama_cloud 관련 필드/임베더 기본값 확인
rg -nP -B3 -A15 'cloud_model' crates/secall-core/src/search/embedding.rs
rg -nP -B3 -A15 'struct.*Cloud|OllamaCloud' crates/secall-core/src
rg -nP 'DEFAULT_OLLAMA_EMBED_MODEL' -g '*.rs'Repository: hang-in/seCall
Length of output: 7293
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 관련 상수/분기 위치와 기본값을 좁혀서 확인
rg -n --hidden --glob '*.rs' 'DEFAULT_OLLAMA_EMBED_MODEL|cloud_model|ollama_cloud|embedding_identity|semantic_backend|embed' crates/secall-core/src
# config.rs의 embedding_identity 주변 읽기
sed -n '340,430p' crates/secall-core/src/vault/config.rs
# embedding 관련 구현/테스트가 있으면 확인
fd -a 'embedding.rs' crates/secall-core/src
fd -a 'config.rs' crates/secall-core/src
fd -a 'test.*rs' crates/secall-core/src | rg 'embedding|vault|config'Repository: hang-in/seCall
Length of output: 46052
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,120p' crates/secall-core/src/search/embedding.rs
sed -n '500,540p' crates/secall-core/src/search/vector.rs
sed -n '370,405p' crates/secall-core/src/vault/config.rsRepository: hang-in/seCall
Length of output: 6898
ollama_cloud identity의 fallback 순서를 런타임과 맞춰주세요
crates/secall-core/src/search/vector.rs:509-519에서는 cloud_model -> ollama_model -> DEFAULT_OLLAMA_EMBED_MODEL 순으로 실제 모델을 고르는데, crates/secall-core/src/vault/config.rs:381-386의 embedding_identity()는 cloud_model이 없으면 바로 DEFAULT_OLLAMA_EMBED_MODEL로 떨어집니다. cloud_model이 비어 있고 ollama_model만 설정된 경우 실제 모델과 다른 마커가 저장돼 불필요한 재임베딩/불일치 판정이 발생합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/secall-core/src/vault/config.rs` around lines 381 - 386, The
ollama_cloud identity fallback in embedding_identity() is out of sync with the
runtime model selection in vector search. Update the identity construction so it
follows the same order as the model resolution logic in search/vector.rs: prefer
cloud_model, then ollama_model, then DEFAULT_OLLAMA_EMBED_MODEL. This ensures
the marker matches the actual embedded model when cloud_model is unset but
ollama_model is configured.
| // 임베딩 모델 불일치 감지: 기존 벡터가 다른 모델로 생성됐으면 교차모델 유사도가 | ||
| // 무의미해 시맨틱 검색이 조용히 저하된다(차원이 같아 에러는 안 남). 재임베딩(--all) | ||
| // 이 아니면 경고만 하고 진행한다. | ||
| let embed_identity = config.embedding.embedding_identity(); | ||
| let stored_model = db.get_embedding_model()?; | ||
| let existing_vectors = db.get_stats().map(|s| s.vector_count).unwrap_or(0); | ||
| if !all { | ||
| match &stored_model { | ||
| Some(m) if *m != embed_identity => { | ||
| eprintln!("⚠ 임베딩 모델 불일치: 기존 벡터={m}, 현재={embed_identity}"); | ||
| eprintln!( | ||
| " 섞이면 시맨틱 검색 정확도가 떨어집니다. `secall embed --all` 로 전체 재임베딩을 권장합니다." | ||
| ); | ||
| } | ||
| None if existing_vectors > 0 => { | ||
| eprintln!( | ||
| "⚠ 임베딩 모델 마커가 없습니다 (v0.7.0 이전 DB 일 수 있음). 기존 벡터가 현재 모델({embed_identity})과 다르면 `secall embed --all` 을 권장합니다." | ||
| ); | ||
| } | ||
| _ => {} | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
backend = "none"일 때 오해 소지 있는 불일치 경고
임베딩이 비활성화된 상태(backend = "none")에서도 embedding_identity()가 "none"을 반환해 저장된 마커와 비교되고, "기존 벡터=... / 현재=none, embed --all 로 재임베딩 권장" 경고가 출력됩니다. 사용자가 의도적으로 임베딩을 끈 상황이라면 이 경고/권고는 혼란을 줍니다. crates/secall/src/commands/status.rs의 동일 로직(69-80행)에도 같은 패턴이 복제되어 있어 같은 문제가 발생합니다. config.embedding.backend != "none" 조건으로 이 검사 블록 전체를 감싸는 편을 권장합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/secall/src/commands/embed.rs` around lines 78 - 100, 임베딩이 비활성화된
backend = "none" 상태에서도 embedding_identity()가 "none"으로 비교되어 불필요한 불일치 경고와 --all
재임베딩 권고가 나오는 문제입니다. embed 명령의 기존 모델 비교 블록에서 config.embedding.backend != "none"일
때만 stored_model와 existing_vectors를 검사하도록 감싸고, 동일한 비교 로직이 복제된 status 명령의 관련 블록도
같은 조건으로 맞춰 일관되게 처리하세요.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
get_stats() 오류를 0으로 처리하면 마커가 잘못 확정될 위험
existing_vectors가 unwrap_or(0)으로 계산되어, get_stats()가 실패하면 실제 벡터가 존재하더라도 0으로 간주됩니다. 이 값은 251행 이하 마커 확정 조건(stored_model.is_none() && existing_vectors == 0)에 그대로 쓰이므로, DB 조회 오류가 발생한 순간 기존 벡터가 있는데도 현재 모델로 마커가 확정될 수 있습니다. 이는 교차모델 오염을 감지하기 위한 이 기능의 목적을 정면으로 무력화합니다. ?로 오류를 전파하는 편이 안전합니다.
🛡️ 제안 수정
- let existing_vectors = db.get_stats().map(|s| s.vector_count).unwrap_or(0);
+ let existing_vectors = db.get_stats()?.vector_count;Also applies to: 251-259
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@crates/secall/src/commands/embed.rs` around lines 78 - 100, `embed` command의
`get_stats()` 실패를 `unwrap_or(0)`으로 숨기지 말고 오류를 전파하도록 바꾸세요. `existing_vectors`를
계산하는 부분에서 `db.get_stats()`가 실패하면 `?`로 즉시 반환하게 하여, 이후 `stored_model.is_none() &&
existing_vectors == 0` 같은 마커 확정 로직이 조회 오류를 0으로 오인하지 않도록 수정하세요. 이 변경은
`embed_identity`, `stored_model`, `existing_vectors`를 사용하는 경고/확정 흐름 전체에 적용해 안전하게
유지하세요.
- ort/openvino embedding_identity 를 전체 경로 → 파일명(file_name)만 사용 (절대경로/OS 구분자 차이로 크로스머신 동기화 시 헛불일치 방지, Gemini HIGH). - backend="none" 시 불일치 경고 skip (embed + status 양쪽, CodeRabbit Major). - embed: get_stats() 실패를 0 으로 뭉개지 않고 `?` 전파 (마커 오확정 방지, Major). - README(ko/en): bge-m3 → qwen3-embedding 업그레이드 안내 추가 (embed --all / config set 마이그레이션 + status 감지 안내). 검증: cargo test config identity pass, clippy -Dwarnings clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LVEL1SnCQBtUMeWVy99ebj
기존 ONNX 블록쿼트 바로 뒤 블록쿼트라 사이 빈 줄이 MD028(blank line inside blockquote)로 걸림. 일반 문단+리스트로 변경. markdownlint 로컬 0 errors 확인. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LVEL1SnCQBtUMeWVy99ebj
…베딩 가드 #137~#142 (6 PR) + v0.7.0 릴리즈 요약. backlog(가드 sync-path·#14/#15 재설계· 스케줄러) + 인프라(DB 경로·8080/5180·정크정리) + 학습(do_get 회귀·교차모델). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LVEL1SnCQBtUMeWVy99ebj
배경
기본 임베딩이
bge-m3→qwen3-embedding:0.6b(#120)로 바뀌면서, 기존 bge-m3 벡터를 가진 사용자가 업그레이드하면 쿼리는 qwen 으로 임베딩돼 교차모델 불일치가 발생합니다. 두 모델 다 1024-dim 이라 차원 에러는 안 나고, 유사도만 무의미해져 시맨틱/하이브리드 검색이 조용히 저하됩니다 (#121 위키 버그와 동일 메커니즘). 지금은 아무 경고도 없어 사용자가 검색이 망가진 줄도 모릅니다.변경
DEFAULT_OLLAMA_EMBED_MODEL상수화 (search/embedding.rs) — magic string 제거.EmbeddingConfig::embedding_identity()—backend:model정규화 식별자 (모델/백엔드 변경 감지 기준).Database::get/set_embedding_model()—configKV 에 벡터 생성 모델 마커 저장 (schema_version 과 동일 패턴).secall embed— 재임베딩(--all)/최초 embed 후 마커 저장. incremental 시 마커≠현재모델(불일치)이거나 마커 없이 벡터만 있으면(v0.7.0 이전 DB) 경고.secall status— 동일 불일치/미표시 경고.do_status(REST/MCP) —embedding_model마커 노출 (web UI 활용 가능).마이그레이션 안내
secall embed --all— qwen 으로 전체 재임베딩 (권장) → 마커 확정 + 경고 해소secall config set embedding.ollama_model bge-m3— 기존 모델 유지검증
cargo test744 pass (신규 2:embedding_identity·embedding_model_marker_roundtrip)clippy --workspace --all-targets(-Dwarnings) clean후속 (별건)
secall sync의 ingest embed 경로에도 동일 경고 연결 (현재는embed/status만).머지 전
Summary by CodeRabbit
secall embed --all또는 기존 모델 유지 방법, BM25는 영향 없음 안내를 추가했습니다.