Files
OllaHub/lib/retry-utils.ts
T
Evandro Fonseca Junior 8c546f6474 feat(chat): implement adaptive timeout and enhanced search functionality
- Introduced adaptive timeout logic for web searches and chat streaming, improving responsiveness and efficiency.
- Updated `smartSearchRag` method to support timeout adjustments based on search rounds.
- Enhanced `executeProgressiveSearch` to utilize adaptive timeouts and improve search result handling.
- Added new utility functions for calculating adaptive timeouts based on rounds and attempts.
- Integrated event listeners for chat session management, improving user experience during chat interactions.
- Refactored chat message handling to accommodate new streaming and timeout features.
2025-11-27 17:32:46 -03:00

170 lines
4.5 KiB
TypeScript

/**
* Utilitários para retry com backoff exponencial
*/
export interface RetryConfig {
maxAttempts: number; // Máximo de tentativas (padrão: 3)
initialDelay: number; // Delay inicial em ms (padrão: 1000)
maxDelay: number; // Delay máximo em ms (padrão: 10000)
backoffMultiplier: number; // Multiplicador do backoff (padrão: 2)
retryableErrors?: string[]; // Padrões de erro que devem ser retentados
}
export const DEFAULT_RETRY_CONFIG: RetryConfig = {
maxAttempts: 3,
initialDelay: 1000,
maxDelay: 10000,
backoffMultiplier: 2,
retryableErrors: [
'timeout',
'network',
'connection',
'ECONNRESET',
'ETIMEDOUT',
'ENOTFOUND',
'sending request',
],
};
export interface RetryResult<T> {
success: boolean;
result?: T;
error?: Error;
attempts: number;
lastError?: Error;
}
/**
* Classifica se um erro é recuperável (deve ser retentado)
*/
export function isRetryableError(error: unknown, config: RetryConfig = DEFAULT_RETRY_CONFIG): boolean {
if (!error) return false;
const errorMessage = error instanceof Error ? error.message : String(error);
const errorString = errorMessage.toLowerCase();
// Verificar padrões de erro recuperáveis
if (config.retryableErrors) {
return config.retryableErrors.some(pattern =>
errorString.includes(pattern.toLowerCase())
);
}
// Erros de rede/timeout são geralmente recuperáveis
return errorString.includes('timeout') ||
errorString.includes('network') ||
errorString.includes('connection');
}
/**
* Calcula o delay para a próxima tentativa usando backoff exponencial
*/
function calculateDelay(attempt: number, config: RetryConfig): number {
const delay = config.initialDelay * Math.pow(config.backoffMultiplier, attempt - 1);
return Math.min(delay, config.maxDelay);
}
/**
* Executa uma função com retry e backoff exponencial
*/
export async function retryWithBackoff<T>(
fn: () => Promise<T>,
config: Partial<RetryConfig> = {}
): Promise<RetryResult<T>> {
const finalConfig: RetryConfig = { ...DEFAULT_RETRY_CONFIG, ...config };
let lastError: Error | undefined;
for (let attempt = 1; attempt <= finalConfig.maxAttempts; attempt++) {
try {
const result = await fn();
return {
success: true,
result,
attempts: attempt,
};
} catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
// Se não for erro recuperável, não tenta novamente
if (!isRetryableError(error, finalConfig)) {
return {
success: false,
error: lastError,
attempts: attempt,
lastError,
};
}
// Se não for a última tentativa, aguarda antes de tentar novamente
if (attempt < finalConfig.maxAttempts) {
const delay = calculateDelay(attempt, finalConfig);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
return {
success: false,
error: lastError,
attempts: finalConfig.maxAttempts,
lastError,
};
}
/**
* Cria um timeout para uma promise
*/
export function withTimeout<T>(
promise: Promise<T>,
timeoutMs: number,
timeoutMessage = 'Operation timed out'
): Promise<T> {
return Promise.race([
promise,
new Promise<T>((_, reject) =>
setTimeout(() => reject(new Error(timeoutMessage)), timeoutMs)
),
]);
}
/**
* Calcula timeout adaptativo baseado no round
* Reduz 20% a cada round: 10s → 8s → 6s → 4.8s
*/
export function calculateAdaptiveTimeout(
round: number,
initialTimeout: number = 10000,
reductionRate: number = 0.2
): number {
const timeout = initialTimeout * Math.pow(1 - reductionRate, round - 1);
return Math.max(timeout, 5000); // Mínimo de 5s
}
/**
* Calcula timeout por motor baseado em tentativas
* Reduz após cada falha: 10s → 7s → 5s
*/
export function calculateEngineTimeout(
attempt: number,
initialTimeout: number = 10000,
reductionRate: number = 0.3
): number {
const timeout = initialTimeout * Math.pow(1 - reductionRate, attempt - 1);
return Math.max(timeout, 3000); // Mínimo de 3s
}
/**
* Configurações otimizadas de timeout
*/
export const TIMEOUT_CONFIG = {
initialTimeout: 10000, // 10s (reduzido de 15s)
timeoutReductionPerRound: 0.2, // 20%
maxTimeoutPerQuery: 25000, // 25s total por query
maxTimeoutPerRound: 15000, // 15s por round
minTimeout: 5000, // Mínimo absoluto
minEngineTimeout: 3000, // Mínimo por motor
} as const;