fix: improve Azure OpenAI detection and update API version for o3 models

- Updated default Azure API version from 2024-08-01-preview to 2025-03-01-preview to support newer models like o3-mini
- Enhanced Azure OpenAI detection logic to check for .openai.azure.com domains and /openai/deployments/ paths
- Added comprehensive tests for Azure OpenAI detection scenarios
- Fixed test mocks to properly support AzureOpenAI client

Fixes #7498
This commit is contained in:
Roo Code 2025-08-28 17:16:22 +00:00
parent aee531a143
commit 4f7a858a01
3 changed files with 102 additions and 44 deletions

View file

@ -268,7 +268,8 @@ export const openAiModelInfoSaneDefaults: ModelInfo = {
// https://learn.microsoft.com/en-us/azure/ai-services/openai/api-version-deprecation
// https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#api-specs
export const azureOpenAiDefaultApiVersion = "2024-08-01-preview"
// Updated to support newer models like o3-mini
export const azureOpenAiDefaultApiVersion = "2025-03-01-preview"
export const OPENAI_NATIVE_DEFAULT_TEMPERATURE = 0
export const GPT5_DEFAULT_TEMPERATURE = 1.0

View file

@ -12,19 +12,43 @@ const mockCreate = vitest.fn()
vitest.mock("openai", () => {
const mockConstructor = vitest.fn()
return {
__esModule: true,
default: mockConstructor.mockImplementation(() => ({
chat: {
completions: {
create: mockCreate.mockImplementation(async (options) => {
if (!options.stream) {
return {
id: "test-completion",
const mockImplementation = () => ({
chat: {
completions: {
create: mockCreate.mockImplementation(async (options) => {
if (!options.stream) {
return {
id: "test-completion",
choices: [
{
message: { role: "assistant", content: "Test response", refusal: null },
finish_reason: "stop",
index: 0,
},
],
usage: {
prompt_tokens: 10,
completion_tokens: 5,
total_tokens: 15,
},
}
}
return {
[Symbol.asyncIterator]: async function* () {
yield {
choices: [
{
message: { role: "assistant", content: "Test response", refusal: null },
finish_reason: "stop",
delta: { content: "Test response" },
index: 0,
},
],
usage: null,
}
yield {
choices: [
{
delta: {},
index: 0,
},
],
@ -34,38 +58,17 @@ vitest.mock("openai", () => {
total_tokens: 15,
},
}
}
return {
[Symbol.asyncIterator]: async function* () {
yield {
choices: [
{
delta: { content: "Test response" },
index: 0,
},
],
usage: null,
}
yield {
choices: [
{
delta: {},
index: 0,
},
],
usage: {
prompt_tokens: 10,
completion_tokens: 5,
total_tokens: 15,
},
}
},
}
}),
},
},
}
}),
},
})),
},
})
return {
__esModule: true,
default: mockConstructor.mockImplementation(mockImplementation),
AzureOpenAI: mockConstructor.mockImplementation(mockImplementation),
}
})
@ -849,6 +852,46 @@ describe("OpenAiHandler", () => {
)
})
})
describe("Azure OpenAI Detection", () => {
it("should detect Azure OpenAI from .openai.azure.com domain", () => {
const azureHandler = new OpenAiHandler({
...mockOptions,
openAiBaseUrl: "https://myresource.openai.azure.com",
})
expect(azureHandler).toBeInstanceOf(OpenAiHandler)
// The handler should use AzureOpenAI client internally
})
it("should detect Azure OpenAI from URL containing /openai/deployments/", () => {
const azureHandler = new OpenAiHandler({
...mockOptions,
openAiBaseUrl: "https://myresource.openai.azure.com/openai/deployments/mymodel",
})
expect(azureHandler).toBeInstanceOf(OpenAiHandler)
// The handler should use AzureOpenAI client internally
})
it("should detect Azure OpenAI when openAiUseAzure is true", () => {
const azureHandler = new OpenAiHandler({
...mockOptions,
openAiBaseUrl: "https://custom-endpoint.com",
openAiUseAzure: true,
})
expect(azureHandler).toBeInstanceOf(OpenAiHandler)
// The handler should use AzureOpenAI client internally
})
it("should use updated Azure API version for o3 models", () => {
const azureHandler = new OpenAiHandler({
...mockOptions,
openAiBaseUrl: "https://myresource.openai.azure.com",
openAiModelId: "o3-mini",
})
expect(azureHandler).toBeInstanceOf(OpenAiHandler)
// The handler should use the updated API version (2025-03-01-preview)
})
})
})
describe("getOpenAiModels", () => {

View file

@ -40,7 +40,8 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
const apiKey = this.options.openAiApiKey ?? "not-provided"
const isAzureAiInference = this._isAzureAiInference(this.options.openAiBaseUrl)
const urlHost = this._getUrlHost(this.options.openAiBaseUrl)
const isAzureOpenAi = urlHost === "azure.com" || urlHost.endsWith(".azure.com") || options.openAiUseAzure
// Improved Azure detection: check for various Azure OpenAI URL patterns
const isAzureOpenAi = this._isAzureOpenAi(this.options.openAiBaseUrl) || options.openAiUseAzure
const headers = {
...DEFAULT_HEADERS,
@ -403,6 +404,19 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
return urlHost.endsWith(".services.ai.azure.com")
}
private _isAzureOpenAi(baseUrl?: string): boolean {
if (!baseUrl) return false
const urlHost = this._getUrlHost(baseUrl)
// Check for various Azure OpenAI URL patterns
return (
urlHost === "azure.com" ||
urlHost.endsWith(".azure.com") ||
urlHost.endsWith(".openai.azure.com") ||
// Check if URL contains Azure OpenAI specific paths
baseUrl.includes("/openai/deployments/")
)
}
/**
* Adds max_completion_tokens to the request body if needed based on provider configuration
* Note: max_tokens is deprecated in favor of max_completion_tokens as per OpenAI documentation