Last updated 2026-05-17

Azure OpenAI Provider

The azureopenai provider talks to Azure-hosted OpenAI deployments. The request and response shapes are OpenAI-compatible; the differences from the OpenAI provider are the endpoint URL (deployment-scoped), the auth header (api-key instead of Authorization: Bearer), and a mandatory api-version query parameter.

Capability-wise it matches OpenAI: chat (Provider), embeddings (Embedder), TTS (Speaker), and Whisper STT (Transcriber).

Import path

import (
"github.com/elloloop/llmrouter"
"github.com/elloloop/llmrouter/providers/azureopenai"
)

Construction

Azure requires the resource name, the deployment name, and the API version. The library models these as required options:

p, err := azureopenai.New(
llmrouter.WithAPIKey(os.Getenv("AZURE_OPENAI_KEY")),
azureopenai.WithResource("my-resource"), // {resource}.openai.azure.com
azureopenai.WithDeployment("gpt-4o-deployment"), // your deployment name
azureopenai.WithAPIVersion("2024-10-21"), // pinned API version
)
if err != nil {
log.Fatal(err)
}

Constructed endpoint: https://{resource}.openai.azure.com/openai/deployments/{deployment}/chat/completions?api-version={version}.

For Microsoft Entra ID (formerly Azure AD) auth, omit WithAPIKey and pass a custom http.Client via WithHTTPClient that injects a bearer token from azidentity.

Model vs. Deployment

Full example

package main
import (
"context"
"fmt"
"log"
"os"
"github.com/elloloop/llmrouter"
"github.com/elloloop/llmrouter/providers/azureopenai"
)
func main() {
p, err := azureopenai.New(
llmrouter.WithAPIKey(os.Getenv("AZURE_OPENAI_KEY")),
azureopenai.WithResource(os.Getenv("AZURE_OPENAI_RESOURCE")),
azureopenai.WithDeployment("gpt-4o"),
azureopenai.WithAPIVersion("2024-10-21"),
)
if err != nil {
log.Fatal(err)
}
stream, err := p.CompletionStream(context.Background(), llmrouter.ChatRequest{
Model: "gpt-4o",
Messages: []llmrouter.Message{
llmrouter.TextMessage("user", "Summarise the Azure OpenAI deployment model in one sentence."),
},
})
if err != nil {
log.Fatal(err)
}
for chunk := range stream.Chunks() {
for _, c := range chunk.Choices {
fmt.Print(c.Delta.Content)
}
}
if err := stream.Err(); err != nil {
log.Fatal(err)
}
fmt.Println()
}

Embeddings

Azure exposes embedding models via separate deployments. Construct a second provider pinned to your embedding deployment:

embed, _ := azureopenai.New(
llmrouter.WithAPIKey(os.Getenv("AZURE_OPENAI_KEY")),
azureopenai.WithResource(resource),
azureopenai.WithDeployment("text-embedding-3-large"),
azureopenai.WithAPIVersion("2024-10-21"),
)
resp, err := embed.Embed(ctx, llmrouter.EmbedRequest{
Model: "text-embedding-3-large",
Inputs: []string{"hello", "world"},
Dimensions: 1024,
})

Audio (TTS / Whisper)

Both Speak and Transcribe are available when the deployment is for a TTS model (tts-1, tts-1-hd) or for Whisper (whisper-1) respectively. The behaviour matches the OpenAI provider — only the auth and endpoint differ.

Error handling

Non-2xx responses surface as *llmrouter.ErrUpstream with Provider == "azureopenai". Common cases:

  • 401 — missing or wrong api-key.
  • 404 — deployment not found (check resource name + deployment name).
  • 429 — Azure capacity exhausted; honour Retry-After.
  • 400 — content filter (Azure's prompt or response policy triggered).

Caveats

  • One provider per deployment. Azure splits chat and embedding and TTS into separate deployments — construct one provider per deployment you need.
  • Content filter. Azure may return 400 with a content-filter body even when the upstream OpenAI model would have responded. Inspect ErrUpstream.Body.
  • Region pinning. Pin api-version to a known-good value; Azure rolls forward and old versions eventually 404.

See also