@quantumai/quantum-cli-core
Version:
Quantum CLI Core - Multi-LLM Collaboration System
482 lines • 20.9 kB
JavaScript
/**
* @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