@mate-academy/llm-gateway 2.5.3 → 3.0.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.
Files changed (72) hide show
  1. package/README.md +411 -117
  2. package/dist/LLMService.factory.d.ts +10 -19
  3. package/dist/LLMService.factory.js +22 -22
  4. package/dist/LLMService.factory.js.map +1 -1
  5. package/dist/LLMService.typedefs.d.ts +51 -4
  6. package/dist/LLMService.typedefs.js.map +1 -1
  7. package/dist/providers/GoogleGenerativeAI/GoogleGenerativeAI.entity.d.ts +2 -3
  8. package/dist/providers/GoogleGenerativeAI/GoogleGenerativeAI.entity.js +1 -1
  9. package/dist/providers/GoogleGenerativeAI/GoogleGenerativeAI.entity.js.map +1 -1
  10. package/dist/providers/GoogleGenerativeAI/GoogleGenerativeAIService.factory.d.ts +3 -4
  11. package/dist/providers/GoogleGenerativeAI/GoogleGenerativeAIService.factory.js +2 -1
  12. package/dist/providers/GoogleGenerativeAI/GoogleGenerativeAIService.factory.js.map +1 -1
  13. package/dist/providers/GoogleGenerativeAI/schemas/GoogleSchemaAdapter.d.ts +12 -0
  14. package/dist/providers/GoogleGenerativeAI/schemas/GoogleSchemaAdapter.js +72 -0
  15. package/dist/providers/GoogleGenerativeAI/schemas/GoogleSchemaAdapter.js.map +1 -0
  16. package/dist/providers/GoogleGenerativeAI/services/GoogleGenerativeAIAssistance.service.d.ts +2 -3
  17. package/dist/providers/GoogleGenerativeAI/services/GoogleGenerativeAIAssistance.service.js +9 -9
  18. package/dist/providers/GoogleGenerativeAI/services/GoogleGenerativeAIAssistance.service.js.map +1 -1
  19. package/dist/providers/GoogleGenerativeAI/services/GoogleGenerativeAICompletion.service.d.ts +2 -3
  20. package/dist/providers/GoogleGenerativeAI/services/GoogleGenerativeAICompletion.service.js +4 -4
  21. package/dist/providers/GoogleGenerativeAI/services/GoogleGenerativeAICompletion.service.js.map +1 -1
  22. package/dist/providers/GoogleGenerativeAI/services/GoogleGenerativeAISpeechToText.service.d.ts +2 -3
  23. package/dist/providers/GoogleGenerativeAI/services/GoogleGenerativeAISpeechToText.service.js +1 -1
  24. package/dist/providers/GoogleGenerativeAI/services/GoogleGenerativeAISpeechToText.service.js.map +1 -1
  25. package/dist/providers/GoogleGenerativeAI/services/GoogleGenerativeAITextToSpeech.service.d.ts +2 -3
  26. package/dist/providers/GoogleGenerativeAI/services/GoogleGenerativeAITextToSpeech.service.js +1 -1
  27. package/dist/providers/GoogleGenerativeAI/services/GoogleGenerativeAITextToSpeech.service.js.map +1 -1
  28. package/dist/providers/OpenAI/OpenAI.entity.d.ts +3 -4
  29. package/dist/providers/OpenAI/OpenAI.entity.js +1 -1
  30. package/dist/providers/OpenAI/OpenAI.entity.js.map +1 -1
  31. package/dist/providers/OpenAI/OpenAIService.factory.d.ts +3 -4
  32. package/dist/providers/OpenAI/OpenAIService.factory.js +2 -1
  33. package/dist/providers/OpenAI/OpenAIService.factory.js.map +1 -1
  34. package/dist/providers/OpenAI/schemas/OpenAISchemaAdapter.d.ts +14 -0
  35. package/dist/providers/OpenAI/schemas/OpenAISchemaAdapter.js +87 -0
  36. package/dist/providers/OpenAI/schemas/OpenAISchemaAdapter.js.map +1 -0
  37. package/dist/providers/OpenAI/services/OpenAIAssistance.service.d.ts +2 -3
  38. package/dist/providers/OpenAI/services/OpenAIAssistance.service.js +8 -8
  39. package/dist/providers/OpenAI/services/OpenAIAssistance.service.js.map +1 -1
  40. package/dist/providers/OpenAI/services/OpenAICompletion.service.d.ts +2 -3
  41. package/dist/providers/OpenAI/services/OpenAICompletion.service.js +4 -4
  42. package/dist/providers/OpenAI/services/OpenAICompletion.service.js.map +1 -1
  43. package/dist/providers/OpenAI/services/OpenAISpeechToText.service.d.ts +2 -3
  44. package/dist/providers/OpenAI/services/OpenAISpeechToText.service.js +1 -1
  45. package/dist/providers/OpenAI/services/OpenAISpeechToText.service.js.map +1 -1
  46. package/dist/providers/OpenAI/services/OpenAITextToSpeech.service.d.ts +2 -3
  47. package/dist/providers/OpenAI/services/OpenAITextToSpeech.service.js +1 -1
  48. package/dist/providers/OpenAI/services/OpenAITextToSpeech.service.js.map +1 -1
  49. package/dist/services/LLMAssistanceService.abstract.d.ts +2 -3
  50. package/dist/services/LLMAssistanceService.abstract.js.map +1 -1
  51. package/dist/services/LLMBaseService.abstract.d.ts +3 -4
  52. package/dist/services/LLMBaseService.abstract.js +1 -1
  53. package/dist/services/LLMBaseService.abstract.js.map +1 -1
  54. package/dist/services/LLMCompletionService.abstract.d.ts +2 -3
  55. package/dist/services/LLMCompletionService.abstract.js.map +1 -1
  56. package/dist/services/LLMServicePurposeFactory.abstract.d.ts +7 -3
  57. package/dist/services/LLMServicePurposeFactory.abstract.js.map +1 -1
  58. package/dist/services/LLMSpeechToTextService.abstract.d.ts +2 -3
  59. package/dist/services/LLMSpeechToTextService.abstract.js.map +1 -1
  60. package/dist/services/LLMTextToSpeechService.abstract.d.ts +2 -3
  61. package/dist/services/LLMTextToSpeechService.abstract.js.map +1 -1
  62. package/dist/utilities/schema/LLMSchema.d.ts +4 -20
  63. package/dist/utilities/schema/LLMSchema.js +8 -147
  64. package/dist/utilities/schema/LLMSchema.js.map +1 -1
  65. package/dist/utilities/schema/LLMSchemaInterface.d.ts +3 -7
  66. package/dist/utilities/schema/adapters/SchemaAdapterInterface.d.ts +12 -0
  67. package/dist/utilities/schema/adapters/SchemaAdapterInterface.js +3 -0
  68. package/dist/utilities/schema/adapters/SchemaAdapterInterface.js.map +1 -0
  69. package/dist/utilities/schema/adapters/SchemaAdapterRegistry.d.ts +28 -0
  70. package/dist/utilities/schema/adapters/SchemaAdapterRegistry.js +36 -0
  71. package/dist/utilities/schema/adapters/SchemaAdapterRegistry.js.map +1 -0
  72. package/package.json +8 -9
package/README.md CHANGED
@@ -16,6 +16,7 @@ npm install @mate-academy/llm-gateway
16
16
 
17
17
  - Support for multiple LLM providers (OpenAI, Google Generative AI)
18
18
  - **Structured Output**: Type-safe JSON responses with schema validation
19
+ - **Extensible Schema Architecture**: Driver pattern with provider-specific adapters
19
20
  - Standardized completion service interface
20
21
  - Standardized assistance service interface with file handling
21
22
  - Speech-to-text transcription capabilities
@@ -30,6 +31,23 @@ npm install @mate-academy/llm-gateway
30
31
 
31
32
  ## Usage
32
33
 
34
+ ### Logger Interface
35
+
36
+ The package accepts an optional logger that implements the `LLMLogger` interface. Most logging libraries are compatible (`@mate-academy/logger`, winston, pino, etc.). If no logger is provided, no logging will occur.
37
+
38
+ ```typescript
39
+ // Using @mate-academy/logger (recommended)
40
+ import { logger } from '@mate-academy/logger';
41
+
42
+ // Or simple console logger
43
+ const logger = {
44
+ info: (msg, meta) => console.log(msg, meta),
45
+ error: (msg, meta) => console.error(msg, meta),
46
+ warn: (msg, meta) => console.warn(msg, meta),
47
+ child: (context) => logger,
48
+ };
49
+ ```
50
+
33
51
  ### Basic Setup
34
52
 
35
53
  ```typescript
@@ -47,6 +65,7 @@ import {
47
65
  LLMModel,
48
66
  LLMSchema,
49
67
  createPromptTemplate,
68
+ LLMLogger,
50
69
  } from '@mate-academy/llm-gateway';
51
70
 
52
71
  // Define provider options
@@ -65,32 +84,32 @@ const llmProviderOptions = LLMServiceFactory.resolveProviderOptions(
65
84
  );
66
85
 
67
86
  // Get completion service
68
- const completionService = LLMServiceFactory.getCompletionService(
69
- LLMProviders.OpenAI,
70
- logger, // your logger instance
71
- llmProviderOptions,
72
- );
87
+ const completionService = LLMServiceFactory.getCompletionService({
88
+ provider: LLMProviders.OpenAI,
89
+ options: llmProviderOptions,
90
+ logger, // optional - omit for no logging
91
+ });
73
92
 
74
93
  // Get assistance service
75
- const assistanceService = LLMServiceFactory.getAssistanceService(
76
- LLMProviders.OpenAI,
77
- logger, // your logger instance
78
- llmProviderOptions,
79
- );
94
+ const assistanceService = LLMServiceFactory.getAssistanceService({
95
+ provider: LLMProviders.OpenAI,
96
+ options: llmProviderOptions,
97
+ logger, // optional - omit for no logging
98
+ });
80
99
 
81
100
  // Get speech-to-text service
82
- const speechToTextService = LLMServiceFactory.getSpeechToTextService(
83
- LLMProviders.OpenAI,
84
- logger, // your logger instance
85
- llmProviderOptions,
86
- );
101
+ const speechToTextService = LLMServiceFactory.getSpeechToTextService({
102
+ provider: LLMProviders.OpenAI,
103
+ options: llmProviderOptions,
104
+ logger, // optional - omit for no logging
105
+ });
87
106
 
88
107
  // Get text-to-speech service
89
- const textToSpeechService = LLMServiceFactory.getTextToSpeechService(
90
- LLMProviders.OpenAI,
91
- logger, // your logger instance
92
- llmProviderOptions,
93
- );
108
+ const textToSpeechService = LLMServiceFactory.getTextToSpeechService({
109
+ provider: LLMProviders.OpenAI,
110
+ options: llmProviderOptions,
111
+ logger, // optional - omit for no logging
112
+ });
94
113
  ```
95
114
 
96
115
  ### Real-world Example
@@ -128,29 +147,29 @@ class MyUseCase {
128
147
  },
129
148
  );
130
149
 
131
- this.llmCompletionService = LLMServiceFactory.getCompletionService(
132
- config.llmProvider,
150
+ this.llmCompletionService = LLMServiceFactory.getCompletionService({
151
+ provider: config.llmProvider,
152
+ options: llmProviderOptions,
133
153
  logger,
134
- llmProviderOptions,
135
- );
154
+ });
136
155
 
137
- this.llmAssistanceService = LLMServiceFactory.getAssistanceService(
138
- config.llmProvider,
156
+ this.llmAssistanceService = LLMServiceFactory.getAssistanceService({
157
+ provider: config.llmProvider,
158
+ options: llmProviderOptions,
139
159
  logger,
140
- llmProviderOptions,
141
- );
160
+ });
142
161
 
143
- this.llmSpeechToTextService = LLMServiceFactory.getSpeechToTextService(
144
- config.llmProvider,
162
+ this.llmSpeechToTextService = LLMServiceFactory.getSpeechToTextService({
163
+ provider: config.llmProvider,
164
+ options: llmProviderOptions,
145
165
  logger,
146
- llmProviderOptions,
147
- );
166
+ });
148
167
 
149
- this.llmTextToSpeechService = LLMServiceFactory.getTextToSpeechService(
150
- config.llmProvider,
168
+ this.llmTextToSpeechService = LLMServiceFactory.getTextToSpeechService({
169
+ provider: config.llmProvider,
170
+ options: llmProviderOptions,
151
171
  logger,
152
- llmProviderOptions,
153
- );
172
+ });
154
173
  }
155
174
 
156
175
  async processRequest(prompt) {
@@ -257,7 +276,7 @@ const result = await completionService.sendMessage({
257
276
  // Type-safe access to structured data
258
277
  if (result.data) {
259
278
  console.log(result.data.name); // string
260
- console.log(result.data.age); // number
279
+ console.log(result.data.age); // number
261
280
  console.log(result.data.email); // string
262
281
  console.log(result.data.isActive); // boolean
263
282
  }
@@ -380,6 +399,65 @@ if (result.data) {
380
399
  - Use `.min()` and `.max()` instead of `.positive()`, `.negative()`
381
400
  - Test schemas with both providers if cross-compatibility is important
382
401
 
402
+ #### Schema Architecture
403
+
404
+ The package uses a **driver pattern** for schema conversion, automatically adapting schemas to each provider's specific format while maintaining a unified API.
405
+
406
+ Schema adapters are automatically registered when providers are imported, so no manual configuration is needed. For detailed architecture information and extending with new providers, see the [Developer Guide](#developer-guide).
407
+
408
+ ### Model Configuration
409
+
410
+ Each model comes with default configuration values that can be customized for your specific needs.
411
+
412
+ #### Accessing and Customizing Models
413
+
414
+ ```typescript
415
+ // Get available models from the service
416
+ const models = completionService.models;
417
+
418
+ // Get a specific model
419
+ const model = models[OpenAIModelNames.GPT_4_OMNI];
420
+
421
+ // Customize model configuration
422
+ const customModel = {
423
+ ...model,
424
+ config: {
425
+ ...model.config,
426
+ temperature: 0.8, // Override temperature (0-2, controls randomness)
427
+ top_p: 0.9, // Override top_p (nucleus sampling)
428
+ }
429
+ };
430
+
431
+ // Use customized model in requests
432
+ const result = await completionService.sendMessage({
433
+ message: {
434
+ role: LLMRoles.User,
435
+ content: [{ type: LLMMessageContentType.TEXT, text: 'Hello!' }]
436
+ },
437
+ model: customModel,
438
+ });
439
+ ```
440
+
441
+ #### Model-Specific Configuration
442
+
443
+ **GPT-5 Models** have special configuration options:
444
+
445
+ ```typescript
446
+ const gpt5Model = models[OpenAIModelNames.GPT_5];
447
+
448
+ const customGPT5Model = {
449
+ ...gpt5Model,
450
+ config: {
451
+ ...gpt5Model.config,
452
+ temperature: 1, // Note: GPT-5 only supports temperature=1
453
+ reasoning_effort: 'high', // 'minimal' | 'medium' | 'high'
454
+ verbosity: 'low', // 'low' | 'medium' | 'high'
455
+ }
456
+ };
457
+ ```
458
+
459
+ **Note:** GPT-5 models require `temperature: 1` and cannot be changed. GPT-5-MINI only supports the default temperature.
460
+
383
461
  #### File-based Assistance
384
462
 
385
463
  ```typescript
@@ -390,11 +468,11 @@ const uploadedFile = await assistanceService.uploadFile({
390
468
  mimeType: LLMUploadFileMimeTypes.PLAIN_TEXT,
391
469
  });
392
470
 
393
- // Create file storage
471
+ // Create file storage with instructions
394
472
  const storage = await assistanceService.createFileStorage({
395
473
  uploadedFiles: [uploadedFile],
396
474
  model: preferredModel,
397
- instructions: 'Help me analyze this document',
475
+ instructions: 'Help me analyze this document', // Optional context for the storage
398
476
  });
399
477
 
400
478
  // Create chat with file context
@@ -403,7 +481,7 @@ const chat = await assistanceService.createChat({
403
481
  model: preferredModel,
404
482
  history: [],
405
483
  files: [uploadedFile],
406
- instructions: 'Answer questions about the uploaded document',
484
+ instructions: 'Answer questions about the uploaded document', // Initial assistant instructions
407
485
  });
408
486
  ```
409
487
 
@@ -414,7 +492,7 @@ const transcription = await speechToTextService.transcribe({
414
492
  pathToAudio: '/path/to/audio.mp3',
415
493
  mimeType: LLMUploadFileMimeTypes.AUDIO_MP3, // Optional, but recommended
416
494
  model: preferredModel,
417
- instructions: 'Transcribe the audio file', // Optional instructions
495
+ instructions: 'Transcribe the audio file', // Optional: custom prompt for transcription context
418
496
  });
419
497
 
420
498
  console.log(transcription.text); // Transcribed text
@@ -563,6 +641,12 @@ Interface for text completion services.
563
641
  #### Methods
564
642
 
565
643
  - `sendMessage(options)`: Send a message to the LLM and get a completion response
644
+ - `message`: The message to send
645
+ - `model`: The LLM model to use (with optional config overrides)
646
+ - `history`: Optional conversation history
647
+ - `instructions`: Optional system instructions to guide the model's behavior
648
+ - `schema`: Optional schema for structured output
649
+ - `abortSignal`: Optional abort signal for cancellation
566
650
 
567
651
  ### LLMAssistanceService
568
652
 
@@ -573,10 +657,22 @@ Interface for chat/assistance services with file handling capabilities.
573
657
  - `uploadFile(fileOptions)`: Upload a file to the LLM service
574
658
  - `deleteFile(fileId)`: Delete a file from the LLM service
575
659
  - `createFileStorage(options)`: Create a file storage for organizing files
660
+ - `uploadedFiles`: Array of previously uploaded files
661
+ - `model`: The LLM model to use
662
+ - `instructions`: Optional instructions for how to use the stored files
576
663
  - `deleteFileStorage(fileStorageId)`: Delete a file storage
577
664
  - `createAssistant(options)`: Create an AI assistant with specific instructions
665
+ - `model`: The LLM model to use
666
+ - `instructions`: Optional instructions defining the assistant's behavior
667
+ - `storageIds`: Optional array of storage IDs to give the assistant access to
668
+ - `name`: Optional name for the assistant
578
669
  - `deleteAssistant(assistantId)`: Delete an assistant
579
670
  - `createChat(options)`: Create a new chat/conversation
671
+ - `model`: The LLM model to use
672
+ - `instructions`: Optional initial instructions for the conversation
673
+ - `history`: Optional conversation history
674
+ - `files`: Optional array of uploaded files
675
+ - `storageId`: Optional storage ID to use
580
676
  - `deleteChat(chatId)`: Delete a chat
581
677
  - `assistInChat(options)`: Send a message in an existing chat and get an assistant response
582
678
  - `countTokens(messages, model)`: Count tokens in messages for context management
@@ -588,6 +684,10 @@ Interface for converting speech audio to text.
588
684
  #### Methods
589
685
 
590
686
  - `transcribe(options)`: Convert audio file to text transcription
687
+ - `pathToAudio`: Path to the audio file
688
+ - `mimeType`: Optional MIME type of the audio file
689
+ - `model`: The LLM model to use
690
+ - `instructions`: Optional custom transcription prompt or context
591
691
 
592
692
  ### LLMTextToSpeechService
593
693
 
@@ -665,6 +765,7 @@ const fullLesson = coursePrompt({
665
765
  const basicLesson = coursePrompt({
666
766
  topicTitle: 'React Hooks',
667
767
  hasPrerequisites: false,
768
+ prerequisites: '',
668
769
  includeExercises: false,
669
770
  difficultyLevel: 'beginner'
670
771
  });
@@ -733,7 +834,7 @@ const reviewInstruction = codeReviewPrompt({
733
834
  outputFormat: 'structured',
734
835
  includeCode: true,
735
836
  codeSnippet: 'function example() { /* code here */ }',
736
- provideExamples: false
837
+ provideExamples: false,
737
838
  });
738
839
  ```
739
840
 
@@ -887,15 +988,33 @@ Creates a prompt template function from a template string.
887
988
  Supports all service types: completion, assistance, speech-to-text, and text-to-speech APIs. For more information, see [OpenAI API documentation](https://platform.openai.com/docs/api-reference).
888
989
 
889
990
  **Available Models:**
890
- - GPT-4 models for completion and assistance
891
- - TTS models for text-to-speech generation
991
+
992
+ | Model | Purpose | Max Input | Max Output | Notes |
993
+ |-------|---------|-----------|------------|-------|
994
+ | gpt-4o | Completion, Assistance | 128K | 16K | General purpose, balanced |
995
+ | gpt-4o-mini | Completion, Assistance | 128K | 16K | Faster, cost-effective |
996
+ | gpt-4.1 | Completion, Assistance | 1M+ | 32K | Extended context window |
997
+ | gpt-5 | Completion, Assistance | 400K | 128K | Advanced reasoning, requires temperature=1 |
998
+ | gpt-5-mini | Completion, Assistance | 400K | 128K | Smaller GPT-5 variant |
999
+ | gpt-4o-transcribe | Speech-to-Text | 16K | 2K | Optimized for transcription |
1000
+ | gpt-4o-mini-transcribe | Speech-to-Text | 16K | 2K | Cost-effective transcription |
1001
+ | tts-1 | Text-to-Speech | - | - | Standard TTS model |
1002
+ | gpt-4o-mini-tts | Text-to-Speech | - | - | Alternative TTS model |
892
1003
 
893
1004
  ### Google Generative AI
894
1005
 
895
- Supports completion and assistance APIs through Google's Generative AI models. For more information, see [Google Generative AI documentation](https://ai.google.dev/docs).
1006
+ Supports completion, assistance, speech-to-text, and text-to-speech APIs through Google's Generative AI models. For more information, see [Google Generative AI documentation](https://ai.google.dev/docs).
896
1007
 
897
1008
  **Available Models:**
898
- - Gemini models for completion, assistance and speech-to-text
1009
+
1010
+ | Model | Purpose | Max Input | Max Output | Notes |
1011
+ |-------|---------|-----------|------------|-------|
1012
+ | gemini-2.5-flash | Completion, Assistance, Speech-to-Text | 1M+ | 65K | Fast, cost-effective, supports caching for long context |
1013
+ | gemini-2.5-pro | Completion, Assistance, Speech-to-Text | 1M+ | 65K | Advanced reasoning, supports caching for long context |
1014
+ | gemini-2.5-flash-preview-tts | Text-to-Speech | 8K | 16K | Preview TTS model with flash performance |
1015
+ | gemini-2.5-pro-preview-tts | Text-to-Speech | 8K | 16K | Preview TTS model with pro capabilities |
1016
+
1017
+ **Note:** Google Generative AI models support context caching for content longer than 32,768 tokens, which can significantly reduce costs for repeated queries on the same large context.
899
1018
 
900
1019
  ## Testing
901
1020
 
@@ -968,33 +1087,84 @@ The integration tests cover:
968
1087
 
969
1088
  The package provides several test utilities:
970
1089
 
971
- ```typescript
972
- import {
973
- mockLogger,
974
- resolveTestConfig,
975
- hasText,
976
- hasError,
977
- } from '@mate-academy/llm-gateway/tests/helpers';
1090
+ #### resolveTestConfig Function
978
1091
 
979
- // Mock logger for testing
980
- const logger = mockLogger;
1092
+ The `resolveTestConfig` function creates standardized test configurations for all supported providers based on the service purpose:
1093
+
1094
+ ```typescript
1095
+ import { resolveTestConfig } from '@mate-academy/llm-gateway/tests/helpers';
981
1096
 
982
- // Get test configuration for specific service purpose
1097
+ // Get test config for completion services
983
1098
  const testConfig = resolveTestConfig(LLMPurposes.Completion);
984
1099
 
1100
+ // testConfig contains configuration for all providers:
1101
+ // {
1102
+ // [LLMProviders.OpenAI]: {
1103
+ // provider: LLMProviders.OpenAI,
1104
+ // availableModels: {...}, // Models available for completion
1105
+ // clientOptions: { apiKey: process.env.OPENAI_SECRET_API_KEY, ... },
1106
+ // requireCredentials: () => void, // Throws if credentials missing
1107
+ // isEnabled: true
1108
+ // },
1109
+ // [LLMProviders.GoogleGenerativeAI]: {
1110
+ // provider: LLMProviders.GoogleGenerativeAI,
1111
+ // availableModels: {...}, // Models available for completion
1112
+ // clientOptions: { apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY },
1113
+ // requireCredentials: () => void, // Throws if credentials missing
1114
+ // isEnabled: true
1115
+ // }
1116
+ // }
1117
+
1118
+ // Use in tests to iterate over all providers
1119
+ Object.values(testConfig).forEach((config) => {
1120
+ const { provider, clientOptions, availableModels, requireCredentials } = config;
1121
+
1122
+ describe(`${provider} Provider`, () => {
1123
+ beforeAll(() => {
1124
+ requireCredentials(); // Ensures API keys are present
1125
+ });
1126
+
1127
+ it('should create service', () => {
1128
+ const service = LLMServiceFactory.getCompletionService({
1129
+ provider,
1130
+ options: clientOptions,
1131
+ logger: mockLogger, // Optional
1132
+ });
1133
+ expect(service).toBeDefined();
1134
+ });
1135
+ });
1136
+ });
1137
+ ```
1138
+
1139
+ #### Mock Logger
1140
+
1141
+ ```typescript
1142
+ // Note: Test helpers are for internal use only
1143
+ // When testing your integration, create your own mock logger:
1144
+ const mockLogger = {
1145
+ info: jest.fn(),
1146
+ error: jest.fn(),
1147
+ warn: jest.fn(),
1148
+ child: jest.fn(() => mockLogger),
1149
+ };
1150
+ ```
1151
+
1152
+ #### Type Guards
1153
+
1154
+ ```typescript
985
1155
  // Type guards for test assertions
986
- if (hasText(result)) {
1156
+ if ('text' in result && result.text) {
987
1157
  expect(result.text).toContain('expected content');
988
1158
  }
989
1159
 
990
- if (hasError(result)) {
1160
+ if ('error' in result && result.error) {
991
1161
  expect(result.error).toBeDefined();
992
1162
  }
993
1163
  ```
994
1164
 
995
1165
  ### Writing Custom Tests
996
1166
 
997
- Example of writing a custom integration test:
1167
+ Example of writing a custom integration test using `resolveTestConfig`:
998
1168
 
999
1169
  ```typescript
1000
1170
  import {
@@ -1007,34 +1177,109 @@ import {
1007
1177
  LLMServiceFactory,
1008
1178
  LLMProviders,
1009
1179
  LLMPurposes,
1180
+ resolveTestConfig,
1010
1181
  } from '@mate-academy/llm-gateway';
1011
- import { mockLogger, resolveTestConfig } from '@mate-academy/llm-gateway/tests/helpers';
1182
+
1183
+ // Create your own mock logger
1184
+ const mockLogger = {
1185
+ info: jest.fn(),
1186
+ error: jest.fn(),
1187
+ warn: jest.fn(),
1188
+ child: jest.fn(() => mockLogger),
1189
+ };
1012
1190
 
1013
1191
  describe('Custom LLM Integration Test', () => {
1192
+ // Use resolveTestConfig for consistent test configuration
1014
1193
  const testConfig = resolveTestConfig(LLMPurposes.Completion);
1015
1194
 
1016
- Object.values(testConfig).forEach(({ provider, clientOptions, requireCredentials }) => {
1195
+ // Test all enabled providers
1196
+ Object.values(testConfig).forEach((config) => {
1197
+ const { provider, clientOptions, requireCredentials } = config;
1198
+
1017
1199
  describe(`${provider} Provider`, () => {
1018
1200
  let service;
1019
1201
 
1020
1202
  beforeAll(() => {
1021
- requireCredentials();
1022
- service = LLMServiceFactory.getCompletionService(
1203
+ requireCredentials(); // Validates API keys are present
1204
+
1205
+ service = LLMServiceFactory.getCompletionService({
1023
1206
  provider,
1024
- mockLogger,
1025
- clientOptions
1026
- );
1207
+ options: clientOptions,
1208
+ logger: mockLogger, // Optional - can be omitted
1209
+ });
1027
1210
  });
1028
1211
 
1029
1212
  it('should process custom request', async () => {
1030
1213
  // Your custom test logic here
1214
+ const result = await service.sendMessage({
1215
+ message: {
1216
+ role: LLMRoles.User,
1217
+ content: [{ type: LLMMessageContentType.TEXT, text: 'Test message' }],
1218
+ },
1219
+ model: Object.values(config.availableModels)[0], // Use first available model
1220
+ });
1221
+
1222
+ // Use type guards for assertions
1223
+ if ('text' in result && result.text) {
1224
+ expect(result.text).toBeDefined();
1225
+ expect(typeof result.text).toBe('string');
1226
+ } else if ('error' in result && result.error) {
1227
+ throw result.error;
1228
+ }
1031
1229
  });
1032
1230
  });
1033
1231
  });
1034
1232
  });
1035
1233
  ```
1036
1234
 
1037
- ## Developer Guide: Adding a New Provider
1235
+ ## Developer Guide
1236
+
1237
+ ### Schema Architecture Overview
1238
+
1239
+ The LLM Gateway uses a **driver pattern** for schema conversion, providing clean separation between core schema logic and provider-specific implementations.
1240
+
1241
+ **Core Components:**
1242
+
1243
+ - **`LLMSchema`**: Core schema builder with unified API
1244
+ - **`SchemaAdapterInterface`**: Simple contract for provider-specific schema converters
1245
+ - **`SCHEMA_ADAPTER_REGISTRY`**: Type-safe registry ensuring all providers are handled
1246
+ - **Provider Adapters**: Convert generic JSON Schema to provider-specific formats
1247
+
1248
+ **How It Works:**
1249
+
1250
+ ```typescript
1251
+ // The schema uses a unified API regardless of provider
1252
+ const schema = LLMSchema.object({
1253
+ name: LLMSchema.string(),
1254
+ age: LLMSchema.number().min(1),
1255
+ });
1256
+
1257
+ // Internally, services call _toProviderSchema() which automatically
1258
+ // converts to the correct provider-specific format:
1259
+ schema._toProviderSchema(LLMProviders.OpenAI); // → OpenAI JSON Schema format
1260
+ schema._toProviderSchema(LLMProviders.GoogleGenerativeAI); // → Google Type-based format
1261
+ ```
1262
+
1263
+ **Provider Schema Adapters:**
1264
+
1265
+ Each provider has its own schema adapter located in `src/providers/{Provider}/schemas/`:
1266
+
1267
+ - **`OpenAISchemaAdapter`**: Converts to OpenAI's JSON Schema format, handles strict mode requirements
1268
+ - **`GoogleSchemaAdapter`**: Converts to Google's Type-based schema format using their `Type` enum
1269
+
1270
+ **Type-Safe Registry:**
1271
+
1272
+ Schema adapters are managed through a centralized, type-safe registry that ensures compile-time safety:
1273
+
1274
+ ```typescript
1275
+ // src/utilities/schema/adapters/SchemaAdapterRegistry.ts
1276
+ export const SCHEMA_ADAPTER_REGISTRY = {
1277
+ OpenAI: new OpenAISchemaAdapter(),
1278
+ GoogleGenerativeAI: new GoogleSchemaAdapter(),
1279
+ } as const satisfies Record<LLMProviders, SchemaAdapterInterface | null>;
1280
+ ```
1281
+
1282
+ ### Adding a New Provider
1038
1283
 
1039
1284
  To add support for a new LLM provider, follow these steps:
1040
1285
 
@@ -1049,6 +1294,8 @@ src/providers/YourProvider/
1049
1294
  ├── YourProvider.entity.ts # Provider-specific entity
1050
1295
  ├── YourProvider.typedefs.ts # TypeScript definitions
1051
1296
  ├── YourProviderService.factory.ts # Factory for your provider's services
1297
+ ├── schemas/ # Schema conversion adapters
1298
+ │ └── YourProviderSchemaAdapter.ts
1052
1299
  └── services/ # Provider service implementations
1053
1300
  ├── index.ts
1054
1301
  ├── YourProviderCompletionService.ts
@@ -1123,7 +1370,60 @@ export type LLMModelName = {
1123
1370
  };
1124
1371
  ```
1125
1372
 
1126
- ### 4. Implement Provider Constants
1373
+ ### 4. Create Schema Adapter
1374
+
1375
+ Implement a schema adapter in `src/providers/YourProvider/schemas/YourProviderSchemaAdapter.ts`:
1376
+
1377
+ ```typescript
1378
+ import { LLMProviders } from '@/LLMService.typedefs';
1379
+ import type { SchemaAdapterInterface } from '@/utilities/schema/adapters/SchemaAdapterInterface';
1380
+
1381
+ export class YourProviderSchemaAdapter implements SchemaAdapterInterface {
1382
+ convertSchema(jsonSchema: any): any {
1383
+ // Convert JSON Schema to your provider's specific format
1384
+ // Example: transform to provider-specific schema structure
1385
+ return this.transformToYourProviderFormat(jsonSchema);
1386
+ }
1387
+
1388
+ private transformToYourProviderFormat(jsonSchema: any): any {
1389
+ // Implement provider-specific schema transformation logic
1390
+ // Handle objects, arrays, strings, numbers, etc.
1391
+ // Return the schema in your provider's expected format
1392
+
1393
+ if (jsonSchema.type === 'object') {
1394
+ // Handle object schemas
1395
+ return {
1396
+ // Your provider's object schema format
1397
+ };
1398
+ }
1399
+
1400
+ // Handle other schema types...
1401
+ return jsonSchema;
1402
+ }
1403
+ }
1404
+ ```
1405
+
1406
+ ### 5. Add Adapter to Schema Registry
1407
+
1408
+ Update the schema adapter registry in `src/utilities/schema/adapters/SchemaAdapterRegistry.ts`:
1409
+
1410
+ ```typescript
1411
+ import { YourProviderSchemaAdapter } from '@/providers/YourProvider/schemas/YourProviderSchemaAdapter';
1412
+
1413
+ export const SCHEMA_ADAPTER_REGISTRY = {
1414
+ OpenAI: new OpenAISchemaAdapter(),
1415
+ GoogleGenerativeAI: new GoogleSchemaAdapter(),
1416
+ YourProvider: new YourProviderSchemaAdapter(), // Add your adapter here
1417
+ // TypeScript will enforce that ALL providers have adapters
1418
+ } as const satisfies Record<LLMProviders, SchemaAdapterInterface | null>;
1419
+ ```
1420
+
1421
+ If your provider doesn't support structured output, set it to `null`:
1422
+ ```typescript
1423
+ YourProvider: null, // Provider doesn't support structured output
1424
+ ```
1425
+
1426
+ ### 6. Implement Provider Constants
1127
1427
 
1128
1428
  Define constants in `src/providers/YourProvider/YourProvider.constants.ts`:
1129
1429
 
@@ -1221,7 +1521,7 @@ export const YOUR_PROVIDER_SERVICE_BUILDERS: {
1221
1521
  };
1222
1522
  ```
1223
1523
 
1224
- ### 5. Implement Provider Entity (if needed)
1524
+ ### 6. Implement Provider Entity (if needed)
1225
1525
 
1226
1526
  Create the entity class in `src/providers/YourProvider/YourProvider.entity.ts`:
1227
1527
 
@@ -1231,28 +1531,28 @@ export class YourProviderEntity {
1231
1531
  }
1232
1532
  ```
1233
1533
 
1234
- ### 6. Implement Service Classes
1534
+ ### 7. Implement Service Classes
1235
1535
 
1236
1536
  Create service implementations in the `services` directory:
1237
1537
 
1238
1538
  **CompletionService (src/providers/YourProvider/services/YourProviderCompletionService.ts)**:
1239
1539
 
1240
1540
  ```typescript
1241
- import { Logger } from '../../../LLMService.typedefs';
1541
+ import { LLMLogger } from '../../../LLMService.typedefs';
1242
1542
  import { LLMCompletionService } from '../../../services/LLMCompletionService.abstract';
1243
1543
  import { CompletionParams, CompletionResult, LLMProviders } from '../../../LLMService.typedefs';
1244
1544
  import { YourProviderEntity } from '../YourProvider.entity';
1245
1545
 
1246
1546
  export class YourProviderCompletionService extends LLMCompletionService<LLMProviders.YourProvider> {
1247
1547
  constructor(
1248
- logger: Logger,
1548
+ logger: LLMLogger | undefined,
1249
1549
  private readonly providerEntity: YourProviderEntity,
1250
1550
  ) {
1251
1551
  super(logger);
1252
1552
  }
1253
1553
 
1254
1554
  async complete(params: CompletionParams): Promise<CompletionResult> {
1255
- this.logger.info('Starting completion with YourProvider', { params });
1555
+ this.logger?.info('Starting completion with YourProvider', { params });
1256
1556
 
1257
1557
  try {
1258
1558
  // Implement provider-specific completion logic
@@ -1262,7 +1562,7 @@ export class YourProviderCompletionService extends LLMCompletionService<LLMProvi
1262
1562
  // Include other required fields
1263
1563
  };
1264
1564
  } catch (error) {
1265
- this.logger.error('Error in YourProvider completion', { error });
1565
+ this.logger?.error('Error in YourProvider completion', { error });
1266
1566
  throw error;
1267
1567
  }
1268
1568
  }
@@ -1272,7 +1572,7 @@ export class YourProviderCompletionService extends LLMCompletionService<LLMProvi
1272
1572
  **AssistanceService (if applicable)**:
1273
1573
 
1274
1574
  ```typescript
1275
- import { Logger } from '../../../LLMService.typedefs';
1575
+ import { LLMLogger } from '../../../LLMService.typedefs';
1276
1576
  import { LLMAssistanceService } from '../../../services/LLMAssistanceService.abstract';
1277
1577
  import {
1278
1578
  LLMAssistanceOptions,
@@ -1283,7 +1583,7 @@ import { YourProviderEntity } from '../YourProvider.entity';
1283
1583
 
1284
1584
  export class YourProviderAssistanceService extends LLMAssistanceService<LLMProviders.YourProvider> {
1285
1585
  constructor(
1286
- logger: Logger,
1586
+ logger: LLMLogger,
1287
1587
  private readonly providerEntity: YourProviderEntity,
1288
1588
  ) {
1289
1589
  super(logger);
@@ -1311,7 +1611,7 @@ export class YourProviderAssistanceService extends LLMAssistanceService<LLMProvi
1311
1611
  **SpeechToTextService (if applicable)**:
1312
1612
 
1313
1613
  ```typescript
1314
- import { Logger } from '../../../LLMService.typedefs';
1614
+ import { LLMLogger } from '../../../LLMService.typedefs';
1315
1615
  import { LLMSpeechToTextService } from '../../../services/LLMSpeechToTextService.abstract';
1316
1616
  import {
1317
1617
  LLMTranscribeOptions,
@@ -1322,14 +1622,14 @@ import { YourProviderEntity } from '../YourProvider.entity';
1322
1622
 
1323
1623
  export class YourProviderSpeechToTextService extends LLMSpeechToTextService<LLMProviders.YourProvider> {
1324
1624
  constructor(
1325
- logger: Logger,
1625
+ logger: LLMLogger | undefined,
1326
1626
  private readonly providerEntity: YourProviderEntity,
1327
1627
  ) {
1328
1628
  super(logger);
1329
1629
  }
1330
1630
 
1331
1631
  async transcribe(options: LLMTranscribeOptions<typeof this.provider>): Promise<LLMTranscribeResult> {
1332
- this.logger.info('Starting transcription with YourProvider', { options });
1632
+ this.logger?.info('Starting transcription with YourProvider', { options });
1333
1633
 
1334
1634
  try {
1335
1635
  // Implement provider-specific transcription logic
@@ -1337,7 +1637,7 @@ export class YourProviderSpeechToTextService extends LLMSpeechToTextService<LLMP
1337
1637
  text: 'Transcribed text from audio',
1338
1638
  };
1339
1639
  } catch (error) {
1340
- this.logger.error('Error in YourProvider transcription', { error });
1640
+ this.logger?.error('Error in YourProvider transcription', { error });
1341
1641
  throw error;
1342
1642
  }
1343
1643
  }
@@ -1347,7 +1647,7 @@ export class YourProviderSpeechToTextService extends LLMSpeechToTextService<LLMP
1347
1647
  **TextToSpeechService (if applicable)**:
1348
1648
 
1349
1649
  ```typescript
1350
- import { Logger } from '../../../LLMService.typedefs';
1650
+ import { LLMLogger } from '../../../LLMService.typedefs';
1351
1651
  import { LLMTextToSpeechService } from '../../../services/LLMTextToSpeechService.abstract';
1352
1652
  import {
1353
1653
  LLMCreateSpeechOptions,
@@ -1358,14 +1658,14 @@ import { YourProviderEntity } from '../YourProvider.entity';
1358
1658
 
1359
1659
  export class YourProviderTextToSpeechService extends LLMTextToSpeechService<LLMProviders.YourProvider> {
1360
1660
  constructor(
1361
- logger: Logger,
1661
+ logger: LLMLogger | undefined,
1362
1662
  private readonly providerEntity: YourProviderEntity,
1363
1663
  ) {
1364
1664
  super(logger);
1365
1665
  }
1366
1666
 
1367
1667
  async createSpeech(options: LLMCreateSpeechOptions<typeof this.provider>): Promise<LLMCreateSpeechResult> {
1368
- this.logger.info('Starting speech creation with YourProvider', { options });
1668
+ this.logger?.info('Starting speech creation with YourProvider', { options });
1369
1669
 
1370
1670
  try {
1371
1671
  // Implement provider-specific speech creation logic
@@ -1374,14 +1674,14 @@ export class YourProviderTextToSpeechService extends LLMTextToSpeechService<LLMP
1374
1674
  mimeType: 'audio/mp3',
1375
1675
  };
1376
1676
  } catch (error) {
1377
- this.logger.error('Error in YourProvider speech creation', { error });
1677
+ this.logger?.error('Error in YourProvider speech creation', { error });
1378
1678
  throw error;
1379
1679
  }
1380
1680
  }
1381
1681
  }
1382
1682
  ```
1383
1683
 
1384
- ### 7. Create Service Factory
1684
+ ### 8. Create Service Factory
1385
1685
 
1386
1686
  First, define service builders in `src/providers/YourProvider/YourProvider.constants.ts`:
1387
1687
 
@@ -1412,14 +1712,13 @@ export const YOUR_PROVIDER_SERVICE_BUILDERS: {
1412
1712
  Then, implement the service factory in `src/providers/YourProvider/YourProviderService.factory.ts`:
1413
1713
 
1414
1714
  ```typescript
1415
- import { type Logger } from '@mate-academy/core';
1416
1715
  import {
1417
1716
  type LLMInstanceOptions,
1418
1717
  LLMProviders,
1419
1718
  type LLMPurposes,
1420
1719
  type LLMServiceByPurpose,
1421
1720
  } from '../../LLMService.typedefs';
1422
- import { LLMServicePurposeFactory } from '../../services';
1721
+ import { LLMServicePurposeFactory, type LLMCreateServiceOptions } from '../../services';
1423
1722
  import { YOUR_PROVIDER_SERVICE_BUILDERS } from './YourProvider.constants';
1424
1723
 
1425
1724
  export class YourProviderServiceFactory extends LLMServicePurposeFactory<
@@ -1428,10 +1727,9 @@ export class YourProviderServiceFactory extends LLMServicePurposeFactory<
1428
1727
  createService<
1429
1728
  Purpose extends LLMPurposes
1430
1729
  >(
1431
- purpose: Purpose,
1432
- logger: Logger,
1433
- options: LLMInstanceOptions[LLMProviders.YourProvider],
1730
+ serviceOptions: LLMCreateServiceOptions<LLMProviders.YourProvider, Purpose>,
1434
1731
  ): LLMServiceByPurpose<LLMProviders.YourProvider>[Purpose] {
1732
+ const { purpose, logger, options } = serviceOptions;
1435
1733
  const serviceBuilder = YOUR_PROVIDER_SERVICE_BUILDERS[purpose];
1436
1734
 
1437
1735
  if (!serviceBuilder) {
@@ -1443,7 +1741,7 @@ export class YourProviderServiceFactory extends LLMServicePurposeFactory<
1443
1741
  }
1444
1742
  ```
1445
1743
 
1446
- ### 8. Update Entry Point Files
1744
+ ### 9. Update Entry Point Files
1447
1745
 
1448
1746
  Update the provider's `index.ts`:
1449
1747
 
@@ -1462,14 +1760,14 @@ Update the main providers `index.ts` at `src/providers/index.ts`:
1462
1760
  export * from './YourProvider';
1463
1761
  ```
1464
1762
 
1465
- ### 9. Update LLM Service Factory
1763
+ ### 10. Update LLM Service Factory
1466
1764
 
1467
1765
  Modify `src/LLMService.factory.ts` to include your new provider:
1468
1766
 
1469
1767
  ```typescript
1470
1768
  import {
1471
1769
  LLMProviders,
1472
- Logger,
1770
+ LLMServiceOptions,
1473
1771
  YourProviderOptions,
1474
1772
  } from './LLMService.typedefs';
1475
1773
  import { YourProviderServiceFactory } from './providers/YourProvider';
@@ -1487,72 +1785,68 @@ export class LLMServiceFactory {
1487
1785
  }
1488
1786
 
1489
1787
  static getCompletionService<T extends LLMProviders>(
1490
- provider: T,
1491
- logger: Logger,
1492
- options: any,
1788
+ serviceOptions: LLMServiceOptions<T>,
1493
1789
  ) {
1790
+ const { provider, options, logger } = serviceOptions;
1494
1791
  switch (provider) {
1495
1792
  // ... other providers
1496
1793
  case LLMProviders.YourProvider:
1497
- return YourProviderServiceFactory.createService(
1498
- LLMPurposes.Completion,
1794
+ return YourProviderServiceFactory.createService({
1795
+ purpose: LLMPurposes.Completion,
1499
1796
  logger,
1500
1797
  options
1501
- );
1798
+ });
1502
1799
  default:
1503
1800
  throw new Error(`Unsupported provider: ${provider}`);
1504
1801
  }
1505
1802
  }
1506
1803
 
1507
1804
  static getAssistanceService<T extends LLMProviders>(
1508
- provider: T,
1509
- logger: Logger,
1510
- options: any,
1805
+ serviceOptions: LLMServiceOptions<T>,
1511
1806
  ) {
1807
+ const { provider, options, logger } = serviceOptions;
1512
1808
  switch (provider) {
1513
1809
  // ... other providers
1514
1810
  case LLMProviders.YourProvider:
1515
- return YourProviderServiceFactory.createService(
1516
- LLMPurposes.Assistance,
1811
+ return YourProviderServiceFactory.createService({
1812
+ purpose: LLMPurposes.Assistance,
1517
1813
  logger,
1518
1814
  options
1519
- );
1815
+ });
1520
1816
  default:
1521
1817
  throw new Error(`Unsupported provider: ${provider}`);
1522
1818
  }
1523
1819
  }
1524
1820
 
1525
1821
  static getSpeechToTextService<T extends LLMProviders>(
1526
- provider: T,
1527
- logger: Logger,
1528
- options: any,
1822
+ serviceOptions: LLMServiceOptions<T>,
1529
1823
  ) {
1824
+ const { provider, options, logger } = serviceOptions;
1530
1825
  switch (provider) {
1531
1826
  // ... other providers
1532
1827
  case LLMProviders.YourProvider:
1533
- return YourProviderServiceFactory.createService(
1534
- LLMPurposes.SpeechToText,
1828
+ return YourProviderServiceFactory.createService({
1829
+ purpose: LLMPurposes.SpeechToText,
1535
1830
  logger,
1536
1831
  options
1537
- );
1832
+ });
1538
1833
  default:
1539
1834
  throw new Error(`Unsupported provider: ${provider}`);
1540
1835
  }
1541
1836
  }
1542
1837
 
1543
1838
  static getTextToSpeechService<T extends LLMProviders>(
1544
- provider: T,
1545
- logger: Logger,
1546
- options: any,
1839
+ serviceOptions: LLMServiceOptions<T>,
1547
1840
  ) {
1841
+ const { provider, options, logger } = serviceOptions;
1548
1842
  switch (provider) {
1549
1843
  // ... other providers
1550
1844
  case LLMProviders.YourProvider:
1551
- return YourProviderServiceFactory.createService(
1552
- LLMPurposes.TextToSpeech,
1845
+ return YourProviderServiceFactory.createService({
1846
+ purpose: LLMPurposes.TextToSpeech,
1553
1847
  logger,
1554
1848
  options
1555
- );
1849
+ });
1556
1850
  default:
1557
1851
  throw new Error(`Unsupported provider: ${provider}`);
1558
1852
  }