Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 5 additions & 0 deletions .changeset/angry-poems-learn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ai-sdk/azure': patch
---

feat (provider/azure): add OpenAI responses API support
140 changes: 140 additions & 0 deletions content/providers/01-ai-sdk-providers/03-azure.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,146 @@ The following optional settings are available for OpenAI chat models:
A unique identifier representing your end-user, which can help OpenAI to
monitor and detect abuse. Learn more.

### Responses Models

You can use the Azure OpenAI responses API with the `azure.responses(deploymentName)` factory method.

```ts
const model = azure.responses('your-deployment-name');
```

Further configuration can be done using OpenAI provider options.
You can validate the provider options using the `OpenAIResponsesProviderOptions` type.

```ts
import { azure, OpenAIResponsesProviderOptions } from '@ai-sdk/azure';
import { generateText } from 'ai';

const result = await generateText({
model: azure.responses('your-deployment-name'),
providerOptions: {
openai: {
parallelToolCalls: false,
store: false,
user: 'user_123',
// ...
} satisfies OpenAIResponsesProviderOptions,
},
// ...
});
```

The following provider options are available:

- **parallelToolCalls** _boolean_
Whether to use parallel tool calls. Defaults to `true`.

- **store** _boolean_
Whether to store the generation. Defaults to `true`.

- **metadata** _Record<string, string>_
Additional metadata to store with the generation.

- **previousResponseId** _string_
The ID of the previous response. You can use it to continue a conversation. Defaults to `undefined`.

- **instructions** _string_
Instructions for the model.
They can be used to change the system or developer message when continuing a conversation using the `previousResponseId` option.
Defaults to `undefined`.

- **user** _string_
A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. Defaults to `undefined`.

- **reasoningEffort** _'low' | 'medium' | 'high'_
Reasoning effort for reasoning models. Defaults to `medium`. If you use `providerOptions` to set the `reasoningEffort` option, this model setting will be ignored.

- **strictSchemas** _boolean_
Whether to use strict JSON schemas in tools and when generating JSON outputs. Defaults to `true`.

The Azure OpenAI responses provider also returns provider-specific metadata:

```ts
const { providerMetadata } = await generateText({
model: azure.responses('your-deployment-name'),
});

const openaiMetadata = providerMetadata?.openai;
```

The following OpenAI-specific metadata is returned:

- **responseId** _string_
The ID of the response. Can be used to continue a conversation.

- **cachedPromptTokens** _number_
The number of prompt tokens that were a cache hit.

- **reasoningTokens** _number_
The number of reasoning tokens that the model generated.

#### Web Search

The Azure OpenAI responses provider supports web search through the `azure.tools.webSearchPreview` tool.

You can force the use of the web search tool by setting the `toolChoice` parameter to `{ type: 'tool', toolName: 'web_search_preview' }`.

```ts
const result = await generateText({
model: azure.responses('your-deployment-name'),
prompt: 'What happened in San Francisco last week?',
tools: {
web_search_preview: azure.tools.webSearchPreview({
Copy link

@matthiasmoke matthiasmoke Sep 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The provided docs example will result in a null pointer, as the azure provider object does not export a tool property.

    const azure = createAzure({
      resourceName: AZURE_OAI_RESOURCE,
      apiKey: AZURE_OAI_KEY
    })
    const model = azure.responses('insert-deployment-here')
    const result = await generateText({
      model,
      prompt: input,
      tools: {
        web_search_preview: azure.tools.webSearchPreview({
        searchContextSize: 'high',
        userLocation: {
          type: 'approximate',
          city: 'San Francisco',
          region: 'California',
        },
        })
      }
    })

Results in: TypeError: Cannot read properties of undefined (reading 'webSearchPreview')

@AVtheking can you judge whether my usage example is correct? If yes, I suppose this is an issue with the provider.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes you are correct, it doesn't exports tool now.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your response! Do you know any workaround to still enable such tools while using the azure provider?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would raise a PR for this to fix this, if I still has azure creds, or you can do it buddy, I can also help you

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

havent tested it yet, but I think its resolved 😄 #8238

// optional configuration:
searchContextSize: 'high',
userLocation: {
type: 'approximate',
city: 'San Francisco',
region: 'California',
},
}),
},
// Force web search tool:
toolChoice: { type: 'tool', toolName: 'web_search_preview' },
});

// URL sources
const sources = result.sources;
```

#### PDF support

The Azure OpenAI Responses API supports reading PDF files.
You can pass PDF files as part of the message content using the `file` type:

```ts
const result = await generateText({
model: azure.responses('your-deployment-name'),
messages: [
{
role: 'user',
content: [
{
type: 'text',
text: 'What is an embedding model?',
},
{
type: 'file',
data: fs.readFileSync('./data/ai.pdf'),
mimeType: 'application/pdf',
filename: 'ai.pdf', // optional
},
],
},
],
});
```

The model will have access to the contents of the PDF file and
respond to questions about it.
The PDF file should be passed using the `data` field,
and the `mimeType` should be set to `'application/pdf'`.

### Completion Models

You can create models that call the completions API using the `.completion()` factory method.
Expand Down
22 changes: 22 additions & 0 deletions examples/ai-core/src/generate-text/azure-responses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { createAzure } from '@ai-sdk/azure';
import { generateText } from 'ai';
import 'dotenv/config';

// Initialize Azure OpenAI provider
const azure = createAzure({
apiKey: process.env.AZURE_API_KEY,
baseURL: process.env.AZURE_BASE_URL,
});

async function main() {
// Basic text generation
const basicResult = await generateText({
model: azure.responses('gpt-4o-mini'),
prompt: 'What is quantum computing?',
});

console.log('\n=== Basic Text Generation ===');
console.log(basicResult.text);
}

main().catch(console.error);
120 changes: 111 additions & 9 deletions packages/azure/src/azure-openai-provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const server = createTestServer({
{},
'https://test-resource.openai.azure.com/openai/deployments/dalle-deployment/images/generations':
{},
'https://test-resource.openai.azure.com/openai/responses': {},
});

describe('chat', () => {
Expand Down Expand Up @@ -74,7 +75,7 @@ describe('chat', () => {

expect(
server.calls[0].requestUrlSearchParams.get('api-version'),
).toStrictEqual('2024-10-01-preview');
).toStrictEqual('2025-03-01-preview');
});

it('should set the correct modified api version', async () => {
Expand Down Expand Up @@ -132,9 +133,8 @@ describe('chat', () => {
mode: { type: 'regular' },
prompt: TEST_PROMPT,
});

expect(server.calls[0].requestUrl).toStrictEqual(
'https://test-resource.openai.azure.com/openai/deployments/test-deployment/chat/completions?api-version=2024-10-01-preview',
'https://test-resource.openai.azure.com/openai/deployments/test-deployment/chat/completions?api-version=2025-03-01-preview',
);
});
});
Expand Down Expand Up @@ -195,10 +195,9 @@ describe('completion', () => {
mode: { type: 'regular' },
prompt: TEST_PROMPT,
});

expect(
server.calls[0].requestUrlSearchParams.get('api-version'),
).toStrictEqual('2024-10-01-preview');
).toStrictEqual('2025-03-01-preview');
});

it('should pass headers', async () => {
Expand Down Expand Up @@ -269,10 +268,9 @@ describe('embedding', () => {
await model.doEmbed({
values: testValues,
});

expect(
server.calls[0].requestUrlSearchParams.get('api-version'),
).toStrictEqual('2024-10-01-preview');
).toStrictEqual('2025-03-01-preview');
});

it('should pass headers', async () => {
Expand Down Expand Up @@ -342,7 +340,7 @@ describe('image', () => {

expect(
server.calls[0].requestUrlSearchParams.get('api-version'),
).toStrictEqual('2024-10-01-preview');
).toStrictEqual('2025-03-01-preview');
});

it('should set the correct modified api version', async () => {
Expand Down Expand Up @@ -413,7 +411,7 @@ describe('image', () => {
});

expect(server.calls[0].requestUrl).toStrictEqual(
'https://test-resource.openai.azure.com/openai/deployments/dalle-deployment/images/generations?api-version=2024-10-01-preview',
'https://test-resource.openai.azure.com/openai/deployments/dalle-deployment/images/generations?api-version=2025-03-01-preview',
);
});

Expand Down Expand Up @@ -465,3 +463,107 @@ describe('image', () => {
});
});
});

describe('responses', () => {
describe('doGenerate', () => {
function prepareJsonResponse({
content = '',
usage = {
input_tokens: 4,
output_tokens: 30,
total_tokens: 34,
},
} = {}) {
server.urls[
'https://test-resource.openai.azure.com/openai/responses'
].response = {
type: 'json-value',
body: {
id: 'resp_67c97c0203188190a025beb4a75242bc',
object: 'response',
created_at: 1741257730,
status: 'completed',
model: 'test-deployment',
output: [
{
id: 'msg_67c97c02656c81908e080dfdf4a03cd1',
type: 'message',
status: 'completed',
role: 'assistant',
content: [
{
type: 'output_text',
text: content,
annotations: [],
},
],
},
],
usage,
incomplete_details: null,
},
};
}

it('should set the correct api version', async () => {
prepareJsonResponse();

await provider.responses('test-deployment').doGenerate({
inputFormat: 'prompt',
mode: { type: 'regular' },
prompt: TEST_PROMPT,
});

expect(
server.calls[0].requestUrlSearchParams.get('api-version'),
).toStrictEqual('2025-03-01-preview');
});

it('should pass headers', async () => {
prepareJsonResponse();

const provider = createAzure({
resourceName: 'test-resource',
apiKey: 'test-api-key',
headers: {
'Custom-Provider-Header': 'provider-header-value',
},
});

await provider.responses('test-deployment').doGenerate({
inputFormat: 'prompt',
mode: { type: 'regular' },
prompt: TEST_PROMPT,
headers: {
'Custom-Request-Header': 'request-header-value',
},
});

expect(server.calls[0].requestHeaders).toStrictEqual({
'api-key': 'test-api-key',
'content-type': 'application/json',
'custom-provider-header': 'provider-header-value',
'custom-request-header': 'request-header-value',
});
});

it('should use the baseURL correctly', async () => {
prepareJsonResponse();

const provider = createAzure({
baseURL: 'https://test-resource.openai.azure.com/openai',
apiKey: 'test-api-key',
});

await provider.responses('test-deployment').doGenerate({
inputFormat: 'prompt',
mode: { type: 'regular' },
prompt: TEST_PROMPT,
});

expect(server.calls[0].requestUrl).toStrictEqual(
'https://test-resource.openai.azure.com/openai/responses?api-version=2025-03-01-preview',
);
});
});
});
Loading
Loading