adaptive-memory-multi-model-router 2.13.18 → 2.13.20
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/.dockerignore +82 -0
- package/.env.example +303 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +83 -12
- package/.github/ISSUE_TEMPLATE/config.yml +12 -6
- package/.github/ISSUE_TEMPLATE/feature_request.md +61 -10
- package/.github/PULL_REQUEST_TEMPLATE.md +53 -26
- package/.github/dependabot.yml +9 -0
- package/.github/workflows/codeql.yml +38 -0
- package/.github/workflows/npm-publish.yml +20 -0
- package/.github/workflows/stale.yml +56 -0
- package/ARCHITECTURE.md +346 -0
- package/AUDIT_REPORT.md +28 -0
- package/CHANGELOG.md +386 -22
- package/CONTRIBUTORS.md +20 -0
- package/Dockerfile +53 -0
- package/Dockerfile.proxy +33 -0
- package/PR_STATUS_REPORT.md +148 -0
- package/README.md +22 -0
- package/RUNKIT.md +83 -0
- package/_schema.html +61 -15
- package/articles/AI_AGENT_LLM_ROUTING.md +150 -0
- package/articles/FROM_ZERO_TO_10K.md +107 -0
- package/articles/LLM_BENCHMARK_DEEP_DIVE.md +153 -0
- package/articles/TWEETS_10K_DOWNLOADS.md +47 -0
- package/articles/TWEETS_BENCHMARK_FIRST.md +46 -0
- package/articles/TWEETS_MCP_PLAY.md +51 -0
- package/articles/TWEETS_SEQUENTIAL_BROKEN.md +49 -0
- package/articles/TWEETS_WHY_BUILD.md +54 -0
- package/benchmark-results.json +26 -45
- package/cli/a3m +840 -0
- package/demo/package.json +13 -0
- package/demo/public/index.html +762 -0
- package/demo/server.js +405 -0
- package/dist/cli.js +4 -0
- package/docker-compose.yml +74 -0
- package/docs/.nojekyll +0 -0
- package/docs/BENCHMARK.md +96 -22
- package/docs/_config.yml +49 -0
- package/docs/api.html +513 -0
- package/docs/benchmark.html +387 -0
- package/docs/cli-cheatsheet.md +339 -0
- package/docs/comparison.md +108 -0
- package/docs/curl-examples.md +247 -0
- package/docs/index.html +390 -99
- package/docs/openapi.yaml +1318 -0
- package/docs/quick-start.html +366 -0
- package/docs/robots.txt +1 -1
- package/docs/sitemap.xml +23 -5
- package/docs/styles.css +682 -0
- package/examples/README.md +61 -0
- package/examples/a3m-sdk.js +124 -0
- package/examples/basic-route.js +54 -0
- package/examples/chat-loop.js +202 -0
- package/examples/classify-then-route.js +102 -0
- package/examples/cost-compare.js +120 -0
- package/examples/ensemble.js +160 -0
- package/integrations/langchain/README.md +216 -0
- package/integrations/langchain/a3m_langchain.ts +1360 -0
- package/integrations/langchain/example.ts +287 -0
- package/integrations/vercel-ai-sdk/README.md +49 -0
- package/integrations/vercel-ai-sdk/a3m_provider.ts +78 -0
- package/integrations/vercel-ai-sdk/example.ts +25 -0
- package/llms-full.txt +43 -0
- package/llms.txt +9 -0
- package/mcp-server/README.md +188 -0
- package/mcp-server/package.json +29 -0
- package/mcp-server/src/index.ts +744 -0
- package/mcp-server/tsconfig.json +19 -0
- package/package.json +3 -3
- package/proxy/README.md +227 -0
- package/proxy/package-lock.json +831 -0
- package/proxy/package.json +17 -0
- package/proxy/rate-limit.js +145 -0
- package/proxy/rate-limit.test.js +311 -0
- package/proxy/server.js +970 -0
- package/scripts/banner.js +29 -0
- package/scripts/compare-providers.sh +230 -0
- package/scripts/cross_post.py +443 -0
- package/scripts/publish_fcc.py +106 -0
- package/scripts/push-to-gitee.sh +52 -0
- package/src/tui/dashboard.ts +13 -0
- package/tests/__mocks__/tokenUtils.ts +22 -0
- package/tests/memory/episodicMemory.test.ts +227 -0
- package/tests/package-lock.json +1628 -0
- package/tests/package.json +18 -0
- package/tests/routing/ensembleVoting.test.ts +236 -0
- package/tests/routing/providerRetry.test.ts +360 -0
- package/tests/routing/queryTypePresets.test.ts +206 -0
- package/tests/tsconfig.json +21 -0
- package/tests/vitest.config.ts +18 -0
- package/.env +0 -2
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
createPresetRouter,
|
|
4
|
+
getPresetForQuery,
|
|
5
|
+
DEFAULT_PRESETS,
|
|
6
|
+
} from '../../tmlpd-pi-extension/src/routing/queryTypePresets';
|
|
7
|
+
|
|
8
|
+
describe('createPresetRouter', () => {
|
|
9
|
+
it('creates a router with default presets', () => {
|
|
10
|
+
const router = createPresetRouter();
|
|
11
|
+
expect(router.presets).toBeDefined();
|
|
12
|
+
expect(router.defaultPreset).toBe('fast');
|
|
13
|
+
expect(Object.keys(router.presets).length).toBeGreaterThanOrEqual(5);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('merges custom presets with defaults', () => {
|
|
17
|
+
const router = createPresetRouter({
|
|
18
|
+
customType: {
|
|
19
|
+
name: 'Custom',
|
|
20
|
+
description: 'Custom test preset',
|
|
21
|
+
provider: 'test',
|
|
22
|
+
temperature: 0.5,
|
|
23
|
+
maxTokens: 1000,
|
|
24
|
+
ensemble: false,
|
|
25
|
+
timeoutMs: 10000,
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
expect(router.presets['customType']).toBeDefined();
|
|
30
|
+
expect(router.presets['customType'].provider).toBe('test');
|
|
31
|
+
expect(router.presets['fast']).toBeDefined(); // defaults still there
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('overrides default presets when custom has same key', () => {
|
|
35
|
+
const router = createPresetRouter({
|
|
36
|
+
fast: {
|
|
37
|
+
name: 'Custom Fast',
|
|
38
|
+
description: 'Overridden',
|
|
39
|
+
provider: 'custom',
|
|
40
|
+
temperature: 0.1,
|
|
41
|
+
maxTokens: 100,
|
|
42
|
+
ensemble: false,
|
|
43
|
+
timeoutMs: 5000,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
expect(router.presets['fast'].provider).toBe('custom');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('classify', () => {
|
|
52
|
+
const router = createPresetRouter();
|
|
53
|
+
|
|
54
|
+
it('classifies code queries', () => {
|
|
55
|
+
const queries = [
|
|
56
|
+
'write a React component with TypeScript',
|
|
57
|
+
'debug this error in the API endpoint',
|
|
58
|
+
'implement a sorting algorithm in Python',
|
|
59
|
+
'fix this bug in the npm package',
|
|
60
|
+
'how to import the class correctly',
|
|
61
|
+
];
|
|
62
|
+
for (const q of queries) {
|
|
63
|
+
expect(router.classify(q)).toBe('code');
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('classifies creative queries', () => {
|
|
68
|
+
const queries = [
|
|
69
|
+
'write a poem about AI',
|
|
70
|
+
'create a short story about a robot',
|
|
71
|
+
'compose a tweet announcing the launch',
|
|
72
|
+
'generate content for the blog post',
|
|
73
|
+
'draft an article about machine learning',
|
|
74
|
+
];
|
|
75
|
+
for (const q of queries) {
|
|
76
|
+
expect(router.classify(q)).toBe('creative');
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('classifies fast/simple queries', () => {
|
|
81
|
+
const queries = [
|
|
82
|
+
'what is 2+2',
|
|
83
|
+
'hello',
|
|
84
|
+
'yes',
|
|
85
|
+
'what time is it',
|
|
86
|
+
];
|
|
87
|
+
for (const q of queries) {
|
|
88
|
+
expect(router.classify(q)).toBe('fast');
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('classifies deep/research queries', () => {
|
|
93
|
+
const queries = [
|
|
94
|
+
'explain quantum computing and compare it with classical computing',
|
|
95
|
+
'analyze the tradeoffs between Redis and Memcached for caching',
|
|
96
|
+
'research the impact of transformer architecture on NLP',
|
|
97
|
+
'evaluate pros and cons of microservices architecture',
|
|
98
|
+
'compare the difference between SQL and NoSQL databases',
|
|
99
|
+
];
|
|
100
|
+
for (const q of queries) {
|
|
101
|
+
expect(router.classify(q)).toBe('research');
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('classifies factual queries', () => {
|
|
106
|
+
const queries = [
|
|
107
|
+
'what is the meaning of life',
|
|
108
|
+
'define machine learning',
|
|
109
|
+
'explain how does photosynthesis work',
|
|
110
|
+
'describe the water cycle',
|
|
111
|
+
];
|
|
112
|
+
for (const q of queries) {
|
|
113
|
+
expect(router.classify(q)).toBe('factual');
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('handles empty query', () => {
|
|
118
|
+
expect(router.classify('')).toBe('fast');
|
|
119
|
+
expect(router.classify(' ')).toBe('fast');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('prefers code pattern over length-based classification for technical queries', () => {
|
|
123
|
+
// Even though this is long, the "function" keyword should trigger code
|
|
124
|
+
const query = 'I need to write a function that handles async requests with proper error handling and retries';
|
|
125
|
+
expect(router.classify(query)).toBe('code');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('falls back to length-based classification for queries with no keyword match', () => {
|
|
129
|
+
const shortQuery = 'hello world';
|
|
130
|
+
expect(router.classify(shortQuery)).toBe('fast');
|
|
131
|
+
|
|
132
|
+
const mediumQuery = 'I was wondering about the weather in San Francisco during the summer months';
|
|
133
|
+
expect(router.classify(mediumQuery)).toBe('factual');
|
|
134
|
+
|
|
135
|
+
const longQuery = Array(40).fill('word').join(' ');
|
|
136
|
+
expect(router.classify(longQuery)).toBe('research');
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe('getPresetForQuery', () => {
|
|
141
|
+
it('returns preset config matching classified type', () => {
|
|
142
|
+
const router = createPresetRouter();
|
|
143
|
+
const preset = getPresetForQuery('write a poem', router);
|
|
144
|
+
expect(preset.name).toBe('Creative / Writing');
|
|
145
|
+
expect(preset.temperature).toBe(0.7);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('returns default preset when no match found', () => {
|
|
149
|
+
const router = createPresetRouter();
|
|
150
|
+
const preset = getPresetForQuery('xyzzz', router);
|
|
151
|
+
expect(preset.name).toBe('Fast Query');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('uses custom presets when provided', () => {
|
|
155
|
+
const router = createPresetRouter({
|
|
156
|
+
code: {
|
|
157
|
+
name: 'Super Coder',
|
|
158
|
+
description: 'My custom code preset',
|
|
159
|
+
provider: 'deepseek',
|
|
160
|
+
temperature: 0.1,
|
|
161
|
+
maxTokens: 5000,
|
|
162
|
+
ensemble: true,
|
|
163
|
+
timeoutMs: 60000,
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
const preset = getPresetForQuery('write a function', router);
|
|
167
|
+
expect(preset.name).toBe('Super Coder');
|
|
168
|
+
expect(preset.provider).toBe('deepseek');
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe('DEFAULT_PRESETS structure', () => {
|
|
173
|
+
it('all presets have required fields', () => {
|
|
174
|
+
for (const [key, preset] of Object.entries(DEFAULT_PRESETS)) {
|
|
175
|
+
expect(preset.name).toBeTruthy();
|
|
176
|
+
expect(preset.description).toBeTruthy();
|
|
177
|
+
expect(preset.provider).toBeTruthy();
|
|
178
|
+
expect(typeof preset.temperature).toBe('number');
|
|
179
|
+
expect(preset.temperature).toBeGreaterThanOrEqual(0);
|
|
180
|
+
expect(preset.temperature).toBeLessThanOrEqual(2);
|
|
181
|
+
expect(preset.maxTokens).toBeGreaterThan(0);
|
|
182
|
+
expect(typeof preset.ensemble).toBe('boolean');
|
|
183
|
+
expect(preset.timeoutMs).toBeGreaterThan(0);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('code preset has ensemble enabled with nvidia and groq', () => {
|
|
188
|
+
const code = DEFAULT_PRESETS['code'];
|
|
189
|
+
expect(code.ensemble).toBe(true);
|
|
190
|
+
expect(code.ensembleProviders).toContain('nvidia');
|
|
191
|
+
expect(code.ensembleProviders).toContain('groq');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('research preset has ensemble enabled with nvidia and groq', () => {
|
|
195
|
+
const research = DEFAULT_PRESETS['research'];
|
|
196
|
+
expect(research.ensemble).toBe(true);
|
|
197
|
+
expect(research.ensembleProviders).toContain('nvidia');
|
|
198
|
+
expect(research.ensembleProviders).toContain('groq');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('fast and creative presets have ensemble disabled', () => {
|
|
202
|
+
expect(DEFAULT_PRESETS['fast'].ensemble).toBe(false);
|
|
203
|
+
expect(DEFAULT_PRESETS['creative'].ensemble).toBe(false);
|
|
204
|
+
expect(DEFAULT_PRESETS['factual'].ensemble).toBe(false);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"types": ["node"],
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"allowImportingTsExtensions": true,
|
|
14
|
+
"noEmit": true
|
|
15
|
+
},
|
|
16
|
+
"include": [
|
|
17
|
+
"../tmlpd-pi-extension/src/**/*.ts",
|
|
18
|
+
"../src/**/*.ts",
|
|
19
|
+
"./**/*.ts"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
globals: true,
|
|
6
|
+
environment: 'node',
|
|
7
|
+
include: ['**/*.test.ts'],
|
|
8
|
+
exclude: ['node_modules', 'dist'],
|
|
9
|
+
testTimeout: 15_000,
|
|
10
|
+
hookTimeout: 10_000,
|
|
11
|
+
pool: 'forks',
|
|
12
|
+
poolOptions: {
|
|
13
|
+
forks: {
|
|
14
|
+
singleFork: true,
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
});
|
package/.env
DELETED