UNPKG

@quantumai/quantum-cli-core

Version:

Quantum CLI Core - Multi-LLM Collaboration System

482 lines 20.9 kB
/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import { QueryType } from '../types.js'; /** * Default preference learning configuration */ export const DEFAULT_PREFERENCE_CONFIG = { minInteractions: 3, learningRate: 0.2, decayFactor: 0.95, confidenceThreshold: 0.7, weights: { satisfaction: 0.4, performance: 0.25, cost: 0.2, reliability: 0.15, }, enableBayesianInference: true, priorPreferences: { [QueryType.CODE]: { 'openai-gpt4': 0.8, 'anthropic-claude': 0.7, 'google-gemini': 0.6, }, [QueryType.CREATIVE]: { 'anthropic-claude': 0.8, 'openai-gpt4': 0.7, 'google-gemini': 0.6, }, [QueryType.ANALYSIS]: { 'anthropic-claude': 0.8, 'openai-gpt4': 0.75, 'google-gemini': 0.7, }, [QueryType.SECURITY]: { 'anthropic-claude': 0.8, 'openai-gpt4': 0.7, 'google-gemini': 0.6, }, [QueryType.GENERAL]: { 'google-gemini': 0.7, 'openai-gpt4': 0.7, 'anthropic-claude': 0.7, }, }, }; /** * Preference learning engine using machine learning algorithms */ export class PreferenceEngine { config; preferences = new Map(); // userId -> preferences learningHistory = new Map(); // userId -> lastLearningTime constructor(config = {}) { this.config = { ...DEFAULT_PREFERENCE_CONFIG, ...config }; } /** * Learns user preferences from query history */ learnPreferences(userId, queryHistory, userStats) { if (queryHistory.length < this.config.minInteractions) { return this.generateDefaultRecommendations(userId); } // Calculate preferences for each provider-querytype combination const preferences = this.calculatePreferences(userId, queryHistory, userStats); // Generate insights from preference patterns const insights = this.generateInsights(preferences, queryHistory, userStats); // Create actionable recommendations const recommendations = this.generateRecommendations(preferences, insights); // Calculate overall confidence in learning results const confidence = this.calculateOverallConfidence(preferences); // Store learned preferences this.preferences.set(userId, preferences); this.learningHistory.set(userId, new Date()); return { preferences, insights, recommendations, confidence, }; } /** * Calculates model preferences for all provider-querytype combinations */ calculatePreferences(userId, queryHistory, userStats) { const preferences = []; const providersByType = this.groupByProviderAndType(queryHistory); for (const [key, records] of providersByType.entries()) { const [providerId, queryType] = key.split('|'); if (records.length >= this.config.minInteractions) { const preference = this.calculateSinglePreference(userId, providerId, queryType, records, userStats); preferences.push(preference); } } return preferences.sort((a, b) => b.preferenceScore - a.preferenceScore); } /** * Calculates preference for a single provider-querytype combination */ calculateSinglePreference(userId, providerId, queryType, records, userStats) { const factors = this.calculatePreferenceFactors(records, userStats); // Calculate weighted preference score let preferenceScore = 0; let totalWeight = 0; for (const factor of factors) { const weight = this.config.weights[factor.type]; preferenceScore += factor.value * weight; totalWeight += weight; } preferenceScore = totalWeight > 0 ? preferenceScore / totalWeight : 0; // Apply Bayesian inference if enabled if (this.config.enableBayesianInference) { const bayesianResult = this.applyBayesianInference(providerId, queryType, preferenceScore, records.length); preferenceScore = bayesianResult.posterior; } // Calculate confidence based on evidence strength const confidence = this.calculatePreferenceConfidence(records, factors); return { userId, providerId, queryType, preferenceScore, confidence, evidenceCount: records.length, lastUpdated: new Date(), factors, }; } /** * Calculates preference factors from query records */ calculatePreferenceFactors(records, userStats) { const factors = []; // Satisfaction factor const ratedRecords = records.filter(r => r.userRating); if (ratedRecords.length > 0) { const avgRating = ratedRecords.reduce((sum, r) => sum + (r.userRating || 0), 0) / ratedRecords.length; const satisfactionScore = (avgRating - 1) / 4; // Normalize 1-5 to 0-1 factors.push({ type: 'satisfaction', weight: this.config.weights.satisfaction, value: satisfactionScore, description: `Average rating: ${avgRating.toFixed(1)}/5.0 (${ratedRecords.length} ratings)`, }); } // Performance factor const avgResponseTime = records.reduce((sum, r) => sum + r.responseTime, 0) / records.length; const performanceScore = Math.max(0, 1 - (avgResponseTime / 10000)); // Normalize to 0-1 (10s = 0) factors.push({ type: 'performance', weight: this.config.weights.performance, value: performanceScore, description: `Average response time: ${avgResponseTime.toFixed(0)}ms`, }); // Cost factor const avgCost = records.reduce((sum, r) => sum + r.cost, 0) / records.length; const userAvgCost = userStats.averageCost; const costScore = userAvgCost > 0 ? Math.max(0, 1 - (avgCost / userAvgCost)) : 0.5; factors.push({ type: 'cost', weight: this.config.weights.cost, value: costScore, description: `Average cost: $${avgCost.toFixed(4)} vs user avg $${userAvgCost.toFixed(4)}`, }); // Reliability factor const satisfiedCount = records.filter(r => r.satisfied).length; const reliabilityScore = satisfiedCount / records.length; factors.push({ type: 'reliability', weight: this.config.weights.reliability, value: reliabilityScore, description: `Satisfaction rate: ${(reliabilityScore * 100).toFixed(1)}% (${satisfiedCount}/${records.length})`, }); return factors; } /** * Applies Bayesian inference to preference calculation */ applyBayesianInference(providerId, queryType, observedPreference, evidenceCount) { // Get prior preference from configuration const prior = this.config.priorPreferences[queryType]?.[providerId] || 0.5; // Likelihood based on observed preference and evidence strength const evidenceWeight = Math.min(evidenceCount / 10, 1); // Cap at 10 interactions const likelihood = observedPreference * evidenceWeight + (1 - evidenceWeight) * 0.5; // Evidence normalization factor const evidence = prior * likelihood + (1 - prior) * (1 - likelihood); // Posterior probability (Bayes' theorem) const posterior = evidence > 0 ? (prior * likelihood) / evidence : prior; return { posterior: Math.max(0, Math.min(1, posterior)), likelihood, prior, evidence, }; } /** * Calculates confidence in preference based on evidence */ calculatePreferenceConfidence(records, factors) { let confidence = 0; // Evidence count factor (more interactions = higher confidence) const evidenceFactor = Math.min(records.length / 20, 1); // Cap at 20 interactions confidence += evidenceFactor * 0.4; // Consistency factor (lower variance = higher confidence) const ratings = records.filter(r => r.userRating).map(r => r.userRating); if (ratings.length > 1) { const mean = ratings.reduce((a, b) => a + b, 0) / ratings.length; const variance = ratings.reduce((sum, rating) => sum + Math.pow(rating - mean, 2), 0) / ratings.length; const consistencyFactor = Math.max(0, 1 - (variance / 4)); // Variance of 4 = 0 confidence confidence += consistencyFactor * 0.3; } // Factor reliability (how well-supported each factor is) const factorReliability = factors.reduce((sum, factor) => sum + (factor.value > 0.1 ? 1 : 0), 0) / factors.length; confidence += factorReliability * 0.3; return Math.max(0, Math.min(1, confidence)); } /** * Generates insights from preference patterns */ generateInsights(preferences, queryHistory, userStats) { const insights = []; // Identify strong preferences const strongPreferences = preferences.filter(p => p.preferenceScore > 0.8 && p.confidence > 0.7); if (strongPreferences.length > 0) { insights.push({ type: 'pattern', title: 'Strong Model Preferences Identified', description: `You have clear preferences for ${strongPreferences.length} model-task combinations`, impact: 'high', evidence: strongPreferences.map(p => `${p.providerId} for ${p.queryType} (${(p.preferenceScore * 100).toFixed(0)}% preference)`), actionable: true, }); } // Identify cost optimization opportunities const costOptimizationOpportunities = this.findCostOptimizationOpportunities(preferences, userStats); if (costOptimizationOpportunities.length > 0) { insights.push({ type: 'opportunity', title: 'Cost Optimization Opportunities', description: 'Alternative models could reduce costs while maintaining quality', impact: 'medium', evidence: costOptimizationOpportunities, actionable: true, }); } // Identify performance trends const performanceTrends = this.identifyPerformanceTrends(queryHistory); if (performanceTrends.length > 0) { insights.push({ type: 'trend', title: 'Performance Trends Detected', description: 'Your usage patterns show performance changes over time', impact: 'medium', evidence: performanceTrends, actionable: false, }); } // Identify anomalies const anomalies = this.detectAnomalies(preferences, userStats); if (anomalies.length > 0) { insights.push({ type: 'anomaly', title: 'Unusual Patterns Detected', description: 'Some preferences differ from typical user patterns', impact: 'low', evidence: anomalies, actionable: false, }); } return insights; } /** * Generates actionable recommendations */ generateRecommendations(preferences, _insights) { const recommendations = []; const preferencesByType = new Map(); // Group preferences by query type for (const pref of preferences) { const typePrefs = preferencesByType.get(pref.queryType) || []; typePrefs.push(pref); preferencesByType.set(pref.queryType, typePrefs); } // Generate recommendations for each query type for (const [queryType, typePrefs] of preferencesByType.entries()) { const sortedPrefs = typePrefs.sort((a, b) => b.preferenceScore - a.preferenceScore); if (sortedPrefs.length > 0 && sortedPrefs[0].confidence >= this.config.confidenceThreshold) { const primary = sortedPrefs[0]; const alternatives = sortedPrefs.slice(1, 3).map(p => p.providerId); const reasoning = [ `Best overall performance for ${queryType} queries`, `${(primary.preferenceScore * 100).toFixed(0)}% preference score`, `Based on ${primary.evidenceCount} interactions`, ]; // Add factor-specific reasoning const topFactor = primary.factors.sort((a, b) => b.value - a.value)[0]; if (topFactor) { reasoning.push(`Strong ${topFactor.type}: ${topFactor.description}`); } recommendations.push({ queryType, primaryProvider: primary.providerId, alternativeProviders: alternatives, confidence: primary.confidence, reasoning, expectedBenefit: this.calculateExpectedBenefit(primary), }); } } return recommendations; } /** * Calculates expected benefit from following recommendation */ calculateExpectedBenefit(preference) { const satisfactionFactor = preference.factors.find(f => f.type === 'satisfaction'); const costFactor = preference.factors.find(f => f.type === 'cost'); const performanceFactor = preference.factors.find(f => f.type === 'performance'); return { satisfaction: (satisfactionFactor?.value || 0.5) * preference.confidence, cost: (costFactor?.value || 0.5) * preference.confidence, performance: (performanceFactor?.value || 0.5) * preference.confidence, }; } /** * Updates preferences with new feedback */ updatePreferences(userId, providerId, queryType, satisfied, rating, _responseTime, _cost) { const userPreferences = this.preferences.get(userId) || []; const existingPref = userPreferences.find(p => p.providerId === providerId && p.queryType === queryType); if (existingPref) { // Update existing preference with exponential moving average const satisfactionUpdate = satisfied ? 1.0 : 0.0; const currentSatisfaction = existingPref.factors.find(f => f.type === 'satisfaction')?.value || 0.5; const newSatisfaction = currentSatisfaction * (1 - this.config.learningRate) + satisfactionUpdate * this.config.learningRate; // Update satisfaction factor const satisfactionFactor = existingPref.factors.find(f => f.type === 'satisfaction'); if (satisfactionFactor) { satisfactionFactor.value = newSatisfaction; if (rating) { satisfactionFactor.description = `Updated rating: ${rating}/5.0`; } } // Update evidence count and timestamp existingPref.evidenceCount++; existingPref.lastUpdated = new Date(); // Recalculate overall preference score existingPref.preferenceScore = this.recalculatePreferenceScore(existingPref); } } /** * Gets current preferences for a user */ getUserPreferences(userId) { return this.preferences.get(userId) || []; } /** * Gets the preferred provider for a specific query type */ getPreferredProvider(userId, queryType) { const userPreferences = this.preferences.get(userId) || []; const typePreferences = userPreferences .filter(p => p.queryType === queryType) .sort((a, b) => b.preferenceScore - a.preferenceScore); return typePreferences.length > 0 ? typePreferences[0].providerId : undefined; } /** * Helper methods */ groupByProviderAndType(queryHistory) { const groups = new Map(); for (const record of queryHistory) { const key = `${record.selectedProvider}|${record.analysis.type}`; const group = groups.get(key) || []; group.push(record); groups.set(key, group); } return groups; } generateDefaultRecommendations(_userId) { const recommendations = []; for (const [queryType, priors] of Object.entries(this.config.priorPreferences)) { const sortedProviders = Object.entries(priors).sort((a, b) => b[1] - a[1]); if (sortedProviders.length > 0) { recommendations.push({ queryType: queryType, primaryProvider: sortedProviders[0][0], alternativeProviders: sortedProviders.slice(1, 3).map(([provider]) => provider), confidence: 0.5, // Low confidence for defaults reasoning: ['Based on general user patterns', 'No personal history available yet'], expectedBenefit: { satisfaction: 0.5, cost: 0.5, performance: 0.5 }, }); } } return { preferences: [], insights: [{ type: 'pattern', title: 'Learning Your Preferences', description: 'Use the system more to get personalized recommendations', impact: 'low', evidence: ['Not enough interaction history'], actionable: true, }], recommendations, confidence: 0.3, }; } findCostOptimizationOpportunities(preferences, _userStats) { const opportunities = []; for (const pref of preferences) { const costFactor = pref.factors.find(f => f.type === 'cost'); const satisfactionFactor = pref.factors.find(f => f.type === 'satisfaction'); if (costFactor && satisfactionFactor && costFactor.value < 0.5 && satisfactionFactor.value > 0.7) { opportunities.push(`${pref.providerId} for ${pref.queryType}: High satisfaction but expensive`); } } return opportunities; } identifyPerformanceTrends(queryHistory) { if (queryHistory.length < 10) return []; const trends = []; const recentRecords = queryHistory.slice(0, 5); const olderRecords = queryHistory.slice(-5); const recentAvgTime = recentRecords.reduce((sum, r) => sum + r.responseTime, 0) / recentRecords.length; const olderAvgTime = olderRecords.reduce((sum, r) => sum + r.responseTime, 0) / olderRecords.length; if (recentAvgTime < olderAvgTime * 0.8) { trends.push('Response times have improved recently'); } else if (recentAvgTime > olderAvgTime * 1.2) { trends.push('Response times have slowed down recently'); } return trends; } detectAnomalies(preferences, _userStats) { const anomalies = []; // Check for unexpectedly low preferences for typically preferred models for (const pref of preferences) { const priorPreference = this.config.priorPreferences[pref.queryType]?.[pref.providerId]; if (priorPreference && pref.preferenceScore < priorPreference - 0.3) { anomalies.push(`Lower than expected preference for ${pref.providerId} in ${pref.queryType} tasks`); } } return anomalies; } recalculatePreferenceScore(preference) { let score = 0; let totalWeight = 0; for (const factor of preference.factors) { const weight = this.config.weights[factor.type]; score += factor.value * weight; totalWeight += weight; } return totalWeight > 0 ? score / totalWeight : 0; } calculateOverallConfidence(preferences) { if (preferences.length === 0) return 0; const avgConfidence = preferences.reduce((sum, p) => sum + p.confidence, 0) / preferences.length; const coverageBonus = Math.min(preferences.length / 10, 0.2); // Bonus for having more preferences return Math.min(1, avgConfidence + coverageBonus); } /** * Updates configuration */ updateConfig(newConfig) { this.config = { ...this.config, ...newConfig }; } /** * Gets current configuration */ getConfig() { return { ...this.config }; } } //# sourceMappingURL=preference-engine.js.map