adaptive-memory-multi-model-router 2.15.3 → 2.16.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/CHANGELOG.md +7 -1
- package/README_ja.md +2 -2
- package/README_zh.md +1 -1
- package/apps/cost-calculator/README.md +72 -0
- package/apps/cost-calculator/calculator.css +280 -0
- package/apps/cost-calculator/calculator.js +150 -0
- package/apps/cost-calculator/index.html +321 -0
- package/apps/cost-calculator/package.json +13 -0
- package/articles/ANNOUNCEMENT_reddit_ml.md +76 -0
- package/articles/ANNOUNCEMENT_vc/347/244/276/345/214/272.md +71 -0
- package/articles/ANNOUNCEMENT_vercel.md +85 -0
- package/dist/providers/providerConfig.d.ts +5 -1
- package/dist/providers/providerConfig.js +1006 -1
- package/dist/providers/providerConfig.js.map +1 -1
- package/docs/llms.txt +3 -3
- package/package.json +2 -2
- package/packages/a3m-vercel-ai/README.md +161 -0
- package/packages/a3m-vercel-ai/dist/a3m-language-model.d.ts +12 -0
- package/packages/a3m-vercel-ai/dist/a3m-language-model.d.ts.map +1 -0
- package/packages/a3m-vercel-ai/dist/a3m-language-model.js +289 -0
- package/packages/a3m-vercel-ai/dist/a3m-language-model.js.map +1 -0
- package/packages/a3m-vercel-ai/dist/index.d.ts +82 -0
- package/packages/a3m-vercel-ai/dist/index.d.ts.map +1 -0
- package/packages/a3m-vercel-ai/dist/index.js +79 -0
- package/packages/a3m-vercel-ai/dist/index.js.map +1 -0
- package/packages/a3m-vercel-ai/dist/types.d.ts +97 -0
- package/packages/a3m-vercel-ai/dist/types.d.ts.map +1 -0
- package/packages/a3m-vercel-ai/dist/types.js +5 -0
- package/packages/a3m-vercel-ai/dist/types.js.map +1 -0
- package/packages/a3m-vercel-ai/package-lock.json +969 -0
- package/packages/a3m-vercel-ai/package.json +46 -0
- package/packages/a3m-vercel-ai/src/a3m-language-model.ts +381 -0
- package/packages/a3m-vercel-ai/src/index.ts +104 -0
- package/packages/a3m-vercel-ai/src/types.ts +116 -0
- package/packages/a3m-vercel-ai/tsconfig.json +20 -0
- package/src/providers/providerConfig.ts +1053 -1
- package/summary.txt +38 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "a3m-vercel-ai",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A3M Router provider for Vercel AI SDK - intelligent cost-based routing with parallel execution",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc",
|
|
21
|
+
"typecheck": "tsc --noEmit",
|
|
22
|
+
"prepublishOnly": "npm run build"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"ai": "^3.0.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^20.0.0",
|
|
30
|
+
"ai": "^3.0.0",
|
|
31
|
+
"typescript": "^5.0.0"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"vercel",
|
|
35
|
+
"ai",
|
|
36
|
+
"sdk",
|
|
37
|
+
"router",
|
|
38
|
+
"llm",
|
|
39
|
+
"cost-optimization",
|
|
40
|
+
"a3m"
|
|
41
|
+
],
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "https://github.com/Das-rebel/a3m-router"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A3M Router Language Model for Vercel AI SDK (v3)
|
|
3
|
+
*
|
|
4
|
+
* Implements the LanguageModelV1 interface from @ai-sdk/provider
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
LanguageModelV1,
|
|
9
|
+
LanguageModelV1CallOptions,
|
|
10
|
+
LanguageModelV1CallWarning,
|
|
11
|
+
LanguageModelV1FinishReason,
|
|
12
|
+
LanguageModelV1ProviderMetadata,
|
|
13
|
+
LanguageModelV1StreamPart,
|
|
14
|
+
LanguageModelV1FunctionToolCall,
|
|
15
|
+
} from '@ai-sdk/provider';
|
|
16
|
+
import type { A3MRouterConfig } from './types';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Convert Vercel AI SDK prompt format to OpenAI format for A3M
|
|
20
|
+
*/
|
|
21
|
+
function convertMessages(
|
|
22
|
+
prompt: LanguageModelV1CallOptions['prompt']
|
|
23
|
+
): Array<{ role: 'user' | 'assistant' | 'system'; content: string }> {
|
|
24
|
+
const result: Array<{ role: 'user' | 'assistant' | 'system'; content: string }> = [];
|
|
25
|
+
|
|
26
|
+
// Handle string prompts
|
|
27
|
+
if (typeof prompt === 'string') {
|
|
28
|
+
return [{ role: 'user', content: prompt }];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// messages is an array of prompt parts
|
|
32
|
+
for (const part of prompt) {
|
|
33
|
+
if (part.role === 'system') {
|
|
34
|
+
result.push({ role: 'system', content: part.content });
|
|
35
|
+
} else if (part.role === 'user') {
|
|
36
|
+
if (typeof part.content === 'string') {
|
|
37
|
+
result.push({ role: 'user', content: part.content });
|
|
38
|
+
} else if (Array.isArray(part.content)) {
|
|
39
|
+
const textContent = part.content
|
|
40
|
+
.filter((p): p is { type: 'text'; text: string } => p.type === 'text')
|
|
41
|
+
.map(p => p.text)
|
|
42
|
+
.join('\n');
|
|
43
|
+
result.push({ role: 'user', content: textContent });
|
|
44
|
+
}
|
|
45
|
+
} else if (part.role === 'assistant') {
|
|
46
|
+
if (typeof part.content === 'string') {
|
|
47
|
+
result.push({ role: 'assistant', content: part.content });
|
|
48
|
+
} else if (Array.isArray(part.content)) {
|
|
49
|
+
const textContent = part.content
|
|
50
|
+
.filter((p): p is { type: 'text'; text: string } => p.type === 'text')
|
|
51
|
+
.map(p => p.text)
|
|
52
|
+
.join('\n');
|
|
53
|
+
result.push({ role: 'assistant', content: textContent });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Create an A3M Router Language Model for Vercel AI SDK
|
|
63
|
+
*/
|
|
64
|
+
export function createA3MLanguageModel(config: A3MRouterConfig = {}) {
|
|
65
|
+
const baseURL = config.baseURL || 'http://localhost:8787';
|
|
66
|
+
const apiKey = config.apiKey || 'not-needed';
|
|
67
|
+
|
|
68
|
+
const model: LanguageModelV1 = {
|
|
69
|
+
specificationVersion: 'v1',
|
|
70
|
+
provider: 'a3m',
|
|
71
|
+
modelId: 'auto',
|
|
72
|
+
defaultObjectGenerationMode: undefined,
|
|
73
|
+
|
|
74
|
+
async doGenerate(options: LanguageModelV1CallOptions): Promise<{
|
|
75
|
+
text?: string;
|
|
76
|
+
toolCalls?: LanguageModelV1FunctionToolCall[];
|
|
77
|
+
finishReason: LanguageModelV1FinishReason;
|
|
78
|
+
usage: {
|
|
79
|
+
promptTokens: number;
|
|
80
|
+
completionTokens: number;
|
|
81
|
+
totalTokens: number;
|
|
82
|
+
};
|
|
83
|
+
rawCall: {
|
|
84
|
+
rawPrompt: unknown;
|
|
85
|
+
rawSettings: Record<string, unknown>;
|
|
86
|
+
};
|
|
87
|
+
warnings?: LanguageModelV1CallWarning[];
|
|
88
|
+
providerMetadata?: LanguageModelV1ProviderMetadata;
|
|
89
|
+
}> {
|
|
90
|
+
const messages = convertMessages(options.prompt);
|
|
91
|
+
|
|
92
|
+
// Extract system prompt if present
|
|
93
|
+
const systemMessages = messages.filter(m => m.role === 'system');
|
|
94
|
+
const nonSystemMessages = messages.filter(m => m.role !== 'system');
|
|
95
|
+
const system = systemMessages.map(m => m.content).join('\n');
|
|
96
|
+
|
|
97
|
+
// Build the request body for A3M
|
|
98
|
+
const requestBody: Record<string, unknown> = {
|
|
99
|
+
model: 'auto',
|
|
100
|
+
messages: nonSystemMessages.length > 0 ? nonSystemMessages : [{ role: 'user', content: ' ' }],
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
if (system) {
|
|
104
|
+
requestBody.system = system;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (options.temperature !== undefined) {
|
|
108
|
+
requestBody.temperature = options.temperature;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (options.maxTokens !== undefined) {
|
|
112
|
+
requestBody.max_tokens = options.maxTokens;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (options.topP !== undefined) {
|
|
116
|
+
requestBody.top_p = options.topP;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (options.stopSequences !== undefined) {
|
|
120
|
+
requestBody.stop = options.stopSequences;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (config.parallelEnsemble) {
|
|
124
|
+
requestBody.parallel = config.parallelCount || 3;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (config.stealth) {
|
|
128
|
+
requestBody.stealth = true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
// Call A3M Router
|
|
133
|
+
const response = await fetch(`${baseURL}/v1/chat/completions`, {
|
|
134
|
+
method: 'POST',
|
|
135
|
+
headers: {
|
|
136
|
+
'Content-Type': 'application/json',
|
|
137
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
138
|
+
},
|
|
139
|
+
body: JSON.stringify(requestBody),
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
if (!response.ok) {
|
|
143
|
+
const error = await response.text();
|
|
144
|
+
throw new Error(`A3M Router error: ${response.status} ${error}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const data = await response.json() as {
|
|
148
|
+
choices: Array<{
|
|
149
|
+
message: {
|
|
150
|
+
content?: string;
|
|
151
|
+
tool_calls?: Array<{
|
|
152
|
+
id: string;
|
|
153
|
+
function: { name: string; arguments: string };
|
|
154
|
+
}>;
|
|
155
|
+
};
|
|
156
|
+
finish_reason: string;
|
|
157
|
+
}>;
|
|
158
|
+
usage?: {
|
|
159
|
+
prompt_tokens: number;
|
|
160
|
+
completion_tokens: number;
|
|
161
|
+
total_tokens: number;
|
|
162
|
+
};
|
|
163
|
+
_meta?: {
|
|
164
|
+
provider: string;
|
|
165
|
+
cost: number;
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const choice = data.choices[0];
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
text: choice.message.content || undefined,
|
|
173
|
+
toolCalls: choice.message.tool_calls?.map(tc => ({
|
|
174
|
+
toolCallType: 'function' as const,
|
|
175
|
+
toolCallId: tc.id,
|
|
176
|
+
toolName: tc.function.name,
|
|
177
|
+
args: tc.function.arguments,
|
|
178
|
+
})),
|
|
179
|
+
finishReason: (choice.finish_reason as LanguageModelV1FinishReason) || 'stop',
|
|
180
|
+
usage: {
|
|
181
|
+
promptTokens: data.usage?.prompt_tokens || 0,
|
|
182
|
+
completionTokens: data.usage?.completion_tokens || 0,
|
|
183
|
+
totalTokens: data.usage?.total_tokens || 0,
|
|
184
|
+
},
|
|
185
|
+
rawCall: {
|
|
186
|
+
rawPrompt: messages,
|
|
187
|
+
rawSettings: requestBody,
|
|
188
|
+
},
|
|
189
|
+
providerMetadata: data._meta ? {
|
|
190
|
+
a3m: {
|
|
191
|
+
provider: data._meta.provider,
|
|
192
|
+
cost: data._meta.cost,
|
|
193
|
+
},
|
|
194
|
+
} : undefined,
|
|
195
|
+
};
|
|
196
|
+
} catch (error) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
`A3M Router generation failed: ${error instanceof Error ? error.message : String(error)}`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
doStream(options: LanguageModelV1CallOptions) {
|
|
204
|
+
const messages = convertMessages(options.prompt);
|
|
205
|
+
|
|
206
|
+
// Extract system prompt if present
|
|
207
|
+
const systemMessages = messages.filter(m => m.role === 'system');
|
|
208
|
+
const nonSystemMessages = messages.filter(m => m.role !== 'system');
|
|
209
|
+
const system = systemMessages.map(m => m.content).join('\n');
|
|
210
|
+
|
|
211
|
+
const requestBody: Record<string, unknown> = {
|
|
212
|
+
model: 'auto',
|
|
213
|
+
messages: nonSystemMessages.length > 0 ? nonSystemMessages : [{ role: 'user', content: ' ' }],
|
|
214
|
+
stream: true,
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
if (system) {
|
|
218
|
+
requestBody.system = system;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (options.temperature !== undefined) {
|
|
222
|
+
requestBody.temperature = options.temperature;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (options.maxTokens !== undefined) {
|
|
226
|
+
requestBody.max_tokens = options.maxTokens;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (config.parallelEnsemble) {
|
|
230
|
+
requestBody.parallel = config.parallelCount || 3;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (config.stealth) {
|
|
234
|
+
requestBody.stealth = true;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
let attemptCount = 0;
|
|
238
|
+
const maxAttempts = 3;
|
|
239
|
+
|
|
240
|
+
const doStream = async (): Promise<{
|
|
241
|
+
stream: ReadableStream<LanguageModelV1StreamPart>;
|
|
242
|
+
rawCall: { rawPrompt: unknown; rawSettings: Record<string, unknown> };
|
|
243
|
+
}> => {
|
|
244
|
+
attemptCount++;
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
const response = await fetch(`${baseURL}/v1/chat/completions`, {
|
|
248
|
+
method: 'POST',
|
|
249
|
+
headers: {
|
|
250
|
+
'Content-Type': 'application/json',
|
|
251
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
252
|
+
},
|
|
253
|
+
body: JSON.stringify(requestBody),
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
if (!response.ok) {
|
|
257
|
+
const error = await response.text();
|
|
258
|
+
throw new Error(`A3M Router streaming error: ${response.status} ${error}`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (!response.body) {
|
|
262
|
+
throw new Error('A3M Router: no response body');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const stream = new ReadableStream<LanguageModelV1StreamPart>({
|
|
266
|
+
async start(controller) {
|
|
267
|
+
const reader = response.body!.getReader();
|
|
268
|
+
const decoder = new TextDecoder();
|
|
269
|
+
let buffer = '';
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
while (true) {
|
|
273
|
+
const { done, value } = await reader.read();
|
|
274
|
+
|
|
275
|
+
if (done) {
|
|
276
|
+
controller.close();
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
buffer += decoder.decode(value, { stream: true });
|
|
281
|
+
const lines = buffer.split('\n');
|
|
282
|
+
buffer = lines.pop() || '';
|
|
283
|
+
|
|
284
|
+
for (const line of lines) {
|
|
285
|
+
if (line.startsWith('data: ')) {
|
|
286
|
+
const data = line.slice(6);
|
|
287
|
+
|
|
288
|
+
if (data === '[DONE]') {
|
|
289
|
+
controller.close();
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
const parsed = JSON.parse(data);
|
|
295
|
+
|
|
296
|
+
// Handle chat completion chunks
|
|
297
|
+
if (parsed.choices?.[0]?.delta?.content) {
|
|
298
|
+
const chunk = parsed.choices[0].delta.content;
|
|
299
|
+
controller.enqueue({
|
|
300
|
+
type: 'text-delta',
|
|
301
|
+
textDelta: chunk,
|
|
302
|
+
} as LanguageModelV1StreamPart);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Handle tool call start
|
|
306
|
+
if (parsed.choices?.[0]?.delta?.tool_calls?.[0]) {
|
|
307
|
+
const toolCall = parsed.choices[0].delta.tool_calls[0];
|
|
308
|
+
if (toolCall.id) {
|
|
309
|
+
controller.enqueue({
|
|
310
|
+
type: 'tool-call',
|
|
311
|
+
toolCallType: 'function',
|
|
312
|
+
toolCallId: toolCall.id,
|
|
313
|
+
toolName: toolCall.function?.name || '',
|
|
314
|
+
args: toolCall.function?.arguments || '',
|
|
315
|
+
} as LanguageModelV1StreamPart);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Handle tool call delta
|
|
320
|
+
if (parsed.choices?.[0]?.delta?.tool_calls?.[0]?.function?.arguments) {
|
|
321
|
+
const argsDelta = parsed.choices[0].delta.tool_calls[0].function.arguments;
|
|
322
|
+
if (argsDelta) {
|
|
323
|
+
controller.enqueue({
|
|
324
|
+
type: 'tool-call-delta',
|
|
325
|
+
toolCallType: 'function',
|
|
326
|
+
toolCallId: parsed.choices[0].delta.tool_calls[0].id,
|
|
327
|
+
toolName: parsed.choices[0].delta.tool_calls[0].function?.name || '',
|
|
328
|
+
argsTextDelta: argsDelta,
|
|
329
|
+
} as LanguageModelV1StreamPart);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Handle finish
|
|
334
|
+
if (parsed.choices?.[0]?.finish_reason) {
|
|
335
|
+
controller.enqueue({
|
|
336
|
+
type: 'finish',
|
|
337
|
+
finishReason: parsed.choices[0].finish_reason as LanguageModelV1FinishReason,
|
|
338
|
+
usage: {
|
|
339
|
+
promptTokens: parsed.usage?.prompt_tokens || 0,
|
|
340
|
+
completionTokens: parsed.usage?.completion_tokens || 0,
|
|
341
|
+
},
|
|
342
|
+
} as LanguageModelV1StreamPart);
|
|
343
|
+
}
|
|
344
|
+
} catch {
|
|
345
|
+
// Skip malformed JSON
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
} catch (streamError) {
|
|
351
|
+
controller.error(streamError);
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
return {
|
|
357
|
+
stream,
|
|
358
|
+
rawCall: {
|
|
359
|
+
rawPrompt: messages,
|
|
360
|
+
rawSettings: requestBody,
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
} catch (error) {
|
|
364
|
+
if (attemptCount < maxAttempts) {
|
|
365
|
+
await new Promise(resolve => setTimeout(resolve, 1000 * attemptCount));
|
|
366
|
+
return doStream();
|
|
367
|
+
}
|
|
368
|
+
throw error;
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
return new Promise((resolve, reject) => {
|
|
373
|
+
doStream()
|
|
374
|
+
.then(resolve)
|
|
375
|
+
.catch(reject);
|
|
376
|
+
});
|
|
377
|
+
},
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
return model;
|
|
381
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @a3m/vercel-ai - A3M Router Provider for Vercel AI SDK
|
|
3
|
+
*
|
|
4
|
+
* Drop-in replacement that routes to the cheapest capable provider
|
|
5
|
+
* with parallel execution, caching, and automatic fallback.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { createA3MProvider } from '@a3m/vercel-ai';
|
|
10
|
+
* import { generateText } from 'ai';
|
|
11
|
+
*
|
|
12
|
+
* const a3m = createA3MProvider();
|
|
13
|
+
*
|
|
14
|
+
* const { text } = await generateText({
|
|
15
|
+
* model: a3m('auto'),
|
|
16
|
+
* prompt: 'What is the capital of France?',
|
|
17
|
+
* });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export type { A3MRouterConfig, A3MRouteResponse, A3MStreamChunk } from './types';
|
|
22
|
+
export { createA3MLanguageModel } from './a3m-language-model';
|
|
23
|
+
|
|
24
|
+
import type { LanguageModelV1 } from '@ai-sdk/provider';
|
|
25
|
+
import type { A3MRouterConfig } from './types';
|
|
26
|
+
import { createA3MLanguageModel } from './a3m-language-model';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A3M Router provider for Vercel AI SDK
|
|
30
|
+
*/
|
|
31
|
+
export interface A3MProvider {
|
|
32
|
+
name: 'a3m';
|
|
33
|
+
/**
|
|
34
|
+
* Returns an A3M language model.
|
|
35
|
+
*
|
|
36
|
+
* @param modelId - Use 'auto' for automatic routing, or specify a model like 'gpt-4', 'claude-3', etc.
|
|
37
|
+
*/
|
|
38
|
+
(modelId: string): LanguageModelV1;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Create an A3M Router provider for Vercel AI SDK
|
|
43
|
+
*/
|
|
44
|
+
export function createA3MProvider(config: A3MRouterConfig = {}): A3MProvider {
|
|
45
|
+
const languageModel = createA3MLanguageModel(config);
|
|
46
|
+
|
|
47
|
+
const provider = Object.assign(
|
|
48
|
+
(modelId: string) => {
|
|
49
|
+
// Return the language model - A3M handles model selection internally
|
|
50
|
+
return languageModel;
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'a3m' as const,
|
|
54
|
+
// Allow accessing config for introspection
|
|
55
|
+
__config: config,
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
return provider;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Example usage with Next.js App Router
|
|
64
|
+
*
|
|
65
|
+
* ```typescript
|
|
66
|
+
* // app/api/chat/route.ts
|
|
67
|
+
* import { createA3MProvider } from '@a3m/vercel-ai';
|
|
68
|
+
* import { generateText } from 'ai';
|
|
69
|
+
*
|
|
70
|
+
* const a3m = createA3MProvider({
|
|
71
|
+
* parallelEnsemble: true,
|
|
72
|
+
* });
|
|
73
|
+
*
|
|
74
|
+
* export async function POST(req: Request) {
|
|
75
|
+
* const { messages } = await req.json();
|
|
76
|
+
*
|
|
77
|
+
* const result = await generateText({
|
|
78
|
+
* model: a3m('auto'),
|
|
79
|
+
* messages,
|
|
80
|
+
* });
|
|
81
|
+
*
|
|
82
|
+
* return Response.json(result);
|
|
83
|
+
* }
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Example usage with streaming
|
|
89
|
+
*
|
|
90
|
+
* ```typescript
|
|
91
|
+
* import { createA3MProvider } from '@a3m/vercel-ai';
|
|
92
|
+
* import { streamText } from 'ai';
|
|
93
|
+
*
|
|
94
|
+
* const a3m = createA3MProvider();
|
|
95
|
+
*
|
|
96
|
+
* const result = await streamText({
|
|
97
|
+
* model: a3m('auto'),
|
|
98
|
+
* prompt: 'Write a story about a robot...',
|
|
99
|
+
* });
|
|
100
|
+
*
|
|
101
|
+
* // Stream to response
|
|
102
|
+
* return result.toDataStreamResponse();
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A3M Router configuration for Vercel AI SDK
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface A3MRouterConfig {
|
|
6
|
+
/**
|
|
7
|
+
* A3M Router endpoint
|
|
8
|
+
* @default 'http://localhost:8787'
|
|
9
|
+
*/
|
|
10
|
+
baseURL?: string;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* API key for A3M Router
|
|
14
|
+
* @default 'not-needed' for local
|
|
15
|
+
*/
|
|
16
|
+
apiKey?: string;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Enable parallel ensemble execution
|
|
20
|
+
* Runs multiple providers and picks the best result
|
|
21
|
+
* @default false
|
|
22
|
+
*/
|
|
23
|
+
parallelEnsemble?: boolean;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Number of providers to run in parallel when parallelEnsemble is true
|
|
27
|
+
* @default 3
|
|
28
|
+
*/
|
|
29
|
+
parallelCount?: number;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Enable stealth mode (anti-detection for browser automation)
|
|
33
|
+
* @default false
|
|
34
|
+
*/
|
|
35
|
+
stealth?: boolean;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Cache configuration
|
|
39
|
+
*/
|
|
40
|
+
cache?: {
|
|
41
|
+
enabled?: boolean;
|
|
42
|
+
ttl?: number; // seconds
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Provider configuration
|
|
47
|
+
* Maps provider names to their API keys
|
|
48
|
+
*/
|
|
49
|
+
providers?: Record<string, {
|
|
50
|
+
apiKey: string;
|
|
51
|
+
/** Override the base URL for this provider */
|
|
52
|
+
baseURL?: string;
|
|
53
|
+
}>;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Cost limits per provider
|
|
57
|
+
*/
|
|
58
|
+
budgetLimits?: Record<string, {
|
|
59
|
+
maxCostPerRequest?: number;
|
|
60
|
+
maxRequestsPerMinute?: number;
|
|
61
|
+
}>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A3M Router response from /route or /generate
|
|
66
|
+
*/
|
|
67
|
+
export interface A3MRouteResponse {
|
|
68
|
+
/** The generated content */
|
|
69
|
+
content: string;
|
|
70
|
+
|
|
71
|
+
/** The provider that was selected */
|
|
72
|
+
provider: string;
|
|
73
|
+
|
|
74
|
+
/** The model that was used */
|
|
75
|
+
model: string;
|
|
76
|
+
|
|
77
|
+
/** Cost in USD */
|
|
78
|
+
cost: number;
|
|
79
|
+
|
|
80
|
+
/** Latency in ms */
|
|
81
|
+
latencyMs: number;
|
|
82
|
+
|
|
83
|
+
/** Token usage */
|
|
84
|
+
usage?: {
|
|
85
|
+
promptTokens: number;
|
|
86
|
+
completionTokens: number;
|
|
87
|
+
totalTokens: number;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/** Confidence score (0-1) */
|
|
91
|
+
confidence?: number;
|
|
92
|
+
|
|
93
|
+
/** Finish reason */
|
|
94
|
+
finishReason?: 'stop' | 'length' | 'error';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* A3M Router streaming response chunk
|
|
99
|
+
*/
|
|
100
|
+
export interface A3MStreamChunk {
|
|
101
|
+
/** The delta content */
|
|
102
|
+
delta: string;
|
|
103
|
+
|
|
104
|
+
/** Provider that responded */
|
|
105
|
+
provider: string;
|
|
106
|
+
|
|
107
|
+
/** Whether this is the final chunk */
|
|
108
|
+
done: boolean;
|
|
109
|
+
|
|
110
|
+
/** Token usage (only on final chunk) */
|
|
111
|
+
usage?: {
|
|
112
|
+
promptTokens: number;
|
|
113
|
+
completionTokens: number;
|
|
114
|
+
totalTokens: number;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2022"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"declaration": true,
|
|
10
|
+
"declarationMap": true,
|
|
11
|
+
"sourceMap": true,
|
|
12
|
+
"strict": true,
|
|
13
|
+
"esModuleInterop": true,
|
|
14
|
+
"skipLibCheck": true,
|
|
15
|
+
"forceConsistentCasingInFileNames": true,
|
|
16
|
+
"resolveJsonModule": true
|
|
17
|
+
},
|
|
18
|
+
"include": ["src/**/*"],
|
|
19
|
+
"exclude": ["node_modules", "dist"]
|
|
20
|
+
}
|