adaptive-memory-multi-model-router 1.9.4 → 2.0.0
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/.github/ISSUE_TEMPLATE/bug_report.md +50 -0
- package/.github/ISSUE_TEMPLATE/config.yml +11 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +37 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +44 -0
- package/.github/workflows/npm-stats-validation.yml +152 -0
- package/.github/workflows/pages.yml +37 -0
- package/CHANGELOG.md +122 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/CONTRIBUTING.md +110 -0
- package/LAUNCH-PAIN-DRIVEN.md +339 -0
- package/LAUNCH.md +575 -0
- package/POPULARITY_BOOSTERS.md +285 -0
- package/README.md +231 -300
- package/SECURITY.md +69 -0
- package/articles/CONTENT_STRUCTURE.md +292 -0
- package/articles/DEVTO_COST_GUIDE.md +473 -0
- package/articles/DEVTO_FINAL.md +416 -0
- package/articles/DEVTO_MULTI_PROVIDER.md +542 -0
- package/articles/HN_10X_BETTER.md +430 -0
- package/articles/HN_CHINESE_STYLE.md +308 -0
- package/articles/HN_FINAL.md +199 -0
- package/articles/HN_POSTED_VERSION.md +56 -0
- package/articles/HN_RESEARCH.md +364 -0
- package/articles/PAIN-DRIVEN-devto-v2.md +308 -0
- package/articles/PAIN-DRIVEN-devto-v3.md +268 -0
- package/articles/PAIN-DRIVEN-devto.md +242 -0
- package/articles/PAIN-DRIVEN-hackernews-v2.md +138 -0
- package/articles/PAIN-DRIVEN-hackernews-v3.md +151 -0
- package/articles/PAIN-DRIVEN-hackernews.md +131 -0
- package/articles/PAIN-DRIVEN-reddit-v2.md +301 -0
- package/articles/PAIN-DRIVEN-reddit-v3.md +236 -0
- package/articles/PAIN-DRIVEN-reddit.md +218 -0
- package/articles/PAIN-DRIVEN-twitter-v2.md +110 -0
- package/articles/PAIN-DRIVEN-twitter-v3.md +121 -0
- package/articles/PAIN-DRIVEN-twitter.md +120 -0
- package/articles/PORTKEY_VS_A3M.md +147 -0
- package/articles/REDDIT_FINAL.md +232 -0
- package/articles/TWITTER_FINAL.md +167 -0
- package/articles/WHY_10X_BETTER.md +261 -0
- package/articles/WHY_CHINESE_STYLE_BETTER.md +323 -0
- package/articles/ai-discoverability-llm-routing.md +210 -0
- package/articles/devto-llm-routing.md +109 -0
- package/articles/hackernews-show-hn.md +65 -0
- package/articles/hashnode-llm-cost-optimization.md +125 -0
- package/articles/medium-building-llm-router.md +205 -0
- package/articles/reddit-ml.md +86 -0
- package/articles/twitter-thread-cost-savings.md +98 -0
- package/articles/youtube-tutorial-script.md +262 -0
- package/assets/banner.svg +109 -0
- package/assets/logo.svg +68 -0
- package/assets/social-preview.svg +64 -0
- package/demo/demo-script.md +53 -0
- package/dist/analytics/costAnalytics.d.ts +77 -0
- package/dist/analytics/costAnalytics.d.ts.map +1 -0
- package/dist/analytics/costAnalytics.js +219 -0
- package/dist/analytics/costAnalytics.js.map +1 -0
- package/dist/cache/semanticCache.d.ts +62 -0
- package/dist/cache/semanticCache.d.ts.map +1 -0
- package/dist/cache/semanticCache.js +176 -0
- package/dist/cache/semanticCache.js.map +1 -0
- package/dist/cli.js +35 -0
- package/dist/geo/generativeEngineOptimization.js +321 -0
- package/dist/geo/geoRouter.js +387 -0
- package/dist/index.d.ts +4 -723
- package/dist/index.js +11 -344
- package/dist/index.js.map +1 -1
- package/dist/integrations/langchainAdapter.d.ts +146 -0
- package/dist/integrations/langchainAdapter.d.ts.map +1 -0
- package/dist/integrations/langchainAdapter.js +731 -0
- package/dist/integrations/langchainAdapter.js.map +1 -0
- package/dist/integrations/oauth.d.ts +69 -0
- package/dist/integrations/oauth.d.ts.map +1 -0
- package/dist/integrations/oauth.js +225 -21
- package/dist/integrations/oauth.js.map +1 -0
- package/dist/memory/autoFetch.d.ts +39 -0
- package/dist/memory/autoFetch.d.ts.map +1 -0
- package/dist/memory/autoFetch.js +80 -88
- package/dist/memory/autoFetch.js.map +1 -0
- package/dist/memory/memoryTree.d.ts +76 -0
- package/dist/memory/memoryTree.d.ts.map +1 -0
- package/dist/memory/memoryTree.js +185 -130
- package/dist/memory/memoryTree.js.map +1 -0
- package/dist/memory/obsidianVault.d.ts +71 -0
- package/dist/memory/obsidianVault.d.ts.map +1 -0
- package/dist/memory/obsidianVault.js +207 -22
- package/dist/memory/obsidianVault.js.map +1 -0
- package/dist/providers/providerConfig.d.ts +49 -0
- package/dist/providers/providerConfig.d.ts.map +1 -0
- package/dist/providers/providerConfig.js +806 -401
- package/dist/providers/providerConfig.js.map +1 -0
- package/dist/security/guardrails.d.ts +76 -0
- package/dist/security/guardrails.d.ts.map +1 -0
- package/dist/security/guardrails.js +479 -0
- package/dist/security/guardrails.js.map +1 -0
- package/dist/security/inputValidation.js +351 -0
- package/dist/server/dashboard.d.ts +58 -0
- package/dist/server/dashboard.d.ts.map +1 -0
- package/dist/server/dashboard.js +553 -0
- package/dist/server/dashboard.js.map +1 -0
- package/dist/server/modelMapper.d.ts +43 -0
- package/dist/server/modelMapper.d.ts.map +1 -0
- package/dist/server/modelMapper.js +154 -0
- package/dist/server/modelMapper.js.map +1 -0
- package/dist/server/proxyServer.d.ts +41 -0
- package/dist/server/proxyServer.d.ts.map +1 -0
- package/dist/server/proxyServer.js +932 -0
- package/dist/server/proxyServer.js.map +1 -0
- package/dist/skills/__tests__/skill_manager.test.d.ts +2 -0
- package/dist/skills/__tests__/skill_manager.test.d.ts.map +1 -0
- package/dist/skills/__tests__/skill_manager.test.js +268 -0
- package/dist/skills/__tests__/skill_manager.test.js.map +1 -0
- package/docs/geo/GENERATIVE_ENGINE_OPTIMIZATION.md +232 -0
- package/docs-site/index.html +347 -0
- package/llms.txt +138 -0
- package/package.json +72 -7
- package/playground/README.md +51 -0
- package/playground/codesandbox.json +12 -0
- package/playground/index.js +39 -0
- package/scripts/update-npm-badges.js +158 -0
- package/src/analytics/costAnalytics.ts +304 -0
- package/src/cache/semanticCache.ts +221 -0
- package/src/index.ts +6 -0
- package/src/integrations/langchainAdapter.ts +955 -0
- package/src/providers/providerConfig.ts +923 -0
- package/src/security/guardrails.ts +585 -0
- package/src/server/dashboard.ts +610 -0
- package/src/server/modelMapper.ts +182 -0
- package/src/server/proxyServer.ts +1105 -0
- package/src/types/langchain.d.ts +83 -0
- package/tsconfig.build.json +20 -0
|
@@ -0,0 +1,731 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* A3M Router - LangChain Adapter
|
|
4
|
+
*
|
|
5
|
+
* Drop-in replacement for ChatOpenAI from @langchain/openai.
|
|
6
|
+
* Routes all LLM calls through the A3M Router for cost optimization,
|
|
7
|
+
* load balancing, and intelligent provider selection.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { A3MChatModel } from 'adaptive-memory-multi-model-router/langchain';
|
|
12
|
+
* import { HumanMessage } from '@langchain/core/messages';
|
|
13
|
+
*
|
|
14
|
+
* const model = new A3MChatModel({
|
|
15
|
+
* modelName: 'auto', // or 'groq/llama-3.3-70b-versatile'
|
|
16
|
+
* temperature: 0.7,
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* const response = await model.invoke([
|
|
20
|
+
* new HumanMessage("What is 2+2?")
|
|
21
|
+
* ]);
|
|
22
|
+
*
|
|
23
|
+
* // Streaming
|
|
24
|
+
* const stream = await model.stream([
|
|
25
|
+
* new HumanMessage("Tell me a story")
|
|
26
|
+
* ]);
|
|
27
|
+
* for await (const chunk of stream) {
|
|
28
|
+
* process.stdout.write(chunk.content as string);
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* LangChain is a PEER DEPENDENCY. Install it separately:
|
|
33
|
+
* npm install @langchain/core @langchain/openai
|
|
34
|
+
*/
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.A3MChatModel = void 0;
|
|
37
|
+
exports.createA3MChatModel = createA3MChatModel;
|
|
38
|
+
exports.createAutoRoutingChatModel = createAutoRoutingChatModel;
|
|
39
|
+
// ============================================================
|
|
40
|
+
// A3M INTERNAL IMPORTS
|
|
41
|
+
// ============================================================
|
|
42
|
+
const providerConfig_js_1 = require("../providers/providerConfig.js");
|
|
43
|
+
const registry_js_1 = require("../providers/registry.js");
|
|
44
|
+
// ============================================================
|
|
45
|
+
// HELPER: Convert LangChain messages to OpenAI format
|
|
46
|
+
// ============================================================
|
|
47
|
+
function langchainMessagesToOpenAI(messages) {
|
|
48
|
+
return messages.map((msg) => {
|
|
49
|
+
const role = msg._getType();
|
|
50
|
+
let content;
|
|
51
|
+
if (typeof msg.content === 'string') {
|
|
52
|
+
content = msg.content;
|
|
53
|
+
}
|
|
54
|
+
else if (Array.isArray(msg.content)) {
|
|
55
|
+
content = msg.content.map((part) => {
|
|
56
|
+
if (typeof part === 'string')
|
|
57
|
+
return { type: 'text', text: part };
|
|
58
|
+
if (part.type === 'text')
|
|
59
|
+
return { type: 'text', text: part.text };
|
|
60
|
+
if (part.type === 'image_url')
|
|
61
|
+
return { type: 'image_url', image_url: part.image_url };
|
|
62
|
+
return part;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
content = String(msg.content);
|
|
67
|
+
}
|
|
68
|
+
const result = { role, content };
|
|
69
|
+
// Tool calls (from AIMessage with tool_calls)
|
|
70
|
+
const aiMsg = msg;
|
|
71
|
+
if (aiMsg.tool_calls && aiMsg.tool_calls.length > 0) {
|
|
72
|
+
result.tool_calls = aiMsg.tool_calls.map((tc) => ({
|
|
73
|
+
id: tc.id || `call_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
74
|
+
type: 'function',
|
|
75
|
+
function: {
|
|
76
|
+
name: tc.name,
|
|
77
|
+
arguments: typeof tc.args === 'string' ? tc.args : JSON.stringify(tc.args),
|
|
78
|
+
},
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
// Tool call ID (from ToolMessage)
|
|
82
|
+
if (aiMsg.tool_call_id) {
|
|
83
|
+
result.tool_call_id = aiMsg.tool_call_id;
|
|
84
|
+
}
|
|
85
|
+
// Name
|
|
86
|
+
if (aiMsg.name) {
|
|
87
|
+
result.name = aiMsg.name;
|
|
88
|
+
}
|
|
89
|
+
return result;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
// ============================================================
|
|
93
|
+
// HELPER: HTTP request with streaming support
|
|
94
|
+
// ============================================================
|
|
95
|
+
async function makeRequest(url, headers, body, timeout = 60000) {
|
|
96
|
+
const controller = new AbortController();
|
|
97
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
98
|
+
try {
|
|
99
|
+
const response = await fetch(url, {
|
|
100
|
+
method: 'POST',
|
|
101
|
+
headers,
|
|
102
|
+
body: JSON.stringify(body),
|
|
103
|
+
signal: controller.signal,
|
|
104
|
+
});
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
const text = await response.text();
|
|
107
|
+
throw new Error(`HTTP ${response.status}: ${text}`);
|
|
108
|
+
}
|
|
109
|
+
return await response.json();
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
clearTimeout(timer);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async function* makeStreamingRequest(url, headers, body, timeout = 120000) {
|
|
116
|
+
const controller = new AbortController();
|
|
117
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
118
|
+
try {
|
|
119
|
+
const response = await fetch(url, {
|
|
120
|
+
method: 'POST',
|
|
121
|
+
headers,
|
|
122
|
+
body: JSON.stringify(body),
|
|
123
|
+
signal: controller.signal,
|
|
124
|
+
});
|
|
125
|
+
if (!response.ok) {
|
|
126
|
+
const text = await response.text();
|
|
127
|
+
throw new Error(`HTTP ${response.status}: ${text}`);
|
|
128
|
+
}
|
|
129
|
+
const reader = response.body?.getReader();
|
|
130
|
+
if (!reader)
|
|
131
|
+
throw new Error('No response body');
|
|
132
|
+
const decoder = new TextDecoder();
|
|
133
|
+
let buffer = '';
|
|
134
|
+
while (true) {
|
|
135
|
+
const { done, value } = await reader.read();
|
|
136
|
+
if (done)
|
|
137
|
+
break;
|
|
138
|
+
buffer += decoder.decode(value, { stream: true });
|
|
139
|
+
const lines = buffer.split('\n');
|
|
140
|
+
buffer = lines.pop() || '';
|
|
141
|
+
for (const line of lines) {
|
|
142
|
+
const trimmed = line.trim();
|
|
143
|
+
if (trimmed.startsWith('data: ')) {
|
|
144
|
+
const data = trimmed.slice(6);
|
|
145
|
+
if (data === '[DONE]')
|
|
146
|
+
return;
|
|
147
|
+
yield data;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
clearTimeout(timer);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// ============================================================
|
|
157
|
+
// HELPER: Parse provider/model from modelName
|
|
158
|
+
// ============================================================
|
|
159
|
+
function parseModel(modelName) {
|
|
160
|
+
if (modelName.includes('/')) {
|
|
161
|
+
const idx = modelName.indexOf('/');
|
|
162
|
+
return {
|
|
163
|
+
provider: modelName.slice(0, idx),
|
|
164
|
+
model: modelName.slice(idx + 1),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
return { provider: modelName, model: '' };
|
|
168
|
+
}
|
|
169
|
+
// ============================================================
|
|
170
|
+
// HELPER: Resolve provider config from registry
|
|
171
|
+
// ============================================================
|
|
172
|
+
function resolveProvider(modelName, registry) {
|
|
173
|
+
(0, providerConfig_js_1.loadConfig)();
|
|
174
|
+
if (modelName === 'auto' || modelName === '') {
|
|
175
|
+
// Use registry's priority-based selection
|
|
176
|
+
const selected = registry.selectModel();
|
|
177
|
+
if (!selected)
|
|
178
|
+
return null;
|
|
179
|
+
const parsed = parseModel(selected);
|
|
180
|
+
const available = (0, providerConfig_js_1.getAvailableProviders)();
|
|
181
|
+
const prov = available[parsed.provider];
|
|
182
|
+
if (!prov)
|
|
183
|
+
return null;
|
|
184
|
+
return { providerDef: prov, model: parsed.model || prov.models[0], providerName: parsed.provider };
|
|
185
|
+
}
|
|
186
|
+
const parsed = parseModel(modelName);
|
|
187
|
+
const available = (0, providerConfig_js_1.getAvailableProviders)();
|
|
188
|
+
const prov = available[parsed.provider];
|
|
189
|
+
if (prov) {
|
|
190
|
+
return { providerDef: prov, model: parsed.model || prov.models[0], providerName: parsed.provider };
|
|
191
|
+
}
|
|
192
|
+
// Try matching by model name across all providers
|
|
193
|
+
for (const [id, providerDef] of Object.entries(available)) {
|
|
194
|
+
if (providerDef.models.includes(modelName)) {
|
|
195
|
+
return { providerDef, model: modelName, providerName: id };
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
// ============================================================
|
|
201
|
+
// HELPER: Build request URL and headers for a provider
|
|
202
|
+
// ============================================================
|
|
203
|
+
function buildRequestConfig(providerDef, apiKeyOverride, extraHeaders) {
|
|
204
|
+
const apiKey = apiKeyOverride || providerDef.apiKey || '';
|
|
205
|
+
switch (providerDef.format) {
|
|
206
|
+
case 'anthropic':
|
|
207
|
+
return {
|
|
208
|
+
url: providerDef.baseUrl,
|
|
209
|
+
headers: {
|
|
210
|
+
'Content-Type': 'application/json',
|
|
211
|
+
'x-api-key': apiKey,
|
|
212
|
+
'anthropic-version': '2023-06-01',
|
|
213
|
+
...extraHeaders,
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
case 'google':
|
|
217
|
+
return {
|
|
218
|
+
url: providerDef.baseUrl,
|
|
219
|
+
headers: {
|
|
220
|
+
'Content-Type': 'application/json',
|
|
221
|
+
...extraHeaders,
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
case 'cohere':
|
|
225
|
+
return {
|
|
226
|
+
url: providerDef.baseUrl,
|
|
227
|
+
headers: {
|
|
228
|
+
'Content-Type': 'application/json',
|
|
229
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
230
|
+
...extraHeaders,
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
case 'openai':
|
|
234
|
+
default:
|
|
235
|
+
return {
|
|
236
|
+
url: providerDef.baseUrl,
|
|
237
|
+
headers: {
|
|
238
|
+
'Content-Type': 'application/json',
|
|
239
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
240
|
+
...extraHeaders,
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// ============================================================
|
|
246
|
+
// HELPER: Build request body per provider format
|
|
247
|
+
// ============================================================
|
|
248
|
+
function buildRequestBody(providerDef, model, openaiMessages, options) {
|
|
249
|
+
switch (providerDef.format) {
|
|
250
|
+
case 'anthropic':
|
|
251
|
+
// Extract system message
|
|
252
|
+
let systemPrompt = '';
|
|
253
|
+
const nonSystemMessages = openaiMessages.filter((m) => {
|
|
254
|
+
if (m.role === 'system') {
|
|
255
|
+
systemPrompt += (typeof m.content === 'string' ? m.content : JSON.stringify(m.content)) + '\n';
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
return true;
|
|
259
|
+
});
|
|
260
|
+
return {
|
|
261
|
+
model,
|
|
262
|
+
max_tokens: options.maxTokens || 4096,
|
|
263
|
+
system: systemPrompt.trim() || undefined,
|
|
264
|
+
messages: nonSystemMessages.map((m) => ({
|
|
265
|
+
role: m.role === 'tool' ? 'user' : m.role,
|
|
266
|
+
content: typeof m.content === 'string' ? m.content : JSON.stringify(m.content),
|
|
267
|
+
})),
|
|
268
|
+
temperature: options.temperature,
|
|
269
|
+
top_p: options.topP,
|
|
270
|
+
stop_sequences: options.stop,
|
|
271
|
+
stream: options.stream || false,
|
|
272
|
+
};
|
|
273
|
+
case 'google': {
|
|
274
|
+
const systemMsg = openaiMessages.find((m) => m.role === 'system');
|
|
275
|
+
const contents = openaiMessages
|
|
276
|
+
.filter((m) => m.role !== 'system')
|
|
277
|
+
.map((m) => ({
|
|
278
|
+
role: m.role === 'assistant' ? 'model' : 'user',
|
|
279
|
+
parts: [{ text: typeof m.content === 'string' ? m.content : JSON.stringify(m.content) }],
|
|
280
|
+
}));
|
|
281
|
+
return {
|
|
282
|
+
contents,
|
|
283
|
+
systemInstruction: systemMsg ? { parts: [{ text: systemMsg.content }] } : undefined,
|
|
284
|
+
generationConfig: {
|
|
285
|
+
maxOutputTokens: options.maxTokens || 4096,
|
|
286
|
+
temperature: options.temperature,
|
|
287
|
+
topP: options.topP,
|
|
288
|
+
stopSequences: options.stop,
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
case 'openai':
|
|
293
|
+
case 'cohere':
|
|
294
|
+
default:
|
|
295
|
+
return {
|
|
296
|
+
model,
|
|
297
|
+
messages: openaiMessages,
|
|
298
|
+
temperature: options.temperature,
|
|
299
|
+
max_tokens: options.maxTokens,
|
|
300
|
+
top_p: options.topP,
|
|
301
|
+
frequency_penalty: options.frequencyPenalty,
|
|
302
|
+
presence_penalty: options.presencePenalty,
|
|
303
|
+
stop: options.stop,
|
|
304
|
+
tools: options.tools,
|
|
305
|
+
stream: options.stream || false,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// ============================================================
|
|
310
|
+
// HELPER: Parse response per provider format
|
|
311
|
+
// ============================================================
|
|
312
|
+
function parseResponse(providerDef, data) {
|
|
313
|
+
switch (providerDef.format) {
|
|
314
|
+
case 'anthropic':
|
|
315
|
+
return {
|
|
316
|
+
content: data.content?.[0]?.text || '',
|
|
317
|
+
toolCalls: data.content?.filter((c) => c.type === 'tool_use').map((tc) => ({
|
|
318
|
+
id: tc.id,
|
|
319
|
+
type: 'function',
|
|
320
|
+
function: { name: tc.name, arguments: JSON.stringify(tc.input) },
|
|
321
|
+
})),
|
|
322
|
+
usage: {
|
|
323
|
+
input_tokens: data.usage?.input_tokens || 0,
|
|
324
|
+
output_tokens: data.usage?.output_tokens || 0,
|
|
325
|
+
},
|
|
326
|
+
model: data.model || '',
|
|
327
|
+
};
|
|
328
|
+
case 'google':
|
|
329
|
+
return {
|
|
330
|
+
content: data.candidates?.[0]?.content?.parts?.[0]?.text || '',
|
|
331
|
+
usage: {
|
|
332
|
+
input_tokens: data.usageMetadata?.promptTokenCount || 0,
|
|
333
|
+
output_tokens: data.usageMetadata?.candidatesTokenCount || 0,
|
|
334
|
+
},
|
|
335
|
+
model: data.modelVersion || '',
|
|
336
|
+
};
|
|
337
|
+
case 'openai':
|
|
338
|
+
case 'cohere':
|
|
339
|
+
default:
|
|
340
|
+
return {
|
|
341
|
+
content: data.choices?.[0]?.message?.content || '',
|
|
342
|
+
toolCalls: data.choices?.[0]?.message?.tool_calls,
|
|
343
|
+
usage: {
|
|
344
|
+
input_tokens: data.usage?.prompt_tokens || 0,
|
|
345
|
+
output_tokens: data.usage?.completion_tokens || 0,
|
|
346
|
+
},
|
|
347
|
+
model: data.model || '',
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
// ============================================================
|
|
352
|
+
// MAIN: A3MChatModel
|
|
353
|
+
// ============================================================
|
|
354
|
+
class A3MChatModel {
|
|
355
|
+
// LangChain BaseChatModel compatibility fields
|
|
356
|
+
lc_namespace = ['adaptive_memory_multi_model_router', 'langchain'];
|
|
357
|
+
lc_sequential = true;
|
|
358
|
+
lc_runnable = true;
|
|
359
|
+
// Parsed options
|
|
360
|
+
modelName;
|
|
361
|
+
temperature;
|
|
362
|
+
maxTokens;
|
|
363
|
+
topP;
|
|
364
|
+
frequencyPenalty;
|
|
365
|
+
presencePenalty;
|
|
366
|
+
stop;
|
|
367
|
+
timeout;
|
|
368
|
+
apiKey;
|
|
369
|
+
baseUrl;
|
|
370
|
+
format;
|
|
371
|
+
extraHeaders;
|
|
372
|
+
verbose;
|
|
373
|
+
boundTools = [];
|
|
374
|
+
// A3M Router
|
|
375
|
+
registry;
|
|
376
|
+
constructor(options = {}) {
|
|
377
|
+
this.modelName = options.modelName || options.model || 'auto';
|
|
378
|
+
this.temperature = options.temperature ?? 0.7;
|
|
379
|
+
this.maxTokens = options.maxTokens ?? 4096;
|
|
380
|
+
this.topP = options.topP;
|
|
381
|
+
this.frequencyPenalty = options.frequencyPenalty;
|
|
382
|
+
this.presencePenalty = options.presencePenalty;
|
|
383
|
+
this.stop = options.stop;
|
|
384
|
+
this.timeout = options.timeout ?? 60000;
|
|
385
|
+
this.apiKey = options.apiKey;
|
|
386
|
+
this.baseUrl = options.baseUrl;
|
|
387
|
+
this.format = options.format;
|
|
388
|
+
this.extraHeaders = options.headers;
|
|
389
|
+
this.verbose = options.verbose ?? false;
|
|
390
|
+
// Initialize A3M Router
|
|
391
|
+
if (options.router) {
|
|
392
|
+
this.registry = options.router;
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
this.registry = new registry_js_1.ProviderRegistry();
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
// ========================================================================
|
|
399
|
+
// LangChain compatibility: property getters
|
|
400
|
+
// ========================================================================
|
|
401
|
+
get model() {
|
|
402
|
+
return this.modelName;
|
|
403
|
+
}
|
|
404
|
+
get identifyingParams() {
|
|
405
|
+
return {
|
|
406
|
+
modelName: this.modelName,
|
|
407
|
+
temperature: this.temperature,
|
|
408
|
+
maxTokens: this.maxTokens,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
get lc_aliases() {
|
|
412
|
+
return {
|
|
413
|
+
model: 'modelName',
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
// ========================================================================
|
|
417
|
+
// LangChain compatibility: serialize/deserialize
|
|
418
|
+
// ========================================================================
|
|
419
|
+
toJSON() {
|
|
420
|
+
return {
|
|
421
|
+
type: 'A3MChatModel',
|
|
422
|
+
kwargs: {
|
|
423
|
+
modelName: this.modelName,
|
|
424
|
+
temperature: this.temperature,
|
|
425
|
+
maxTokens: this.maxTokens,
|
|
426
|
+
},
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
static async deserialize(data) {
|
|
430
|
+
return new A3MChatModel(data.kwargs || {});
|
|
431
|
+
}
|
|
432
|
+
// ========================================================================
|
|
433
|
+
// Core: _generate (non-streaming)
|
|
434
|
+
// ========================================================================
|
|
435
|
+
async _generate(messages, options) {
|
|
436
|
+
const resolved = resolveProvider(this.modelName, this.registry);
|
|
437
|
+
if (!resolved) {
|
|
438
|
+
throw new Error(`A3M Router: No provider available for model "${this.modelName}". ` +
|
|
439
|
+
`Set API keys via environment variables (e.g., GROQ_API_KEY, OPENAI_API_KEY).`);
|
|
440
|
+
}
|
|
441
|
+
const { providerDef, model, providerName } = resolved;
|
|
442
|
+
const requestConfig = buildRequestConfig(providerDef, this.apiKey, this.extraHeaders);
|
|
443
|
+
const openaiMessages = langchainMessagesToOpenAI(messages);
|
|
444
|
+
const tools = options?.tools || this.boundTools;
|
|
445
|
+
const body = buildRequestBody(providerDef, model, openaiMessages, {
|
|
446
|
+
temperature: this.temperature,
|
|
447
|
+
maxTokens: this.maxTokens,
|
|
448
|
+
topP: this.topP,
|
|
449
|
+
frequencyPenalty: this.frequencyPenalty,
|
|
450
|
+
presencePenalty: this.presencePenalty,
|
|
451
|
+
stop: this.stop,
|
|
452
|
+
tools: tools.length > 0 ? tools : undefined,
|
|
453
|
+
stream: false,
|
|
454
|
+
});
|
|
455
|
+
if (this.verbose) {
|
|
456
|
+
console.log(`[A3M Router] ${providerName}/${model} -> ${requestConfig.url}`);
|
|
457
|
+
}
|
|
458
|
+
const data = await makeRequest(requestConfig.url, requestConfig.headers, body, this.timeout);
|
|
459
|
+
const parsed = parseResponse(providerDef, data);
|
|
460
|
+
// Build AIMessage-compatible response
|
|
461
|
+
const generationInfo = {
|
|
462
|
+
model: parsed.model,
|
|
463
|
+
provider: providerName,
|
|
464
|
+
};
|
|
465
|
+
if (parsed.usage) {
|
|
466
|
+
generationInfo.tokenUsage = {
|
|
467
|
+
promptTokens: parsed.usage.input_tokens,
|
|
468
|
+
completionTokens: parsed.usage.output_tokens,
|
|
469
|
+
totalTokens: parsed.usage.input_tokens + parsed.usage.output_tokens,
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
const aiMessage = {
|
|
473
|
+
content: parsed.content,
|
|
474
|
+
additional_kwargs: {},
|
|
475
|
+
response_metadata: generationInfo,
|
|
476
|
+
};
|
|
477
|
+
if (parsed.toolCalls && parsed.toolCalls.length > 0) {
|
|
478
|
+
aiMessage.tool_calls = parsed.toolCalls.map((tc) => ({
|
|
479
|
+
id: tc.id,
|
|
480
|
+
name: tc.function.name,
|
|
481
|
+
args: JSON.parse(tc.function.arguments),
|
|
482
|
+
}));
|
|
483
|
+
aiMessage.additional_kwargs.tool_calls = parsed.toolCalls;
|
|
484
|
+
}
|
|
485
|
+
return {
|
|
486
|
+
generations: [{
|
|
487
|
+
text: parsed.content,
|
|
488
|
+
message: aiMessage,
|
|
489
|
+
generationInfo,
|
|
490
|
+
}],
|
|
491
|
+
llmOutput: generationInfo,
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
// ========================================================================
|
|
495
|
+
// Core: invoke (high-level)
|
|
496
|
+
// ========================================================================
|
|
497
|
+
async invoke(input, options) {
|
|
498
|
+
const messages = typeof input === 'string'
|
|
499
|
+
? [{ _getType: () => 'human', content: input }]
|
|
500
|
+
: input;
|
|
501
|
+
const result = await this._generate(messages, options);
|
|
502
|
+
return result.generations[0]?.message;
|
|
503
|
+
}
|
|
504
|
+
// ========================================================================
|
|
505
|
+
// Core: _streamResponseChunks (streaming)
|
|
506
|
+
// ========================================================================
|
|
507
|
+
async *_streamResponseChunks(messages, options) {
|
|
508
|
+
const resolved = resolveProvider(this.modelName, this.registry);
|
|
509
|
+
if (!resolved) {
|
|
510
|
+
throw new Error(`A3M Router: No provider available for model "${this.modelName}".`);
|
|
511
|
+
}
|
|
512
|
+
const { providerDef, model, providerName } = resolved;
|
|
513
|
+
const requestConfig = buildRequestConfig(providerDef, this.apiKey, this.extraHeaders);
|
|
514
|
+
const openaiMessages = langchainMessagesToOpenAI(messages);
|
|
515
|
+
const tools = options?.tools || this.boundTools;
|
|
516
|
+
const body = buildRequestBody(providerDef, model, openaiMessages, {
|
|
517
|
+
temperature: this.temperature,
|
|
518
|
+
maxTokens: this.maxTokens,
|
|
519
|
+
topP: this.topP,
|
|
520
|
+
frequencyPenalty: this.frequencyPenalty,
|
|
521
|
+
presencePenalty: this.presencePenalty,
|
|
522
|
+
stop: this.stop,
|
|
523
|
+
tools: tools.length > 0 ? tools : undefined,
|
|
524
|
+
stream: true,
|
|
525
|
+
});
|
|
526
|
+
if (this.verbose) {
|
|
527
|
+
console.log(`[A3M Router] streaming ${providerName}/${model} -> ${requestConfig.url}`);
|
|
528
|
+
}
|
|
529
|
+
const stream = makeStreamingRequest(requestConfig.url, requestConfig.headers, body, this.timeout * 2);
|
|
530
|
+
for await (const chunk of stream) {
|
|
531
|
+
try {
|
|
532
|
+
const parsed = JSON.parse(chunk);
|
|
533
|
+
// OpenAI streaming format
|
|
534
|
+
const delta = parsed.choices?.[0]?.delta;
|
|
535
|
+
if (delta) {
|
|
536
|
+
const content = delta.content || '';
|
|
537
|
+
const toolCalls = delta.tool_calls;
|
|
538
|
+
const messageChunk = {
|
|
539
|
+
content,
|
|
540
|
+
additional_kwargs: {},
|
|
541
|
+
};
|
|
542
|
+
if (toolCalls) {
|
|
543
|
+
messageChunk.tool_call_chunks = toolCalls.map((tc) => ({
|
|
544
|
+
id: tc.id || '',
|
|
545
|
+
name: tc.function?.name || '',
|
|
546
|
+
args: tc.function?.arguments || '',
|
|
547
|
+
}));
|
|
548
|
+
}
|
|
549
|
+
yield {
|
|
550
|
+
text: content,
|
|
551
|
+
message: messageChunk,
|
|
552
|
+
generationInfo: {
|
|
553
|
+
model: parsed.model || model,
|
|
554
|
+
provider: providerName,
|
|
555
|
+
},
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
catch {
|
|
560
|
+
// Skip malformed SSE chunks
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
// ========================================================================
|
|
565
|
+
// Core: stream (high-level)
|
|
566
|
+
// ========================================================================
|
|
567
|
+
async *stream(input, options) {
|
|
568
|
+
const messages = typeof input === 'string'
|
|
569
|
+
? [{ _getType: () => 'human', content: input }]
|
|
570
|
+
: input;
|
|
571
|
+
yield* this._streamResponseChunks(messages, options);
|
|
572
|
+
}
|
|
573
|
+
// ========================================================================
|
|
574
|
+
// Tool calling support
|
|
575
|
+
// ========================================================================
|
|
576
|
+
bindTools(tools) {
|
|
577
|
+
const clone = new A3MChatModel({
|
|
578
|
+
router: this.registry,
|
|
579
|
+
modelName: this.modelName,
|
|
580
|
+
temperature: this.temperature,
|
|
581
|
+
maxTokens: this.maxTokens,
|
|
582
|
+
topP: this.topP,
|
|
583
|
+
frequencyPenalty: this.frequencyPenalty,
|
|
584
|
+
presencePenalty: this.presencePenalty,
|
|
585
|
+
stop: this.stop,
|
|
586
|
+
timeout: this.timeout,
|
|
587
|
+
apiKey: this.apiKey,
|
|
588
|
+
baseUrl: this.baseUrl,
|
|
589
|
+
format: this.format,
|
|
590
|
+
headers: this.extraHeaders,
|
|
591
|
+
verbose: this.verbose,
|
|
592
|
+
});
|
|
593
|
+
clone.boundTools = tools;
|
|
594
|
+
return clone;
|
|
595
|
+
}
|
|
596
|
+
// ========================================================================
|
|
597
|
+
// Structured output support
|
|
598
|
+
// ========================================================================
|
|
599
|
+
withStructuredOutput(schema, config) {
|
|
600
|
+
// Build a wrapper that parses the response as structured JSON
|
|
601
|
+
const self = this;
|
|
602
|
+
const schemaDef = typeof schema === 'object' && 'schema' in schema
|
|
603
|
+
? schema.schema
|
|
604
|
+
: schema;
|
|
605
|
+
const toolDef = {
|
|
606
|
+
type: 'function',
|
|
607
|
+
function: {
|
|
608
|
+
name: 'structured_output',
|
|
609
|
+
description: 'Structured output',
|
|
610
|
+
parameters: schemaDef,
|
|
611
|
+
},
|
|
612
|
+
};
|
|
613
|
+
const bound = this.bindTools([toolDef]);
|
|
614
|
+
return {
|
|
615
|
+
async invoke(input) {
|
|
616
|
+
const result = await bound.invoke(input);
|
|
617
|
+
// Try to parse tool call args, fall back to content JSON parse
|
|
618
|
+
if (result?.tool_calls?.[0]?.args) {
|
|
619
|
+
return result.tool_calls[0].args;
|
|
620
|
+
}
|
|
621
|
+
if (result?.additional_kwargs?.tool_calls?.[0]?.function?.arguments) {
|
|
622
|
+
return JSON.parse(result.additional_kwargs.tool_calls[0].function.arguments);
|
|
623
|
+
}
|
|
624
|
+
if (typeof result?.content === 'string') {
|
|
625
|
+
try {
|
|
626
|
+
const jsonMatch = result.content.match(/```json\n([\s\S]*?)\n```/) ||
|
|
627
|
+
result.content.match(/\{[\s\S]*\}/);
|
|
628
|
+
if (jsonMatch) {
|
|
629
|
+
return JSON.parse(jsonMatch[0].replace(/```json\n?/g, '').replace(/\n?```/g, ''));
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
catch {
|
|
633
|
+
// Fall through
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
throw new Error('A3M Router: Failed to parse structured output from response');
|
|
637
|
+
},
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
// ========================================================================
|
|
641
|
+
// Provider management
|
|
642
|
+
// ========================================================================
|
|
643
|
+
/**
|
|
644
|
+
* Get the list of available providers
|
|
645
|
+
*/
|
|
646
|
+
getAvailableProviders() {
|
|
647
|
+
return (0, providerConfig_js_1.getAvailableProviders)();
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Get provider status from the registry
|
|
651
|
+
*/
|
|
652
|
+
getProviderStatus() {
|
|
653
|
+
return this.registry.getStatus();
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Switch the model at runtime
|
|
657
|
+
*/
|
|
658
|
+
withModel(modelName) {
|
|
659
|
+
const clone = new A3MChatModel({
|
|
660
|
+
router: this.registry,
|
|
661
|
+
modelName,
|
|
662
|
+
temperature: this.temperature,
|
|
663
|
+
maxTokens: this.maxTokens,
|
|
664
|
+
topP: this.topP,
|
|
665
|
+
frequencyPenalty: this.frequencyPenalty,
|
|
666
|
+
presencePenalty: this.presencePenalty,
|
|
667
|
+
stop: this.stop,
|
|
668
|
+
timeout: this.timeout,
|
|
669
|
+
apiKey: this.apiKey,
|
|
670
|
+
baseUrl: this.baseUrl,
|
|
671
|
+
format: this.format,
|
|
672
|
+
headers: this.extraHeaders,
|
|
673
|
+
verbose: this.verbose,
|
|
674
|
+
});
|
|
675
|
+
clone.boundTools = [...this.boundTools];
|
|
676
|
+
return clone;
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Set temperature
|
|
680
|
+
*/
|
|
681
|
+
withTemperature(temperature) {
|
|
682
|
+
const clone = new A3MChatModel({
|
|
683
|
+
router: this.registry,
|
|
684
|
+
modelName: this.modelName,
|
|
685
|
+
temperature,
|
|
686
|
+
maxTokens: this.maxTokens,
|
|
687
|
+
topP: this.topP,
|
|
688
|
+
frequencyPenalty: this.frequencyPenalty,
|
|
689
|
+
presencePenalty: this.presencePenalty,
|
|
690
|
+
stop: this.stop,
|
|
691
|
+
timeout: this.timeout,
|
|
692
|
+
apiKey: this.apiKey,
|
|
693
|
+
baseUrl: this.baseUrl,
|
|
694
|
+
format: this.format,
|
|
695
|
+
headers: this.extraHeaders,
|
|
696
|
+
verbose: this.verbose,
|
|
697
|
+
});
|
|
698
|
+
clone.boundTools = [...this.boundTools];
|
|
699
|
+
return clone;
|
|
700
|
+
}
|
|
701
|
+
// ========================================================================
|
|
702
|
+
// LangChain event system compatibility
|
|
703
|
+
// ========================================================================
|
|
704
|
+
/** LLM type identifier for LangChain compatibility */
|
|
705
|
+
_llmType() {
|
|
706
|
+
return 'a3m-router';
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
exports.A3MChatModel = A3MChatModel;
|
|
710
|
+
// ============================================================
|
|
711
|
+
// FACTORY HELPERS
|
|
712
|
+
// ============================================================
|
|
713
|
+
/**
|
|
714
|
+
* Create an A3MChatModel pre-configured for a specific provider
|
|
715
|
+
*/
|
|
716
|
+
function createA3MChatModel(provider, options = {}) {
|
|
717
|
+
return new A3MChatModel({
|
|
718
|
+
...options,
|
|
719
|
+
modelName: provider,
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Create an A3MChatModel with automatic routing
|
|
724
|
+
*/
|
|
725
|
+
function createAutoRoutingChatModel(options = {}) {
|
|
726
|
+
return new A3MChatModel({
|
|
727
|
+
...options,
|
|
728
|
+
modelName: 'auto',
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
//# sourceMappingURL=langchainAdapter.js.map
|