UNPKG

@claude-flow/embeddings

Version:

V3 Embedding Service - OpenAI, Transformers.js, Agentic-Flow (ONNX), Mock providers with hyperbolic embeddings, normalization, and chunking

153 lines • 5.04 kB
/** * Base classes for the embedding-service module — extracted from * embedding-service.ts so sibling provider files (e.g. * ruvector-onnx-embedding-service.ts, future plugin providers) can * extend the base without causing a circular-import TDZ. * * The split happened during ADR-121 Phase 1. Before the split, every * concrete provider lived in embedding-service.ts; introducing a new * provider in a separate file that also imported `BaseEmbeddingService` * from embedding-service.ts created a cycle that crashed at module * load with "Class extends undefined". * * This file has no concrete provider classes — only the abstract base * + the LRU cache it uses. That keeps the dependency graph from any * provider back to base, and never the reverse. */ import { EventEmitter } from 'events'; import { normalize } from './normalization.js'; import { PersistentEmbeddingCache } from './persistent-cache.js'; // ============================================================================ // LRU Cache Implementation // ============================================================================ export class LRUCache { maxSize; cache = new Map(); hits = 0; misses = 0; constructor(maxSize) { this.maxSize = maxSize; } get(key) { const value = this.cache.get(key); if (value !== undefined) { // Move to end (most recently used) this.cache.delete(key); this.cache.set(key, value); this.hits++; return value; } this.misses++; return undefined; } set(key, value) { if (this.cache.has(key)) { this.cache.delete(key); } else if (this.cache.size >= this.maxSize) { const firstKey = this.cache.keys().next().value; if (firstKey !== undefined) { this.cache.delete(firstKey); } } this.cache.set(key, value); } clear() { this.cache.clear(); this.hits = 0; this.misses = 0; } get size() { return this.cache.size; } get hitRate() { const total = this.hits + this.misses; return total > 0 ? this.hits / total : 0; } getStats() { return { size: this.cache.size, maxSize: this.maxSize, hits: this.hits, misses: this.misses, hitRate: this.hitRate, }; } } // ============================================================================ // Base Embedding Service // ============================================================================ export class BaseEmbeddingService extends EventEmitter { config; cache; persistentCache = null; embeddingListeners = new Set(); normalizationType; constructor(config) { super(); this.config = config; this.cache = new LRUCache(config.cacheSize ?? 1000); this.normalizationType = config.normalization ?? 'none'; if (config.persistentCache?.enabled) { const pcConfig = config.persistentCache; this.persistentCache = new PersistentEmbeddingCache({ dbPath: pcConfig.dbPath ?? '.cache/embeddings.db', maxSize: pcConfig.maxSize ?? 10000, ttlMs: pcConfig.ttlMs, }); } } /** Apply normalization to embedding if configured. */ applyNormalization(embedding) { if (this.normalizationType === 'none') return embedding; return normalize(embedding, { type: this.normalizationType }); } /** Check persistent cache for embedding. */ async checkPersistentCache(text) { if (!this.persistentCache) return null; return this.persistentCache.get(text); } /** Store embedding in persistent cache. */ async storePersistentCache(text, embedding) { if (!this.persistentCache) return; await this.persistentCache.set(text, embedding); } emitEvent(event) { for (const listener of this.embeddingListeners) { try { listener(event); } catch (error) { console.error('Error in embedding event listener:', error); } } this.emit(event.type, event); } addEventListener(listener) { this.embeddingListeners.add(listener); } removeEventListener(listener) { this.embeddingListeners.delete(listener); } clearCache() { const size = this.cache.size; this.cache.clear(); this.emitEvent({ type: 'cache_eviction', size }); } getCacheStats() { const stats = this.cache.getStats(); return { size: stats.size, maxSize: stats.maxSize, hitRate: stats.hitRate, }; } async shutdown() { this.clearCache(); this.embeddingListeners.clear(); } } //# sourceMappingURL=embedding-service-base.js.map