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,67 @@
1
+ /**
2
+ * A3M Router - Cross-Provider Cache Key Generator
3
+ *
4
+ * Normalizes prompts so same semantic content maps to same cache key
5
+ * regardless of provider-specific formatting, system prompts, etc.
6
+ *
7
+ * Usage:
8
+ * const cacheKey = generateCacheKey("What is Python?", { provider: "openai" });
9
+ * const key2 = generateCacheKey("What is Python?", { provider: "anthropic" });
10
+ * // key === key2 (same semantic content = same key)
11
+ */
12
+ export interface CacheKeyOptions {
13
+ /** Target provider (affects normalization rules) */
14
+ provider?: string;
15
+ /** Target model (for model-specific normalization) */
16
+ model?: string;
17
+ /** Whether to include system prompt in normalization */
18
+ includeSystemPrompt?: boolean;
19
+ /** Custom normalization rules */
20
+ customRules?: NormalizationRule[];
21
+ }
22
+ export interface NormalizationRule {
23
+ pattern: RegExp;
24
+ replacement: string;
25
+ }
26
+ export interface CacheKeyResult {
27
+ /** The normalized cache key string */
28
+ key: string;
29
+ /** Hash of the normalized content */
30
+ hash: string;
31
+ /** Metadata about what was normalized */
32
+ metadata: {
33
+ originalLength: number;
34
+ normalizedLength: number;
35
+ rulesApplied: number;
36
+ provider?: string;
37
+ };
38
+ }
39
+ /**
40
+ * Normalize text for cross-provider cache key generation.
41
+ * Removes provider-specific formatting while preserving semantic content.
42
+ */
43
+ export declare function normalizeForCacheKey(text: string, options?: CacheKeyOptions): string;
44
+ /**
45
+ * Generate a deterministic cache key from a query.
46
+ * Same semantic content = same key across providers.
47
+ */
48
+ export declare function generateCacheKey(query: string, options?: CacheKeyOptions): CacheKeyResult;
49
+ /**
50
+ * Add cross-provider cache key methods to existing SemanticCache.
51
+ * Call this to enhance the cache with provider-normalized lookups.
52
+ */
53
+ export declare function createCacheKeyGenerator(defaultOptions?: CacheKeyOptions): {
54
+ generateKey: (query: string, options?: CacheKeyOptions) => CacheKeyResult;
55
+ createNormalizedMatcher: (cache: Map<string, any>) => (query: string, options?: CacheKeyOptions) => string | null;
56
+ };
57
+ /**
58
+ * Quick cache key generation (simplified API).
59
+ * Use this for simple cross-provider cache lookups.
60
+ *
61
+ * @example
62
+ * const key1 = toCacheKey("What is Python?", "openai");
63
+ * const key2 = toCacheKey("What is Python?", "anthropic");
64
+ * console.log(key1 === key2); // true
65
+ */
66
+ export declare function toCacheKey(query: string, provider?: string): string;
67
+ //# sourceMappingURL=cacheKeyGenerator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cacheKeyGenerator.d.ts","sourceRoot":"","sources":["../../src/cache/cacheKeyGenerator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH,MAAM,WAAW,eAAe;IAC9B,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,iCAAiC;IACjC,WAAW,CAAC,EAAE,iBAAiB,EAAE,CAAC;CACnC;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,sCAAsC;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,QAAQ,EAAE;QACR,cAAc,EAAE,MAAM,CAAC;QACvB,gBAAgB,EAAE,MAAM,CAAC;QACzB,YAAY,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AA2BD;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,eAAoB,GAC5B,MAAM,CA8CR;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,eAAoB,GAC5B,cAAc,CAwChB;AAMD;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,cAAc,CAAC,EAAE,eAAe,GAC/B;IACD,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,KAAK,cAAc,CAAC;IAC1E,uBAAuB,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,KAAK,MAAM,GAAG,IAAI,CAAC;CACnH,CAkCA;AAMD;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAEnE"}
@@ -0,0 +1,211 @@
1
+ "use strict";
2
+ /**
3
+ * A3M Router - Cross-Provider Cache Key Generator
4
+ *
5
+ * Normalizes prompts so same semantic content maps to same cache key
6
+ * regardless of provider-specific formatting, system prompts, etc.
7
+ *
8
+ * Usage:
9
+ * const cacheKey = generateCacheKey("What is Python?", { provider: "openai" });
10
+ * const key2 = generateCacheKey("What is Python?", { provider: "anthropic" });
11
+ * // key === key2 (same semantic content = same key)
12
+ */
13
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
20
+ }) : (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ o[k2] = m[k];
23
+ }));
24
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
26
+ }) : function(o, v) {
27
+ o["default"] = v;
28
+ });
29
+ var __importStar = (this && this.__importStar) || (function () {
30
+ var ownKeys = function(o) {
31
+ ownKeys = Object.getOwnPropertyNames || function (o) {
32
+ var ar = [];
33
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
34
+ return ar;
35
+ };
36
+ return ownKeys(o);
37
+ };
38
+ return function (mod) {
39
+ if (mod && mod.__esModule) return mod;
40
+ var result = {};
41
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
42
+ __setModuleDefault(result, mod);
43
+ return result;
44
+ };
45
+ })();
46
+ Object.defineProperty(exports, "__esModule", { value: true });
47
+ exports.normalizeForCacheKey = normalizeForCacheKey;
48
+ exports.generateCacheKey = generateCacheKey;
49
+ exports.createCacheKeyGenerator = createCacheKeyGenerator;
50
+ exports.toCacheKey = toCacheKey;
51
+ const crypto = __importStar(require("crypto"));
52
+ // ============================================================
53
+ // Provider-specific system prompt patterns
54
+ // ============================================================
55
+ const PROVIDER_SYSTEM_PATTERNS = {
56
+ anthropic: [
57
+ /<anthropic_thinking>[\s\S]*?<\/anthropic_thinking>/gi,
58
+ /<thinking>[\s\S]*?<\/thinking>/gi,
59
+ /Human:/gi,
60
+ /Assistant:/gi,
61
+ ],
62
+ openai: [
63
+ /<|im_start|>/gi,
64
+ /<|im_end|>/gi,
65
+ ],
66
+ google: [
67
+ /<content>[\s\S]*?<\/content>/gi,
68
+ /[\Parts|thought]/gi,
69
+ ],
70
+ };
71
+ // ============================================================
72
+ // Core normalizer
73
+ // ============================================================
74
+ /**
75
+ * Normalize text for cross-provider cache key generation.
76
+ * Removes provider-specific formatting while preserving semantic content.
77
+ */
78
+ function normalizeForCacheKey(text, options = {}) {
79
+ let normalized = text;
80
+ // Step 1: Unicode normalization (NFC)
81
+ normalized = normalized.normalize('NFC');
82
+ // Step 2: Collapse whitespace
83
+ normalized = normalized.replace(/\s+/g, ' ');
84
+ // Step 3: Remove control characters
85
+ normalized = normalized.replace(/[\x00-\x1F\x7F]/g, '');
86
+ // Step 4: Strip provider-specific formatting
87
+ if (options.provider) {
88
+ const patterns = PROVIDER_SYSTEM_PATTERNS[options.provider] || [];
89
+ for (const pattern of patterns) {
90
+ normalized = normalized.replace(pattern, '');
91
+ }
92
+ }
93
+ // Step 5: General system/assistant role removal
94
+ normalized = normalized
95
+ .replace(/\b(system|user|assistant|human|bot)\s*:/gi, '')
96
+ .replace(/^(system|user|assistant|human|bot)\s*/gim, '');
97
+ // Step 6: Remove markdown formatting (often provider-specific)
98
+ normalized = normalized
99
+ .replace(/```[\s\S]*?```/g, '[CODE_BLOCK]') // Preserve code block indicator
100
+ .replace(/`([^`]+)`/g, '$1') // Inline code content
101
+ .replace(/\*\*([^*]+)\*\*/g, '$1') // Bold
102
+ .replace(/_([^_]+)_/g, '$1') // Italic
103
+ .replace(/#+\s*/g, '') // Headers
104
+ .replace(/^\s*[-*+]\s+/gm, '') // List bullets
105
+ .replace(/^\s*\d+\.\s+/gm, ''); // Numbered lists
106
+ // Step 7: Apply custom rules
107
+ if (options.customRules) {
108
+ for (const rule of options.customRules) {
109
+ normalized = normalized.replace(rule.pattern, rule.replacement);
110
+ }
111
+ }
112
+ // Step 8: Collapse whitespace again after removals
113
+ normalized = normalized.replace(/\s+/g, ' ').trim();
114
+ return normalized;
115
+ }
116
+ /**
117
+ * Generate a deterministic cache key from a query.
118
+ * Same semantic content = same key across providers.
119
+ */
120
+ function generateCacheKey(query, options = {}) {
121
+ const originalLength = query.length;
122
+ // Normalize the query
123
+ let normalized = normalizeForCacheKey(query, {
124
+ ...options,
125
+ includeSystemPrompt: false, // Always exclude for user query matching
126
+ });
127
+ // Count rules that were applied (approximate)
128
+ let rulesApplied = 3; // Base normalizations
129
+ if (options.provider)
130
+ rulesApplied += 2;
131
+ if (options.customRules)
132
+ rulesApplied += options.customRules.length;
133
+ // Generate hash
134
+ const hash = crypto
135
+ .createHash('sha256')
136
+ .update(normalized)
137
+ .digest('hex')
138
+ .substring(0, 16); // First 16 chars = 64-bit key
139
+ // Final key format: v1:{hash}:{provider?[:model]?}
140
+ let key = `v1:${hash}`;
141
+ if (options.provider) {
142
+ key += `:${options.provider}`;
143
+ if (options.model) {
144
+ key += `:${options.model}`;
145
+ }
146
+ }
147
+ return {
148
+ key,
149
+ hash,
150
+ metadata: {
151
+ originalLength,
152
+ normalizedLength: normalized.length,
153
+ rulesApplied,
154
+ provider: options.provider,
155
+ },
156
+ };
157
+ }
158
+ // ============================================================
159
+ // SemanticCache enhancement
160
+ // ============================================================
161
+ /**
162
+ * Add cross-provider cache key methods to existing SemanticCache.
163
+ * Call this to enhance the cache with provider-normalized lookups.
164
+ */
165
+ function createCacheKeyGenerator(defaultOptions) {
166
+ return {
167
+ /**
168
+ * Generate a cache key for a query.
169
+ */
170
+ generateKey: (query, options) => {
171
+ return generateCacheKey(query, { ...defaultOptions, ...options });
172
+ },
173
+ /**
174
+ * Create a matcher function that finds existing cache entries
175
+ * by comparing normalized keys.
176
+ */
177
+ createNormalizedMatcher: (cache) => {
178
+ return (query, options) => {
179
+ const { key } = generateCacheKey(query, { ...defaultOptions, ...options });
180
+ // Check exact match
181
+ if (cache.has(key)) {
182
+ return key;
183
+ }
184
+ // Check hash-only match (v1:{hash} prefix)
185
+ const hashPrefix = key.split(':').slice(0, 2).join(':');
186
+ for (const cachedKey of cache.keys()) {
187
+ if (cachedKey.startsWith(hashPrefix + ':')) {
188
+ return cachedKey;
189
+ }
190
+ }
191
+ return null;
192
+ };
193
+ },
194
+ };
195
+ }
196
+ // ============================================================
197
+ // Convenience exports
198
+ // ============================================================
199
+ /**
200
+ * Quick cache key generation (simplified API).
201
+ * Use this for simple cross-provider cache lookups.
202
+ *
203
+ * @example
204
+ * const key1 = toCacheKey("What is Python?", "openai");
205
+ * const key2 = toCacheKey("What is Python?", "anthropic");
206
+ * console.log(key1 === key2); // true
207
+ */
208
+ function toCacheKey(query, provider) {
209
+ return generateCacheKey(query, { provider }).key;
210
+ }
211
+ //# sourceMappingURL=cacheKeyGenerator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cacheKeyGenerator.js","sourceRoot":"","sources":["../../src/cache/cacheKeyGenerator.ts"],"names":[],"mappings":"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"}
package/dist/cli.js CHANGED
File without changes
@@ -0,0 +1,114 @@
1
+ /**
2
+ * A3M Router - Pre-Call Cost Estimator
3
+ *
4
+ * Estimates cost BEFORE making an API call based on input features.
5
+ * Uses historical data patterns and token count estimation via character ratio.
6
+ *
7
+ * No external API calls - all estimation is local.
8
+ *
9
+ * Usage:
10
+ * const estimator = new PreCallCostEstimator();
11
+ * const estimate = estimator.estimate({
12
+ * query: "What is Python?",
13
+ * modelTier: 'mid',
14
+ * provider: 'groq'
15
+ * });
16
+ * console.log(estimate); // { estimatedTokens: 24, estimatedCost: 0.0014, estimatedLatency: 200 }
17
+ */
18
+ import { ProviderTier } from '../providers/providerConfig';
19
+ export interface CostEstimateInput {
20
+ /** Query text to estimate tokens for */
21
+ query: string;
22
+ /** Model tier (free|cheap|mid|premium|enterprise) */
23
+ modelTier?: ProviderTier;
24
+ /** Provider ID for provider-specific cost lookup */
25
+ provider?: string;
26
+ /** Optional: explicit token count (if already known) */
27
+ explicitTokens?: number;
28
+ /** System prompt length (if using a system prompt) */
29
+ systemPromptLength?: number;
30
+ }
31
+ export interface CostEstimate {
32
+ /** Estimated total tokens (input + output) */
33
+ estimatedTokens: number;
34
+ /** Estimated input tokens */
35
+ estimatedInputTokens: number;
36
+ /** Estimated output tokens */
37
+ estimatedOutputTokens: number;
38
+ /** Estimated cost in USD */
39
+ estimatedCost: number;
40
+ /** Estimated latency in milliseconds */
41
+ estimatedLatency: number;
42
+ /** Confidence score 0-1 */
43
+ confidence: number;
44
+ /** Breakdown of estimation */
45
+ breakdown: {
46
+ inputCostPerM: number;
47
+ outputCostPerM: number;
48
+ charToTokenRatio: number;
49
+ };
50
+ }
51
+ export interface ProviderCostConfig {
52
+ input: number;
53
+ output: number;
54
+ }
55
+ export declare class PreCallCostEstimator {
56
+ private historicalData;
57
+ private slope;
58
+ private intercept;
59
+ private latencyHistory;
60
+ private ewmaAlpha;
61
+ constructor(historicalData?: Array<{
62
+ chars: number;
63
+ tokens: number;
64
+ }>);
65
+ /**
66
+ * Main estimation method - estimates tokens, cost, and latency.
67
+ */
68
+ estimate(input: CostEstimateInput): CostEstimate;
69
+ /**
70
+ * Estimate input tokens using character-to-token ratio.
71
+ * Uses linear regression if historical data is available.
72
+ */
73
+ estimateTokens(text: string, systemPromptLength?: number): number;
74
+ /**
75
+ * Estimate output tokens based on query complexity.
76
+ * More complex queries (code, analysis) tend to need more output.
77
+ */
78
+ estimateOutputTokens(query: string, inputTokens: number): number;
79
+ /**
80
+ * Calculate cost in USD.
81
+ */
82
+ calculateCost(totalTokens: number, config: ProviderCostConfig): number;
83
+ /**
84
+ * Estimate latency in milliseconds.
85
+ */
86
+ estimateLatency(tier: ProviderTier, inputTokens: number, totalTokens: number): number;
87
+ /**
88
+ * Record actual tokens for future regression improvements.
89
+ */
90
+ recordActualTokens(queryLength: number, actualTokens: number): void;
91
+ /**
92
+ * Record actual latency for EWMA updates.
93
+ */
94
+ recordActualLatency(latencyMs: number): void;
95
+ /**
96
+ * Fit linear regression to historical data.
97
+ * Uses ordinary least squares.
98
+ */
99
+ fitLinearRegression(data: Array<{
100
+ chars: number;
101
+ tokens: number;
102
+ }>): void;
103
+ private getCostConfig;
104
+ private containsKeyword;
105
+ private calculateEwma;
106
+ private calculateConfidence;
107
+ private estimateInputTokensFromTotal;
108
+ private estimateOutputTokensFromTotal;
109
+ }
110
+ export declare function createPreCallCostEstimator(historicalData?: Array<{
111
+ chars: number;
112
+ tokens: number;
113
+ }>): PreCallCostEstimator;
114
+ //# sourceMappingURL=preCallCostEstimator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preCallCostEstimator.d.ts","sourceRoot":"","sources":["../../src/cost/preCallCostEstimator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAM3D,MAAM,WAAW,iBAAiB;IAChC,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sDAAsD;IACtD,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,YAAY;IAC3B,8CAA8C;IAC9C,eAAe,EAAE,MAAM,CAAC;IACxB,6BAA6B;IAC7B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,8BAA8B;IAC9B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,4BAA4B;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,wCAAwC;IACxC,gBAAgB,EAAE,MAAM,CAAC;IACzB,2BAA2B;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,8BAA8B;IAC9B,SAAS,EAAE;QACT,aAAa,EAAE,MAAM,CAAC;QACtB,cAAc,EAAE,MAAM,CAAC;QACvB,gBAAgB,EAAE,MAAM,CAAC;KAC1B,CAAC;CACH;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAeD,qBAAa,oBAAoB;IAE/B,OAAO,CAAC,cAAc,CAAgD;IAEtE,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,SAAS,CAAM;IAEvB,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,SAAS,CAAO;gBAEZ,cAAc,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAMrE;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,iBAAiB,GAAG,YAAY;IA+ChD;;;OAGG;IACH,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,kBAAkB,SAAI,GAAG,MAAM;IAY5D;;;OAGG;IACH,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM;IAwBhE;;OAEG;IACH,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,GAAG,MAAM;IAMtE;;OAEG;IACH,eAAe,CAAC,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM;IAmBrF;;OAEG;IACH,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI;IAcnE;;OAEG;IACH,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAO5C;;;OAGG;IACH,mBAAmB,CAAC,IAAI,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,IAAI;IA4BzE,OAAO,CAAC,aAAa;IA8BrB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,4BAA4B;IAIpC,OAAO,CAAC,6BAA6B;CAGtC;AAMD,wBAAgB,0BAA0B,CACxC,cAAc,CAAC,EAAE,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,GACxD,oBAAoB,CAEtB"}
@@ -0,0 +1,256 @@
1
+ "use strict";
2
+ /**
3
+ * A3M Router - Pre-Call Cost Estimator
4
+ *
5
+ * Estimates cost BEFORE making an API call based on input features.
6
+ * Uses historical data patterns and token count estimation via character ratio.
7
+ *
8
+ * No external API calls - all estimation is local.
9
+ *
10
+ * Usage:
11
+ * const estimator = new PreCallCostEstimator();
12
+ * const estimate = estimator.estimate({
13
+ * query: "What is Python?",
14
+ * modelTier: 'mid',
15
+ * provider: 'groq'
16
+ * });
17
+ * console.log(estimate); // { estimatedTokens: 24, estimatedCost: 0.0014, estimatedLatency: 200 }
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.PreCallCostEstimator = void 0;
21
+ exports.createPreCallCostEstimator = createPreCallCostEstimator;
22
+ // Default latency estimates per tier (ms)
23
+ const TIER_LATENCY = {
24
+ free: { min: 500, max: 5000, avg: 2000 },
25
+ cheap: { min: 100, max: 800, avg: 300 },
26
+ mid: { min: 200, max: 1500, avg: 600 },
27
+ premium: { min: 300, max: 2000, avg: 800 },
28
+ enterprise: { min: 200, max: 1500, avg: 500 },
29
+ };
30
+ // ============================================================
31
+ // PreCallCostEstimator
32
+ // ============================================================
33
+ class PreCallCostEstimator {
34
+ // Historical data for regression: [charCount, actualTokens][] tuples
35
+ historicalData = [];
36
+ // Linear regression coefficients
37
+ slope = 0.25; // chars per token ratio
38
+ intercept = 10;
39
+ // EWMA for latency estimation
40
+ latencyHistory = [];
41
+ ewmaAlpha = 0.3;
42
+ constructor(historicalData) {
43
+ if (historicalData && historicalData.length > 0) {
44
+ this.fitLinearRegression(historicalData);
45
+ }
46
+ }
47
+ /**
48
+ * Main estimation method - estimates tokens, cost, and latency.
49
+ */
50
+ estimate(input) {
51
+ const { query, modelTier = 'mid', provider, explicitTokens, systemPromptLength = 0, } = input;
52
+ // Token estimation
53
+ let inputTokens;
54
+ if (explicitTokens !== undefined) {
55
+ inputTokens = explicitTokens;
56
+ }
57
+ else {
58
+ inputTokens = this.estimateTokens(query, systemPromptLength);
59
+ }
60
+ // Output token estimation based on query complexity
61
+ const outputTokens = this.estimateOutputTokens(query, inputTokens);
62
+ const totalTokens = inputTokens + outputTokens;
63
+ // Cost estimation
64
+ const costConfig = this.getCostConfig(provider, modelTier);
65
+ const estimatedCost = this.calculateCost(totalTokens, costConfig);
66
+ // Latency estimation
67
+ const estimatedLatency = this.estimateLatency(modelTier, inputTokens, totalTokens);
68
+ // Confidence based on amount of historical data we have
69
+ const confidence = this.calculateConfidence();
70
+ return {
71
+ estimatedTokens: totalTokens,
72
+ estimatedInputTokens: inputTokens,
73
+ estimatedOutputTokens: outputTokens,
74
+ estimatedCost: Math.round(estimatedCost * 1000000) / 1000000, // 6 decimal places
75
+ estimatedLatency,
76
+ confidence,
77
+ breakdown: {
78
+ inputCostPerM: costConfig.input,
79
+ outputCostPerM: costConfig.output,
80
+ charToTokenRatio: this.slope,
81
+ },
82
+ };
83
+ }
84
+ /**
85
+ * Estimate input tokens using character-to-token ratio.
86
+ * Uses linear regression if historical data is available.
87
+ */
88
+ estimateTokens(text, systemPromptLength = 0) {
89
+ const totalChars = text.length + systemPromptLength;
90
+ if (this.historicalData.length >= 5) {
91
+ // Use linear regression
92
+ return Math.max(1, Math.round(this.slope * totalChars + this.intercept));
93
+ }
94
+ // Fallback: general English average ~4 chars per token
95
+ return Math.max(1, Math.round(totalChars / 4));
96
+ }
97
+ /**
98
+ * Estimate output tokens based on query complexity.
99
+ * More complex queries (code, analysis) tend to need more output.
100
+ */
101
+ estimateOutputTokens(query, inputTokens) {
102
+ const lower = query.toLowerCase();
103
+ // Base estimate: ~30% of input tokens as output
104
+ let multiplier = 0.3;
105
+ // Complexity adjustments
106
+ if (this.containsKeyword(lower, ['code', 'implement', 'function', 'class', 'algorithm'])) {
107
+ multiplier = 0.5; // Code needs more output
108
+ }
109
+ else if (this.containsKeyword(lower, ['explain', 'describe', 'what is', 'how does'])) {
110
+ multiplier = 0.35; // Explanations need moderate output
111
+ }
112
+ else if (this.containsKeyword(lower, ['list', 'count', 'find all'])) {
113
+ multiplier = 0.4; // List queries need more output
114
+ }
115
+ else if (this.containsKeyword(lower, ['yes', 'no', 'is', 'are', 'does'])) {
116
+ multiplier = 0.1; // Simple questions need minimal output
117
+ }
118
+ // Cap to reasonable bounds
119
+ return Math.min(Math.max(10, Math.round(inputTokens * multiplier)), 4000 // Max 4k output tokens
120
+ );
121
+ }
122
+ /**
123
+ * Calculate cost in USD.
124
+ */
125
+ calculateCost(totalTokens, config) {
126
+ const inputM = this.estimateInputTokensFromTotal(totalTokens) / 1_000_000;
127
+ const outputM = this.estimateOutputTokensFromTotal(totalTokens) / 1_000_000;
128
+ return inputM * config.input + outputM * config.output;
129
+ }
130
+ /**
131
+ * Estimate latency in milliseconds.
132
+ */
133
+ estimateLatency(tier, inputTokens, totalTokens) {
134
+ const tierLatency = TIER_LATENCY[tier];
135
+ // Base latency from tier
136
+ let latency = tierLatency.avg;
137
+ // Scale by token count (rough linear approximation)
138
+ const tokenScale = totalTokens / 100;
139
+ latency *= Math.max(0.5, Math.min(3, tokenScale));
140
+ // Adjust for historical EWMA if available
141
+ if (this.latencyHistory.length > 0) {
142
+ const ewmaLatency = this.calculateEwma();
143
+ latency = latency * 0.7 + ewmaLatency * 0.3;
144
+ }
145
+ return Math.round(latency);
146
+ }
147
+ /**
148
+ * Record actual tokens for future regression improvements.
149
+ */
150
+ recordActualTokens(queryLength, actualTokens) {
151
+ this.historicalData.push({ chars: queryLength, tokens: actualTokens });
152
+ // Keep only last 100 data points
153
+ if (this.historicalData.length > 100) {
154
+ this.historicalData.shift();
155
+ }
156
+ // Refit regression periodically
157
+ if (this.historicalData.length % 10 === 0) {
158
+ this.fitLinearRegression(this.historicalData);
159
+ }
160
+ }
161
+ /**
162
+ * Record actual latency for EWMA updates.
163
+ */
164
+ recordActualLatency(latencyMs) {
165
+ this.latencyHistory.push(latencyMs);
166
+ if (this.latencyHistory.length > 50) {
167
+ this.latencyHistory.shift();
168
+ }
169
+ }
170
+ /**
171
+ * Fit linear regression to historical data.
172
+ * Uses ordinary least squares.
173
+ */
174
+ fitLinearRegression(data) {
175
+ if (data.length < 2)
176
+ return;
177
+ const n = data.length;
178
+ let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
179
+ for (const { chars, tokens } of data) {
180
+ sumX += chars;
181
+ sumY += tokens;
182
+ sumXY += chars * tokens;
183
+ sumX2 += chars * chars;
184
+ }
185
+ const denominator = n * sumX2 - sumX * sumX;
186
+ if (denominator === 0)
187
+ return;
188
+ this.slope = (n * sumXY - sumX * sumY) / denominator;
189
+ this.intercept = (sumY - this.slope * sumX) / n;
190
+ // Sanity check
191
+ if (this.slope <= 0 || this.slope > 1) {
192
+ this.slope = 0.25; // Reset to default if outlier
193
+ this.intercept = 10;
194
+ }
195
+ }
196
+ // ---- Private helpers ----
197
+ getCostConfig(provider, tier) {
198
+ // Provider-specific costs (from providerConfig.ts patterns)
199
+ const providerCosts = {
200
+ groq: { input: 0.59, output: 0.79 },
201
+ cerebras: { input: 0.60, output: 0.60 },
202
+ deepseek: { input: 0.14, output: 0.28 },
203
+ deepinfra: { input: 0.05, output: 0.05 },
204
+ together: { input: 0.18, output: 0.18 },
205
+ fireworks: { input: 0.20, output: 0.20 },
206
+ mistral: { input: 0.20, output: 0.60 },
207
+ openai: { input: 2.50, output: 10.00 },
208
+ anthropic: { input: 3.00, output: 15.00 },
209
+ };
210
+ if (provider && providerCosts[provider]) {
211
+ return providerCosts[provider];
212
+ }
213
+ // Tier fallback
214
+ const tierCosts = {
215
+ free: { input: 0, output: 0 },
216
+ cheap: { input: 0.20, output: 0.40 },
217
+ mid: { input: 1.00, output: 3.00 },
218
+ premium: { input: 3.00, output: 12.00 },
219
+ enterprise: { input: 5.00, output: 20.00 },
220
+ };
221
+ return tier ? tierCosts[tier] : tierCosts.mid;
222
+ }
223
+ containsKeyword(text, keywords) {
224
+ return keywords.some(kw => text.includes(kw));
225
+ }
226
+ calculateEwma() {
227
+ if (this.latencyHistory.length === 0)
228
+ return 0;
229
+ let ewma = this.latencyHistory[0];
230
+ for (let i = 1; i < this.latencyHistory.length; i++) {
231
+ ewma = this.ewmaAlpha * this.latencyHistory[i] + (1 - this.ewmaAlpha) * ewma;
232
+ }
233
+ return ewma;
234
+ }
235
+ calculateConfidence() {
236
+ // More historical data = higher confidence
237
+ const dataFactor = Math.min(this.historicalData.length / 50, 1.0);
238
+ // More latency history = higher confidence
239
+ const latencyFactor = Math.min(this.latencyHistory.length / 20, 1.0);
240
+ return Math.round((dataFactor * 0.6 + latencyFactor * 0.4) * 100) / 100;
241
+ }
242
+ estimateInputTokensFromTotal(total) {
243
+ return Math.round(total * 0.7); // Assume 70% input
244
+ }
245
+ estimateOutputTokensFromTotal(total) {
246
+ return Math.round(total * 0.3); // Assume 30% output
247
+ }
248
+ }
249
+ exports.PreCallCostEstimator = PreCallCostEstimator;
250
+ // ============================================================
251
+ // Factory
252
+ // ============================================================
253
+ function createPreCallCostEstimator(historicalData) {
254
+ return new PreCallCostEstimator(historicalData);
255
+ }
256
+ //# sourceMappingURL=preCallCostEstimator.js.map