Skip to main content
Glama
fallback.ts5.57 kB
/** * 备用模型适配器 * 支持自动重试、指数退避和速率限制处理 */ import type { ModelAdapter, ChatMessage } from './base.js'; /** 错误类型 */ type RetryableError = Error & { status?: number; code?: string }; /** * 备用适配器配置 */ export interface FallbackConfig { /** 主适配器 */ readonly primary: ModelAdapter; /** 备用适配器列表 */ readonly fallbacks: ModelAdapter[]; /** 最大重试次数 */ readonly maxRetries?: number; /** 初始重试延迟(毫秒) */ readonly retryDelay?: number; /** 最大重试延迟(毫秒) */ readonly maxRetryDelay?: number; /** 退避倍数 */ readonly backoffMultiplier?: number; /** 进度回调 */ readonly onProgress?: (message: string) => void; } /** * 带备用模型的适配器 * 当主模型失败时自动切换到备用模型 */ export class FallbackAdapter implements ModelAdapter { private readonly primary: ModelAdapter; private readonly fallbacks: ModelAdapter[]; private readonly maxRetries: number; private readonly retryDelay: number; private readonly maxRetryDelay: number; private readonly backoffMultiplier: number; private readonly onProgress?: (message: string) => void; constructor(config: FallbackConfig) { this.primary = config.primary; this.fallbacks = config.fallbacks; this.maxRetries = config.maxRetries ?? 3; this.retryDelay = config.retryDelay ?? 1000; this.maxRetryDelay = config.maxRetryDelay ?? 30000; this.backoffMultiplier = config.backoffMultiplier ?? 2; this.onProgress = config.onProgress; } /** * 发送聊天请求,失败时自动切换备用模型 * 支持指数退避和速率限制处理 */ async chat(messages: ChatMessage[]): Promise<string> { const adapters = [this.primary, ...this.fallbacks]; let lastError: RetryableError | null = null; for (let i = 0; i < adapters.length; i++) { const adapter = adapters[i]; const adapterName = this.getAdapterName(adapter, i); for (let retry = 0; retry <= this.maxRetries; retry++) { try { if (retry > 0) { const delay = this.calculateBackoff(retry, lastError); this.onProgress?.(`🔄 重试 ${adapterName} (${retry}/${this.maxRetries}),等待 ${Math.round(delay / 1000)}s...`); await this.sleep(delay); } const result = await adapter.chat(messages); if (i > 0) { this.onProgress?.(`✅ 使用备用模型 ${adapterName} 成功`); } return result; } catch (error) { lastError = error as RetryableError; const errorMsg = lastError.message || String(error); // 检查是否应该重试 if (!this.shouldRetry(lastError, retry)) { this.onProgress?.(`❌ ${adapterName} 失败(不可重试): ${errorMsg.slice(0, 50)}...`); break; } if (retry === this.maxRetries) { this.onProgress?.(`❌ ${adapterName} 失败: ${errorMsg.slice(0, 50)}...`); } } } // 当前适配器所有重试都失败,尝试下一个 if (i < adapters.length - 1) { this.onProgress?.(`⚠️ ${adapterName} 不可用,切换到备用模型...`); } } // 所有适配器都失败 throw new Error(`所有模型都调用失败: ${lastError?.message || '未知错误'}`); } /** * 计算指数退避延迟 * 对于 429 错误,使用更长的等待时间 */ private calculateBackoff(retry: number, error: RetryableError | null): number { let delay = this.retryDelay * Math.pow(this.backoffMultiplier, retry - 1); // 429 速率限制:使用更长的延迟 if (error?.status === 429 || error?.code === 'rate_limit_exceeded') { delay = Math.max(delay, 5000) * 2; // 至少 10 秒 this.onProgress?.(`⏳ 检测到速率限制,延长等待时间...`); } // 添加抖动(±20%)避免同时重试 const jitter = delay * 0.2 * (Math.random() - 0.5); delay = delay + jitter; // 限制最大延迟 return Math.min(delay, this.maxRetryDelay); } /** * 判断错误是否可重试 */ private shouldRetry(error: RetryableError, currentRetry: number): boolean { if (currentRetry >= this.maxRetries) return false; const status = error.status; // 可重试的状态码 if (status === 429) return true; // 速率限制 if (status === 500) return true; // 服务器错误 if (status === 502) return true; // 网关错误 if (status === 503) return true; // 服务不可用 if (status === 504) return true; // 网关超时 // 网络错误 if (error.code === 'ECONNRESET') return true; if (error.code === 'ETIMEDOUT') return true; if (error.code === 'ENOTFOUND') return true; // 默认:没有状态码时尝试重试 if (!status) return true; // 4xx 客户端错误(除 429)不重试 if (status >= 400 && status < 500) return false; return true; } /** * 获取适配器名称 */ private getAdapterName(adapter: ModelAdapter, index: number): string { // 使用类型安全的元数据接口 if (adapter.modelName) { return adapter.modelName; } return index === 0 ? '主模型' : `备用模型${index}`; } /** * 延迟执行 */ private sleep(ms: number): Promise<void> { return new Promise(resolve => setTimeout(resolve, ms)); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/7836246/claude-team-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server