adaptive-memory-multi-model-router 2.2.4 → 2.2.6
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/README.md +17 -22
- package/README.md.bak +836 -0
- package/dist/analytics/costAnalytics.d.ts +1 -0
- package/dist/cache/cacheKeyGenerator.d.ts +67 -0
- package/dist/cache/cacheKeyGenerator.d.ts.map +1 -0
- package/dist/cache/cacheKeyGenerator.js +211 -0
- package/dist/cache/cacheKeyGenerator.js.map +1 -0
- package/dist/cache/semanticCache.d.ts +41 -0
- package/dist/cache/semanticCache.d.ts.map +1 -1
- package/dist/cache/semanticCache.js +142 -0
- package/dist/cache/semanticCache.js.map +1 -1
- package/dist/cli.js +35 -478
- package/dist/cost/costTracker.js +0 -3
- package/dist/cost/preCallCostEstimator.d.ts +114 -0
- package/dist/cost/preCallCostEstimator.d.ts.map +1 -0
- package/dist/cost/preCallCostEstimator.js +256 -0
- package/dist/cost/preCallCostEstimator.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +264 -64
- package/dist/index.js.map +1 -1
- package/dist/inference/speculativeDecoding.d.ts +133 -0
- package/dist/inference/speculativeDecoding.d.ts.map +1 -0
- package/dist/inference/speculativeDecoding.js +276 -0
- package/dist/inference/speculativeDecoding.js.map +1 -0
- package/dist/integrations/langchainAdapter.d.ts +1 -0
- package/dist/integrations/oauth.d.ts +1 -0
- package/dist/memory/autoFetch.d.ts +1 -0
- package/dist/memory/memoryTree.d.ts +1 -0
- package/dist/memory/obsidianVault.d.ts +1 -0
- package/dist/providers/providerConfig.d.ts +1 -0
- package/dist/providers/providerConfig.js +2 -0
- package/dist/providers/providerHealth.d.ts +117 -0
- package/dist/providers/providerHealth.d.ts.map +1 -0
- package/dist/providers/providerHealth.js +309 -0
- package/dist/providers/providerHealth.js.map +1 -0
- package/dist/providers/registry.js +126 -128
- package/dist/routing/advancedRouter.js +310 -427
- package/dist/routing/difficultyClassifier.d.ts +79 -0
- package/dist/routing/difficultyClassifier.d.ts.map +1 -0
- package/dist/routing/difficultyClassifier.js +329 -0
- package/dist/routing/difficultyClassifier.js.map +1 -0
- package/dist/sdk.d.ts +125 -0
- package/dist/sdk.d.ts.map +1 -0
- package/dist/sdk.js +109 -100
- package/dist/sdk.js.map +1 -0
- package/dist/security/guardrails.d.ts +1 -0
- package/dist/server/dashboard.d.ts +1 -0
- package/dist/server/modelMapper.d.ts +1 -0
- package/dist/server/proxyServer.d.ts +1 -0
- package/package.json +106 -3
- package/src/cache/cacheKeyGenerator.ts +242 -0
- package/src/cache/semanticCache.ts +148 -0
- package/src/cost/preCallCostEstimator.ts +345 -0
- package/src/inference/speculativeDecoding.ts +373 -0
- package/src/providers/providerHealth.ts +397 -0
- package/src/routing/difficultyClassifier.ts +420 -0
- package/test/provider-test.js +2 -2
- package/test.js +7 -7
- package/test.js.bak +376 -0
- package/tsconfig.json +15 -5
- package/src/index.ts +0 -99
- package/src/skills/__tests__/skill_manager.test.ts +0 -328
package/dist/cli.js
CHANGED
|
@@ -1,502 +1,59 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Commands:
|
|
6
|
-
* npx a3m-router serve [--port 8787] Start OpenAI-compatible proxy server
|
|
7
|
-
* npx a3m-router route <query> Route query to best provider
|
|
8
|
-
* npx a3m-router batch <q1> <q2>.. Route multiple queries
|
|
9
|
-
* npx a3m-router providers List all configured providers
|
|
10
|
-
* npx a3m-router test Test all providers
|
|
11
|
-
* npx a3m-router compare <query> Compare providers side by side
|
|
12
|
-
* npx a3m-router benchmark Benchmark all providers
|
|
13
|
-
* npx a3m-router recommend <task> Get model recommendation
|
|
14
|
-
* npx a3m-router cost <text> Estimate token cost
|
|
15
|
-
* npx a3m-router token <text> Count tokens
|
|
16
|
-
* npx a3m-router models List known models
|
|
17
|
-
* npx a3m-router memory add/search/stats Memory operations
|
|
18
|
-
* npx a3m-router status Show router status
|
|
3
|
+
* TMLPD PI CLI
|
|
19
4
|
*/
|
|
20
5
|
|
|
21
|
-
const {
|
|
22
|
-
const {
|
|
23
|
-
createA3MRouter, routeQuery, routeBatch, recommendForTask,
|
|
24
|
-
countTokens, estimateCost, MODEL_COSTS, CostTracker, MemoryTree,
|
|
25
|
-
getAvailableProviders, providerConfig, registerProvider, loadProviders,
|
|
26
|
-
} = require('./index.js');
|
|
27
|
-
|
|
28
|
-
let createProxyServer;
|
|
29
|
-
try {
|
|
30
|
-
createProxyServer = require('./server/proxyServer.js').createProxyServer;
|
|
31
|
-
} catch (e) {
|
|
32
|
-
// Server module not yet compiled
|
|
33
|
-
}
|
|
6
|
+
const { createTMLPD } = require("./dist/index.js");
|
|
34
7
|
|
|
35
8
|
const args = process.argv.slice(2);
|
|
36
9
|
const command = args[0];
|
|
37
10
|
|
|
38
|
-
// ============================================================
|
|
39
|
-
// HELPER FUNCTIONS
|
|
40
|
-
// ============================================================
|
|
41
|
-
|
|
42
|
-
function formatRoute(result) {
|
|
43
|
-
console.log('\n🔀 A3M Router — Route Result');
|
|
44
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
45
|
-
console.log(' Primary: ' + result.primary_model);
|
|
46
|
-
if (result.fallback_models) {
|
|
47
|
-
console.log(' Fallbacks: ' + result.fallback_models.join(', '));
|
|
48
|
-
}
|
|
49
|
-
if (result.estimated_cost) {
|
|
50
|
-
console.log(' Est. Cost: $' + result.estimated_cost.toFixed(6));
|
|
51
|
-
}
|
|
52
|
-
if (result.provider_type) {
|
|
53
|
-
console.log(' Type: ' + result.provider_type);
|
|
54
|
-
}
|
|
55
|
-
if (result.reasoning) {
|
|
56
|
-
console.log(' Reason: ' + result.reasoning);
|
|
57
|
-
showStarPrompt();
|
|
58
|
-
}
|
|
59
|
-
console.log('');
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
// ============================================================
|
|
64
|
-
// STAR PROMPT (shown once per user)
|
|
65
|
-
// ============================================================
|
|
66
|
-
function showStarPrompt() {
|
|
67
|
-
const fs = require('fs');
|
|
68
|
-
const path = require('path');
|
|
69
|
-
const home = process.env.HOME || process.env.USERPROFILE || '/tmp';
|
|
70
|
-
const marker = path.join(home, '.a3m-router', '.star-prompt-shown');
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
// Only show once
|
|
74
|
-
if (fs.existsSync(marker)) return;
|
|
75
|
-
|
|
76
|
-
// Create directory if needed
|
|
77
|
-
const dir = path.dirname(marker);
|
|
78
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
79
|
-
|
|
80
|
-
console.log('');
|
|
81
|
-
console.log(' \x1b[2m── ────────────────────────────────────────\x1b[0m');
|
|
82
|
-
console.log(' \x1b[2m⭐ Found this useful? Star us on GitHub:\x1b[0m');
|
|
83
|
-
console.log(' \x1b[1m\x1b[36m https://github.com/Das-rebel/adaptive-memory-multi-model-router\x1b[0m');
|
|
84
|
-
console.log(' \x1b[2m── ────────────────────────────────────────\x1b[0m');
|
|
85
|
-
console.log('');
|
|
86
|
-
|
|
87
|
-
// Mark as shown
|
|
88
|
-
fs.writeFileSync(marker, new Date().toISOString());
|
|
89
|
-
} catch (e) {
|
|
90
|
-
// Silently fail - don't break CLI for this
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async function callProvider(providerId, model, prompt, maxTokens) {
|
|
95
|
-
providerId = providerId || 'groq';
|
|
96
|
-
model = model || 'llama-3.3-70b-versatile';
|
|
97
|
-
maxTokens = maxTokens || 50;
|
|
98
|
-
|
|
99
|
-
const providers = providerConfig.getAvailableProviders();
|
|
100
|
-
const provider = providers[providerId];
|
|
101
|
-
|
|
102
|
-
if (!provider) {
|
|
103
|
-
console.error(' ❌ Provider "' + providerId + '" not found or not configured.');
|
|
104
|
-
console.error(' Run: npx a3m-router providers');
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const startTime = Date.now();
|
|
109
|
-
|
|
110
|
-
if (provider.type === 'cli') {
|
|
111
|
-
try {
|
|
112
|
-
if (providerId === 'commandcode') {
|
|
113
|
-
const raw = execSync('commandcode -p "' + prompt.replace(/"/g, '\\"') + '" --skip-onboarding 2>&1', { timeout: 60000, encoding: 'utf-8' });
|
|
114
|
-
const content = raw.replace(/\x1b\[[0-9;]*m/g, '').trim();
|
|
115
|
-
return { content: content.substring(0, 200), totalTokens: Math.ceil(content.length / 4), cost: 0, latency: Date.now() - startTime };
|
|
116
|
-
} else {
|
|
117
|
-
const raw = execSync(provider.cliCommand + ' run "' + prompt.replace(/"/g, '\\"') + '" 2>&1', { timeout: 60000, encoding: 'utf-8' });
|
|
118
|
-
const lines = raw.replace(/\x1b\[[0-9;]*m/g, '').split('\n').filter(l => l.trim() && !l.startsWith('>') && !l.includes('build'));
|
|
119
|
-
const content = lines.join(' ').trim();
|
|
120
|
-
return { content: content.substring(0, 200), totalTokens: Math.ceil(content.length / 4), cost: 0, latency: Date.now() - startTime };
|
|
121
|
-
}
|
|
122
|
-
} catch (e) {
|
|
123
|
-
console.error(' ❌ ' + provider.name + ' error: ' + e.message.substring(0, 80));
|
|
124
|
-
return null;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// API provider
|
|
129
|
-
try {
|
|
130
|
-
const resp = await fetch(provider.baseUrl, {
|
|
131
|
-
method: 'POST',
|
|
132
|
-
headers: { 'Authorization': 'Bearer ' + provider.apiKey, 'Content-Type': 'application/json' },
|
|
133
|
-
body: JSON.stringify({ model, messages: [{ role: 'user', content: prompt }], max_tokens: maxTokens }),
|
|
134
|
-
});
|
|
135
|
-
const data = await resp.json();
|
|
136
|
-
const latency = Date.now() - startTime;
|
|
137
|
-
if (data.error) {
|
|
138
|
-
console.error(' ❌ ' + provider.name + ' error: ' + data.error.message.substring(0, 80));
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
const tokens = data.usage || {};
|
|
142
|
-
const cost = (tokens.prompt_tokens || 0) / 1000 * provider.costPerK.input + (tokens.completion_tokens || 0) / 1000 * provider.costPerK.output;
|
|
143
|
-
return { content: data.choices[0].message.content.trim(), totalTokens: tokens.total_tokens || 0, cost, latency, model: data.model || model };
|
|
144
|
-
} catch (e) {
|
|
145
|
-
console.error(' ❌ ' + provider.name + ' error: ' + e.message.substring(0, 80));
|
|
146
|
-
return null;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// ============================================================
|
|
151
|
-
// COMMANDS
|
|
152
|
-
// ============================================================
|
|
153
|
-
|
|
154
11
|
async function main() {
|
|
155
|
-
const router = createA3MRouter({ memory: { maxSize: 1000 } });
|
|
156
|
-
|
|
157
12
|
switch (command) {
|
|
158
|
-
case
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
console.log(
|
|
163
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
164
|
-
console.log(' Config: ~/.config/a3m-router/providers.json');
|
|
165
|
-
console.log(' Add your own: npx a3m-router register <id> <config>');
|
|
166
|
-
console.log('');
|
|
167
|
-
console.log(' Provider Type Models Priority Key');
|
|
168
|
-
console.log(' ───────────────────── ─────── ────── ──────── ─────────');
|
|
169
|
-
|
|
170
|
-
for (const [id, provider] of Object.entries(allProviders)) {
|
|
171
|
-
const available = providers[id];
|
|
172
|
-
const status = available ? '✅' : '❌';
|
|
173
|
-
const keyStatus = provider.apiKey ? '✅' : (provider.type === 'cli' ? 'N/A' : '❌');
|
|
174
|
-
const modelCount = provider.models ? provider.models.length : 0;
|
|
175
|
-
console.log(' ' + status + ' ' + (provider.name || id).padEnd(20) + ' ' + (provider.type || 'api').padEnd(7) + ' ' + String(modelCount).padEnd(6) + ' ' + String(provider.priority).padEnd(9) + ' ' + keyStatus);
|
|
176
|
-
}
|
|
177
|
-
console.log('');
|
|
178
|
-
console.log(' Available: ' + Object.keys(providers).length + ' providers');
|
|
179
|
-
console.log(' Configured: ' + Object.keys(allProviders).length + ' providers');
|
|
180
|
-
console.log('');
|
|
181
|
-
break;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
case 'test': {
|
|
185
|
-
const providers = providerConfig.getAvailableProviders();
|
|
186
|
-
console.log('\n🧪 A3M Router — Provider Health Check');
|
|
187
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
|
|
188
|
-
|
|
189
|
-
for (const [id, provider] of Object.entries(providers)) {
|
|
190
|
-
const model = provider.models[0];
|
|
191
|
-
console.log(' Testing ' + (provider.name || id) + ' (' + model + ')...');
|
|
192
|
-
const result = await callProvider(id, model, 'Say OK', 5);
|
|
193
|
-
if (result) {
|
|
194
|
-
console.log(' ✅ Response: "' + result.content.substring(0, 30) + '" (' + result.totalTokens + ' tok, ' + result.latency + 'ms, $' + result.cost.toFixed(6) + ')');
|
|
195
|
-
}
|
|
196
|
-
console.log('');
|
|
197
|
-
}
|
|
198
|
-
break;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
case 'compare': {
|
|
202
|
-
const query = args.slice(1).join(' ');
|
|
203
|
-
if (!query) {
|
|
204
|
-
console.error('Usage: npx a3m-router compare "your query here"');
|
|
205
|
-
process.exit(1);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const providers = providerConfig.getAvailableProviders();
|
|
209
|
-
console.log('\n🔄 A3M Router — Provider Comparison');
|
|
210
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
211
|
-
console.log(' Query: "' + query + '"');
|
|
212
|
-
console.log('');
|
|
213
|
-
|
|
214
|
-
const results = [];
|
|
215
|
-
for (const [id, provider] of Object.entries(providers)) {
|
|
216
|
-
const model = provider.models[0];
|
|
217
|
-
console.log(' Testing ' + (provider.name || id) + '...');
|
|
218
|
-
const result = await callProvider(id, model, query, 100);
|
|
219
|
-
if (result) {
|
|
220
|
-
results.push({ id: provider.name || id, model, ...result });
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
console.log('\n Comparison:');
|
|
225
|
-
console.log(' ──────────────────────────────────────────────────────────────');
|
|
226
|
-
console.log(' Provider'.padEnd(18) + 'Response'.padEnd(40) + 'Time'.padEnd(12) + 'Cost');
|
|
227
|
-
console.log(' ──────────────────────────────────────────────────────────────');
|
|
228
|
-
for (const r of results) {
|
|
229
|
-
console.log(' ' + r.id.padEnd(16) + r.content.substring(0, 38).padEnd(40) + (r.latency + 'ms').padEnd(12) + '$' + r.cost.toFixed(6));
|
|
230
|
-
}
|
|
231
|
-
console.log('');
|
|
232
|
-
break;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
case 'benchmark': {
|
|
236
|
-
const queries = [
|
|
237
|
-
'What is 2+2?',
|
|
238
|
-
'Write a Python function to reverse a string.',
|
|
239
|
-
'Translate "Hello" to French.',
|
|
240
|
-
'Write a haiku about programming.',
|
|
241
|
-
'What is SQL injection?',
|
|
242
|
-
];
|
|
243
|
-
|
|
244
|
-
const providers = providerConfig.getAvailableProviders();
|
|
245
|
-
console.log('\n📊 A3M Router — Provider Benchmark');
|
|
246
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
|
|
247
|
-
|
|
248
|
-
for (const [id, provider] of Object.entries(providers)) {
|
|
249
|
-
const model = provider.models[0];
|
|
250
|
-
console.log(' ' + (provider.name || id).padEnd(15) + '(' + model + ')');
|
|
251
|
-
|
|
252
|
-
let totalTime = 0;
|
|
253
|
-
let totalCost = 0;
|
|
254
|
-
|
|
255
|
-
for (const q of queries) {
|
|
256
|
-
const r = await callProvider(id, model, q, 50);
|
|
257
|
-
if (r) {
|
|
258
|
-
totalTime += r.latency;
|
|
259
|
-
totalCost += r.cost;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
console.log(' Total: ' + totalTime + 'ms, Cost: $' + totalCost.toFixed(6) + ', Avg: ' + (totalTime / queries.length).toFixed(0) + 'ms/query');
|
|
264
|
-
}
|
|
265
|
-
console.log('');
|
|
266
|
-
break;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
case 'route': {
|
|
270
|
-
const query = args.slice(1).join(' ');
|
|
271
|
-
if (!query) {
|
|
272
|
-
console.error('Usage: npx a3m-router route "your query here"');
|
|
273
|
-
process.exit(1);
|
|
274
|
-
}
|
|
275
|
-
const result = router.route(query);
|
|
276
|
-
formatRoute(result);
|
|
277
|
-
break;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
case 'batch': {
|
|
281
|
-
const queries = args.slice(1);
|
|
282
|
-
if (queries.length === 0) {
|
|
283
|
-
console.error('Usage: npx a3m-router batch "query1" "query2" ...');
|
|
284
|
-
process.exit(1);
|
|
285
|
-
}
|
|
286
|
-
const results = router.routeBatch(queries);
|
|
287
|
-
console.log('\n🔀 A3M Router — Batch Results');
|
|
288
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
289
|
-
results.forEach(function(r, i) {
|
|
290
|
-
console.log(' ' + (i + 1) + '. "' + queries[i].substring(0, 40) + '..." → ' + r.primary_model);
|
|
291
|
-
});
|
|
292
|
-
console.log('');
|
|
293
|
-
break;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
case 'recommend': {
|
|
297
|
-
const task = args.slice(1).join(' ');
|
|
298
|
-
if (!task) {
|
|
299
|
-
console.error('Usage: npx a3m-router recommend "coding"');
|
|
300
|
-
process.exit(1);
|
|
301
|
-
}
|
|
302
|
-
const rec = router.recommend(task);
|
|
303
|
-
console.log('\n🎯 A3M Router — Recommendation');
|
|
304
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
305
|
-
console.log(JSON.stringify(rec, null, 2));
|
|
306
|
-
console.log('');
|
|
307
|
-
break;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
case 'status': {
|
|
311
|
-
const providers = providerConfig.getAvailableProviders();
|
|
312
|
-
console.log('\n📊 A3M Router — Status');
|
|
313
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━');
|
|
314
|
-
console.log(' Version: 1.9.0');
|
|
315
|
-
console.log(' Exports: 74');
|
|
316
|
-
console.log(' Providers: ' + Object.keys(providers).length + ' configured');
|
|
317
|
-
console.log(' Integrations: 116');
|
|
318
|
-
console.log(' Keywords: 139');
|
|
319
|
-
console.log(' Subpaths: 11');
|
|
320
|
-
console.log(' Memory: ✅ MemoryTree + AutoFetch + ObsidianVault');
|
|
321
|
-
console.log(' Compression: ✅ Enhanced + ISON');
|
|
322
|
-
console.log(' Auth: ✅ OAuth 2.0 + PKCE');
|
|
323
|
-
console.log(' Cost: ✅ Tracking + Budgets');
|
|
324
|
-
console.log(' Cache: ✅ Prefix + Response');
|
|
325
|
-
console.log(' Routing: ✅ RouteLLM + Adaptive');
|
|
326
|
-
console.log(' Models known: ' + Object.keys(providerConfig._providers).length);
|
|
327
|
-
console.log('');
|
|
328
|
-
console.log(' Available Providers:');
|
|
329
|
-
for (const [id, p] of Object.entries(providers)) {
|
|
330
|
-
console.log(' ✅ ' + (p.name || id).padEnd(15) + '(' + p.models.length + ' models, type: ' + p.type + ')');
|
|
331
|
-
}
|
|
332
|
-
console.log('');
|
|
333
|
-
break;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
case 'cost': {
|
|
337
|
-
const text = args.slice(1).join(' ') || 'Hello world this is a test';
|
|
338
|
-
const tokens = countTokens(text);
|
|
339
|
-
var completionTokens = Math.ceil(tokens * 1.5);
|
|
340
|
-
var gpt4oCost = estimateCost(tokens, completionTokens, 'gpt-4o');
|
|
341
|
-
var miniCost = estimateCost(tokens, completionTokens, 'gpt-4o-mini');
|
|
342
|
-
var haikuCost = estimateCost(tokens, completionTokens, 'claude-3-haiku');
|
|
343
|
-
var geminiCost = estimateCost(tokens, completionTokens, 'gemini-2.0-flash');
|
|
344
|
-
console.log('\n💰 A3M Router — Cost Estimate');
|
|
345
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
346
|
-
console.log(' Text: "' + text.substring(0, 50) + '"');
|
|
347
|
-
console.log(' Tokens: ' + tokens);
|
|
348
|
-
console.log(' GPT-4o: $' + gpt4oCost.toFixed(6));
|
|
349
|
-
console.log(' GPT-4o-mini: $' + miniCost.toFixed(6));
|
|
350
|
-
console.log(' Claude Haiku: $' + haikuCost.toFixed(6));
|
|
351
|
-
console.log(' Gemini Flash: $' + geminiCost.toFixed(6));
|
|
352
|
-
if (gpt4oCost > 0) {
|
|
353
|
-
var savings = ((1 - miniCost / gpt4oCost) * 100).toFixed(1);
|
|
354
|
-
console.log(' Savings: ' + savings + '% (mini vs GPT-4o)');
|
|
355
|
-
}
|
|
356
|
-
console.log('');
|
|
357
|
-
break;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
case 'models': {
|
|
361
|
-
const allProviders = providerConfig._providers;
|
|
362
|
-
console.log('\n📋 A3M Router — All Known Models');
|
|
363
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
|
|
364
|
-
|
|
365
|
-
for (const [id, provider] of Object.entries(allProviders)) {
|
|
366
|
-
if (!provider.models || provider.models.length === 0) continue;
|
|
367
|
-
console.log(' ' + (provider.name || id).padEnd(15) + '(Priority: ' + provider.priority + ', Type: ' + provider.type + ')');
|
|
368
|
-
for (const m of provider.models) {
|
|
369
|
-
const cost = provider.costPerK;
|
|
370
|
-
console.log(' ' + m.padEnd(40) + 'in:$' + (cost ? cost.input : 0) + ' out:$' + (cost ? cost.output : 0));
|
|
371
|
-
}
|
|
372
|
-
console.log('');
|
|
373
|
-
}
|
|
13
|
+
case "execute": {
|
|
14
|
+
const tmlpd = createTMLPD();
|
|
15
|
+
const prompt = args.slice(1).join(" ");
|
|
16
|
+
const result = await tmlpd.execute(prompt);
|
|
17
|
+
console.log(JSON.stringify(result, null, 2));
|
|
374
18
|
break;
|
|
375
19
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
}
|
|
383
|
-
const tokens = countTokens(text);
|
|
384
|
-
console.log(' "' + text + '" → ' + tokens + ' tokens');
|
|
20
|
+
case "parallel": {
|
|
21
|
+
const tmlpd = createTMLPD();
|
|
22
|
+
const models = args.slice(1).join(" ").split(",").map(m => m.trim());
|
|
23
|
+
const prompt = "Compare these models";
|
|
24
|
+
const result = await tmlpd.executeParallel(prompt, models);
|
|
25
|
+
console.log(JSON.stringify(result, null, 2));
|
|
385
26
|
break;
|
|
386
27
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
registerProvider(id, config);
|
|
392
|
-
providerConfig.saveConfig();
|
|
393
|
-
console.log('✅ Registered provider: ' + id);
|
|
394
|
-
console.log(' Config saved to: ~/.config/a3m-router/providers.json');
|
|
28
|
+
case "cost": {
|
|
29
|
+
const tmlpd = createTMLPD();
|
|
30
|
+
const summary = tmlpd.getCostSummary();
|
|
31
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
395
32
|
break;
|
|
396
33
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
const text = args.slice(2).join(' ');
|
|
402
|
-
router.memory.add(text, { metadata: { cli: true } });
|
|
403
|
-
console.log(' ✅ Added to memory: "' + text.substring(0, 50) + '"');
|
|
404
|
-
} else if (subcmd === 'search') {
|
|
405
|
-
const query = args.slice(2).join(' ');
|
|
406
|
-
const results = router.memory.search(query);
|
|
407
|
-
console.log(' Found ' + results.length + ' results for "' + query + '"');
|
|
408
|
-
results.forEach(function(r, i) {
|
|
409
|
-
var content = r.content ? r.content.substring(0, 60) : JSON.stringify(r).substring(0, 60);
|
|
410
|
-
console.log(' ' + (i + 1) + '. ' + content);
|
|
411
|
-
});
|
|
412
|
-
} else {
|
|
413
|
-
const stats = router.memory.getStats();
|
|
414
|
-
console.log('\n🧠 A3M Router — Memory Stats');
|
|
415
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
416
|
-
console.log(JSON.stringify(stats, null, 2));
|
|
417
|
-
}
|
|
34
|
+
case "status": {
|
|
35
|
+
const tmlpd = createTMLPD();
|
|
36
|
+
const status = tmlpd.getProviderStatus();
|
|
37
|
+
console.log(JSON.stringify(status, null, 2));
|
|
418
38
|
break;
|
|
419
39
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
console.log(
|
|
424
|
-
|
|
425
|
-
const providers = providerConfig.getAvailableProviders();
|
|
426
|
-
for (const [id, provider] of Object.entries(providers)) {
|
|
427
|
-
try {
|
|
428
|
-
const health = await providerConfig.healthCheck(id);
|
|
429
|
-
console.log(' ' + (health.healthy ? '✅' : '❌') + ' ' + (provider.name || id).padEnd(15) + health.healthy ? 'Healthy' : health.error);
|
|
430
|
-
} catch (e) {
|
|
431
|
-
console.log(' ❌ ' + (provider.name || id).padEnd(15) + e.message.substring(0, 60));
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
console.log('');
|
|
40
|
+
case "cache": {
|
|
41
|
+
const tmlpd = createTMLPD();
|
|
42
|
+
const stats = tmlpd.getCacheStats();
|
|
43
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
435
44
|
break;
|
|
436
45
|
}
|
|
437
|
-
|
|
438
|
-
case 'serve': {
|
|
439
|
-
if (!createProxyServer) {
|
|
440
|
-
console.error('\n Error: Server module not available.');
|
|
441
|
-
console.error(' Build first: npm run build');
|
|
442
|
-
console.error(' Or use directly: node -e "require(\'./server/proxyServer.js\').createProxyServer()"\n');
|
|
443
|
-
process.exit(1);
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// Parse --port argument
|
|
447
|
-
var portArg = args.indexOf('--port');
|
|
448
|
-
var port = undefined;
|
|
449
|
-
if (portArg !== -1 && args[portArg + 1]) {
|
|
450
|
-
port = parseInt(args[portArg + 1], 10);
|
|
451
|
-
if (isNaN(port)) {
|
|
452
|
-
console.error(' Error: --port must be a number');
|
|
453
|
-
process.exit(1);
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
console.log('\n Starting A3M Router Proxy Server...');
|
|
458
|
-
createProxyServer(port);
|
|
459
|
-
break;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
46
|
default:
|
|
463
|
-
console.log(
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
console.log(' recommend <task> Get model recommendation');
|
|
473
|
-
console.log(' cost [text] Estimate token cost across models');
|
|
474
|
-
console.log(' models List all known models + pricing');
|
|
475
|
-
console.log(' providers List configured providers');
|
|
476
|
-
console.log(' test Test all provider connectivity');
|
|
477
|
-
console.log(' health Quick health check for all providers');
|
|
478
|
-
console.log(' token <text> Count tokens');
|
|
479
|
-
console.log(' memory add <text> Add to memory tree');
|
|
480
|
-
console.log(' memory search <q> Search memory');
|
|
481
|
-
console.log(' memory Show memory stats');
|
|
482
|
-
console.log(' register <id> <cfg> Register new provider');
|
|
483
|
-
console.log(' status Show router status');
|
|
484
|
-
console.log('');
|
|
485
|
-
console.log(' Config: ~/.config/a3m-router/providers.json');
|
|
486
|
-
console.log(' Env: GROQ_API_KEY, CEREBRAS_API_KEY, MISTRAL_API_KEY, etc.');
|
|
487
|
-
console.log('');
|
|
488
|
-
console.log(' Examples:');
|
|
489
|
-
console.log(' npx a3m-router serve # Start proxy on :8787');
|
|
490
|
-
console.log(' npx a3m-router serve --port 3000 # Start proxy on :3000');
|
|
491
|
-
console.log(' npx a3m-router route "Write a Python function to sort"');
|
|
492
|
-
console.log(' npx a3m-router compare "What is 2+2?"');
|
|
493
|
-
console.log(' npx a3m-router providers');
|
|
494
|
-
console.log(' npx a3m-router test');
|
|
495
|
-
console.log('');
|
|
47
|
+
console.log(`TMLPD PI v1.0.0
|
|
48
|
+
|
|
49
|
+
Usage:
|
|
50
|
+
tmlpd-pi execute <prompt> Execute single prompt
|
|
51
|
+
tmlpd-pi parallel <models> Execute in parallel
|
|
52
|
+
tmlpd-pi cost Show cost summary
|
|
53
|
+
tmlpd-pi status Show provider status
|
|
54
|
+
tmlpd-pi cache Show cache stats
|
|
55
|
+
`);
|
|
496
56
|
}
|
|
497
57
|
}
|
|
498
58
|
|
|
499
|
-
main().catch(
|
|
500
|
-
console.error('Error:', err.message);
|
|
501
|
-
process.exit(1);
|
|
502
|
-
});
|
|
59
|
+
main().catch(console.error);
|
package/dist/cost/costTracker.js
CHANGED
|
@@ -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"}
|