UNPKG

llm-checker

Version:

Intelligent CLI tool with AI-powered model selection that analyzes your hardware and recommends optimal LLM models for your system

1,069 lines (900 loc) 44.6 kB
const HardwareDetector = require('./hardware/detector'); const ExpandedModelsDatabase = require('./models/expanded_database'); const IntelligentModelRecommender = require('./models/intelligent-recommender'); const CompatibilityAnalyzer = require('../analyzer/compatibility'); const PerformanceAnalyzer = require('../analyzer/performance'); const OllamaClient = require('./ollama/client'); const { getLogger } = require('./utils/logger'); const { getOllamaModelsIntegration, OllamaNativeScraper } = require('./ollama/native-scraper'); class LLMChecker { constructor() { this.hardwareDetector = new HardwareDetector(); this.expandedModelsDatabase = new ExpandedModelsDatabase(); this.intelligentRecommender = new IntelligentModelRecommender(); this.ollamaScraper = new OllamaNativeScraper(); this.compatibilityAnalyzer = new CompatibilityAnalyzer(); this.performanceAnalyzer = new PerformanceAnalyzer(); this.ollamaClient = new OllamaClient(); this.logger = getLogger().createChild('LLMChecker'); } async analyze(options = {}) { try { const hardware = await this.hardwareDetector.getSystemInfo(); this.logger.info('Hardware detected', { hardware }); // Actualizar base de datos de Ollama al inicio await this.updateOllamaDatabase(); let models = this.expandedModelsDatabase.getAllModels(); const ollamaIntegration = await this.integrateOllamaModels(hardware, models); if (options.filter) { models = this.filterModels(models, options.filter); } if (!options.includeCloud) { models = models.filter(model => model.type === 'local'); } const compatibility = this.compatibilityAnalyzer.analyzeCompatibility(hardware, models); if (ollamaIntegration.compatibleOllamaModels && ollamaIntegration.compatibleOllamaModels.length > 0) { for (const ollamaModel of ollamaIntegration.compatibleOllamaModels) { if (ollamaModel.matchedModel && ollamaModel.canRun) { const enhancedModel = { ...ollamaModel.matchedModel, score: ollamaModel.compatibilityScore, issues: ollamaModel.issues || [], notes: [...(ollamaModel.notes || []), '📦 Installed in Ollama'], performanceEstimate: ollamaModel.performanceEstimate, isOllamaInstalled: true, ollamaInfo: { localName: ollamaModel.name, isRunning: ollamaModel.isRunning, cloudInfo: ollamaModel.cloudInfo } }; if (ollamaModel.compatibilityScore >= 75) { compatibility.compatible.push(enhancedModel); } else if (ollamaModel.compatibilityScore >= 60) { compatibility.marginal.push(enhancedModel); } } } compatibility.compatible.sort((a, b) => b.score - a.score); compatibility.marginal.sort((a, b) => b.score - a.score); } const enrichedResults = await this.enrichWithPerformanceData(hardware, compatibility); const recommendations = await this.generateEnhancedRecommendations( hardware, enrichedResults, ollamaIntegration, options.useCase || 'general' ); // Generar recomendaciones inteligentes por categoría const intelligentRecommendations = await this.generateIntelligentRecommendations(hardware); return { hardware, compatible: enrichedResults.compatible, marginal: enrichedResults.marginal, incompatible: enrichedResults.incompatible, recommendations, intelligentRecommendations, ollamaInfo: ollamaIntegration.ollamaInfo, ollamaModels: ollamaIntegration.compatibleOllamaModels, summary: this.generateEnhancedSummary(hardware, enrichedResults, ollamaIntegration), performanceEstimates: enrichedResults.performanceEstimates }; } catch (error) { this.logger.error('Analysis failed', { error: error.message, component: 'LLMChecker', method: 'analyze' }); throw new Error(`Analysis failed: ${error.message}`); } } async integrateOllamaModels(hardware, availableModels) { const integration = { ollamaInfo: { available: false }, compatibleOllamaModels: [], recommendedPulls: [], currentlyRunning: [] }; try { const ollamaStatus = await this.ollamaClient.checkOllamaAvailability(); integration.ollamaInfo = ollamaStatus; if (!ollamaStatus.available) { this.logger.warn('Ollama not available', { error: ollamaStatus.error }); return integration; } const [localModels, runningModels] = await Promise.all([ this.ollamaClient.getLocalModels().catch(() => []), this.ollamaClient.getRunningModels().catch(() => []) ]); integration.currentlyRunning = runningModels; try { this.logger.info('Using enhanced model database for compatibility...'); const enhancedCompatibility = await getOllamaModelsIntegration(localModels); if (enhancedCompatibility.compatible_models && enhancedCompatibility.compatible_models.length > 0) { for (const compatibleMatch of enhancedCompatibility.compatible_models) { const ollamaModel = compatibleMatch.local; const cloudModel = compatibleMatch.cloud; let matchedModel = this.findMatchingModelInDatabase(cloudModel, availableModels); if (!matchedModel) { matchedModel = this.createModelFromCloudData(cloudModel); } const compatibility = this.compatibilityAnalyzer.calculateModelCompatibility(hardware, matchedModel); let finalScore = compatibility.score; if (compatibleMatch.match_type === 'exact') { finalScore = Math.max(finalScore, 75); } else { finalScore = Math.max(finalScore, 65); } const enrichedOllamaModel = { ...ollamaModel, matchedModel, compatibilityScore: finalScore, issues: compatibility.issues || [], notes: compatibility.notes || [], isRunning: runningModels.some(r => r.name === ollamaModel.name), canRun: finalScore >= 60, performanceEstimate: await this.performanceAnalyzer.estimateModelPerformance(matchedModel, hardware), cloudInfo: { pulls: cloudModel.pulls, url: cloudModel.url, match_type: compatibleMatch.match_type, model_type: cloudModel.model_type } }; integration.compatibleOllamaModels.push(enrichedOllamaModel); } this.logger.info('Enhanced Ollama integration completed', { data: { localModels: localModels.length, compatibleModels: integration.compatibleOllamaModels.length, runningModels: runningModels.length, totalAvailable: enhancedCompatibility.all_available, enhancedMatching: true } }); } else { this.logger.warn('No enhanced compatible models found, using fallback'); await this.processFallbackModels(localModels, runningModels, availableModels, hardware, integration); } } catch (enhancedError) { this.logger.warn('Enhanced matching failed, using fallback method', { error: enhancedError.message }); await this.processFallbackModels(localModels, runningModels, availableModels, hardware, integration); } integration.recommendedPulls = await this.generateOllamaRecommendations(hardware, availableModels, localModels); } catch (error) { this.logger.error('Ollama integration failed', { error: error.message, component: 'LLMChecker', method: 'integrateOllamaModels' }); } return integration; } async processFallbackModels(localModels, runningModels, availableModels, hardware, integration) { for (const ollamaModel of localModels) { const matchedModel = this.findMatchingModel(ollamaModel, availableModels); if (matchedModel) { const compatibility = this.compatibilityAnalyzer.calculateModelCompatibility(hardware, matchedModel); const enrichedOllamaModel = { ...ollamaModel, matchedModel, compatibilityScore: compatibility.score, issues: compatibility.issues, notes: compatibility.notes, isRunning: runningModels.some(r => r.name === ollamaModel.name), canRun: compatibility.score >= 60, performanceEstimate: await this.performanceAnalyzer.estimateModelPerformance(matchedModel, hardware) }; integration.compatibleOllamaModels.push(enrichedOllamaModel); } } } findMatchingModelInDatabase(cloudModel, availableModels) { const cloudName = cloudModel.model_name.toLowerCase(); const cloudId = cloudModel.model_identifier.toLowerCase(); let match = availableModels.find(m => m.name.toLowerCase() === cloudName || m.name.toLowerCase().includes(cloudId) ); if (match) return match; const keywords = cloudId.split('-'); match = availableModels.find(model => { const modelName = model.name.toLowerCase(); return keywords.some(keyword => keyword.length > 2 && modelName.includes(keyword) ); }); return match; } createModelFromCloudData(cloudModel) { const sizeMatch = cloudModel.model_identifier.match(/(\d+\.?\d*)[bm]/i); const size = sizeMatch ? sizeMatch[1] + (sizeMatch[0].slice(-1).toUpperCase()) : 'Unknown'; let category = 'medium'; if (size !== 'Unknown') { const sizeNum = parseFloat(size); const unit = size.slice(-1); const sizeInB = unit === 'M' ? sizeNum / 1000 : sizeNum; if (sizeInB < 1) category = 'ultra_small'; else if (sizeInB <= 4) category = 'small'; else if (sizeInB <= 15) category = 'medium'; else category = 'large'; } let specialization = 'general'; const id = cloudModel.model_identifier.toLowerCase(); if (id.includes('code')) specialization = 'code'; else if (id.includes('chat')) specialization = 'chat'; else if (id.includes('embed')) specialization = 'embeddings'; return { name: cloudModel.model_name, size: size, type: 'local', category: category, specialization: specialization, frameworks: ['ollama'], requirements: { ram: Math.ceil((parseFloat(size) || 4) * 0.6), vram: Math.ceil((parseFloat(size) || 4) * 0.4), cpu_cores: 4, storage: Math.ceil((parseFloat(size) || 4) * 0.7) }, installation: { ollama: `ollama pull ${cloudModel.model_identifier}`, description: cloudModel.description || 'Model from Ollama library' }, year: 2024, description: cloudModel.description || `${cloudModel.model_name} model`, cloudData: { pulls: cloudModel.pulls, url: cloudModel.url, model_type: cloudModel.model_type, identifier: cloudModel.model_identifier } }; } findMatchingModel(ollamaModel, availableModels) { const ollamaName = ollamaModel.name.toLowerCase(); const nameMapping = { 'llama3.2:3b': 'Llama 3.2 3B', 'llama3.1:8b': 'Llama 3.1 8B', 'mistral:7b': 'Mistral 7B v0.3', 'mistral:latest': 'Mistral 7B v0.3', 'codellama:7b': 'CodeLlama 7B', 'phi3:mini': 'Phi-3 Mini 3.8B', 'gemma2:2b': 'Gemma 2B', 'tinyllama:1.1b': 'TinyLlama 1.1B', 'qwen2.5:7b': 'Qwen 2.5 7B' }; if (nameMapping[ollamaName]) { return availableModels.find(m => m.name === nameMapping[ollamaName]); } const modelKeywords = ollamaName.split(':')[0].split('-'); return availableModels.find(model => { const modelName = model.name.toLowerCase(); return modelKeywords.some(keyword => keyword.length > 2 && modelName.includes(keyword) ); }); } async generateOllamaRecommendations(hardware, availableModels, installedModels) { const recommendations = []; const installedNames = new Set(installedModels.map(m => m.name.toLowerCase())); const compatibleModels = availableModels.filter(model => { const compatibility = this.compatibilityAnalyzer.calculateModelCompatibility(hardware, model); return compatibility.score >= 75 && model.frameworks?.includes('ollama'); }); for (const model of compatibleModels.slice(0, 5)) { const ollamaCommand = this.getOllamaCommand(model); if (ollamaCommand && !installedNames.has(ollamaCommand.split(' ')[2])) { const performance = await this.performanceAnalyzer.estimateModelPerformance(model, hardware); recommendations.push({ model, command: ollamaCommand, reason: this.getRecommendationReason(model, hardware), estimatedPerformance: performance, priority: this.calculatePriority(model, hardware) }); } } return recommendations.sort((a, b) => b.priority - a.priority); } async enrichWithPerformanceData(hardware, compatibility) { const performanceEstimates = new Map(); for (const model of [...compatibility.compatible, ...compatibility.marginal]) { try { const estimate = await this.performanceAnalyzer.estimateModelPerformance(model, hardware); performanceEstimates.set(model.name, estimate); model.performanceEstimate = estimate; model.tokensPerSecond = estimate.estimatedTokensPerSecond; model.loadTime = estimate.loadTimeEstimate; } catch (error) { this.logger.warn(`Failed to estimate performance for ${model.name}`, { error }); } } return { ...compatibility, performanceEstimates: Object.fromEntries(performanceEstimates) }; } async generateEnhancedRecommendations(hardware, results, ollamaIntegration, useCase) { const recommendations = { general: [], installedModels: [], cloudSuggestions: [], quickCommands: [] }; const generalRecs = this.compatibilityAnalyzer.generateRecommendations(hardware, results); recommendations.general.push(...generalRecs); if (ollamaIntegration.ollamaInfo.available) { if (ollamaIntegration.compatibleOllamaModels.length === 0) { recommendations.general.push('🦙 No compatible models installed in Ollama'); } else { recommendations.installedModels.push(`🦙 ${ollamaIntegration.compatibleOllamaModels.length} compatible models found in Ollama:`); ollamaIntegration.compatibleOllamaModels.forEach((model, index) => { const runningStatus = model.isRunning ? ' 🚀 (running)' : ''; const score = model.compatibilityScore || 'N/A'; recommendations.installedModels.push(`${index + 1}. 📦 ${model.name} (Score: ${score}/100)${runningStatus}`); }); const bestModel = ollamaIntegration.compatibleOllamaModels .sort((a, b) => (b.compatibilityScore || 0) - (a.compatibilityScore || 0))[0]; if (bestModel) { recommendations.quickCommands.push(`ollama run ${bestModel.name}`); } } this.logger.info('Searching for cloud recommendations...'); try { const cloudRecommendations = await this.searchOllamaCloudRecommendations(hardware, ollamaIntegration.compatibleOllamaModels); if (cloudRecommendations.length > 0) { this.logger.info(`Found ${cloudRecommendations.length} cloud recommendations`); recommendations.cloudSuggestions.push('💡 Recommended models from Ollama library for your hardware:'); cloudRecommendations.forEach((model, index) => { recommendations.cloudSuggestions.push(`${index + 1}. ollama pull ${model.identifier} - ${model.reason} (${model.pulls.toLocaleString()} pulls)`); recommendations.quickCommands.push(`ollama pull ${model.identifier}`); }); } else { this.logger.warn('No cloud recommendations found, using fallback'); this.addFallbackSuggestions(recommendations, ollamaIntegration.compatibleOllamaModels); } } catch (error) { this.logger.error('Failed to get cloud recommendations:', error); this.addFallbackSuggestions(recommendations, ollamaIntegration.compatibleOllamaModels); } } else { recommendations.general.push('🦙 Install Ollama for local LLM management: https://ollama.ai'); } const useCaseRecs = this.getUseCaseRecommendations(results, useCase); recommendations.general.push(...useCaseRecs); return recommendations; } addFallbackSuggestions(recommendations, installedModels) { const installedNames = new Set(installedModels.map(m => m.name.toLowerCase())); const allSuggestions = [ { name: 'qwen:0.5b', reason: 'Ultra-fast 0.5B model, runs on any hardware', minRAM: 1, tier: 'any' }, { name: 'tinyllama:1.1b', reason: 'Tiny but capable, perfect for testing', minRAM: 2, tier: 'any' }, { name: 'phi3:mini', reason: 'Microsoft\'s efficient 3.8B model with excellent reasoning', minRAM: 4, tier: 'low' }, { name: 'llama3.2:1b', reason: 'Meta\'s latest compact 1B model', minRAM: 2, tier: 'any' }, { name: 'llama3.2:3b', reason: 'Meta\'s balanced 3B model', minRAM: 4, tier: 'low' }, { name: 'gemma2:2b', reason: 'Google\'s optimized 2B model', minRAM: 3, tier: 'any' }, { name: 'mistral:7b', reason: 'High-quality European 7B model', minRAM: 8, tier: 'medium' }, { name: 'llama3.1:8b', reason: 'Meta\'s flagship 8B model', minRAM: 10, tier: 'medium' }, { name: 'qwen2.5:7b', reason: 'Advanced Chinese 7B model', minRAM: 8, tier: 'medium' }, { name: 'codellama:7b', reason: 'Specialized for coding tasks', minRAM: 8, tier: 'medium', specialty: 'code' }, { name: 'nomic-embed-text', reason: 'Best for text embeddings', minRAM: 2, tier: 'any', specialty: 'embeddings' } ]; const availableSuggestions = allSuggestions.filter(model => !installedNames.has(model.name) && !installedNames.has(model.name.split(':')[0]) ); if (availableSuggestions.length > 0) { recommendations.cloudSuggestions.push('💡 Curated model suggestions for your hardware:'); availableSuggestions.slice(0, 5).forEach((model, index) => { recommendations.cloudSuggestions.push(`${index + 1}. ollama pull ${model.name} - ${model.reason}`); recommendations.quickCommands.push(`ollama pull ${model.name}`); }); } } async searchOllamaCloudRecommendations(hardware, installedModels) { try { this.logger.info('Searching Ollama cloud for compatible models...'); const { getOllamaModelsIntegration } = require('./ollama/native-scraper'); const allModelsData = await getOllamaModelsIntegration([]); if (!allModelsData.recommendations || allModelsData.recommendations.length === 0) { this.logger.warn('No recommendations found from cloud search'); return []; } this.logger.info(`Found ${allModelsData.recommendations.length} total models from cloud`); const installedIdentifiers = new Set( installedModels.map(m => { const name = m.name.toLowerCase(); return name.split(':')[0]; }) ); this.logger.info(`Installed models identifiers: ${Array.from(installedIdentifiers).join(', ')}`); const hardwareTier = this.getHardwareTier(hardware); this.logger.info(`Hardware tier: ${hardwareTier}`); const compatibleModels = allModelsData.recommendations .filter(model => { const baseIdentifier = model.model_identifier.split(':')[0].toLowerCase(); const isNotInstalled = !installedIdentifiers.has(baseIdentifier) && !installedIdentifiers.has(model.model_identifier.toLowerCase()); if (!isNotInstalled) { this.logger.debug(`Skipping already installed model: ${model.model_identifier}`); } return isNotInstalled; }) .map(model => { const score = this.calculateCloudModelCompatibility(model, hardware); return { ...model, compatibilityScore: score, reason: this.getCloudModelReason(model, hardware) }; }) .filter(model => { const isCompatible = model.compatibilityScore >= 60; if (!isCompatible) { this.logger.debug(`Model ${model.model_identifier} has low compatibility score: ${model.compatibilityScore}`); } return isCompatible; }) .sort((a, b) => { if (b.compatibilityScore !== a.compatibilityScore) { return b.compatibilityScore - a.compatibilityScore; } return (b.pulls || 0) - (a.pulls || 0); }) .slice(0, 5); this.logger.info(`Final compatible models for recommendations: ${compatibleModels.length}`); compatibleModels.forEach(model => { this.logger.debug(`Recommending: ${model.model_identifier} (score: ${model.compatibilityScore}, pulls: ${model.pulls})`); }); return compatibleModels.map(model => ({ identifier: model.model_identifier, name: model.model_name, pulls: model.pulls || 0, reason: model.reason, score: model.compatibilityScore, size: this.extractModelSize(model.model_identifier), description: model.description || '' })); } catch (error) { this.logger.error('Error searching Ollama cloud recommendations:', error); return []; } } getHardwareTier(hardware) { if (hardware.memory.total >= 64 && hardware.gpu.vram >= 32) return 'ultra_high'; if (hardware.memory.total >= 32 && hardware.gpu.vram >= 16) return 'high'; if (hardware.memory.total >= 16 && hardware.gpu.vram >= 8) return 'medium'; if (hardware.memory.total >= 8) return 'low'; return 'ultra_low'; } calculateCloudModelCompatibility(model, hardware) { let score = 50; const sizeMatch = model.model_identifier.match(/(\d+\.?\d*)[bm]/i); let modelSizeB = 1; if (sizeMatch) { const num = parseFloat(sizeMatch[1]); const unit = sizeMatch[0].slice(-1).toLowerCase(); modelSizeB = unit === 'm' ? num / 1000 : num; } const estimatedRAM = modelSizeB * 1.2; const ramRatio = hardware.memory.total / estimatedRAM; if (ramRatio >= 3) { score += 40; } else if (ramRatio >= 2) { score += 30; } else if (ramRatio >= 1.5) { score += 20; } else if (ramRatio >= 1.2) { score += 10; } else { score -= 20; } if (modelSizeB <= 0.5) { score += 25; } else if (modelSizeB <= 1) { score += 20; } else if (modelSizeB <= 3) { score += 15; } else if (modelSizeB <= 7) { score += 10; } else if (modelSizeB <= 13) { score += 5; } else { score -= 15; } const hardwareTier = this.getHardwareTier(hardware); switch (hardwareTier) { case 'ultra_high': score += 15; break; case 'high': score += 10; break; case 'medium': score += 5; break; case 'low': if (modelSizeB <= 3) score += 5; break; case 'ultra_low': if (modelSizeB <= 1) score += 10; else score -= 10; break; } if (hardware.cpu.cores >= 8) { score += 10; } else if (hardware.cpu.cores >= 4) { score += 5; } else if (hardware.cpu.cores < 4) { score -= 5; } const pulls = model.pulls || 0; if (pulls > 10000000) { score += 15; } else if (pulls > 1000000) { score += 10; } else if (pulls > 100000) { score += 5; } if (model.model_type === 'official') { score += 8; } const identifier = model.model_identifier.toLowerCase(); if (identifier.includes('tinyllama') || identifier.includes('phi3') || identifier.includes('qwen')) { score += 5; } if (identifier.includes('code') && hardware.cpu.cores >= 6) { score += 5; } if (identifier.includes('mini') || identifier.includes('tiny')) { score += 8; } if (hardware.cpu.architecture === 'Apple Silicon') { score += 5; } this.logger.debug(`Model ${model.model_identifier}: size=${modelSizeB}B, RAM ratio=${ramRatio.toFixed(2)}, score=${score}`); return Math.max(0, Math.min(100, Math.round(score))); } getCloudModelReason(model, hardware) { const identifier = model.model_identifier.toLowerCase(); const sizeMatch = model.model_identifier.match(/(\d+\.?\d*)[bm]/i); const modelSizeB = sizeMatch ? (sizeMatch[0].slice(-1).toLowerCase() === 'm' ? parseFloat(sizeMatch[1]) / 1000 : parseFloat(sizeMatch[1])) : 1; if (identifier.includes('qwen') && modelSizeB <= 1) { return 'Ultra-efficient Chinese model, great for limited hardware'; } if (identifier.includes('tinyllama')) { return 'Tiny but capable, perfect for testing and light tasks'; } if (identifier.includes('phi3') && identifier.includes('mini')) { return 'Microsoft\'s efficient model with excellent reasoning'; } if (identifier.includes('gemma') && modelSizeB <= 2) { return 'Google\'s compact model, well-optimized'; } if (identifier.includes('mistral') && modelSizeB <= 7) { return 'High-quality European model, excellent performance'; } if (identifier.includes('llama3.2') && modelSizeB <= 3) { return 'Meta\'s latest compact model, state-of-the-art'; } if (identifier.includes('code')) { return 'Specialized for coding tasks'; } const ramRatio = hardware.memory.total / (modelSizeB * 0.6); if (modelSizeB <= 1) { return 'Ultra-small model, runs very fast on your hardware'; } else if (modelSizeB <= 3 && ramRatio >= 2) { return 'Small model with good performance balance'; } else if (modelSizeB <= 7 && ramRatio >= 1.5) { return 'Medium-sized model, good capabilities'; } else if (ramRatio >= 1.2) { return 'Should run well on your system'; } else { return 'Recommended with quantization for your hardware'; } } extractModelSize(identifier) { const sizeMatch = identifier.match(/(\d+\.?\d*)[bm]/i); if (sizeMatch) { const num = parseFloat(sizeMatch[1]); const unit = sizeMatch[0].slice(-1).toUpperCase(); return `${num}${unit}`; } return 'Unknown'; } getOllamaCommand(model) { const mapping = { 'TinyLlama 1.1B': 'ollama pull tinyllama:1.1b', 'Qwen 0.5B': 'ollama pull qwen:0.5b', 'Gemma 2B': 'ollama pull gemma2:2b', 'Phi-3 Mini 3.8B': 'ollama pull phi3:mini', 'Llama 3.2 3B': 'ollama pull llama3.2:3b', 'Llama 3.1 8B': 'ollama pull llama3.1:8b', 'Mistral 7B v0.3': 'ollama pull mistral:7b', 'CodeLlama 7B': 'ollama pull codellama:7b', 'Qwen 2.5 7B': 'ollama pull qwen2.5:7b' }; return mapping[model.name] || null; } getRecommendationReason(model, hardware) { if (model.specialization === 'code') { return 'Excellent for coding tasks'; } if (hardware.memory.total >= 16 && model.size.includes('8B')) { return 'Perfect size for your RAM capacity'; } if (model.category === 'small' && hardware.memory.total < 16) { return 'Optimized for systems with limited RAM'; } return 'Great balance of performance and efficiency'; } calculatePriority(model, hardware) { let priority = 50; const modelSize = this.parseModelSize(model.size); const requiredRAM = model.requirements?.ram || 4; const ramRatio = hardware.memory.total / requiredRAM; if (ramRatio >= 2) priority += 20; else if (ramRatio >= 1.5) priority += 10; else if (ramRatio < 1) priority -= 20; if (modelSize <= 1) priority += 15; else if (modelSize <= 3) priority += 10; else if (modelSize <= 7) priority += 5; else if (modelSize > 30) priority -= 15; if (model.specialization === 'code') priority += 15; else if (model.specialization === 'chat') priority += 10; else if (model.specialization === 'embeddings') priority += 5; if (model.year >= 2024) priority += 10; else if (model.year >= 2023) priority += 5; if (hardware.gpu.dedicated && model.requirements?.vram > 0) { if (hardware.gpu.vram >= model.requirements.vram) { priority += 10; } else { priority -= 5; } } if (hardware.cpu.architecture === 'Apple Silicon' && model.frameworks?.includes('llama.cpp')) { priority += 8; } return Math.max(0, priority); } parseModelSize(sizeString) { const match = sizeString.match(/(\d+\.?\d*)[BM]/i); if (!match) return 1; const num = parseFloat(match[1]); const unit = match[0].slice(-1).toUpperCase(); return unit === 'B' ? num : num / 1000; } getUseCaseRecommendations(results, useCase) { const recommendations = []; switch (useCase) { case 'code': const codeModels = results.compatible.filter(m => m.specialization === 'code'); if (codeModels.length > 0) { recommendations.push(`💻 Top coding model: ${codeModels[0].name}`); } break; case 'chat': const chatModels = results.compatible.filter(m => m.specialization === 'chat' || m.specialization === 'general' ); if (chatModels.length > 0) { recommendations.push(`💬 Best chat model: ${chatModels[0].name}`); } break; case 'multimodal': const multiModels = results.compatible.filter(m => m.multimodal); if (multiModels.length > 0) { recommendations.push(`🖼️ Multimodal option: ${multiModels[0].name}`); } break; } return recommendations; } generateEnhancedSummary(hardware, results, ollamaIntegration) { const baseSummary = this.generateSummary(hardware, results); return { ...baseSummary, ollama: { available: ollamaIntegration.ollamaInfo.available, installedModels: ollamaIntegration.compatibleOllamaModels.length, runningModels: ollamaIntegration.currentlyRunning.length, recommendedInstalls: ollamaIntegration.recommendedPulls.length }, hardwareTier: this.getHardwareTier(hardware), topPerformanceModel: this.getTopPerformanceModel(results) }; } getTopPerformanceModel(results) { if (results.compatible.length === 0) return null; const sorted = results.compatible .filter(m => m.performanceEstimate) .sort((a, b) => (b.performanceEstimate.estimatedTokensPerSecond || 0) - (a.performanceEstimate.estimatedTokensPerSecond || 0)); return sorted[0] || results.compatible[0]; } async analyzeOllamaModel(modelName) { try { const [hardware, model] = await Promise.all([ this.getSystemInfo(), Promise.resolve(this.findModel(modelName)) ]); if (!model) { throw new Error(`Model "${modelName}" not found in database`); } const [localModels, runningModels] = await Promise.all([ this.ollamaClient.getLocalModels().catch(() => []), this.ollamaClient.getRunningModels().catch(() => []) ]); const isInstalled = localModels.some(m => m.name.toLowerCase().includes(modelName.toLowerCase())); const isRunning = runningModels.some(m => m.name.toLowerCase().includes(modelName.toLowerCase())); const [compatibility, performance] = await Promise.all([ Promise.resolve(this.compatibilityAnalyzer.calculateModelCompatibility(hardware, model)), this.performanceAnalyzer.estimateModelPerformance(model, hardware) ]); let benchmarkResults = null; if (isInstalled) { try { benchmarkResults = await this.performanceAnalyzer.benchmarkInferenceSpeed( modelName, hardware, this.ollamaClient ); } catch (error) { this.logger.warn(`Benchmark failed for ${modelName}`, { error }); } } return { model, hardware, status: { installed: isInstalled, running: isRunning, canRun: compatibility.score >= 60 }, compatibility, performance, benchmarkResults, recommendations: this.generateModelSpecificRecommendations(model, hardware, compatibility) }; } catch (error) { this.logger.error('Model analysis failed', { error: error.message, component: 'LLMChecker', method: 'analyzeOllamaModel' }); throw error; } } generateModelSpecificRecommendations(model, hardware, compatibility) { const recommendations = []; if (compatibility.score < 60) { recommendations.push('⚠️ Model may not run well on this hardware'); recommendations.push('💡 Consider using heavy quantization (Q2_K, Q3_K_M)'); } else if (compatibility.score < 75) { recommendations.push('✅ Model should run with some optimizations'); recommendations.push('🎯 Use Q4_K_M quantization for best balance'); } else { recommendations.push('🚀 Model should run excellently on this hardware'); if (hardware.memory.total >= 32) { recommendations.push('💎 You can use higher quality quantization (Q5_K_M, Q6_K)'); } } if (hardware.gpu.dedicated && hardware.gpu.vram >= (model.requirements?.vram || 0)) { recommendations.push('🎮 Enable GPU acceleration for faster inference'); } return recommendations; } filterModels(models, filter) { switch (filter.toLowerCase()) { case 'local': return models.filter(m => m.type === 'local'); case 'cloud': return models.filter(m => m.type === 'cloud'); case 'ultra_small': return models.filter(m => m.category === 'ultra_small'); case 'small': return models.filter(m => m.category === 'small'); case 'medium': return models.filter(m => m.category === 'medium'); case 'large': return models.filter(m => m.category === 'large'); case 'code': return models.filter(m => m.specialization === 'code'); case 'chat': return models.filter(m => m.specialization === 'chat' || !m.specialization); case 'multimodal': return models.filter(m => m.specialization === 'multimodal' || m.multimodal); case 'embeddings': return models.filter(m => m.specialization === 'embeddings'); default: return models; } } generateSummary(hardware, compatibility) { return { grade: this.calculateGrade(compatibility), systemClass: this.getSystemClass(hardware), compatibleCount: compatibility.compatible.length, marginalCount: compatibility.marginal.length, incompatibleCount: compatibility.incompatible.length, totalModels: compatibility.compatible.length + compatibility.marginal.length + compatibility.incompatible.length }; } calculateGrade(compatibility) { const total = compatibility.compatible.length + compatibility.marginal.length + compatibility.incompatible.length; const compatiblePercent = total > 0 ? (compatibility.compatible.length / total) * 100 : 0; if (compatiblePercent >= 80) return 'A'; if (compatiblePercent >= 60) return 'B'; if (compatiblePercent >= 40) return 'C'; if (compatiblePercent >= 20) return 'D'; return 'F'; } getSystemClass(hardware) { if (hardware.memory.total >= 32 && hardware.gpu.vram >= 16) return 'High End'; if (hardware.memory.total >= 16 && hardware.gpu.vram >= 8) return 'Mid Range'; if (hardware.memory.total >= 8) return 'Budget'; return 'Entry Level'; } async getOllamaInfo() { return await this.integrateOllamaModels(await this.getSystemInfo(), []); } async getSystemInfo() { return await this.hardwareDetector.getSystemInfo(); } getAllModels() { return this.expandedModelsDatabase.getAllModels(); } findModel(name) { return this.expandedModelsDatabase.findModel ? this.expandedModelsDatabase.findModel(name) : this.getAllModels().find(m => m.name.toLowerCase().includes(name.toLowerCase())); } async updateOllamaDatabase() { try { this.logger.info('Updating Ollama model database...'); const data = await this.ollamaScraper.scrapeAllModels(false); // false = usar cache si es válido this.logger.info(`Database updated with ${data.total_count} models`); return data; } catch (error) { this.logger.warn('Failed to update Ollama database', { error: error.message }); return null; } } async forceUpdateOllamaDatabase() { try { this.logger.info('Force updating Ollama model database...'); const data = await this.ollamaScraper.scrapeAllModels(true); // true = forzar actualización this.logger.info(`Database force updated with ${data.total_count} models`); return data; } catch (error) { this.logger.error('Failed to force update Ollama database', { error: error.message }); throw error; } } async generateIntelligentRecommendations(hardware) { try { this.logger.info('Generating intelligent recommendations...'); // Obtener todos los modelos de Ollama const ollamaData = await this.ollamaScraper.scrapeAllModels(false); const allModels = ollamaData.models || []; if (allModels.length === 0) { this.logger.warn('No Ollama models available for recommendations'); return null; } // Generar recomendaciones inteligentes const recommendations = this.intelligentRecommender.getBestModelsForHardware(hardware, allModels); const summary = this.intelligentRecommender.generateRecommendationSummary(recommendations, hardware); this.logger.info(`Generated recommendations for ${Object.keys(recommendations).length} categories`); return { recommendations, summary, totalModelsAnalyzed: allModels.length, generatedAt: new Date().toISOString() }; } catch (error) { this.logger.error('Failed to generate intelligent recommendations', { error: error.message }); return null; } } async getOllamaModelStats() { try { const data = await this.ollamaScraper.scrapeAllModels(false); return this.ollamaScraper.getStats ? await this.ollamaScraper.getStats() : { total_models: data.total_count, last_updated: data.cached_at }; } catch (error) { this.logger.error('Failed to get Ollama stats', { error: error.message }); return null; } } } module.exports = LLMChecker;