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