@quantumai/quantum-cli-core
Version:
Quantum CLI Core - Multi-LLM Collaboration System
494 lines • 19.4 kB
JavaScript
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { QueryAnalyzer } from '../analysis/query-analyzer.js';
import { QueryType } from '../types.js';
/**
* Default smart router configuration
*/
export const DEFAULT_ROUTER_CONFIG = {
defaultFallbackStrategy: 'best',
costWeight: 0.3,
performanceWeight: 0.3,
capabilityWeight: 0.4,
satisfactionWeight: 0.2,
maxCostPerQuery: 0.1,
maxLatencyPerQuery: 10000,
enablePreferenceLearning: true,
routingRules: [
// Code-related queries prefer code-capable models
{
condition: { queryType: [QueryType.CODE] },
action: {
requireCapabilities: ['code', 'reasoning'],
scoreMultiplier: 1.2,
},
},
// Security queries require security-aware models
{
condition: { queryType: [QueryType.SECURITY], securitySensitive: true },
action: {
requireCapabilities: ['security-aware', 'safety-filtering'],
scoreMultiplier: 1.5,
},
},
// Creative queries prefer models with creative capabilities
{
condition: { queryType: [QueryType.CREATIVE] },
action: {
requireCapabilities: ['creative', 'language'],
scoreMultiplier: 1.1,
},
},
// Complex queries prefer high-capability models
{
condition: { complexity: { min: 0.7 } },
action: {
requireCapabilities: ['complex-reasoning', 'multi-step'],
scoreMultiplier: 1.3,
},
},
],
};
/**
* Smart routing engine that selects optimal providers based on query analysis
*/
export class SmartRouter {
config;
queryAnalyzer;
userPreferences = new Map();
providers = new Map();
providerConfigs = new Map();
constructor(config = {}, queryAnalyzer) {
this.config = { ...DEFAULT_ROUTER_CONFIG, ...config };
this.queryAnalyzer = queryAnalyzer || new QueryAnalyzer();
}
/**
* Registers a provider with the router
*/
registerProvider(provider, config) {
this.providers.set(provider.id, provider);
this.providerConfigs.set(provider.id, config);
}
/**
* Unregisters a provider from the router
*/
unregisterProvider(providerId) {
this.providers.delete(providerId);
this.providerConfigs.delete(providerId);
}
/**
* Routes a query to the optimal provider(s)
*/
async route(query, context, userId) {
// Analyze the query
const analysis = this.queryAnalyzer.analyze(query, context);
// Score all available providers
const providerScores = await this.scoreProviders(analysis, userId);
// Apply routing rules
const adjustedScores = this.applyRoutingRules(providerScores, analysis);
// Apply A/B testing if enabled
const finalScores = this.applyABTesting(adjustedScores, analysis);
// Select primary and secondary providers
const sortedProviders = finalScores.sort((a, b) => b.score - a.score);
if (sortedProviders.length === 0) {
throw new Error('No suitable providers available');
}
const primaryProvider = sortedProviders[0];
const secondaryProviders = sortedProviders
.slice(1)
.filter((p) => p.score > 0.3) // Only include reasonably capable providers
.map((p) => p.providerId)
.slice(0, 2); // Limit to 2 secondary providers
// Calculate confidence based on score difference
const confidence = sortedProviders.length > 1
? Math.min((primaryProvider.score - sortedProviders[1].score) * 2, 1.0)
: 1.0;
// Generate reasoning
const reasoning = this.generateReasoningExplanation(primaryProvider, analysis, sortedProviders);
// Estimate total cost
const estimatedTotalCost = this.estimateTotalCost(primaryProvider, secondaryProviders.map((id) => finalScores.find((s) => s.providerId === id)), analysis);
return {
primaryProvider: primaryProvider.providerId,
secondaryProviders,
confidence,
reasoning,
fallbackStrategy: this.config.defaultFallbackStrategy,
estimatedTotalCost,
};
}
/**
* Scores all providers based on query analysis and user preferences
*/
async scoreProviders(analysis, userId) {
const scores = [];
for (const [providerId, provider] of this.providers) {
const config = this.providerConfigs.get(providerId);
if (!config || !config.enabled) {
continue;
}
const score = await this.calculateProviderScore(provider, config, analysis, userId);
scores.push(score);
}
return scores;
}
/**
* Calculates a score for a specific provider
*/
async calculateProviderScore(provider, config, analysis, userId) {
let totalScore = 0;
const reasons = [];
// Capability matching score
const capabilityScore = this.calculateCapabilityScore(config, analysis.suggestedModelCapabilities);
totalScore += capabilityScore * this.config.capabilityWeight;
if (capabilityScore > 0.8) {
reasons.push(`Strong capability match (${(capabilityScore * 100).toFixed(0)}%)`);
}
// Cost score (lower cost = higher score)
const estimatedCost = this.estimateProviderCost(config, analysis);
const costScore = Math.max(0, 1 - estimatedCost / this.config.maxCostPerQuery);
totalScore += costScore * this.config.costWeight;
if (costScore > 0.8) {
reasons.push(`Cost-effective ($${estimatedCost.toFixed(4)})`);
}
// Performance score
const estimatedLatency = this.estimateProviderLatency(config, analysis);
const performanceScore = Math.max(0, 1 - estimatedLatency / this.config.maxLatencyPerQuery);
totalScore += performanceScore * this.config.performanceWeight;
if (performanceScore > 0.8) {
reasons.push(`Fast response (~${estimatedLatency}ms)`);
}
// User satisfaction score
if (userId && this.config.enablePreferenceLearning) {
const satisfactionScore = this.calculateSatisfactionScore(userId, provider.id, analysis.type);
totalScore += satisfactionScore * this.config.satisfactionWeight;
if (satisfactionScore > 0.7) {
reasons.push(`High user satisfaction (${(satisfactionScore * 100).toFixed(0)}%)`);
}
}
// Provider-specific bonuses
const providerBonus = this.calculateProviderBonus(config, analysis);
totalScore += providerBonus;
if (providerBonus > 0.1) {
reasons.push(`Specialized for this query type`);
}
// Availability penalty
const availability = await this.checkProviderAvailability(provider);
totalScore *= availability;
if (availability < 1.0) {
reasons.push(`Reduced availability (${(availability * 100).toFixed(0)}%)`);
}
return {
providerId: provider.id,
score: Math.min(totalScore, 1.0),
reasons,
capabilities: config.capabilities || [],
estimatedCost,
estimatedLatency,
};
}
/**
* Calculates capability matching score
*/
calculateCapabilityScore(config, requiredCapabilities) {
if (!config.capabilities || requiredCapabilities.length === 0) {
return 0.5; // Neutral score
}
const matches = requiredCapabilities.filter((cap) => config.capabilities.includes(cap)).length;
return matches / requiredCapabilities.length;
}
/**
* Estimates the cost for a provider to handle this query
*/
estimateProviderCost(config, analysis) {
const baseCost = config.costPerToken || 0.00001;
// Estimate token usage based on query complexity and expected response length
let estimatedTokens = 100; // Base tokens
if (analysis.expectedResponseLength === 'long') {
estimatedTokens *= 3;
}
else if (analysis.expectedResponseLength === 'medium') {
estimatedTokens *= 2;
}
if (analysis.complexity > 0.7) {
estimatedTokens *= 1.5;
}
if (analysis.requiresCodeGeneration) {
estimatedTokens *= 1.3;
}
return baseCost * estimatedTokens;
}
/**
* Estimates the latency for a provider to handle this query
*/
estimateProviderLatency(config, analysis) {
// Base latency from provider config or estimate
let baseLatency = 2000; // 2 seconds default
// Adjust based on query complexity
if (analysis.complexity > 0.8) {
baseLatency *= 1.5;
}
else if (analysis.complexity > 0.5) {
baseLatency *= 1.2;
}
// Adjust based on expected response length
if (analysis.expectedResponseLength === 'long') {
baseLatency *= 1.4;
}
else if (analysis.expectedResponseLength === 'medium') {
baseLatency *= 1.2;
}
return baseLatency;
}
/**
* Calculates user satisfaction score for a provider
*/
calculateSatisfactionScore(userId, providerId, queryType) {
const userPrefs = this.userPreferences.get(userId) || [];
const relevantPrefs = userPrefs.filter((p) => p.queryType === queryType && p.preferredProviders.includes(providerId));
if (relevantPrefs.length === 0) {
return 0.5; // Neutral score for new providers
}
// Calculate average satisfaction
const totalSatisfaction = relevantPrefs.reduce((sum, pref) => {
const providerScore = pref.satisfactionScores[providerId] || 0.5;
return sum + providerScore;
}, 0);
return totalSatisfaction / relevantPrefs.length;
}
/**
* Calculates provider-specific bonus based on strengths
*/
calculateProviderBonus(config, analysis) {
let bonus = 0;
if (!config.strengths) {
return bonus;
}
// Check if provider strengths align with query characteristics
const queryCharacteristics = [analysis.type.toLowerCase()];
if (analysis.domain) {
queryCharacteristics.push(analysis.domain);
}
if (analysis.language) {
queryCharacteristics.push(analysis.language);
}
const alignedStrengths = config.strengths.filter((strength) => queryCharacteristics.some((char) => char.includes(strength) || strength.includes(char)));
bonus = alignedStrengths.length * 0.1; // 0.1 bonus per aligned strength
return Math.min(bonus, 0.3); // Cap at 0.3
}
/**
* Checks provider availability (could be extended to check API status)
*/
async checkProviderAvailability(provider) {
try {
// For now, assume all providers are available
// This could be extended to ping provider APIs
return 1.0;
}
catch (error) {
return 0.1; // Heavily penalize unavailable providers
}
}
/**
* Applies routing rules to adjust provider scores
*/
applyRoutingRules(providerScores, analysis) {
return providerScores.map((score) => {
let adjustedScore = score.score;
const newReasons = [...score.reasons];
for (const rule of this.config.routingRules) {
if (this.matchesCondition(rule.condition, analysis)) {
// Apply score multiplier
if (rule.action.scoreMultiplier) {
adjustedScore *= rule.action.scoreMultiplier;
newReasons.push(`Rule applied: ${rule.action.scoreMultiplier}x multiplier`);
}
// Check required capabilities
if (rule.action.requireCapabilities) {
const hasRequiredCaps = rule.action.requireCapabilities.every((cap) => score.capabilities.includes(cap));
if (!hasRequiredCaps) {
adjustedScore *= 0.3; // Heavy penalty for missing required capabilities
newReasons.push('Missing required capabilities');
}
}
// Apply preference/avoidance
if (rule.action.preferProviders?.includes(score.providerId)) {
adjustedScore *= 1.2;
newReasons.push('Preferred provider for this query type');
}
if (rule.action.avoidProviders?.includes(score.providerId)) {
adjustedScore *= 0.5;
newReasons.push('Not recommended for this query type');
}
}
}
return {
...score,
score: Math.min(adjustedScore, 1.0),
reasons: newReasons,
};
});
}
/**
* Checks if analysis matches a routing rule condition
*/
matchesCondition(condition, analysis) {
if (condition.queryType && !condition.queryType.includes(analysis.type)) {
return false;
}
if (condition.complexity) {
if (condition.complexity.min &&
analysis.complexity < condition.complexity.min) {
return false;
}
if (condition.complexity.max &&
analysis.complexity > condition.complexity.max) {
return false;
}
}
if (condition.domain &&
analysis.domain &&
!condition.domain.includes(analysis.domain)) {
return false;
}
if (condition.language &&
analysis.language &&
!condition.language.includes(analysis.language)) {
return false;
}
if (condition.frameworks &&
!condition.frameworks.some((f) => analysis.frameworks.includes(f))) {
return false;
}
if (condition.securitySensitive !== undefined &&
condition.securitySensitive !== analysis.securitySensitive) {
return false;
}
return true;
}
/**
* Applies A/B testing variants if configured
*/
applyABTesting(providerScores, analysis) {
if (!this.config.abTestConfig?.enabled) {
return providerScores;
}
// Simple random variant selection (could be improved with user bucketing)
const totalWeight = this.config.abTestConfig.variants.reduce((sum, v) => sum + v.weight, 0);
const random = Math.random() * totalWeight;
let currentWeight = 0;
let selectedVariant = this.config.abTestConfig.variants[0];
for (const variant of this.config.abTestConfig.variants) {
currentWeight += variant.weight;
if (random <= currentWeight) {
selectedVariant = variant;
break;
}
}
// Apply variant routing rules
let adjustedScores = [...providerScores];
for (const rule of selectedVariant.routingRules) {
adjustedScores = this.applyRoutingRules(adjustedScores, analysis);
}
return adjustedScores;
}
/**
* Generates human-readable reasoning for the routing decision
*/
generateReasoningExplanation(selectedProvider, analysis, allProviders) {
const reasoning = [];
reasoning.push(`Selected ${selectedProvider.providerId} (score: ${(selectedProvider.score * 100).toFixed(0)}%)`);
reasoning.push(`Query type: ${analysis.type} (confidence: ${(analysis.confidence * 100).toFixed(0)}%)`);
reasoning.push(`Complexity: ${(analysis.complexity * 100).toFixed(0)}%`);
if (analysis.domain) {
reasoning.push(`Domain: ${analysis.domain}`);
}
if (selectedProvider.reasons.length > 0) {
reasoning.push(`Reasons: ${selectedProvider.reasons.join(', ')}`);
}
if (allProviders.length > 1) {
const secondBest = allProviders[1];
const scoreDiff = selectedProvider.score - secondBest.score;
reasoning.push(`${(scoreDiff * 100).toFixed(0)}% better than ${secondBest.providerId}`);
}
return reasoning;
}
/**
* Estimates total cost including secondary providers
*/
estimateTotalCost(primary, secondaries, analysis) {
let totalCost = primary.estimatedCost;
// Add cost for secondary providers (typically used for verification)
for (const secondary of secondaries) {
totalCost += secondary.estimatedCost * 0.5; // Assume verification uses less tokens
}
return totalCost;
}
/**
* Records user feedback for preference learning
*/
recordUserFeedback(userId, providerId, queryType, satisfied, responseTime, cost) {
if (!this.config.enablePreferenceLearning) {
return;
}
const userPrefs = this.userPreferences.get(userId) || [];
const existingPref = userPrefs.find((p) => p.queryType === queryType);
if (existingPref) {
// Update existing preference
if (!existingPref.preferredProviders.includes(providerId) && satisfied) {
existingPref.preferredProviders.push(providerId);
}
existingPref.satisfactionScores[providerId] =
(existingPref.satisfactionScores[providerId] || 0.5) * 0.8 +
(satisfied ? 1.0 : 0.0) * 0.2;
existingPref.usageHistory.push({
providerId,
timestamp: new Date(),
satisfied,
responseTime,
cost,
});
}
else {
// Create new preference
const newPref = {
userId,
queryType,
preferredProviders: satisfied ? [providerId] : [],
satisfactionScores: { [providerId]: satisfied ? 1.0 : 0.0 },
usageHistory: [
{
providerId,
timestamp: new Date(),
satisfied,
responseTime,
cost,
},
],
};
userPrefs.push(newPref);
}
this.userPreferences.set(userId, userPrefs);
}
/**
* Updates router configuration
*/
updateConfig(newConfig) {
this.config = { ...this.config, ...newConfig };
}
/**
* Gets current configuration
*/
getConfig() {
return { ...this.config };
}
/**
* Gets user preferences for debugging/analysis
*/
getUserPreferences(userId) {
return this.userPreferences.get(userId) || [];
}
}
//# sourceMappingURL=smart-router.js.map