@robota-sdk/agent-provider-openai 3.0.0-beta.55
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 +543 -0
- package/LICENSE +21 -0
- package/README.md +443 -0
- package/dist/browser/index.d.ts +239 -0
- package/dist/browser/index.js +2 -0
- package/dist/node/index.cjs +2 -0
- package/dist/node/index.d.cts +239 -0
- package/dist/node/index.d.ts +239 -0
- package/dist/node/index.js +2 -0
- package/package.json +111 -0
package/README.md
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
# @robota-sdk/agent-provider-openai
|
|
2
|
+
|
|
3
|
+
OpenAI Provider for Robota SDK - Complete type-safe integration with OpenAI's GPT models, featuring function calling, streaming, and advanced AI capabilities.
|
|
4
|
+
|
|
5
|
+
## š Features
|
|
6
|
+
|
|
7
|
+
### Core Capabilities
|
|
8
|
+
|
|
9
|
+
- **šÆ Type-Safe Integration**: Complete TypeScript support with zero `any` types
|
|
10
|
+
- **š¤ GPT Model Support**: GPT-4, GPT-3.5 Turbo, OpenAI models, and OpenAI-compatible chat completion models
|
|
11
|
+
- **ā” Real-Time Streaming**: Asynchronous streaming responses with proper error handling
|
|
12
|
+
- **š ļø Function Calling**: Native OpenAI function calling with type validation
|
|
13
|
+
- **š Provider-Agnostic Design**: Seamless integration with other Robota providers
|
|
14
|
+
- **š Payload Logging**: Optional API request/response logging for debugging
|
|
15
|
+
|
|
16
|
+
### Architecture Highlights
|
|
17
|
+
|
|
18
|
+
- **Generic Type Parameters**: Full `BaseAIProvider<TConfig, TMessage, TResponse>` implementation
|
|
19
|
+
- **Facade Pattern**: Modular design with separated concerns
|
|
20
|
+
- **Error Safety**: Comprehensive error handling without any-type compromises
|
|
21
|
+
- **OpenAI SDK Compatibility**: Direct integration with official OpenAI SDK types
|
|
22
|
+
|
|
23
|
+
## š¦ Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install @robota-sdk/agent-provider-openai @robota-sdk/agent-core openai
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## š§ Basic Usage
|
|
30
|
+
|
|
31
|
+
### Simple Chat Integration
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { Robota } from '@robota-sdk/agent-core';
|
|
35
|
+
import { OpenAIProvider } from '@robota-sdk/agent-provider-openai';
|
|
36
|
+
|
|
37
|
+
// Create type-safe OpenAI provider
|
|
38
|
+
const provider = new OpenAIProvider({
|
|
39
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Create Robota agent with OpenAI provider
|
|
43
|
+
const agent = new Robota({
|
|
44
|
+
name: 'MyAgent',
|
|
45
|
+
aiProviders: [provider],
|
|
46
|
+
defaultModel: {
|
|
47
|
+
provider: 'openai',
|
|
48
|
+
model: 'gpt-4',
|
|
49
|
+
temperature: 0.7,
|
|
50
|
+
systemMessage: 'You are a helpful AI assistant specialized in technical topics.',
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Execute conversation
|
|
55
|
+
const response = await agent.run('Explain the benefits of TypeScript over JavaScript');
|
|
56
|
+
console.log(response);
|
|
57
|
+
|
|
58
|
+
// Clean up
|
|
59
|
+
await agent.destroy();
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### OpenAI-Compatible Endpoints
|
|
63
|
+
|
|
64
|
+
Use `baseURL` to point the provider at an OpenAI-compatible Chat Completions endpoint. For LM Studio, the local API typically listens on `http://localhost:1234/v1` and accepts a local placeholder API key:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { OpenAIProvider } from '@robota-sdk/agent-provider-openai';
|
|
68
|
+
|
|
69
|
+
const provider = new OpenAIProvider({
|
|
70
|
+
apiKey: 'lm-studio',
|
|
71
|
+
baseURL: 'http://localhost:1234/v1',
|
|
72
|
+
defaultModel: '<local-openai-compatible-model>',
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Gemma-family local models should use `@robota-sdk/agent-provider-gemma` instead of this
|
|
77
|
+
OpenAI provider so Gemma chat-template channel markers are projected out of user-facing
|
|
78
|
+
streamed text.
|
|
79
|
+
|
|
80
|
+
### Streaming Responses
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// Real-time streaming for immediate feedback
|
|
84
|
+
const stream = await agent.runStream('Write a detailed explanation of machine learning');
|
|
85
|
+
|
|
86
|
+
for await (const chunk of stream) {
|
|
87
|
+
if (chunk.content) {
|
|
88
|
+
process.stdout.write(chunk.content);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Handle streaming metadata
|
|
92
|
+
if (chunk.metadata?.isComplete) {
|
|
93
|
+
console.log('\nā Stream completed');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
When `chat()` receives an `onTextDelta` callback, the provider uses the streaming Chat Completions path internally, forwards text deltas to the callback, assembles streamed tool-call chunks, and returns the final assistant message.
|
|
99
|
+
|
|
100
|
+
## š ļø Function Calling
|
|
101
|
+
|
|
102
|
+
OpenAI Provider supports type-safe function calling with automatic parameter validation:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import { FunctionTool } from '@robota-sdk/agent-core';
|
|
106
|
+
import { z } from 'zod';
|
|
107
|
+
|
|
108
|
+
// Define type-safe function tools
|
|
109
|
+
const weatherTool = new FunctionTool({
|
|
110
|
+
name: 'getWeather',
|
|
111
|
+
description: 'Get current weather information for a location',
|
|
112
|
+
parameters: z.object({
|
|
113
|
+
location: z.string().describe('City name'),
|
|
114
|
+
unit: z.enum(['celsius', 'fahrenheit']).default('celsius'),
|
|
115
|
+
}),
|
|
116
|
+
handler: async ({ location, unit }) => {
|
|
117
|
+
// Type-safe handler implementation
|
|
118
|
+
const weatherData = await fetchWeatherAPI(location, unit);
|
|
119
|
+
return {
|
|
120
|
+
temperature: weatherData.temp,
|
|
121
|
+
condition: weatherData.condition,
|
|
122
|
+
location,
|
|
123
|
+
unit,
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const calculatorTool = new FunctionTool({
|
|
129
|
+
name: 'calculate',
|
|
130
|
+
description: 'Perform mathematical operations',
|
|
131
|
+
parameters: z.object({
|
|
132
|
+
operation: z.enum(['add', 'subtract', 'multiply', 'divide']),
|
|
133
|
+
a: z.number(),
|
|
134
|
+
b: z.number(),
|
|
135
|
+
}),
|
|
136
|
+
handler: async ({ operation, a, b }) => {
|
|
137
|
+
const operations = {
|
|
138
|
+
add: a + b,
|
|
139
|
+
subtract: a - b,
|
|
140
|
+
multiply: a * b,
|
|
141
|
+
divide: a / b,
|
|
142
|
+
};
|
|
143
|
+
return { result: operations[operation] };
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Register tools with the agent
|
|
148
|
+
agent.registerTool(weatherTool);
|
|
149
|
+
agent.registerTool(calculatorTool);
|
|
150
|
+
|
|
151
|
+
// Execute with function calling
|
|
152
|
+
const result = await agent.run("What's the weather in Tokyo and what's 25 * 4?");
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## š Multi-Provider Architecture
|
|
156
|
+
|
|
157
|
+
Seamlessly integrate with other providers:
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import { AnthropicProvider } from '@robota-sdk/agent-provider-anthropic';
|
|
161
|
+
import { GoogleProvider } from '@robota-sdk/agent-provider-google';
|
|
162
|
+
|
|
163
|
+
const openaiProvider = new OpenAIProvider({
|
|
164
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const anthropicProvider = new AnthropicProvider({
|
|
168
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const googleProvider = new GoogleProvider({
|
|
172
|
+
apiKey: process.env.GOOGLE_AI_API_KEY,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const agent = new Robota({
|
|
176
|
+
name: 'MultiProviderAgent',
|
|
177
|
+
aiProviders: [openaiProvider, anthropicProvider, googleProvider],
|
|
178
|
+
defaultModel: {
|
|
179
|
+
provider: 'openai',
|
|
180
|
+
model: 'gpt-4',
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Dynamic provider switching
|
|
185
|
+
const openaiResponse = await agent.run('Respond using GPT-4');
|
|
186
|
+
|
|
187
|
+
agent.setModel({ provider: 'anthropic', model: 'claude-3-sonnet-20240229' });
|
|
188
|
+
const claudeResponse = await agent.run('Respond using Claude');
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## āļø Configuration Options
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
interface IOpenAIProviderOptions {
|
|
195
|
+
// Required
|
|
196
|
+
client: OpenAI; // OpenAI SDK client instance
|
|
197
|
+
|
|
198
|
+
// Model Configuration
|
|
199
|
+
model?: string; // Default: 'gpt-4'
|
|
200
|
+
temperature?: number; // 0-1, default: 0.7
|
|
201
|
+
maxTokens?: number; // Maximum tokens to generate
|
|
202
|
+
|
|
203
|
+
// API Configuration
|
|
204
|
+
apiKey?: string; // API key (if not set in client)
|
|
205
|
+
organization?: string; // OpenAI organization ID
|
|
206
|
+
timeout?: number; // Request timeout (ms)
|
|
207
|
+
baseURL?: string; // Custom API base URL
|
|
208
|
+
|
|
209
|
+
// Response Configuration
|
|
210
|
+
responseFormat?: 'text' | 'json_object' | 'json_schema';
|
|
211
|
+
jsonSchema?: {
|
|
212
|
+
// For structured outputs
|
|
213
|
+
name: string;
|
|
214
|
+
description?: string;
|
|
215
|
+
schema?: Record<string, string | number | boolean | object>;
|
|
216
|
+
strict?: boolean;
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
// Debugging & Logging
|
|
220
|
+
payloadLogger?: IPayloadLogger; // Environment-specific payload logger
|
|
221
|
+
|
|
222
|
+
// Interface-based logger implementations:
|
|
223
|
+
// - FilePayloadLogger: Node.js file-based logging
|
|
224
|
+
// - ConsolePayloadLogger: Browser console-based logging
|
|
225
|
+
// - Custom: Implement IPayloadLogger interface
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## š Supported Models
|
|
230
|
+
|
|
231
|
+
| Model | Description | Use Cases |
|
|
232
|
+
| ---------------------- | -------------------- | ------------------------------------------- |
|
|
233
|
+
| `gpt-4` | Most capable model | Complex reasoning, analysis, creative tasks |
|
|
234
|
+
| `gpt-4-turbo` | Faster GPT-4 variant | Balanced performance and cost |
|
|
235
|
+
| `gpt-3.5-turbo` | Fast and efficient | Simple conversations, basic tasks |
|
|
236
|
+
| `gpt-4-vision-preview` | Vision capabilities | Image analysis and understanding |
|
|
237
|
+
|
|
238
|
+
## š API Reference
|
|
239
|
+
|
|
240
|
+
### OpenAIProvider Class
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
class OpenAIProvider extends BaseAIProvider<
|
|
244
|
+
IOpenAIProviderOptions,
|
|
245
|
+
UniversalMessage,
|
|
246
|
+
UniversalMessage
|
|
247
|
+
> {
|
|
248
|
+
// Core methods
|
|
249
|
+
async chat(messages: UniversalMessage[], options?: ChatOptions): Promise<UniversalMessage>;
|
|
250
|
+
async chatStream(
|
|
251
|
+
messages: UniversalMessage[],
|
|
252
|
+
options?: ChatOptions,
|
|
253
|
+
): AsyncIterable<UniversalMessage>;
|
|
254
|
+
|
|
255
|
+
// Provider information
|
|
256
|
+
readonly name: string = 'openai';
|
|
257
|
+
readonly version: string = '1.0.0';
|
|
258
|
+
|
|
259
|
+
// Utility methods
|
|
260
|
+
supportsTools(): boolean;
|
|
261
|
+
validateConfig(): boolean;
|
|
262
|
+
async dispose(): Promise<void>;
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Type Definitions
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
// Chat Options
|
|
270
|
+
interface ChatOptions {
|
|
271
|
+
tools?: ToolSchema[];
|
|
272
|
+
maxTokens?: number;
|
|
273
|
+
temperature?: number;
|
|
274
|
+
model?: string;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// OpenAI-specific types
|
|
278
|
+
interface OpenAIToolCall {
|
|
279
|
+
id: string;
|
|
280
|
+
type: 'function';
|
|
281
|
+
function: {
|
|
282
|
+
name: string;
|
|
283
|
+
arguments: string;
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
interface OpenAILogData {
|
|
288
|
+
model: string;
|
|
289
|
+
messagesCount: number;
|
|
290
|
+
hasTools: boolean;
|
|
291
|
+
temperature?: number;
|
|
292
|
+
maxTokens?: number;
|
|
293
|
+
timestamp: string;
|
|
294
|
+
requestId?: string;
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## š Debugging & Logging
|
|
299
|
+
|
|
300
|
+
### Environment-Specific Payload Logging
|
|
301
|
+
|
|
302
|
+
The OpenAI Provider supports environment-specific payload logging through interface-based dependency injection:
|
|
303
|
+
|
|
304
|
+
#### Node.js Environment (File-Based Logging)
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
import { OpenAIProvider } from '@robota-sdk/agent-provider-openai';
|
|
308
|
+
import { FilePayloadLogger } from '@robota-sdk/agent-provider-openai/loggers/file';
|
|
309
|
+
|
|
310
|
+
const provider = new OpenAIProvider({
|
|
311
|
+
client: openaiClient,
|
|
312
|
+
model: 'gpt-4',
|
|
313
|
+
payloadLogger: new FilePayloadLogger({
|
|
314
|
+
logDir: './logs/openai-api',
|
|
315
|
+
enabled: true,
|
|
316
|
+
includeTimestamp: true,
|
|
317
|
+
}),
|
|
318
|
+
});
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
#### Browser Environment (Console-Based Logging)
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
import { OpenAIProvider } from '@robota-sdk/agent-provider-openai';
|
|
325
|
+
import { ConsolePayloadLogger } from '@robota-sdk/agent-provider-openai/loggers/console';
|
|
326
|
+
|
|
327
|
+
const provider = new OpenAIProvider({
|
|
328
|
+
client: openaiClient,
|
|
329
|
+
model: 'gpt-4',
|
|
330
|
+
payloadLogger: new ConsolePayloadLogger({
|
|
331
|
+
enabled: true,
|
|
332
|
+
includeTimestamp: true,
|
|
333
|
+
}),
|
|
334
|
+
});
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
#### No Logging (Both Environments)
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
const provider = new OpenAIProvider({
|
|
341
|
+
client: openaiClient,
|
|
342
|
+
model: 'gpt-4',
|
|
343
|
+
// payloadLogger: undefined (default - no logging)
|
|
344
|
+
});
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Custom Logger Implementation
|
|
348
|
+
|
|
349
|
+
You can create custom logger implementations by implementing the IPayloadLogger interface:
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
import type { IPayloadLogger, OpenAILogData } from '@robota-sdk/agent-provider-openai';
|
|
353
|
+
|
|
354
|
+
class CustomPayloadLogger implements IPayloadLogger {
|
|
355
|
+
isEnabled(): boolean {
|
|
356
|
+
return true;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async logPayload(payload: OpenAILogData, type: 'chat' | 'stream'): Promise<void> {
|
|
360
|
+
// Custom logging implementation
|
|
361
|
+
console.log(`[Custom Logger] ${type}:`, payload);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const provider = new OpenAIProvider({
|
|
366
|
+
client: openaiClient,
|
|
367
|
+
payloadLogger: new CustomPayloadLogger(),
|
|
368
|
+
});
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
This creates detailed logs of all API requests and responses for debugging purposes.
|
|
372
|
+
|
|
373
|
+
## š Security Best Practices
|
|
374
|
+
|
|
375
|
+
### API Key Management
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
// ā
Good: Use environment variables
|
|
379
|
+
const client = new OpenAI({
|
|
380
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
// ā Bad: Hardcoded keys
|
|
384
|
+
const client = new OpenAI({
|
|
385
|
+
apiKey: 'sk-...', // Never do this!
|
|
386
|
+
});
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Error Handling
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
try {
|
|
393
|
+
const response = await agent.run('Your query');
|
|
394
|
+
} catch (error) {
|
|
395
|
+
if (error instanceof Error) {
|
|
396
|
+
console.error('AI Error:', error.message);
|
|
397
|
+
}
|
|
398
|
+
// Handle specific OpenAI errors
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
## š Performance Optimization
|
|
403
|
+
|
|
404
|
+
### Token Management
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
const provider = new OpenAIProvider({
|
|
408
|
+
client: openaiClient,
|
|
409
|
+
model: 'gpt-4',
|
|
410
|
+
maxTokens: 1000, // Limit response length
|
|
411
|
+
temperature: 0.3, // More deterministic responses
|
|
412
|
+
});
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Model Selection Strategy
|
|
416
|
+
|
|
417
|
+
- Use `gpt-3.5-turbo` for simple tasks
|
|
418
|
+
- Use `gpt-4` for complex reasoning
|
|
419
|
+
- Use `gpt-4-turbo` for balanced performance
|
|
420
|
+
|
|
421
|
+
## š¤ Contributing
|
|
422
|
+
|
|
423
|
+
This package follows strict type safety guidelines:
|
|
424
|
+
|
|
425
|
+
- Zero `any` or `unknown` types allowed
|
|
426
|
+
- Complete TypeScript coverage
|
|
427
|
+
- Comprehensive error handling
|
|
428
|
+
- Provider-agnostic design principles
|
|
429
|
+
|
|
430
|
+
## š License
|
|
431
|
+
|
|
432
|
+
MIT License - see LICENSE file for details.
|
|
433
|
+
|
|
434
|
+
## š Related Packages
|
|
435
|
+
|
|
436
|
+
- **[@robota-sdk/agent-core](../agents/)**: Core agent framework
|
|
437
|
+
- **[@robota-sdk/agent-provider-anthropic](../anthropic/)**: Anthropic Claude provider
|
|
438
|
+
- **[@robota-sdk/agent-provider-google](../google/)**: Google AI provider
|
|
439
|
+
- **[@robota-sdk/agent-team](../team/)**: assignTask MCP tool collection (team creation removed)
|
|
440
|
+
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
For complete documentation and examples, visit the [Robota SDK Documentation](https://robota.io).
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
import { ILogger, IExecutor, TProviderOptionValueBase, AbstractAIProvider, TTextDeltaCallback, TUniversalMessage, IChatOptions, IProviderDefinition } from '@robota-sdk/agent-core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Payload logging data structure
|
|
6
|
+
*/
|
|
7
|
+
interface IOpenAILogData {
|
|
8
|
+
model: string;
|
|
9
|
+
messagesCount: number;
|
|
10
|
+
hasTools: boolean;
|
|
11
|
+
temperature?: number | undefined;
|
|
12
|
+
maxTokens?: number | undefined;
|
|
13
|
+
timestamp: string;
|
|
14
|
+
requestId?: string | undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* IPayloadLogger interface for logging OpenAI API payloads
|
|
19
|
+
*
|
|
20
|
+
* This interface provides a contract for different logging implementations:
|
|
21
|
+
* - FilePayloadLogger: Node.js file-based logging
|
|
22
|
+
* - ConsolePayloadLogger: Browser console-based logging
|
|
23
|
+
* - Custom implementations: User-defined loggers
|
|
24
|
+
*/
|
|
25
|
+
interface IPayloadLogger {
|
|
26
|
+
/**
|
|
27
|
+
* Check if logging is enabled
|
|
28
|
+
* @returns true if logging is active, false otherwise
|
|
29
|
+
*/
|
|
30
|
+
isEnabled(): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Log API payload data
|
|
33
|
+
* @param payload - The API request/response payload data
|
|
34
|
+
* @param type - Type of operation ('chat' or 'stream')
|
|
35
|
+
*/
|
|
36
|
+
logPayload(payload: IOpenAILogData, type: 'chat' | 'stream'): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Configuration options for payload loggers
|
|
40
|
+
*/
|
|
41
|
+
interface IPayloadLoggerOptions {
|
|
42
|
+
/**
|
|
43
|
+
* Whether logging is enabled
|
|
44
|
+
* @defaultValue true
|
|
45
|
+
*/
|
|
46
|
+
enabled?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Include timestamp in log entries
|
|
49
|
+
* @defaultValue true
|
|
50
|
+
*/
|
|
51
|
+
includeTimestamp?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Logger instance for console output
|
|
54
|
+
* @defaultValue SilentLogger
|
|
55
|
+
*/
|
|
56
|
+
logger?: ILogger;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Valid provider option value types
|
|
61
|
+
*/
|
|
62
|
+
type TOpenAIProviderOptionValue = string | number | boolean | undefined | null | OpenAI | IPayloadLogger | ILogger | IExecutor | TProviderOptionValueBase | TOpenAIProviderOptionValue[] | {
|
|
63
|
+
[key: string]: TOpenAIProviderOptionValue;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* OpenAI provider options
|
|
67
|
+
*/
|
|
68
|
+
interface IOpenAIProviderOptions {
|
|
69
|
+
/**
|
|
70
|
+
* Additional provider-specific options
|
|
71
|
+
*/
|
|
72
|
+
[key: string]: TOpenAIProviderOptionValue;
|
|
73
|
+
/**
|
|
74
|
+
* OpenAI API key (required when client is not provided)
|
|
75
|
+
*/
|
|
76
|
+
apiKey?: string;
|
|
77
|
+
/**
|
|
78
|
+
* OpenAI organization ID (optional)
|
|
79
|
+
*/
|
|
80
|
+
organization?: string;
|
|
81
|
+
/**
|
|
82
|
+
* API request timeout (milliseconds)
|
|
83
|
+
*/
|
|
84
|
+
timeout?: number;
|
|
85
|
+
/**
|
|
86
|
+
* API base URL (default: 'https://api.openai.com/v1')
|
|
87
|
+
*/
|
|
88
|
+
baseURL?: string;
|
|
89
|
+
/**
|
|
90
|
+
* Response format (default: 'text')
|
|
91
|
+
* - 'text': Plain text response
|
|
92
|
+
* - 'json_object': JSON object mode (requires system message)
|
|
93
|
+
* - 'json_schema': Structured Outputs with schema validation
|
|
94
|
+
*/
|
|
95
|
+
responseFormat?: 'text' | 'json_object' | 'json_schema';
|
|
96
|
+
/**
|
|
97
|
+
* JSON schema for structured outputs (required when responseFormat is 'json_schema')
|
|
98
|
+
*/
|
|
99
|
+
jsonSchema?: {
|
|
100
|
+
name: string;
|
|
101
|
+
description?: string;
|
|
102
|
+
schema?: Record<string, TOpenAIProviderOptionValue>;
|
|
103
|
+
strict?: boolean;
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* OpenAI client instance (optional: will be created from apiKey if not provided)
|
|
107
|
+
*/
|
|
108
|
+
client?: OpenAI;
|
|
109
|
+
/**
|
|
110
|
+
* Payload logger instance for debugging API requests/responses
|
|
111
|
+
*
|
|
112
|
+
* Use different implementations based on your environment:
|
|
113
|
+
* - FilePayloadLogger: Node.js file-based logging
|
|
114
|
+
* - ConsolePayloadLogger: Browser console-based logging
|
|
115
|
+
* - Custom: Implement IPayloadLogger interface
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```typescript
|
|
119
|
+
* // Node.js
|
|
120
|
+
* import { FilePayloadLogger } from '@robota-sdk/agent-provider-openai/loggers/file';
|
|
121
|
+
* const provider = new OpenAIProvider({
|
|
122
|
+
* client: openaiClient,
|
|
123
|
+
* payloadLogger: new FilePayloadLogger({ logDir: './logs/openai' })
|
|
124
|
+
* });
|
|
125
|
+
*
|
|
126
|
+
* // Browser
|
|
127
|
+
* import { ConsolePayloadLogger } from '@robota-sdk/agent-provider-openai/loggers/console';
|
|
128
|
+
* const provider = new OpenAIProvider({
|
|
129
|
+
* client: openaiClient,
|
|
130
|
+
* payloadLogger: new ConsolePayloadLogger()
|
|
131
|
+
* });
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
payloadLogger?: IPayloadLogger;
|
|
135
|
+
/**
|
|
136
|
+
* Optional executor for handling AI requests
|
|
137
|
+
*
|
|
138
|
+
* When provided, the provider will delegate all chat operations to this executor
|
|
139
|
+
* instead of making direct API calls. This enables remote execution capabilities.
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```typescript
|
|
143
|
+
* import { LocalExecutor, RemoteExecutor } from '@robota-sdk/agent-core';
|
|
144
|
+
*
|
|
145
|
+
* // Local execution (registers this provider)
|
|
146
|
+
* const localExecutor = new LocalExecutor();
|
|
147
|
+
* localExecutor.registerProvider('openai', new OpenAIProvider({ apiKey: 'sk-...' }));
|
|
148
|
+
*
|
|
149
|
+
* // Remote execution
|
|
150
|
+
* const remoteExecutor = new RemoteExecutor({
|
|
151
|
+
* serverUrl: 'https://api.robota.io',
|
|
152
|
+
* userApiKey: 'user-token-123'
|
|
153
|
+
* });
|
|
154
|
+
*
|
|
155
|
+
* const provider = new OpenAIProvider({
|
|
156
|
+
* executor: remoteExecutor // No direct API key needed
|
|
157
|
+
* });
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
executor?: IExecutor;
|
|
161
|
+
/**
|
|
162
|
+
* Logger instance for internal OpenAI provider logging
|
|
163
|
+
* @defaultValue SilentLogger
|
|
164
|
+
*/
|
|
165
|
+
logger?: ILogger;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* OpenAI provider implementation for Robota
|
|
170
|
+
*
|
|
171
|
+
* Provides integration with OpenAI's GPT models following BaseAIProvider guidelines.
|
|
172
|
+
* Uses OpenAI SDK native types internally for optimal performance and feature support.
|
|
173
|
+
*
|
|
174
|
+
* @public
|
|
175
|
+
*/
|
|
176
|
+
declare class OpenAIProvider extends AbstractAIProvider {
|
|
177
|
+
readonly name = "openai";
|
|
178
|
+
readonly version = "1.0.0";
|
|
179
|
+
private readonly client?;
|
|
180
|
+
private readonly options;
|
|
181
|
+
private readonly payloadLogger;
|
|
182
|
+
private readonly responseParser;
|
|
183
|
+
/**
|
|
184
|
+
* Optional callback for text deltas during streaming.
|
|
185
|
+
* Set by the consumer (e.g., Session) to receive real-time text chunks.
|
|
186
|
+
* When set, chat() uses streaming internally while still returning
|
|
187
|
+
* the complete assembled message.
|
|
188
|
+
*/
|
|
189
|
+
onTextDelta?: TTextDeltaCallback;
|
|
190
|
+
constructor(options: IOpenAIProviderOptions);
|
|
191
|
+
chat(messages: TUniversalMessage[], options?: IChatOptions): Promise<TUniversalMessage>;
|
|
192
|
+
private chatWithStreamingAssembly;
|
|
193
|
+
chatStream(messages: TUniversalMessage[], options?: IChatOptions): AsyncIterable<TUniversalMessage>;
|
|
194
|
+
supportsTools(): boolean;
|
|
195
|
+
validateConfig(): boolean;
|
|
196
|
+
dispose(): Promise<void>;
|
|
197
|
+
protected validateMessages(messages: TUniversalMessage[]): void;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* OpenAI Conversation Adapter
|
|
202
|
+
*
|
|
203
|
+
* Converts between TUniversalMessage format and OpenAI native types.
|
|
204
|
+
* Provides bidirectional conversion for seamless integration.
|
|
205
|
+
*
|
|
206
|
+
* @public
|
|
207
|
+
*/
|
|
208
|
+
declare class OpenAIConversationAdapter {
|
|
209
|
+
/**
|
|
210
|
+
* Filter messages for OpenAI compatibility
|
|
211
|
+
*
|
|
212
|
+
* OpenAI has specific requirements:
|
|
213
|
+
* - Tool messages must have valid toolCallId
|
|
214
|
+
* - Messages must be in proper sequence
|
|
215
|
+
* - Tool messages without toolCallId should be excluded
|
|
216
|
+
*/
|
|
217
|
+
static filterMessagesForOpenAI(messages: TUniversalMessage[]): TUniversalMessage[];
|
|
218
|
+
/**
|
|
219
|
+
* Convert TUniversalMessage array to OpenAI message format
|
|
220
|
+
* Now properly handles tool messages for OpenAI's tool calling feature
|
|
221
|
+
*/
|
|
222
|
+
static toOpenAIFormat(messages: TUniversalMessage[]): OpenAI.Chat.ChatCompletionMessageParam[];
|
|
223
|
+
/**
|
|
224
|
+
* Convert a single TUniversalMessage to OpenAI format
|
|
225
|
+
* Handles all message types including tool messages
|
|
226
|
+
*/
|
|
227
|
+
static convertMessage(msg: TUniversalMessage): OpenAI.Chat.ChatCompletionMessageParam;
|
|
228
|
+
/**
|
|
229
|
+
* Add system prompt to message array if needed
|
|
230
|
+
*/
|
|
231
|
+
static addSystemPromptIfNeeded(messages: OpenAI.Chat.ChatCompletionMessageParam[], systemPrompt?: string): OpenAI.Chat.ChatCompletionMessageParam[];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
declare const DEFAULT_OPENAI_PROVIDER_MODEL: string | undefined;
|
|
235
|
+
declare const DEFAULT_OPENAI_COMPATIBLE_PROVIDER_API_KEY = "lm-studio";
|
|
236
|
+
declare const DEFAULT_OPENAI_COMPATIBLE_PROVIDER_BASE_URL = "http://localhost:1234/v1";
|
|
237
|
+
declare function createOpenAIProviderDefinition(): IProviderDefinition;
|
|
238
|
+
|
|
239
|
+
export { DEFAULT_OPENAI_COMPATIBLE_PROVIDER_API_KEY, DEFAULT_OPENAI_COMPATIBLE_PROVIDER_BASE_URL, DEFAULT_OPENAI_PROVIDER_MODEL, type IOpenAIProviderOptions, type IPayloadLogger, type IPayloadLoggerOptions, OpenAIConversationAdapter, OpenAIProvider, type TOpenAIProviderOptionValue, createOpenAIProviderDefinition };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import C from'openai';import {AbstractAIProvider,SilentLogger}from'@robota-sdk/agent-core';import {assembleOpenAICompatibleStream,probeOpenAICompatibleProfile,OpenAICompatibleResponseParser,convertToOpenAICompatibleMessages,convertToOpenAICompatibleTools}from'@robota-sdk/agent-provider-openai-compatible';var p=class{parser;constructor(e){this.parser=new OpenAICompatibleResponseParser({logger:e});}parseResponse(e){try{return this.parser.parseResponse(e)}catch(t){let r=d(t instanceof Error?t.message:"OpenAI response parsing failed");throw new Error(`OpenAI response parsing failed: ${r}`)}}parseStreamingChunk(e){try{return this.parser.parseStreamingChunk(e)}catch(t){let r=d(t instanceof Error?t.message:"OpenAI chunk parsing failed");throw new Error(`OpenAI chunk parsing failed: ${r}`)}}};function d(a){return a.replace(/^OpenAI-compatible response parsing failed: /,"").replace(/^OpenAI-compatible chunk parsing failed: /,"").replace("OpenAI-compatible response","OpenAI response")}function c(a){return convertToOpenAICompatibleMessages(a)}function m(a){return convertToOpenAICompatibleTools(a)}var i=class extends AbstractAIProvider{name="openai";version="1.0.0";client;options;payloadLogger;responseParser;onTextDelta;constructor(e){if(super(e.logger||SilentLogger),this.options=e,e.executor&&(this.executor=e.executor),!this.executor)if(e.client)this.client=e.client;else if(e.apiKey)this.client=new C({apiKey:e.apiKey,...e.organization&&{organization:e.organization},...e.timeout&&{timeout:e.timeout},...e.baseURL&&{baseURL:e.baseURL}});else throw new Error("Either OpenAI client, apiKey, or executor is required");this.responseParser=new p(this.logger),this.payloadLogger=e.payloadLogger;}async chat(e,t){if(this.validateMessages(e),this.executor)try{return await this.executeViaExecutorOrDirect(e,t)}catch(r){throw this.logger.error("OpenAI Provider executor chat error:",r instanceof Error?r.message:String(r)),r}if(!this.client)throw new Error("OpenAI client not available. Either provide a client/apiKey or use an executor.");try{let r=c(e),o=t;if(!o?.model)throw new Error("Model is required in chat options. Please specify a model in defaultModel configuration.");let s={model:o.model,messages:r,...o.temperature!==void 0&&{temperature:o.temperature},...o.maxTokens&&{max_tokens:o.maxTokens},...o.tools&&{tools:m(o.tools),tool_choice:"auto"}},n=o.onTextDelta??this.onTextDelta;if(n)return await this.chatWithStreamingAssembly({...s,stream:!0},{...o,onTextDelta:n});if(this.payloadLogger?.isEnabled()){let I={model:s.model,messagesCount:r.length,hasTools:!!s.tools,temperature:s.temperature??void 0,maxTokens:s.max_tokens??void 0,timestamp:new Date().toISOString()};await this.payloadLogger.logPayload(I,"chat");}let l=await this.client.chat.completions.create(s);return this.responseParser.parseResponse(l)}catch(r){let s=r.message||"OpenAI API request failed";throw new Error(`OpenAI chat failed: ${s}`)}}async chatWithStreamingAssembly(e,t){if(!this.client)throw new Error("OpenAI client not available. Either provide a client/apiKey or use an executor.");try{if(this.payloadLogger?.isEnabled()){let o={model:e.model,messagesCount:e.messages.length,hasTools:!!e.tools,temperature:e.temperature??void 0,maxTokens:e.max_tokens??void 0,timestamp:new Date().toISOString()};await this.payloadLogger.logPayload(o,"stream");}let r=await this.client.chat.completions.create(e,t.signal?{signal:t.signal}:void 0);return assembleOpenAICompatibleStream({stream:r,onTextDelta:t.onTextDelta,signal:t.signal})}catch(r){let s=r.message||"OpenAI streaming request failed";throw new Error(`OpenAI stream failed: ${s}`)}}async*chatStream(e,t){if(this.executor)try{yield*this.executeStreamViaExecutorOrDirect(e,t);return}catch(r){throw this.logger.error("OpenAI Provider executor stream error:",r instanceof Error?r.message:String(r)),r}if(!this.client)throw new Error("OpenAI client not available. Either provide a client/apiKey or use an executor.");try{let r=c(e);if(!t?.model)throw new Error("Model is required in chat options. Please specify a model in defaultModel configuration.");let o={model:t.model,messages:r,stream:!0,...t?.temperature!==void 0&&{temperature:t.temperature},...t?.maxTokens&&{max_tokens:t.maxTokens},...t?.tools&&{tools:m(t.tools),tool_choice:"auto"}};if(this.payloadLogger?.isEnabled()){let n={model:o.model,messagesCount:r.length,hasTools:!!o.tools,temperature:o.temperature??void 0,maxTokens:o.max_tokens??void 0,timestamp:new Date().toISOString()};await this.payloadLogger.logPayload(n,"stream");}let s=await this.client.chat.completions.create(o);for await(let n of s){let l=this.responseParser.parseStreamingChunk(n);l&&(yield l);}}catch(r){let s=r.message||"OpenAI API request failed";throw new Error(`OpenAI stream failed: ${s}`)}}supportsTools(){return true}validateConfig(){return !!this.client&&!!this.options}async dispose(){}validateMessages(e){super.validateMessages(e);for(let t of e)if(t.role==="assistant"){let r=t;if(r.toolCalls&&r.toolCalls.length>0&&r.content==="")continue}}};var g=class{static filterMessagesForOpenAI(e){return e.filter(t=>t.role==="user"||t.role==="assistant"||t.role==="system"?true:t.role==="tool"?!!(t.toolCallId&&t.toolCallId.trim()!==""&&t.toolCallId!=="unknown"):false)}static toOpenAIFormat(e){return this.filterMessagesForOpenAI(e).map(r=>this.convertMessage(r))}static convertMessage(e){let t=e.role;if(t==="user")return {role:"user",content:e.content};if(t==="assistant"){let o=e;return o.toolCalls&&o.toolCalls.length>0?{role:"assistant",content:o.content===""?null:o.content||null,tool_calls:o.toolCalls.map(n=>({id:n.id,type:"function",function:{name:n.function.name,arguments:n.function.arguments}}))}:{role:"assistant",content:o.content===null||o.content===""?null:o.content||""}}if(t==="system")return {role:"system",content:e.content};if(t==="tool"){if(!e.toolCallId||e.toolCallId.trim()==="")throw new Error(`Tool message missing toolCallId: ${JSON.stringify(e)}`);return {role:"tool",content:e.content,tool_call_id:e.toolCallId}}let r=t;throw new Error(`Unsupported message role: ${r}`)}static addSystemPromptIfNeeded(e,t){return !t||e.some(o=>o.role==="system")?e:[{role:"system",content:t},...e]}};var N=void 0,h="lm-studio",f="http://localhost:1234/v1";function z(){return {type:"openai",defaults:{apiKey:h,baseURL:f},setupSteps:[{key:"baseURL",title:"OpenAI-compatible base URL",defaultValue:f},{key:"model",title:"OpenAI-compatible model",required:true},{key:"apiKey",title:"OpenAI-compatible API key",defaultValue:h,masked:true}],requiresApiKey:true,probeProfile:probeOpenAICompatibleProfile,createProvider:a=>new i({apiKey:x(a.apiKey),...a.baseURL!==void 0&&{baseURL:a.baseURL},...a.timeout!==void 0&&{timeout:a.timeout},defaultModel:a.model})}}function x(a){if(!a)throw new Error("Provider openai requires apiKey");return a}
|
|
2
|
+
export{h as DEFAULT_OPENAI_COMPATIBLE_PROVIDER_API_KEY,f as DEFAULT_OPENAI_COMPATIBLE_PROVIDER_BASE_URL,N as DEFAULT_OPENAI_PROVIDER_MODEL,g as OpenAIConversationAdapter,i as OpenAIProvider,z as createOpenAIProviderDefinition};
|