Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/fix-azure-baseurl-no-v1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@ai-sdk/azure': patch
---

fix(azure): skip /v1 path segment and api-version query param when baseURL is provided

When `createAzure({ baseURL: '...' })` is used, the provider now constructs
`{baseURL}{path}` instead of `{baseURL}/v1{path}?api-version=...`. This allows
custom API gateways and proxies that handle routing internally to work without
receiving unexpected path segments or query parameters.

Closes #13956. Also fixes the api-version issue reported in #14009.
55 changes: 47 additions & 8 deletions packages/azure/src/azure-openai-provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ const server = createTestServer({
'https://test-resource.openai.azure.com/openai/v1/audio/speech': {},
'https://test-resource.openai.azure.com/openai/deployments/whisper-1/audio/transcriptions':
{},
'https://test-resource.openai.azure.com/openai/responses': {},
'https://test-resource.openai.azure.com/openai/chat/completions': {},
'https://test-resource.openai.azure.com/openai/images/generations': {},
});

describe('responses (default language model)', () => {
Expand Down Expand Up @@ -187,8 +190,14 @@ describe('responses (default language model)', () => {
);
});

it('should use the baseURL correctly', async () => {
it('should use the baseURL without appending /v1 or api-version', async () => {
prepareJsonResponse();
server.urls[
'https://test-resource.openai.azure.com/openai/responses'
].response =
server.urls[
'https://test-resource.openai.azure.com/openai/v1/responses'
].response;

const provider = createAzure({
baseURL: 'https://test-resource.openai.azure.com/openai',
Expand All @@ -199,8 +208,11 @@ describe('responses (default language model)', () => {
prompt: TEST_PROMPT,
});
expect(server.calls[0].requestUrl).toMatchInlineSnapshot(
`"https://test-resource.openai.azure.com/openai/v1/responses?api-version=v1"`,
`"https://test-resource.openai.azure.com/openai/responses"`,
);
expect(
server.calls[0].requestUrlSearchParams.get('api-version'),
).toBeNull();
});
});
});
Expand Down Expand Up @@ -292,8 +304,14 @@ describe('chat', () => {
);
});

it('should use the baseURL correctly', async () => {
it('should use the baseURL without appending /v1 or api-version', async () => {
prepareJsonResponse();
server.urls[
'https://test-resource.openai.azure.com/openai/chat/completions'
].response =
server.urls[
'https://test-resource.openai.azure.com/openai/v1/chat/completions'
].response;

const provider = createAzure({
baseURL: 'https://test-resource.openai.azure.com/openai',
Expand All @@ -304,8 +322,11 @@ describe('chat', () => {
prompt: TEST_PROMPT,
});
expect(server.calls[0].requestUrl).toMatchInlineSnapshot(
`"https://test-resource.openai.azure.com/openai/v1/chat/completions?api-version=v1"`,
`"https://test-resource.openai.azure.com/openai/chat/completions"`,
);
expect(
server.calls[0].requestUrlSearchParams.get('api-version'),
).toBeNull();
});
});
});
Expand Down Expand Up @@ -650,8 +671,14 @@ describe('image', () => {
);
});

it('should use the baseURL correctly', async () => {
it('should use the baseURL without appending /v1 or api-version', async () => {
prepareJsonResponse();
server.urls[
'https://test-resource.openai.azure.com/openai/images/generations'
].response =
server.urls[
'https://test-resource.openai.azure.com/openai/v1/images/generations'
].response;

const provider = createAzure({
baseURL: 'https://test-resource.openai.azure.com/openai',
Expand All @@ -670,8 +697,11 @@ describe('image', () => {
});

expect(server.calls[0].requestUrl).toMatchInlineSnapshot(
`"https://test-resource.openai.azure.com/openai/v1/images/generations?api-version=v1"`,
`"https://test-resource.openai.azure.com/openai/images/generations"`,
);
expect(
server.calls[0].requestUrlSearchParams.get('api-version'),
).toBeNull();
});

it('should extract the generated images', async () => {
Expand Down Expand Up @@ -873,8 +903,14 @@ describe('responses', () => {
);
});

it('should use the baseURL correctly', async () => {
it('should use the baseURL without appending /v1 or api-version', async () => {
prepareJsonFixtureResponse('azure-text.1');
server.urls[
'https://test-resource.openai.azure.com/openai/responses'
].response =
server.urls[
'https://test-resource.openai.azure.com/openai/v1/responses'
].response;

const provider = createAzure({
baseURL: 'https://test-resource.openai.azure.com/openai',
Expand All @@ -886,8 +922,11 @@ describe('responses', () => {
});

expect(server.calls[0].requestUrl).toMatchInlineSnapshot(
`"https://test-resource.openai.azure.com/openai/v1/responses?api-version=v1"`,
`"https://test-resource.openai.azure.com/openai/responses"`,
);
expect(
server.calls[0].requestUrlSearchParams.get('api-version'),
).toBeNull();
});

it('should handle Azure file IDs with assistant- prefix', async () => {
Expand Down
12 changes: 10 additions & 2 deletions packages/azure/src/azure-openai-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,9 @@ export interface AzureOpenAIProviderSettings {
* Use a different URL prefix for API calls, e.g. to use proxy servers. Either this or `resourceName` can be used.
* When a baseURL is provided, the resourceName is ignored.
*
* With a baseURL, the resolved URL is `{baseURL}/v1{path}`.
* With a baseURL, the resolved URL is `{baseURL}{path}` — no `/v1` is appended and no
* `api-version` query parameter is added. This allows custom gateways and proxies that
* handle routing internally to receive the URL exactly as provided.
*/
baseURL?: string;

Expand Down Expand Up @@ -174,12 +176,18 @@ export function createAzure(
if (options.useDeploymentBasedUrls) {
// Use deployment-based format for compatibility with certain Azure OpenAI models
fullUrl = new URL(`${baseUrlPrefix}/deployments/${modelId}${path}`);
} else if (options.baseURL) {
// When baseURL is explicitly provided, use it as-is without appending /v1.
// Callers using a custom gateway or proxy control the URL shape themselves.
fullUrl = new URL(`${baseUrlPrefix}${path}`);
} else {
// Use v1 API format - no deployment ID in URL
fullUrl = new URL(`${baseUrlPrefix}/v1${path}`);
}

fullUrl.searchParams.set('api-version', apiVersion);
if (!options.baseURL) {
fullUrl.searchParams.set('api-version', apiVersion);
}
return fullUrl.toString();
};

Expand Down