UNPKG

@juspay/neurolink

Version:

Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio

305 lines (304 loc) 10.8 kB
/** * RAG Retry Handler * * Provides retry logic with exponential backoff and jitter * specifically designed for RAG operations including embeddings, * vector queries, and LLM-based extraction. */ import { withRetry } from "../../core/infrastructure/index.js"; import { isAbortError } from "../../utils/errorHandling.js"; import { logger } from "../../utils/logger.js"; import { EmbeddingError, isRetryableRAGError, MetadataExtractionError, RAGError, RAGErrorCodes, VectorQueryError, } from "../errors/RAGError.js"; /** * Default retry configuration */ export const DEFAULT_RAG_RETRY_CONFIG = { maxRetries: 3, initialDelay: 1000, maxDelay: 30000, backoffMultiplier: 2, jitter: true, retryableErrorCodes: [ RAGErrorCodes.EMBEDDING_RATE_LIMIT, RAGErrorCodes.VECTOR_QUERY_TIMEOUT, RAGErrorCodes.VECTOR_STORE_CONNECTION_ERROR, RAGErrorCodes.METADATA_EXTRACTION_TIMEOUT, RAGErrorCodes.RERANKER_API_ERROR, RAGErrorCodes.OPERATION_TIMEOUT, ], retryableStatusCodes: [408, 429, 500, 502, 503, 504], }; /** * Calculate delay with exponential backoff */ function calculateDelay(attempt, config) { const exponentialDelay = config.initialDelay * config.backoffMultiplier ** attempt; const delay = Math.min(exponentialDelay, config.maxDelay); if (config.jitter) { // Add random jitter between 0 and 50% of the delay const jitterFactor = 0.5 * Math.random(); return delay * (1 + jitterFactor); } return delay; } /** * Sleep utility */ function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } /** * Check if an error is retryable based on configuration */ export function isRetryable(error, config = DEFAULT_RAG_RETRY_CONFIG) { // Never retry abort errors - the operation was intentionally cancelled if (isAbortError(error)) { return false; } // Use custom shouldRetry if provided if (config.shouldRetry) { return config.shouldRetry(error); } // Check if it's a RAG error with retryable flag if (isRetryableRAGError(error)) { return true; } // Check error code if (error instanceof RAGError && config.retryableErrorCodes) { if (config.retryableErrorCodes.includes(error.code)) { return true; } } // Check for common network/timeout errors if (error && typeof error === "object") { const errorObj = error; // Timeout errors if (errorObj.name === "TimeoutError" || errorObj.code === "TIMEOUT" || errorObj.code === "ETIMEDOUT") { return true; } // Network errors if (errorObj.code === "ECONNRESET" || errorObj.code === "ENOTFOUND" || errorObj.code === "ECONNREFUSED" || errorObj.code === "ECONNABORTED" || errorObj.code === "EPIPE" || errorObj.code === "ENETUNREACH" || errorObj.code === "EHOSTUNREACH") { return true; } // HTTP status codes if (typeof errorObj.status === "number" && config.retryableStatusCodes?.includes(errorObj.status)) { return true; } if (typeof errorObj.statusCode === "number" && config.retryableStatusCodes?.includes(errorObj.statusCode)) { return true; } } return false; } /** * Execute a RAG operation with retry logic * * Implements exponential backoff with jitter to prevent thundering herd. * Only retries on errors that are considered retryable. * * @param operation - Async operation to execute with retries * @param config - Partial retry configuration (merged with defaults) * @returns Result of the operation * @throws Last error if all retry attempts fail */ export async function withRAGRetry(operation, config = {}) { const mergedConfig = { ...DEFAULT_RAG_RETRY_CONFIG, ...config, }; let lastError; let attempt = 0; while (attempt <= mergedConfig.maxRetries) { try { return await operation(); } catch (error) { lastError = error; // Don't retry if this is the last attempt if (attempt === mergedConfig.maxRetries) { logger.debug(`[RAGRetryHandler] All ${mergedConfig.maxRetries} attempts exhausted`); break; } // Check if we should retry this error if (!isRetryable(error, mergedConfig)) { logger.debug(`[RAGRetryHandler] Non-retryable error encountered: ${error instanceof Error ? error.message : String(error)}`); break; } // Calculate delay with backoff and jitter const delay = calculateDelay(attempt, mergedConfig); const errorMessage = error instanceof Error ? error.message : String(error); logger.warn(`[RAGRetryHandler] Attempt ${attempt + 1}/${mergedConfig.maxRetries + 1} failed: ${errorMessage}. Retrying in ${Math.round(delay)}ms...`); await sleep(delay); attempt++; } } // Wrap the last error if it's not already a RAG error if (lastError instanceof RAGError) { throw lastError; } throw new RAGError(`RAG operation failed after ${attempt + 1} attempts: ${lastError instanceof Error ? lastError.message : String(lastError)}`, RAGErrorCodes.RETRY_EXHAUSTED, { retryable: false, cause: lastError instanceof Error ? lastError : undefined, details: { totalAttempts: attempt + 1, maxRetries: mergedConfig.maxRetries, }, }); } /** * RAG Retry Handler class for more complex retry scenarios */ export class RAGRetryHandler { config; constructor(config = {}) { this.config = { ...DEFAULT_RAG_RETRY_CONFIG, ...config }; } /** * Execute an operation with retry logic */ async executeWithRetry(operation, maxRetries) { const config = maxRetries !== undefined ? { ...this.config, maxRetries } : this.config; return withRAGRetry(operation, config); } /** * Execute multiple operations with retry, collecting results * Returns successful results and failed operations with their errors */ async executeBatch(items, operation, options) { const { concurrency = 5, continueOnError = true } = options ?? {}; const successful = []; const failed = []; // Process in batches for concurrency control for (let i = 0; i < items.length; i += concurrency) { const batch = items.slice(i, i + concurrency); const batchPromises = batch.map(async (item, batchIndex) => { const index = i + batchIndex; try { const result = await this.executeWithRetry(() => operation(item, index)); successful.push({ item, result, index }); } catch (error) { const errorObj = error instanceof Error ? error : new Error(String(error)); failed.push({ item, error: errorObj, index }); if (!continueOnError) { throw error; } } }); await Promise.all(batchPromises); } const total = successful.length + failed.length; const successRate = total > 0 ? successful.length / total : 0; return { successful, failed, successRate }; } /** * Get current configuration */ getConfig() { return { ...this.config }; } /** * Update configuration */ updateConfig(config) { this.config = { ...this.config, ...config }; } } /** * Specialized retry handler for embedding operations */ export class EmbeddingRetryHandler extends RAGRetryHandler { constructor(config) { super({ maxRetries: 5, // More retries for rate-limited embedding APIs initialDelay: 2000, // Longer initial delay retryableErrorCodes: [ RAGErrorCodes.EMBEDDING_ERROR, RAGErrorCodes.EMBEDDING_RATE_LIMIT, RAGErrorCodes.EMBEDDING_PROVIDER_ERROR, ], shouldRetry: (error) => { // Always retry rate limit errors if (error instanceof EmbeddingError) { return error.retryable; } return isRetryable(error); }, ...config, }); } } /** * Specialized retry handler for vector store operations */ export class VectorStoreRetryHandler extends RAGRetryHandler { constructor(config) { super({ maxRetries: 3, initialDelay: 1000, retryableErrorCodes: [ RAGErrorCodes.VECTOR_QUERY_ERROR, RAGErrorCodes.VECTOR_QUERY_TIMEOUT, RAGErrorCodes.VECTOR_STORE_UNAVAILABLE, RAGErrorCodes.VECTOR_STORE_CONNECTION_ERROR, ], shouldRetry: (error) => { if (error instanceof VectorQueryError) { return error.retryable; } return isRetryable(error); }, ...config, }); } } /** * Specialized retry handler for metadata extraction */ export class MetadataExtractionRetryHandler extends RAGRetryHandler { constructor(config) { super({ maxRetries: 3, initialDelay: 1500, retryableErrorCodes: [ RAGErrorCodes.METADATA_EXTRACTION_ERROR, RAGErrorCodes.METADATA_EXTRACTION_TIMEOUT, ], shouldRetry: (error) => { if (error instanceof MetadataExtractionError) { return error.retryable; } return isRetryable(error); }, ...config, }); } } /** * Create a retry handler with the core infrastructure withRetry */ export function createRetryHandler(config = {}) { const mergedConfig = { ...DEFAULT_RAG_RETRY_CONFIG, ...config }; return (operation) => withRetry(operation, { maxRetries: mergedConfig.maxRetries, baseDelayMs: mergedConfig.initialDelay, maxDelayMs: mergedConfig.maxDelay, shouldRetry: (error) => isRetryable(error, mergedConfig), }); } /** * Global retry handlers for common RAG operations */ export const embeddingRetryHandler = new EmbeddingRetryHandler(); export const vectorStoreRetryHandler = new VectorStoreRetryHandler(); export const metadataExtractionRetryHandler = new MetadataExtractionRetryHandler();