adaptive-memory-multi-model-router 2.6.0 → 2.7.0

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.
@@ -0,0 +1,371 @@
1
+ "use strict";
2
+ /**
3
+ * Provider Health Manager with Circuit Breaker
4
+ *
5
+ * Intelligent failover system for A3M Router providing:
6
+ * - Rolling window metrics tracking (latency, error rate)
7
+ * - Health scoring based on latency percentile + error rate
8
+ * - Circuit breaker: 3 consecutive errors → 60s cooldown
9
+ * - Probe mode after cooldown for recovery
10
+ * - Sorted fallback chain based on health scores
11
+ *
12
+ * Usage:
13
+ * import { ProviderHealthManager, ProviderHealth } from './routing/providerHealth';
14
+ *
15
+ * const healthManager = new ProviderHealthManager();
16
+ *
17
+ * // Record outcomes
18
+ * healthManager.recordSuccess('openai/gpt-4o', 150);
19
+ * healthManager.recordFailure('anthropic/claude-3-5-sonnet', 'rate_limit');
20
+ *
21
+ * // Get health status
22
+ * const health = healthManager.getHealth('openai/gpt-4o');
23
+ *
24
+ * // Get sorted fallback chain
25
+ * const chain = healthManager.getFallbackChain(['openai/gpt-4o', 'anthropic/claude-3-5-sonnet']);
26
+ */
27
+ Object.defineProperty(exports, "__esModule", { value: true });
28
+ exports.ProviderHealthManager = exports.HealthEvent = void 0;
29
+ const events_1 = require("events");
30
+ // ============================================================
31
+ // Events
32
+ // ============================================================
33
+ var HealthEvent;
34
+ (function (HealthEvent) {
35
+ HealthEvent["HEALTH_CHANGED"] = "healthChanged";
36
+ HealthEvent["CIRCUIT_OPENED"] = "circuitOpened";
37
+ HealthEvent["CIRCUIT_CLOSED"] = "circuitClosed";
38
+ HealthEvent["COOLDOWN_STARTED"] = "cooldownStarted";
39
+ HealthEvent["COOLDOWN_ENDED"] = "cooldownEnded";
40
+ HealthEvent["PROVIDER_DISABLED"] = "providerDisabled";
41
+ HealthEvent["PROVIDER_ENABLED"] = "providerEnabled";
42
+ HealthEvent["PROBE_ALLOWED"] = "probeAllowed";
43
+ })(HealthEvent || (exports.HealthEvent = HealthEvent = {}));
44
+ // ============================================================
45
+ // ProviderHealthManager
46
+ // ============================================================
47
+ class ProviderHealthManager extends events_1.EventEmitter {
48
+ // Rolling window metrics per provider
49
+ metrics = new Map();
50
+ // Current health state per provider
51
+ health = new Map();
52
+ // Disabled providers (manual disable)
53
+ disabled = new Map();
54
+ // Config
55
+ config;
56
+ constructor(config = {}) {
57
+ super();
58
+ this.config = {
59
+ windowSize: config.windowSize ?? 100,
60
+ circuitBreakerThreshold: config.circuitBreakerThreshold ?? 3,
61
+ cooldownMs: config.cooldownMs ?? 60000,
62
+ latencyPercentile: config.latencyPercentile ?? 95,
63
+ weights: {
64
+ latency: config.weights?.latency ?? 0.3,
65
+ errorRate: config.weights?.errorRate ?? 0.5,
66
+ consecutiveErrors: config.weights?.consecutiveErrors ?? 0.2,
67
+ },
68
+ };
69
+ }
70
+ /**
71
+ * Record a successful request
72
+ */
73
+ recordSuccess(provider, latencyMs) {
74
+ this.ensureProviderExists(provider);
75
+ const now = Date.now();
76
+ const window = this.getMetricsWindow(provider);
77
+ window.push({
78
+ name: provider,
79
+ totalRequests: 1,
80
+ successfulRequests: 1,
81
+ failedRequests: 0,
82
+ totalLatency: latencyMs,
83
+ lastLatency: latencyMs,
84
+ });
85
+ // Trim to window size
86
+ while (window.length > this.config.windowSize) {
87
+ window.shift();
88
+ }
89
+ // Update health state
90
+ const health = this.health.get(provider);
91
+ health.lastSuccess = now;
92
+ health.consecutiveErrors = 0;
93
+ health.cooldownUntil = 0;
94
+ health.isHealthy = true;
95
+ // Recalculate health score
96
+ this.recalculateHealthScore(provider);
97
+ this.emit(HealthEvent.HEALTH_CHANGED, health);
98
+ }
99
+ /**
100
+ * Record a failed request
101
+ */
102
+ recordFailure(provider, error) {
103
+ this.ensureProviderExists(provider);
104
+ const now = Date.now();
105
+ const window = this.getMetricsWindow(provider);
106
+ window.push({
107
+ name: provider,
108
+ totalRequests: 1,
109
+ successfulRequests: 0,
110
+ failedRequests: 1,
111
+ totalLatency: 0,
112
+ lastLatency: 0,
113
+ });
114
+ // Trim to window size
115
+ while (window.length > this.config.windowSize) {
116
+ window.shift();
117
+ }
118
+ // Update health state
119
+ const health = this.health.get(provider);
120
+ health.lastError = now;
121
+ health.consecutiveErrors++;
122
+ // Check circuit breaker
123
+ if (health.consecutiveErrors >= this.config.circuitBreakerThreshold) {
124
+ health.cooldownUntil = now + this.config.cooldownMs;
125
+ health.isHealthy = false;
126
+ this.emit(HealthEvent.CIRCUIT_OPENED, {
127
+ provider,
128
+ consecutiveErrors: health.consecutiveErrors,
129
+ cooldownUntil: health.cooldownUntil,
130
+ reason: error,
131
+ });
132
+ this.emit(HealthEvent.COOLDOWN_STARTED, {
133
+ provider,
134
+ duration: this.config.cooldownMs,
135
+ reason: error,
136
+ });
137
+ }
138
+ this.recalculateHealthScore(provider);
139
+ this.emit(HealthEvent.HEALTH_CHANGED, health);
140
+ }
141
+ /**
142
+ * Get current health for a provider
143
+ */
144
+ getHealth(provider) {
145
+ this.ensureProviderExists(provider);
146
+ return { ...this.health.get(provider) };
147
+ }
148
+ /**
149
+ * Get all provider health statuses
150
+ */
151
+ getAllHealth() {
152
+ const result = new Map();
153
+ for (const [name, health] of this.health.entries()) {
154
+ result.set(name, { ...health });
155
+ }
156
+ return result;
157
+ }
158
+ /**
159
+ * Check if a provider is available (healthy and not in cooldown/manual disable)
160
+ */
161
+ isAvailable(provider) {
162
+ const health = this.health.get(provider);
163
+ if (!health)
164
+ return false;
165
+ // Check manual disable
166
+ const disabled = this.disabled.get(provider);
167
+ if (disabled && disabled.until > Date.now()) {
168
+ return false;
169
+ }
170
+ // Check cooldown
171
+ if (health.cooldownUntil > Date.now()) {
172
+ return false;
173
+ }
174
+ return health.isHealthy;
175
+ }
176
+ /**
177
+ * Check if cooldown has expired and probe is allowed
178
+ */
179
+ isProbeAllowed(provider) {
180
+ const health = this.health.get(provider);
181
+ if (!health)
182
+ return false;
183
+ // If not in cooldown, no probe needed
184
+ if (health.cooldownUntil === 0)
185
+ return true;
186
+ // If cooldown has expired
187
+ if (health.cooldownUntil <= Date.now()) {
188
+ // Only allow one probe request per cooldown period
189
+ // After probe (marked by consecutiveErrors reset), normal requests allowed
190
+ return true;
191
+ }
192
+ return false;
193
+ }
194
+ /**
195
+ * Get the best provider from a list based on health scores
196
+ */
197
+ getBestProvider(providers) {
198
+ const available = providers.filter(p => this.isAvailable(p));
199
+ if (available.length === 0)
200
+ return null;
201
+ return available.reduce((best, current) => {
202
+ const health = this.health.get(current);
203
+ const bestHealth = this.health.get(best);
204
+ if (!health || !bestHealth)
205
+ return current;
206
+ return health.healthScore >= bestHealth.healthScore ? current : best;
207
+ });
208
+ }
209
+ /**
210
+ * Get sorted fallback chain based on health scores
211
+ * Returns providers sorted by health score (descending)
212
+ */
213
+ getFallbackChain(providers) {
214
+ // Score each provider
215
+ const scored = providers.map(p => ({
216
+ provider: p,
217
+ score: this.isAvailable(p) ? (this.health.get(p)?.healthScore ?? 0) : -1,
218
+ }));
219
+ // Sort by health score (descending), unavailable at end
220
+ scored.sort((a, b) => {
221
+ if (a.score === -1 && b.score === -1)
222
+ return 0;
223
+ if (a.score === -1)
224
+ return 1;
225
+ if (b.score === -1)
226
+ return -1;
227
+ return b.score - a.score;
228
+ });
229
+ return scored.map(s => s.provider);
230
+ }
231
+ /**
232
+ * Mark provider as disabled (manual circuit breaker)
233
+ */
234
+ disableProvider(provider, reason) {
235
+ const until = Number.MAX_SAFE_INTEGER; // Manual disable until explicitly enabled
236
+ this.disabled.set(provider, { reason, until });
237
+ const health = this.health.get(provider);
238
+ if (health) {
239
+ health.isHealthy = false;
240
+ }
241
+ this.emit(HealthEvent.PROVIDER_DISABLED, { provider, reason });
242
+ }
243
+ /**
244
+ * Enable a previously disabled provider
245
+ */
246
+ enableProvider(provider) {
247
+ this.disabled.delete(provider);
248
+ const health = this.health.get(provider);
249
+ if (health) {
250
+ health.isHealthy = true;
251
+ health.consecutiveErrors = 0;
252
+ health.cooldownUntil = 0;
253
+ }
254
+ this.emit(HealthEvent.PROVIDER_ENABLED, { provider });
255
+ }
256
+ /**
257
+ * Clear cooldown and reset circuit breaker for a provider
258
+ */
259
+ resetCircuitBreaker(provider) {
260
+ const health = this.health.get(provider);
261
+ if (health) {
262
+ health.consecutiveErrors = 0;
263
+ health.cooldownUntil = 0;
264
+ health.isHealthy = true;
265
+ this.emit(HealthEvent.CIRCUIT_CLOSED, { provider });
266
+ }
267
+ }
268
+ /**
269
+ * Get health stats for monitoring
270
+ */
271
+ getStats() {
272
+ let healthyCount = 0;
273
+ let cooldownCount = 0;
274
+ let disabledCount = 0;
275
+ let totalScore = 0;
276
+ for (const [name, health] of this.health.entries()) {
277
+ totalScore += health.healthScore;
278
+ if (!health.isHealthy && health.cooldownUntil > Date.now()) {
279
+ cooldownCount++;
280
+ }
281
+ else if (health.isHealthy) {
282
+ healthyCount++;
283
+ }
284
+ if (this.disabled.has(name)) {
285
+ disabledCount++;
286
+ }
287
+ }
288
+ const total = this.health.size;
289
+ return {
290
+ totalProviders: total,
291
+ healthyProviders: healthyCount,
292
+ cooldownProviders: cooldownCount,
293
+ disabledProviders: disabledCount,
294
+ avgHealthScore: total > 0 ? totalScore / total : 0,
295
+ };
296
+ }
297
+ // ============================================================
298
+ // Private Methods
299
+ // ============================================================
300
+ ensureProviderExists(provider) {
301
+ if (!this.health.has(provider)) {
302
+ const now = Date.now();
303
+ this.health.set(provider, {
304
+ name: provider,
305
+ latency: 0,
306
+ errorRate: 0,
307
+ lastSuccess: 0,
308
+ lastError: 0,
309
+ consecutiveErrors: 0,
310
+ isHealthy: true,
311
+ cooldownUntil: 0,
312
+ healthScore: 1.0,
313
+ });
314
+ this.metrics.set(provider, []);
315
+ }
316
+ }
317
+ getMetricsWindow(provider) {
318
+ return this.metrics.get(provider) ?? [];
319
+ }
320
+ recalculateHealthScore(provider) {
321
+ const window = this.getMetricsWindow(provider);
322
+ const health = this.health.get(provider);
323
+ if (!health || window.length === 0)
324
+ return;
325
+ // Calculate error rate
326
+ const totalRequests = window.reduce((sum, m) => sum + m.totalRequests, 0);
327
+ const failedRequests = window.reduce((sum, m) => sum + m.failedRequests, 0);
328
+ const errorRate = totalRequests > 0 ? failedRequests / totalRequests : 0;
329
+ health.errorRate = errorRate;
330
+ // Calculate latency metrics
331
+ const latencies = window.filter(m => m.totalLatency > 0).map(m => m.lastLatency);
332
+ const avgLatency = latencies.length > 0
333
+ ? latencies.reduce((a, b) => a + b, 0) / latencies.length
334
+ : 0;
335
+ health.latency = avgLatency;
336
+ // Percentile latency (simplified: use avg latency as proxy)
337
+ // For true percentile, we'd need raw data points
338
+ const latencyScore = this.calculateLatencyScore(avgLatency);
339
+ // Health score: weighted combination
340
+ // Higher error rate = lower score, higher latency = lower score
341
+ const errorScore = 1 - errorRate;
342
+ const consecutiveScore = Math.max(0, 1 - (health.consecutiveErrors / this.config.circuitBreakerThreshold));
343
+ const score = this.config.weights.latency * latencyScore +
344
+ this.config.weights.errorRate * errorScore +
345
+ this.config.weights.consecutiveErrors * consecutiveScore;
346
+ health.healthScore = Math.max(0, Math.min(1, score));
347
+ }
348
+ calculateLatencyScore(avgLatency) {
349
+ // Latency score: 1 at 0ms, 0 at 10000ms+, with exponential decay
350
+ // Configurable thresholds could be passed in
351
+ const latencyThresholds = {
352
+ excellent: 100, // 100ms - score 1.0
353
+ good: 500, // 500ms - score 0.8
354
+ acceptable: 1000, // 1s - score 0.6
355
+ poor: 3000, // 3s - score 0.3
356
+ terrible: 10000, // 10s+ - score 0.0
357
+ };
358
+ if (avgLatency <= 0)
359
+ return 1.0;
360
+ if (avgLatency <= latencyThresholds.excellent)
361
+ return 1.0;
362
+ if (avgLatency >= latencyThresholds.terrible)
363
+ return 0.0;
364
+ // Exponential interpolation
365
+ const k = 0.003; // decay constant
366
+ return Math.exp(-k * avgLatency);
367
+ }
368
+ }
369
+ exports.ProviderHealthManager = ProviderHealthManager;
370
+ exports.default = ProviderHealthManager;
371
+ //# sourceMappingURL=providerHealth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providerHealth.js","sourceRoot":"","sources":["../../src/routing/providerHealth.ts"],"names":[],"mappings":"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"}
package/dist/sdk.d.ts ADDED
@@ -0,0 +1,124 @@
1
+ /**
2
+ * A3M Router TypeScript SDK
3
+ *
4
+ * Clean wrapper class providing a better DX than raw exports.
5
+ *
6
+ * Usage:
7
+ * import { A3MRouter } from 'adaptive-memory-multi-model-router/sdk';
8
+ *
9
+ * const router = new A3MRouter();
10
+ *
11
+ * // Route a query (no execution, just model selection)
12
+ * const decision = router.route("What is 2+2?");
13
+ * console.log(decision.model, decision.tier, decision.cost);
14
+ *
15
+ * // Start the OpenAI-compatible proxy server
16
+ * const proxyURL = await router.serve(8787);
17
+ *
18
+ * // Use with any OpenAI SDK
19
+ * import OpenAI from 'openai';
20
+ * const client = new OpenAI({ baseURL: router.proxyURL });
21
+ * const response = await client.chat.completions.create({
22
+ * model: 'auto',
23
+ * messages: [{ role: 'user', content: 'Hello' }]
24
+ * });
25
+ */
26
+ export interface RoutingResult {
27
+ /** Selected model identifier (e.g. "groq/llama-3.3-70b-versatile") */
28
+ model: string;
29
+ /** Cost tier classification */
30
+ tier: 'free' | 'cheap' | 'mid' | 'premium';
31
+ /** Estimated cost in USD */
32
+ cost: number;
33
+ /** Complexity score 0.0–1.0 */
34
+ complexity: number;
35
+ /** Human-readable reasoning for the selection */
36
+ reasoning: string;
37
+ /** Alternative models in priority order */
38
+ fallbackModels: string[];
39
+ /** Whether the selected model is free */
40
+ isFree: boolean;
41
+ /** Whether this is classified as an expert-level query */
42
+ isExpert: boolean;
43
+ }
44
+ export interface QueryFeatures {
45
+ complexity: number;
46
+ length: number;
47
+ has_code: boolean;
48
+ has_math: boolean;
49
+ is_multilingual: boolean;
50
+ is_translation: boolean;
51
+ is_creative: boolean;
52
+ requires_reasoning: boolean;
53
+ is_security: boolean;
54
+ is_devops: boolean;
55
+ is_data: boolean;
56
+ detected_domain: string;
57
+ domain_score: number;
58
+ }
59
+ export interface A3MRouterConfig {
60
+ /** Default model to use when routing is ambiguous */
61
+ defaultModel?: string;
62
+ /** Maximum cost per query in USD (routes to cheaper models if exceeded) */
63
+ maxCostPerQuery?: number;
64
+ /** Prefer fast responses over higher quality */
65
+ preferSpeedOverQuality?: boolean;
66
+ /** Restrict routing to these provider IDs */
67
+ providers?: string[];
68
+ }
69
+ export declare class A3MRouter {
70
+ private config;
71
+ private _proxyURL;
72
+ constructor(config?: A3MRouterConfig);
73
+ /**
74
+ * Route a query — returns model selection without executing it.
75
+ *
76
+ * @param query - The user prompt to route
77
+ * @returns Routing decision with model, tier, cost, complexity
78
+ */
79
+ route(query: string): RoutingResult;
80
+ /**
81
+ * Route multiple queries in batch.
82
+ *
83
+ * @param queries - Array of user prompts
84
+ * @returns Array of routing decisions
85
+ */
86
+ routeBatch(queries: string[]): RoutingResult[];
87
+ /**
88
+ * Get model recommendation for a task description.
89
+ *
90
+ * @param task - Task description (e.g. "code generation", "summarization")
91
+ * @returns Routing decision
92
+ */
93
+ recommend(task: string): RoutingResult;
94
+ /**
95
+ * Start the OpenAI-compatible proxy server.
96
+ *
97
+ * @param port - Port to listen on (default: 8787)
98
+ * @returns The proxy base URL (e.g. "http://localhost:8787/v1")
99
+ */
100
+ serve(port?: number): Promise<string>;
101
+ /**
102
+ * Get the proxy URL. Available after serve() is called,
103
+ * otherwise returns the default.
104
+ */
105
+ get proxyURL(): string;
106
+ /**
107
+ * Extract features from a query for debugging or analysis.
108
+ *
109
+ * @param query - The user prompt to analyze
110
+ * @returns Detailed feature breakdown
111
+ */
112
+ analyze(query: string): QueryFeatures;
113
+ /**
114
+ * Classify a complexity score into a named tier.
115
+ */
116
+ private classifyTier;
117
+ }
118
+ /**
119
+ * Convenience: create an A3MRouter instance.
120
+ *
121
+ * @param config - Optional configuration
122
+ * @returns Configured A3MRouter instance
123
+ */
124
+ export declare function createSDK(config?: A3MRouterConfig): A3MRouter;