adaptive-memory-multi-model-router 2.13.18 → 2.13.22

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 (91) hide show
  1. package/.dockerignore +82 -0
  2. package/.env.example +303 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.md +83 -12
  4. package/.github/ISSUE_TEMPLATE/config.yml +12 -6
  5. package/.github/ISSUE_TEMPLATE/feature_request.md +61 -10
  6. package/.github/PULL_REQUEST_TEMPLATE.md +53 -26
  7. package/.github/dependabot.yml +9 -0
  8. package/.github/workflows/codeql.yml +38 -0
  9. package/.github/workflows/npm-publish.yml +20 -0
  10. package/.github/workflows/stale.yml +56 -0
  11. package/ARCHITECTURE.md +346 -0
  12. package/AUDIT_REPORT.md +28 -0
  13. package/CHANGELOG.md +386 -22
  14. package/CONTRIBUTORS.md +20 -0
  15. package/Dockerfile +53 -0
  16. package/Dockerfile.proxy +33 -0
  17. package/PR_STATUS_REPORT.md +148 -0
  18. package/README.md +22 -0
  19. package/RUNKIT.md +83 -0
  20. package/_schema.html +61 -15
  21. package/articles/AI_AGENT_LLM_ROUTING.md +150 -0
  22. package/articles/FROM_ZERO_TO_10K.md +107 -0
  23. package/articles/LLM_BENCHMARK_DEEP_DIVE.md +153 -0
  24. package/articles/TWEETS_10K_DOWNLOADS.md +47 -0
  25. package/articles/TWEETS_BENCHMARK_FIRST.md +46 -0
  26. package/articles/TWEETS_MCP_PLAY.md +51 -0
  27. package/articles/TWEETS_SEQUENTIAL_BROKEN.md +49 -0
  28. package/articles/TWEETS_WHY_BUILD.md +54 -0
  29. package/benchmark-results.json +26 -45
  30. package/cli/a3m +840 -0
  31. package/demo/package.json +13 -0
  32. package/demo/public/index.html +762 -0
  33. package/demo/server.js +405 -0
  34. package/dist/cli.js +4 -0
  35. package/docker-compose.yml +74 -0
  36. package/docs/.nojekyll +0 -0
  37. package/docs/BENCHMARK.md +96 -22
  38. package/docs/_config.yml +49 -0
  39. package/docs/api.html +513 -0
  40. package/docs/benchmark.html +387 -0
  41. package/docs/cli-cheatsheet.md +339 -0
  42. package/docs/comparison.md +108 -0
  43. package/docs/curl-examples.md +247 -0
  44. package/docs/index.html +390 -99
  45. package/docs/openapi.yaml +1318 -0
  46. package/docs/quick-start.html +366 -0
  47. package/docs/robots.txt +1 -1
  48. package/docs/sitemap.xml +23 -5
  49. package/docs/styles.css +682 -0
  50. package/examples/README.md +61 -0
  51. package/examples/a3m-sdk.js +124 -0
  52. package/examples/basic-route.js +54 -0
  53. package/examples/chat-loop.js +202 -0
  54. package/examples/classify-then-route.js +102 -0
  55. package/examples/cost-compare.js +120 -0
  56. package/examples/ensemble.js +160 -0
  57. package/integrations/langchain/README.md +216 -0
  58. package/integrations/langchain/a3m_langchain.ts +1360 -0
  59. package/integrations/langchain/example.ts +287 -0
  60. package/integrations/vercel-ai-sdk/README.md +49 -0
  61. package/integrations/vercel-ai-sdk/a3m_provider.ts +78 -0
  62. package/integrations/vercel-ai-sdk/example.ts +25 -0
  63. package/llms-full.txt +43 -0
  64. package/llms.txt +9 -0
  65. package/mcp-server/README.md +188 -0
  66. package/mcp-server/package.json +29 -0
  67. package/mcp-server/src/index.ts +744 -0
  68. package/mcp-server/tsconfig.json +19 -0
  69. package/package.json +3 -3
  70. package/proxy/README.md +227 -0
  71. package/proxy/package-lock.json +831 -0
  72. package/proxy/package.json +17 -0
  73. package/proxy/rate-limit.js +145 -0
  74. package/proxy/rate-limit.test.js +311 -0
  75. package/proxy/server.js +970 -0
  76. package/scripts/banner.js +29 -0
  77. package/scripts/compare-providers.sh +230 -0
  78. package/scripts/cross_post.py +443 -0
  79. package/scripts/publish_fcc.py +106 -0
  80. package/scripts/push-to-gitee.sh +52 -0
  81. package/src/tui/dashboard.ts +13 -0
  82. package/tests/__mocks__/tokenUtils.ts +22 -0
  83. package/tests/memory/episodicMemory.test.ts +227 -0
  84. package/tests/package-lock.json +1628 -0
  85. package/tests/package.json +18 -0
  86. package/tests/routing/ensembleVoting.test.ts +236 -0
  87. package/tests/routing/providerRetry.test.ts +360 -0
  88. package/tests/routing/queryTypePresets.test.ts +206 -0
  89. package/tests/tsconfig.json +21 -0
  90. package/tests/vitest.config.ts +18 -0
  91. package/.env +0 -2
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ensemble.js — Query multiple providers in parallel and merge results.
4
+ *
5
+ * A3M's signature capability: parallel multi-LLM execution with voting.
6
+ * Unlike every other router (which does sequential fallback A -> B -> C),
7
+ * A3M queries all selected providers simultaneously and compares responses.
8
+ *
9
+ * Usage:
10
+ * node examples/ensemble.js
11
+ * QUERY="What is the capital of France?" PROVIDERS="openai,groq,anthropic" node examples/ensemble.js
12
+ */
13
+
14
+ const { routeQuery, getAvailableProviders } = require('../dist/index.js');
15
+
16
+ const query = process.env.QUERY || 'Explain the concept of recursion with a real-world analogy';
17
+ const providerList = (process.env.PROVIDERS || 'openai,groq,gemini')
18
+ .split(',')
19
+ .map(s => s.trim());
20
+
21
+ /**
22
+ * Simulate calling each provider. In production, replace with real API calls.
23
+ * A3M Router selects the model — you call its API endpoint.
24
+ */
25
+ async function callProvider(provider, model, prompt) {
26
+ const apiKey = process.env[provider.toUpperCase() + '_API_KEY'];
27
+
28
+ if (!apiKey) {
29
+ return {
30
+ provider,
31
+ model,
32
+ text: `[SKIP — set ${provider.toUpperCase()}_API_KEY]`,
33
+ cost: 0,
34
+ latency: 0,
35
+ skipped: true,
36
+ };
37
+ }
38
+
39
+ const start = Date.now();
40
+
41
+ // Each provider has a different base URL and format
42
+ const endpoints = {
43
+ openai: { url: 'https://api.openai.com/v1/chat/completions', model: model || 'gpt-4o-mini' },
44
+ groq: { url: 'https://api.groq.com/openai/v1/chat/completions', model: model || 'llama-3.3-70b-versatile' },
45
+ gemini: { url: 'https://generativelanguage.googleapis.com/v1beta/openai/chat/completions', model: model || 'gemini-2.0-flash' },
46
+ anthropic: { url: 'https://api.anthropic.com/v1/messages', model: model || 'claude-3-5-haiku-latest' },
47
+ };
48
+
49
+ const ep = endpoints[provider];
50
+ if (!ep) {
51
+ return { provider, model, text: `[UNSUPPORTED PROVIDER: ${provider}]`, cost: 0, latency: 0, skipped: true };
52
+ }
53
+
54
+ const headers = { 'Content-Type': 'application/json' };
55
+ if (provider === 'anthropic') {
56
+ headers['x-api-key'] = apiKey;
57
+ headers['anthropic-version'] = '2023-06-01';
58
+ } else {
59
+ headers['Authorization'] = `Bearer ${apiKey}`;
60
+ }
61
+
62
+ const body = provider === 'anthropic'
63
+ ? { model: ep.model, max_tokens: 512, messages: [{ role: 'user', content: prompt }] }
64
+ : { model: ep.model, messages: [{ role: 'user', content: prompt }], max_tokens: 512 };
65
+
66
+ try {
67
+ const res = await fetch(ep.url, {
68
+ method: 'POST',
69
+ headers,
70
+ body: JSON.stringify(body),
71
+ });
72
+
73
+ if (!res.ok) {
74
+ const errText = await res.text().catch(() => '');
75
+ return { provider, model: ep.model, text: `[ERROR ${res.status}: ${errText}]`, cost: 0, latency: Date.now() - start, skipped: true };
76
+ }
77
+
78
+ const data = await res.json();
79
+ const text = provider === 'anthropic'
80
+ ? data.content?.[0]?.text || JSON.stringify(data)
81
+ : data.choices?.[0]?.message?.content || JSON.stringify(data);
82
+
83
+ return {
84
+ provider,
85
+ model: ep.model,
86
+ text: text.trim(),
87
+ latency: Date.now() - start,
88
+ skipped: false,
89
+ usage: data.usage,
90
+ };
91
+ } catch (err) {
92
+ return { provider, model: ep.model, text: `[FETCH ERROR: ${err.message}]`, cost: 0, latency: Date.now() - start, skipped: true };
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Simple text voting: find common key phrases across responses.
98
+ */
99
+ function voteOnResults(results) {
100
+ const answered = results.filter(r => !r.skipped);
101
+ if (answered.length === 0) return { winner: null, consensus: false, details: 'No providers returned results' };
102
+
103
+ const texts = answered.map(r => r.text.toLowerCase());
104
+ const wordSets = texts.map(t => new Set(t.split(/\s+/).filter(w => w.length > 3)));
105
+ const intersection = wordSets.reduce((a, b) => new Set([...a].filter(x => b.has(x))));
106
+
107
+ const consensusScore = intersection.size > 5 ? 0.85 : 0.3;
108
+ return {
109
+ winner: answered[0],
110
+ consensus: consensusScore > 0.5,
111
+ consensusScore,
112
+ sharedTerms: [...intersection].slice(0, 10),
113
+ totalAnswered: answered.length,
114
+ totalSkipped: results.length - answered.length,
115
+ };
116
+ }
117
+
118
+ async function main() {
119
+ console.log('A3M Router — Parallel Ensemble');
120
+ console.log('=' .repeat(50));
121
+ console.log('Query:', query);
122
+ console.log('Providers:', providerList.join(', '));
123
+ console.log('');
124
+
125
+ // Get route decisions from A3M for insight
126
+ console.log('-- A3M Route Recommendations --');
127
+ for (const provider of providerList) {
128
+ const decision = routeQuery(query);
129
+ console.log(` ${provider}: ${decision.primary_model} (conf: ${(decision.confidence * 100).toFixed(0)}%, $${decision.estimated_cost.toFixed(6)})`);
130
+ }
131
+ console.log('');
132
+
133
+ // Execute all providers in parallel
134
+ console.log('-- Parallel Execution --');
135
+ const promises = providerList.map(provider => callProvider(provider, null, query));
136
+ const results = await Promise.all(promises);
137
+
138
+ for (const r of results) {
139
+ const icon = r.skipped ? ' [SKIP]' : ' [OK] ';
140
+ console.log(`${icon} ${r.provider.padEnd(12)} ${r.model}`);
141
+ console.log(` latency: ${r.latency}ms`);
142
+ if (!r.skipped) {
143
+ const preview = r.text.slice(0, 120).replace(/\n/g, ' ');
144
+ console.log(` response: ${preview}...`);
145
+ }
146
+ console.log('');
147
+ }
148
+
149
+ // Vote on results
150
+ const vote = voteOnResults(results);
151
+ console.log('-- Consensus Vote --');
152
+ console.log(' Consensus:', vote.consensus ? 'YES' : 'NO');
153
+ console.log(' Score:', vote.consensusScore.toFixed(2));
154
+ console.log(' Answered:', vote.totalAnswered, '/', vote.totalSkipped + vote.totalAnswered);
155
+ if (vote.sharedTerms?.length) {
156
+ console.log(' Shared key terms:', vote.sharedTerms.join(', '));
157
+ }
158
+ }
159
+
160
+ main().catch(console.error);
@@ -0,0 +1,216 @@
1
+ # A3M Router — LangChain Integration
2
+
3
+ Use **A3M Router** as a drop-in LLM provider inside LangChain chains and agents. Route every query to the cheapest capable provider with automatic fallback and optional parallel ensemble execution.
4
+
5
+ > **This is a community integration. PRs welcome!**
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @langchain/core
11
+ ```
12
+
13
+ The integration itself is a single TypeScript file. Copy it into your project:
14
+
15
+ ```bash
16
+ cp integrations/langchain/a3m_langchain.ts ./src/
17
+ ```
18
+
19
+ Or import directly from the package (when published):
20
+
21
+ ```bash
22
+ npm install adaptive-memory-multi-model-router
23
+ ```
24
+
25
+ ```typescript
26
+ import { A3MLLM } from 'adaptive-memory-multi-model-router/integrations/langchain';
27
+ ```
28
+
29
+ ## Quick Start
30
+
31
+ ```typescript
32
+ import { A3MLLM } from './a3m_langchain';
33
+
34
+ const llm = new A3MLLM({
35
+ providers: {
36
+ groq: {
37
+ name: 'Groq',
38
+ baseUrl: 'https://api.groq.com/openai/v1/chat/completions',
39
+ apiKey: process.env.GROQ_API_KEY,
40
+ models: ['llama-3.3-70b-versatile'],
41
+ tier: 'cheap',
42
+ },
43
+ openai: {
44
+ name: 'OpenAI',
45
+ baseUrl: 'https://api.openai.com/v1/chat/completions',
46
+ apiKey: process.env.OPENAI_API_KEY,
47
+ models: ['gpt-4o-mini'],
48
+ tier: 'premium',
49
+ },
50
+ },
51
+ routingStrategy: 'cheapest', // auto-pick cheapest
52
+ fallbackEnabled: true, // fall back on failure
53
+ });
54
+
55
+ const response = await llm.invoke('What is the capital of France?');
56
+ console.log(response);
57
+ ```
58
+
59
+ ## Configuration Options
60
+
61
+ | Option | Type | Default | Description |
62
+ |--------|------|---------|-------------|
63
+ | `providers` | `Record<string, A3MProviderConfig>` | (required) | Provider configurations |
64
+ | `routingStrategy` | `'cheapest' \| 'fastest' \| 'priority' \| 'random'` | `'cheapest'` | Provider selection strategy |
65
+ | `defaultModel` | `string` | `''` | Fallback model name |
66
+ | `temperature` | `number` | `0.7` | LLM temperature |
67
+ | `maxTokens` | `number` | `4096` | Max output tokens |
68
+ | `timeout` | `number` | `60000` | Request timeout (ms) |
69
+ | `fallbackEnabled` | `boolean` | `true` | Auto-fallback on failure |
70
+ | `priorityOrder` | `string[]` | `[]` | Provider priority order |
71
+ | `onRoute` | `function` | — | Route decision callback |
72
+ | `onError` | `function` | — | Error callback |
73
+
74
+ ## Provider Config Format
75
+
76
+ ```typescript
77
+ interface A3MProviderConfig {
78
+ name: string; // Human-readable name
79
+ baseUrl: string; // API endpoint URL
80
+ apiKey?: string; // API key
81
+ models: string[]; // Available models
82
+ tier: 'free' | 'cheap' | 'mid' | 'premium' | 'enterprise';
83
+ cost?: { input: number; output: number }; // $ per 1M tokens
84
+ format?: 'openai' | 'anthropic' | 'google' | 'cohere';
85
+ headers?: Record<string, string>; // Extra HTTP headers
86
+ maxTokens?: number;
87
+ }
88
+ ```
89
+
90
+ ## Routing Strategies
91
+
92
+ - **`cheapest`** — Picks the provider with the lowest combined input+output cost per 1M tokens.
93
+ - **`fastest`** — Picks providers in registration order (register fast ones first).
94
+ - **`priority`** — Uses explicit `priorityOrder` array.
95
+ - **`random`** — Random provider selection (load balancing).
96
+
97
+ ## Routing Metadata
98
+
99
+ Every `invokeWithMetadata()` call returns A3M routing details:
100
+
101
+ ```typescript
102
+ {
103
+ provider: 'groq',
104
+ model: 'llama-3.3-70b-versatile',
105
+ latencyMs: 342,
106
+ costUsd: 0.00012,
107
+ tier: 'cheap',
108
+ tokensUsed: { input: 45, output: 120, total: 165 },
109
+ ensemble: false
110
+ }
111
+ ```
112
+
113
+ ## Ensemble Mode
114
+
115
+ Run multiple providers in parallel and merge results:
116
+
117
+ ```typescript
118
+ const result = await llm.ensembleInvoke('Explain quantum computing', {
119
+ ensemble: 'longest', // 'first' | 'longest' | 'concat'
120
+ });
121
+
122
+ console.log(result.text);
123
+ console.log('Used providers:', result.metadata.ensembleProviders);
124
+ ```
125
+
126
+ **Ensemble strategies:**
127
+
128
+ | Strategy | Behavior |
129
+ |----------|----------|
130
+ | `first` | Return first response received (lowest latency) |
131
+ | `longest` | Return the most verbose response |
132
+ | `concat` | Concatenate all responses with provider headers |
133
+
134
+ ## LangChain Chain Integration
135
+
136
+ ```typescript
137
+ import { PromptTemplate } from '@langchain/core/prompts';
138
+ import { StringOutputParser } from '@langchain/core/output_parsers';
139
+
140
+ const prompt = PromptTemplate.fromTemplate(
141
+ 'Answer as a {role}: {question}',
142
+ );
143
+
144
+ const chain = prompt
145
+ .pipe(llm as any) // A3MLLM works as a runnable
146
+ .pipe(new StringOutputParser());
147
+
148
+ const response = await chain.invoke({
149
+ role: 'physicist',
150
+ question: 'Why is the sky blue?',
151
+ });
152
+ ```
153
+
154
+ ## Factory Functions
155
+
156
+ ```typescript
157
+ // Single provider
158
+ const groq = createA3MProvider('groq', {
159
+ apiKey: process.env.GROQ_API_KEY,
160
+ });
161
+
162
+ // Auto-router across providers
163
+ const router = createA3MRouter({
164
+ groq: { apiKey: process.env.GROQ_API_KEY },
165
+ openai: { apiKey: process.env.OPENAI_API_KEY },
166
+ nvidia: { apiKey: process.env.NVIDIA_API_KEY },
167
+ });
168
+
169
+ const { text, metadata } = await router.invokeWithMetadata('Hello!');
170
+ console.log('Routed to:', metadata.provider, '| Cost: $' + metadata.costUsd);
171
+ ```
172
+
173
+ ## Cost Comparison
174
+
175
+ | Scenario | Cost per 1K queries | vs All-Premium |
176
+ |----------|-------------------|----------------|
177
+ | **A3M Router** (cheapest routing) | **~$0.30** | **~82% less** |
178
+ | All-GPT-4o | $2.50 | — |
179
+ | All-Claude-3 | $3.00 | — |
180
+ | All-Mistral-Large | $0.60 | — |
181
+
182
+ Routing strategy routes simple queries (summaries, facts) to free/cheap providers and only uses premium providers for complex reasoning. Result: typical cost savings of **60-82%** with negligible quality difference on most queries.
183
+
184
+ ## Built-in Default Providers
185
+
186
+ The integration includes 12 built-in provider configs — just add your API keys:
187
+
188
+ `groq`, `openai`, `anthropic`, `deepseek`, `google`, `cerebras`, `nvidia`, `deepinfra`, `together`, `mistral`
189
+
190
+ ```typescript
191
+ import { A3MLLM, A3M_DEFAULT_PROVIDERS } from './a3m_langchain';
192
+
193
+ const llm = new A3MLLM({
194
+ providers: {
195
+ groq: { ...A3M_DEFAULT_PROVIDERS.groq, apiKey: process.env.GROQ_API_KEY },
196
+ },
197
+ });
198
+ ```
199
+
200
+ ## Files
201
+
202
+ | File | Purpose |
203
+ |------|---------|
204
+ | `a3m_langchain.ts` | Core integration — `A3MLLM` class, providers, routing, ensemble |
205
+ | `example.ts` | Runnable examples for all features |
206
+ | `README.md` | This file |
207
+
208
+ ## Requirements
209
+
210
+ - Node.js 18+
211
+ - `@langchain/core` ≥ 0.1.0 (peer dependency)
212
+ - At least one LLM provider API key
213
+
214
+ ## License
215
+
216
+ MIT — Same as A3M Router.