@llumiverse/drivers 0.20.0 → 0.21.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.
Files changed (180) hide show
  1. package/lib/cjs/azure/azure_foundry.js +379 -0
  2. package/lib/cjs/azure/azure_foundry.js.map +1 -0
  3. package/lib/cjs/bedrock/index.js +8 -5
  4. package/lib/cjs/bedrock/index.js.map +1 -1
  5. package/lib/cjs/groq/index.js +91 -10
  6. package/lib/cjs/groq/index.js.map +1 -1
  7. package/lib/cjs/index.js +2 -1
  8. package/lib/cjs/index.js.map +1 -1
  9. package/lib/cjs/mistral/index.js +2 -1
  10. package/lib/cjs/mistral/index.js.map +1 -1
  11. package/lib/cjs/openai/azure_openai.js +72 -0
  12. package/lib/cjs/openai/azure_openai.js.map +1 -0
  13. package/lib/cjs/openai/index.js +6 -9
  14. package/lib/cjs/openai/index.js.map +1 -1
  15. package/lib/cjs/openai/openai.js +2 -2
  16. package/lib/cjs/openai/openai.js.map +1 -1
  17. package/lib/cjs/openai/openai_format.js +138 -0
  18. package/lib/cjs/openai/openai_format.js.map +1 -0
  19. package/lib/cjs/vertexai/models/claude.js +5 -3
  20. package/lib/cjs/vertexai/models/claude.js.map +1 -1
  21. package/lib/cjs/watsonx/index.js +1 -1
  22. package/lib/cjs/watsonx/index.js.map +1 -1
  23. package/lib/cjs/xai/index.js +3 -3
  24. package/lib/cjs/xai/index.js.map +1 -1
  25. package/lib/esm/azure/azure_foundry.js +373 -0
  26. package/lib/esm/azure/azure_foundry.js.map +1 -0
  27. package/lib/esm/bedrock/index.js +8 -5
  28. package/lib/esm/bedrock/index.js.map +1 -1
  29. package/lib/esm/groq/index.js +91 -10
  30. package/lib/esm/groq/index.js.map +1 -1
  31. package/lib/esm/index.js +2 -1
  32. package/lib/esm/index.js.map +1 -1
  33. package/lib/esm/mistral/index.js +2 -1
  34. package/lib/esm/mistral/index.js.map +1 -1
  35. package/lib/esm/openai/azure_openai.js +68 -0
  36. package/lib/esm/openai/azure_openai.js.map +1 -0
  37. package/lib/esm/openai/index.js +5 -8
  38. package/lib/esm/openai/index.js.map +1 -1
  39. package/lib/esm/openai/openai.js +2 -2
  40. package/lib/esm/openai/openai.js.map +1 -1
  41. package/lib/esm/openai/openai_format.js +134 -0
  42. package/lib/esm/openai/openai_format.js.map +1 -0
  43. package/lib/esm/src/adobe/firefly.js +115 -0
  44. package/lib/esm/src/adobe/firefly.js.map +1 -0
  45. package/lib/esm/src/bedrock/converse.js +278 -0
  46. package/lib/esm/src/bedrock/converse.js.map +1 -0
  47. package/lib/esm/src/bedrock/index.js +797 -0
  48. package/lib/esm/src/bedrock/index.js.map +1 -0
  49. package/lib/esm/src/bedrock/nova-image-payload.js +203 -0
  50. package/lib/esm/src/bedrock/nova-image-payload.js.map +1 -0
  51. package/lib/esm/src/bedrock/payloads.js +2 -0
  52. package/lib/esm/src/bedrock/payloads.js.map +1 -0
  53. package/lib/esm/src/bedrock/s3.js +99 -0
  54. package/lib/esm/src/bedrock/s3.js.map +1 -0
  55. package/lib/esm/src/groq/index.js +130 -0
  56. package/lib/esm/src/groq/index.js.map +1 -0
  57. package/lib/esm/src/huggingface_ie.js +196 -0
  58. package/lib/esm/src/huggingface_ie.js.map +1 -0
  59. package/lib/esm/src/index.js +13 -0
  60. package/lib/esm/src/index.js.map +1 -0
  61. package/lib/esm/src/mistral/index.js +167 -0
  62. package/lib/esm/src/mistral/index.js.map +1 -0
  63. package/lib/esm/src/mistral/types.js +80 -0
  64. package/lib/esm/src/mistral/types.js.map +1 -0
  65. package/{src/openai/azure.ts → lib/esm/src/openai/azure.js} +7 -34
  66. package/lib/esm/src/openai/azure.js.map +1 -0
  67. package/lib/esm/src/openai/index.js +463 -0
  68. package/lib/esm/src/openai/index.js.map +1 -0
  69. package/lib/esm/src/openai/openai.js +14 -0
  70. package/lib/esm/src/openai/openai.js.map +1 -0
  71. package/lib/esm/src/replicate.js +268 -0
  72. package/lib/esm/src/replicate.js.map +1 -0
  73. package/lib/esm/src/test/TestErrorCompletionStream.js +16 -0
  74. package/lib/esm/src/test/TestErrorCompletionStream.js.map +1 -0
  75. package/lib/esm/src/test/TestValidationErrorCompletionStream.js +20 -0
  76. package/lib/esm/src/test/TestValidationErrorCompletionStream.js.map +1 -0
  77. package/lib/esm/src/test/index.js +91 -0
  78. package/lib/esm/src/test/index.js.map +1 -0
  79. package/lib/esm/src/test/utils.js +25 -0
  80. package/lib/esm/src/test/utils.js.map +1 -0
  81. package/lib/esm/src/togetherai/index.js +122 -0
  82. package/lib/esm/src/togetherai/index.js.map +1 -0
  83. package/lib/esm/src/togetherai/interfaces.js +2 -0
  84. package/lib/esm/src/togetherai/interfaces.js.map +1 -0
  85. package/lib/esm/src/vertexai/debug.js +6 -0
  86. package/lib/esm/src/vertexai/debug.js.map +1 -0
  87. package/lib/esm/src/vertexai/embeddings/embeddings-image.js +24 -0
  88. package/lib/esm/src/vertexai/embeddings/embeddings-image.js.map +1 -0
  89. package/lib/esm/src/vertexai/embeddings/embeddings-text.js +20 -0
  90. package/lib/esm/src/vertexai/embeddings/embeddings-text.js.map +1 -0
  91. package/lib/esm/src/vertexai/index.js +270 -0
  92. package/lib/esm/src/vertexai/index.js.map +1 -0
  93. package/lib/esm/src/vertexai/models/claude.js +370 -0
  94. package/lib/esm/src/vertexai/models/claude.js.map +1 -0
  95. package/lib/esm/src/vertexai/models/gemini.js +700 -0
  96. package/lib/esm/src/vertexai/models/gemini.js.map +1 -0
  97. package/lib/esm/src/vertexai/models/imagen.js +310 -0
  98. package/lib/esm/src/vertexai/models/imagen.js.map +1 -0
  99. package/lib/esm/src/vertexai/models/llama.js +178 -0
  100. package/lib/esm/src/vertexai/models/llama.js.map +1 -0
  101. package/lib/esm/src/vertexai/models.js +21 -0
  102. package/lib/esm/src/vertexai/models.js.map +1 -0
  103. package/lib/esm/src/watsonx/index.js +157 -0
  104. package/lib/esm/src/watsonx/index.js.map +1 -0
  105. package/lib/esm/src/watsonx/interfaces.js +2 -0
  106. package/lib/esm/src/watsonx/interfaces.js.map +1 -0
  107. package/lib/esm/src/xai/index.js +64 -0
  108. package/lib/esm/src/xai/index.js.map +1 -0
  109. package/lib/esm/tsconfig.tsbuildinfo +1 -0
  110. package/lib/esm/vertexai/models/claude.js +5 -3
  111. package/lib/esm/vertexai/models/claude.js.map +1 -1
  112. package/lib/esm/watsonx/index.js +1 -1
  113. package/lib/esm/watsonx/index.js.map +1 -1
  114. package/lib/esm/xai/index.js +2 -2
  115. package/lib/esm/xai/index.js.map +1 -1
  116. package/lib/types/azure/azure_foundry.d.ts +50 -0
  117. package/lib/types/azure/azure_foundry.d.ts.map +1 -0
  118. package/lib/types/bedrock/index.d.ts.map +1 -1
  119. package/lib/types/groq/index.d.ts +5 -5
  120. package/lib/types/groq/index.d.ts.map +1 -1
  121. package/lib/types/index.d.ts +2 -1
  122. package/lib/types/index.d.ts.map +1 -1
  123. package/lib/types/mistral/index.d.ts +2 -2
  124. package/lib/types/mistral/index.d.ts.map +1 -1
  125. package/lib/types/openai/azure_openai.d.ts +25 -0
  126. package/lib/types/openai/azure_openai.d.ts.map +1 -0
  127. package/lib/types/openai/index.d.ts +6 -7
  128. package/lib/types/openai/index.d.ts.map +1 -1
  129. package/lib/types/openai/openai.d.ts +2 -2
  130. package/lib/types/openai/openai.d.ts.map +1 -1
  131. package/lib/types/openai/openai_format.d.ts +19 -0
  132. package/lib/types/openai/openai_format.d.ts.map +1 -0
  133. package/lib/types/src/adobe/firefly.d.ts +29 -0
  134. package/lib/types/src/bedrock/converse.d.ts +8 -0
  135. package/lib/types/src/bedrock/index.d.ts +57 -0
  136. package/lib/types/src/bedrock/nova-image-payload.d.ts +73 -0
  137. package/lib/types/src/bedrock/payloads.d.ts +11 -0
  138. package/lib/types/src/bedrock/s3.d.ts +22 -0
  139. package/lib/types/src/groq/index.d.ts +23 -0
  140. package/lib/types/src/huggingface_ie.d.ts +31 -0
  141. package/lib/types/src/index.d.ts +12 -0
  142. package/lib/types/src/mistral/index.d.ts +24 -0
  143. package/lib/types/src/mistral/types.d.ts +131 -0
  144. package/lib/types/src/openai/azure.d.ts +19 -0
  145. package/lib/types/src/openai/index.d.ts +25 -0
  146. package/lib/types/src/openai/openai.d.ts +14 -0
  147. package/lib/types/src/replicate.d.ts +44 -0
  148. package/lib/types/src/test/TestErrorCompletionStream.d.ts +8 -0
  149. package/lib/types/src/test/TestValidationErrorCompletionStream.d.ts +8 -0
  150. package/lib/types/src/test/index.d.ts +23 -0
  151. package/lib/types/src/test/utils.d.ts +4 -0
  152. package/lib/types/src/togetherai/index.d.ts +22 -0
  153. package/lib/types/src/togetherai/interfaces.d.ts +95 -0
  154. package/lib/types/src/vertexai/debug.d.ts +1 -0
  155. package/lib/types/src/vertexai/embeddings/embeddings-image.d.ts +10 -0
  156. package/lib/types/src/vertexai/embeddings/embeddings-text.d.ts +9 -0
  157. package/lib/types/src/vertexai/index.d.ts +49 -0
  158. package/lib/types/src/vertexai/models/claude.d.ts +17 -0
  159. package/lib/types/src/vertexai/models/gemini.d.ts +16 -0
  160. package/lib/types/src/vertexai/models/imagen.d.ts +74 -0
  161. package/lib/types/src/vertexai/models/llama.d.ts +19 -0
  162. package/lib/types/src/vertexai/models.d.ts +14 -0
  163. package/lib/types/src/watsonx/index.d.ts +26 -0
  164. package/lib/types/src/watsonx/interfaces.d.ts +64 -0
  165. package/lib/types/src/xai/index.d.ts +18 -0
  166. package/lib/types/vertexai/models/claude.d.ts.map +1 -1
  167. package/lib/types/xai/index.d.ts.map +1 -1
  168. package/package.json +20 -16
  169. package/src/azure/azure_foundry.ts +450 -0
  170. package/src/bedrock/index.ts +8 -5
  171. package/src/groq/index.ts +107 -16
  172. package/src/index.ts +2 -1
  173. package/src/mistral/index.ts +3 -2
  174. package/src/openai/azure_openai.ts +92 -0
  175. package/src/openai/index.ts +19 -22
  176. package/src/openai/openai.ts +2 -5
  177. package/src/openai/openai_format.ts +165 -0
  178. package/src/vertexai/models/claude.ts +5 -3
  179. package/src/watsonx/index.ts +5 -5
  180. package/src/xai/index.ts +2 -3
@@ -0,0 +1,450 @@
1
+ import { DefaultAzureCredential, getBearerTokenProvider, TokenCredential } from "@azure/identity";
2
+ import { AbstractDriver, AIModel, Completion, CompletionChunk, DriverOptions, EmbeddingsOptions, EmbeddingsResult, ExecutionOptions, getModelCapabilities, modelModalitiesToArray, Providers } from "@llumiverse/core";
3
+ import { AIProjectClient, DeploymentUnion, ModelDeployment } from '@azure/ai-projects';
4
+ import { isUnexpected } from "@azure-rest/ai-inference";
5
+ import { ChatCompletionMessageParam } from "openai/resources";
6
+ import type {
7
+ ChatCompletionsOutput,
8
+ ChatCompletionsToolCall,
9
+ ChatRequestMessage,
10
+ } from "@azure-rest/ai-inference";
11
+ import { AzureOpenAIDriver } from "../openai/azure_openai.js";
12
+ import { createSseStream, NodeJSReadableStream } from "@azure/core-sse";
13
+ import { formatOpenAILikeMultimodalPrompt } from "../openai/openai_format.js";
14
+ export interface AzureFoundryDriverOptions extends DriverOptions {
15
+ /**
16
+ * The credentials to use to access Azure AI Foundry
17
+ */
18
+ azureADTokenProvider?: TokenCredential;
19
+
20
+ endpoint?: string;
21
+
22
+ apiVersion?: string;
23
+ }
24
+
25
+ export interface AzureFoundryInferencePrompt {
26
+ messages: ChatRequestMessage[];
27
+ }
28
+
29
+ export interface AzureFoundryOpenAIPrompt {
30
+ messages: ChatCompletionMessageParam[]
31
+ }
32
+
33
+ export type AzureFoundryPrompt = AzureFoundryInferencePrompt | AzureFoundryOpenAIPrompt
34
+
35
+ export class AzureFoundryDriver extends AbstractDriver<AzureFoundryDriverOptions, ChatCompletionMessageParam[]> {
36
+ service: AIProjectClient;
37
+ readonly provider = Providers.azure_foundry;
38
+
39
+ OPENAI_API_VERSION = "2025-01-01-preview";
40
+ INFERENCE_API_VERSION = "2024-05-01-preview";
41
+
42
+ constructor(opts: AzureFoundryDriverOptions) {
43
+ super(opts);
44
+
45
+ this.formatPrompt = formatOpenAILikeMultimodalPrompt;
46
+
47
+ if (!opts.endpoint) {
48
+ throw new Error("Azure AI Foundry endpoint is required");
49
+ }
50
+
51
+ try {
52
+ if (!opts.azureADTokenProvider) {
53
+ // Using Microsoft Entra ID (Azure AD) for authentication
54
+ opts.azureADTokenProvider = new DefaultAzureCredential();
55
+ }
56
+ } catch (error) {
57
+ this.logger.error("Failed to initialize Azure AD token provider:", error);
58
+ throw new Error("Failed to initialize Azure AD token provider");
59
+ }
60
+
61
+ // Initialize AI Projects client which provides access to inference operations
62
+ this.service = new AIProjectClient(
63
+ opts.endpoint,
64
+ opts.azureADTokenProvider
65
+ );
66
+
67
+ if (opts.apiVersion) {
68
+ this.OPENAI_API_VERSION = opts.apiVersion;
69
+ this.INFERENCE_API_VERSION = opts.apiVersion;
70
+ this.logger.info(`[Azure Foundry] Overriding default API version, using API version: ${opts.apiVersion}`);
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Get default authentication for Azure AI Foundry API
76
+ */
77
+ getDefaultAIFoundryAuth() {
78
+ const scope = "https://ai.azure.com/.default";
79
+ const azureADTokenProvider = getBearerTokenProvider(new DefaultAzureCredential(), scope);
80
+ return azureADTokenProvider;
81
+ }
82
+
83
+ async isOpenAIDeployment(model: string): Promise<boolean> {
84
+ const { deploymentName } = parseAzureFoundryModelId(model);
85
+
86
+ let deployment = undefined;
87
+ // First, verify the deployment exists
88
+ try {
89
+ deployment = await this.service.deployments.get(deploymentName);
90
+ this.logger.debug(`[Azure Foundry] Deployment ${deploymentName} found`);
91
+ } catch (deploymentError) {
92
+ this.logger.error(`[Azure Foundry] Deployment ${deploymentName} not found:`, deploymentError);
93
+ }
94
+
95
+ return (deployment as ModelDeployment).modelPublisher == "OpenAI";
96
+ }
97
+
98
+ protected canStream(_options: ExecutionOptions): Promise<boolean> {
99
+ return Promise.resolve(true);
100
+ }
101
+
102
+ async requestTextCompletion(prompt: ChatCompletionMessageParam[], options: ExecutionOptions): Promise<Completion> {
103
+ const { deploymentName } = parseAzureFoundryModelId(options.model);
104
+ const model_options = options.model_options as any;
105
+ const isOpenAI = await this.isOpenAIDeployment(options.model);
106
+
107
+ let response;
108
+ if (isOpenAI) {
109
+ // Use the Azure OpenAI client for OpenAI models
110
+ const azureOpenAI = await this.service.inference.azureOpenAI({ apiVersion: this.OPENAI_API_VERSION });
111
+ const subDriver = new AzureOpenAIDriver(azureOpenAI);
112
+ // Use deployment name for API calls
113
+ const modifiedOptions = { ...options, model: deploymentName };
114
+ const response = await subDriver.requestTextCompletion(prompt, modifiedOptions);
115
+ return response;
116
+
117
+ } else {
118
+ // Use the chat completions client from the inference operations
119
+ const chatClient = this.service.inference.chatCompletions({ apiVersion: this.INFERENCE_API_VERSION });
120
+ response = await chatClient.post({
121
+ body: {
122
+ messages: prompt,
123
+ max_tokens: model_options?.max_tokens,
124
+ model: deploymentName,
125
+ stream: true,
126
+ temperature: model_options?.temperature,
127
+ top_p: model_options?.top_p,
128
+ frequency_penalty: model_options?.frequency_penalty,
129
+ presence_penalty: model_options?.presence_penalty,
130
+ stop: model_options?.stop_sequence,
131
+ }
132
+ });
133
+ if (response.status !== "200") {
134
+ this.logger.error(`[Azure Foundry] Chat completion request failed:`, response);
135
+ throw new Error(`Chat completion request failed with status ${response.status}: ${response.body}`);
136
+ }
137
+
138
+ return this.extractDataFromResponse(response.body as ChatCompletionsOutput);
139
+ }
140
+ }
141
+
142
+ async requestTextCompletionStream(prompt: ChatCompletionMessageParam[], options: ExecutionOptions): Promise<AsyncIterable<CompletionChunk>> {
143
+ const { deploymentName } = parseAzureFoundryModelId(options.model);
144
+ const model_options = options.model_options as any;
145
+ const isOpenAI = await this.isOpenAIDeployment(options.model);
146
+
147
+ if (isOpenAI) {
148
+ const azureOpenAI = await this.service.inference.azureOpenAI({ apiVersion: this.OPENAI_API_VERSION });
149
+ const subDriver = new AzureOpenAIDriver(azureOpenAI);
150
+ const modifiedOptions = { ...options, model: deploymentName };
151
+ const stream = await subDriver.requestTextCompletionStream(prompt, modifiedOptions);
152
+ return stream;
153
+ } else {
154
+ const chatClient = this.service.inference.chatCompletions({ apiVersion: this.INFERENCE_API_VERSION });
155
+ const response = await chatClient.post({
156
+ body: {
157
+ messages: prompt,
158
+ max_tokens: model_options?.max_tokens,
159
+ model: deploymentName,
160
+ stream: true,
161
+ temperature: model_options?.temperature,
162
+ top_p: model_options?.top_p,
163
+ frequency_penalty: model_options?.frequency_penalty,
164
+ presence_penalty: model_options?.presence_penalty,
165
+ stop: model_options?.stop_sequence,
166
+ }
167
+ }).asNodeStream();
168
+
169
+ // We type assert from NodeJS.ReadableStream to NodeJSReadableStream
170
+ // The Azure Examples, expect a .destroy() method on the stream
171
+ const stream = response.body as NodeJSReadableStream;
172
+ if (!stream) {
173
+ throw new Error("The response stream is undefined");
174
+ }
175
+
176
+ if (response.status !== "200") {
177
+ stream.destroy();
178
+ throw new Error(`Failed to get chat completions, http operation failed with ${response.status} code`);
179
+ }
180
+
181
+ const sseStream = createSseStream(stream);
182
+
183
+ return this.processStreamResponse(sseStream);
184
+ }
185
+ }
186
+
187
+ private async *processStreamResponse(sseStream: any): AsyncIterable<CompletionChunk> {
188
+ try {
189
+ for await (const event of sseStream) {
190
+ if (event.data === "[DONE]") {
191
+ break;
192
+ }
193
+
194
+ try {
195
+ const data = JSON.parse(event.data);
196
+ const choice = data.choices?.[0];
197
+
198
+ if (!choice) {
199
+ continue;
200
+ }
201
+ const chunk: CompletionChunk = {
202
+ result: choice.delta?.content || "",
203
+ finish_reason: this.convertFinishReason(choice.finish_reason),
204
+ };
205
+
206
+ yield chunk;
207
+ } catch (parseError) {
208
+ this.logger.warn(`[Azure Foundry] Failed to parse streaming response:`, parseError);
209
+ continue;
210
+ }
211
+ }
212
+ } catch (error) {
213
+ this.logger.error(`[Azure Foundry] Streaming error:`, error);
214
+ throw error;
215
+ }
216
+ }
217
+
218
+
219
+ private extractDataFromResponse(result: ChatCompletionsOutput): Completion {
220
+ const tokenInfo = {
221
+ prompt: result.usage?.prompt_tokens,
222
+ result: result.usage?.completion_tokens,
223
+ total: result.usage?.total_tokens,
224
+ };
225
+
226
+ const choice = result.choices?.[0];
227
+ if (!choice) {
228
+ this.logger?.error("[Azure Foundry] No choices in response", result);
229
+ throw new Error("No choices in response");
230
+ }
231
+
232
+ const data = choice.message?.content;
233
+ const toolCalls = choice.message?.tool_calls;
234
+
235
+ if (!data && !toolCalls) {
236
+ this.logger?.error("[Azure Foundry] Response is not valid", result);
237
+ throw new Error("Response is not valid: no content or tool calls");
238
+ }
239
+
240
+ const completion: Completion = {
241
+ result: data,
242
+ token_usage: tokenInfo,
243
+ finish_reason: this.convertFinishReason(choice.finish_reason),
244
+ };
245
+
246
+ if (toolCalls && toolCalls.length > 0) {
247
+ completion.tool_use = toolCalls.map((call: ChatCompletionsToolCall) => ({
248
+ id: call.id,
249
+ tool_name: call.function?.name,
250
+ tool_input: call.function?.arguments ? JSON.parse(call.function.arguments) : {}
251
+ }));
252
+ }
253
+
254
+ return completion;
255
+ }
256
+
257
+ private convertFinishReason(reason: string | null | undefined): string | undefined {
258
+ if (!reason) return undefined;
259
+ // Map Azure AI finish reasons to standard format
260
+ switch (reason) {
261
+ case 'stop': return 'stop';
262
+ case 'length': return 'length';
263
+ case 'tool_calls': return 'tool_use';
264
+ default: return reason;
265
+ }
266
+ }
267
+
268
+ async validateConnection(): Promise<boolean> {
269
+ try {
270
+ // Test the AI Projects client by listing deployments
271
+ const deploymentsIterable = this.service.deployments.list();
272
+ let hasDeployments = false;
273
+
274
+ for await (const deployment of deploymentsIterable) {
275
+ hasDeployments = true;
276
+ this.logger.debug(`[Azure Foundry] Found deployment: ${deployment.name} (${deployment.type})`);
277
+ break; // Just check if we can get at least one deployment
278
+ }
279
+
280
+ if (!hasDeployments) {
281
+ this.logger.warn("[Azure Foundry] No deployments found in the project");
282
+ }
283
+
284
+ return true;
285
+ } catch (error) {
286
+ this.logger.error("Azure Foundry connection validation failed:", error);
287
+ return false;
288
+ }
289
+ }
290
+
291
+ async generateEmbeddings(options: EmbeddingsOptions): Promise<EmbeddingsResult> {
292
+ if (!options.model) {
293
+ throw new Error("Default embedding model selection not supported for Azure Foundry. Please specify a model.");
294
+ }
295
+
296
+ if (options.text) {
297
+ return this.generateTextEmbeddings(options);
298
+ } else if (options.image) {
299
+ return this.generateImageEmbeddings(options);
300
+ } else {
301
+ throw new Error("No text or images provided for embeddings");
302
+ }
303
+ }
304
+
305
+ async generateTextEmbeddings(options: EmbeddingsOptions): Promise<EmbeddingsResult> {
306
+ if (!options.text) {
307
+ throw new Error("No text provided for text embeddings");
308
+ }
309
+
310
+ const { deploymentName } = parseAzureFoundryModelId(options.model || "");
311
+
312
+ let response;
313
+ try {
314
+ // Use the embeddings client from the inference operations
315
+ const embeddingsClient = this.service.inference.embeddings({ apiVersion: this.INFERENCE_API_VERSION });
316
+ response = await embeddingsClient.post({
317
+ body: {
318
+ input: Array.isArray(options.text) ? options.text : [options.text],
319
+ model: deploymentName
320
+ }
321
+ });
322
+ } catch (error) {
323
+ this.logger.error("Azure Foundry text embeddings error:", error);
324
+ throw error;
325
+ }
326
+
327
+ if (isUnexpected(response)) {
328
+ throw new Error(`Text embeddings request failed: ${response.status} ${response.body?.error?.message || 'Unknown error'}`);
329
+ }
330
+
331
+ const embeddings = response.body.data?.[0]?.embedding;
332
+ if (!embeddings || !Array.isArray(embeddings) || embeddings.length === 0) {
333
+ throw new Error("No valid embedding array found in response");
334
+ }
335
+
336
+ return {
337
+ values: embeddings,
338
+ model: options.model ?? ""
339
+ };
340
+ }
341
+
342
+ async generateImageEmbeddings(options: EmbeddingsOptions): Promise<EmbeddingsResult> {
343
+ if (!options.image) {
344
+ throw new Error("No images provided for image embeddings");
345
+ }
346
+
347
+ const { deploymentName } = parseAzureFoundryModelId(options.model || "");
348
+
349
+ let response;
350
+ try {
351
+ // Use the embeddings client from the inference operations
352
+ const embeddingsClient = this.service.inference.embeddings({ apiVersion: this.INFERENCE_API_VERSION });
353
+ response = await embeddingsClient.post({
354
+ body: {
355
+ input: Array.isArray(options.image) ? options.image : [options.image],
356
+ model: deploymentName
357
+ }
358
+ });
359
+ } catch (error) {
360
+ this.logger.error("Azure Foundry image embeddings error:", error);
361
+ throw error;
362
+ }
363
+ if (isUnexpected(response)) {
364
+ throw new Error(`Image embeddings request failed: ${response.status} ${response.body?.error?.message || 'Unknown error'}`);
365
+ }
366
+ const embeddings = response.body.data?.[0]?.embedding;
367
+ if (!embeddings || !Array.isArray(embeddings) || embeddings.length === 0) {
368
+ throw new Error("No valid embedding array found in response");
369
+ }
370
+ return {
371
+ values: embeddings,
372
+ model: options.model ?? ""
373
+ };
374
+ }
375
+
376
+ async listModels(): Promise<AIModel[]> {
377
+ const filter = (m: ModelDeployment) => {
378
+ // Only include models that support chat completions
379
+ return !!m.capabilities.chat_completion;
380
+ };
381
+ return this._listModels(filter);
382
+ }
383
+
384
+ async _listModels(filter?: (m: ModelDeployment) => boolean): Promise<AIModel[]> {
385
+ let deploymentsIterable;
386
+ try {
387
+ // List all deployments in the Azure AI Foundry project
388
+ deploymentsIterable = this.service.deployments.list();
389
+ } catch (error) {
390
+ this.logger.error("Failed to list deployments:", error);
391
+ throw new Error("Failed to list deployments in Azure AI Foundry project");
392
+ }
393
+ const deployments: DeploymentUnion[] = [];
394
+
395
+ for await (const page of deploymentsIterable.byPage()) {
396
+ for (const deployment of page) {
397
+ deployments.push(deployment);
398
+ }
399
+ }
400
+
401
+ let modelDeployments: ModelDeployment[] = deployments.filter((d): d is ModelDeployment => {
402
+ return d.type === "ModelDeployment";
403
+ });
404
+
405
+ if (filter) {
406
+ modelDeployments = modelDeployments.filter(filter);
407
+ }
408
+
409
+ const aiModels = modelDeployments.map((model) => {
410
+ // Create composite ID: deployment_name::base_model
411
+ const compositeId = `${model.name}::${model.modelName}`;
412
+
413
+ const modelCapability = getModelCapabilities(model.modelName, Providers.azure_foundry);
414
+ return {
415
+ id: compositeId,
416
+ name: model.name,
417
+ description: `${model.modelName} - ${model.modelVersion}`,
418
+ version: model.modelVersion,
419
+ provider: this.provider,
420
+ owner: model.modelPublisher,
421
+ input_modalities: modelModalitiesToArray(modelCapability.input),
422
+ output_modalities: modelModalitiesToArray(modelCapability.output),
423
+ tool_support: modelCapability.tool_support,
424
+ } satisfies AIModel<string>;
425
+ }).sort((modelA, modelB) => modelA.id.localeCompare(modelB.id));
426
+
427
+ return aiModels;
428
+ }
429
+ }
430
+
431
+ // Helper functions to parse the composite ID
432
+ export function parseAzureFoundryModelId(compositeId: string): { deploymentName: string; baseModel: string } {
433
+ const parts = compositeId.split('::');
434
+ if (parts.length === 2) {
435
+ return {
436
+ deploymentName: parts[0],
437
+ baseModel: parts[1]
438
+ };
439
+ }
440
+
441
+ // Backwards compatibility: if no delimiter found, treat as deployment name
442
+ return {
443
+ deploymentName: compositeId,
444
+ baseModel: compositeId
445
+ };
446
+ }
447
+
448
+ export function isCompositeModelId(modelId: string): boolean {
449
+ return modelId.includes('::');
450
+ }
@@ -74,11 +74,13 @@ function maxTokenFallbackClaude(option: StatelessExecutionOptions): number {
74
74
  if (modelOptions && typeof modelOptions.max_tokens === "number") {
75
75
  return modelOptions.max_tokens;
76
76
  } else {
77
+ const thinking_budget = modelOptions?.thinking_budget_tokens ?? 0;
78
+ let maxSupportedTokens = getMaxTokensLimitBedrock(option.model) ?? 8192; // Should always return a number for claude, 8192 is to satisfy the TypeScript type checker;
77
79
  // Fallback to the default max tokens limit for the model
78
- if (option.model.includes('claude-3-7-sonnet') && (modelOptions?.thinking_budget_tokens ?? 0) < 64000) {
79
- return 64000; // Claude 3.7 can go up to 128k with a beta header, but when no max tokens is specified, we default to 64k.
80
+ if (option.model.includes('claude-3-7-sonnet') && (modelOptions?.thinking_budget_tokens ?? 0) < 48000) {
81
+ maxSupportedTokens = 64000; // Claude 3.7 can go up to 128k with a beta header, but when no max tokens is specified, we default to 64k.
80
82
  }
81
- return getMaxTokensLimitBedrock(option.model) ?? 8192; // Should always return a number for claude, 8192 is to satisfy the TypeScript type checker
83
+ return Math.min(16000 + thinking_budget, maxSupportedTokens); // Cap to 16k, to avoid taking up too much context window and quota.
82
84
  }
83
85
  }
84
86
 
@@ -380,7 +382,7 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
380
382
  });
381
383
 
382
384
  }).catch((err) => {
383
- this.logger.error("[Bedrock] Failed to stream", err);
385
+ this.logger.error("[Bedrock] Failed to stream", { error: err });
384
386
  throw err;
385
387
  });
386
388
  }
@@ -737,7 +739,6 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
737
739
  id: m.modelArn ?? m.modelId,
738
740
  name: `${m.providerName} ${m.modelName}`,
739
741
  provider: this.provider,
740
- //description: ``,
741
742
  owner: m.providerName,
742
743
  can_stream: m.responseStreamingSupported ?? false,
743
744
  input_modalities: m.inputModalities ? formatAmazonModalities(m.inputModalities) : modelModalitiesToArray(modelCapability.input),
@@ -762,6 +763,7 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
762
763
  id: m.modelArn,
763
764
  name: m.modelName ?? m.modelArn,
764
765
  provider: this.provider,
766
+ owner: "custom",
765
767
  description: `Custom model from ${m.baseModelName}`,
766
768
  is_custom: true,
767
769
  input_modalities: modelModalitiesToArray(modelCapability.input),
@@ -801,6 +803,7 @@ export class BedrockDriver extends AbstractDriver<BedrockDriverOptions, BedrockP
801
803
  id: p.inferenceProfileArn ?? p.inferenceProfileId,
802
804
  name: p.inferenceProfileName ?? p.inferenceProfileArn,
803
805
  provider: this.provider,
806
+ owner: providerName,
804
807
  input_modalities: modelModalitiesToArray(modelCapability.input),
805
808
  output_modalities: modelModalitiesToArray(modelCapability.output),
806
809
  tool_support: modelCapability.tool_support,
package/src/groq/index.ts CHANGED
@@ -1,16 +1,16 @@
1
1
  import { AIModel, AbstractDriver, Completion, CompletionChunkObject, DriverOptions, EmbeddingsOptions, EmbeddingsResult, ExecutionOptions, PromptSegment, TextFallbackOptions } from "@llumiverse/core";
2
2
  import { transformAsyncIterator } from "@llumiverse/core/async";
3
- import { OpenAITextMessage, formatOpenAILikeTextPrompt, getJSONSafetyNotice } from "@llumiverse/core/formatters";
4
- import Groq from "groq-sdk";
3
+ import { formatOpenAILikeMultimodalPrompt } from "../openai/openai_format.js";
5
4
 
5
+ import Groq from "groq-sdk";
6
+ import type { ChatCompletionMessageParam } from "groq-sdk/resources/chat/completions";
6
7
 
7
8
  interface GroqDriverOptions extends DriverOptions {
8
9
  apiKey: string;
9
10
  endpoint_url?: string;
10
11
  }
11
12
 
12
-
13
- export class GroqDriver extends AbstractDriver<GroqDriverOptions, OpenAITextMessage[]> {
13
+ export class GroqDriver extends AbstractDriver<GroqDriverOptions, ChatCompletionMessageParam[]> {
14
14
  static PROVIDER = "groq";
15
15
  provider = GroqDriver.PROVIDER;
16
16
  apiKey: string;
@@ -46,19 +46,110 @@ export class GroqDriver extends AbstractDriver<GroqDriverOptions, OpenAITextMess
46
46
  return undefined;
47
47
  }
48
48
 
49
- protected async formatPrompt(segments: PromptSegment[], opts: ExecutionOptions): Promise<OpenAITextMessage[]> {
50
- const messages = formatOpenAILikeTextPrompt(segments);
51
- //Add JSON instruction is schema is provided
52
- if (opts.result_schema) {
53
- messages.push({
54
- role: "user",
55
- content: "IMPORTANT: " + getJSONSafetyNotice(opts.result_schema)
56
- });
57
- }
58
- return messages;
49
+ protected async formatPrompt(segments: PromptSegment[], opts: ExecutionOptions): Promise<ChatCompletionMessageParam[]> {
50
+ // Use OpenAI's multimodal formatter as base then convert to Groq types
51
+ const openaiMessages = await formatOpenAILikeMultimodalPrompt(segments, {
52
+ ...opts,
53
+ multimodal: true,
54
+ });
55
+
56
+ // Convert OpenAI ChatCompletionMessageParam[] to Groq ChatCompletionMessageParam[]
57
+ // Handle differences between OpenAI and Groq SDK types
58
+ const groqMessages: ChatCompletionMessageParam[] = openaiMessages.map(msg => {
59
+ // Handle OpenAI developer messages - convert to system messages for Groq
60
+ if (msg.role === 'developer' || msg.role === 'system') {
61
+ const systemMsg: ChatCompletionMessageParam = {
62
+ role: 'system',
63
+ content: Array.isArray(msg.content)
64
+ ? msg.content.map(part => part.text).join('\n')
65
+ : msg.content,
66
+ // Preserve name if present
67
+ ...(msg.name && { name: msg.name })
68
+ };
69
+ return systemMsg;
70
+ }
71
+
72
+ // Handle user messages - filter content parts to only supported types
73
+ if (msg.role === 'user') {
74
+ let content: string | Array<{type: 'text', text: string} | {type: 'image_url', image_url: {url: string, detail?: 'auto' | 'low' | 'high'}}> | undefined = undefined;
75
+
76
+ if (typeof msg.content === 'string') {
77
+ content = msg.content;
78
+ } else if (Array.isArray(msg.content)) {
79
+ // Filter to only text and image_url parts that Groq supports
80
+ const supportedParts = msg.content.filter(part =>
81
+ part.type === 'text' || part.type === 'image_url'
82
+ ).map(part => {
83
+ if (part.type === 'text') {
84
+ return { type: 'text' as const, text: part.text };
85
+ } else if (part.type === 'image_url') {
86
+ return {
87
+ type: 'image_url' as const,
88
+ image_url: {
89
+ url: part.image_url.url,
90
+ ...(part.image_url.detail && { detail: part.image_url.detail })
91
+ }
92
+ };
93
+ }
94
+ return null;
95
+ }).filter(Boolean) as Array<{type: 'text', text: string} | {type: 'image_url', image_url: {url: string, detail?: 'auto' | 'low' | 'high'}}>;
96
+
97
+ content = supportedParts.length > 0 ? supportedParts : 'Content not supported';
98
+ }
99
+
100
+ const userMsg: ChatCompletionMessageParam = {
101
+ role: 'user',
102
+ content: content ?? "",
103
+ // Preserve name if present
104
+ ...(msg.name && { name: msg.name })
105
+ };
106
+ return userMsg;
107
+ }
108
+
109
+ // Handle assistant messages - handle content arrays if needed
110
+ if (msg.role === 'assistant') {
111
+ const assistantMsg: ChatCompletionMessageParam = {
112
+ role: 'assistant',
113
+ content: Array.isArray(msg.content)
114
+ ? msg.content.map(part => 'text' in part ? part.text : '').filter(Boolean).join('\n') || null
115
+ : msg.content,
116
+ // Preserve other assistant message properties
117
+ ...(msg.function_call && { function_call: msg.function_call }),
118
+ ...(msg.tool_calls && { tool_calls: msg.tool_calls }),
119
+ ...(msg.name && { name: msg.name })
120
+ };
121
+ return assistantMsg;
122
+ }
123
+
124
+ // For tool and function messages, they should be compatible
125
+ if (msg.role === 'tool') {
126
+ const toolMsg: ChatCompletionMessageParam = {
127
+ role: 'tool',
128
+ tool_call_id: msg.tool_call_id,
129
+ content: Array.isArray(msg.content)
130
+ ? msg.content.map(part => part.text).join('\n')
131
+ : msg.content
132
+ };
133
+ return toolMsg;
134
+ }
135
+
136
+ if (msg.role === 'function') {
137
+ const functionMsg: ChatCompletionMessageParam = {
138
+ role: 'function',
139
+ name: msg.name,
140
+ content: msg.content
141
+ };
142
+ return functionMsg;
143
+ }
144
+
145
+ // Fallback - should not reach here but provides type safety
146
+ throw new Error(`Unsupported message role: ${(msg as any).role}`);
147
+ });
148
+
149
+ return groqMessages;
59
150
  }
60
151
 
61
- async requestTextCompletion(messages: OpenAITextMessage[], options: ExecutionOptions): Promise<Completion<any>> {
152
+ async requestTextCompletion(messages: ChatCompletionMessageParam[], options: ExecutionOptions): Promise<Completion> {
62
153
  if (options.model_options?._option_id !== "text-fallback" && options.model_options?._option_id !== "groq-deepseek-thinking") {
63
154
  this.logger.warn("Invalid model options", {options: options.model_options });
64
155
  }
@@ -93,7 +184,7 @@ export class GroqDriver extends AbstractDriver<GroqDriverOptions, OpenAITextMess
93
184
  };
94
185
  }
95
186
 
96
- async requestTextCompletionStream(messages: OpenAITextMessage[], options: ExecutionOptions): Promise < AsyncIterable < CompletionChunkObject >> {
187
+ async requestTextCompletionStream(messages: ChatCompletionMessageParam[], options: ExecutionOptions): Promise <AsyncIterable<CompletionChunkObject>> {
97
188
  if (options.model_options?._option_id !== "text-fallback") {
98
189
  this.logger.warn("Invalid model options", {options: options.model_options });
99
190
  }
package/src/index.ts CHANGED
@@ -2,7 +2,8 @@ export * from "./bedrock/index.js";
2
2
  export * from "./groq/index.js";
3
3
  export * from "./huggingface_ie.js";
4
4
  export * from "./mistral/index.js";
5
- export * from "./openai/azure.js";
5
+ export * from "./openai/azure_openai.js";
6
+ export * from "./azure/azure_foundry.js";
6
7
  export * from "./openai/openai.js";
7
8
  export * from "./replicate.js";
8
9
  export * from "./test/index.js";
@@ -1,6 +1,7 @@
1
1
  import { AIModel, AbstractDriver, Completion, CompletionChunk, DriverOptions, EmbeddingsOptions, EmbeddingsResult, ExecutionOptions, PromptSegment, TextFallbackOptions } from "@llumiverse/core";
2
2
  import { transformSSEStream } from "@llumiverse/core/async";
3
- import { OpenAITextMessage, formatOpenAILikeTextPrompt, getJSONSafetyNotice } from "@llumiverse/core/formatters";
3
+ import { getJSONSafetyNotice } from "@llumiverse/core/formatters";
4
+ import { formatOpenAILikeTextPrompt, OpenAITextMessage } from "../openai/openai_format.js";
4
5
  import { FetchClient } from "@vertesia/api-fetch-client";
5
6
  import { ChatCompletionResponse, CompletionRequestParams, ListModelsResponse, ResponseFormat } from "./types.js";
6
7
 
@@ -61,7 +62,7 @@ export class MistralAIDriver extends AbstractDriver<MistralAIDriverOptions, Open
61
62
  return messages;
62
63
  }
63
64
 
64
- async requestTextCompletion(messages: OpenAITextMessage[], options: ExecutionOptions): Promise<Completion<any>> {
65
+ async requestTextCompletion(messages: OpenAITextMessage[], options: ExecutionOptions): Promise<Completion> {
65
66
  if (options.model_options?._option_id !== "text-fallback") {
66
67
  this.logger.warn("Invalid model options", {options: options.model_options });
67
68
  }