UNPKG

@quantumai/quantum-cli-core

Version:

Quantum CLI Core - Multi-LLM Collaboration System

494 lines 19.4 kB
/** * @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