adaptive-memory-multi-model-router 2.2.5 → 2.2.7

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 (52) hide show
  1. package/README.md +149 -116
  2. package/README.md.bak +836 -0
  3. package/assets/benchmark-results.png +0 -0
  4. package/assets/complexity-scoring-v2.png +0 -0
  5. package/assets/complexity-scoring.png +0 -0
  6. package/assets/cost-comparison-chart.png +0 -0
  7. package/assets/cost-comparison-v2.png +0 -0
  8. package/assets/feature-comparison-v2.png +0 -0
  9. package/assets/feature-comparison-v3.png +0 -0
  10. package/assets/provider-health-chart.png +0 -0
  11. package/assets/provider-health-v2.png +0 -0
  12. package/assets/routing-flow-v2.png +0 -0
  13. package/assets/routing-flow-v3.png +0 -0
  14. package/assets/routing-flow.png +0 -0
  15. package/assets/tier-distribution.png +0 -0
  16. package/benchmark-results.json +620 -46
  17. package/dist/cache/cacheKeyGenerator.d.ts +67 -0
  18. package/dist/cache/cacheKeyGenerator.d.ts.map +1 -0
  19. package/dist/cache/cacheKeyGenerator.js +211 -0
  20. package/dist/cache/cacheKeyGenerator.js.map +1 -0
  21. package/dist/cli.js +0 -0
  22. package/dist/cost/preCallCostEstimator.d.ts +114 -0
  23. package/dist/cost/preCallCostEstimator.d.ts.map +1 -0
  24. package/dist/cost/preCallCostEstimator.js +256 -0
  25. package/dist/cost/preCallCostEstimator.js.map +1 -0
  26. package/dist/inference/speculativeDecoding.d.ts +133 -0
  27. package/dist/inference/speculativeDecoding.d.ts.map +1 -0
  28. package/dist/inference/speculativeDecoding.js +276 -0
  29. package/dist/inference/speculativeDecoding.js.map +1 -0
  30. package/dist/providers/providerHealth.d.ts +117 -0
  31. package/dist/providers/providerHealth.d.ts.map +1 -0
  32. package/dist/providers/providerHealth.js +309 -0
  33. package/dist/providers/providerHealth.js.map +1 -0
  34. package/dist/routing/difficultyClassifier.d.ts +79 -0
  35. package/dist/routing/difficultyClassifier.d.ts.map +1 -0
  36. package/dist/routing/difficultyClassifier.js +329 -0
  37. package/dist/routing/difficultyClassifier.js.map +1 -0
  38. package/dist/sdk.d.ts +125 -0
  39. package/dist/sdk.d.ts.map +1 -0
  40. package/dist/sdk.js.map +1 -0
  41. package/package.json +2 -322
  42. package/scripts/run-mmlu-benchmark.js +176 -0
  43. package/scripts/run-provider-benchmark.js +244 -0
  44. package/src/cache/cacheKeyGenerator.ts +242 -0
  45. package/src/cost/preCallCostEstimator.ts +345 -0
  46. package/src/inference/speculativeDecoding.ts +373 -0
  47. package/src/providers/providerHealth.ts +397 -0
  48. package/src/routing/difficultyClassifier.ts +420 -0
  49. package/test/provider-test.js +69 -90
  50. package/test.js +43 -69
  51. package/test.js.bak +376 -0
  52. package/src/skills/__tests__/skill_manager.test.ts +0 -328
@@ -0,0 +1,420 @@
1
+ /**
2
+ * A3M Router - Difficulty Classifier
3
+ *
4
+ * Classifies queries as simple/medium/complex based on features.
5
+ * Used to route queries to appropriate model tiers.
6
+ *
7
+ * Usage:
8
+ * const classifier = new DifficultyClassifier();
9
+ * const result = classifier.classify("What is Python?");
10
+ * console.log(result); // { level: 'simple', confidence: 0.85, signals: [...] }
11
+ *
12
+ * const complex = classifier.classify("Implement a red-black tree in Rust with full tests");
13
+ * console.log(complex); // { level: 'complex', confidence: 0.92, signals: [...] }
14
+ */
15
+
16
+ import { ProviderTier } from '../providers/providerConfig';
17
+
18
+ // ============================================================
19
+ // Types
20
+ // ============================================================
21
+
22
+ export type DifficultyLevel = 'simple' | 'medium' | 'complex';
23
+
24
+ export interface ClassificationResult {
25
+ /** Difficulty level */
26
+ level: DifficultyLevel;
27
+ /** Confidence score 0-1 */
28
+ confidence: number;
29
+ /** Signals that contributed to the classification */
30
+ signals: string[];
31
+ /** Feature scores for each dimension */
32
+ features: {
33
+ length: number; // 0-1, longer = potentially more complex
34
+ keywords: number; // 0-1, presence of complex keywords
35
+ reasoning: number; // 0-1, reasoning requirements
36
+ language: number; // 0-1, language complexity
37
+ code: number; // 0-1, code-related content
38
+ };
39
+ /** Recommended model tier */
40
+ recommendedTier: ProviderTier;
41
+ /** Alternative tiers (in order of preference) */
42
+ alternativeTiers: ProviderTier[];
43
+ }
44
+
45
+ export interface ClassifierConfig {
46
+ /** Thresholds for classification */
47
+ thresholds?: {
48
+ simpleMax?: number;
49
+ mediumMax?: number;
50
+ };
51
+ /** Enable keyword-based classification */
52
+ useKeywords?: boolean;
53
+ /** Enable linguistic complexity analysis */
54
+ useLinguistic?: boolean;
55
+ }
56
+
57
+ const DEFAULT_CONFIG: Required<ClassifierConfig> = {
58
+ thresholds: {
59
+ simpleMax: 0.35,
60
+ mediumMax: 0.65,
61
+ },
62
+ useKeywords: true,
63
+ useLinguistic: true,
64
+ };
65
+
66
+ // ============================================================
67
+ // Signal patterns
68
+ // ============================================================
69
+
70
+ const COMPLEX_KEYWORDS = [
71
+ // Math/Science
72
+ 'calculate', 'compute', 'algorithm', 'mathematical', 'equation',
73
+ 'statistical', 'probability', 'derivative', 'integral', 'matrix',
74
+ 'vector', 'optimize', 'optimization', 'minimize', 'maximize',
75
+ // Code
76
+ 'implement', 'refactor', 'architect', 'design pattern', 'factory',
77
+ 'singleton', 'decorator', 'middleware', 'pipeline', 'async',
78
+ 'concurrency', 'parallel', 'thread', 'process', 'memory leak',
79
+ 'database', 'sql', 'nosql', 'index', 'shard', 'replica',
80
+ 'api', 'rest', 'graphql', 'microservice', 'container', 'kubernetes',
81
+ 'deploy', 'ci/cd', 'pipeline', 'test', 'mock', 'stub',
82
+ // Reasoning
83
+ 'analyze', 'compare', 'contrast', 'evaluate', 'synthesis',
84
+ 'implications', 'hypothesis', 'theory', 'principle', 'why does',
85
+ 'explain why', 'reasoning', 'logical', 'deduce', 'infer',
86
+ // Academic/Complex
87
+ 'research', 'comprehensive', 'detailed', 'thorough', 'extensive',
88
+ 'in-depth', 'multi-step', 'hierarchical', 'nested', 'recursive',
89
+ ];
90
+
91
+ const SIMPLE_KEYWORDS = [
92
+ 'what is', 'who is', 'when did', 'where is', 'simple',
93
+ 'basic', 'intro', 'tutorial', 'hello', 'hi ',
94
+ 'thanks', 'please', 'help me', 'quick', 'brief',
95
+ 'yes', 'no', 'maybe', 'ok', 'sure', 'okay',
96
+ 'list of', 'names of', 'definition', 'meaning of',
97
+ ];
98
+
99
+ const CODE_INDICATORS = [
100
+ 'code', 'function', 'class', 'method', 'variable',
101
+ 'const', 'let', 'var', 'return', 'import', 'export',
102
+ 'def ', 'fn ', 'pub ', 'struct', 'enum', 'trait',
103
+ 'python', 'javascript', 'typescript', 'java', 'rust', 'go',
104
+ 'html', 'css', 'sql', 'bash', 'shell', 'script',
105
+ 'bug', 'error', 'exception', 'debug', 'stack trace',
106
+ 'api', 'endpoint', 'route', 'handler', 'controller',
107
+ ];
108
+
109
+ const REASONING_INDICATORS = [
110
+ 'why', 'how', 'because', 'therefore', 'thus',
111
+ 'reasoning', 'logic', 'deduce', 'infer', 'conclude',
112
+ 'implies', 'suggest', 'indicate', 'evidence',
113
+ 'analyze', 'investigate', 'examine', 'evaluate',
114
+ 'compare', 'differences', 'similar', 'versus',
115
+ 'if then', 'hypothesis', 'assumption', 'premise',
116
+ ];
117
+
118
+ // ============================================================
119
+ // DifficultyClassifier
120
+ // ============================================================
121
+
122
+ export class DifficultyClassifier {
123
+ private config: Required<ClassifierConfig>;
124
+ private trainingData: Array<{ text: string; level: DifficultyLevel }> = [];
125
+
126
+ constructor(config: ClassifierConfig = {}) {
127
+ this.config = { ...DEFAULT_CONFIG, ...config };
128
+ }
129
+
130
+ /**
131
+ * Classify a query's difficulty.
132
+ */
133
+ classify(query: string): ClassificationResult {
134
+ const query_lower = query.toLowerCase();
135
+ const words = query.split(/\s+/);
136
+
137
+ // Calculate feature scores
138
+ const features = {
139
+ length: this.calculateLengthScore(query),
140
+ keywords: this.calculateKeywordScore(query_lower),
141
+ reasoning: this.calculateReasoningScore(query_lower, words),
142
+ language: this.calculateLanguageScore(query_lower, words),
143
+ code: this.calculateCodeScore(query_lower),
144
+ };
145
+
146
+ // Weighted combination
147
+ const rawScore =
148
+ features.length * 0.15 +
149
+ features.keywords * 0.30 +
150
+ features.reasoning * 0.20 +
151
+ features.language * 0.15 +
152
+ features.code * 0.20;
153
+
154
+ // Clamp and determine level
155
+ const score = Math.max(0, Math.min(1, rawScore));
156
+ const level = this.determineLevel(score);
157
+ const confidence = this.calculateConfidence(features, score);
158
+
159
+ // Generate signals list
160
+ const signals = this.generateSignals(query_lower, features);
161
+
162
+ // Determine recommended tier
163
+ const { recommendedTier, alternativeTiers } = this.determineTier(level, features);
164
+
165
+ return {
166
+ level,
167
+ confidence: Math.round(confidence * 1000) / 1000,
168
+ signals,
169
+ features: {
170
+ length: Math.round(features.length * 1000) / 1000,
171
+ keywords: Math.round(features.keywords * 1000) / 1000,
172
+ reasoning: Math.round(features.reasoning * 1000) / 1000,
173
+ language: Math.round(features.language * 1000) / 1000,
174
+ code: Math.round(features.code * 1000) / 1000,
175
+ },
176
+ recommendedTier,
177
+ alternativeTiers,
178
+ };
179
+ }
180
+
181
+ /**
182
+ * Classify multiple queries.
183
+ */
184
+ classifyBatch(queries: string[]): ClassificationResult[] {
185
+ return queries.map(q => this.classify(q));
186
+ }
187
+
188
+ /**
189
+ * Add training example for future improvements.
190
+ */
191
+ addTrainingExample(query: string, level: DifficultyLevel): void {
192
+ this.trainingData.push({ text: query, level });
193
+ }
194
+
195
+ /**
196
+ * Get distribution of difficulties in a batch.
197
+ */
198
+ getDistribution(queries: string[]): Record<DifficultyLevel, number> {
199
+ const results = this.classifyBatch(queries);
200
+ const total = results.length;
201
+
202
+ const distribution = { simple: 0, medium: 0, complex: 0 };
203
+ for (const r of results) {
204
+ distribution[r.level]++;
205
+ }
206
+
207
+ return {
208
+ simple: Math.round((distribution.simple / total) * 1000) / 1000,
209
+ medium: Math.round((distribution.medium / total) * 1000) / 1000,
210
+ complex: Math.round((distribution.complex / total) * 1000) / 1000,
211
+ };
212
+ }
213
+
214
+ // ---- Feature calculations ----
215
+
216
+ private calculateLengthScore(query: string): number {
217
+ const words = query.split(/\s+/).length;
218
+ const chars = query.length;
219
+
220
+ // Normalize: 1-10 words = simple, 10-50 = medium, 50+ = complex
221
+ const wordScore = Math.min(words / 50, 1.0);
222
+ const charScore = Math.min(chars / 500, 1.0);
223
+
224
+ return (wordScore * 0.6 + charScore * 0.4);
225
+ }
226
+
227
+ private calculateKeywordScore(query: string): number {
228
+ if (!this.config.useKeywords) return 0.5;
229
+
230
+ let score = 0.5; // Base score
231
+
232
+ // Check for complex keywords
233
+ for (const kw of COMPLEX_KEYWORDS) {
234
+ if (query.includes(kw)) score += 0.1;
235
+ }
236
+
237
+ // Check for simple keywords
238
+ for (const kw of SIMPLE_KEYWORDS) {
239
+ if (query.includes(kw)) score -= 0.15;
240
+ }
241
+
242
+ return Math.max(0, Math.min(1, score));
243
+ }
244
+
245
+ private calculateReasoningScore(query: string, words: string[]): number {
246
+ let score = 0.2; // Base score
247
+
248
+ // Check reasoning indicators
249
+ for (const indicator of REASONING_INDICATORS) {
250
+ if (query.includes(indicator)) score += 0.12;
251
+ }
252
+
253
+ // "How" questions often require explanation
254
+ if (query.startsWith('how')) score += 0.1;
255
+
256
+ // Multi-step indicators (first, then, finally, etc.)
257
+ const multiStep = ['first', 'then', 'next', 'finally', 'after', 'before'];
258
+ const hasMultiStep = multiStep.filter(m => query.includes(m)).length;
259
+ score += hasMultiStep * 0.05;
260
+
261
+ // Question length (>3 words after question word = more complex)
262
+ if (words.length > 10) score += 0.1;
263
+
264
+ return Math.max(0, Math.min(1, score));
265
+ }
266
+
267
+ private calculateLanguageScore(query: string, words: string[]): number {
268
+ if (!this.config.useLinguistic) return 0.5;
269
+
270
+ // Simple heuristics for language complexity
271
+
272
+ // Average word length (longer words = more complex)
273
+ const avgWordLen = words.reduce((sum, w) => sum + w.length, 0) / words.length;
274
+ const lenScore = Math.min((avgWordLen - 3) / 4, 1.0); // 3 chars = simple, 7+ = complex
275
+
276
+ // Presence of technical/academic vocabulary
277
+ const technicalWords = [
278
+ 'analysis', 'methodology', 'framework', 'paradigm', 'synthesis',
279
+ 'theoretical', 'empirical', 'conceptual', 'phenomenon', 'correlation',
280
+ ];
281
+ let techCount = 0;
282
+ for (const tw of technicalWords) {
283
+ if (query.includes(tw)) techCount++;
284
+ }
285
+ const techScore = Math.min(techCount * 0.15, 0.4);
286
+
287
+ // Sentence complexity (commas, semicolons)
288
+ const punctCount = (query.match(/[,;:]/g) || []).length;
289
+ const punctScore = Math.min(punctCount * 0.1, 0.3);
290
+
291
+ return Math.max(0, Math.min(1, 0.3 + lenScore * 0.3 + techScore + punctScore));
292
+ }
293
+
294
+ private calculateCodeScore(query: string): number {
295
+ let score = 0.1; // Base score (low probability of code)
296
+
297
+ // Check code indicators
298
+ for (const indicator of CODE_INDICATORS) {
299
+ if (query.includes(indicator)) score += 0.1;
300
+ }
301
+
302
+ // Code block indicators
303
+ if (query.includes('```') || query.includes('`')) score += 0.15;
304
+
305
+ // Programming language mentions
306
+ const langs = ['python', 'javascript', 'java', 'rust', 'go', 'c++', 'typescript', 'ruby'];
307
+ for (const lang of langs) {
308
+ if (query.includes(lang)) score += 0.1;
309
+ }
310
+
311
+ // Code-like patterns (brackets, semicolons in unusual contexts)
312
+ if (/[{}\[\];]/.test(query)) score += 0.1;
313
+
314
+ return Math.max(0, Math.min(1, score));
315
+ }
316
+
317
+ // ---- Classification ----
318
+
319
+ private determineLevel(score: number): DifficultyLevel {
320
+ if (score <= this.config.thresholds.simpleMax) {
321
+ return 'simple';
322
+ }
323
+ if (score <= this.config.thresholds.mediumMax) {
324
+ return 'medium';
325
+ }
326
+ return 'complex';
327
+ }
328
+
329
+ private calculateConfidence(
330
+ features: ClassificationResult['features'],
331
+ score: number
332
+ ): number {
333
+ // Higher agreement between features = higher confidence
334
+ const featureValues = Object.values(features);
335
+ const mean = featureValues.reduce((a, b) => a + b, 0) / featureValues.length;
336
+ const variance = featureValues.reduce((sum, f) => sum + Math.pow(f - mean, 2), 0) / featureValues.length;
337
+ const stdDev = Math.sqrt(variance);
338
+
339
+ // Low variance = high confidence
340
+ const agreement = 1 - Math.min(stdDev * 2, 1);
341
+
342
+ // Distance from 0.5 also matters (extreme scores = more confident)
343
+ const extremity = Math.abs(score - 0.5) * 2;
344
+
345
+ return (agreement * 0.6 + extremity * 0.4);
346
+ }
347
+
348
+ // ---- Signal generation ----
349
+
350
+ private generateSignals(query: string, features: ClassificationResult['features']): string[] {
351
+ const signals: string[] = [];
352
+
353
+ if (features.length > 0.6) signals.push('long query');
354
+ if (features.length < 0.2) signals.push('short query');
355
+
356
+ if (features.keywords > 0.6) signals.push('complex vocabulary');
357
+ if (features.keywords < 0.3) signals.push('simple vocabulary');
358
+
359
+ if (features.code > 0.5) signals.push('code-related');
360
+ if (features.reasoning > 0.5) signals.push('reasoning required');
361
+ if (features.language > 0.6) signals.push('complex language');
362
+
363
+ // Specific keyword matches
364
+ for (const kw of COMPLEX_KEYWORDS) {
365
+ if (query.includes(kw)) signals.push(`keyword: ${kw.slice(0, 10)}`);
366
+ }
367
+
368
+ return signals;
369
+ }
370
+
371
+ // ---- Tier determination ----
372
+
373
+ private determineTier(
374
+ level: DifficultyLevel,
375
+ features: ClassificationResult['features']
376
+ ): { recommendedTier: ProviderTier; alternativeTiers: ProviderTier[] } {
377
+ if (level === 'simple') {
378
+ return {
379
+ recommendedTier: 'cheap',
380
+ alternativeTiers: ['free', 'mid'],
381
+ };
382
+ }
383
+
384
+ if (level === 'medium') {
385
+ // Medium but with high code = needs better model
386
+ if (features.code > 0.5) {
387
+ return {
388
+ recommendedTier: 'mid',
389
+ alternativeTiers: ['premium', 'cheap'],
390
+ };
391
+ }
392
+ return {
393
+ recommendedTier: 'mid',
394
+ alternativeTiers: ['cheap', 'premium'],
395
+ };
396
+ }
397
+
398
+ // Complex
399
+ if (features.code > 0.6 || features.reasoning > 0.6) {
400
+ return {
401
+ recommendedTier: 'premium',
402
+ alternativeTiers: ['mid', 'enterprise'],
403
+ };
404
+ }
405
+ return {
406
+ recommendedTier: 'premium',
407
+ alternativeTiers: ['mid', 'enterprise'],
408
+ };
409
+ }
410
+ }
411
+
412
+ // ============================================================
413
+ // Factory
414
+ // ============================================================
415
+
416
+ export function createDifficultyClassifier(
417
+ config?: ClassifierConfig
418
+ ): DifficultyClassifier {
419
+ return new DifficultyClassifier(config);
420
+ }