/** * LocalEmbeddingProvider — uses @xenova/transformers (optional dependency). * * @xenova/transformers is NOT installed by default. This provider uses a * dynamic import so the module is only required at runtime when the local * provider is actually configured. If the package is absent, isAvailable() * returns false and embed() throws a clear error. */ import { EmbeddingError, type EmbeddingProvider, type EmbeddingVector } from './provider.js'; export class LocalEmbeddingProvider implements EmbeddingProvider { readonly name = 'local'; readonly model = 'Xenova/all-MiniLM-L6-v2'; readonly dimensions = 384; // eslint-disable-next-line @typescript-eslint/no-explicit-any private pipeline: ((text: string, options: Record) => Promise) | null = null; async embed(texts: string[]): Promise { if (!this.pipeline) { let transformers: { pipeline: Function }; try { // Dynamic import — only succeeds when @xenova/transformers is installed. // eslint-disable-next-line @typescript-eslint/no-explicit-any transformers = (await import('@xenova/transformers' as any)) as { pipeline: Function; }; } catch { throw new EmbeddingError( '@xenova/transformers is not installed. Install it to use the local embedding provider.' ); } this.pipeline = await transformers.pipeline('feature-extraction', this.model); } const results: EmbeddingVector[] = []; for (const text of texts) { const output = await this.pipeline!(text, { pooling: 'mean', normalize: true }); results.push({ values: new Float32Array(output.data), dimensions: this.dimensions, model: this.model }); } return results; } async isAvailable(): Promise { try { await import('@xenova/transformers' as never); return true; } catch { return false; } } }