@bernierllc/ai-provider-openai 1.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/.eslintrc.js +35 -0
- package/README.md +294 -0
- package/__tests__/OpenAIProvider.test.ts +574 -0
- package/__tests__/error-handling.test.ts +315 -0
- package/__tests__/model-registry.test.ts +270 -0
- package/__tests__/openai-specific.test.ts +333 -0
- package/dist/OpenAIProvider.d.ts +22 -0
- package/dist/OpenAIProvider.d.ts.map +1 -0
- package/dist/OpenAIProvider.js +320 -0
- package/dist/OpenAIProvider.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/models/model-registry.d.ts +8 -0
- package/dist/models/model-registry.d.ts.map +1 -0
- package/dist/models/model-registry.js +147 -0
- package/dist/models/model-registry.js.map +1 -0
- package/dist/types/openai-types.d.ts +28 -0
- package/dist/types/openai-types.d.ts.map +1 -0
- package/dist/types/openai-types.js +3 -0
- package/dist/types/openai-types.js.map +1 -0
- package/dist/utils/error-handling.d.ts +5 -0
- package/dist/utils/error-handling.d.ts.map +1 -0
- package/dist/utils/error-handling.js +67 -0
- package/dist/utils/error-handling.js.map +1 -0
- package/jest.config.cjs +29 -0
- package/package.json +63 -0
- package/src/OpenAIProvider.ts +435 -0
- package/src/index.ts +12 -0
- package/src/models/model-registry.ts +178 -0
- package/src/types/openai-types.ts +51 -0
- package/src/utils/error-handling.ts +101 -0
- package/tsconfig.json +31 -0
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code only within the scope of the project it was delivered for.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
AIProvider,
|
|
11
|
+
CompletionRequest,
|
|
12
|
+
CompletionResponse,
|
|
13
|
+
StreamChunk,
|
|
14
|
+
EmbeddingRequest,
|
|
15
|
+
EmbeddingResponse,
|
|
16
|
+
ModerationResponse,
|
|
17
|
+
ModelInfo,
|
|
18
|
+
HealthStatus,
|
|
19
|
+
CostEstimate
|
|
20
|
+
} from '@bernierllc/ai-provider-core';
|
|
21
|
+
import OpenAI from 'openai';
|
|
22
|
+
import { OpenAIProviderConfig, OpenAIFunction } from './types/openai-types';
|
|
23
|
+
import { OPENAI_MODELS } from './models/model-registry';
|
|
24
|
+
import { handleOpenAIError } from './utils/error-handling';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* OpenAI Provider Implementation
|
|
28
|
+
* Implements the unified AI provider interface for OpenAI's API
|
|
29
|
+
*/
|
|
30
|
+
export class OpenAIProvider extends AIProvider {
|
|
31
|
+
private client: OpenAI;
|
|
32
|
+
private models: Map<string, ModelInfo> = new Map();
|
|
33
|
+
|
|
34
|
+
constructor(config: OpenAIProviderConfig) {
|
|
35
|
+
super(config);
|
|
36
|
+
|
|
37
|
+
this.client = new OpenAI({
|
|
38
|
+
apiKey: config.apiKey,
|
|
39
|
+
organization: config.organizationId,
|
|
40
|
+
baseURL: config.baseURL,
|
|
41
|
+
timeout: config.timeout || 60000,
|
|
42
|
+
maxRetries: config.maxRetries || 3
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
this.initializeModels();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ============================================
|
|
49
|
+
// CORE OPERATIONS (REQUIRED)
|
|
50
|
+
// ============================================
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Generate text completion using OpenAI API
|
|
54
|
+
*/
|
|
55
|
+
async complete(request: CompletionRequest): Promise<CompletionResponse> {
|
|
56
|
+
// Validate request
|
|
57
|
+
const validation = this.validateRequest(request);
|
|
58
|
+
if (!validation.isValid) {
|
|
59
|
+
return {
|
|
60
|
+
success: false,
|
|
61
|
+
error: validation.errors.join(', ')
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const completion = await this.client.chat.completions.create({
|
|
67
|
+
model: request.model || this.config.defaultModel || 'gpt-4-turbo',
|
|
68
|
+
messages: request.messages.map(msg => ({
|
|
69
|
+
role: msg.role as 'system' | 'user' | 'assistant',
|
|
70
|
+
content: msg.content,
|
|
71
|
+
name: msg.name
|
|
72
|
+
})),
|
|
73
|
+
max_tokens: request.maxTokens,
|
|
74
|
+
temperature: request.temperature,
|
|
75
|
+
top_p: request.topP,
|
|
76
|
+
frequency_penalty: request.frequencyPenalty,
|
|
77
|
+
presence_penalty: request.presencePenalty,
|
|
78
|
+
stop: request.stop,
|
|
79
|
+
user: request.user
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const choice = completion.choices[0];
|
|
83
|
+
|
|
84
|
+
// Map OpenAI's finish_reason to base interface types
|
|
85
|
+
const finishReason = choice.finish_reason === 'tool_calls' || choice.finish_reason === 'function_call'
|
|
86
|
+
? 'function_call' as const
|
|
87
|
+
: choice.finish_reason as 'stop' | 'length' | 'content_filter' | undefined;
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
success: true,
|
|
91
|
+
content: choice.message.content || '',
|
|
92
|
+
finishReason,
|
|
93
|
+
usage: completion.usage ? {
|
|
94
|
+
promptTokens: completion.usage.prompt_tokens,
|
|
95
|
+
completionTokens: completion.usage.completion_tokens,
|
|
96
|
+
totalTokens: completion.usage.total_tokens
|
|
97
|
+
} : undefined,
|
|
98
|
+
model: completion.model,
|
|
99
|
+
metadata: {
|
|
100
|
+
id: completion.id,
|
|
101
|
+
created: completion.created,
|
|
102
|
+
systemFingerprint: completion.system_fingerprint
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
} catch (error) {
|
|
106
|
+
const aiError = handleOpenAIError(error);
|
|
107
|
+
return {
|
|
108
|
+
success: false,
|
|
109
|
+
error: aiError.message
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Generate streaming text completion
|
|
116
|
+
*/
|
|
117
|
+
async *streamComplete(
|
|
118
|
+
request: CompletionRequest
|
|
119
|
+
): AsyncGenerator<StreamChunk, void, unknown> {
|
|
120
|
+
const validation = this.validateRequest(request);
|
|
121
|
+
if (!validation.isValid) {
|
|
122
|
+
throw new Error(validation.errors.join(', '));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const stream = await this.client.chat.completions.create({
|
|
127
|
+
model: request.model || this.config.defaultModel || 'gpt-4-turbo',
|
|
128
|
+
messages: request.messages.map(msg => ({
|
|
129
|
+
role: msg.role as 'system' | 'user' | 'assistant',
|
|
130
|
+
content: msg.content
|
|
131
|
+
})),
|
|
132
|
+
max_tokens: request.maxTokens,
|
|
133
|
+
temperature: request.temperature,
|
|
134
|
+
stream: true
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
for await (const chunk of stream) {
|
|
138
|
+
const delta = chunk.choices[0]?.delta?.content || '';
|
|
139
|
+
const rawFinishReason = chunk.choices[0]?.finish_reason;
|
|
140
|
+
|
|
141
|
+
// Map finish_reason for streaming (StreamChunk doesn't include function_call)
|
|
142
|
+
const finishReason = (rawFinishReason === 'stop' || rawFinishReason === 'length' || rawFinishReason === 'content_filter')
|
|
143
|
+
? rawFinishReason
|
|
144
|
+
: undefined;
|
|
145
|
+
|
|
146
|
+
yield {
|
|
147
|
+
delta,
|
|
148
|
+
finishReason,
|
|
149
|
+
usage: chunk.usage ? {
|
|
150
|
+
promptTokens: chunk.usage.prompt_tokens,
|
|
151
|
+
completionTokens: chunk.usage.completion_tokens,
|
|
152
|
+
totalTokens: chunk.usage.total_tokens
|
|
153
|
+
} : undefined
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
} catch (error) {
|
|
157
|
+
throw handleOpenAIError(error);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Generate embeddings using OpenAI embeddings API
|
|
163
|
+
*/
|
|
164
|
+
async generateEmbeddings(
|
|
165
|
+
request: EmbeddingRequest
|
|
166
|
+
): Promise<EmbeddingResponse> {
|
|
167
|
+
try {
|
|
168
|
+
const response = await this.client.embeddings.create({
|
|
169
|
+
model: request.model || 'text-embedding-3-small',
|
|
170
|
+
input: request.input,
|
|
171
|
+
user: request.user
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
success: true,
|
|
176
|
+
embeddings: response.data.map(item => item.embedding),
|
|
177
|
+
usage: {
|
|
178
|
+
promptTokens: response.usage.prompt_tokens,
|
|
179
|
+
completionTokens: 0,
|
|
180
|
+
totalTokens: response.usage.total_tokens
|
|
181
|
+
},
|
|
182
|
+
model: response.model
|
|
183
|
+
};
|
|
184
|
+
} catch (error) {
|
|
185
|
+
const aiError = handleOpenAIError(error);
|
|
186
|
+
return {
|
|
187
|
+
success: false,
|
|
188
|
+
error: aiError.message
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Check content moderation using OpenAI moderation API
|
|
195
|
+
*/
|
|
196
|
+
async moderate(content: string): Promise<ModerationResponse> {
|
|
197
|
+
try {
|
|
198
|
+
const response = await this.client.moderations.create({
|
|
199
|
+
input: content
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const result = response.results[0];
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
success: true,
|
|
206
|
+
flagged: result.flagged,
|
|
207
|
+
categories: result.categories as unknown as Record<string, boolean>,
|
|
208
|
+
categoryScores: result.category_scores as unknown as Record<string, number>
|
|
209
|
+
};
|
|
210
|
+
} catch (error) {
|
|
211
|
+
const aiError = handleOpenAIError(error);
|
|
212
|
+
return {
|
|
213
|
+
success: false,
|
|
214
|
+
flagged: false,
|
|
215
|
+
error: aiError.message
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Get available OpenAI models
|
|
222
|
+
*/
|
|
223
|
+
async getAvailableModels(): Promise<ModelInfo[]> {
|
|
224
|
+
try {
|
|
225
|
+
const response = await this.client.models.list();
|
|
226
|
+
|
|
227
|
+
return response.data
|
|
228
|
+
.filter(model =>
|
|
229
|
+
model.id.startsWith('gpt-') ||
|
|
230
|
+
model.id.startsWith('text-embedding')
|
|
231
|
+
)
|
|
232
|
+
.map(model => this.mapOpenAIModel(model));
|
|
233
|
+
} catch {
|
|
234
|
+
// Return cached models if API fails
|
|
235
|
+
return Array.from(this.models.values());
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Check OpenAI API health
|
|
241
|
+
*/
|
|
242
|
+
async checkHealth(): Promise<HealthStatus> {
|
|
243
|
+
const startTime = Date.now();
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
// Simple test request to verify API connectivity
|
|
247
|
+
await this.client.models.retrieve('gpt-3.5-turbo');
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
status: 'healthy',
|
|
251
|
+
latency: Date.now() - startTime,
|
|
252
|
+
lastChecked: new Date()
|
|
253
|
+
};
|
|
254
|
+
} catch (error) {
|
|
255
|
+
return {
|
|
256
|
+
status: 'unavailable',
|
|
257
|
+
latency: Date.now() - startTime,
|
|
258
|
+
lastChecked: new Date(),
|
|
259
|
+
details: {
|
|
260
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ============================================
|
|
267
|
+
// OPENAI-SPECIFIC FEATURES
|
|
268
|
+
// ============================================
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Chat completion with function calling
|
|
272
|
+
*/
|
|
273
|
+
async completionWithFunctions(
|
|
274
|
+
request: CompletionRequest & { functions: OpenAIFunction[] }
|
|
275
|
+
): Promise<CompletionResponse> {
|
|
276
|
+
try {
|
|
277
|
+
const completion = await this.client.chat.completions.create({
|
|
278
|
+
model: request.model || 'gpt-4-turbo',
|
|
279
|
+
messages: request.messages.map(msg => ({
|
|
280
|
+
role: msg.role as 'system' | 'user' | 'assistant',
|
|
281
|
+
content: msg.content
|
|
282
|
+
})),
|
|
283
|
+
functions: request.functions,
|
|
284
|
+
function_call: 'auto'
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
const choice = completion.choices[0];
|
|
288
|
+
|
|
289
|
+
// Map finish_reason to base types
|
|
290
|
+
const finishReason = choice.finish_reason === 'tool_calls' || choice.finish_reason === 'function_call'
|
|
291
|
+
? 'function_call' as const
|
|
292
|
+
: choice.finish_reason as 'stop' | 'length' | 'content_filter' | undefined;
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
success: true,
|
|
296
|
+
content: choice.message.content || '',
|
|
297
|
+
finishReason,
|
|
298
|
+
usage: completion.usage ? {
|
|
299
|
+
promptTokens: completion.usage.prompt_tokens,
|
|
300
|
+
completionTokens: completion.usage.completion_tokens,
|
|
301
|
+
totalTokens: completion.usage.total_tokens
|
|
302
|
+
} : undefined,
|
|
303
|
+
model: completion.model,
|
|
304
|
+
metadata: {
|
|
305
|
+
functionCall: choice.message.function_call
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
} catch (error) {
|
|
309
|
+
const aiError = handleOpenAIError(error);
|
|
310
|
+
return {
|
|
311
|
+
success: false,
|
|
312
|
+
error: aiError.message
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Vision capabilities (GPT-4 Vision)
|
|
319
|
+
*/
|
|
320
|
+
async analyzeImage(
|
|
321
|
+
imageUrl: string,
|
|
322
|
+
prompt: string,
|
|
323
|
+
model: string = 'gpt-4-vision-preview'
|
|
324
|
+
): Promise<CompletionResponse> {
|
|
325
|
+
try {
|
|
326
|
+
const completion = await this.client.chat.completions.create({
|
|
327
|
+
model,
|
|
328
|
+
messages: [
|
|
329
|
+
{
|
|
330
|
+
role: 'user',
|
|
331
|
+
content: [
|
|
332
|
+
{ type: 'text', text: prompt },
|
|
333
|
+
{ type: 'image_url', image_url: { url: imageUrl } }
|
|
334
|
+
]
|
|
335
|
+
}
|
|
336
|
+
],
|
|
337
|
+
max_tokens: 1000
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
const choice = completion.choices[0];
|
|
341
|
+
|
|
342
|
+
// Map finish_reason to base types
|
|
343
|
+
const finishReason = choice.finish_reason === 'tool_calls' || choice.finish_reason === 'function_call'
|
|
344
|
+
? 'function_call' as const
|
|
345
|
+
: choice.finish_reason as 'stop' | 'length' | 'content_filter' | undefined;
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
success: true,
|
|
349
|
+
content: choice.message.content || '',
|
|
350
|
+
finishReason,
|
|
351
|
+
usage: completion.usage ? {
|
|
352
|
+
promptTokens: completion.usage.prompt_tokens,
|
|
353
|
+
completionTokens: completion.usage.completion_tokens,
|
|
354
|
+
totalTokens: completion.usage.total_tokens
|
|
355
|
+
} : undefined,
|
|
356
|
+
model: completion.model
|
|
357
|
+
};
|
|
358
|
+
} catch (error) {
|
|
359
|
+
const aiError = handleOpenAIError(error);
|
|
360
|
+
return {
|
|
361
|
+
success: false,
|
|
362
|
+
error: aiError.message
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// ============================================
|
|
368
|
+
// COST ESTIMATION (OVERRIDE)
|
|
369
|
+
// ============================================
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Estimate cost using OpenAI pricing
|
|
373
|
+
*/
|
|
374
|
+
estimateCost(request: CompletionRequest): CostEstimate {
|
|
375
|
+
const model = request.model || this.config.defaultModel || 'gpt-4-turbo';
|
|
376
|
+
const pricing = this.getModelPricing(model);
|
|
377
|
+
|
|
378
|
+
const inputTokens = this.estimateTokens(
|
|
379
|
+
request.messages.map(m => m.content).join(' ')
|
|
380
|
+
);
|
|
381
|
+
const outputTokens = request.maxTokens || 1000;
|
|
382
|
+
|
|
383
|
+
const inputCost = (inputTokens / 1000) * pricing.inputPrice;
|
|
384
|
+
const outputCost = (outputTokens / 1000) * pricing.outputPrice;
|
|
385
|
+
|
|
386
|
+
return {
|
|
387
|
+
inputTokens,
|
|
388
|
+
outputTokens,
|
|
389
|
+
totalTokens: inputTokens + outputTokens,
|
|
390
|
+
estimatedCostUSD: inputCost + outputCost,
|
|
391
|
+
currency: 'USD'
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// ============================================
|
|
396
|
+
// PRIVATE METHODS
|
|
397
|
+
// ============================================
|
|
398
|
+
|
|
399
|
+
private initializeModels() {
|
|
400
|
+
OPENAI_MODELS.forEach(model => {
|
|
401
|
+
this.models.set(model.id, model);
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
private mapOpenAIModel(model: OpenAI.Model): ModelInfo {
|
|
406
|
+
const cached = this.models.get(model.id);
|
|
407
|
+
if (cached) {
|
|
408
|
+
return cached;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return {
|
|
412
|
+
id: model.id,
|
|
413
|
+
name: model.id,
|
|
414
|
+
contextWindow: 8192, // Default
|
|
415
|
+
maxOutputTokens: 4096, // Default
|
|
416
|
+
capabilities: ['chat']
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
private getModelPricing(model: string): { inputPrice: number; outputPrice: number } {
|
|
421
|
+
const modelInfo = this.models.get(model);
|
|
422
|
+
if (modelInfo?.pricing) {
|
|
423
|
+
return {
|
|
424
|
+
inputPrice: modelInfo.pricing.inputPricePerToken * 1000,
|
|
425
|
+
outputPrice: modelInfo.pricing.outputPricePerToken * 1000
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Default pricing (gpt-3.5-turbo)
|
|
430
|
+
return {
|
|
431
|
+
inputPrice: 0.0005,
|
|
432
|
+
outputPrice: 0.0015
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code only within the scope of the project it was delivered for.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export { OpenAIProvider } from './OpenAIProvider';
|
|
10
|
+
export * from './types/openai-types';
|
|
11
|
+
export * from './models/model-registry';
|
|
12
|
+
export * from './utils/error-handling';
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code only within the scope of the project it was delivered for.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { ModelInfo } from '@bernierllc/ai-provider-core';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* OpenAI Model Registry
|
|
13
|
+
* Contains information about all available OpenAI models
|
|
14
|
+
*/
|
|
15
|
+
export const OPENAI_MODELS: ModelInfo[] = [
|
|
16
|
+
{
|
|
17
|
+
id: 'gpt-4-turbo',
|
|
18
|
+
name: 'GPT-4 Turbo',
|
|
19
|
+
contextWindow: 128000,
|
|
20
|
+
maxOutputTokens: 4096,
|
|
21
|
+
pricing: {
|
|
22
|
+
inputPricePerToken: 0.01 / 1000,
|
|
23
|
+
outputPricePerToken: 0.03 / 1000,
|
|
24
|
+
currency: 'USD'
|
|
25
|
+
},
|
|
26
|
+
capabilities: ['chat', 'completion', 'function-calling', 'vision']
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: 'gpt-4-turbo-preview',
|
|
30
|
+
name: 'GPT-4 Turbo Preview',
|
|
31
|
+
contextWindow: 128000,
|
|
32
|
+
maxOutputTokens: 4096,
|
|
33
|
+
pricing: {
|
|
34
|
+
inputPricePerToken: 0.01 / 1000,
|
|
35
|
+
outputPricePerToken: 0.03 / 1000,
|
|
36
|
+
currency: 'USD'
|
|
37
|
+
},
|
|
38
|
+
capabilities: ['chat', 'completion', 'function-calling']
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 'gpt-4',
|
|
42
|
+
name: 'GPT-4',
|
|
43
|
+
contextWindow: 8192,
|
|
44
|
+
maxOutputTokens: 8192,
|
|
45
|
+
pricing: {
|
|
46
|
+
inputPricePerToken: 0.03 / 1000,
|
|
47
|
+
outputPricePerToken: 0.06 / 1000,
|
|
48
|
+
currency: 'USD'
|
|
49
|
+
},
|
|
50
|
+
capabilities: ['chat', 'completion', 'function-calling']
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 'gpt-4-32k',
|
|
54
|
+
name: 'GPT-4 32K',
|
|
55
|
+
contextWindow: 32768,
|
|
56
|
+
maxOutputTokens: 32768,
|
|
57
|
+
pricing: {
|
|
58
|
+
inputPricePerToken: 0.06 / 1000,
|
|
59
|
+
outputPricePerToken: 0.12 / 1000,
|
|
60
|
+
currency: 'USD'
|
|
61
|
+
},
|
|
62
|
+
capabilities: ['chat', 'completion', 'function-calling']
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 'gpt-3.5-turbo',
|
|
66
|
+
name: 'GPT-3.5 Turbo',
|
|
67
|
+
contextWindow: 16385,
|
|
68
|
+
maxOutputTokens: 4096,
|
|
69
|
+
pricing: {
|
|
70
|
+
inputPricePerToken: 0.0005 / 1000,
|
|
71
|
+
outputPricePerToken: 0.0015 / 1000,
|
|
72
|
+
currency: 'USD'
|
|
73
|
+
},
|
|
74
|
+
capabilities: ['chat', 'completion', 'function-calling']
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: 'gpt-3.5-turbo-16k',
|
|
78
|
+
name: 'GPT-3.5 Turbo 16K',
|
|
79
|
+
contextWindow: 16385,
|
|
80
|
+
maxOutputTokens: 16385,
|
|
81
|
+
pricing: {
|
|
82
|
+
inputPricePerToken: 0.003 / 1000,
|
|
83
|
+
outputPricePerToken: 0.004 / 1000,
|
|
84
|
+
currency: 'USD'
|
|
85
|
+
},
|
|
86
|
+
capabilities: ['chat', 'completion', 'function-calling']
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: 'gpt-4-vision-preview',
|
|
90
|
+
name: 'GPT-4 Vision Preview',
|
|
91
|
+
contextWindow: 128000,
|
|
92
|
+
maxOutputTokens: 4096,
|
|
93
|
+
pricing: {
|
|
94
|
+
inputPricePerToken: 0.01 / 1000,
|
|
95
|
+
outputPricePerToken: 0.03 / 1000,
|
|
96
|
+
currency: 'USD'
|
|
97
|
+
},
|
|
98
|
+
capabilities: ['chat', 'completion', 'vision']
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: 'text-embedding-3-small',
|
|
102
|
+
name: 'Text Embedding 3 Small',
|
|
103
|
+
contextWindow: 8191,
|
|
104
|
+
maxOutputTokens: 0,
|
|
105
|
+
pricing: {
|
|
106
|
+
inputPricePerToken: 0.00002 / 1000,
|
|
107
|
+
outputPricePerToken: 0,
|
|
108
|
+
currency: 'USD'
|
|
109
|
+
},
|
|
110
|
+
capabilities: ['embeddings']
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: 'text-embedding-3-large',
|
|
114
|
+
name: 'Text Embedding 3 Large',
|
|
115
|
+
contextWindow: 8191,
|
|
116
|
+
maxOutputTokens: 0,
|
|
117
|
+
pricing: {
|
|
118
|
+
inputPricePerToken: 0.00013 / 1000,
|
|
119
|
+
outputPricePerToken: 0,
|
|
120
|
+
currency: 'USD'
|
|
121
|
+
},
|
|
122
|
+
capabilities: ['embeddings']
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: 'text-embedding-ada-002',
|
|
126
|
+
name: 'Text Embedding Ada 002',
|
|
127
|
+
contextWindow: 8191,
|
|
128
|
+
maxOutputTokens: 0,
|
|
129
|
+
pricing: {
|
|
130
|
+
inputPricePerToken: 0.0001 / 1000,
|
|
131
|
+
outputPricePerToken: 0,
|
|
132
|
+
currency: 'USD'
|
|
133
|
+
},
|
|
134
|
+
capabilities: ['embeddings']
|
|
135
|
+
}
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get model information by ID
|
|
140
|
+
*/
|
|
141
|
+
export function getModelInfo(modelId: string): ModelInfo | undefined {
|
|
142
|
+
return OPENAI_MODELS.find(model => model.id === modelId);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get all chat models
|
|
147
|
+
*/
|
|
148
|
+
export function getChatModels(): ModelInfo[] {
|
|
149
|
+
return OPENAI_MODELS.filter(model =>
|
|
150
|
+
model.capabilities.includes('chat') || model.capabilities.includes('completion')
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get all embedding models
|
|
156
|
+
*/
|
|
157
|
+
export function getEmbeddingModels(): ModelInfo[] {
|
|
158
|
+
return OPENAI_MODELS.filter(model =>
|
|
159
|
+
model.capabilities.includes('embeddings')
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get all vision-capable models
|
|
165
|
+
*/
|
|
166
|
+
export function getVisionModels(): ModelInfo[] {
|
|
167
|
+
return OPENAI_MODELS.filter(model =>
|
|
168
|
+
model.capabilities.includes('vision')
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Check if model supports function calling
|
|
174
|
+
*/
|
|
175
|
+
export function supportsFunctionCalling(modelId: string): boolean {
|
|
176
|
+
const model = getModelInfo(modelId);
|
|
177
|
+
return model?.capabilities.includes('function-calling') ?? false;
|
|
178
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code only within the scope of the project it was delivered for.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { AIProviderConfig } from '@bernierllc/ai-provider-core';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* OpenAI Provider Configuration
|
|
13
|
+
*/
|
|
14
|
+
export interface OpenAIProviderConfig extends AIProviderConfig {
|
|
15
|
+
providerName: 'openai';
|
|
16
|
+
organizationId?: string;
|
|
17
|
+
baseURL?: string;
|
|
18
|
+
timeout?: number;
|
|
19
|
+
maxRetries?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* OpenAI Function Definition (for function calling)
|
|
24
|
+
*/
|
|
25
|
+
export interface OpenAIFunction {
|
|
26
|
+
name: string;
|
|
27
|
+
description: string;
|
|
28
|
+
parameters: {
|
|
29
|
+
type: 'object';
|
|
30
|
+
properties: Record<string, unknown>;
|
|
31
|
+
required?: string[];
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* OpenAI Vision Request
|
|
37
|
+
*/
|
|
38
|
+
export interface OpenAIVisionRequest {
|
|
39
|
+
imageUrl: string;
|
|
40
|
+
prompt: string;
|
|
41
|
+
model?: string;
|
|
42
|
+
maxTokens?: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* OpenAI Model Pricing Information
|
|
47
|
+
*/
|
|
48
|
+
export interface OpenAIModelPricing {
|
|
49
|
+
inputPrice: number; // USD per 1K tokens
|
|
50
|
+
outputPrice: number; // USD per 1K tokens
|
|
51
|
+
}
|