UNPKG

route-claudecode

Version:

Advanced routing and transformation system for Claude Code outputs to multiple AI providers

615 lines 24.9 kB
"use strict"; /** * 动态模型发现系统 * 通过API获取模型列表并验证可用性 */ Object.defineProperty(exports, "__esModule", { value: true }); exports.DynamicModelDiscovery = void 0; exports.createDynamicModelDiscovery = createDynamicModelDiscovery; const logger_1 = require("@/utils/logger"); const types_1 = require("@/types"); /** * 模型发现和验证核心类 */ class DynamicModelDiscovery { config; cache = new Map(); availabilityCache = new Map(); cacheExpiry = new Map(); CACHE_DURATION = 30 * 60 * 1000; // 30分钟 constructor(config) { this.config = config; } /** * 获取可用的模型列表 */ async fetchAvailableModels() { const cacheKey = `${this.config.providerId}:models`; // 检查缓存 if (this.isCacheValid(cacheKey)) { logger_1.logger.debug('Using cached model list', { providerId: this.config.providerId }, 'model-discovery'); return this.cache.get(cacheKey); } try { logger_1.logger.info('Fetching model list from API', { providerId: this.config.providerId }, 'model-discovery'); const models = await this.fetchModelsFromAPI(); // 缓存结果 this.cache.set(cacheKey, models); this.cacheExpiry.set(cacheKey, Date.now() + this.CACHE_DURATION); logger_1.logger.info('Successfully fetched model list', { providerId: this.config.providerId, modelCount: models.length, modelNames: models.slice(0, 5).map(m => m.id) }, 'model-discovery'); return models; } catch (error) { logger_1.logger.error('Failed to fetch model list', { providerId: this.config.providerId, error: error instanceof Error ? error.message : String(error) }, 'model-discovery'); // 如果API获取失败,返回默认模型列表 return this.getDefaultModels(); } } /** * 从提供商API获取模型列表 */ async fetchModelsFromAPI() { const { provider, providerId } = this.config; // 根据提供商类型使用不同的API端点 switch (provider.type) { case 'openai': case 'shuaihong': case 'lmstudio': return this.fetchOpenAICompatibleModels(); case 'anthropic': return this.fetchAnthropicModels(); case 'gemini': return this.fetchGeminiModels(); default: logger_1.logger.warn(`Unsupported provider type for model discovery: ${provider.type}`, { providerId }, 'model-discovery'); return this.getDefaultModels(); } } /** * 获取OpenAI兼容的模型列表 */ async fetchOpenAICompatibleModels() { const endpoint = this.getModelsEndpoint(); try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), this.config.requestTimeout); const response = await fetch(`${endpoint}/models`, { method: 'GET', headers: this.getAuthHeaders(), signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); const models = []; // 处理OpenAI格式的模型列表 if (data.data && Array.isArray(data.data)) { for (const model of data.data) { models.push({ id: model.id, name: model.id, description: `Model: ${model.id}`, created: model.created, owned_by: model.owned_by, capabilities: this.inferCapabilities(model.id) }); } } return models; } catch (error) { logger_1.logger.error('Failed to fetch OpenAI-compatible models', { providerId: this.config.providerId, endpoint, error: error instanceof Error ? error.message : String(error) }, 'model-discovery'); throw error; } } /** * 获取Anthropic模型列表 */ async fetchAnthropicModels() { // Anthropic不提供模型列表API,返回硬编码的已知模型 return [ { id: 'claude-3-5-sonnet-20241022', name: 'Claude 3.5 Sonnet', description: 'Latest Claude 3.5 Sonnet model', capabilities: { text: true, vision: true, tools: true, streaming: true } }, { id: 'claude-3-5-haiku-20241022', name: 'Claude 3.5 Haiku', description: 'Fast Claude 3.5 Haiku model', capabilities: { text: true, vision: true, tools: true, streaming: true } }, { id: 'claude-3-opus-20240229', name: 'Claude 3 Opus', description: 'Most capable Claude 3 model', capabilities: { text: true, vision: true, tools: true, streaming: true } }, { id: 'claude-3-sonnet-20240229', name: 'Claude 3 Sonnet', description: 'Balanced Claude 3 model', capabilities: { text: true, vision: true, tools: true, streaming: true } }, { id: 'claude-3-haiku-20240307', name: 'Claude 3 Haiku', description: 'Fast Claude 3 model', capabilities: { text: true, vision: false, tools: true, streaming: true } } ]; } /** * 获取Gemini模型列表 */ async fetchGeminiModels() { const endpoint = this.getModelsEndpoint(); try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), this.config.requestTimeout); const response = await fetch(`${endpoint}/v1beta/models?key=${this.getAPIKey()}`, { method: 'GET', signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); const models = []; // 处理Gemini格式的模型列表 if (data.models && Array.isArray(data.models)) { for (const model of data.models) { if (model.supportedGenerationMethods?.includes('generateContent')) { models.push({ id: model.name.replace('models/', ''), name: model.displayName || model.name, description: model.description, capabilities: { text: true, vision: model.supportedGenerationMethods?.includes('generateContent') || false, tools: model.supportedGenerationMethods?.includes('generateContent') || false, streaming: true } }); } } } return models; } catch (error) { logger_1.logger.error('Failed to fetch Gemini models', { providerId: this.config.providerId, endpoint, error: error instanceof Error ? error.message : String(error) }, 'model-discovery'); throw error; } } /** * 测试模型可用性(多次测试避免流控误判) */ async testModelAvailability(modelId, testCount = 3) { const cacheKey = `${this.config.providerId}:${modelId}:availability`; // 检查缓存 if (this.isCacheValid(cacheKey)) { return this.availabilityCache.get(cacheKey); } const results = []; for (let i = 0; i < testCount; i++) { try { logger_1.logger.debug(`Testing model availability (${i + 1}/${testCount})`, { providerId: this.config.providerId, modelId, testIndex: i + 1 }, 'model-discovery'); const startTime = Date.now(); // 发送测试请求 const result = await this.sendTestRequest(modelId); const responseTime = Date.now() - startTime; results.push({ modelId, isAvailable: true, responseTime, testTimestamp: startTime, retryCount: i }); logger_1.logger.debug(`Model test successful (${i + 1}/${testCount})`, { providerId: this.config.providerId, modelId, responseTime, testIndex: i + 1 }, 'model-discovery'); } catch (error) { const responseTime = Date.now(); results.push({ modelId, isAvailable: false, responseTime, error: error instanceof Error ? error.message : String(error), statusCode: error?.response?.status || error?.status, testTimestamp: responseTime, retryCount: i }); logger_1.logger.warn(`Model test failed (${i + 1}/${testCount})`, { providerId: this.config.providerId, modelId, error: error instanceof Error ? error.message : String(error), statusCode: error?.response?.status || error?.status, testIndex: i + 1 }, 'model-discovery'); } // 测试间隔 if (i < testCount - 1) { await this.delay(this.config.retryDelay); } } // 分析测试结果 const finalResult = this.analyzeTestResults(results); // 缓存结果 this.availabilityCache.set(cacheKey, finalResult); this.cacheExpiry.set(cacheKey, Date.now() + this.CACHE_DURATION); logger_1.logger.info(`Model availability test completed`, { providerId: this.config.providerId, modelId, isAvailable: finalResult.isAvailable, successRate: results.filter(r => r.isAvailable).length / results.length, avgResponseTime: finalResult.responseTime, testCount: results.length }, 'model-discovery'); return finalResult; } /** * 分析测试结果,避免流控误判 */ analyzeTestResults(results) { const successfulTests = results.filter(r => r.isAvailable); const failedTests = results.filter(r => !r.isAvailable); // 计算成功率 const successRate = successfulTests.length / results.length; // 计算平均响应时间(仅成功请求) const avgResponseTime = successfulTests.length > 0 ? successfulTests.reduce((sum, r) => sum + r.responseTime, 0) / successfulTests.length : failedTests.reduce((sum, r) => sum + r.responseTime, 0) / failedTests.length; // 错误分析 const rateLimitErrors = failedTests.filter(r => r.statusCode === 429); const authErrors = failedTests.filter(r => r.statusCode === 401 || r.statusCode === 403); const networkErrors = failedTests.filter(r => !r.statusCode || r.statusCode >= 500); // 判断模型可用性 let isAvailable = false; let retryCount = Math.max(...results.map(r => r.retryCount)); if (successRate >= 0.6) { // 成功率超过60%,认为模型可用 isAvailable = true; } else if (successRate === 0.5 && results.length === 2) { // 2次测试中1次成功,认为模型可用(可能是一次性流控) isAvailable = true; } else if (successRate === 0.33 && results.length === 3) { // 3次测试中1次成功,需要进一步判断 // 如果失败的都是429错误,可能是流控,认为模型可用 if (rateLimitErrors.length === failedTests.length) { isAvailable = true; retryCount = Math.min(retryCount + 1, this.config.maxRetries); } } // 认证错误直接认为不可用 if (authErrors.length > 0) { isAvailable = false; } return { modelId: results[0].modelId, isAvailable, responseTime: avgResponseTime, error: isAvailable ? undefined : failedTests[0]?.error, statusCode: isAvailable ? undefined : failedTests[0]?.statusCode, testTimestamp: Date.now(), retryCount }; } /** * 执行完整的模型发现流程 */ async discoverModels() { const startTime = Date.now(); const errors = []; try { logger_1.logger.info('Starting model discovery', { providerId: this.config.providerId }, 'model-discovery'); // 1. 获取模型列表 const models = await this.fetchAvailableModels(); if (models.length === 0) { errors.push('No models found from API'); return { providerId: this.config.providerId, totalModels: 0, availableModels: [], unavailableModels: [], averageResponseTime: 0, discoveryTimestamp: Date.now(), errors }; } // 2. 测试每个模型的可用性 const availableModels = []; const unavailableModels = []; const responseTimes = []; // 限制测试的模型数量,避免耗时过长 const modelsToTest = models.slice(0, 10); // 最多测试10个模型 for (const model of modelsToTest) { try { const result = await this.testModelAvailability(model.id, 3); responseTimes.push(result.responseTime); if (result.isAvailable) { availableModels.push(model.id); logger_1.logger.info('Model available', { providerId: this.config.providerId, modelId: model.id, responseTime: result.responseTime }, 'model-discovery'); } else { unavailableModels.push(model.id); logger_1.logger.warn('Model unavailable', { providerId: this.config.providerId, modelId: model.id, error: result.error, statusCode: result.statusCode }, 'model-discovery'); } } catch (error) { unavailableModels.push(model.id); errors.push(`Failed to test model ${model.id}: ${error}`); logger_1.logger.error('Error testing model', { providerId: this.config.providerId, modelId: model.id, error: error instanceof Error ? error.message : String(error) }, 'model-discovery'); } // 模型测试间隔 await this.delay(1000); } const averageResponseTime = responseTimes.length > 0 ? responseTimes.reduce((sum, time) => sum + time, 0) / responseTimes.length : 0; const discoveryResult = { providerId: this.config.providerId, totalModels: models.length, availableModels, unavailableModels, averageResponseTime, discoveryTimestamp: Date.now(), errors }; logger_1.logger.info('Model discovery completed', { providerId: this.config.providerId, totalModels: discoveryResult.totalModels, availableModels: discoveryResult.availableModels.length, unavailableModels: discoveryResult.unavailableModels.length, averageResponseTime: discoveryResult.averageResponseTime, discoveryDuration: Date.now() - startTime }, 'model-discovery'); return discoveryResult; } catch (error) { const errorMessage = `Model discovery failed: ${error instanceof Error ? error.message : String(error)}`; errors.push(errorMessage); logger_1.logger.error('Model discovery failed', { providerId: this.config.providerId, error: error instanceof Error ? error.message : String(error) }, 'model-discovery'); return { providerId: this.config.providerId, totalModels: 0, availableModels: [], unavailableModels: [], averageResponseTime: 0, discoveryTimestamp: Date.now(), errors }; } } /** * 发送测试请求 */ async sendTestRequest(modelId) { const { provider, providerId } = this.config; const testRequest = { model: modelId, messages: [{ role: 'user', content: this.config.testPrompt }], max_tokens: this.config.maxTokens, metadata: { requestId: `test-${Date.now()}-${Math.random().toString(36).substr(2, 9)}` } }; const endpoint = this.getChatEndpoint(); const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), this.config.requestTimeout); const response = await fetch(endpoint, { method: 'POST', headers: this.getAuthHeaders(), body: JSON.stringify(testRequest), signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { throw new types_1.ProviderError(`Model test failed: ${response.statusText}`, providerId, response.status); } return response.json(); } /** * 获取模型列表端点 */ getModelsEndpoint() { const { provider } = this.config; if (provider.type === 'gemini') { return provider.endpoint; } // 对于OpenAI兼容的服务,从chat端点推导models端点 const url = new URL(provider.endpoint); const pathParts = url.pathname.split('/').filter(p => p); if (pathParts.includes('chat') && pathParts.includes('completions')) { // /v1/chat/completions -> /v1/models const basePath = pathParts.slice(0, -2).join('/'); url.pathname = basePath ? `/${basePath}/models` : '/v1/models'; } else if (pathParts.includes('completions')) { // /v1/completions -> /v1/models const basePath = pathParts.slice(0, -1).join('/'); url.pathname = basePath ? `/${basePath}/models` : '/v1/models'; } return url.toString(); } /** * 获取聊天端点 */ getChatEndpoint() { return this.config.provider.endpoint; } /** * 获取认证头 */ getAuthHeaders() { const { provider } = this.config; const headers = { 'Content-Type': 'application/json', 'User-Agent': 'claude-code-router/2.0.0' }; if (provider.authentication.type === 'none') { return headers; } const credentials = provider.authentication.credentials; const apiKey = credentials ? (credentials.apiKey || credentials.api_key) : ''; if (Array.isArray(apiKey)) { headers['Authorization'] = `Bearer ${apiKey[0]}`; } else if (apiKey) { headers['Authorization'] = `Bearer ${apiKey}`; } return headers; } /** * 获取API密钥 */ getAPIKey() { const { provider } = this.config; const credentials = provider.authentication.credentials; const apiKey = credentials ? (credentials.apiKey || credentials.api_key) : ''; if (Array.isArray(apiKey)) { return apiKey[0]; } return apiKey || ''; } /** * 推断模型能力 */ inferCapabilities(modelId) { const capabilities = { text: true, vision: false, tools: false, streaming: true }; // 基于模型名称推断能力 const lowerId = modelId.toLowerCase(); if (lowerId.includes('vision') || lowerId.includes('image')) { capabilities.vision = true; } if (lowerId.includes('tool') || lowerId.includes('function')) { capabilities.tools = true; } // 常见模型的能力推断 if (lowerId.includes('gpt-4') || lowerId.includes('claude-3') || lowerId.includes('gemini-pro')) { capabilities.tools = true; } if (lowerId.includes('gpt-4-vision') || lowerId.includes('claude-3') || lowerId.includes('gemini-pro-vision')) { capabilities.vision = true; } return capabilities; } /** * 获取默认模型列表 */ getDefaultModels() { const { provider } = this.config; switch (provider.type) { case 'openai': case 'shuaihong': return [ { id: 'gpt-4', name: 'GPT-4', capabilities: { text: true, vision: false, tools: true, streaming: true } }, { id: 'gpt-4-turbo', name: 'GPT-4 Turbo', capabilities: { text: true, vision: true, tools: true, streaming: true } }, { id: 'gpt-3.5-turbo', name: 'GPT-3.5 Turbo', capabilities: { text: true, vision: false, tools: true, streaming: true } } ]; case 'lmstudio': return [ { id: 'llama-2-7b-chat', name: 'Llama 2 7B Chat', capabilities: { text: true, vision: false, tools: false, streaming: true } }, { id: 'mistral-7b-instruct', name: 'Mistral 7B Instruct', capabilities: { text: true, vision: false, tools: false, streaming: true } }, { id: 'qwen-7b-chat', name: 'Qwen 7B Chat', capabilities: { text: true, vision: false, tools: true, streaming: true } } ]; default: return [ { id: 'default-model', name: 'Default Model', capabilities: { text: true, vision: false, tools: false, streaming: true } } ]; } } /** * 检查缓存是否有效 */ isCacheValid(cacheKey) { const expiryTime = this.cacheExpiry.get(cacheKey); return expiryTime ? Date.now() < expiryTime : false; } /** * 延迟函数 */ delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * 清除缓存 */ clearCache() { this.cache.clear(); this.availabilityCache.clear(); this.cacheExpiry.clear(); logger_1.logger.info('Model discovery cache cleared', { providerId: this.config.providerId }, 'model-discovery'); } /** * 获取缓存统计 */ getCacheStats() { return { modelCacheSize: this.cache.size, availabilityCacheSize: this.availabilityCache.size, cacheKeys: Array.from(this.cache.keys()).concat(Array.from(this.availabilityCache.keys())) }; } } exports.DynamicModelDiscovery = DynamicModelDiscovery; /** * 工厂函数创建动态模型发现实例 */ function createDynamicModelDiscovery(config) { return new DynamicModelDiscovery(config); } //# sourceMappingURL=dynamic-model-discovery.js.map