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