@sparkleideas/providers 3.5.2-patch.1
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 +574 -0
- package/package.json +70 -0
- package/src/__tests__/provider-integration.test.ts +446 -0
- package/src/__tests__/quick-test.ts +356 -0
- package/src/anthropic-provider.ts +435 -0
- package/src/base-provider.ts +596 -0
- package/src/cohere-provider.ts +423 -0
- package/src/google-provider.ts +429 -0
- package/src/index.ts +40 -0
- package/src/ollama-provider.ts +408 -0
- package/src/openai-provider.ts +490 -0
- package/src/provider-manager.ts +538 -0
- package/src/ruvector-provider.ts +721 -0
- package/src/types.ts +435 -0
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V3 Anthropic (Claude) Provider
|
|
3
|
+
*
|
|
4
|
+
* Supports Claude 3.5, 3 Opus, Sonnet, and Haiku models.
|
|
5
|
+
*
|
|
6
|
+
* @module @sparkleideas/providers/anthropic-provider
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { BaseProvider, BaseProviderOptions } from './base-provider.js';
|
|
10
|
+
import {
|
|
11
|
+
LLMProvider,
|
|
12
|
+
LLMModel,
|
|
13
|
+
LLMRequest,
|
|
14
|
+
LLMResponse,
|
|
15
|
+
LLMStreamEvent,
|
|
16
|
+
ModelInfo,
|
|
17
|
+
ProviderCapabilities,
|
|
18
|
+
HealthCheckResult,
|
|
19
|
+
AuthenticationError,
|
|
20
|
+
RateLimitError,
|
|
21
|
+
LLMProviderError,
|
|
22
|
+
} from './types.js';
|
|
23
|
+
|
|
24
|
+
interface AnthropicRequest {
|
|
25
|
+
model: string;
|
|
26
|
+
messages: Array<{
|
|
27
|
+
role: 'user' | 'assistant';
|
|
28
|
+
content: string | Array<{ type: string; text?: string; source?: unknown }>;
|
|
29
|
+
}>;
|
|
30
|
+
system?: string;
|
|
31
|
+
max_tokens: number;
|
|
32
|
+
temperature?: number;
|
|
33
|
+
top_p?: number;
|
|
34
|
+
top_k?: number;
|
|
35
|
+
stop_sequences?: string[];
|
|
36
|
+
stream?: boolean;
|
|
37
|
+
tools?: Array<{
|
|
38
|
+
name: string;
|
|
39
|
+
description: string;
|
|
40
|
+
input_schema: unknown;
|
|
41
|
+
}>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface AnthropicResponse {
|
|
45
|
+
id: string;
|
|
46
|
+
type: string;
|
|
47
|
+
role: string;
|
|
48
|
+
model: string;
|
|
49
|
+
content: Array<{ type: string; text?: string; name?: string; input?: unknown }>;
|
|
50
|
+
stop_reason: string;
|
|
51
|
+
usage: {
|
|
52
|
+
input_tokens: number;
|
|
53
|
+
output_tokens: number;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class AnthropicProvider extends BaseProvider {
|
|
58
|
+
readonly name: LLMProvider = 'anthropic';
|
|
59
|
+
readonly capabilities: ProviderCapabilities = {
|
|
60
|
+
supportedModels: [
|
|
61
|
+
'claude-3-5-sonnet-20241022',
|
|
62
|
+
'claude-3-5-sonnet-latest',
|
|
63
|
+
'claude-3-opus-20240229',
|
|
64
|
+
'claude-3-sonnet-20240229',
|
|
65
|
+
'claude-3-haiku-20240307',
|
|
66
|
+
],
|
|
67
|
+
maxContextLength: {
|
|
68
|
+
'claude-3-5-sonnet-20241022': 200000,
|
|
69
|
+
'claude-3-5-sonnet-latest': 200000,
|
|
70
|
+
'claude-3-opus-20240229': 200000,
|
|
71
|
+
'claude-3-sonnet-20240229': 200000,
|
|
72
|
+
'claude-3-haiku-20240307': 200000,
|
|
73
|
+
},
|
|
74
|
+
maxOutputTokens: {
|
|
75
|
+
'claude-3-5-sonnet-20241022': 8192,
|
|
76
|
+
'claude-3-5-sonnet-latest': 8192,
|
|
77
|
+
'claude-3-opus-20240229': 4096,
|
|
78
|
+
'claude-3-sonnet-20240229': 4096,
|
|
79
|
+
'claude-3-haiku-20240307': 4096,
|
|
80
|
+
},
|
|
81
|
+
supportsStreaming: true,
|
|
82
|
+
supportsToolCalling: true,
|
|
83
|
+
supportsSystemMessages: true,
|
|
84
|
+
supportsVision: true,
|
|
85
|
+
supportsAudio: false,
|
|
86
|
+
supportsFineTuning: false,
|
|
87
|
+
supportsEmbeddings: false,
|
|
88
|
+
supportsBatching: true,
|
|
89
|
+
rateLimit: {
|
|
90
|
+
requestsPerMinute: 1000,
|
|
91
|
+
tokensPerMinute: 100000,
|
|
92
|
+
concurrentRequests: 100,
|
|
93
|
+
},
|
|
94
|
+
pricing: {
|
|
95
|
+
'claude-3-5-sonnet-20241022': {
|
|
96
|
+
promptCostPer1k: 0.003,
|
|
97
|
+
completionCostPer1k: 0.015,
|
|
98
|
+
currency: 'USD',
|
|
99
|
+
},
|
|
100
|
+
'claude-3-5-sonnet-latest': {
|
|
101
|
+
promptCostPer1k: 0.003,
|
|
102
|
+
completionCostPer1k: 0.015,
|
|
103
|
+
currency: 'USD',
|
|
104
|
+
},
|
|
105
|
+
'claude-3-opus-20240229': {
|
|
106
|
+
promptCostPer1k: 0.015,
|
|
107
|
+
completionCostPer1k: 0.075,
|
|
108
|
+
currency: 'USD',
|
|
109
|
+
},
|
|
110
|
+
'claude-3-sonnet-20240229': {
|
|
111
|
+
promptCostPer1k: 0.003,
|
|
112
|
+
completionCostPer1k: 0.015,
|
|
113
|
+
currency: 'USD',
|
|
114
|
+
},
|
|
115
|
+
'claude-3-haiku-20240307': {
|
|
116
|
+
promptCostPer1k: 0.00025,
|
|
117
|
+
completionCostPer1k: 0.00125,
|
|
118
|
+
currency: 'USD',
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
private baseUrl: string = 'https://api.anthropic.com/v1';
|
|
124
|
+
private headers: Record<string, string> = {};
|
|
125
|
+
|
|
126
|
+
constructor(options: BaseProviderOptions) {
|
|
127
|
+
super(options);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
protected async doInitialize(): Promise<void> {
|
|
131
|
+
if (!this.config.apiKey) {
|
|
132
|
+
throw new AuthenticationError('Anthropic API key is required', 'anthropic');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
this.baseUrl = this.config.apiUrl || 'https://api.anthropic.com/v1';
|
|
136
|
+
this.headers = {
|
|
137
|
+
'x-api-key': this.config.apiKey,
|
|
138
|
+
'anthropic-version': '2023-06-01',
|
|
139
|
+
'Content-Type': 'application/json',
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
protected async doComplete(request: LLMRequest): Promise<LLMResponse> {
|
|
144
|
+
const anthropicRequest = this.buildRequest(request);
|
|
145
|
+
|
|
146
|
+
const controller = new AbortController();
|
|
147
|
+
const timeout = setTimeout(() => controller.abort(), this.config.timeout || 60000);
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
const response = await fetch(`${this.baseUrl}/messages`, {
|
|
151
|
+
method: 'POST',
|
|
152
|
+
headers: this.headers,
|
|
153
|
+
body: JSON.stringify(anthropicRequest),
|
|
154
|
+
signal: controller.signal,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
clearTimeout(timeout);
|
|
158
|
+
|
|
159
|
+
if (!response.ok) {
|
|
160
|
+
await this.handleErrorResponse(response);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const data = await response.json() as AnthropicResponse;
|
|
164
|
+
return this.transformResponse(data, request);
|
|
165
|
+
} catch (error) {
|
|
166
|
+
clearTimeout(timeout);
|
|
167
|
+
throw this.transformError(error);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
protected async *doStreamComplete(request: LLMRequest): AsyncIterable<LLMStreamEvent> {
|
|
172
|
+
const anthropicRequest = this.buildRequest(request, true);
|
|
173
|
+
|
|
174
|
+
const controller = new AbortController();
|
|
175
|
+
const timeout = setTimeout(() => controller.abort(), (this.config.timeout || 60000) * 2);
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
const response = await fetch(`${this.baseUrl}/messages`, {
|
|
179
|
+
method: 'POST',
|
|
180
|
+
headers: this.headers,
|
|
181
|
+
body: JSON.stringify(anthropicRequest),
|
|
182
|
+
signal: controller.signal,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
if (!response.ok) {
|
|
186
|
+
await this.handleErrorResponse(response);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const reader = response.body!.getReader();
|
|
190
|
+
const decoder = new TextDecoder();
|
|
191
|
+
let buffer = '';
|
|
192
|
+
let totalOutputTokens = 0;
|
|
193
|
+
let inputTokens = 0;
|
|
194
|
+
|
|
195
|
+
while (true) {
|
|
196
|
+
const { done, value } = await reader.read();
|
|
197
|
+
if (done) break;
|
|
198
|
+
|
|
199
|
+
buffer += decoder.decode(value, { stream: true });
|
|
200
|
+
const lines = buffer.split('\n');
|
|
201
|
+
buffer = lines.pop() || '';
|
|
202
|
+
|
|
203
|
+
for (const line of lines) {
|
|
204
|
+
if (line.startsWith('data: ')) {
|
|
205
|
+
const data = line.slice(6);
|
|
206
|
+
if (data === '[DONE]') continue;
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
const event = JSON.parse(data);
|
|
210
|
+
|
|
211
|
+
if (event.type === 'content_block_delta' && event.delta?.text) {
|
|
212
|
+
yield {
|
|
213
|
+
type: 'content',
|
|
214
|
+
delta: { content: event.delta.text },
|
|
215
|
+
};
|
|
216
|
+
} else if (event.type === 'message_delta' && event.usage) {
|
|
217
|
+
totalOutputTokens = event.usage.output_tokens;
|
|
218
|
+
} else if (event.type === 'message_start' && event.message?.usage) {
|
|
219
|
+
inputTokens = event.message.usage.input_tokens;
|
|
220
|
+
} else if (event.type === 'message_stop') {
|
|
221
|
+
const model = request.model || this.config.model;
|
|
222
|
+
const pricing = this.capabilities.pricing[model];
|
|
223
|
+
|
|
224
|
+
const promptCost = (inputTokens / 1000) * pricing.promptCostPer1k;
|
|
225
|
+
const completionCost = (totalOutputTokens / 1000) * pricing.completionCostPer1k;
|
|
226
|
+
|
|
227
|
+
yield {
|
|
228
|
+
type: 'done',
|
|
229
|
+
usage: {
|
|
230
|
+
promptTokens: inputTokens,
|
|
231
|
+
completionTokens: totalOutputTokens,
|
|
232
|
+
totalTokens: inputTokens + totalOutputTokens,
|
|
233
|
+
},
|
|
234
|
+
cost: {
|
|
235
|
+
promptCost,
|
|
236
|
+
completionCost,
|
|
237
|
+
totalCost: promptCost + completionCost,
|
|
238
|
+
currency: 'USD',
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
} catch {
|
|
243
|
+
// Ignore parse errors
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
} catch (error) {
|
|
249
|
+
clearTimeout(timeout);
|
|
250
|
+
throw this.transformError(error);
|
|
251
|
+
} finally {
|
|
252
|
+
clearTimeout(timeout);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async listModels(): Promise<LLMModel[]> {
|
|
257
|
+
return this.capabilities.supportedModels;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async getModelInfo(model: LLMModel): Promise<ModelInfo> {
|
|
261
|
+
const descriptions: Record<string, string> = {
|
|
262
|
+
'claude-3-5-sonnet-20241022': 'Latest Claude 3.5 Sonnet - Best balance of intelligence and speed',
|
|
263
|
+
'claude-3-5-sonnet-latest': 'Claude 3.5 Sonnet latest version',
|
|
264
|
+
'claude-3-opus-20240229': 'Most capable Claude model for complex tasks',
|
|
265
|
+
'claude-3-sonnet-20240229': 'Balanced Claude 3 model',
|
|
266
|
+
'claude-3-haiku-20240307': 'Fastest Claude 3 model for simple tasks',
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
model,
|
|
271
|
+
name: model,
|
|
272
|
+
description: descriptions[model] || 'Anthropic Claude model',
|
|
273
|
+
contextLength: this.capabilities.maxContextLength[model] || 200000,
|
|
274
|
+
maxOutputTokens: this.capabilities.maxOutputTokens[model] || 4096,
|
|
275
|
+
supportedFeatures: ['chat', 'completion', 'vision', 'tool_calling'],
|
|
276
|
+
pricing: this.capabilities.pricing[model],
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
protected async doHealthCheck(): Promise<HealthCheckResult> {
|
|
281
|
+
try {
|
|
282
|
+
// Use a minimal request to check API availability
|
|
283
|
+
const response = await fetch(`${this.baseUrl}/messages`, {
|
|
284
|
+
method: 'POST',
|
|
285
|
+
headers: this.headers,
|
|
286
|
+
body: JSON.stringify({
|
|
287
|
+
model: this.config.model,
|
|
288
|
+
max_tokens: 1,
|
|
289
|
+
messages: [{ role: 'user', content: 'Hi' }],
|
|
290
|
+
}),
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
healthy: response.ok,
|
|
295
|
+
timestamp: new Date(),
|
|
296
|
+
...(response.ok ? {} : { error: `HTTP ${response.status}` }),
|
|
297
|
+
};
|
|
298
|
+
} catch (error) {
|
|
299
|
+
return {
|
|
300
|
+
healthy: false,
|
|
301
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
302
|
+
timestamp: new Date(),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private buildRequest(request: LLMRequest, stream = false): AnthropicRequest {
|
|
308
|
+
// Extract system message
|
|
309
|
+
const systemMessage = request.messages.find((m) => m.role === 'system');
|
|
310
|
+
const otherMessages = request.messages.filter((m) => m.role !== 'system');
|
|
311
|
+
|
|
312
|
+
// Transform messages
|
|
313
|
+
const messages = otherMessages.map((msg) => ({
|
|
314
|
+
role: msg.role as 'user' | 'assistant',
|
|
315
|
+
content: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content),
|
|
316
|
+
}));
|
|
317
|
+
|
|
318
|
+
const anthropicRequest: AnthropicRequest = {
|
|
319
|
+
model: request.model || this.config.model,
|
|
320
|
+
messages,
|
|
321
|
+
max_tokens: request.maxTokens || this.config.maxTokens || 4096,
|
|
322
|
+
stream,
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
if (systemMessage) {
|
|
326
|
+
anthropicRequest.system = typeof systemMessage.content === 'string'
|
|
327
|
+
? systemMessage.content
|
|
328
|
+
: JSON.stringify(systemMessage.content);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (request.temperature !== undefined) {
|
|
332
|
+
anthropicRequest.temperature = request.temperature;
|
|
333
|
+
} else if (this.config.temperature !== undefined) {
|
|
334
|
+
anthropicRequest.temperature = this.config.temperature;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (request.topP !== undefined || this.config.topP !== undefined) {
|
|
338
|
+
anthropicRequest.top_p = request.topP ?? this.config.topP;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (request.topK !== undefined || this.config.topK !== undefined) {
|
|
342
|
+
anthropicRequest.top_k = request.topK ?? this.config.topK;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (request.stopSequences || this.config.stopSequences) {
|
|
346
|
+
anthropicRequest.stop_sequences = request.stopSequences || this.config.stopSequences;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Add tools if present
|
|
350
|
+
if (request.tools) {
|
|
351
|
+
anthropicRequest.tools = request.tools.map((tool) => ({
|
|
352
|
+
name: tool.function.name,
|
|
353
|
+
description: tool.function.description,
|
|
354
|
+
input_schema: tool.function.parameters,
|
|
355
|
+
}));
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return anthropicRequest;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
private transformResponse(data: AnthropicResponse, request: LLMRequest): LLMResponse {
|
|
362
|
+
const model = request.model || this.config.model;
|
|
363
|
+
const pricing = this.capabilities.pricing[model];
|
|
364
|
+
|
|
365
|
+
const promptCost = (data.usage.input_tokens / 1000) * pricing.promptCostPer1k;
|
|
366
|
+
const completionCost = (data.usage.output_tokens / 1000) * pricing.completionCostPer1k;
|
|
367
|
+
|
|
368
|
+
// Extract text content
|
|
369
|
+
const textContent = data.content
|
|
370
|
+
.filter((c) => c.type === 'text')
|
|
371
|
+
.map((c) => c.text)
|
|
372
|
+
.join('');
|
|
373
|
+
|
|
374
|
+
// Extract tool calls
|
|
375
|
+
const toolCalls = data.content
|
|
376
|
+
.filter((c) => c.type === 'tool_use')
|
|
377
|
+
.map((c) => ({
|
|
378
|
+
id: `tool_${Date.now()}`,
|
|
379
|
+
type: 'function' as const,
|
|
380
|
+
function: {
|
|
381
|
+
name: c.name || '',
|
|
382
|
+
arguments: JSON.stringify(c.input || {}),
|
|
383
|
+
},
|
|
384
|
+
}));
|
|
385
|
+
|
|
386
|
+
return {
|
|
387
|
+
id: data.id,
|
|
388
|
+
model: model as LLMModel,
|
|
389
|
+
provider: 'anthropic',
|
|
390
|
+
content: textContent,
|
|
391
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
392
|
+
usage: {
|
|
393
|
+
promptTokens: data.usage.input_tokens,
|
|
394
|
+
completionTokens: data.usage.output_tokens,
|
|
395
|
+
totalTokens: data.usage.input_tokens + data.usage.output_tokens,
|
|
396
|
+
},
|
|
397
|
+
cost: {
|
|
398
|
+
promptCost,
|
|
399
|
+
completionCost,
|
|
400
|
+
totalCost: promptCost + completionCost,
|
|
401
|
+
currency: 'USD',
|
|
402
|
+
},
|
|
403
|
+
finishReason: data.stop_reason === 'end_turn' ? 'stop' : 'length',
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
private async handleErrorResponse(response: Response): Promise<never> {
|
|
408
|
+
const errorText = await response.text();
|
|
409
|
+
let errorData: { error?: { message?: string } };
|
|
410
|
+
|
|
411
|
+
try {
|
|
412
|
+
errorData = JSON.parse(errorText);
|
|
413
|
+
} catch {
|
|
414
|
+
errorData = { error: { message: errorText } };
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const message = errorData.error?.message || 'Unknown error';
|
|
418
|
+
|
|
419
|
+
switch (response.status) {
|
|
420
|
+
case 401:
|
|
421
|
+
throw new AuthenticationError(message, 'anthropic', errorData);
|
|
422
|
+
case 429:
|
|
423
|
+
throw new RateLimitError(message, 'anthropic', undefined, errorData);
|
|
424
|
+
default:
|
|
425
|
+
throw new LLMProviderError(
|
|
426
|
+
message,
|
|
427
|
+
`ANTHROPIC_${response.status}`,
|
|
428
|
+
'anthropic',
|
|
429
|
+
response.status,
|
|
430
|
+
response.status >= 500,
|
|
431
|
+
errorData
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|