Migrate Azure OpenAI Integration To v1 API | Enable Streaming for Reasoning Models in Azure OpenAI Basic Inference Provider (#4744)

* Refactor Azure OpenAI integration to use OpenAI SDK and the v1 API | Enable streaming for Azure Open AI basic inference provider

* Add info tooltip to inform user about 'Model Type' form field

* Add 'model_type_tooltip' key to multiple language translations

* Validate AZURE_OPENAI_ENDPOINT in provider construction

* remove unused import, update error handler, rescope URL utils

---------

Co-authored-by: Timothy Carambat <rambat1010@gmail.com>
pull/4761/head^2
Marcello Fitton 2025-12-10 18:56:55 -08:00 committed by GitHub
parent 692fa755ee
commit a7da757c84
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 89 additions and 23 deletions

View File

@ -1,4 +1,6 @@
import { Info } from "@phosphor-icons/react";
import { useTranslation } from "react-i18next";
import { Tooltip } from "react-tooltip";
export default function AzureAiOptions({ settings }) {
const { t } = useTranslation();
@ -79,9 +81,33 @@ export default function AzureAiOptions({ settings }) {
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-3">
{t("llm.providers.azure_openai.model_type")}
</label>
<div className="flex items-center gap-1 mb-3">
<label className="text-white text-sm font-semibold block">
{t("llm.providers.azure_openai.model_type")}
</label>
<Tooltip
id="azure-openai-model-type"
place="top"
delayShow={300}
className="tooltip !text-xs !opacity-100"
style={{
maxWidth: "250px",
whiteSpace: "normal",
wordWrap: "break-word",
}}
/>
<div
type="button"
className="text-theme-text-secondary cursor-pointer hover:bg-theme-bg-primary flex items-center justify-center rounded-full"
data-tooltip-id="azure-openai-model-type"
data-tooltip-place="top"
data-tooltip-content={t(
"llm.providers.azure_openai.model_type_tooltip"
)}
>
<Info size={18} className="text-theme-text-secondary" />
</div>
</div>
<select
name="AzureOpenAiModelType"
defaultValue={settings?.AzureOpenAiModelType || "default"}

View File

@ -363,6 +363,7 @@ const TRANSLATIONS = {
model_type: null,
default: null,
reasoning: null,
model_type_tooltip: null,
},
},
},

View File

@ -365,6 +365,7 @@ const TRANSLATIONS = {
model_type: null,
default: null,
reasoning: null,
model_type_tooltip: null,
},
},
},

View File

@ -557,6 +557,7 @@ const TRANSLATIONS = {
model_type: "Art des Modells",
default: "Standard",
reasoning: "Reasoning",
model_type_tooltip: null,
},
},
},

View File

@ -583,6 +583,8 @@ const TRANSLATIONS = {
chat_deployment_name: "Chat Deployment Name",
chat_model_token_limit: "Chat Model Token Limit",
model_type: "Model Type",
model_type_tooltip:
"If your deployment uses a reasoning model (o1, o1-mini, o3-mini, etc.), set this to “Reasoning”. Otherwise, your chat requests may fail.",
default: "Default",
reasoning: "Reasoning",
},

View File

@ -568,6 +568,7 @@ const TRANSLATIONS = {
model_type: "Tipo de modelo",
default: "Predeterminado",
reasoning: "Razonamiento",
model_type_tooltip: null,
},
},
},

View File

@ -534,6 +534,7 @@ const TRANSLATIONS = {
model_type: "Mudeli tüüp",
default: "Vaikimisi",
reasoning: "Põhjendus",
model_type_tooltip: null,
},
},
},

View File

@ -355,6 +355,7 @@ const TRANSLATIONS = {
model_type: null,
default: null,
reasoning: null,
model_type_tooltip: null,
},
},
},

View File

@ -371,6 +371,7 @@ const TRANSLATIONS = {
model_type: "Type de modèle",
default: "Par défaut",
reasoning: "Raisonnement",
model_type_tooltip: null,
},
},
},

View File

@ -541,6 +541,7 @@ const TRANSLATIONS = {
model_type: "סוג מודל",
default: "ברירת מחדל",
reasoning: "היגיון",
model_type_tooltip: null,
},
},
},

View File

@ -360,6 +360,7 @@ const TRANSLATIONS = {
model_type: null,
default: null,
reasoning: null,
model_type_tooltip: null,
},
},
},

View File

@ -363,6 +363,7 @@ const TRANSLATIONS = {
model_type: null,
default: null,
reasoning: null,
model_type_tooltip: null,
},
},
},

View File

@ -544,6 +544,7 @@ const TRANSLATIONS = {
model_type: "모델 유형",
default: "기본값",
reasoning: "추론",
model_type_tooltip: null,
},
},
},

View File

@ -552,6 +552,7 @@ const TRANSLATIONS = {
model_type: null,
default: null,
reasoning: null,
model_type_tooltip: null,
},
},
},

View File

@ -357,6 +357,7 @@ const TRANSLATIONS = {
model_type: null,
default: null,
reasoning: null,
model_type_tooltip: null,
},
},
},

View File

@ -557,6 +557,7 @@ const TRANSLATIONS = {
model_type: "Typ modelu",
default: "Domyślne",
reasoning: "Uzasadnienie",
model_type_tooltip: null,
},
},
},

View File

@ -542,6 +542,7 @@ const TRANSLATIONS = {
model_type: "Tipo do Modelo",
default: "Padrão",
reasoning: "Raciocínio",
model_type_tooltip: null,
},
},
},

View File

@ -1011,6 +1011,7 @@ const TRANSLATIONS = {
model_type: "Tip model",
default: "Implicit",
reasoning: "Raționament",
model_type_tooltip: null,
},
},
},

View File

@ -366,6 +366,7 @@ const TRANSLATIONS = {
model_type: null,
default: null,
reasoning: null,
model_type_tooltip: null,
},
},
},

View File

@ -357,6 +357,7 @@ const TRANSLATIONS = {
model_type: null,
default: null,
reasoning: null,
model_type_tooltip: null,
},
},
},

View File

@ -356,6 +356,7 @@ const TRANSLATIONS = {
model_type: null,
default: null,
reasoning: null,
model_type_tooltip: null,
},
},
},

View File

@ -520,6 +520,7 @@ const TRANSLATIONS = {
model_type: "模型类型",
default: "预设",
reasoning: "推理",
model_type_tooltip: null,
},
},
},

View File

@ -348,6 +348,7 @@ const TRANSLATIONS = {
model_type: "模型類型",
default: "預設",
reasoning: "推理",
model_type_tooltip: null,
},
},
},

View File

@ -9,19 +9,23 @@ const {
class AzureOpenAiLLM {
constructor(embedder = null, modelPreference = null) {
const { AzureOpenAI } = require("openai");
const { OpenAI } = require("openai");
if (!process.env.AZURE_OPENAI_ENDPOINT)
throw new Error("No Azure API endpoint was set.");
if (!process.env.AZURE_OPENAI_KEY)
throw new Error("No Azure API key was set.");
this.apiVersion = "2024-12-01-preview";
this.openai = new AzureOpenAI({
this.openai = new OpenAI({
apiKey: process.env.AZURE_OPENAI_KEY,
apiVersion: this.apiVersion,
endpoint: process.env.AZURE_OPENAI_ENDPOINT,
baseURL: AzureOpenAiLLM.formatBaseUrl(process.env.AZURE_OPENAI_ENDPOINT),
});
this.model = modelPreference ?? process.env.OPEN_MODEL_PREF;
/*
Note: Azure OpenAI deployments do not expose model metadata that would allow us to
programmatically detect whether the deployment uses a reasoning model (o1, o1-mini, o3-mini, etc.).
As a result, we rely on the user to explicitly set AZURE_OPENAI_MODEL_TYPE="reasoning"
when using reasoning models, as incorrect configuration might result in chat errors.
*/
this.isOTypeModel =
process.env.AZURE_OPENAI_MODEL_TYPE === "reasoning" || false;
this.limits = {
@ -37,6 +41,26 @@ class AzureOpenAiLLM {
);
}
/**
* Formats the Azure OpenAI endpoint URL to the correct format.
* @param {string} azureOpenAiEndpoint - The Azure OpenAI endpoint URL.
* @returns {string} The formatted URL.
*/
static formatBaseUrl(azureOpenAiEndpoint) {
try {
const url = new URL(azureOpenAiEndpoint);
url.pathname = "/openai/v1";
url.protocol = "https";
url.search = "";
url.hash = "";
return url.href;
} catch (error) {
throw new Error(
`"${azureOpenAiEndpoint}" is not a valid URL. Check your settings for the Azure OpenAI provider and set a valid endpoint URL.`
);
}
}
#log(text, ...args) {
console.log(`\x1b[32m[AzureOpenAi]\x1b[0m ${text}`, ...args);
}
@ -54,13 +78,6 @@ class AzureOpenAiLLM {
}
streamingEnabled() {
// Streaming of reasoning models is not supported
if (this.isOTypeModel) {
this.#log(
"Streaming will be disabled. AZURE_OPENAI_MODEL_TYPE is set to 'reasoning'."
);
return false;
}
return "streamGetChatCompletion" in this;
}

View File

@ -1,4 +1,5 @@
const { AzureOpenAI } = require("openai");
const { OpenAI } = require("openai");
const { AzureOpenAiLLM } = require("../../../AiProviders/azureOpenAi");
const Provider = require("./ai-provider.js");
const { RetryError } = require("../error.js");
@ -9,10 +10,9 @@ class AzureOpenAiProvider extends Provider {
model;
constructor(config = { model: null }) {
const client = new AzureOpenAI({
const client = new OpenAI({
apiKey: process.env.AZURE_OPENAI_KEY,
endpoint: process.env.AZURE_OPENAI_ENDPOINT,
apiVersion: "2024-12-01-preview",
baseURL: AzureOpenAiLLM.formatBaseUrl(process.env.AZURE_OPENAI_ENDPOINT),
});
super(client);
this.model = config.model ?? process.env.OPEN_MODEL_PREF;
@ -84,12 +84,12 @@ class AzureOpenAiProvider extends Provider {
} catch (error) {
// If invalid Auth error we need to abort because no amount of waiting
// will make auth better.
if (error instanceof AzureOpenAI.AuthenticationError) throw error;
if (error instanceof OpenAI.AuthenticationError) throw error;
if (
error instanceof AzureOpenAI.RateLimitError ||
error instanceof AzureOpenAI.InternalServerError ||
error instanceof AzureOpenAI.APIError // Also will catch AuthenticationError!!!
error instanceof OpenAI.RateLimitError ||
error instanceof OpenAI.InternalServerError ||
error instanceof OpenAI.APIError // Also will catch AuthenticationError!!!
) {
throw new RetryError(error.message);
}