UNPKG

codecrucible-synth

Version:

Production-Ready AI Development Platform with Multi-Voice Synthesis, Smithery MCP Integration, Enterprise Security, and Zero-Timeout Reliability

253 lines (219 loc) 7.62 kB
/** * Model Selection Coordinator - Single Source of Truth for Model Selection * Implements audit report recommendations for fixing model inconsistency * Created: August 21, 2025 */ import { EventEmitter } from 'events'; import { Logger } from './logger.js'; export interface ModelSelectionConfig { provider: 'ollama' | 'lm-studio' | 'auto'; model: string; reason: string; confidence: number; taskType?: string; } export interface ProviderCapabilities { provider: string; available: boolean; models: string[]; preferredModels: string[]; strengths: string[]; responseTime: string; } /** * Single authoritative model selection system * Resolves conflicts between multiple routing systems */ export class ModelSelectionCoordinator extends EventEmitter { private logger: Logger; private selectedModels: Map<string, ModelSelectionConfig> = new Map(); private providerCapabilities: Map<string, ProviderCapabilities> = new Map(); private routingHistory: ModelSelectionConfig[] = []; // Unified configuration (single source of truth) private readonly modelPriority = { ollama: { preferred: ['qwen2.5-coder:7b', 'qwen2.5-coder:3b', 'deepseek-coder:8b'], fallback: ['llama3.2:latest', 'gemma:latest'], taskTypes: ['analysis', 'planning', 'complex', 'multi-file'], }, 'lm-studio': { preferred: ['codellama-7b-instruct', 'gemma-2b-it'], fallback: ['qwen/qwen2.5-coder-14b'], taskTypes: ['template', 'edit', 'format', 'boilerplate'], }, }; constructor() { super(); this.logger = new Logger('ModelSelectionCoordinator'); } /** * Select model based on task requirements and provider availability * This is the ONLY method that should be used for model selection */ async selectModel( provider: string, taskType: string, availableModels: string[] = [] ): Promise<ModelSelectionConfig> { this.logger.info(`Selecting model for ${provider} - task: ${taskType}`); // Check if we already have a selection for this provider const existing = this.selectedModels.get(provider); if (existing && existing.confidence > 0.8) { this.logger.info(`Using cached selection: ${existing.model}`); return existing; } // Determine best model based on unified configuration const config = this.modelPriority[provider as keyof typeof this.modelPriority]; if (!config) { throw new Error(`Unknown provider: ${provider}`); } // Find best available model let selectedModel: string | undefined; let confidence = 1.0; let reason = 'Model selected based on availability and task type'; // Try preferred models first for (const preferred of config.preferred) { if (availableModels.length === 0 || availableModels.includes(preferred)) { selectedModel = preferred; reason = `Preferred model for ${taskType} tasks`; break; } } // Fall back to any available model if (!selectedModel && availableModels.length > 0) { selectedModel = availableModels[0]; confidence = 0.7; reason = 'Using first available model'; } // Use fallback if nothing available if (!selectedModel) { selectedModel = config.fallback[0]; confidence = 0.5; reason = 'Using fallback model (provider may be unavailable)'; } const selection: ModelSelectionConfig = { provider: provider as 'ollama' | 'lm-studio', model: selectedModel, reason, confidence, taskType, }; // Cache the selection this.selectedModels.set(provider, selection); this.routingHistory.push(selection); // Emit event for monitoring this.emit('model-selected', selection); this.logger.info(`Selected ${selectedModel} with confidence ${confidence}: ${reason}`); return selection; } /** * Get the currently selected model for a provider * Returns consistent model throughout the session */ getSelectedModel(provider: string): string { const selection = this.selectedModels.get(provider); if (!selection) { this.logger.warn(`No model selected for ${provider}, using default`); return ( this.modelPriority[provider as keyof typeof this.modelPriority]?.preferred[0] || 'unknown' ); } return selection.model; } /** * Update provider capabilities (called when providers report available models) */ updateProviderCapabilities(provider: string, capabilities: Partial<ProviderCapabilities>): void { const existing = this.providerCapabilities.get(provider) || { provider, available: false, models: [], preferredModels: [], strengths: [], responseTime: 'unknown', }; this.providerCapabilities.set(provider, { ...existing, ...capabilities, }); this.logger.info(`Updated capabilities for ${provider}:`, capabilities); this.emit('capabilities-updated', { provider, capabilities }); } /** * Determine which provider to use based on task complexity * This replaces the conflicting routing logic in multiple places */ async routeToProvider( taskType: string, complexity: 'simple' | 'complex' | 'auto' ): Promise<string> { // Simple routing based on task complexity if (complexity === 'simple' || ['template', 'edit', 'format'].includes(taskType)) { // Check if LM Studio is available const lmStudioCap = this.providerCapabilities.get('lm-studio'); if (lmStudioCap?.available) { return 'lm-studio'; } } // Default to Ollama for complex tasks or when LM Studio unavailable return 'ollama'; } /** * Get routing statistics for monitoring */ getRoutingStats(): any { const stats = { totalRoutings: this.routingHistory.length, providerDistribution: {} as Record<string, number>, averageConfidence: 0, modelUsage: {} as Record<string, number>, }; for (const routing of this.routingHistory) { // Provider distribution stats.providerDistribution[routing.provider] = (stats.providerDistribution[routing.provider] || 0) + 1; // Model usage stats.modelUsage[routing.model] = (stats.modelUsage[routing.model] || 0) + 1; // Confidence sum stats.averageConfidence += routing.confidence; } if (this.routingHistory.length > 0) { stats.averageConfidence /= this.routingHistory.length; } return stats; } /** * Clear cached selections (useful for testing or provider changes) */ clearSelections(): void { this.selectedModels.clear(); this.logger.info('Cleared all cached model selections'); } /** * Export current configuration for persistence */ exportConfiguration(): any { return { selectedModels: Array.from(this.selectedModels.entries()), providerCapabilities: Array.from(this.providerCapabilities.entries()), routingHistory: this.routingHistory.slice(-100), // Keep last 100 for analysis }; } /** * Import configuration from persistence */ importConfiguration(config: any): void { if (config.selectedModels) { this.selectedModels = new Map(config.selectedModels); } if (config.providerCapabilities) { this.providerCapabilities = new Map(config.providerCapabilities); } if (config.routingHistory) { this.routingHistory = config.routingHistory; } this.logger.info('Imported configuration successfully'); } } // Singleton instance for global access export const modelCoordinator = new ModelSelectionCoordinator();