File size: 8,362 Bytes
5569a86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/**
 * Routing simulation β€” exercises the real routeRequest() against an in-memory
 * DB seeded with synthetic traffic, so you can SEE how each strategy distributes
 * requests and how the bandit adapts when a model starts failing.
 *
 *   cd server && npm run build && node dist/scripts/routing-sim.js
 */
process.env.ENCRYPTION_KEY = '0'.repeat(64);
process.env.DEV_MODE = 'true';

import { initDb, getDb } from '../db/index.js';
import { encrypt } from '../lib/crypto.js';
import {
  routeRequest, refreshStatsCache, setRoutingStrategy, getRoutingScores,
} from '../services/router.js';
import type { RoutingStrategy } from '../services/scoring.js';

interface Profile {
  platform: string; modelId: string; name: string;
  intelligenceRank: number; sizeLabel: string; budget: string;
  // synthetic history
  successes: number; failures: number; outTokens: number; latencyMs: number; ttfbMs: number;
}

// Deliberately no model that wins on every axis β€” so each strategy's weight
// vector picks a DIFFERENT winner (that's the whole point of the redesign).
const PROFILES: Profile[] = [
  // Frontier intelligence, good-not-great reliability, genuinely slow β†’ wins SMARTEST.
  { platform: 'google', modelId: 'gemini-x', name: 'Frontier-Smart', intelligenceRank: 1, sizeLabel: 'Frontier', budget: '~50M', successes: 22, failures: 4, outTokens: 200, latencyMs: 5000, ttfbMs: 2500 },
  // Small/dumb but blazing fast and reliable β†’ wins FASTEST.
  { platform: 'groq', modelId: 'llama-fast', name: 'Speed-King', intelligenceRank: 9, sizeLabel: 'Small', budget: '~50M', successes: 27, failures: 3, outTokens: 1100, latencyMs: 700, ttfbMs: 110 },
  // Middling intel/speed but rock-solid reliability β†’ wins RELIABLE.
  { platform: 'openrouter', modelId: 'rock', name: 'Rock-Solid', intelligenceRank: 5, sizeLabel: 'Medium', budget: '~50M', successes: 40, failures: 1, outTokens: 250, latencyMs: 2000, ttfbMs: 1500 },
  // Large/smart but flaky β€” high intelligence dragged down by failures.
  { platform: 'cerebras', modelId: 'smart-flaky', name: 'Smart-Flaky', intelligenceRank: 3, sizeLabel: 'Large', budget: '~50M', successes: 18, failures: 14, outTokens: 500, latencyMs: 1500, ttfbMs: 600 },
];

async function seed() {
  const db = getDb();
  await db.execMulti('DELETE FROM fallback_config; DELETE FROM api_keys; DELETE FROM models; DELETE FROM requests;');

  for (let i = 0; i < PROFILES.length; i++) {
    const p = PROFILES[i];
    await db.run(`
      INSERT INTO models (platform, model_id, display_name, intelligence_rank, speed_rank, size_label, rpm_limit, rpd_limit, tpm_limit, tpd_limit, monthly_token_budget, enabled)
      VALUES (?, ?, ?, ?, 1, ?, 100000, 1000000, 100000000, 1000000000, ?, 1)
    `, [p.platform, p.modelId, p.name, p.intelligenceRank, p.sizeLabel, p.budget]);

    const modelRow = await db.get<{ id: number }>('SELECT id FROM models WHERE platform=? AND model_id=?', [p.platform, p.modelId]);
    const id = modelRow!.id;

    await db.run('INSERT INTO fallback_config (model_db_id, priority, enabled) VALUES (?, ?, 1)', [id, i + 1]);

    const { encrypted, iv, authTag } = encrypt(`key-${p.platform}`);
    await db.run(`INSERT INTO api_keys (platform, label, encrypted_key, iv, auth_tag, status, enabled) VALUES (?, 'sim', ?, ?, ?, 'healthy', 1)`, [p.platform, encrypted, iv, authTag]);

    for (let s = 0; s < p.successes; s++) {
      await db.run(`
        INSERT INTO requests (platform, model_id, key_id, status, input_tokens, output_tokens, latency_ms, error, ttfb_ms)
        VALUES (?, ?, 1, 'success', 0, ?, ?, ?, ?)
      `, [p.platform, p.modelId, p.outTokens, p.latencyMs, null, p.ttfbMs]);
    }
    for (let f = 0; f < p.failures; f++) {
      await db.run(`
        INSERT INTO requests (platform, model_id, key_id, status, input_tokens, output_tokens, latency_ms, error, ttfb_ms)
        VALUES (?, ?, 1, 'error', 0, 0, ?, ?, ?)
      `, [p.platform, p.modelId, p.latencyMs, 'sim-fail', p.ttfbMs]);
    }
  }
}

async function distribution(runs: number): Promise<Map<string, number>> {
  const counts = new Map<string, number>();
  for (let i = 0; i < runs; i++) {
    const r = await routeRequest(100);
    counts.set(r.displayName, (counts.get(r.displayName) ?? 0) + 1);
  }
  return counts;
}

function pct(n: number, total: number): string {
  return `${((n / total) * 100).toFixed(1)}%`.padStart(6);
}

function printDistribution(title: string, counts: Map<string, number>, runs: number) {
  console.log(`\n  ${title}`);
  const sorted = [...counts.entries()].sort((a, b) => b[1] - a[1]);
  for (const [name, n] of sorted) {
    const bar = 'β–ˆ'.repeat(Math.round((n / runs) * 30));
    console.log(`    ${name.padEnd(22)} ${pct(n, runs)}  ${bar}`);
  }
}

async function printScores() {
  const { scores } = await getRoutingScores();
  console.log('    model                  rel  spd  int  guard  score');
  for (const s of scores) {
    const guard = s.headroom * s.rateLimit;
    console.log(
      `    ${s.displayName.padEnd(22)} ` +
      `${Math.round(s.reliability * 100).toString().padStart(3)}  ` +
      `${Math.round(s.speed * 100).toString().padStart(3)}  ` +
      `${Math.round(s.intelligence * 100).toString().padStart(3)}  ` +
      `${guard.toFixed(2).padStart(5)}  ` +
      `${s.score.toFixed(3)}`,
    );
  }
}

async function main() {
  await initDb(':memory:');
  await seed();
  const RUNS = 2000;

  console.log('\n══════════════════════════════════════════════════════════════');
  console.log('  ROUTING SIMULATION  β€”  4 models, 2000 requests per strategy');
  console.log('══════════════════════════════════════════════════════════════');
  console.log('\n  Synthetic profiles:');
  for (const p of PROFILES) {
    const rate = (p.successes / (p.successes + p.failures) * 100).toFixed(0);
    const tps = (p.outTokens * 1000 / p.latencyMs).toFixed(0);
    console.log(`    ${p.name.padEnd(22)} ${p.sizeLabel.padEnd(9)} success ${rate}%  ${tps} tok/s  ${p.ttfbMs}ms ttfb`);
  }

  const strategies: RoutingStrategy[] = ['priority', 'balanced', 'smartest', 'fastest', 'reliable'];
  for (const strat of strategies) {
    await setRoutingStrategy(strat);
    await refreshStatsCache(getDb(), true);
    if (strat === 'balanced') {
      console.log('\n  ── balanced score breakdown ──');
      await printScores();
    }
    const dist = await distribution(RUNS);
    printDistribution(`Strategy: ${strat.toUpperCase()}`, dist, RUNS);
  }

  // ── Adaptation: the favored model under 'balanced' suddenly starts failing ──
  console.log('\n══════════════════════════════════════════════════════════════');
  console.log('  ADAPTATION  β€”  inject a failure burst into the top model');
  console.log('══════════════════════════════════════════════════════════════');
  await setRoutingStrategy('balanced');
  await refreshStatsCache(getDb(), true);
  const before = await distribution(RUNS);
  printDistribution('Before (balanced, steady state)', before, RUNS);

  // Find the current favorite and slam it with fresh failures.
  const fav = [...before.entries()].sort((a, b) => b[1] - a[1])[0][0];
  const favProfile = PROFILES.find(p => p.name === fav)!;
  const db = getDb();

  for (let i = 0; i < 300; i++) {
    await db.run(`
      INSERT INTO requests (platform, model_id, key_id, status, input_tokens, output_tokens, latency_ms, error, ttfb_ms)
      VALUES (?, ?, 1, 'error', 0, 0, 1000, 'outage', NULL)
    `, [favProfile.platform, favProfile.modelId]);
  }
  console.log(`\n  β†’ Injected 300 fresh failures into "${fav}" (simulated outage)…`);
  await refreshStatsCache(getDb(), true);
  await printScores();
  const after = await distribution(RUNS);
  printDistribution('After (balanced, post-outage)', after, RUNS);
  console.log('');
}

main().catch(err => {
  console.error('Failed to run routing-sim:', err);
  process.exit(1);
});