| |
| |
| |
| |
| |
|
|
| import { GemmaClassifierStrategy } from './strategies/gemmaClassifierStrategy.js'; |
| import type { Config } from '../config/config.js'; |
| import type { |
| RoutingContext, |
| RoutingDecision, |
| RoutingStrategy, |
| TerminalStrategy, |
| } from './routingStrategy.js'; |
| import { DefaultStrategy } from './strategies/defaultStrategy.js'; |
| import { ClassifierStrategy } from './strategies/classifierStrategy.js'; |
| import { NumericalClassifierStrategy } from './strategies/numericalClassifierStrategy.js'; |
| import { CompositeStrategy } from './strategies/compositeStrategy.js'; |
| import { FallbackStrategy } from './strategies/fallbackStrategy.js'; |
| import { OverrideStrategy } from './strategies/overrideStrategy.js'; |
| import { ApprovalModeStrategy } from './strategies/approvalModeStrategy.js'; |
|
|
| import { logModelRouting } from '../telemetry/loggers.js'; |
| import { ModelRoutingEvent } from '../telemetry/types.js'; |
| import { debugLogger } from '../utils/debugLogger.js'; |
|
|
| |
| |
| |
| export class ModelRouterService { |
| private config: Config; |
| private strategy: TerminalStrategy; |
|
|
| constructor(config: Config) { |
| this.config = config; |
| this.strategy = this.initializeDefaultStrategy(); |
| } |
|
|
| private initializeDefaultStrategy(): TerminalStrategy { |
| const strategies: RoutingStrategy[] = []; |
|
|
| |
| strategies.push(new FallbackStrategy()); |
| strategies.push(new OverrideStrategy()); |
|
|
| |
| strategies.push(new ApprovalModeStrategy()); |
|
|
| |
| if (this.config.getGemmaModelRouterSettings()?.enabled) { |
| strategies.push(new GemmaClassifierStrategy()); |
| } |
|
|
| |
| strategies.push(new ClassifierStrategy()); |
|
|
| |
| strategies.push(new NumericalClassifierStrategy()); |
|
|
| |
| const terminalStrategy = new DefaultStrategy(); |
|
|
| return new CompositeStrategy( |
| [...strategies, terminalStrategy], |
| 'agent-router', |
| ); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| async route(context: RoutingContext): Promise<RoutingDecision> { |
| const startTime = Date.now(); |
| let decision: RoutingDecision | undefined; |
|
|
| const [enableNumericalRouting, thresholdValue] = await Promise.all([ |
| this.config.getNumericalRoutingEnabled(), |
| this.config.getResolvedClassifierThreshold(), |
| ]); |
| const classifierThreshold = String(thresholdValue); |
|
|
| let failed = false; |
| let error_message: string | undefined; |
|
|
| try { |
| decision = await this.strategy.route( |
| context, |
| this.config, |
| this.config.getBaseLlmClient(), |
| this.config.getLocalLiteRtLmClient(), |
| ); |
|
|
| debugLogger.debug( |
| `[Routing] Selected model: ${decision.model} (Source: ${decision.metadata.source}, Latency: ${decision.metadata.latencyMs}ms)\n\t[Routing] Reasoning: ${decision.metadata.reasoning}`, |
| ); |
| } catch (e) { |
| failed = true; |
| error_message = e instanceof Error ? e.message : String(e); |
| |
| |
| |
| decision = { |
| model: this.config.getModel(), |
| metadata: { |
| source: 'router-exception', |
| latencyMs: Date.now() - startTime, |
| reasoning: 'An exception occurred during routing.', |
| error: error_message, |
| }, |
| }; |
|
|
| debugLogger.debug( |
| `[Routing] Exception during routing: ${error_message}\n\tFallback model: ${decision.model} (Source: ${decision.metadata.source})`, |
| ); |
| } finally { |
| const event = new ModelRoutingEvent( |
| decision?.model || 'unknown', |
| decision?.metadata?.source || 'unknown', |
| decision?.metadata?.latencyMs || 0, |
| decision?.metadata?.reasoning, |
| failed, |
| error_message, |
| this.config.getApprovalMode(), |
| enableNumericalRouting, |
| classifierThreshold, |
| ); |
| logModelRouting(this.config, event); |
| } |
|
|
| return decision; |
| } |
| } |
|
|