UNPKG

@quantumai/quantum-cli-core

Version:

Quantum CLI Core - Multi-LLM Collaboration System

633 lines 27.9 kB
/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import { QueryType } from '../types.js'; /** * Default recommendation engine configuration */ export const DEFAULT_RECOMMENDATION_CONFIG = { enableContextualRecommendations: true, enableAdaptiveLearning: true, minConfidenceThreshold: 0.6, weights: { historicalPerformance: 0.3, userPreference: 0.25, contextualMatch: 0.2, costOptimization: 0.1, qualityOptimization: 0.1, timeOptimization: 0.05, }, historicalLookbackDays: 30, enablePredictiveRecommendations: true, defaultFallbackStrategy: 'next_best', maxAlternatives: 3, }; /** * Intelligent recommendation engine for context-aware model selection */ export class RecommendationEngine { config; recommendations = new Map(); // userId -> recommendations contextualPatterns = new Map(); // userId -> patterns performanceHistory = new Map(); // userId -> history constructor(config = {}) { this.config = { ...DEFAULT_RECOMMENDATION_CONFIG, ...config }; } /** * Generates intelligent recommendation for query */ generateRecommendation(userId, query, analysis, context, userHistory, preferences, thresholds) { const queryId = this.generateQueryId(); // Analyze contextual factors const contextFactors = this.analyzeContextFactors(analysis, context, userHistory); // Find similar historical queries const similarQueries = this.findSimilarQueries(userId, query, analysis, userHistory); // Calculate provider scores based on multiple factors const providerScores = this.calculateProviderScores(userId, analysis, context, contextFactors, similarQueries, preferences, thresholds); // Generate reasoning for recommendation const reasoning = this.generateRecommendationReasoning(providerScores, contextFactors, similarQueries, preferences); // Select primary and alternative providers const sortedProviders = providerScores.sort((a, b) => b.score - a.score); const primaryProvider = sortedProviders[0]; const alternativeProviders = sortedProviders .slice(1, this.config.maxAlternatives + 1) .filter(p => p.score > 0.3) .map(p => p.providerId); // Calculate expected outcome const expectedOutcome = this.calculateExpectedOutcome(primaryProvider.providerId, analysis, contextFactors, similarQueries); // Generate fallback strategy const fallbackStrategy = this.generateFallbackStrategy(sortedProviders, context, thresholds); // Identify adaptive factors const adaptiveFactors = this.identifyAdaptiveFactors(userId, primaryProvider.providerId, analysis.type, userHistory); // Calculate overall confidence const confidence = this.calculateRecommendationConfidence(primaryProvider, reasoning, contextFactors, similarQueries.length); const recommendation = { queryId, primaryProvider: primaryProvider.providerId, alternativeProviders, confidence, reasoning, contextFactors, expectedOutcome, fallbackStrategy, adaptiveFactors, }; // Store recommendation for learning this.storeRecommendation(userId, recommendation); return recommendation; } /** * Analyzes contextual factors affecting model selection */ analyzeContextFactors(analysis, context, userHistory) { const factors = []; // Time of day factor factors.push({ type: 'time_of_day', value: context.timeOfDay / 23, impact: this.calculateTimeOfDayImpact(context.timeOfDay, userHistory), description: `Time: ${context.timeOfDay}:00 - ${this.getTimeOfDayDescription(context.timeOfDay)}`, }); // Query complexity factor factors.push({ type: 'query_complexity', value: analysis.complexity, impact: analysis.complexity > 0.7 ? 0.3 : analysis.complexity < 0.3 ? -0.2 : 0, description: `Complexity: ${(analysis.complexity * 100).toFixed(0)}% - ${this.getComplexityDescription(analysis.complexity)}`, }); // Domain specificity factor const domainSpecificity = analysis.domain ? 0.8 : 0.2; factors.push({ type: 'domain_specificity', value: domainSpecificity, impact: domainSpecificity > 0.5 ? 0.2 : -0.1, description: analysis.domain ? `Specialized domain: ${analysis.domain}` : 'General domain query', }); // Urgency factor const urgencyMapping = { low: 0.2, medium: 0.5, high: 0.8, critical: 1.0 }; const urgencyValue = urgencyMapping[context.urgency]; factors.push({ type: 'urgency', value: urgencyValue, impact: urgencyValue > 0.7 ? 0.4 : urgencyValue < 0.3 ? -0.2 : 0, description: `Urgency: ${context.urgency} - ${this.getUrgencyDescription(context.urgency)}`, }); // Cost budget factor const budgetMapping = { strict: 0.2, limited: 0.4, normal: 0.7, unlimited: 1.0 }; const budgetValue = budgetMapping[context.budget]; factors.push({ type: 'cost_budget', value: budgetValue, impact: budgetValue < 0.5 ? -0.3 : budgetValue > 0.8 ? 0.2 : 0, description: `Budget: ${context.budget} - ${this.getBudgetDescription(context.budget)}`, }); // Quality requirement factor const qualityMapping = { basic: 0.2, good: 0.5, high: 0.8, perfect: 1.0 }; const qualityValue = qualityMapping[context.qualityRequirement]; factors.push({ type: 'quality_requirement', value: qualityValue, impact: qualityValue > 0.7 ? 0.3 : qualityValue < 0.3 ? -0.2 : 0, description: `Quality: ${context.qualityRequirement} - ${this.getQualityDescription(context.qualityRequirement)}`, }); return factors; } /** * Finds similar queries from user history */ findSimilarQueries(userId, query, analysis, userHistory) { const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - this.config.historicalLookbackDays); const recentHistory = userHistory.filter(r => r.timestamp > cutoffDate); const similarQueries = []; for (const record of recentHistory) { const similarity = this.calculateQuerySimilarity(analysis, record.analysis); if (similarity > 0.6) { similarQueries.push({ query: record, similarity }); } } return similarQueries .sort((a, b) => b.similarity - a.similarity) .slice(0, 10) .map(item => item.query); } /** * Calculates provider scores based on multiple factors */ calculateProviderScores(userId, analysis, context, contextFactors, similarQueries, preferences, thresholds) { const availableProviders = ['openai-gpt4', 'anthropic-claude', 'google-gemini']; // Could be dynamic const providerScores = []; for (const providerId of availableProviders) { const factors = {}; // Historical performance factor factors.historicalPerformance = this.calculateHistoricalPerformance(providerId, analysis.type, similarQueries); // User preference factor factors.userPreference = this.calculateUserPreference(providerId, analysis.type, preferences); // Contextual match factor factors.contextualMatch = this.calculateContextualMatch(providerId, analysis, contextFactors); // Cost optimization factor factors.costOptimization = this.calculateCostOptimization(providerId, context, thresholds); // Quality optimization factor factors.qualityOptimization = this.calculateQualityOptimization(providerId, analysis, context); // Time optimization factor factors.timeOptimization = this.calculateTimeOptimization(providerId, context, similarQueries); // Calculate weighted total score let totalScore = 0; for (const [factor, value] of Object.entries(factors)) { const weight = this.config.weights[factor]; totalScore += value * weight; } providerScores.push({ providerId, score: Math.max(0, Math.min(1, totalScore)), factors, }); } return providerScores; } /** * Calculates historical performance score for a provider */ calculateHistoricalPerformance(providerId, queryType, similarQueries) { const providerQueries = similarQueries.filter(q => q.selectedProvider === providerId); if (providerQueries.length === 0) { return 0.5; // Neutral score for no history } // Calculate average satisfaction const ratedQueries = providerQueries.filter(q => q.userRating); const avgSatisfaction = ratedQueries.length > 0 ? ratedQueries.reduce((sum, q) => sum + (q.userRating - 1), 0) / (ratedQueries.length * 4) : 0.5; // Calculate reliability (satisfied queries ratio) const satisfiedRatio = providerQueries.filter(q => q.satisfied).length / providerQueries.length; // Weight recency (more recent queries have higher impact) const now = Date.now(); const weightedSatisfaction = providerQueries.reduce((sum, q) => { const age = (now - q.timestamp.getTime()) / (1000 * 60 * 60 * 24); // days const weight = Math.exp(-age / 7); // Exponential decay with 7-day half-life const satisfaction = q.userRating ? (q.userRating - 1) / 4 : (q.satisfied ? 0.7 : 0.3); return sum + satisfaction * weight; }, 0); const totalWeight = providerQueries.reduce((sum, q) => { const age = (now - q.timestamp.getTime()) / (1000 * 60 * 60 * 24); return sum + Math.exp(-age / 7); }, 0); const recentSatisfaction = totalWeight > 0 ? weightedSatisfaction / totalWeight : 0.5; // Combine factors return (avgSatisfaction * 0.4 + satisfiedRatio * 0.3 + recentSatisfaction * 0.3); } /** * Calculates user preference score for a provider */ calculateUserPreference(providerId, queryType, preferences) { const relevantPrefs = preferences.filter(p => p.providerId === providerId && p.queryType === queryType); if (relevantPrefs.length === 0) { return 0.5; // Neutral score for no preferences } // Use the most recent preference const mostRecent = relevantPrefs.reduce((latest, pref) => pref.lastUpdated > latest.lastUpdated ? pref : latest); return mostRecent.preferenceScore * mostRecent.confidence; } /** * Calculates contextual match score */ calculateContextualMatch(providerId, analysis, contextFactors) { // Provider-specific strengths (could be loaded from configuration) const providerStrengths = { 'openai-gpt4': { code: 0.9, creative: 0.8, analysis: 0.8, general: 0.8, complexity_high: 0.9, response_time: 0.7, }, 'anthropic-claude': { creative: 0.9, analysis: 0.9, security: 0.9, general: 0.8, quality_high: 0.9, safety: 0.9, }, 'google-gemini': { general: 0.8, code: 0.7, cost_efficient: 0.9, multilingual: 0.8, response_time: 0.8, }, }; const strengths = providerStrengths[providerId] || {}; let matchScore = 0; let totalWeight = 0; // Query type match const queryTypeScore = strengths[analysis.type] || 0.5; matchScore += queryTypeScore * 0.4; totalWeight += 0.4; // Complexity match if (analysis.complexity > 0.7 && strengths.complexity_high) { matchScore += strengths.complexity_high * 0.2; totalWeight += 0.2; } // Context factor matches for (const factor of contextFactors) { let factorScore = 0; if (factor.type === 'urgency' && factor.value > 0.7 && strengths.response_time) { factorScore = strengths.response_time; } else if (factor.type === 'quality_requirement' && factor.value > 0.7 && strengths.quality_high) { factorScore = strengths.quality_high; } else if (factor.type === 'cost_budget' && factor.value < 0.5 && strengths.cost_efficient) { factorScore = strengths.cost_efficient; } if (factorScore > 0) { matchScore += factorScore * 0.1; totalWeight += 0.1; } } return totalWeight > 0 ? matchScore / totalWeight : 0.5; } /** * Calculates cost optimization score */ calculateCostOptimization(providerId, context, _thresholds) { // Cost rankings (could be loaded from configuration) const costRankings = { 'google-gemini': 0.9, // Most cost-effective 'openai-gpt4': 0.5, // Medium cost 'anthropic-claude': 0.3, // Higher cost but high value }; const baseCostScore = costRankings[providerId] || 0.5; // Adjust based on budget context const budgetMapping = { strict: 1.0, limited: 0.8, normal: 0.5, unlimited: 0.2 }; const budgetWeight = budgetMapping[context.budget]; return baseCostScore * budgetWeight + (1 - budgetWeight) * 0.5; } /** * Calculates quality optimization score */ calculateQualityOptimization(providerId, analysis, context) { // Quality rankings for different types const qualityRankings = { 'anthropic-claude': { [QueryType.CREATIVE]: 0.95, [QueryType.ANALYSIS]: 0.9, [QueryType.SECURITY]: 0.95, [QueryType.CODE]: 0.8, [QueryType.GENERAL]: 0.85, }, 'openai-gpt4': { [QueryType.CODE]: 0.95, [QueryType.ANALYSIS]: 0.85, [QueryType.CREATIVE]: 0.8, [QueryType.SECURITY]: 0.8, [QueryType.GENERAL]: 0.85, }, 'google-gemini': { [QueryType.GENERAL]: 0.8, [QueryType.CODE]: 0.75, [QueryType.CREATIVE]: 0.7, [QueryType.ANALYSIS]: 0.75, [QueryType.SECURITY]: 0.7, }, }; const baseQualityScore = qualityRankings[providerId]?.[analysis.type] || 0.5; // Adjust based on quality requirement const qualityMapping = { basic: 0.3, good: 0.6, high: 0.8, perfect: 1.0 }; const qualityWeight = qualityMapping[context.qualityRequirement]; return baseQualityScore * qualityWeight + (1 - qualityWeight) * 0.5; } /** * Calculates time optimization score */ calculateTimeOptimization(providerId, context, similarQueries) { // Speed rankings (could be based on actual performance data) const speedRankings = { 'google-gemini': 0.9, // Fastest 'openai-gpt4': 0.7, // Medium speed 'anthropic-claude': 0.6, // More thoughtful, slower }; const baseSpeedScore = speedRankings[providerId] || 0.5; // Adjust based on urgency const urgencyMapping = { low: 0.2, medium: 0.5, high: 0.8, critical: 1.0 }; const urgencyWeight = urgencyMapping[context.urgency]; // Factor in historical response times for this provider const providerQueries = similarQueries.filter(q => q.selectedProvider === providerId); let historicalSpeedScore = 0.5; if (providerQueries.length > 0) { const avgResponseTime = providerQueries.reduce((sum, q) => sum + q.responseTime, 0) / providerQueries.length; historicalSpeedScore = Math.max(0, Math.min(1, 1 - (avgResponseTime / 10000))); // Normalize to 0-1 (10s = 0) } return (baseSpeedScore * 0.6 + historicalSpeedScore * 0.4) * urgencyWeight + (1 - urgencyWeight) * 0.5; } /** * Helper methods for descriptive text */ getTimeOfDayDescription(hour) { if (hour < 6) return 'early morning'; if (hour < 12) return 'morning'; if (hour < 18) return 'afternoon'; if (hour < 22) return 'evening'; return 'night'; } getComplexityDescription(complexity) { if (complexity < 0.3) return 'simple'; if (complexity < 0.6) return 'moderate'; if (complexity < 0.8) return 'complex'; return 'very complex'; } getUrgencyDescription(urgency) { const descriptions = { low: 'no rush, quality over speed', medium: 'balanced timing expectations', high: 'needs quick turnaround', critical: 'immediate response required', }; return descriptions[urgency] || 'normal timing'; } getBudgetDescription(budget) { const descriptions = { strict: 'minimize costs', limited: 'cost-conscious', normal: 'balanced cost-quality', unlimited: 'quality prioritized', }; return descriptions[budget] || 'normal budget'; } getQualityDescription(quality) { const descriptions = { basic: 'acceptable quality', good: 'good quality expected', high: 'high quality required', perfect: 'perfect quality essential', }; return descriptions[quality] || 'normal quality'; } calculateTimeOfDayImpact(hour, _userHistory) { // Analyze if user has different satisfaction patterns at different times // This is a simplified version - real implementation would analyze historical data if (hour >= 9 && hour <= 17) return 0.1; // Work hours - slight positive impact if (hour >= 22 || hour <= 6) return -0.1; // Late/early hours - slight negative impact return 0; } calculateQuerySimilarity(analysis1, analysis2) { let similarity = 0; // Type similarity (high weight) if (analysis1.type === analysis2.type) similarity += 0.4; // Domain similarity if (analysis1.domain === analysis2.domain && analysis1.domain) similarity += 0.2; // Language similarity if (analysis1.language === analysis2.language && analysis1.language) similarity += 0.1; // Framework similarity const commonFrameworks = analysis1.frameworks.filter(f => analysis2.frameworks.includes(f)); if (commonFrameworks.length > 0) { similarity += (commonFrameworks.length / Math.max(analysis1.frameworks.length, analysis2.frameworks.length)) * 0.1; } // Complexity similarity const complexityDiff = Math.abs(analysis1.complexity - analysis2.complexity); similarity += (1 - complexityDiff) * 0.1; // Keyword similarity const commonKeywords = analysis1.keywords.filter(k => analysis2.keywords.includes(k)); if (commonKeywords.length > 0) { similarity += (commonKeywords.length / Math.max(analysis1.keywords.length, analysis2.keywords.length)) * 0.1; } return similarity; } generateRecommendationReasoning(providerScores, contextFactors, similarQueries, preferences) { const reasoning = []; const topProvider = providerScores[0]; // Historical performance reasoning if (topProvider.factors.historicalPerformance > 0.7) { reasoning.push({ type: 'historical_performance', weight: this.config.weights.historicalPerformance, description: 'Strong historical performance for similar queries', evidence: [`${similarQueries.length} similar queries with positive outcomes`], confidence: 0.8, }); } // User preference reasoning if (topProvider.factors.userPreference > 0.6) { reasoning.push({ type: 'user_preference', weight: this.config.weights.userPreference, description: 'Aligns with your learned preferences', evidence: ['Based on your previous ratings and feedback'], confidence: 0.7, }); } // Contextual match reasoning const strongContextFactors = contextFactors.filter(f => Math.abs(f.impact) > 0.2); if (strongContextFactors.length > 0 && topProvider.factors.contextualMatch > 0.6) { reasoning.push({ type: 'contextual_match', weight: this.config.weights.contextualMatch, description: 'Well-suited for current context and requirements', evidence: strongContextFactors.map(f => f.description), confidence: 0.6, }); } return reasoning; } calculateExpectedOutcome(providerId, analysis, contextFactors, similarQueries) { // This would typically use historical data and ML models // For now, we'll use simplified estimates const providerQueries = similarQueries.filter(q => q.selectedProvider === providerId); let satisfaction = 0.7; let cost = 0.05; let responseTime = 3000; const accuracy = 0.8; const completeness = 0.85; if (providerQueries.length > 0) { // Calculate actual historical averages const ratings = providerQueries.filter(q => q.userRating); if (ratings.length > 0) { satisfaction = ratings.reduce((sum, q) => sum + (q.userRating - 1), 0) / (ratings.length * 4); } cost = providerQueries.reduce((sum, q) => sum + q.cost, 0) / providerQueries.length; responseTime = providerQueries.reduce((sum, q) => sum + q.responseTime, 0) / providerQueries.length; } return { satisfaction: { value: satisfaction, confidence: providerQueries.length > 2 ? 0.8 : 0.5 }, cost: { value: cost, confidence: 0.9 }, responseTime: { value: responseTime, confidence: providerQueries.length > 2 ? 0.8 : 0.6 }, accuracy: { value: accuracy, confidence: 0.6 }, completeness: { value: completeness, confidence: 0.6 }, }; } generateFallbackStrategy(sortedProviders, context, thresholds) { const sequence = sortedProviders .slice(1) .filter(p => p.score > 0.2) .map(p => p.providerId); const triggers = [ { condition: 'response_timeout', threshold: 15000, // 15 seconds action: 'immediate', }, { condition: 'api_error', threshold: 1, action: 'immediate', }, ]; // Add quality threshold if user has high quality requirements if (context.qualityRequirement === 'high' || context.qualityRequirement === 'perfect') { triggers.push({ condition: 'quality_threshold', threshold: 0.7, action: 'immediate', }); } return { strategy: this.config.defaultFallbackStrategy, sequence, triggers, maxRetries: 2, }; } identifyAdaptiveFactors(userId, providerId, queryType, userHistory) { const factors = []; // Analyze performance trends for this provider const providerHistory = userHistory .filter(q => q.selectedProvider === providerId && q.analysis.type === queryType) .sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); if (providerHistory.length >= 5) { const recentPerformance = providerHistory.slice(-3); const olderPerformance = providerHistory.slice(0, 3); const recentSatisfaction = recentPerformance .filter(q => q.userRating) .reduce((sum, q) => sum + (q.userRating - 1), 0) / (recentPerformance.length * 4); const olderSatisfaction = olderPerformance .filter(q => q.userRating) .reduce((sum, q) => sum + (q.userRating - 1), 0) / (olderPerformance.length * 4); if (recentSatisfaction > olderSatisfaction + 0.1) { factors.push({ type: 'performance_drift', trend: 'improving', strength: Math.min(1, (recentSatisfaction - olderSatisfaction) * 2), prediction: 'Performance likely to continue improving', }); } else if (recentSatisfaction < olderSatisfaction - 0.1) { factors.push({ type: 'performance_drift', trend: 'declining', strength: Math.min(1, (olderSatisfaction - recentSatisfaction) * 2), prediction: 'Performance may need attention', }); } } return factors; } calculateRecommendationConfidence(primaryProvider, reasoning, contextFactors, similarQueriesCount) { let confidence = 0.5; // Provider score contributes to confidence confidence += primaryProvider.score * 0.3; // Number of reasoning factors confidence += Math.min(reasoning.length / 5, 0.2); // Historical data availability confidence += Math.min(similarQueriesCount / 10, 0.2); // Strong context factors const strongFactors = contextFactors.filter(f => Math.abs(f.impact) > 0.2); confidence += Math.min(strongFactors.length / 3, 0.1); // Average reasoning confidence if (reasoning.length > 0) { const avgReasoningConfidence = reasoning.reduce((sum, r) => sum + r.confidence, 0) / reasoning.length; confidence += avgReasoningConfidence * 0.2; } return Math.max(0, Math.min(1, confidence)); } /** * Utility methods */ generateQueryId() { return Date.now().toString(36) + Math.random().toString(36).substr(2); } storeRecommendation(userId, recommendation) { const userRecommendations = this.recommendations.get(userId) || []; userRecommendations.unshift(recommendation); // Keep only recent recommendations if (userRecommendations.length > 100) { userRecommendations.splice(100); } this.recommendations.set(userId, userRecommendations); } /** * Gets recommendation history for analysis */ getRecommendationHistory(userId, limit = 10) { const recommendations = this.recommendations.get(userId) || []; return recommendations.slice(0, limit); } /** * Updates configuration */ updateConfig(newConfig) { this.config = { ...this.config, ...newConfig }; } /** * Gets current configuration */ getConfig() { return { ...this.config }; } } //# sourceMappingURL=recommendation-engine.js.map