mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-10 22:43:40 +00:00
fix: address review feedback — deduplicate HTTP client, remove config cache, add guards
- Extract shared HTTP client (http-client.ts) used by both core and MCP embedders - Remove module-level httpConfig cache — read env vars fresh on every call so config set after module load (e.g. via dotenv) takes effect - Add NaN/non-positive guard on GITNEXUS_EMBEDDING_DIMS in schema.ts - Include scrubbed URL and batch index in error messages (no API key) - Wrap fetch rejections (DNS/timeout/connection) with same scrubbed context - MCP embedder delegates to shared httpEmbedQuery() instead of inline logic - apiKey confined to http-client.ts internals — not exported in any type or accessor - Remove HttpEmbeddingConfig from types.ts (replaced by internal HttpConfig) - All 16 HTTP embedder tests pass, tsc clean
This commit is contained in:
parent
7dcafb647b
commit
c8480f899d
8 changed files with 209 additions and 153 deletions
12
AGENTS.md
12
AGENTS.md
|
|
@ -1,7 +1,7 @@
|
|||
<!-- gitnexus:start -->
|
||||
# GitNexus — Code Intelligence
|
||||
|
||||
This project is indexed by GitNexus as **GitNexus** (2169 symbols, 5213 relationships, 165 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
||||
This project is indexed by GitNexus as **gitnexus-fork** (2179 symbols, 5243 relationships, 166 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
||||
|
||||
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ This project is indexed by GitNexus as **GitNexus** (2169 symbols, 5213 relation
|
|||
|
||||
1. `gitnexus_query({query: "<error or symptom>"})` — find execution flows related to the issue
|
||||
2. `gitnexus_context({name: "<suspect function>"})` — see all callers, callees, and process participation
|
||||
3. `READ gitnexus://repo/GitNexus/process/{processName}` — trace the full execution flow step by step
|
||||
3. `READ gitnexus://repo/gitnexus-fork/process/{processName}` — trace the full execution flow step by step
|
||||
4. For regressions: `gitnexus_detect_changes({scope: "compare", base_ref: "main"})` — see what your branch changed
|
||||
|
||||
## When Refactoring
|
||||
|
|
@ -56,10 +56,10 @@ This project is indexed by GitNexus as **GitNexus** (2169 symbols, 5213 relation
|
|||
|
||||
| Resource | Use for |
|
||||
|----------|---------|
|
||||
| `gitnexus://repo/GitNexus/context` | Codebase overview, check index freshness |
|
||||
| `gitnexus://repo/GitNexus/clusters` | All functional areas |
|
||||
| `gitnexus://repo/GitNexus/processes` | All execution flows |
|
||||
| `gitnexus://repo/GitNexus/process/{name}` | Step-by-step execution trace |
|
||||
| `gitnexus://repo/gitnexus-fork/context` | Codebase overview, check index freshness |
|
||||
| `gitnexus://repo/gitnexus-fork/clusters` | All functional areas |
|
||||
| `gitnexus://repo/gitnexus-fork/processes` | All execution flows |
|
||||
| `gitnexus://repo/gitnexus-fork/process/{name}` | Step-by-step execution trace |
|
||||
|
||||
## Self-Check Before Finishing
|
||||
|
||||
|
|
|
|||
12
CLAUDE.md
12
CLAUDE.md
|
|
@ -1,7 +1,7 @@
|
|||
<!-- gitnexus:start -->
|
||||
# GitNexus — Code Intelligence
|
||||
|
||||
This project is indexed by GitNexus as **GitNexus** (2169 symbols, 5213 relationships, 165 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
||||
This project is indexed by GitNexus as **gitnexus-fork** (2179 symbols, 5243 relationships, 166 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
||||
|
||||
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ This project is indexed by GitNexus as **GitNexus** (2169 symbols, 5213 relation
|
|||
|
||||
1. `gitnexus_query({query: "<error or symptom>"})` — find execution flows related to the issue
|
||||
2. `gitnexus_context({name: "<suspect function>"})` — see all callers, callees, and process participation
|
||||
3. `READ gitnexus://repo/GitNexus/process/{processName}` — trace the full execution flow step by step
|
||||
3. `READ gitnexus://repo/gitnexus-fork/process/{processName}` — trace the full execution flow step by step
|
||||
4. For regressions: `gitnexus_detect_changes({scope: "compare", base_ref: "main"})` — see what your branch changed
|
||||
|
||||
## When Refactoring
|
||||
|
|
@ -56,10 +56,10 @@ This project is indexed by GitNexus as **GitNexus** (2169 symbols, 5213 relation
|
|||
|
||||
| Resource | Use for |
|
||||
|----------|---------|
|
||||
| `gitnexus://repo/GitNexus/context` | Codebase overview, check index freshness |
|
||||
| `gitnexus://repo/GitNexus/clusters` | All functional areas |
|
||||
| `gitnexus://repo/GitNexus/processes` | All execution flows |
|
||||
| `gitnexus://repo/GitNexus/process/{name}` | Step-by-step execution trace |
|
||||
| `gitnexus://repo/gitnexus-fork/context` | Codebase overview, check index freshness |
|
||||
| `gitnexus://repo/gitnexus-fork/clusters` | All functional areas |
|
||||
| `gitnexus://repo/gitnexus-fork/processes` | All execution flows |
|
||||
| `gitnexus://repo/gitnexus-fork/process/{name}` | Step-by-step execution trace |
|
||||
|
||||
## Self-Check Before Finishing
|
||||
|
||||
|
|
|
|||
|
|
@ -18,92 +18,8 @@ import { pipeline, env, type FeatureExtractionPipeline } from '@huggingface/tran
|
|||
import { existsSync } from 'fs';
|
||||
import { execFileSync } from 'child_process';
|
||||
import { join } from 'path';
|
||||
import { DEFAULT_EMBEDDING_CONFIG, type EmbeddingConfig, type HttpEmbeddingConfig, type ModelProgress } from './types.js';
|
||||
|
||||
// ─── HTTP Embedding Backend ───────────────────────────────────────────────────
|
||||
// When GITNEXUS_EMBEDDING_URL + GITNEXUS_EMBEDDING_MODEL are set, embedding
|
||||
// calls proxy to a remote OpenAI-compatible /v1/embeddings endpoint.
|
||||
|
||||
function getHttpConfig(): HttpEmbeddingConfig | null {
|
||||
const baseUrl = process.env.GITNEXUS_EMBEDDING_URL;
|
||||
const model = process.env.GITNEXUS_EMBEDDING_MODEL;
|
||||
if (!baseUrl || !model) return null;
|
||||
return {
|
||||
baseUrl: baseUrl.replace(/\/+$/, ''),
|
||||
model,
|
||||
apiKey: process.env.GITNEXUS_EMBEDDING_API_KEY ?? 'unused',
|
||||
dimensions: process.env.GITNEXUS_EMBEDDING_DIMS
|
||||
? parseInt(process.env.GITNEXUS_EMBEDDING_DIMS, 10)
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
let httpConfig: HttpEmbeddingConfig | null | undefined;
|
||||
let httpDimensions: number | null = null;
|
||||
|
||||
const HTTP_TIMEOUT_MS = 30_000;
|
||||
const HTTP_MAX_RETRIES = 2;
|
||||
const HTTP_RETRY_BACKOFF_MS = 1_000;
|
||||
|
||||
async function httpEmbedBatch(
|
||||
url: string,
|
||||
batch: string[],
|
||||
model: string,
|
||||
apiKey: string,
|
||||
attempt = 0,
|
||||
): Promise<Array<{ embedding: number[] }>> {
|
||||
const resp = await fetch(url, {
|
||||
method: 'POST',
|
||||
signal: AbortSignal.timeout(HTTP_TIMEOUT_MS),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({ input: batch, model }),
|
||||
});
|
||||
|
||||
if (!resp.ok) {
|
||||
const status = resp.status;
|
||||
if ((status === 429 || status >= 500) && attempt < HTTP_MAX_RETRIES) {
|
||||
const delay = HTTP_RETRY_BACKOFF_MS * (attempt + 1);
|
||||
await new Promise(r => setTimeout(r, delay));
|
||||
return httpEmbedBatch(url, batch, model, apiKey, attempt + 1);
|
||||
}
|
||||
throw new Error(`Embedding endpoint returned ${status}`);
|
||||
}
|
||||
|
||||
const data = (await resp.json()) as { data: Array<{ embedding: number[] }> };
|
||||
return data.data;
|
||||
}
|
||||
|
||||
async function httpEmbed(texts: string[]): Promise<Float32Array[]> {
|
||||
if (httpConfig === undefined) httpConfig = getHttpConfig();
|
||||
if (!httpConfig) throw new Error('HTTP embedding not configured');
|
||||
|
||||
const url = `${httpConfig.baseUrl}/embeddings`;
|
||||
const batchSize = 64;
|
||||
const allVectors: Float32Array[] = [];
|
||||
|
||||
for (let i = 0; i < texts.length; i += batchSize) {
|
||||
const batch = texts.slice(i, i + batchSize);
|
||||
const items = await httpEmbedBatch(url, batch, httpConfig.model, httpConfig.apiKey);
|
||||
|
||||
for (const item of items) {
|
||||
allVectors.push(new Float32Array(item.embedding));
|
||||
}
|
||||
|
||||
if (httpDimensions === null && items.length > 0) {
|
||||
httpDimensions = items[0].embedding.length;
|
||||
}
|
||||
}
|
||||
|
||||
return allVectors;
|
||||
}
|
||||
|
||||
function isHttpMode(): boolean {
|
||||
if (httpConfig === undefined) httpConfig = getHttpConfig();
|
||||
return httpConfig !== null;
|
||||
}
|
||||
import { DEFAULT_EMBEDDING_CONFIG, type EmbeddingConfig, type ModelProgress } from './types.js';
|
||||
import { isHttpMode, getHttpDimensions, httpEmbed } from './http-client.js';
|
||||
|
||||
/**
|
||||
* Check whether CUDA libraries are actually available on this system.
|
||||
|
|
@ -292,13 +208,11 @@ export const isEmbedderReady = (): boolean => {
|
|||
|
||||
/**
|
||||
* Get the effective embedding dimensions.
|
||||
* Returns configured dimensions. In HTTP mode, uses GITNEXUS_EMBEDDING_DIMS
|
||||
* or falls back to auto-detected dims from the last HTTP response.
|
||||
* In HTTP mode, uses GITNEXUS_EMBEDDING_DIMS if set, otherwise the default.
|
||||
*/
|
||||
export const getEmbeddingDimensions = (): number => {
|
||||
if (isHttpMode()) {
|
||||
const cfg = getHttpConfig();
|
||||
return cfg?.dimensions ?? httpDimensions ?? DEFAULT_EMBEDDING_CONFIG.dimensions;
|
||||
return getHttpDimensions() ?? DEFAULT_EMBEDDING_CONFIG.dimensions;
|
||||
}
|
||||
return DEFAULT_EMBEDDING_CONFIG.dimensions;
|
||||
};
|
||||
|
|
|
|||
177
gitnexus/src/core/embeddings/http-client.ts
Normal file
177
gitnexus/src/core/embeddings/http-client.ts
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
/**
|
||||
* HTTP Embedding Client
|
||||
*
|
||||
* Shared fetch+retry logic for OpenAI-compatible /v1/embeddings endpoints.
|
||||
* Imported by both the core embedder (batch) and MCP embedder (query).
|
||||
*/
|
||||
|
||||
const HTTP_TIMEOUT_MS = 30_000;
|
||||
const HTTP_MAX_RETRIES = 2;
|
||||
const HTTP_RETRY_BACKOFF_MS = 1_000;
|
||||
const HTTP_BATCH_SIZE = 64;
|
||||
|
||||
interface HttpConfig {
|
||||
baseUrl: string;
|
||||
model: string;
|
||||
apiKey: string;
|
||||
dimensions?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build config from the current process.env snapshot.
|
||||
* Returns null when GITNEXUS_EMBEDDING_URL + GITNEXUS_EMBEDDING_MODEL are unset.
|
||||
* Not cached — env vars are read fresh so late configuration takes effect.
|
||||
*/
|
||||
const readConfig = (): HttpConfig | null => {
|
||||
const baseUrl = process.env.GITNEXUS_EMBEDDING_URL;
|
||||
const model = process.env.GITNEXUS_EMBEDDING_MODEL;
|
||||
if (!baseUrl || !model) return null;
|
||||
|
||||
const rawDims = process.env.GITNEXUS_EMBEDDING_DIMS;
|
||||
let dimensions: number | undefined;
|
||||
if (rawDims !== undefined) {
|
||||
const parsed = parseInt(rawDims, 10);
|
||||
if (Number.isNaN(parsed) || parsed <= 0) {
|
||||
throw new Error(
|
||||
`GITNEXUS_EMBEDDING_DIMS must be a positive integer, got "${rawDims}"`,
|
||||
);
|
||||
}
|
||||
dimensions = parsed;
|
||||
}
|
||||
|
||||
return {
|
||||
baseUrl: baseUrl.replace(/\/+$/, ''),
|
||||
model,
|
||||
apiKey: process.env.GITNEXUS_EMBEDDING_API_KEY ?? 'unused',
|
||||
dimensions,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether HTTP embedding mode is active (env vars are set).
|
||||
*/
|
||||
export const isHttpMode = (): boolean => readConfig() !== null;
|
||||
|
||||
/**
|
||||
* Return the configured embedding dimensions for HTTP mode, or undefined
|
||||
* if HTTP mode is not active or no explicit dimensions are set.
|
||||
*/
|
||||
export const getHttpDimensions = (): number | undefined => readConfig()?.dimensions;
|
||||
|
||||
/**
|
||||
* Return a safe representation of a URL for error messages.
|
||||
* Strips query string (may contain tokens) and userinfo.
|
||||
*/
|
||||
const safeUrl = (url: string): string => {
|
||||
try {
|
||||
const u = new URL(url);
|
||||
return `${u.protocol}//${u.host}${u.pathname}`;
|
||||
} catch {
|
||||
return '<invalid-url>';
|
||||
}
|
||||
};
|
||||
|
||||
interface EmbeddingItem {
|
||||
embedding: number[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a single batch of texts to the embedding endpoint with retry.
|
||||
*
|
||||
* @param url - Full endpoint URL (e.g. https://host/v1/embeddings)
|
||||
* @param batch - Texts to embed
|
||||
* @param model - Model name for the request body
|
||||
* @param apiKey - Bearer token (only used in Authorization header)
|
||||
* @param batchIndex - Logical batch number (for error context)
|
||||
* @param attempt - Current retry attempt (internal)
|
||||
*/
|
||||
const httpEmbedBatch = async (
|
||||
url: string,
|
||||
batch: string[],
|
||||
model: string,
|
||||
apiKey: string,
|
||||
batchIndex = 0,
|
||||
attempt = 0,
|
||||
): Promise<EmbeddingItem[]> => {
|
||||
let resp: Response;
|
||||
try {
|
||||
resp = await fetch(url, {
|
||||
method: 'POST',
|
||||
signal: AbortSignal.timeout(HTTP_TIMEOUT_MS),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({ input: batch, model }),
|
||||
});
|
||||
} catch (err) {
|
||||
// DNS, timeout, connection errors — add context without leaking the key
|
||||
if (attempt < HTTP_MAX_RETRIES) {
|
||||
const delay = HTTP_RETRY_BACKOFF_MS * (attempt + 1);
|
||||
await new Promise(r => setTimeout(r, delay));
|
||||
return httpEmbedBatch(url, batch, model, apiKey, batchIndex, attempt + 1);
|
||||
}
|
||||
const reason = err instanceof Error ? err.message : String(err);
|
||||
throw new Error(
|
||||
`Embedding request failed (${safeUrl(url)}, batch ${batchIndex}): ${reason}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!resp.ok) {
|
||||
const status = resp.status;
|
||||
if ((status === 429 || status >= 500) && attempt < HTTP_MAX_RETRIES) {
|
||||
const delay = HTTP_RETRY_BACKOFF_MS * (attempt + 1);
|
||||
await new Promise(r => setTimeout(r, delay));
|
||||
return httpEmbedBatch(url, batch, model, apiKey, batchIndex, attempt + 1);
|
||||
}
|
||||
throw new Error(
|
||||
`Embedding endpoint returned ${status} (${safeUrl(url)}, batch ${batchIndex})`,
|
||||
);
|
||||
}
|
||||
|
||||
const data = (await resp.json()) as { data: EmbeddingItem[] };
|
||||
return data.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Embed texts via the HTTP backend, splitting into batches.
|
||||
* Reads config from env vars on every call.
|
||||
*
|
||||
* @param texts - Array of texts to embed
|
||||
* @returns Array of Float32Array embedding vectors
|
||||
*/
|
||||
export const httpEmbed = async (texts: string[]): Promise<Float32Array[]> => {
|
||||
const config = readConfig();
|
||||
if (!config) throw new Error('HTTP embedding not configured');
|
||||
|
||||
const url = `${config.baseUrl}/embeddings`;
|
||||
const allVectors: Float32Array[] = [];
|
||||
|
||||
for (let i = 0; i < texts.length; i += HTTP_BATCH_SIZE) {
|
||||
const batch = texts.slice(i, i + HTTP_BATCH_SIZE);
|
||||
const batchIndex = Math.floor(i / HTTP_BATCH_SIZE);
|
||||
const items = await httpEmbedBatch(url, batch, config.model, config.apiKey, batchIndex);
|
||||
|
||||
for (const item of items) {
|
||||
allVectors.push(new Float32Array(item.embedding));
|
||||
}
|
||||
}
|
||||
|
||||
return allVectors;
|
||||
};
|
||||
|
||||
/**
|
||||
* Embed a single query text via the HTTP backend.
|
||||
* Convenience for MCP search where only one vector is needed.
|
||||
*
|
||||
* @param text - Query text to embed
|
||||
* @returns Embedding vector as number array
|
||||
*/
|
||||
export const httpEmbedQuery = async (text: string): Promise<number[]> => {
|
||||
const config = readConfig();
|
||||
if (!config) throw new Error('HTTP embedding not configured');
|
||||
|
||||
const url = `${config.baseUrl}/embeddings`;
|
||||
const items = await httpEmbedBatch(url, [text], config.model, config.apiKey);
|
||||
return items[0].embedding;
|
||||
};
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
export * from './types.js';
|
||||
export * from './http-client.js';
|
||||
export * from './embedder.js';
|
||||
export * from './text-generator.js';
|
||||
export * from './embedding-pipeline.js';
|
||||
|
|
|
|||
|
|
@ -65,20 +65,6 @@ export interface EmbeddingConfig {
|
|||
maxSnippetLength: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for HTTP embedding endpoint (OpenAI-compatible /v1/embeddings).
|
||||
* Populated from GITNEXUS_EMBEDDING_* environment variables.
|
||||
*/
|
||||
export interface HttpEmbeddingConfig {
|
||||
/** Base URL for the embedding API (must include /v1) */
|
||||
baseUrl: string;
|
||||
/** Model name to send in the request */
|
||||
model: string;
|
||||
/** API key for authentication */
|
||||
apiKey: string;
|
||||
/** Vector dimensions — must match model output (default: 384) */
|
||||
dimensions?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default embedding configuration
|
||||
|
|
|
|||
|
|
@ -399,7 +399,13 @@ CREATE REL TABLE ${REL_TABLE_NAME} (
|
|||
// ============================================================================
|
||||
|
||||
/** Embedding vector dimensions. Default 384 (snowflake-arctic-embed-xs). */
|
||||
export const EMBEDDING_DIMS = parseInt(process.env.GITNEXUS_EMBEDDING_DIMS ?? '384', 10);
|
||||
const _rawDims = parseInt(process.env.GITNEXUS_EMBEDDING_DIMS ?? '384', 10);
|
||||
if (Number.isNaN(_rawDims) || _rawDims <= 0) {
|
||||
throw new Error(
|
||||
`GITNEXUS_EMBEDDING_DIMS must be a positive integer, got "${process.env.GITNEXUS_EMBEDDING_DIMS}"`,
|
||||
);
|
||||
}
|
||||
export const EMBEDDING_DIMS = _rawDims;
|
||||
|
||||
export const EMBEDDING_SCHEMA = `
|
||||
CREATE NODE TABLE ${EMBEDDING_TABLE_NAME} (
|
||||
|
|
|
|||
|
|
@ -6,16 +6,10 @@
|
|||
*/
|
||||
|
||||
import { pipeline, env, type FeatureExtractionPipeline } from '@huggingface/transformers';
|
||||
|
||||
// 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);
|
||||
import { isHttpMode, getHttpDimensions, httpEmbedQuery } from '../../core/embeddings/http-client.js';
|
||||
|
||||
// Model config
|
||||
const MODEL_ID = 'Snowflake/snowflake-arctic-embed-xs';
|
||||
const EMBEDDING_DIMS = parseInt(process.env.GITNEXUS_EMBEDDING_DIMS ?? '384', 10);
|
||||
|
||||
// Module-level state for singleton pattern
|
||||
let embedderInstance: FeatureExtractionPipeline | null = null;
|
||||
|
|
@ -26,7 +20,7 @@ let initPromise: Promise<FeatureExtractionPipeline> | null = null;
|
|||
* Initialize the embedding model (lazy, on first search)
|
||||
*/
|
||||
export const initEmbedder = async (): Promise<FeatureExtractionPipeline> => {
|
||||
if (USE_HTTP) {
|
||||
if (isHttpMode()) {
|
||||
throw new Error('initEmbedder() should not be called in HTTP mode.');
|
||||
}
|
||||
|
||||
|
|
@ -97,38 +91,14 @@ export const initEmbedder = async (): Promise<FeatureExtractionPipeline> => {
|
|||
/**
|
||||
* Check if embedder is ready
|
||||
*/
|
||||
export const isEmbedderReady = (): boolean => USE_HTTP || embedderInstance !== null;
|
||||
export const isEmbedderReady = (): boolean => isHttpMode() || embedderInstance !== null;
|
||||
|
||||
/**
|
||||
* Embed a query text for semantic search
|
||||
*/
|
||||
export const embedQuery = async (query: string): Promise<number[]> => {
|
||||
if (USE_HTTP) {
|
||||
const url = `${HTTP_URL.replace(/\/+$/, '')}/embeddings`;
|
||||
const body = JSON.stringify({ input: [query], model: HTTP_MODEL });
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${HTTP_KEY}`,
|
||||
};
|
||||
|
||||
for (let attempt = 0; attempt <= 1; attempt++) {
|
||||
const resp = await fetch(url, {
|
||||
method: 'POST',
|
||||
signal: AbortSignal.timeout(30_000),
|
||||
headers,
|
||||
body,
|
||||
});
|
||||
if (!resp.ok) {
|
||||
if ((resp.status === 429 || resp.status >= 500) && attempt < 1) {
|
||||
await new Promise(r => setTimeout(r, 1_000));
|
||||
continue;
|
||||
}
|
||||
throw new Error(`Embedding endpoint returned ${resp.status}`);
|
||||
}
|
||||
const data = (await resp.json()) as { data: Array<{ embedding: number[] }> };
|
||||
return data.data[0].embedding;
|
||||
}
|
||||
throw new Error('Embedding request failed after retry');
|
||||
if (isHttpMode()) {
|
||||
return httpEmbedQuery(query);
|
||||
}
|
||||
|
||||
const embedder = await initEmbedder();
|
||||
|
|
@ -144,7 +114,9 @@ export const embedQuery = async (query: string): Promise<number[]> => {
|
|||
/**
|
||||
* Get embedding dimensions
|
||||
*/
|
||||
export const getEmbeddingDims = (): number => EMBEDDING_DIMS;
|
||||
export const getEmbeddingDims = (): number => {
|
||||
return getHttpDimensions() ?? 384;
|
||||
};
|
||||
|
||||
/**
|
||||
* Cleanup embedder
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue