adaptive-memory-multi-model-router 2.3.0 → 2.4.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.
Files changed (53) hide show
  1. package/README.md +81 -771
  2. package/package.json +1 -1
  3. package/src/skills/__tests__/skill_manager.test.ts +328 -0
  4. package/assets/benchmark-results-pro.svg +0 -1857
  5. package/assets/benchmark-results.png +0 -0
  6. package/assets/complexity-scoring-v2.png +0 -0
  7. package/assets/complexity-scoring.png +0 -0
  8. package/assets/cost-comparison-chart.png +0 -0
  9. package/assets/cost-comparison-pro.svg +0 -2708
  10. package/assets/cost-comparison-v2.png +0 -0
  11. package/assets/feature-comparison-v2.png +0 -0
  12. package/assets/feature-comparison-v3.png +0 -0
  13. package/assets/feature-matrix-pro.svg +0 -2899
  14. package/assets/growth-chart-pro.svg +0 -1050
  15. package/assets/hero-banner.svg +0 -2033
  16. package/assets/logo-icon.svg +0 -99
  17. package/assets/provider-health-chart.png +0 -0
  18. package/assets/provider-health-pro.svg +0 -2710
  19. package/assets/provider-health-v2.png +0 -0
  20. package/assets/routing-flow-pro.svg +0 -2238
  21. package/assets/routing-flow-v2.png +0 -0
  22. package/assets/routing-flow-v3.png +0 -0
  23. package/assets/routing-flow.png +0 -0
  24. package/assets/social-preview-pro.svg +0 -1685
  25. package/assets/tier-distribution-pro.svg +0 -2110
  26. package/assets/tier-distribution.png +0 -0
  27. package/dist/cache/cacheKeyGenerator.d.ts +0 -67
  28. package/dist/cache/cacheKeyGenerator.d.ts.map +0 -1
  29. package/dist/cache/cacheKeyGenerator.js +0 -211
  30. package/dist/cache/cacheKeyGenerator.js.map +0 -1
  31. package/dist/cost/preCallCostEstimator.d.ts +0 -114
  32. package/dist/cost/preCallCostEstimator.d.ts.map +0 -1
  33. package/dist/cost/preCallCostEstimator.js +0 -256
  34. package/dist/cost/preCallCostEstimator.js.map +0 -1
  35. package/dist/inference/speculativeDecoding.d.ts +0 -133
  36. package/dist/inference/speculativeDecoding.d.ts.map +0 -1
  37. package/dist/inference/speculativeDecoding.js +0 -276
  38. package/dist/inference/speculativeDecoding.js.map +0 -1
  39. package/dist/providers/providerHealth.d.ts +0 -117
  40. package/dist/providers/providerHealth.d.ts.map +0 -1
  41. package/dist/providers/providerHealth.js +0 -309
  42. package/dist/providers/providerHealth.js.map +0 -1
  43. package/dist/routing/difficultyClassifier.d.ts +0 -79
  44. package/dist/routing/difficultyClassifier.d.ts.map +0 -1
  45. package/dist/routing/difficultyClassifier.js +0 -329
  46. package/dist/routing/difficultyClassifier.js.map +0 -1
  47. package/dist/sdk.d.ts +0 -125
  48. package/docs/HN_CAMPAIGN.md +0 -785
  49. package/src/cache/cacheKeyGenerator.ts +0 -242
  50. package/src/cost/preCallCostEstimator.ts +0 -345
  51. package/src/inference/speculativeDecoding.ts +0 -373
  52. package/src/providers/providerHealth.ts +0 -397
  53. package/src/routing/difficultyClassifier.ts +0 -420
@@ -1,397 +0,0 @@
1
- /**
2
- * A3M Router - Provider Health Score
3
- *
4
- * Probabilistic health scoring (not binary up/down).
5
- * Uses latency history, error rate, partial failure patterns, queue depth estimates.
6
- *
7
- * Usage:
8
- * const healthMonitor = new ProviderHealthMonitor();
9
- * healthMonitor.recordLatency('groq', 250);
10
- * healthMonitor.recordError('groq', 'rate_limit');
11
- * const score = healthMonitor.getHealthScore('groq');
12
- * console.log(score); // { score: 0.85, confidence: 0.7, status: 'healthy' }
13
- */
14
-
15
- import { ProviderTier } from '../providers/providerConfig';
16
-
17
- // ============================================================
18
- // Types
19
- // ============================================================
20
-
21
- export type HealthStatus = 'healthy' | 'degraded' | 'critical';
22
-
23
- export interface HealthScore {
24
- /** Overall health score 0-1 */
25
- score: number;
26
- /** Confidence in the score 0-1 */
27
- confidence: number;
28
- /** Human-readable status */
29
- status: HealthStatus;
30
- /** Component scores */
31
- components: {
32
- latency: number;
33
- errorRate: number;
34
- recentTrend: number;
35
- };
36
- /** Last updated timestamp */
37
- lastUpdated: number;
38
- /** Metadata about current state */
39
- metadata: {
40
- sampleSize: number;
41
- isAnomaly: boolean;
42
- anomalyReason?: string;
43
- };
44
- }
45
-
46
- export interface HealthRecord {
47
- timestamp: number;
48
- latency?: number;
49
- success?: boolean;
50
- errorType?: string;
51
- partialFailure?: boolean;
52
- }
53
-
54
- export interface HealthConfig {
55
- /** EWMA alpha for latency smoothing */
56
- latencyAlpha?: number;
57
- /** EWMA alpha for error rate smoothing */
58
- errorAlpha?: number;
59
- /** Window size for anomaly detection */
60
- anomalyWindow?: number;
61
- /** Standard deviation threshold for anomaly */
62
- anomalyThreshold?: number;
63
- /** Health score thresholds */
64
- healthyThreshold?: number;
65
- /** Degraded threshold */
66
- degradedThreshold?: number;
67
- }
68
-
69
- const DEFAULT_CONFIG: Required<HealthConfig> = {
70
- latencyAlpha: 0.3,
71
- errorAlpha: 0.2,
72
- anomalyWindow: 20,
73
- anomalyThreshold: 2.0,
74
- healthyThreshold: 0.75,
75
- degradedThreshold: 0.4,
76
- };
77
-
78
- // ============================================================
79
- // ProviderHealthMonitor
80
- // ============================================================
81
-
82
- export class ProviderHealthMonitor {
83
- private records: Map<string, HealthRecord[]> = new Map();
84
- private config: Required<HealthConfig>;
85
- private ewmaLatency: Map<string, number> = new Map();
86
- private ewmaErrorRate: Map<string, number> = new Map();
87
- private anomalyScores: Map<string, number> = new Map();
88
-
89
- constructor(config: HealthConfig = {}) {
90
- this.config = { ...DEFAULT_CONFIG, ...config };
91
- }
92
-
93
- /**
94
- * Record a successful request with latency.
95
- */
96
- recordSuccess(providerId: string, latencyMs: number): void {
97
- this.addRecord(providerId, {
98
- timestamp: Date.now(),
99
- latency: latencyMs,
100
- success: true,
101
- });
102
- this.updateEwmaLatency(providerId, latencyMs);
103
- this.updateEwmaErrorRate(providerId, false);
104
- this.updateAnomalyScore(providerId, latencyMs);
105
- }
106
-
107
- /**
108
- * Record a failed request.
109
- */
110
- recordError(providerId: string, errorType: string = 'unknown'): void {
111
- this.addRecord(providerId, {
112
- timestamp: Date.now(),
113
- success: false,
114
- errorType,
115
- });
116
- this.updateEwmaErrorRate(providerId, true);
117
- }
118
-
119
- /**
120
- * Record a partial failure (slow response, incomplete output, etc).
121
- */
122
- recordPartialFailure(providerId: string, latencyMs?: number): void {
123
- this.addRecord(providerId, {
124
- timestamp: Date.now(),
125
- latency: latencyMs,
126
- success: false,
127
- partialFailure: true,
128
- });
129
- if (latencyMs !== undefined) {
130
- this.updateEwmaLatency(providerId, latencyMs);
131
- this.updateAnomalyScore(providerId, latencyMs);
132
- }
133
- // Partial failures count as half an error
134
- this.updateEwmaErrorRate(providerId, false, 0.5);
135
- }
136
-
137
- /**
138
- * Record queue depth estimate (when available from provider metadata).
139
- */
140
- recordQueueDepth(providerId: string, depth: number): void {
141
- // Queue depth affects latency estimation but doesn't directly
142
- // impact the health score unless it's extremely high
143
- const maxQueueDepth = 100;
144
- const queueFactor = 1 + Math.min(depth / maxQueueDepth, 1.0) * 0.5;
145
- const currentLatency = this.ewmaLatency.get(providerId) || 0;
146
- if (currentLatency > 0) {
147
- this.ewmaLatency.set(providerId, currentLatency * queueFactor);
148
- }
149
- }
150
-
151
- /**
152
- * Get the current health score for a provider.
153
- */
154
- getHealthScore(providerId: string): HealthScore {
155
- const records = this.getRecords(providerId);
156
- const sampleSize = records.length;
157
- const now = Date.now();
158
-
159
- // Not enough data
160
- if (sampleSize < 3) {
161
- return {
162
- score: sampleSize === 0 ? 1.0 : 0.8,
163
- confidence: sampleSize / 10, // 0 at 0 samples, 0.3 at 3 samples
164
- status: sampleSize === 0 ? 'healthy' : 'degraded',
165
- components: { latency: 1.0, errorRate: 1.0, recentTrend: 1.0 },
166
- lastUpdated: now,
167
- metadata: { sampleSize, isAnomaly: false },
168
- };
169
- }
170
-
171
- const latencyScore = this.calculateLatencyScore(providerId);
172
- const errorScore = this.calculateErrorScore(providerId);
173
- const trendScore = this.calculateTrendScore(providerId);
174
-
175
- // Weighted combination
176
- const score =
177
- latencyScore * 0.35 +
178
- errorScore * 0.45 +
179
- trendScore * 0.20;
180
-
181
- // Determine status
182
- let status: HealthStatus;
183
- if (score >= this.config.healthyThreshold) {
184
- status = 'healthy';
185
- } else if (score >= this.config.degradedThreshold) {
186
- status = 'degraded';
187
- } else {
188
- status = 'critical';
189
- }
190
-
191
- // Anomaly detection
192
- const anomalyScore = this.anomalyScores.get(providerId) || 0;
193
- const isAnomaly = anomalyScore > this.config.anomalyThreshold;
194
- const anomalyReason = isAnomaly
195
- ? `Anomaly detected: score ${anomalyScore.toFixed(2)} > threshold ${this.config.anomalyThreshold}`
196
- : undefined;
197
-
198
- // Confidence based on sample size and anomaly status
199
- const baseConfidence = Math.min(sampleSize / 30, 1.0);
200
- const confidence = isAnomaly
201
- ? Math.min(baseConfidence * 0.5, 0.5) // Lower confidence when anomalous
202
- : baseConfidence;
203
-
204
- return {
205
- score: Math.round(score * 1000) / 1000,
206
- confidence: Math.round(confidence * 1000) / 1000,
207
- status,
208
- components: {
209
- latency: Math.round(latencyScore * 1000) / 1000,
210
- errorRate: Math.round(errorScore * 1000) / 1000,
211
- recentTrend: Math.round(trendScore * 1000) / 1000,
212
- },
213
- lastUpdated: now,
214
- metadata: {
215
- sampleSize,
216
- isAnomaly,
217
- anomalyReason,
218
- },
219
- };
220
- }
221
-
222
- /**
223
- * Get health scores for all tracked providers.
224
- */
225
- getAllHealthScores(): Record<string, HealthScore> {
226
- const result: Record<string, HealthScore> = {};
227
- for (const providerId of this.records.keys()) {
228
- result[providerId] = this.getHealthScore(providerId);
229
- }
230
- return result;
231
- }
232
-
233
- /**
234
- * Get the recommended provider based on health scores.
235
- * Excludes providers that are 'critical'.
236
- */
237
- getRecommendedProvider(
238
- providers: Array<{ id: string; tier?: ProviderTier; priority?: number }>
239
- ): string | null {
240
- const candidates = providers.filter(p => {
241
- const score = this.getHealthScore(p.id);
242
- return score.status !== 'critical';
243
- });
244
-
245
- if (candidates.length === 0) return null;
246
-
247
- // Sort by health score, then by priority (if available)
248
- candidates.sort((a, b) => {
249
- const scoreA = this.getHealthScore(a.id).score;
250
- const scoreB = this.getHealthScore(b.id).score;
251
- if (Math.abs(scoreA - scoreB) > 0.1) {
252
- return scoreB - scoreA; // Higher score first
253
- }
254
- return (a.priority || 50) - (b.priority || 50); // Lower priority first
255
- });
256
-
257
- return candidates[0].id;
258
- }
259
-
260
- /**
261
- * Clear all records for a provider.
262
- */
263
- clearProvider(providerId: string): void {
264
- this.records.delete(providerId);
265
- this.ewmaLatency.delete(providerId);
266
- this.ewmaErrorRate.delete(providerId);
267
- this.anomalyScores.delete(providerId);
268
- }
269
-
270
- /**
271
- * Clear all records for all providers.
272
- */
273
- clearAll(): void {
274
- this.records.clear();
275
- this.ewmaLatency.clear();
276
- this.ewmaErrorRate.clear();
277
- this.anomalyScores.clear();
278
- }
279
-
280
- // ---- Private methods ----
281
-
282
- private addRecord(providerId: string, record: HealthRecord): void {
283
- if (!this.records.has(providerId)) {
284
- this.records.set(providerId, []);
285
- }
286
- const records = this.records.get(providerId)!;
287
- records.push(record);
288
-
289
- // Keep only recent records (last hour)
290
- const oneHourAgo = Date.now() - 3600000;
291
- const filtered = records.filter(r => r.timestamp >= oneHourAgo);
292
- this.records.set(providerId, filtered);
293
- }
294
-
295
- private getRecords(providerId: string): HealthRecord[] {
296
- return this.records.get(providerId) || [];
297
- }
298
-
299
- private updateEwmaLatency(providerId: string, latency: number): void {
300
- const current = this.ewmaLatency.get(providerId);
301
- if (current === undefined) {
302
- this.ewmaLatency.set(providerId, latency);
303
- } else {
304
- this.ewmaLatency.set(
305
- providerId,
306
- this.config.latencyAlpha * latency + (1 - this.config.latencyAlpha) * current
307
- );
308
- }
309
- }
310
-
311
- private updateEwmaErrorRate(providerId: string, isError: boolean, weight = 1.0): void {
312
- const current = this.ewmaErrorRate.get(providerId) || 0;
313
- const errorValue = isError ? 1.0 * weight : 0;
314
- if (current === 0 && !isError) {
315
- this.ewmaErrorRate.set(providerId, 0);
316
- } else {
317
- this.ewmaErrorRate.set(
318
- providerId,
319
- this.config.errorAlpha * errorValue + (1 - this.config.errorAlpha) * current
320
- );
321
- }
322
- }
323
-
324
- private updateAnomalyScore(providerId: string, latency: number): void {
325
- const records = this.getRecords(providerId).filter(r => r.latency !== undefined);
326
- if (records.length < 3) return;
327
-
328
- // Calculate standard deviation of recent latencies
329
- const recentLatencies = records
330
- .slice(-this.config.anomalyWindow)
331
- .map(r => r.latency!)
332
- .filter(l => l !== undefined);
333
-
334
- if (recentLatencies.length < 3) return;
335
-
336
- const mean = recentLatencies.reduce((a, b) => a + b, 0) / recentLatencies.length;
337
- const variance =
338
- recentLatencies.reduce((sum, l) => sum + Math.pow(l - mean, 2), 0) /
339
- recentLatencies.length;
340
- const stdDev = Math.sqrt(variance);
341
-
342
- // Z-score of current latency
343
- const zScore = stdDev > 0 ? Math.abs(latency - mean) / stdDev : 0;
344
-
345
- // Update anomaly score with decay
346
- const currentScore = this.anomalyScores.get(providerId) || 0;
347
- const newScore = currentScore * 0.9 + zScore * 0.1;
348
- this.anomalyScores.set(providerId, newScore);
349
- }
350
-
351
- private calculateLatencyScore(providerId: string): number {
352
- const latency = this.ewmaLatency.get(providerId);
353
- if (latency === undefined) return 1.0;
354
-
355
- // Score based on latency brackets (ms)
356
- // Tier-appropriate expectations
357
- if (latency < 200) return 1.0;
358
- if (latency < 500) return 0.95;
359
- if (latency < 800) return 0.85;
360
- if (latency < 1500) return 0.7;
361
- if (latency < 3000) return 0.5;
362
- return Math.max(0.1, 1 - latency / 10000);
363
- }
364
-
365
- private calculateErrorScore(providerId: string): number {
366
- const errorRate = this.ewmaErrorRate.get(providerId) || 0;
367
- // Score: 1 - errorRate, but with floor
368
- return Math.max(0.05, 1 - errorRate);
369
- }
370
-
371
- private calculateTrendScore(providerId: string): number {
372
- const records = this.getRecords(providerId).filter(r => r.latency !== undefined);
373
- if (records.length < 5) return 1.0;
374
-
375
- // Compare recent 5 vs previous 5
376
- const recent = records.slice(-5);
377
- const previous = records.slice(-10, -5);
378
-
379
- if (previous.length === 0) return 1.0;
380
-
381
- const recentAvg = recent.reduce((sum, r) => sum + (r.latency || 0), 0) / recent.length;
382
- const previousAvg = previous.reduce((sum, r) => sum + (r.latency || 0), 0) / previous.length;
383
-
384
- // If improving (lower latency), score > 1
385
- // If degrading, score < 1
386
- const ratio = previousAvg / recentAvg;
387
- return Math.min(1.2, Math.max(0.6, ratio));
388
- }
389
- }
390
-
391
- // ============================================================
392
- // Factory
393
- // ============================================================
394
-
395
- export function createProviderHealthMonitor(config?: HealthConfig): ProviderHealthMonitor {
396
- return new ProviderHealthMonitor(config);
397
- }