UNPKG

crewai-ts

Version:

TypeScript port of crewAI for agent-based workflows

204 lines (203 loc) 7.64 kB
/** * Base Embedder Abstract Class * * Defines the interface and common functionality for all embedders * with optimized performance and memory characteristics */ /** * Abstract base class for all embedders * Implements common functionality with optimized performance */ export class BaseEmbedder { options; client; _model; retryOptions; cache = new Map(); constructor(options) { this.options = options; const defaultRetry = { maxRetries: 3, initialBackoff: 1000, maxBackoff: 30000 }; this.options = { ...options, model: options.model || 'text-embedding-3-small', provider: options.provider || 'unknown', retry: options.retry || defaultRetry, debug: options.debug ?? false, dimensions: options.dimensions ?? 768, cacheSize: options.cacheSize ?? 1000, cacheTTL: options.cacheTTL ?? 3600000, batchSize: options.batchSize ?? 16, maxConcurrency: options.maxConcurrency ?? 4, apiKey: options.apiKey ?? '', apiUrl: options.apiUrl ?? '', useLocal: options.useLocal ?? false, localModelPath: options.localModelPath ?? '', maxLength: options.maxLength ?? 512, useAveragePooling: options.useAveragePooling ?? true, timeout: options.timeout ?? 30000, embeddingFunction: options.embeddingFunction || ((text) => Promise.resolve(new Float32Array(this.options.dimensions))), normalize: options.normalize ?? false }; this._model = this.options.model || 'text-embedding-3-small'; this.retryOptions = this.options.retry; } async executeWithRetry(operation, maxRetries = this.retryOptions.maxRetries, initialBackoff = this.retryOptions.initialBackoff, maxBackoff = this.retryOptions.maxBackoff) { let currentBackoff = initialBackoff; let lastError = null; for (let attempt = 0; attempt < maxRetries; attempt++) { try { return await operation(); } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); if (this.isTransientError(lastError)) { if (attempt === maxRetries - 1) { throw lastError; } await new Promise(resolve => setTimeout(resolve, currentBackoff)); currentBackoff = Math.min(currentBackoff * 2, maxBackoff); } else { throw lastError; } } } throw lastError || new Error('Unknown error during operation'); } async executeBatchWithRetry(operation, maxRetries = this.retryOptions.maxRetries, initialBackoff = this.retryOptions.initialBackoff, maxBackoff = this.retryOptions.maxBackoff) { return await this.executeWithRetry(operation, maxRetries, initialBackoff, maxBackoff); } isTransientError(error) { const message = error.message.toLowerCase(); return ( // Network errors message.includes('etimedout') || message.includes('econnreset') || message.includes('econnrefused') || message.includes('network error') || message.includes('aborted') || message.includes('timeout') || // Rate limiting and server errors message.includes('rate limit') || message.includes('too many requests') || message.includes('429') || message.includes('500') || message.includes('503')); } normalizeVector(vector) { if (!vector || vector.length === 0) { throw new Error('Vector is required and must have at least one element'); } let magnitude = 0; for (let i = 0; i < vector.length; i++) { const value = vector[i]; if (value === undefined) { throw new Error('Vector contains undefined values'); } magnitude += value * value; } magnitude = Math.sqrt(magnitude); if (magnitude === 0) { return vector; } const normalized = new Float32Array(vector.length); for (let i = 0; i < vector.length; i++) { const value = vector[i]; if (value === undefined) { throw new Error('Vector contains undefined values'); } normalized[i] = value / magnitude; } return normalized; } clearCache() { this.cache.clear(); } getCacheSize() { return this.cache.size; } generateCacheKey(text) { if (!text) { throw new Error('Text is required for cache key generation'); } return `${this._model}-${text}`; } async embedText(text) { if (!text) { if (this.options.debug) { console.warn('Empty text provided for embedding, returning zero vector'); } return new Float32Array(this.options.dimensions); } const cacheKey = this.generateCacheKey(text); const cached = this.getCachedEmbedding(cacheKey); if (cached) { return cached; } try { const embedding = await this.embed(text); if (!embedding) { throw new Error('Embedding function returned undefined'); } // Ensure embedding is Float32Array const embeddingArray = embedding instanceof Float32Array ? embedding : new Float32Array(embedding); this.cache.set(cacheKey, embeddingArray); return embeddingArray; } catch (error) { console.error(`Embedding failed:`, error); return new Float32Array(this.options.dimensions); } } async embedTexts(texts) { if (!texts?.length) { return []; } const batchSize = this.options.batchSize; const numBatches = Math.ceil(texts.length / batchSize); const results = []; for (let i = 0; i < numBatches; i++) { const batch = texts.slice(i * batchSize, (i + 1) * batchSize); try { const embeddings = await this.embedBatch(batch); if (!embeddings) { throw new Error('EmbedBatch returned undefined'); } results.push(...embeddings); } catch (error) { console.error(`Batch embedding failed:`, error); const zeroVector = new Float32Array(this.options.dimensions); results.push(...Array(batch.length).fill(zeroVector)); } } return results; } getCachedEmbedding(cacheKey) { if (!cacheKey) { return null; } const cachedItem = this.cache.get(cacheKey); if (!cachedItem) { return null; } return cachedItem; } async batchProcess(items, batchSize, processFn) { if (!items?.length) { return []; } const results = []; const numBatches = Math.ceil(items.length / batchSize); for (let i = 0; i < numBatches; i++) { const batch = items.slice(i * batchSize, (i + 1) * batchSize); const batchResults = await processFn(batch); results.push(...batchResults); } return results; } }