/** * 임베딩 모델 자동 감지 부트스트랩 — 하이브리드(sparse+dense) 검색 온보딩. * * 문제: g1nation.embeddingModel 기본값이 '' (비활성) — LM Studio 에 임베딩 모델이 * 로드돼 있어도 사용자가 설정 키와 모델명 문자열을 알아야만 켜진다. 비개발자 * 사용자에게는 사실상 영구 비활성. 측정 결과 임베딩 블렌드가 어휘 갭 * ("심판"↔"LLM-as-a-Judge" 류 동의어/패러프레이즈) miss 를 해소하므로 기본 ON 가치가 있다. * * 해결: 활성화 시 embeddingModel 이 비어 있으면 엔진(ollamaUrl)의 모델 목록을 조회해 * 이름에 'embed' 가 들어간 모델을 찾아 자동 설정 + 1회 알림 ("전체 색인" 버튼 포함). * * 보호 장치: * - 이미 embeddingModel 설정됨 → no-op (기존 사용자 무영향). * - 자동 설정 이력이 있는데 현재 비어 있음 = 사용자가 의도적으로 껐다 → 재설정 안 함. * - 엔진 미응답/임베딩 모델 없음 → 조용히 skip (다음 활성화 때 재시도). */ import * as vscode from 'vscode'; import { logError, logInfo } from '../utils'; const STATE_KEY = 'astra.embeddingAutoConfigured'; const PROBE_TIMEOUT_MS = 4000; /** 엔진의 모델 목록에서 임베딩 모델 id 를 찾는다 (LM Studio /v1/models · Ollama /api/tags). */ async function detectEmbeddingModel(baseUrl: string): Promise { const root = baseUrl.replace(/\/+$/, ''); const tryFetch = async (url: string): Promise => { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS); try { const r = await fetch(url, { signal: controller.signal }); return r.ok ? await r.json() : null; } catch { return null; } finally { clearTimeout(timer); } }; // OpenAI-호환 (LM Studio) 우선, 실패 시 Ollama. const v1 = await tryFetch(`${root}/v1/models`); let ids: string[] = Array.isArray(v1?.data) ? v1.data.map((m: any) => String(m?.id || '')) : []; if (ids.length === 0) { const tags = await tryFetch(`${root}/api/tags`); ids = Array.isArray(tags?.models) ? tags.models.map((m: any) => String(m?.name || '')) : []; } // 임베딩 모델 후보 — 'embed' 외에 대표 임베딩 계열 이름도 포착 // (multilingual-e5-small, bge-m3 등은 이름에 'embed' 가 없다). const candidates = ids.filter(id => /embed|(^|[^a-z0-9])(bge|gte|e5)([^a-z0-9]|$)|minilm|arctic-embed/i.test(id)); if (candidates.length === 0) return null; // 한국어 두뇌 특성상 다국어 모델 우선 (골든셋 측정: nomic 은 한국어 동의어 매칭 한계). const rank = (id: string) => (/multilingual|bge-m3|e5/i.test(id) ? 0 : 1); candidates.sort((a, b) => rank(a) - rank(b)); return candidates[0]; } /** * embeddingModel 미설정 시 엔진에서 임베딩 모델을 자동 감지해 설정한다. * 활성화 초기에 1회 호출 (fire-and-forget — 검색은 TF-IDF 로 이미 동작 중). */ export async function ensureEmbeddingConfigured(context: vscode.ExtensionContext): Promise { try { const cfg = vscode.workspace.getConfiguration('g1nation'); const current = (cfg.get('embeddingModel', '') || '').trim(); if (current) return; // 이미 설정됨 if (context.globalState.get(STATE_KEY)) return; // 사용자가 의도적으로 비움 const baseUrl = (cfg.get('ollamaUrl', '') || 'http://127.0.0.1:1234').trim(); const model = await detectEmbeddingModel(baseUrl); if (!model) { logInfo('Embedding bootstrap: 엔진에서 임베딩 모델 미발견 — skip.', { baseUrl }); return; } await cfg.update('embeddingModel', model, vscode.ConfigurationTarget.Global); await context.globalState.update(STATE_KEY, true); logInfo('Embedding bootstrap: 하이브리드 검색 자동 활성화.', { model, baseUrl }); void vscode.window.showInformationMessage( `Astra: 임베딩 모델 "${model}" 을 발견해 하이브리드 검색을 켰습니다. ` + '전체 색인을 한 번 실행하면 즉시 전체 두뇌에 적용됩니다 (이후는 자동 증분).', '지금 전체 색인', ).then(pick => { if (pick === '지금 전체 색인') void vscode.commands.executeCommand('g1nation.embeddings.backfill'); }); } catch (e: any) { logError('Embedding bootstrap 실패 (TF-IDF 검색은 정상 동작).', { error: e?.message ?? String(e) }); } }