@llumiverse/drivers 0.19.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.
- package/lib/cjs/azure/azure_foundry.js +379 -0
- package/lib/cjs/azure/azure_foundry.js.map +1 -0
- package/lib/cjs/bedrock/converse.js +181 -123
- package/lib/cjs/bedrock/converse.js.map +1 -1
- package/lib/cjs/bedrock/index.js +157 -72
- package/lib/cjs/bedrock/index.js.map +1 -1
- package/lib/cjs/groq/index.js +91 -10
- package/lib/cjs/groq/index.js.map +1 -1
- package/lib/cjs/index.js +2 -1
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/mistral/index.js +2 -1
- package/lib/cjs/mistral/index.js.map +1 -1
- package/lib/cjs/openai/azure_openai.js +72 -0
- package/lib/cjs/openai/azure_openai.js.map +1 -0
- package/lib/cjs/openai/index.js +6 -9
- package/lib/cjs/openai/index.js.map +1 -1
- package/lib/cjs/openai/openai.js +2 -2
- package/lib/cjs/openai/openai.js.map +1 -1
- package/lib/cjs/openai/openai_format.js +138 -0
- package/lib/cjs/openai/openai_format.js.map +1 -0
- package/lib/cjs/vertexai/index.js +1 -0
- package/lib/cjs/vertexai/index.js.map +1 -1
- package/lib/cjs/vertexai/models/claude.js +229 -118
- package/lib/cjs/vertexai/models/claude.js.map +1 -1
- package/lib/cjs/vertexai/models/gemini.js +110 -70
- package/lib/cjs/vertexai/models/gemini.js.map +1 -1
- package/lib/cjs/vertexai/models/imagen.js +2 -2
- package/lib/cjs/vertexai/models/imagen.js.map +1 -1
- package/lib/cjs/watsonx/index.js +11 -11
- package/lib/cjs/watsonx/index.js.map +1 -1
- package/lib/cjs/xai/index.js +3 -3
- package/lib/cjs/xai/index.js.map +1 -1
- package/lib/esm/azure/azure_foundry.js +373 -0
- package/lib/esm/azure/azure_foundry.js.map +1 -0
- package/lib/esm/bedrock/converse.js +180 -122
- package/lib/esm/bedrock/converse.js.map +1 -1
- package/lib/esm/bedrock/index.js +158 -73
- package/lib/esm/bedrock/index.js.map +1 -1
- package/lib/esm/groq/index.js +91 -10
- package/lib/esm/groq/index.js.map +1 -1
- package/lib/esm/index.js +2 -1
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/mistral/index.js +2 -1
- package/lib/esm/mistral/index.js.map +1 -1
- package/lib/esm/openai/azure_openai.js +68 -0
- package/lib/esm/openai/azure_openai.js.map +1 -0
- package/lib/esm/openai/index.js +5 -8
- package/lib/esm/openai/index.js.map +1 -1
- package/lib/esm/openai/openai.js +2 -2
- package/lib/esm/openai/openai.js.map +1 -1
- package/lib/esm/openai/openai_format.js +134 -0
- package/lib/esm/openai/openai_format.js.map +1 -0
- package/lib/esm/src/adobe/firefly.js +115 -0
- package/lib/esm/src/adobe/firefly.js.map +1 -0
- package/lib/esm/src/bedrock/converse.js +278 -0
- package/lib/esm/src/bedrock/converse.js.map +1 -0
- package/lib/esm/src/bedrock/index.js +797 -0
- package/lib/esm/src/bedrock/index.js.map +1 -0
- package/lib/esm/src/bedrock/nova-image-payload.js +203 -0
- package/lib/esm/src/bedrock/nova-image-payload.js.map +1 -0
- package/lib/esm/src/bedrock/payloads.js +2 -0
- package/lib/esm/src/bedrock/payloads.js.map +1 -0
- package/lib/esm/src/bedrock/s3.js +99 -0
- package/lib/esm/src/bedrock/s3.js.map +1 -0
- package/lib/esm/src/groq/index.js +130 -0
- package/lib/esm/src/groq/index.js.map +1 -0
- package/lib/esm/src/huggingface_ie.js +196 -0
- package/lib/esm/src/huggingface_ie.js.map +1 -0
- package/lib/esm/src/index.js +13 -0
- package/lib/esm/src/index.js.map +1 -0
- package/lib/esm/src/mistral/index.js +167 -0
- package/lib/esm/src/mistral/index.js.map +1 -0
- package/lib/esm/src/mistral/types.js +80 -0
- package/lib/esm/src/mistral/types.js.map +1 -0
- package/{src/openai/azure.ts → lib/esm/src/openai/azure.js} +7 -34
- package/lib/esm/src/openai/azure.js.map +1 -0
- package/lib/esm/src/openai/index.js +463 -0
- package/lib/esm/src/openai/index.js.map +1 -0
- package/lib/esm/src/openai/openai.js +14 -0
- package/lib/esm/src/openai/openai.js.map +1 -0
- package/lib/esm/src/replicate.js +268 -0
- package/lib/esm/src/replicate.js.map +1 -0
- package/lib/esm/src/test/TestErrorCompletionStream.js +16 -0
- package/lib/esm/src/test/TestErrorCompletionStream.js.map +1 -0
- package/lib/esm/src/test/TestValidationErrorCompletionStream.js +20 -0
- package/lib/esm/src/test/TestValidationErrorCompletionStream.js.map +1 -0
- package/lib/esm/src/test/index.js +91 -0
- package/lib/esm/src/test/index.js.map +1 -0
- package/lib/esm/src/test/utils.js +25 -0
- package/lib/esm/src/test/utils.js.map +1 -0
- package/lib/esm/src/togetherai/index.js +122 -0
- package/lib/esm/src/togetherai/index.js.map +1 -0
- package/lib/esm/src/togetherai/interfaces.js +2 -0
- package/lib/esm/src/togetherai/interfaces.js.map +1 -0
- package/lib/esm/src/vertexai/debug.js +6 -0
- package/lib/esm/src/vertexai/debug.js.map +1 -0
- package/lib/esm/src/vertexai/embeddings/embeddings-image.js +24 -0
- package/lib/esm/src/vertexai/embeddings/embeddings-image.js.map +1 -0
- package/lib/esm/src/vertexai/embeddings/embeddings-text.js +20 -0
- package/lib/esm/src/vertexai/embeddings/embeddings-text.js.map +1 -0
- package/lib/esm/src/vertexai/index.js +270 -0
- package/lib/esm/src/vertexai/index.js.map +1 -0
- package/lib/esm/src/vertexai/models/claude.js +370 -0
- package/lib/esm/src/vertexai/models/claude.js.map +1 -0
- package/lib/esm/src/vertexai/models/gemini.js +700 -0
- package/lib/esm/src/vertexai/models/gemini.js.map +1 -0
- package/lib/esm/src/vertexai/models/imagen.js +310 -0
- package/lib/esm/src/vertexai/models/imagen.js.map +1 -0
- package/lib/esm/src/vertexai/models/llama.js +178 -0
- package/lib/esm/src/vertexai/models/llama.js.map +1 -0
- package/lib/esm/src/vertexai/models.js +21 -0
- package/lib/esm/src/vertexai/models.js.map +1 -0
- package/lib/esm/src/watsonx/index.js +157 -0
- package/lib/esm/src/watsonx/index.js.map +1 -0
- package/lib/esm/src/watsonx/interfaces.js +2 -0
- package/lib/esm/src/watsonx/interfaces.js.map +1 -0
- package/lib/esm/src/xai/index.js +64 -0
- package/lib/esm/src/xai/index.js.map +1 -0
- package/lib/esm/tsconfig.tsbuildinfo +1 -0
- package/lib/esm/vertexai/index.js +1 -0
- package/lib/esm/vertexai/index.js.map +1 -1
- package/lib/esm/vertexai/models/claude.js +230 -119
- package/lib/esm/vertexai/models/claude.js.map +1 -1
- package/lib/esm/vertexai/models/gemini.js +109 -70
- package/lib/esm/vertexai/models/gemini.js.map +1 -1
- package/lib/esm/vertexai/models/imagen.js +2 -2
- package/lib/esm/vertexai/models/imagen.js.map +1 -1
- package/lib/esm/watsonx/index.js +11 -11
- package/lib/esm/watsonx/index.js.map +1 -1
- package/lib/esm/xai/index.js +2 -2
- package/lib/esm/xai/index.js.map +1 -1
- package/lib/types/azure/azure_foundry.d.ts +50 -0
- package/lib/types/azure/azure_foundry.d.ts.map +1 -0
- package/lib/types/bedrock/converse.d.ts +2 -2
- package/lib/types/bedrock/converse.d.ts.map +1 -1
- package/lib/types/bedrock/index.d.ts +5 -5
- package/lib/types/bedrock/index.d.ts.map +1 -1
- package/lib/types/groq/index.d.ts +5 -5
- package/lib/types/groq/index.d.ts.map +1 -1
- package/lib/types/index.d.ts +2 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/mistral/index.d.ts +2 -2
- package/lib/types/mistral/index.d.ts.map +1 -1
- package/lib/types/openai/azure_openai.d.ts +25 -0
- package/lib/types/openai/azure_openai.d.ts.map +1 -0
- package/lib/types/openai/index.d.ts +6 -7
- package/lib/types/openai/index.d.ts.map +1 -1
- package/lib/types/openai/openai.d.ts +2 -2
- package/lib/types/openai/openai.d.ts.map +1 -1
- package/lib/types/openai/openai_format.d.ts +19 -0
- package/lib/types/openai/openai_format.d.ts.map +1 -0
- package/lib/types/src/adobe/firefly.d.ts +29 -0
- package/lib/types/src/bedrock/converse.d.ts +8 -0
- package/lib/types/src/bedrock/index.d.ts +57 -0
- package/lib/types/src/bedrock/nova-image-payload.d.ts +73 -0
- package/lib/types/src/bedrock/payloads.d.ts +11 -0
- package/lib/types/src/bedrock/s3.d.ts +22 -0
- package/lib/types/src/groq/index.d.ts +23 -0
- package/lib/types/src/huggingface_ie.d.ts +31 -0
- package/lib/types/src/index.d.ts +12 -0
- package/lib/types/src/mistral/index.d.ts +24 -0
- package/lib/types/src/mistral/types.d.ts +131 -0
- package/lib/types/src/openai/azure.d.ts +19 -0
- package/lib/types/src/openai/index.d.ts +25 -0
- package/lib/types/src/openai/openai.d.ts +14 -0
- package/lib/types/src/replicate.d.ts +44 -0
- package/lib/types/src/test/TestErrorCompletionStream.d.ts +8 -0
- package/lib/types/src/test/TestValidationErrorCompletionStream.d.ts +8 -0
- package/lib/types/src/test/index.d.ts +23 -0
- package/lib/types/src/test/utils.d.ts +4 -0
- package/lib/types/src/togetherai/index.d.ts +22 -0
- package/lib/types/src/togetherai/interfaces.d.ts +95 -0
- package/lib/types/src/vertexai/debug.d.ts +1 -0
- package/lib/types/src/vertexai/embeddings/embeddings-image.d.ts +10 -0
- package/lib/types/src/vertexai/embeddings/embeddings-text.d.ts +9 -0
- package/lib/types/src/vertexai/index.d.ts +49 -0
- package/lib/types/src/vertexai/models/claude.d.ts +17 -0
- package/lib/types/src/vertexai/models/gemini.d.ts +16 -0
- package/lib/types/src/vertexai/models/imagen.d.ts +74 -0
- package/lib/types/src/vertexai/models/llama.d.ts +19 -0
- package/lib/types/src/vertexai/models.d.ts +14 -0
- package/lib/types/src/watsonx/index.d.ts +26 -0
- package/lib/types/src/watsonx/interfaces.d.ts +64 -0
- package/lib/types/src/xai/index.d.ts +18 -0
- package/lib/types/vertexai/index.d.ts +2 -3
- package/lib/types/vertexai/index.d.ts.map +1 -1
- package/lib/types/vertexai/models/claude.d.ts +5 -7
- package/lib/types/vertexai/models/claude.d.ts.map +1 -1
- package/lib/types/vertexai/models/gemini.d.ts +4 -2
- package/lib/types/vertexai/models/gemini.d.ts.map +1 -1
- package/lib/types/vertexai/models.d.ts +2 -2
- package/lib/types/vertexai/models.d.ts.map +1 -1
- package/lib/types/xai/index.d.ts.map +1 -1
- package/package.json +20 -16
- package/src/azure/azure_foundry.ts +450 -0
- package/src/bedrock/converse.ts +194 -129
- package/src/bedrock/index.ts +182 -84
- package/src/groq/index.ts +107 -16
- package/src/index.ts +2 -1
- package/src/mistral/index.ts +3 -2
- package/src/openai/azure_openai.ts +92 -0
- package/src/openai/index.ts +19 -22
- package/src/openai/openai.ts +2 -5
- package/src/openai/openai_format.ts +165 -0
- package/src/vertexai/index.ts +3 -3
- package/src/vertexai/models/claude.ts +270 -138
- package/src/vertexai/models/gemini.ts +120 -77
- package/src/vertexai/models/imagen.ts +3 -3
- package/src/vertexai/models.ts +2 -2
- package/src/watsonx/index.ts +17 -17
- 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
|
+
}
|