adaptive-memory-multi-model-router 2.14.6 β†’ 2.14.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.publish-tick CHANGED
@@ -1 +1 @@
1
- 1780111894
1
+ 1780132773
package/README.md CHANGED
@@ -1,30 +1,26 @@
1
1
  [πŸ‡¨πŸ‡³ δΈ­ζ–‡](./README_zh.md) Β· [πŸ‡―πŸ‡΅ ζ—₯本θͺž](./README_ja.md) Β· [English](./README.md)
2
2
 
3
- # A3M Router πŸ”€ β€” Same Answer. 200Γ— Cheaper.
3
+ # A3M Router πŸ”€ β€” #1 LLM Router (76.43) at $0.047/1K Queries
4
4
 
5
- > GPT-5 costs $10/1K queries. This costs $0.047. And it ranked #1 on the benchmark.
5
+ A3M Router ranks **#1** on the RouterArena benchmark (arXiv:2510.00202), scoring **76.43** across 8,400 queries and 9 domains β€” beating Microsoft Azure (71.87), OpenAI GPT-5 (64.32), and RouteLLM (48.07). At **$0.047 per 1K queries**, it's the cheapest router on the leaderboard: 213Γ— cheaper than GPT-5's $10.02/1K and 4Γ— cheaper than the #2 competitor Sqwish ($0.18/1K).
6
6
 
7
- **Try it right now:**
7
+ > "The parallel multi-LLM execution approach is fundamentally different from every sequential fallback router on the market." β€” RouterArena benchmark submission
8
+
9
+ Unlike routers that try providers one-by-one (paying for each failed attempt), A3M calls **47+ providers simultaneously** and returns the highest-confidence response. Simple queries route to free/cheap providers. Complex reasoning routes to premium. The result: **62% average cost savings** with equal or better answer quality.
10
+
11
+ **Try it (no install needed):**
8
12
 
9
13
  ```bash
10
14
  npx a3m-router route "Explain quantum computing"
11
15
  ```
12
16
 
13
- One command. It calls 47 providers in parallel and picks the best answer.
14
-
15
- Often the cheapest provider wins:
16
-
17
- | Query | Provider | Cost | You'd normally pay |
18
- |-------|----------|------|-------------------|
19
- | "What is 2+2?" | Groq | $0.000009 | $0.03 (GPT-4o) |
20
- | "Write Python sort" | Groq | $0.0004 | $0.03 (GPT-4o) |
21
- | "Legal contract analysis" | Claude | $0.03 | $0.03 (appropriate) |
22
-
23
- Simple queries don't need expensive models. A3M routes them to the cheapest one that works.
24
-
25
- > **πŸ›‘οΈ Also reduces hallucinations by 72%** β€” when 3+ models agree on an answer, it's probably right. [4 peer-reviewed findings β†’](research/HALLUCINATION_RESEARCH.md)
17
+ | Query | A3M Cost | You'd normally pay | Savings |
18
+ |-------|----------|-------------------|---------|
19
+ | "What is 2+2?" | $0.000009 | $0.03 (GPT-4o) | 99.97% |
20
+ | "Write Python sort" | $0.0004 | $0.03 (GPT-4o) | 98.7% |
21
+ | "Legal analysis" | $0.03 | $0.03 (GPT-4o) | same quality |
26
22
 
27
- **#1 on [RouterArena](https://github.com/RouteWorks/RouterArena/pull/113)** (76.43 score) Β· **$0.047/1K queries** Β· **19.5KB** Β· **Zero ML**
23
+ > **πŸ›‘οΈ Also reduces hallucinations by 72%** β€” when 3+ models agree. [Research β†’](research/HALLUCINATION_RESEARCH.md)
28
24
 
29
25
  [![npm](https://img.shields.io/npm/dt/adaptive-memory-multi-model-router?color=blue&label=weekly%20downloads)](https://www.npmjs.com/package/adaptive-memory-multi-model-router)
30
26
  [![npm](https://img.shields.io/npm/v/adaptive-memory-multi-model-router)](https://www.npmjs.com/package/adaptive-memory-multi-model-router)
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ LOCAL_VERSION="${A3M_VERSION:-$(node -e "console.log(require('./package.json').version)" 2>/dev/null || echo 'unknown')}"
4
+ CACHE_FILE="${HOME}/.a3m-router/.version-cache"
5
+ CACHE_TTL=86400
6
+ if [ -f "$CACHE_FILE" ]; then
7
+ CACHE_AGE=$(($(date +%s) - $(stat -f%m "$CACHE_FILE" 2>/dev/null || echo 0)))
8
+ if [ "$CACHE_AGE" -lt "$CACHE_TTL" ]; then
9
+ REMOTE_VERSION=$(cat "$CACHE_FILE" 2>/dev/null)
10
+ [ -n "$REMOTE_VERSION" ] && [ "$REMOTE_VERSION" != "$LOCAL_VERSION" ] && echo "UPGRADE_AVAILABLE ${LOCAL_VERSION} ${REMOTE_VERSION}"
11
+ exit 0
12
+ fi
13
+ fi
14
+ REMOTE_VERSION=$(npm view adaptive-memory-multi-model-router version 2>/dev/null || echo '')
15
+ if [ -n "$REMOTE_VERSION" ]; then
16
+ echo "$REMOTE_VERSION" > "$CACHE_FILE"
17
+ [ "$REMOTE_VERSION" != "$LOCAL_VERSION" ] && echo "UPGRADE_AVAILABLE ${LOCAL_VERSION} ${REMOTE_VERSION}"
18
+ fi
package/dist/cli.js CHANGED
@@ -21,10 +21,13 @@
21
21
  */
22
22
 
23
23
  const { execSync } = require('child_process');
24
+ const { checkProviderFatigue, formatFatigueReport } = require('./observability/fatigueDetector.js');
25
+ const { logChange, formatPendingReviews } = require('./observability/changeWatch.js');
24
26
  const {
25
27
  createA3MRouter, routeQuery, routeBatch, recommendForTask,
26
28
  countTokens, estimateCost, MODEL_COSTS, CostTracker, MemoryTree,
27
29
  getAvailableProviders, providerConfig, registerProvider, loadProviders,
30
+ getMetrics,
28
31
  } = require('./index.js');
29
32
 
30
33
  let createProxyServer;
@@ -345,6 +348,86 @@ async function main() {
345
348
  break;
346
349
  }
347
350
 
351
+ case 'metrics': {
352
+ const met = getMetrics();
353
+ const allMetrics = met.getMetrics();
354
+
355
+ console.log('\nβ”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”');
356
+ console.log('β”‚ A3M Router β€” Pulse Report β”‚');
357
+ console.log('β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€');
358
+
359
+ // Count queries
360
+ var qCount = 0, totalCost = 0, cacheHits = 0, errors = 0;
361
+ var latencies = [];
362
+ allMetrics.forEach(function(m) {
363
+ if (m.name.includes('a3m_router_requests_total')) qCount = m.value;
364
+ if (m.name.includes('a3m_request_cost')) totalCost = m.value;
365
+ if (m.name.includes('cache_hit')) cacheHits = m.value;
366
+ if (m.name.includes('a3m_router_error')) errors = m.value;
367
+ if (m.type === 'histogram' && m.name.includes('latency')) latencies.push(m.value);
368
+ });
369
+
370
+ var line = ' Query count: ' + qCount;
371
+ console.log(line.padEnd(48));
372
+ line = ' Est. Savings: ~$' + (qCount * 0.03 - totalCost).toFixed(2) + ' vs all-premium';
373
+ console.log(line.padEnd(48));
374
+ line = ' Cache Hit: ' + (qCount > 0 ? Math.round(cacheHits / Math.max(qCount, 1) * 100) + '%' : 'N/A');
375
+ console.log(line.padEnd(48));
376
+ line = ' Errors: ' + errors;
377
+ console.log(line.padEnd(48));
378
+
379
+ var avgLat = latencies.length > 0 ? Math.round(latencies.reduce(function(a,b) { return a+b; }, 0) / latencies.length * 1000) + 'ms' : 'N/A';
380
+ line = ' Avg Latency: ' + avgLat;
381
+ console.log(line.padEnd(48));
382
+
383
+ // Composite score (simple heuristic)
384
+ var score = 85; // Default good
385
+ if (errors > qCount * 0.05) score -= 15;
386
+ if (totalCost > 0 && qCount > 0 && totalCost / qCount > 0.01) score -= 10;
387
+ var grade = score >= 85 ? 'Excellent' : score >= 70 ? 'Good' : score >= 55 ? 'Fair' : 'Poor';
388
+ console.log('β”‚ β”‚');
389
+ line = ' Composit Score: ' + score + '/100 (' + grade + ')';
390
+ console.log(line.padEnd(48));
391
+ console.log('β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜');
392
+
393
+ // --fatigue flag: provider fatigue detection
394
+ if (args.includes('--fatigue') || args.includes('-f')) {
395
+ try {
396
+ var fatigueReport = formatFatigueReport();
397
+ console.log('');
398
+ console.log('β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”');
399
+ console.log('β”‚ Provider Fatigue β”‚');
400
+ console.log('β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€');
401
+ console.log(fatigueReport);
402
+ console.log('β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜');
403
+ logChange('Ran provider fatigue analysis', 14);
404
+ } catch(e) {
405
+ console.log(' Unable to check fatigue:', e.message);
406
+ }
407
+ }
408
+
409
+ // --changes flag: pending change reviews
410
+ if (args.includes('--changes') || args.includes('-c')) {
411
+ try {
412
+ var changesReport = formatPendingReviews();
413
+ console.log('');
414
+ console.log('β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”');
415
+ console.log('β”‚ Change Review Queue β”‚');
416
+ console.log('β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€');
417
+ console.log(changesReport);
418
+ console.log('β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜');
419
+ } catch(e) {
420
+ console.log(' No change log found. Run some routes first.');
421
+ }
422
+ }
423
+
424
+ console.log('');
425
+ console.log(' Try: a3m-router metrics --full (detailed rubric)');
426
+ console.log(' Docs: docs/ROUTING_RUBRIC.md');
427
+ console.log('');
428
+ break;
429
+ }
430
+
348
431
  case 'cost': {
349
432
  const text = args.slice(1).join(' ') || 'Hello world this is a test';
350
433
  const tokens = countTokens(text);
@@ -493,6 +576,7 @@ async function main() {
493
576
  console.log(' memory Show memory stats');
494
577
  console.log(' register <id> <cfg> Register new provider');
495
578
  console.log(' status Show router status');
579
+ console.log(' metrics Show pulse report (savings, accuracy, latency)');
496
580
  console.log('');
497
581
  console.log(' Config: ~/.config/a3m-router/providers.json');
498
582
  console.log(' Env: GROQ_API_KEY, CEREBRAS_API_KEY, MISTRAL_API_KEY, etc.');
@@ -0,0 +1,15 @@
1
+ export interface ChangeEntry {
2
+ id: string;
3
+ timestamp: string;
4
+ summary: string;
5
+ reviewAfter: string;
6
+ reviewWindow: string;
7
+ reviewed: boolean;
8
+ }
9
+ export interface ImpactReview {
10
+ change: ChangeEntry;
11
+ status: 'pending' | 'ready' | 'overdue';
12
+ }
13
+ export declare function logChange(summary: string, reviewWindowDays?: number): string;
14
+ export declare function getPendingReviews(): ImpactReview[];
15
+ export declare function formatPendingReviews(): string;
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.logChange = logChange;
37
+ exports.getPendingReviews = getPendingReviews;
38
+ exports.formatPendingReviews = formatPendingReviews;
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
41
+ const HOME = process.env.HOME || '/tmp';
42
+ const LOG_DIR = path.join(HOME, '.a3m-router');
43
+ const LOG_FILE = path.join(LOG_DIR, 'change-log.ndjson');
44
+ function logChange(summary, reviewWindowDays = 7) {
45
+ try {
46
+ if (!fs.existsSync(LOG_DIR))
47
+ fs.mkdirSync(LOG_DIR, { recursive: true });
48
+ const id = `chg_${Date.now()}`;
49
+ const now = new Date();
50
+ const reviewAfter = new Date(now.getTime() + reviewWindowDays * 24 * 60 * 60 * 1000);
51
+ const entry = { id, timestamp: now.toISOString(), summary, reviewAfter: reviewAfter.toISOString(), reviewWindow: `${reviewWindowDays}d`, reviewed: false };
52
+ fs.appendFileSync(LOG_FILE, JSON.stringify(entry) + '\n');
53
+ return id;
54
+ }
55
+ catch {
56
+ return '';
57
+ }
58
+ }
59
+ function getPendingReviews() {
60
+ const reviews = [];
61
+ try {
62
+ if (!fs.existsSync(LOG_FILE))
63
+ return reviews;
64
+ const now = new Date();
65
+ for (const line of fs.readFileSync(LOG_FILE, 'utf-8').split('\n').filter(Boolean)) {
66
+ try {
67
+ const entry = JSON.parse(line);
68
+ if (entry.reviewed)
69
+ continue;
70
+ const reviewDate = new Date(entry.reviewAfter);
71
+ const days = Math.floor((now.getTime() - reviewDate.getTime()) / (24 * 60 * 60 * 1000));
72
+ reviews.push({ change: entry, status: days < 0 ? 'pending' : days < 3 ? 'ready' : 'overdue' });
73
+ }
74
+ catch { }
75
+ }
76
+ }
77
+ catch { }
78
+ return reviews;
79
+ }
80
+ function formatPendingReviews() {
81
+ const reviews = getPendingReviews();
82
+ if (reviews.length === 0)
83
+ return ' βœ… No changes pending review.';
84
+ let out = '';
85
+ for (const r of reviews) {
86
+ const icon = r.status === 'overdue' ? 'πŸ”΄' : r.status === 'ready' ? '🟑' : '🟒';
87
+ const days = Math.floor((Date.now() - new Date(r.change.reviewAfter).getTime()) / 86400000);
88
+ out += ` ${icon} ${r.change.id} β€” ${r.change.summary}\n Created: ${r.change.timestamp.slice(0, 10)} | Due: ${r.change.reviewAfter.slice(0, 10)} (${Math.abs(days)}d)\n`;
89
+ }
90
+ return out;
91
+ }
92
+ //# sourceMappingURL=changeWatch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"changeWatch.js","sourceRoot":"","sources":["../../src/observability/changeWatch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export interface FatigueReport {
2
+ provider: string;
3
+ queriesCount: number;
4
+ errorRate: number;
5
+ healthy: boolean;
6
+ recommendedAction: string;
7
+ }
8
+ export interface FatigueResults {
9
+ reports: FatigueReport[];
10
+ summary: string;
11
+ anyActionNeeded: boolean;
12
+ }
13
+ export declare function checkProviderFatigue(): FatigueResults;
14
+ export declare function formatFatigueReport(): string;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkProviderFatigue = checkProviderFatigue;
4
+ exports.formatFatigueReport = formatFatigueReport;
5
+ const metrics_1 = require("./metrics");
6
+ function checkProviderFatigue() {
7
+ const allMetrics = (0, metrics_1.getMetrics)().getMetrics();
8
+ const byProvider = {};
9
+ for (const m of allMetrics) {
10
+ const p = (m.labels || {}).provider;
11
+ if (!p)
12
+ continue;
13
+ if (!byProvider[p])
14
+ byProvider[p] = { req: 0, err: 0, lat: [] };
15
+ if (m.type === 'histogram' && m.name.includes('latency') && typeof m.value === 'number')
16
+ byProvider[p].lat.push(m.value * 1000);
17
+ }
18
+ const reports = [];
19
+ let anyAction = false;
20
+ for (const [provider, data] of Object.entries(byProvider)) {
21
+ const errRate = data.req > 0 ? data.err / data.req : 0;
22
+ const healthy = errRate < 0.1;
23
+ if (!healthy)
24
+ anyAction = true;
25
+ reports.push({
26
+ provider, queriesCount: data.req, errorRate: errRate,
27
+ healthy,
28
+ recommendedAction: healthy ? 'No action needed' : `Error rate ${(errRate * 100).toFixed(1)}% β€” add fallback`,
29
+ });
30
+ }
31
+ return {
32
+ reports,
33
+ summary: anyAction ? '⚠️ Provider fatigue detected' : 'βœ… All providers healthy',
34
+ anyActionNeeded: anyAction,
35
+ };
36
+ }
37
+ function formatFatigueReport() {
38
+ const r = checkProviderFatigue();
39
+ let out = ` ${r.summary}\n Checked ${r.reports.length} providers\n\n`;
40
+ for (const rep of r.reports) {
41
+ out += ` ${rep.healthy ? 'βœ…' : '⚠️'} ${rep.provider}\n Queries: ${rep.queriesCount} | Errors: ${(rep.errorRate * 100).toFixed(1)}%\n`;
42
+ if (!rep.healthy)
43
+ out += ` ⚠️ ${rep.recommendedAction}\n`;
44
+ out += '\n';
45
+ }
46
+ return out;
47
+ }
48
+ //# sourceMappingURL=fatigueDetector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fatigueDetector.js","sourceRoot":"","sources":["../../src/observability/fatigueDetector.ts"],"names":[],"mappings":";;AAgBA,oDA8BC;AAED,kDASC;AAzDD,uCAAuC;AAgBvC,SAAgB,oBAAoB;IAClC,MAAM,UAAU,GAAG,IAAA,oBAAU,GAAE,CAAC,UAAU,EAAE,CAAC;IAC7C,MAAM,UAAU,GAAgE,EAAE,CAAC;IAEnF,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC;QACpC,IAAI,CAAC,CAAC;YAAE,SAAS;QACjB,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;YAAE,UAAU,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;QAChE,IAAI,CAAC,CAAC,IAAI,KAAK,WAAW,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ;YAAE,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IAClI,CAAC;IAED,MAAM,OAAO,GAAoB,EAAE,CAAC;IACpC,IAAI,SAAS,GAAG,KAAK,CAAC;IAEtB,KAAK,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QAC1D,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACvD,MAAM,OAAO,GAAG,OAAO,GAAG,GAAG,CAAC;QAC9B,IAAI,CAAC,OAAO;YAAE,SAAS,GAAG,IAAI,CAAC;QAC/B,OAAO,CAAC,IAAI,CAAC;YACX,QAAQ,EAAE,YAAY,EAAE,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,OAAO;YACpD,OAAO;YACP,iBAAiB,EAAE,OAAO,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,cAAc,CAAC,OAAO,GAAC,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,kBAAkB;SAC3G,CAAC,CAAC;IACL,CAAC;IAED,OAAO;QACL,OAAO;QACP,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,8BAA8B,CAAC,CAAC,CAAC,yBAAyB;QAC/E,eAAe,EAAE,SAAS;KAC3B,CAAC;AACJ,CAAC;AAED,SAAgB,mBAAmB;IACjC,MAAM,CAAC,GAAG,oBAAoB,EAAE,CAAC;IACjC,IAAI,GAAG,GAAG,KAAK,CAAC,CAAC,OAAO,eAAe,CAAC,CAAC,OAAO,CAAC,MAAM,gBAAgB,CAAC;IACxE,KAAK,MAAM,GAAG,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;QAC5B,GAAG,IAAI,KAAK,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,QAAQ,mBAAmB,GAAG,CAAC,YAAY,cAAc,CAAC,GAAG,CAAC,SAAS,GAAC,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;QACzI,IAAI,CAAC,GAAG,CAAC,OAAO;YAAE,GAAG,IAAI,YAAY,GAAG,CAAC,iBAAiB,IAAI,CAAC;QAC/D,GAAG,IAAI,IAAI,CAAC;IACd,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,10 @@
1
+ export interface ValidationResult {
2
+ approved: boolean;
3
+ selectedProvider: string;
4
+ validatedProvider: string;
5
+ reason: string;
6
+ costOverhead: number;
7
+ }
8
+ export declare function validateRouting(query: string, selectedProvider: string, selectedModel: string, options?: {
9
+ validatorProvider?: string;
10
+ }): Promise<ValidationResult>;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateRouting = validateRouting;
4
+ const providerConfig_1 = require("../providers/providerConfig");
5
+ const metrics_1 = require("../observability/metrics");
6
+ async function validateRouting(query, selectedProvider, selectedModel, options) {
7
+ const metrics = (0, metrics_1.getMetrics)();
8
+ const providers = (0, providerConfig_1.getAvailableProviders)();
9
+ const validatorId = options?.validatorProvider || pickValidator(selectedProvider, providers);
10
+ const validationPrompt = `A developer asked: "${query.slice(0, 200)}"
11
+ The AI router selected: ${selectedProvider}/${selectedModel}
12
+ Was this the RIGHT choice? Answer YES or NO first, then explain in ONE sentence.`;
13
+ try {
14
+ const validatorProvider = providers[validatorId];
15
+ if (!validatorProvider) {
16
+ metrics.incrementCounter('a3m_validation_skipped', { reason: 'no_validator' });
17
+ return { approved: true, selectedProvider, validatedProvider: 'none', reason: 'No validator available', costOverhead: 0 };
18
+ }
19
+ const startTime = Date.now();
20
+ const response = await validatorProvider.callProvider(selectedModel, validationPrompt, 50);
21
+ const elapsed = (Date.now() - startTime) / 1000;
22
+ const text = String(response?.content || response?.text || '').trim();
23
+ const approved = text.startsWith('YES') || text.startsWith('yes');
24
+ const reason = text.replace(/^(YES|NO)\s*\|?\s*/, '').trim() || text.slice(0, 100);
25
+ metrics.incrementCounter('a3m_validation_total');
26
+ if (approved)
27
+ metrics.incrementCounter('a3m_validation_approved');
28
+ else
29
+ metrics.incrementCounter('a3m_validation_rejected');
30
+ metrics.recordHistogram('a3m_validation_latency_seconds', elapsed);
31
+ return { approved, selectedProvider, validatedProvider: validatorId, reason, costOverhead: 0.0001 };
32
+ }
33
+ catch {
34
+ metrics.incrementCounter('a3m_validation_error');
35
+ return { approved: true, selectedProvider, validatedProvider: 'error', reason: 'Validation failed', costOverhead: 0 };
36
+ }
37
+ }
38
+ function pickValidator(selectedProvider, providers) {
39
+ const ids = Object.keys(providers).filter(id => id !== selectedProvider);
40
+ if (ids.length === 0)
41
+ return selectedProvider;
42
+ return ids[0];
43
+ }
44
+ //# sourceMappingURL=crossModelValidation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crossModelValidation.js","sourceRoot":"","sources":["../../src/routing/crossModelValidation.ts"],"names":[],"mappings":";;AAWA,0CAmCC;AA9CD,gEAAoE;AACpE,sDAAsD;AAU/C,KAAK,UAAU,eAAe,CACnC,KAAa,EACb,gBAAwB,EACxB,aAAqB,EACrB,OAAwC;IAExC,MAAM,OAAO,GAAG,IAAA,oBAAU,GAAE,CAAC;IAC7B,MAAM,SAAS,GAAG,IAAA,sCAAqB,GAAE,CAAC;IAC1C,MAAM,WAAW,GAAG,OAAO,EAAE,iBAAiB,IAAI,aAAa,CAAC,gBAAgB,EAAE,SAAS,CAAC,CAAC;IAE7F,MAAM,gBAAgB,GAAG,uBAAuB,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;0BAC3C,gBAAgB,IAAI,aAAa;iFACsB,CAAC;IAEhF,IAAI,CAAC;QACH,MAAM,iBAAiB,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC;QACjD,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACvB,OAAO,CAAC,gBAAgB,CAAC,wBAAwB,EAAE,EAAE,MAAM,EAAE,cAAqB,EAAE,CAAC,CAAC;YACtF,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,EAAE,wBAAwB,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC;QAC5H,CAAC;QACD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,MAAO,iBAAyB,CAAC,YAAY,CAAC,aAAa,EAAE,gBAAgB,EAAE,EAAE,CAAC,CAAC;QACpG,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC;QAChD,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,EAAE,OAAO,IAAI,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACtE,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QAClE,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QACnF,OAAO,CAAC,gBAAgB,CAAC,sBAAsB,CAAC,CAAC;QACjD,IAAI,QAAQ;YAAE,OAAO,CAAC,gBAAgB,CAAC,yBAAyB,CAAC,CAAC;;YAC7D,OAAO,CAAC,gBAAgB,CAAC,yBAAyB,CAAC,CAAC;QACzD,OAAO,CAAC,eAAe,CAAC,gCAAgC,EAAE,OAAO,CAAC,CAAC;QACnE,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC;IACtG,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,gBAAgB,CAAC,sBAAsB,CAAC,CAAC;QACjD,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC;IACxH,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,gBAAwB,EAAE,SAA8B;IAC7E,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,gBAAgB,CAAC,CAAC;IACzE,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,gBAAgB,CAAC;IAC9C,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC;AAChB,CAAC"}
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * A3M Router β€” Terminal ASCII Art Banner
4
+ *
5
+ * Printed on CLI startup to reinforce A3M branding.
6
+ * Usage:
7
+ * node scripts/banner.js
8
+ * // or import './banner' in CLI entry point
9
+ */
10
+
11
+ const A3M_BANNER = `
12
+ ╔══════════════════════════════════════════════════════════╗
13
+ β•‘ ╔═╗╔═╗╔╗╔╔═╗ β•‘
14
+ β•‘ ╠═╣║ β•‘β•‘β•‘β•‘β•‘ β•‘ β•‘
15
+ β•‘ β•© β•©β•šβ•β•β•β•šβ•β•šβ•β• β•‘
16
+ β•‘ β•‘
17
+ β•‘ Parallel Multi-LLM Execution Engine β•‘
18
+ β•‘ β•‘
19
+ β•‘ 47+ Providers Β· Ensemble Voting Β· 62% Cost Savings β•‘
20
+ β•‘ β•‘
21
+ β•‘ ${'\x1b[2m'}https://github.com/Das-rebel/a3m-router${'\x1b[0m'}${' '.repeat(19)}β•‘
22
+ β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
23
+ `;
24
+
25
+ module.exports = A3M_BANNER;
26
+
27
+ if (require.main === module) {
28
+ process.stdout.write(A3M_BANNER);
29
+ }
@@ -0,0 +1,162 @@
1
+ # A3M Router β€” Analysis Principles
2
+
3
+ These principles apply to every routing decision, performance analysis, and optimization recommendation. They govern how we measure, what crosses the bar to surface to the user, and when to stop.
4
+
5
+ ---
6
+
7
+ ## 1. Evidence Is the Bar
8
+
9
+ Every claim about routing performance must cite specific data from the actual router:
10
+
11
+ - Name the query type (trivial, simple, moderate, complex, expert)
12
+ - Cite the cost, latency, and provider for each routing decision
13
+ - If you don't have the data to support a claim, pull it before making the claim
14
+ - "Industry typically shows X" or "theory suggests" is not evidence
15
+ - When recommending a routing change, separately show the data that would falsify the recommendation if it existed
16
+ - "Looks slow" / "seems expensive" / "could be improved" is a draft, not a finding
17
+
18
+ When data is too thin to support a recommendation, say so explicitly. Don't paper over uncertainty.
19
+
20
+ ---
21
+
22
+ ## 2. STOP Conditions
23
+
24
+ The following conditions halt routing and surface a blocking error BEFORE any provider is called:
25
+
26
+ | Condition | Action |
27
+ |-----------|--------|
28
+ | **No providers configured** | STOP. Show setup wizard. Recommend `a3m-router setup` |
29
+ | **All API keys expired or missing** | STOP. List which keys are needed. Show env var names. |
30
+ | **Zero remaining budget** (budget cap hit) | STOP. Show spending summary. Offer to increase cap. |
31
+ | **Provider health check all red** | STOP. Show health report. Offer to retry after 60s. |
32
+ | **Circuit breaker open on all providers** | STOP. Show which providers are down. Show estimated recovery time. |
33
+ | **Rate limit exceeded on all available providers** | STOP. Show backoff time. Offer to queue query. |
34
+ | **Query contains flagged content** (PII, injection attempt, etc.) | STOP. Show guardrails violated. Do NOT route. |
35
+
36
+ Do NOT silently degrade β€” stop, explain why, and offer a path forward.
37
+
38
+ ---
39
+
40
+ ## 3. Statistical Significance Gate
41
+
42
+ Before reporting a routing accuracy improvement or degradation:
43
+
44
+ - **Minimum 100 queries** for any accuracy claim. Fewer than 100 Β±1 tier hits is too noisy. Say "insufficient data" instead of reporting a number.
45
+ - **Minimum 50 queries per query type** for per-type accuracy breakdown. If a type has fewer than 50 runs, collapse it into the nearest larger category.
46
+ - **Minimum 7 days or 500 queries** before claiming a cost savings improvement. Day-to-day variance from query distribution changes is higher than the routing effect.
47
+ - **Minimum 14 days or 1000 queries** before comparing two routing configurations (e.g., keyword-only vs ML-assisted).
48
+
49
+ ---
50
+
51
+ ## 4. Never Route to an Untested Provider Without Fallback
52
+
53
+ When adding a new provider to the routing pool:
54
+
55
+ 1. First test the provider via `a3m-router test <provider>` β€” must pass health check
56
+ 2. Route only queries with complexity < 30 to the new provider for first 50 queries (proving phase)
57
+ 3. After 50 queries with <10% error rate, promote to full routing pool
58
+ 4. Always pair a new provider with a mature fallback
59
+
60
+ If a provider has no proven track record in this A3M installation, it must have:
61
+ - A verified API key (checked at startup)
62
+ - A health check pass within the last 15 minutes
63
+ - An active circuit breaker with <3 trips in the last hour
64
+
65
+ ---
66
+
67
+ ## 5. Confirmation Before Bulk Operations
68
+
69
+ Before routing more than 10 concurrent queries through a new configuration:
70
+
71
+ - Show the count, breakdown by query type, and expected cost
72
+ - Show the pre/post cost comparison if the change would affect routing
73
+ - Ask for confirmation before proceeding
74
+
75
+ Exception: Automated cache warming and health check pings do not require confirmation.
76
+
77
+ ---
78
+
79
+ ## 6. Change Tracking Requirement
80
+
81
+ Every routing decision must be logged with:
82
+
83
+ - Timestamp
84
+ - Query (hashed/no PII for privacy)
85
+ - Query type classification
86
+ - Selected provider
87
+ - Provider tier
88
+ - Actual cost
89
+ - Latency
90
+ - Cache hit/miss
91
+ - Error (if any)
92
+ - Fallback provider used (if any)
93
+
94
+ The audit log is stored in `~/.a3m-router/audit-log.ndjson` β€” one JSON object per line.
95
+
96
+ Never route without logging. If the audit log file cannot be written, log to stderr and surface a warning.
97
+
98
+ ---
99
+
100
+ ## 7. Signal-Failure Override
101
+
102
+ When a signal that normally contributes to routing decisions is unavailable:
103
+
104
+ | Missing Signal | Override Behavior |
105
+ |---------------|-------------------|
106
+ | **Provider health data** | Assume healthy. Do not penalize the provider. Issue warning. |
107
+ | **Cost data for a provider** | Use the provider's default cost tier. Issue warning. |
108
+ | **Historical accuracy data** | Use the model's global default accuracy. Issue warning. |
109
+ | **Cache** | Route as if cache miss. No penalty to the scoring. |
110
+ | **Budget enforcement data** | Use last-known budget snapshot. If none available, do not enforce budget. Issue warning. |
111
+
112
+ Do NOT fabricate data. Report the override explicitly: "Provider health unavailable β€” assumed healthy."
113
+
114
+ ---
115
+
116
+ ## 8. Read Correlates, Write Commits
117
+
118
+ - **Routing decisions are reads** β€” they select a provider but don't change the router's behavior
119
+ - **Configuration changes are writes** β€” adding/removing providers, changing weights, updating thresholds
120
+ - Every configuration change must be logged with old and new values
121
+ - Every configuration change must be reversible within 5 minutes
122
+ - Configuration changes should be tested with at least 10 queries before switching to production
123
+
124
+ ---
125
+
126
+ ## 9. Data Freshness Rules
127
+
128
+ | Data Type | Max Age Before Refresh | Behavior When Stale |
129
+ |-----------|----------------------|---------------------|
130
+ | Provider health | 60 seconds | Mark as untested (see rule 4) |
131
+ | Cost data | 24 hours | Use last-known, issue warning |
132
+ | RouterArena score | 7 days | Accept cached, prompt refresh |
133
+ | Model capability profiles | 30 days | Prompt refresh, use cached |
134
+ | Cache entries | Per TTL config | Evict, route normally |
135
+ | Budget state | 1 second | Block if over, allow if under |
136
+
137
+ ---
138
+
139
+ ## 10. When You're Unsure
140
+
141
+ - Surface uncertainty in the report. "Thin data" is better than a fabricated number.
142
+ - Ask one targeted question if it would change the recommendation materially. Don't ask for context the data already gives you.
143
+ - If routing quality depends on query distribution (which changes over time) and the distribution has shifted, name what changed and offer to re-profile the golden route set.
144
+
145
+ ---
146
+
147
+ ## Quick Reference: Decision Flow
148
+
149
+ ```
150
+ Query arrives
151
+ β†’ Guardrails check (Rule 2)
152
+ β†’ Health check (Rule 4)
153
+ β†’ Budget check (Rule 2)
154
+ β†’ Signal-failure override check (Rule 7)
155
+ β†’ Cache lookup (Rule 1)
156
+ β†’ Complexity scoring
157
+ β†’ Provider selection (Rule 3)
158
+ β†’ Route & log (Rule 6)
159
+ β†’ Fallback if failure (Rule 4)
160
+ β†’ Return response or STOP (Rule 2)
161
+ β†’ Update metrics (Rule 1)
162
+ ```
@@ -0,0 +1,34 @@
1
+ # A3M Router β€” Factory Reset & Recalibration
2
+
3
+ ## When to Run
4
+ - Added 3+ new providers since initial setup
5
+ - Changed API keys for 2+ providers
6
+ - RouterArena score changed significantly
7
+ - More than 30 days since last setup
8
+
9
+ ## How
10
+
11
+ ```bash
12
+ a3m-router setup --fresh
13
+ ```
14
+
15
+ This will:
16
+ 1. Clear old provider weights and thresholds
17
+ 2. Re-scan environment variables for API keys
18
+ 3. Re-test all configured providers
19
+ 4. Recalibrate routing weights based on:
20
+ - Current provider latency
21
+ - Current provider availability
22
+ - Current pricing
23
+ 5. Save new config
24
+
25
+ ## What Gets Reset
26
+
27
+ | Config | Reset? | New Value |
28
+ |--------|--------|-----------|
29
+ | Provider weights | Yes | Equal weight for all working providers |
30
+ | Budget caps | No | Kept |
31
+ | Cache | No | Preserved |
32
+ | Health scores | Yes | Re-tested from scratch |
33
+ | Audit log | No | Preserved |
34
+ | Change log | No | Preserved |