Files
Profa fd2f37eaa0 fix(v3): update README claims and init hooks to use correct package name
Changes:
- Fix README.md agent counts (48 QE agents = 41 main + 7 subagents)
- Fix README.md skill counts (15 QE-specific skills)
- Fix v3/README.md agent counts (47 → 48)
- Fix init-wizard.ts hooks from 'npx @agentic-qe/v3' to 'npx agentic-qe'
- Complete v2 → v2/ folder reorganization (54 items moved)
- Update root package.json to point to v3 as main version

Verified claims:
- 48 QE agents (41 main + 7 TDD subagents)
- 15 QE-specific skills
- 12 DDD bounded contexts
- ~5,600+ tests
- HNSW, ReasoningBank, Queen Coordinator features exist

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 18:03:40 +00:00

17 KiB

Routing API Reference

Version: 1.0.5 Module: agentic-qe/routing


Overview

The Routing API provides intelligent model selection for AI-powered quality engineering operations. It analyzes task complexity and automatically routes requests to the most cost-effective model while maintaining quality thresholds.


Core Classes

ModelRouter

Main class for model selection and routing.

import { ModelRouter } from 'agentic-qe';

class ModelRouter {
  constructor(config?: RouterConfig);

  // Model selection
  selectModel(task: QETask): Promise<ModelSelection>;
  selectModelSync(task: QETask): ModelSelection;

  // Cost tracking
  trackCost(execution: ModelExecution): void;
  getCosts(options?: CostQueryOptions): Promise<CostReport>;

  // Fallback management
  getFallbackModel(failedModel: string): string;
  testFallbackChain(): Promise<FallbackTestResult>;

  // Configuration
  updateConfig(config: Partial<RouterConfig>): void;
  getConfig(): RouterConfig;

  // Analytics
  getPerformanceStats(): Promise<PerformanceStats>;
  getModelDistribution(): Promise<ModelDistribution>;
}

Interfaces

RouterConfig

interface RouterConfig {
  // Model definitions
  models: {
    available: ModelDefinition[];
    defaultModel: string;
    fallbackChain: string[];
  };

  // Routing strategy
  routing: {
    strategy: RoutingStrategy;
    complexity: ComplexityThresholds;
    agentOverrides?: AgentOverrides;
  };

  // Cost control
  costControl?: {
    maxCostPerTask?: number;
    dailyLimit?: number;
    monthlyLimit?: number;
    onLimitReached?: 'pause' | 'downgrade' | 'notify';
  };

  // Caching
  caching?: {
    enabled: boolean;
    ttl: number;
    maxSize: number;
    similarityThreshold?: number;
  };

  // Rate limiting
  rateLimit?: {
    enabled: boolean;
    maxRequestsPerMinute: number;
    perModel?: Record<string, number>;
  };
}

ModelDefinition

interface ModelDefinition {
  // Identity
  id: string;
  provider: 'openai' | 'anthropic' | 'custom';

  // Pricing
  costPer1kTokens: number;

  // Capabilities
  maxTokens: number;
  supportsStreaming?: boolean;
  supportsFunctionCalling?: boolean;

  // Performance
  avgLatency?: number;
  reliability?: number;

  // Metadata
  metadata?: Record<string, any>;
}

Example:

const model: ModelDefinition = {
  id: 'gpt-4',
  provider: 'openai',
  costPer1kTokens: 0.03,
  maxTokens: 8192,
  supportsStreaming: true,
  supportsFunctionCalling: true,
  avgLatency: 2000,
  reliability: 0.99
};

ModelSelection

interface ModelSelection {
  // Selected model
  modelId: string;
  provider: string;

  // Selection reasoning
  reason: string;
  confidence: number;
  complexity: ComplexityLevel;

  // Cost estimation
  estimatedCost: number;
  estimatedTokens: number;

  // Alternatives
  alternatives: Array<{
    modelId: string;
    score: number;
    reason: string;
  }>;

  // Metadata
  timestamp: number;
  taskId: string;
}

Example:

const selection: ModelSelection = {
  modelId: 'gpt-3.5-turbo',
  provider: 'openai',
  reason: 'Task complexity is low (score: 0.2), using cost-optimized model',
  confidence: 0.92,
  complexity: 'simple',
  estimatedCost: 0.02,
  estimatedTokens: 1000,
  alternatives: [
    {
      modelId: 'claude-haiku-3',
      score: 0.88,
      reason: 'Slightly higher cost but faster'
    }
  ],
  timestamp: Date.now(),
  taskId: 'task-123'
};

QETask

interface QETask {
  // Task identity
  id: string;
  type: TaskType;
  agentType: string;

  // Source code
  sourceFile?: string;
  sourceCode?: string;
  linesOfCode?: number;

  // Complexity metrics
  cyclomaticComplexity?: number;
  cognitiveComplexity?: number;
  halsteadComplexity?: number;

  // Structural metrics
  classCount?: number;
  functionCount?: number;
  dependencyCount?: number;

  // Test requirements
  testCount?: number;
  targetCoverage?: number;
  testTypes?: string[];

  // Metadata
  metadata?: {
    priority?: 'low' | 'medium' | 'high' | 'critical';
    critical?: boolean;
    deadline?: number;
    [key: string]: any;
  };
}

ComplexityLevel

type ComplexityLevel = 'simple' | 'moderate' | 'complex' | 'critical';

interface ComplexityAnalysis {
  level: ComplexityLevel;
  score: number;  // 0-1
  confidence: number;  // 0-1

  factors: {
    codeSize: number;
    structuralComplexity: number;
    algorithmicComplexity: number;
    testRequirements: number;
  };

  reasoning: string[];
}

RoutingStrategy

type RoutingStrategy =
  | 'cost-optimized'
  | 'quality-first'
  | 'balanced'
  | 'adaptive';

interface StrategyConfig {
  strategy: RoutingStrategy;

  // Cost-optimized
  costOptimized?: {
    maxCostPerTask: number;
    minQualityScore: number;
  };

  // Quality-first
  qualityFirst?: {
    minQualityScore: number;
    maxCostMultiplier: number;
  };

  // Balanced
  balanced?: {
    costWeight: number;
    qualityWeight: number;
  };

  // Adaptive
  adaptive?: {
    learningRate: number;
    explorationRate: number;
    optimizationWindow: number;
  };
}

Methods

selectModel()

Select optimal model for a given task.

async selectModel(task: QETask): Promise<ModelSelection>

Parameters:

  • task: QETask - Task to route

Returns: Promise<ModelSelection> - Selected model and reasoning

Throws:

  • NoModelsAvailableError - No models configured
  • ComplexityAnalysisError - Failed to analyze complexity
  • RateLimitExceededError - Rate limit reached

Example:

const router = new ModelRouter(config);

const task: QETask = {
  id: 'test-gen-123',
  type: 'test-generation',
  agentType: 'test-generator',
  sourceFile: 'src/services/user-service.ts',
  linesOfCode: 250,
  cyclomaticComplexity: 12,
  targetCoverage: 90
};

const selection = await router.selectModel(task);

console.log(`Selected: ${selection.modelId}`);
console.log(`Reason: ${selection.reason}`);
console.log(`Estimated cost: $${selection.estimatedCost}`);

trackCost()

Track cost of a model execution.

trackCost(execution: ModelExecution): void

Parameters:

interface ModelExecution {
  taskId: string;
  modelId: string;
  inputTokens: number;
  outputTokens: number;
  totalCost: number;
  duration: number;
  success: boolean;
  timestamp: number;
}

Example:

router.trackCost({
  taskId: 'test-gen-123',
  modelId: 'gpt-3.5-turbo',
  inputTokens: 800,
  outputTokens: 1200,
  totalCost: 0.004,
  duration: 2300,
  success: true,
  timestamp: Date.now()
});

getCosts()

Get cost report.

async getCosts(options?: CostQueryOptions): Promise<CostReport>

Parameters:

interface CostQueryOptions {
  period?: 'today' | 'week' | 'month' | 'all';
  startDate?: string;
  endDate?: string;
  groupBy?: 'model' | 'agent' | 'project' | 'day';
  sortBy?: 'cost' | 'count' | 'date';
  limit?: number;
}

Returns:

interface CostReport {
  totalCost: number;
  totalTasks: number;
  avgCostPerTask: number;
  period: {
    start: string;
    end: string;
  };

  byModel: Array<{
    modelId: string;
    cost: number;
    tasks: number;
    avgCost: number;
  }>;

  byAgent: Array<{
    agentType: string;
    cost: number;
    tasks: number;
    avgCost: number;
  }>;

  savings: {
    vsBaseline: number;
    percentage: number;
  };
}

Example:

const report = await router.getCosts({
  period: 'today',
  groupBy: 'model'
});

console.log(`Total cost today: $${report.totalCost}`);
console.log(`Savings: $${report.savings.vsBaseline} (${report.savings.percentage}%)`);

report.byModel.forEach(m => {
  console.log(`${m.modelId}: ${m.tasks} tasks, $${m.cost}`);
});

getFallbackModel()

Get fallback model when primary fails.

getFallbackModel(failedModel: string): string

Parameters:

  • failedModel: string - Model that failed

Returns: string - Next model in fallback chain

Throws:

  • NoFallbackAvailableError - No more fallbacks

Example:

try {
  await executeWithModel('gpt-4');
} catch (error) {
  const fallback = router.getFallbackModel('gpt-4');
  console.log(`Falling back to: ${fallback}`);
  await executeWithModel(fallback);
}

getPerformanceStats()

Get performance statistics for model selection.

async getPerformanceStats(): Promise<PerformanceStats>

Returns:

interface PerformanceStats {
  totalSelections: number;
  avgSelectionTime: number;

  accuracy: {
    correctSelections: number;
    incorrectSelections: number;
    accuracyRate: number;
  };

  models: Array<{
    modelId: string;
    selections: number;
    successRate: number;
    avgCost: number;
    avgDuration: number;
  }>;

  costEfficiency: {
    avgCostPerTask: number;
    savingsVsBaseline: number;
    costEfficiencyScore: number;
  };
}

Example:

const stats = await router.getPerformanceStats();

console.log(`Accuracy: ${stats.accuracy.accuracyRate * 100}%`);
console.log(`Avg selection time: ${stats.avgSelectionTime}ms`);
console.log(`Cost efficiency: ${stats.costEfficiency.costEfficiencyScore}`);

Complexity Analyzer

ComplexityAnalyzer

Analyze code complexity for routing decisions.

class ComplexityAnalyzer {
  constructor(config?: AnalyzerConfig);

  analyze(task: QETask): Promise<ComplexityAnalysis>;
  analyzeCode(code: string): Promise<ComplexityMetrics>;
  analyzeFile(filePath: string): Promise<ComplexityMetrics>;
}

Example:

import { ComplexityAnalyzer } from 'agentic-qe/routing';

const analyzer = new ComplexityAnalyzer();

const analysis = await analyzer.analyze(task);

console.log(`Complexity: ${analysis.level}`);
console.log(`Score: ${analysis.score}`);
console.log(`Confidence: ${analysis.confidence}`);

Cost Tracker

CostTracker

Track and report on model usage costs.

class CostTracker {
  constructor(config?: TrackerConfig);

  // Recording
  record(execution: ModelExecution): void;
  recordBatch(executions: ModelExecution[]): void;

  // Querying
  getTodayCost(): Promise<number>;
  getCost(period: string): Promise<number>;
  getBreakdown(options: CostQueryOptions): Promise<CostReport>;

  // Budgets
  setBudget(budget: BudgetConfig): void;
  checkBudget(): Promise<BudgetStatus>;

  // Alerts
  setAlert(alert: AlertConfig): void;
  getAlerts(): Promise<Alert[]>;

  // Exports
  exportCosts(options: ExportOptions): Promise<string>;
}

Example:

import { CostTracker } from 'agentic-qe/routing';

const tracker = new CostTracker();

// Set budget
tracker.setBudget({
  period: 'daily',
  limit: 50.00,
  onExceeded: 'pause'
});

// Check budget status
const status = await tracker.checkBudget();
if (status.exceeded) {
  console.warn(`Budget exceeded: $${status.current} / $${status.limit}`);
}

// Get breakdown
const breakdown = await tracker.getBreakdown({
  period: 'today',
  groupBy: 'model'
});

Error Types

NoModelsAvailableError

class NoModelsAvailableError extends Error {
  constructor(message: string);
}

Thrown when no models are configured or available.


ComplexityAnalysisError

class ComplexityAnalysisError extends Error {
  constructor(
    message: string,
    public task: QETask,
    public reason: string
  );
}

Thrown when complexity analysis fails.


RateLimitExceededError

class RateLimitExceededError extends Error {
  constructor(
    message: string,
    public modelId: string,
    public retryAfter: number
  );
}

Thrown when rate limit is exceeded.


BudgetExceededError

class BudgetExceededError extends Error {
  constructor(
    message: string,
    public period: string,
    public limit: number,
    public current: number
  );
}

Thrown when cost budget is exceeded.


Type Guards

// Check if model selection is valid
function isValidModelSelection(sel: any): sel is ModelSelection;

// Check if task is complete
function isCompleteTask(task: any): task is QETask;

// Check if complexity level is valid
function isComplexityLevel(level: any): level is ComplexityLevel;

Utility Functions

calculateCost()

function calculateCost(
  inputTokens: number,
  outputTokens: number,
  costPer1k: number
): number

Calculate cost from token counts.

Example:

import { calculateCost } from 'agentic-qe/routing';

const cost = calculateCost(800, 1200, 0.002);
console.log(`Cost: $${cost}`);  // $0.004

estimateTokens()

function estimateTokens(text: string): number

Estimate token count from text.

Example:

import { estimateTokens } from 'agentic-qe/routing';

const code = `function hello() { return "world"; }`;
const tokens = estimateTokens(code);
console.log(`Estimated tokens: ${tokens}`);

Configuration Examples

Cost-Optimized Configuration

const config: RouterConfig = {
  models: {
    available: [
      { id: 'gpt-3.5-turbo', provider: 'openai', costPer1kTokens: 0.002, maxTokens: 4096 },
      { id: 'gpt-4', provider: 'openai', costPer1kTokens: 0.03, maxTokens: 8192 }
    ],
    defaultModel: 'gpt-3.5-turbo',
    fallbackChain: ['gpt-3.5-turbo', 'gpt-4']
  },
  routing: {
    strategy: 'cost-optimized',
    complexity: {
      simple: { maxLines: 100, maxComplexity: 5, model: 'gpt-3.5-turbo' },
      moderate: { maxLines: 500, maxComplexity: 15, model: 'gpt-3.5-turbo' },
      complex: { maxLines: 2000, maxComplexity: 30, model: 'gpt-4' },
      critical: { maxLines: Infinity, maxComplexity: Infinity, model: 'gpt-4' }
    }
  },
  costControl: {
    maxCostPerTask: 0.05,
    dailyLimit: 50.00
  }
};

Quality-First Configuration

const config: RouterConfig = {
  models: {
    available: [
      { id: 'gpt-4', provider: 'openai', costPer1kTokens: 0.03, maxTokens: 8192 },
      { id: 'claude-sonnet-4.5', provider: 'anthropic', costPer1kTokens: 0.015, maxTokens: 200000 }
    ],
    defaultModel: 'gpt-4',
    fallbackChain: ['gpt-4', 'claude-sonnet-4.5']
  },
  routing: {
    strategy: 'quality-first',
    complexity: {
      simple: { model: 'gpt-4' },
      moderate: { model: 'gpt-4' },
      complex: { model: 'claude-sonnet-4.5' },
      critical: { model: 'claude-sonnet-4.5' }
    }
  }
};

Best Practices

1. Always Handle Fallbacks

async function generateWithFallback(task: QETask) {
  let currentModel = await router.selectModel(task);

  while (true) {
    try {
      return await generate(task, currentModel.modelId);
    } catch (error) {
      try {
        const fallback = router.getFallbackModel(currentModel.modelId);
        console.log(`Falling back to: ${fallback}`);
        currentModel = { ...currentModel, modelId: fallback };
      } catch {
        throw new Error('All models failed');
      }
    }
  }
}

2. Track All Executions

async function executeWithTracking(task: QETask) {
  const selection = await router.selectModel(task);
  const startTime = Date.now();

  try {
    const result = await execute(task, selection.modelId);

    router.trackCost({
      taskId: task.id,
      modelId: selection.modelId,
      inputTokens: result.inputTokens,
      outputTokens: result.outputTokens,
      totalCost: calculateCost(result.inputTokens, result.outputTokens, selection.cost),
      duration: Date.now() - startTime,
      success: true,
      timestamp: startTime
    });

    return result;
  } catch (error) {
    router.trackCost({
      taskId: task.id,
      modelId: selection.modelId,
      inputTokens: 0,
      outputTokens: 0,
      totalCost: 0,
      duration: Date.now() - startTime,
      success: false,
      timestamp: startTime
    });

    throw error;
  }
}

3. Monitor Budgets

async function executeWithBudgetCheck(task: QETask) {
  const status = await tracker.checkBudget();

  if (status.exceeded) {
    throw new BudgetExceededError(
      'Daily budget exceeded',
      'daily',
      status.limit,
      status.current
    );
  }

  if (status.current / status.limit > 0.8) {
    console.warn('Budget at 80%, consider downgrading models');
  }

  return await execute(task);
}

Migration Guide

From Non-Routed to Routed

Before:

const result = await agent.generateTests({
  sourceFile: 'src/service.ts',
  model: 'gpt-4'  // Hardcoded
});

After:

const router = new ModelRouter(config);
const selection = await router.selectModel(task);

const result = await agent.generateTests({
  sourceFile: 'src/service.ts',
  model: selection.modelId  // Auto-selected
});


Questions? Open an issue: https://github.com/proffesor-for-testing/agentic-qe/issues