UNPKG

@quantumai/quantum-cli-core

Version:

Quantum CLI Core - Multi-LLM Collaboration System

572 lines 25.1 kB
/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ /** * Default adaptive weighting configuration */ export const DEFAULT_ADAPTIVE_CONFIG = { enableAdaptation: true, minFeedbackCount: 5, learningRate: 0.1, momentum: 0.8, satisfactionSensitivity: 0.3, adaptationFrequency: 10, confidenceThreshold: 0.7, enableABTesting: true, testPeriod: 7, }; /** * Adaptive weighting engine that adjusts weights based on user satisfaction */ export class AdaptiveWeightingEngine { config; userProfiles = new Map(); // userId -> profiles by queryType adaptationHistory = new Map(); // userId -> history testingGroups = new Map(); // userId -> test group (A/B testing) constructor(config = {}) { this.config = { ...DEFAULT_ADAPTIVE_CONFIG, ...config }; } /** * Adapts weights based on user feedback and performance */ adaptWeights(userId, queryType, recentFeedback, currentPreferences) { // Get or create user weight profile let profile = this.getUserProfile(userId, queryType); if (!profile) { profile = this.createDefaultProfile(userId, queryType); } // Analyze satisfaction patterns const satisfactionAnalysis = this.analyzeSatisfactionPatterns(recentFeedback); // Detect performance drifts const performanceDrifts = this.detectPerformanceDrifts(recentFeedback, currentPreferences); // Identify weight adjustment opportunities const adjustmentOpportunities = this.identifyAdjustmentOpportunities(profile, satisfactionAnalysis, performanceDrifts); // Calculate optimal weight adjustments const weightChanges = this.calculateWeightAdjustments(profile, adjustmentOpportunities, recentFeedback); // Generate adaptation recommendation const recommendation = this.generateAdaptationRecommendation(profile, weightChanges, satisfactionAnalysis); // Apply changes if recommended if (recommendation.action === 'apply') { profile = this.applyWeightChanges(profile, weightChanges); this.updateUserProfile(userId, profile); } // Calculate expected improvement const expectedImprovement = this.calculateExpectedImprovement(profile, weightChanges, satisfactionAnalysis); // Generate reasoning const reasoning = this.generateAdaptationReasoning(profile, weightChanges, satisfactionAnalysis, performanceDrifts); return { profile, changes: weightChanges, reasoning, expectedImprovement, confidence: this.calculateAdaptationConfidence(profile, weightChanges), recommendation, }; } /** * Analyzes satisfaction patterns from recent feedback */ analyzeSatisfactionPatterns(feedback) { const ratedFeedback = feedback.filter(r => r.userRating); if (ratedFeedback.length === 0) { return { averageSatisfaction: 0.5, satisfactionTrend: 0, variability: 0, factorCorrelations: {}, }; } // Calculate average satisfaction const averageSatisfaction = ratedFeedback.reduce((sum, r) => sum + (r.userRating - 1), 0) / (ratedFeedback.length * 4); // Calculate satisfaction trend (comparing recent vs older feedback) const sortedFeedback = feedback.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); const midPoint = Math.floor(sortedFeedback.length / 2); const olderFeedback = sortedFeedback.slice(0, midPoint); const newerFeedback = sortedFeedback.slice(midPoint); const olderSatisfaction = olderFeedback.length > 0 ? olderFeedback.reduce((sum, r) => sum + (r.userRating - 1), 0) / (olderFeedback.length * 4) : averageSatisfaction; const newerSatisfaction = newerFeedback.length > 0 ? newerFeedback.reduce((sum, r) => sum + (r.userRating - 1), 0) / (newerFeedback.length * 4) : averageSatisfaction; const satisfactionTrend = newerSatisfaction - olderSatisfaction; // Calculate variability const satisfactionValues = ratedFeedback.map(r => (r.userRating - 1) / 4); const mean = satisfactionValues.reduce((a, b) => a + b, 0) / satisfactionValues.length; const variance = satisfactionValues.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / satisfactionValues.length; const variability = Math.min(1, variance * 4); // Normalize to 0-1 // Calculate factor correlations const factorCorrelations = this.calculateFactorCorrelations(ratedFeedback); return { averageSatisfaction, satisfactionTrend, variability, factorCorrelations, }; } /** * Calculates correlations between factors and satisfaction */ calculateFactorCorrelations(ratedFeedback) { const correlations = {}; // Cost correlation const costCorrelation = this.calculateCorrelation(ratedFeedback.map(r => (r.userRating - 1) / 4), ratedFeedback.map(r => -r.cost) // Negative because lower cost should correlate with higher satisfaction ); correlations.cost = costCorrelation; // Performance correlation (response time) const performanceCorrelation = this.calculateCorrelation(ratedFeedback.map(r => (r.userRating - 1) / 4), ratedFeedback.map(r => -r.responseTime) // Negative because lower time should correlate with higher satisfaction ); correlations.performance = performanceCorrelation; // Reliability correlation (based on alternative providers usage) const reliabilityCorrelation = this.calculateCorrelation(ratedFeedback.map(r => (r.userRating - 1) / 4), ratedFeedback.map(r => r.alternativeProviders.length > 0 ? 1 : 0) // More alternatives = higher reliability ); correlations.reliability = reliabilityCorrelation; return correlations; } /** * Calculates correlation coefficient between two arrays */ calculateCorrelation(x, y) { if (x.length !== y.length || x.length < 2) return 0; const n = x.length; const meanX = x.reduce((a, b) => a + b, 0) / n; const meanY = y.reduce((a, b) => a + b, 0) / n; let numerator = 0; let sumXSquared = 0; let sumYSquared = 0; for (let i = 0; i < n; i++) { const dx = x[i] - meanX; const dy = y[i] - meanY; numerator += dx * dy; sumXSquared += dx * dx; sumYSquared += dy * dy; } const denominator = Math.sqrt(sumXSquared * sumYSquared); return denominator === 0 ? 0 : numerator / denominator; } /** * Detects performance drifts in recent feedback */ detectPerformanceDrifts(recentFeedback, _currentPreferences) { const drifts = []; if (recentFeedback.length < 6) return drifts; // Split feedback into older and recent periods const sortedFeedback = recentFeedback.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); const midPoint = Math.floor(sortedFeedback.length / 2); const olderFeedback = sortedFeedback.slice(0, midPoint); const newerFeedback = sortedFeedback.slice(midPoint); // Cost drift detection const olderAvgCost = olderFeedback.reduce((sum, f) => sum + f.cost, 0) / olderFeedback.length; const newerAvgCost = newerFeedback.reduce((sum, f) => sum + f.cost, 0) / newerFeedback.length; if (newerAvgCost > olderAvgCost * 1.2) { // 20% increase drifts.push({ type: 'cost_increase', severity: Math.min(1, (newerAvgCost - olderAvgCost) / olderAvgCost), evidence: [`Average cost increased from ${olderAvgCost.toFixed(3)} to ${newerAvgCost.toFixed(3)}`], }); } // Performance drift detection const olderAvgTime = olderFeedback.reduce((sum, f) => sum + f.responseTime, 0) / olderFeedback.length; const newerAvgTime = newerFeedback.reduce((sum, f) => sum + f.responseTime, 0) / newerFeedback.length; if (newerAvgTime > olderAvgTime * 1.3) { // 30% increase drifts.push({ type: 'performance_decrease', severity: Math.min(1, (newerAvgTime - olderAvgTime) / olderAvgTime), evidence: [`Average response time increased from ${olderAvgTime.toFixed(0)}ms to ${newerAvgTime.toFixed(0)}ms`], }); } // Reliability drift detection (based on satisfaction rates) const olderSatisfiedRate = olderFeedback.filter(f => f.userRating && f.userRating >= 4).length / olderFeedback.length; const newerSatisfiedRate = newerFeedback.filter(f => f.userRating && f.userRating >= 4).length / newerFeedback.length; if (newerSatisfiedRate < olderSatisfiedRate * 0.8) { // 20% decrease drifts.push({ type: 'reliability_drop', severity: olderSatisfiedRate - newerSatisfiedRate, evidence: [`Satisfaction rate dropped from ${(olderSatisfiedRate * 100).toFixed(0)}% to ${(newerSatisfiedRate * 100).toFixed(0)}%`], }); } return drifts; } /** * Identifies opportunities for weight adjustments */ identifyAdjustmentOpportunities(profile, satisfactionAnalysis, performanceDrifts) { const opportunities = []; // Low satisfaction trigger if (satisfactionAnalysis.averageSatisfaction < 0.6) { opportunities.push({ type: 'satisfaction_feedback', description: 'Low user satisfaction detected', evidence: [`Average satisfaction: ${(satisfactionAnalysis.averageSatisfaction * 100).toFixed(0)}%`], severity: 1 - satisfactionAnalysis.averageSatisfaction, }); } // Declining satisfaction trigger if (satisfactionAnalysis.satisfactionTrend < -0.1) { opportunities.push({ type: 'satisfaction_feedback', description: 'Declining satisfaction trend', evidence: [`Satisfaction trending down by ${(satisfactionAnalysis.satisfactionTrend * 100).toFixed(0)}%`], severity: Math.abs(satisfactionAnalysis.satisfactionTrend), }); } // Performance drift triggers for (const drift of performanceDrifts) { opportunities.push({ type: 'performance_drift', description: `${drift.type.replace('_', ' ')} detected`, evidence: drift.evidence, severity: drift.severity, }); } // Factor correlation triggers for (const [factor, correlation] of Object.entries(satisfactionAnalysis.factorCorrelations)) { const correlationValue = typeof correlation === 'number' ? correlation : 0; if (Math.abs(correlationValue) > 0.5) { const currentWeight = profile.weights.find(w => w.factor === factor)?.currentWeight || 0.25; const optimalWeight = (correlationValue + 1) / 2; // Convert -1,1 to 0,1 if (Math.abs(currentWeight - optimalWeight) > 0.1) { opportunities.push({ type: 'pattern_shift', description: `Strong correlation between ${factor} and satisfaction`, evidence: [`Correlation coefficient: ${correlationValue.toFixed(2)}`], severity: Math.abs(currentWeight - optimalWeight), }); } } } return opportunities; } /** * Calculates optimal weight adjustments */ calculateWeightAdjustments(profile, opportunities, feedback) { const changes = []; for (const opportunity of opportunities) { const relevantWeights = this.getRelevantWeights(profile, opportunity); for (const weight of relevantWeights) { const adjustment = this.calculateWeightAdjustment(weight, opportunity, feedback); if (Math.abs(adjustment) > 0.05) { // Only significant changes const newWeight = Math.max(weight.bounds.min, Math.min(weight.bounds.max, weight.currentWeight + adjustment)); changes.push({ timestamp: new Date(), oldWeight: weight.currentWeight, newWeight, trigger: opportunity, impact: this.estimateWeightChangeImpact(weight, newWeight, opportunity), confidence: this.calculateWeightChangeConfidence(weight, opportunity, feedback), }); } } } return changes; } /** * Calculates individual weight adjustment */ calculateWeightAdjustment(weight, trigger, feedback) { let adjustment = 0; switch (trigger.type) { case 'satisfaction_feedback': // Increase weight of factors that correlate positively with satisfaction if (weight.factor === 'satisfaction') { adjustment = trigger.severity * this.config.learningRate; } break; case 'performance_drift': // Adjust weights based on drift type if (trigger.description.includes('cost') && weight.factor === 'cost') { adjustment = trigger.severity * this.config.learningRate; } else if (trigger.description.includes('performance') && weight.factor === 'performance') { adjustment = trigger.severity * this.config.learningRate; } else if (trigger.description.includes('reliability') && weight.factor === 'reliability') { adjustment = trigger.severity * this.config.learningRate; } break; case 'pattern_shift': { // Adjust based on correlation analysis const correlation = this.getCorrelationForFactor(weight.factor, feedback); if (correlation !== 0) { const optimalWeight = (correlation + 1) / 2; adjustment = (optimalWeight - weight.currentWeight) * this.config.learningRate; } break; } default: // No adjustment for unknown trigger types break; } // Apply momentum smoothing adjustment = adjustment * (1 - this.config.momentum) + (weight.currentWeight - weight.baseWeight) * this.config.momentum; return adjustment; } /** * Creates default weight profile for new users */ createDefaultProfile(userId, queryType) { const weights = [ { factor: 'satisfaction', baseWeight: 0.4, currentWeight: 0.4, learningRate: this.config.learningRate, momentum: this.config.momentum, bounds: { min: 0.2, max: 0.7 }, lastUpdate: new Date(), adaptationHistory: [], }, { factor: 'performance', baseWeight: 0.25, currentWeight: 0.25, learningRate: this.config.learningRate, momentum: this.config.momentum, bounds: { min: 0.1, max: 0.5 }, lastUpdate: new Date(), adaptationHistory: [], }, { factor: 'cost', baseWeight: 0.2, currentWeight: 0.2, learningRate: this.config.learningRate, momentum: this.config.momentum, bounds: { min: 0.05, max: 0.4 }, lastUpdate: new Date(), adaptationHistory: [], }, { factor: 'reliability', baseWeight: 0.15, currentWeight: 0.15, learningRate: this.config.learningRate, momentum: this.config.momentum, bounds: { min: 0.05, max: 0.3 }, lastUpdate: new Date(), adaptationHistory: [], }, ]; return { userId, queryType, weights, adaptationEnabled: true, totalAdaptations: 0, lastSignificantChange: new Date(), stabilityScore: 1.0, performanceScore: 0.5, adaptationHistory: [], }; } /** * Gets user weight profile for specific query type */ getUserProfile(userId, queryType) { const profiles = this.userProfiles.get(userId); return profiles?.find(p => p.queryType === queryType); } /** * Updates user weight profile */ updateUserProfile(userId, profile) { let profiles = this.userProfiles.get(userId); if (!profiles) { profiles = []; this.userProfiles.set(userId, profiles); } const existingIndex = profiles.findIndex(p => p.queryType === profile.queryType); if (existingIndex >= 0) { profiles[existingIndex] = profile; } else { profiles.push(profile); } } /** * Gets relevant weights for a specific trigger */ getRelevantWeights(profile, trigger) { switch (trigger.type) { case 'satisfaction_feedback': return profile.weights.filter(w => w.factor === 'satisfaction'); case 'performance_drift': if (trigger.description.includes('cost')) { return profile.weights.filter(w => w.factor === 'cost'); } else if (trigger.description.includes('performance')) { return profile.weights.filter(w => w.factor === 'performance'); } else if (trigger.description.includes('reliability')) { return profile.weights.filter(w => w.factor === 'reliability'); } return []; case 'pattern_shift': // Return all weights for pattern shifts return profile.weights; default: return []; } } /** * Gets correlation for a specific factor */ getCorrelationForFactor(_factor, _feedback) { // Simplified implementation - would calculate actual correlation return 0; } /** * Estimates impact of weight change on user satisfaction */ estimateWeightChangeImpact(weight, newWeight, trigger) { const weightChange = Math.abs(newWeight - weight.currentWeight); return weightChange * trigger.severity * 0.5; // Simplified impact calculation } /** * Calculates confidence in weight change */ calculateWeightChangeConfidence(weight, trigger, feedback) { let confidence = 0.5; // More feedback = higher confidence confidence += Math.min(feedback.length / 20, 0.3); // Higher severity = higher confidence confidence += trigger.severity * 0.2; // More stable weights = lower confidence (less need for change) const allHistory = weight.adaptationHistory.slice(-10); // Keep last 10 const stabilityBonus = Math.max(0, 1 - allHistory.length * 0.1); return Math.min(1, confidence + stabilityBonus * 0.2); } /** * Applies weight changes to profile */ applyWeightChanges(profile, changes) { const updatedWeights = profile.weights.map(weight => { const change = changes.find(c => profile.weights.find(w => w.currentWeight === c.oldWeight) === weight); if (change) { return { ...weight, currentWeight: change.newWeight, lastUpdate: change.timestamp, adaptationHistory: [...weight.adaptationHistory, change].slice(-10), // Keep last 10 }; } return weight; }); return { ...profile, weights: updatedWeights, totalAdaptations: profile.totalAdaptations + changes.length, lastSignificantChange: new Date(), }; } /** * Generates adaptation recommendation */ generateAdaptationRecommendation(profile, changes, _satisfactionAnalysis) { if (changes.length === 0) { return { action: 'reject', reasoning: ['No significant weight adjustments needed'], }; } const totalImpact = changes.reduce((sum, c) => sum + c.impact, 0); const avgConfidence = changes.reduce((sum, c) => sum + c.confidence, 0) / changes.length; if (avgConfidence < this.config.confidenceThreshold) { return { action: 'defer', reasoning: [`Low confidence (${(avgConfidence * 100).toFixed(0)}%) in weight changes`], }; } if (this.config.enableABTesting && profile.totalAdaptations < 3) { return { action: 'test', reasoning: ['Testing weight changes with A/B testing'], testPeriod: this.config.testPeriod, rollbackPlan: 'Revert to previous weights if satisfaction decreases', }; } if (totalImpact > 0.2) { return { action: 'apply', reasoning: [`High expected impact (${(totalImpact * 100).toFixed(0)}%) from weight changes`], }; } return { action: 'apply', reasoning: ['Applying weight changes based on user feedback analysis'], }; } /** * Calculates expected improvement from weight changes */ calculateExpectedImprovement(profile, changes, _satisfactionAnalysis) { if (changes.length === 0) return 0; const totalImpact = changes.reduce((sum, c) => sum + c.impact, 0); const avgConfidence = changes.reduce((sum, c) => sum + c.confidence, 0) / changes.length; return totalImpact * avgConfidence; } /** * Generates reasoning for adaptation */ generateAdaptationReasoning(profile, changes, _satisfactionAnalysis, performanceDrifts) { const reasoning = []; if (changes.length === 0) { reasoning.push('No weight adjustments needed based on current feedback'); return reasoning; } reasoning.push(`Adjusting ${changes.length} weight(s) based on user feedback analysis`); for (const change of changes) { const direction = change.newWeight > change.oldWeight ? 'increased' : 'decreased'; const factor = change.trigger.description.includes('cost') ? 'cost' : change.trigger.description.includes('performance') ? 'performance' : change.trigger.description.includes('reliability') ? 'reliability' : 'satisfaction'; reasoning.push(`${factor} weight ${direction} by ${Math.abs((change.newWeight - change.oldWeight) * 100).toFixed(1)}%`); } if (performanceDrifts.length > 0) { reasoning.push(`Detected ${performanceDrifts.length} performance drift(s)`); } return reasoning; } /** * Calculates overall confidence in adaptation */ calculateAdaptationConfidence(profile, changes) { if (changes.length === 0) return 1.0; const avgChangeConfidence = changes.reduce((sum, c) => sum + c.confidence, 0) / changes.length; const allHistory = profile.weights.flatMap(w => w.adaptationHistory); const stabilityBonus = Math.max(0, 1 - allHistory.length * 0.1); return Math.min(1, avgChangeConfidence + stabilityBonus * 0.2); } /** * Gets current weights for a user and query type */ getCurrentWeights(userId, queryType) { const profile = this.getUserProfile(userId, queryType); if (!profile) { const defaultProfile = this.createDefaultProfile(userId, queryType); return Object.fromEntries(defaultProfile.weights.map(w => [w.factor, w.currentWeight])); } return Object.fromEntries(profile.weights.map(w => [w.factor, w.currentWeight])); } /** * Updates configuration */ updateConfig(newConfig) { this.config = { ...this.config, ...newConfig }; } /** * Gets current configuration */ getConfig() { return { ...this.config }; } } //# sourceMappingURL=adaptive-weighting.js.map