# Azure OpenAI Provider

Azure OpenAI Service provides OpenAI-compatible models through Azure resources and deployments. In Go-AI, the deployment name is the model ID you pass to the provider.

## Setup

```go
import (
    "os"

    "github.com/digitallysavvy/go-ai/pkg/ai"
    "github.com/digitallysavvy/go-ai/pkg/providers/azure"
)
```

Set credentials:

```bash
export AZURE_API_KEY=your-key
export AZURE_RESOURCE_NAME=your-resource-name
```

Create a Responses model:

```go
provider, err := azure.New(azure.Config{
    APIKey:       os.Getenv("AZURE_API_KEY"),
    ResourceName: os.Getenv("AZURE_RESOURCE_NAME"),
})
if err != nil {
    log.Fatal(err)
}

model, err := provider.LanguageModel("gpt-4o-deployment")
if err != nil {
    log.Fatal(err)
}
```

`LanguageModel` matches the TypeScript Azure provider default and uses the Responses API at `/openai/v1/responses?api-version=v1`. Use `ChatModel` when you need the Chat Completions endpoint or `CompletionModel` when you need the legacy Completions endpoint.

## Microsoft Entra ID Authentication

Use `ADTokenProvider` when Azure OpenAI should authenticate with Microsoft Entra ID instead of an API key. The token provider is called with the request context and sets `Authorization: Bearer <token>` across Responses, Chat Completions, Completions, embeddings, image, speech, and transcription request surfaces.

```go
provider, err := azure.New(azure.Config{
    ResourceName: os.Getenv("AZURE_RESOURCE_NAME"),
    ADTokenProvider: func(ctx context.Context) (string, error) {
        return acquireToken(ctx)
    },
})
if err != nil {
    log.Fatal(err)
}
```

Passing both `APIKey` and `ADTokenProvider` returns an invalid-argument error, matching the TypeScript `createAzure` validation.

## Responses Provider Tools

Azure exposes the OpenAI Responses provider tools as Azure helpers:

- `azure.CodeInterpreter`
- `azure.FileSearch`
- `azure.ImageGeneration`
- `azure.WebSearch`
- `azure.WebSearchPreview`

Use them with Responses-compatible Azure deployments.

```go
searchContext := "low"
result, err := ai.StreamText(ctx, ai.StreamTextOptions{
    Model:  model,
    Prompt: "Summarize how to upgrade AI SDK from 5 to 6.",
    Tools: []types.Tool{
        azure.WebSearch(openaitool.WebSearchConfig{
            SearchContextSize: searchContext,
            Filters: &openaitool.WebSearchFilters{
                AllowedDomains: []string{"ai-sdk.dev"},
            },
        }),
    },
})
if err != nil {
    log.Fatal(err)
}
defer result.Close()
```

This matches the TypeScript `azure.tools` behavior by serializing provider-executed tools as OpenAI Responses hosted tools.

## Regional And Private Endpoints

For the standard Azure URL format, set `ResourceName`. For custom regional, proxy, or private-link endpoints, set `BaseURL` to the Azure OpenAI `/openai` prefix:

`ResourceName` is interpolated directly into the request host (`https://{resourceName}.openai.azure.com/...`), so `New` rejects a value that isn't a single DNS label (letters, digits, and hyphens, 1-63 characters, no leading/trailing hyphen) with an `InvalidArgumentError` before building the client — a value like `user@internal:8080/#` would otherwise rewrite the destination host. This only applies when `BaseURL` is unset; a custom `BaseURL` is used as-is and `ResourceName` is ignored.

```go
provider, err := azure.New(azure.Config{
    BaseURL: "https://your-resource-eastus.openai.azure.com/openai",
    APIKey:  os.Getenv("AZURE_API_KEY"),
})
if err != nil {
    log.Fatal(err)
}
```

The default URL shape matches TypeScript: `{baseURL}/v1{path}?api-version=v1`. This only applies to a recognized Azure OpenAI / AI Foundry / Cognitive Services host (`*.openai.azure.com`, `*.services.ai.azure.com`, `*.cognitiveservices.azure.com`); a `BaseURL` host outside those patterns is treated as a custom gateway and used exactly as given, with no `/v1` suffix or `api-version` query param appended — verify your proxy's URL matches one of the recognized patterns if you expect Azure's own query-string behavior.

Set `UseDeploymentBasedURLs` only for Azure deployments that still require `{baseURL}/deployments/{deployment}{path}`:

```go
provider, err := azure.New(azure.Config{
    ResourceName:           os.Getenv("AZURE_RESOURCE_NAME"),
    APIKey:                 os.Getenv("AZURE_API_KEY"),
    UseDeploymentBasedURLs: true,
})
if err != nil {
    log.Fatal(err)
}
```

## Other Model Types

Azure also supports completions, embeddings, image generation, speech, and transcription through deployment names:

```go
completionModel, err := provider.CompletionModel("gpt-35-turbo-instruct")
embeddingModel, err := provider.EmbeddingModel("text-embedding-3-small")
imageModel, err := provider.ImageModel("dall-e-3")
speechModel, err := provider.SpeechModel("tts-1")
transcriptionModel, err := provider.TranscriptionModel("whisper-1")
```

Azure DeepSeek deployments are available through `provider.DeepSeek("deployment-name")`. The model uses Azure authentication, the Azure `api-version` query parameter, and the `"azure"` provider option namespace while preserving DeepSeek reasoning-effort request behavior.

## MAI-Transcribe And MAI-Voice (Azure Speech)

`provider.TranscriptionModel` and `provider.SpeechModel` route Microsoft AI's MAI-Transcribe and MAI-Voice model families through the Azure Speech API instead of Azure OpenAI, since they use a different protocol and endpoint. Routing is automatic by model ID, and can be overridden per call with `providerOptions.azure.api` (`"openai"`, `"speech"`, or `"mai"`):

| Model ID | Default API |
| --- | --- |
| `mai-transcribe-2`, `mai-transcribe-1.5` | Speech (file transcription) |
| `mai-transcribe-2-streaming` | MAI realtime (streaming transcription only) |
| `mai-voice-2`, `mai-voice-2-flash`, `mai-voice-2.1`, `mai-voice-2.1-flash` | Speech (SSML text-to-speech) |
| any other deployment name | OpenAI |

```go
provider, err := azure.New(azure.Config{
    ResourceName: os.Getenv("AZURE_RESOURCE_NAME"),
    APIKey:       os.Getenv("AZURE_API_KEY"),
})
if err != nil {
    log.Fatal(err)
}

transcriptionModel, err := provider.TranscriptionModel("mai-transcribe-2")
if err != nil {
    log.Fatal(err)
}
result, err := transcriptionModel.DoTranscribe(ctx, &provider.TranscriptionOptions{
    Audio:    audioBytes,
    MimeType: "audio/wav",
    ProviderOptions: map[string]interface{}{
        "azure": map[string]interface{}{
            "timestamps": "segment", // "word" | "segment" | "none"; default "segment"
            "locales":    []interface{}{"en"},
        },
    },
})
```

`providerOptions.azure` is validated strictly for both models: an unrecognized key, or an option a given MAI model doesn't support (e.g. `diarization` on MAI-Transcribe-1.5), returns an `InvalidArgumentError` instead of being silently dropped; an option that only applies to a different backend (e.g. `style` routed to OpenAI TTS) is reported as an `unsupported` warning on the result instead.

MAI-Voice speech picks a default voice from `Language` when `Voice` is unset, and accepts `providerOptions.azure.style` / `styleDegree` for Azure's `mstts:express-as` speaking styles:

```go
speechModel, err := provider.SpeechModel("mai-voice-2")
result, err := speechModel.DoGenerate(ctx, &provider.SpeechGenerateOptions{
    Text:     "Hello from Azure.",
    Language: "es", // selects es-MX-Valeria when Voice is unset
    ProviderOptions: map[string]interface{}{
        "azure": map[string]interface{}{"style": "excited", "styleDegree": 1.5},
    },
})
```

MAI-Transcribe-2-Streaming only supports `ai.ExperimentalStreamTranscribe` (streaming transcription), over a WebSocket to the MAI realtime API; it requires 16-bit mono PCM audio at 16000 or 24000 Hz.

`gpt-realtime-whisper` deployments (and dated snapshots, e.g. `gpt-realtime-whisper-2026-01-01`) also support `ai.ExperimentalStreamTranscribe`, streaming over the OpenAI-compatible realtime WebSocket at your Azure resource's `/realtime?intent=transcription` endpoint instead of the REST transcription endpoint; `DoTranscribe` (non-streaming) is unsupported for these deployments, matching OpenAI's own `gpt-realtime-whisper` restriction.

Two optional `azure.Config` settings control the endpoints these models use, both defaulting from `ResourceName` and validated the same way as `ResourceName` itself (a single DNS label) before being used:

- `SpeechBaseURL` — the Azure Speech endpoint prefix (default `https://{ResourceName}.cognitiveservices.azure.com`), used for both MAI-Transcribe file transcription and MAI-Voice speech.
- `MaiBaseURL` — the MAI realtime endpoint prefix (default `https://{ResourceName}.services.ai.azure.com/mai/v1`), used for MAI-Transcribe-2-Streaming.

```go
provider, err := azure.New(azure.Config{
    ResourceName:  os.Getenv("AZURE_RESOURCE_NAME"),
    APIKey:        os.Getenv("AZURE_API_KEY"),
    SpeechBaseURL: "https://eastus.api.cognitive.microsoft.com",
})
```

## Custom Headers

Use `Headers` for request metadata that should be sent with every provider call:

```go
provider, err := azure.New(azure.Config{
    ResourceName: os.Getenv("AZURE_RESOURCE_NAME"),
    APIKey:       os.Getenv("AZURE_API_KEY"),
    Headers: map[string]string{
        "X-Request-ID": "unique-request-id",
    },
})
if err != nil {
    log.Fatal(err)
}
```

Per-call headers can also be supplied through generation options and override provider-level headers.
