From a67863563acb2a1aa050b0e13df23379673df900 Mon Sep 17 00:00:00 2001 From: zm2231 Date: Fri, 20 Mar 2026 00:31:39 -0400 Subject: [PATCH] fix: dynamic embedding dimensions for HTTP backend - schema.ts: FLOAT[${EMBEDDING_DIMS}] reads from GITNEXUS_EMBEDDING_DIMS env - embedding-pipeline.ts: vector search CAST uses actual query vector length - mcp/core/embedder.ts: HTTP embedding support for MCP query-time search Without this, using a 1024d model (e.g. bge-large) fails with 'Expected: 384, Actual: 1024' on LadybugDB vector insert. --- .../src/core/embeddings/embedding-pipeline.ts | 2 +- gitnexus/src/core/lbug/schema.ts | 4 ++- gitnexus/src/mcp/core/embedder.ts | 26 +++++++++++++++++-- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/gitnexus/src/core/embeddings/embedding-pipeline.ts b/gitnexus/src/core/embeddings/embedding-pipeline.ts index 5fbd5cd0b..bee01ed5f 100644 --- a/gitnexus/src/core/embeddings/embedding-pipeline.ts +++ b/gitnexus/src/core/embeddings/embedding-pipeline.ts @@ -326,7 +326,7 @@ export const semanticSearch = async ( // Query the vector index on CodeEmbedding to get nodeIds and distances const vectorQuery = ` CALL QUERY_VECTOR_INDEX('CodeEmbedding', 'code_embedding_idx', - CAST(${queryVecStr} AS FLOAT[384]), ${k}) + CAST(${queryVecStr} AS FLOAT[${queryVec.length}]), ${k}) YIELD node AS emb, distance WITH emb, distance WHERE distance < ${maxDistance} diff --git a/gitnexus/src/core/lbug/schema.ts b/gitnexus/src/core/lbug/schema.ts index ef1fbad50..689fb4df0 100644 --- a/gitnexus/src/core/lbug/schema.ts +++ b/gitnexus/src/core/lbug/schema.ts @@ -398,10 +398,12 @@ CREATE REL TABLE ${REL_TABLE_NAME} ( // Separate table for vector storage to avoid copy-on-write overhead // ============================================================================ +export const EMBEDDING_DIMS = parseInt(process.env.GITNEXUS_EMBEDDING_DIMS ?? '384', 10); + export const EMBEDDING_SCHEMA = ` CREATE NODE TABLE ${EMBEDDING_TABLE_NAME} ( nodeId STRING, - embedding FLOAT[384], + embedding FLOAT[${EMBEDDING_DIMS}], PRIMARY KEY (nodeId) )`; diff --git a/gitnexus/src/mcp/core/embedder.ts b/gitnexus/src/mcp/core/embedder.ts index ee480a6a9..404e6dd88 100644 --- a/gitnexus/src/mcp/core/embedder.ts +++ b/gitnexus/src/mcp/core/embedder.ts @@ -9,7 +9,13 @@ import { pipeline, env, type FeatureExtractionPipeline } from '@huggingface/tran // Model config const MODEL_ID = 'Snowflake/snowflake-arctic-embed-xs'; -const EMBEDDING_DIMS = 384; +const EMBEDDING_DIMS = parseInt(process.env.GITNEXUS_EMBEDDING_DIMS ?? '384', 10); + +// HTTP embedding config +const HTTP_URL = process.env.GITNEXUS_EMBEDDING_URL ?? ''; +const HTTP_MODEL = process.env.GITNEXUS_EMBEDDING_MODEL ?? ''; +const HTTP_KEY = process.env.GITNEXUS_EMBEDDING_API_KEY ?? 'unused'; +const USE_HTTP = !!(HTTP_URL && HTTP_MODEL); // Module-level state for singleton pattern let embedderInstance: FeatureExtractionPipeline | null = null; @@ -20,6 +26,8 @@ let initPromise: Promise | null = null; * Initialize the embedding model (lazy, on first search) */ export const initEmbedder = async (): Promise => { + if (USE_HTTP) return null as unknown as FeatureExtractionPipeline; + if (embedderInstance) { return embedderInstance; } @@ -87,12 +95,26 @@ export const initEmbedder = async (): Promise => { /** * Check if embedder is ready */ -export const isEmbedderReady = (): boolean => embedderInstance !== null; +export const isEmbedderReady = (): boolean => USE_HTTP || embedderInstance !== null; /** * Embed a query text for semantic search */ export const embedQuery = async (query: string): Promise => { + if (USE_HTTP) { + const resp = await fetch(`${HTTP_URL.replace(/\/+$/, '')}/embeddings`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${HTTP_KEY}`, + }, + body: JSON.stringify({ input: [query], model: HTTP_MODEL }), + }); + if (!resp.ok) throw new Error(`Embedding endpoint ${resp.status}`); + const data = (await resp.json()) as { data: Array<{ embedding: number[] }> }; + return data.data[0].embedding; + } + const embedder = await initEmbedder(); const result = await embedder(query, {