Use Microsoft Azure (Foundry)
Plenty of organizations already have an agreement with Microsoft, with their rules about data, spending and access written into it. Microsoft Foundry, which includes the service long known as Azure OpenAI, runs OpenAI’s models inside that agreement, along with models from DeepSeek, Moonshot, Anthropic and others. Getting to a first answer takes a little longer than elsewhere, though, because Azure has its own words for things, and you meet three of them before any answer arrives: a resource, a deployment, and an endpoint.
LM15 reaches OpenAI’s models on Azure through the provider azure. The
request is the one you already know. What’s new is who signs in, which
resource the request goes to, and the name you gave the model when you
deployed it.
On this page, we’ll start by creating a resource and a deployment. Then we’ll send a question from a laptop and ask LM15 where it found the sign-in. Next, we’ll look at keys, and at what happens when a deployment name is wrong. We’ll run the same code with a managed identity and with a service principal, and plug in a credential from Microsoft’s own library. We’ll finish with the other models Foundry offers, and the mistakes that cost people the most time.
You’ll need an Azure subscription and the az command-line tool, which
Microsoft’s install guide
explains. Every request on this page costs a fraction of a cent.
Create a resource and a deployment
Section titled “Create a resource and a deployment”Everything on Azure lives in a resource: a named container, in a region
you choose, that holds your models and receives their bill. In the
Foundry portal, create a Foundry resource, then
deploy a model into it. A deployment is a model you’ve set up in your
resource, under a name you choose, with its own quota. That name, not the
model’s, is what your requests will use. Name the deployment after the model,
gpt-4.1-mini for example, and you’ll save yourself some confusion later.
The resource’s page shows its endpoint, the address your requests go to,
such as https://station.services.ai.azure.com, and two keys. You’ll need
the endpoint. Put it in the variable that Microsoft’s own libraries read:
export AZURE_OPENAI_ENDPOINT=https://station.services.ai.azure.comOlder Azure OpenAI resources have an address ending in openai.azure.com
instead. LM15 accepts either, and adds the rest of the path itself. Our test
resource is one of those older ones, so its address appears in the
recordings below.
Last, give yourself permission to call the models without a key. In the Azure portal, open the resource’s Access control (IAM) page and give your account the role Cognitive Services OpenAI User. Owning the resource isn’t enough, because the Owner role lets you manage a resource but not call its models. Like every new permission on Azure, it can take a few minutes to apply.
Sign in on your laptop
Section titled “Sign in on your laptop”Sign in once with az:
az loginThat’s all LM15 needs. The examples in these guides follow one small program: an assistant for a wildlife research station. Here’s its first question, sent to GPT-4.1 mini on Azure:
from lm15 import LMRouter, Message, Request
router = LMRouter()request = Request( model="azure:gpt-4.1-mini", system=( "You are the field assistant for a wildlife research " "station. Answer in two sentences." ), messages=[Message.user( "What might be eating the acorns under our oak trees at " "night?" )],)print(router.complete(request).text)Likely nocturnal animals eating acorns could include squirrels, raccoons, or deer, as they are known to forage at night. Additionally, small rodents like mice or even wild boars might also be responsible.
import { LMRouter, Message } from "@lm15/lm15";
const router = new LMRouter();const request = { model: "azure:gpt-4.1-mini", system: "You are the field assistant for a wildlife research station. " + "Answer in two sentences.", messages: [Message.user( "What might be eating the acorns under our oak trees at night?", )],};const response = await router.complete(request);console.log(response.text);Animals such as squirrels, mice, and raccoons commonly forage for acorns under oak trees at night. Some species of deer and opossums may also feed on acorns during nighttime hours.
use lm15::{LMRouter, Message, Request};
// Dependencies: lm15, tokio (macros, rt-multi-thread)#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let router = LMRouter::new(); let request = Request { model: "azure:gpt-4.1-mini".into(), system: Some(concat!( "You are the field assistant for a wildlife research ", "station. Answer in two sentences.", ).into()), messages: vec![Message::user(concat!( "What might be eating the acorns under our oak trees at ", "night?", ))?], ..Default::default() }; let response = router.complete(&request).await?; println!("{}", response.text().unwrap_or_default()); Ok(())}At night, animals like raccoons, deer, and nocturnal rodents such as mice or squirrels are likely eating the acorns under your oak trees. Additionally, some wild pigs or opossums might also be foraging for acorns after dark.
package main
import ( "context" "fmt" lm15 "github.com/lm15-dev/lm15-go/v2")
func main() { router := lm15.NewRouter() request := &lm15.Request{ Model: "azure:gpt-4.1-mini", System: lm15.System( "You are the field assistant for a wildlife research " + "station. Answer in two sentences.", ), Messages: []lm15.Message{ lm15.UserMessage( "What might be eating the acorns under our oak trees " + "at night?", ), }, } response, err := router.Complete(context.Background(), request) if err != nil { panic(err) } fmt.Println(response.TextOr(""))}Nocturnal animals like raccoons, opossums, and rodents such as mice or squirrels commonly eat acorns at night. Additionally, some species of deer and wild boars may also forage for acorns during nighttime hours.
This page has no R example yet: choose Python, TypeScript, Rust or Go above.
This page has no Julia example yet: choose Python, TypeScript, Rust or Go above.
The model string azure:gpt-4.1-mini names the provider, azure, and the
deployment. Nothing in the program names a key or a resource: LM15 asked
az for a sign-in token, and found the resource in AZURE_OPENAI_ENDPOINT.
(Our test machine signed az in with a service principal; a person’s
az login works the same way.)
Ask where the sign-in came from
Section titled “Ask where the sign-in came from”A program that finds its sign-in on its own should be able to say where. The doctor answers that question without sending anything:
from lm15.doctor import explain_auth
print(explain_auth("azure"))auth for provider 'azure': - explicit api_keys entry: not provided - env $AZURE_OPENAI_API_KEY: not set - Entra service principal from AZURE_* env: AZURE_TENANT_ID/AZURE_CLIENT_ID + secret or certificate not set - Entra workload identity: AZURE_FEDERATED_TOKEN_FILE not set ? Azure managed identity: managed identity (imds) probed at request time ? az account get-access-token: `az` run at request time - Azure PowerShell Get-AzAccessToken: pwsh is not on PATH - azd auth token: azd is not on PATH configured: probably — Azure managed identity, az account get-access-token (unprobed offline) setting authority_host: https://login.microsoftonline.com (from default) setting scope: https://ai.azure.com/.default (from default) base url: https://lm15-oai-29d280ed6f8e.openai.azure.com/openai/v1 (from env $AZURE_OPENAI_ENDPOINT)
import { describeReport, explainAuth } from "@lm15/lm15";
console.log(describeReport(explainAuth("azure")));auth for provider "azure": - explicit api_keys entry: not provided - env $AZURE_OPENAI_API_KEY: not set - Entra service principal from AZURE_* env: AZURE_TENANT_ID/AZURE_CLIENT_ID + secret or certificate not set - Entra workload identity: AZURE_FEDERATED_TOKEN_FILE not set ? Azure managed identity: managed identity (imds) probed at request time ? az account get-access-token: `az` run at request time - Azure PowerShell Get-AzAccessToken: pwsh is not on PATH - azd auth token: azd is not on PATH base URL: https://lm15-oai-29d280ed6f8e.openai.azure.com/openai/v1 (env $AZURE_OPENAI_ENDPOINT) configured: probably — Azure managed identity, az account get-access-token (unprobed offline) setting authority_host: https://login.microsoftonline.com (from default) setting scope: https://ai.azure.com/.default (from default)
use lm15::auth::explain_auth;
fn main() -> Result<(), Box<dyn std::error::Error>> { let report = explain_auth("azure", &Default::default())?; println!("{}", report.describe()); Ok(())}auth for provider "azure": - explicit api_keys entry: not provided - env $AZURE_OPENAI_API_KEY: not set - Entra service principal from AZURE_* env: AZURE_TENANT_ID/AZURE_CLIENT_ID + secret or certificate not set - Entra workload identity: AZURE_FEDERATED_TOKEN_FILE not set ? Azure managed identity: managed identity (imds) probed at request time ? az account get-access-token: `az` run at request time - Azure PowerShell Get-AzAccessToken: pwsh is not on PATH - azd auth token: azd is not on PATH configured: probably — Azure managed identity, az account get-access-token (unprobed offline) setting authority_host: https://login.microsoftonline.com (from default) setting scope: https://ai.azure.com/.default (from default) base url: https://lm15-oai-29d280ed6f8e.openai.azure.com/openai/v1 (from env $AZURE_OPENAI_ENDPOINT)
package main
import ( "fmt" lm15 "github.com/lm15-dev/lm15-go/v2")
func main() { report, err := lm15.ExplainAuth("azure", lm15.ExplainOptions{}) if err != nil { panic(err) } fmt.Println(report.Describe())}auth for provider "azure": - explicit api_keys entry: not provided - env $AZURE_OPENAI_API_KEY: not set - Entra service principal from AZURE_* env: AZURE_TENANT_ID/AZURE_CLIENT_ID + secret or certificate not set - Entra workload identity: AZURE_FEDERATED_TOKEN_FILE not set ? Azure managed identity: managed identity (imds) probed at request time ? az account get-access-token: `az` run at request time - Azure PowerShell Get-AzAccessToken: pwsh is not on PATH - azd auth token: azd is not on PATH configured: probably — Azure managed identity, az account get-access-token (unprobed offline) setting authority_host: https://login.microsoftonline.com (from default) setting scope: https://ai.azure.com/.default (from default) base url: https://lm15-oai-29d280ed6f8e.openai.azure.com/openai/v1 (from env $AZURE_OPENAI_ENDPOINT)
This page has no R example yet: choose Python, TypeScript, Rust or Go above.
This page has no Julia example yet: choose Python, TypeScript, Rust or Go above.
The report lists every place LM15 looks for an Azure sign-in. After the
resource’s key, the order is the same as in Microsoft’s own
DefaultAzureCredential. A - means nothing is there. A ? means something
might be there, but finding out needs the network or a command, so the
doctor, which uses neither, can’t say. Here
there are two. The managed identity only exists on Azure’s own machines, so
on a laptop it comes up empty, and az answers. Because LM15 asks for the
managed identity first, the first request on a laptop takes a moment longer:
about a second and a half in our Python test.
The last lines say where the request would go. The authority is the
Microsoft server that issues sign-in tokens, and the scope names the
service the token is for; you’d change either only for a government cloud or
a policy that names another. The base URL came from AZURE_OPENAI_ENDPOINT,
with LM15’s path added.
Run the doctor whenever a request seems to use an account or a resource you didn’t expect. It prints no secret, so it’s safe to paste into a bug report.
Keys, and the one you forgot
Section titled “Keys, and the one you forgot”Each resource also has two keys, and some people would rather use one than
sign in. Put it in AZURE_OPENAI_API_KEY, and the program above works
unchanged; we ran it with a key and the endpoint and nothing else, and it
answered in all four languages.
Be careful with a key on a machine where you also use az. Here’s the
doctor on our laptop again, with the key set as well:
auth for provider 'azure': - explicit api_keys entry: not provided => env $AZURE_OPENAI_API_KEY: set (value never shown) - Entra service principal from AZURE_* env: AZURE_TENANT_ID/AZURE_CLIENT_ID + secret or certificate not set - Entra workload identity: AZURE_FEDERATED_TOKEN_FILE not set ~ Azure managed identity: managed identity (imds) probed at request time ~ az account get-access-token: `az` run at request time - Azure PowerShell Get-AzAccessToken: pwsh is not on PATH - azd auth token: azd is not on PATH configured: yes — env $AZURE_OPENAI_API_KEY setting authority_host: https://login.microsoftonline.com (from default) setting scope: https://ai.azure.com/.default (from default) base url: https://lm15-oai-29d280ed6f8e.openai.azure.com/openai/v1 (from env $AZURE_OPENAI_ENDPOINT)
auth for provider "azure": - explicit api_keys entry: not provided => env $AZURE_OPENAI_API_KEY: set (value never shown) - Entra service principal from AZURE_* env: AZURE_TENANT_ID/AZURE_CLIENT_ID + secret or certificate not set - Entra workload identity: AZURE_FEDERATED_TOKEN_FILE not set ~ Azure managed identity: managed identity (imds) probed at request time ~ az account get-access-token: `az` run at request time - Azure PowerShell Get-AzAccessToken: pwsh is not on PATH - azd auth token: azd is not on PATH base URL: https://lm15-oai-29d280ed6f8e.openai.azure.com/openai/v1 (env $AZURE_OPENAI_ENDPOINT) configured: yes — env $AZURE_OPENAI_API_KEY setting authority_host: https://login.microsoftonline.com (from default) setting scope: https://ai.azure.com/.default (from default)
auth for provider "azure": - explicit api_keys entry: not provided => env $AZURE_OPENAI_API_KEY: set (value never shown) - Entra service principal from AZURE_* env: AZURE_TENANT_ID/AZURE_CLIENT_ID + secret or certificate not set - Entra workload identity: AZURE_FEDERATED_TOKEN_FILE not set ~ Azure managed identity: managed identity (imds) probed at request time ~ az account get-access-token: `az` run at request time - Azure PowerShell Get-AzAccessToken: pwsh is not on PATH - azd auth token: azd is not on PATH configured: yes — env $AZURE_OPENAI_API_KEY setting authority_host: https://login.microsoftonline.com (from default) setting scope: https://ai.azure.com/.default (from default) base url: https://lm15-oai-29d280ed6f8e.openai.azure.com/openai/v1 (from env $AZURE_OPENAI_ENDPOINT)
auth for provider "azure": - explicit api_keys entry: not provided => env $AZURE_OPENAI_API_KEY: set (value never shown) - Entra service principal from AZURE_* env: AZURE_TENANT_ID/AZURE_CLIENT_ID + secret or certificate not set - Entra workload identity: AZURE_FEDERATED_TOKEN_FILE not set ~ Azure managed identity: managed identity (imds) probed at request time ~ az account get-access-token: `az` run at request time - Azure PowerShell Get-AzAccessToken: pwsh is not on PATH - azd auth token: azd is not on PATH configured: yes — env $AZURE_OPENAI_API_KEY setting authority_host: https://login.microsoftonline.com (from default) setting scope: https://ai.azure.com/.default (from default) base url: https://lm15-oai-29d280ed6f8e.openai.azure.com/openai/v1 (from env $AZURE_OPENAI_ENDPOINT)
This page has no R example yet: choose Python, TypeScript, Rust or Go above.
This page has no Julia example yet: choose Python, TypeScript, Rust or Go above.
The key wins. It comes second in the list, right after a key your program
gives, and => marks it. Both places marked ? before are now marked ~,
which means they’re still there but LM15 won’t reach them. So a key you
exported last week for a test quietly replaces your sign-in. If it belongs to
the resource, requests keep working, but Azure no longer knows which person
or program sent them; if it belongs to another resource, they fail, and the
error says the credential came from AZURE_OPENAI_API_KEY. Microsoft
recommends signing in rather than using keys, and many organizations turn
keys off entirely. Use a key to try things; in a program, sign in.
The model string is a deployment
Section titled “The model string is a deployment”Most mistakes on a first Azure request come from the model string. Here’s the
first program with azure:gpt-4.1, the name of a model, where the resource
only has a deployment called gpt-4.1-mini:
UnsupportedModelError: The API deployment for this resource does not exist. If you created the deployment within the last 5 minutes, please wait a moment and try again. (azure, HTTP 404, request 7fe8ab6f-79d9-4255-ba9a-5071da2aaae8)
UnsupportedModelError: The API deployment for this resource does not exist. If you created the deployment within the last 5 minutes, please wait a moment and try again.
Error: UnsupportedModelError(ErrorMeta { message: "The API deployment for this resource does not exist. If you created the deployment within the last 5 minutes, please wait a moment and try again.", provider: Some("azure"), provider_code: Some("DeploymentNotFound"), status: Some(404), request_id: Some("3a539f2f-7891-46a8-8a3d-937de73f4d95"), retry_after: None, rate_limit_headers: DiagnosticHeaders({}), content_type: None, body_excerpt: Some("{\n \"error\": {\n \"type\": \"invalid_request_error\",\n \"code\": \"DeploymentNotFound\",\n \"message\": \"The API deployment for this resource does not exist. If you created the deployment within the last"), credential_source: None, feature: None })panic: The API deployment for this resource does not exist. If you created the deployment within the last 5 minutes, please wait a moment and try again. (azure, HTTP 404, request 868a10b4-d783-4674-87db-09ec82e50877)
This page has no R example yet: choose Python, TypeScript, Rust or Go above.
This page has no Julia example yet: choose Python, TypeScript, Rust or Go above.
Azure looks for a deployment called gpt-4.1, finds none, and says so, and
LM15 raises the same error as for any unknown model. The second sentence of
Azure’s message is worth taking seriously, because a deployment you created a
moment ago really does take a few minutes to answer. If yours is older than
that, open the resource in the Foundry portal and copy the deployment’s name
exactly.
Run on Azure
Section titled “Run on Azure”On Azure Container Apps, App Service, Functions or a virtual machine, your program can run as a managed identity, an identity Azure gives to a program instead of a person. Turn one on for your app, give it the Cognitive Services OpenAI User role on the resource, and store no key anywhere.
The code from your laptop works there unchanged. It’s better, though, to say which sign-in you expect:
from lm15 import LMRouter, Message, Request, RouterConfig
router = LMRouter(RouterConfig( credentials={"azure": "platform"},))request = Request( model="azure:gpt-4.1-mini", system=( "You are the field assistant for a wildlife research " "station. Answer in two sentences." ), messages=[Message.user( "What might be eating the acorns under our oak trees at " "night?" )],)print(router.complete(request).text)NotConfiguredError: azure: named credential "platform" — Azure managed identity — answered nothing (Azure managed identity: managed identity (imds) probed at request time). This door was told to use that identity only; it will not try the rest of the azure-chain chain.
To fix:
- credentials={"azure": "<platform|workload|environment|cli>"} names one identity; omit it to walk the azure-chain chainimport { LMRouter, Message } from "@lm15/lm15";
const router = new LMRouter({ credentials: { azure: "platform" },});const request = { model: "azure:gpt-4.1-mini", system: "You are the field assistant for a wildlife research station. " + "Answer in two sentences.", messages: [Message.user( "What might be eating the acorns under our oak trees at night?", )],};const response = await router.complete(request);console.log(response.text);NotConfiguredError: azure: named credential "platform" — Azure managed identity — answered nothing (managed-identity: managed identity (imds) probed at request time). Only this identity was requested; the rest of the azure-chain chain is not tried.
To fix:
- Configure credentials for azureuse lm15::{LMRouter, Message, Request, RouterConfig};
// Dependencies: lm15, tokio (macros, rt-multi-thread)#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let config = RouterConfig::new().credential("azure", "platform"); let router = LMRouter::with_config(config)?; let request = Request { model: "azure:gpt-4.1-mini".into(), system: Some(concat!( "You are the field assistant for a wildlife research ", "station. Answer in two sentences.", ).into()), messages: vec![Message::user(concat!( "What might be eating the acorns under our oak trees at ", "night?", ))?], ..Default::default() }; let response = router.complete(&request).await?; println!("{}", response.text().unwrap_or_default()); Ok(())}Error: NotConfiguredError(ErrorMeta { message: "azure: named credential \"platform\" — Azure managed identity — answered nothing (Azure managed identity: managed identity (imds) probed at request time); no other rung of the azure-chain chain will be tried\n\n To fix:\n - configure this identity, or omit the named credential to walk the chain\n", provider: Some("azure"), provider_code: None, status: None, request_id: None, retry_after: None, rate_limit_headers: DiagnosticHeaders({}), content_type: None, body_excerpt: None, credential_source: None, feature: None })package main
import ( "context" "fmt" lm15 "github.com/lm15-dev/lm15-go/v2")
func main() { router, err := lm15.NewRouterWithConfig(lm15.RouterConfig{ Credentials: map[string]string{"azure": "platform"}, }) if err != nil { panic(err) } request := &lm15.Request{ Model: "azure:gpt-4.1-mini", System: lm15.System( "You are the field assistant for a wildlife research " + "station. Answer in two sentences.", ), Messages: []lm15.Message{ lm15.UserMessage( "What might be eating the acorns under our oak trees " + "at night?", ), }, } response, err := router.Complete(context.Background(), request) if err != nil { panic(err) } fmt.Println(response.TextOr(""))}panic: azure: named credential "platform" — Azure managed identity — answered nothing (Azure managed identity: managed identity (imds) probed at request time). This door was told to use that identity only; it will not try the rest of the azure-chain chain.
To fix:
- credentials={"azure": "<platform|workload|environment|cli>"} names one identity; omit it to walk the azure-chain chainThis page has no R example yet: choose Python, TypeScript, Rust or Go above.
This page has no Julia example yet: choose Python, TypeScript, Rust or Go above.
"platform" tells LM15 to use the managed identity and nothing else. Without
it, LM15 walks the whole list from the doctor’s report, so an app deployed
with a leftover AZURE_OPENAI_API_KEY would quietly use that key. With it, a
missing identity stops the program at the first request. The recording above
is from our laptop, which has no managed identity, so it stopped at once,
naming what it looked for. That’s what you want: a laptop isn’t the machine
it names. (We haven’t recorded this on an Azure machine for this page.) If
your app has more than one managed identity, set AZURE_CLIENT_ID to the one
you want, as with Microsoft’s own libraries.
Run somewhere else
Section titled “Run somewhere else”GitHub Actions, another cloud, or a server in your own building have no
managed identity. There, a program signs in as a service principal, an
identity you register in Microsoft Entra ID for a program, with a secret or a
certificate. Give it the same role on the resource, put its details in the
variables Microsoft’s libraries read, AZURE_TENANT_ID, AZURE_CLIENT_ID
and AZURE_CLIENT_SECRET (or AZURE_CLIENT_CERTIFICATE_PATH), and name the
sign-in "environment":
from lm15 import LMRouter, Message, Request, RouterConfig
router = LMRouter(RouterConfig( credentials={"azure": "environment"},))request = Request( model="azure:gpt-4.1-mini", system=( "You are the field assistant for a wildlife research " "station. Answer in two sentences." ), messages=[Message.user( "What might be eating the acorns under our oak trees at " "night?" )],)print(router.complete(request).text)import { LMRouter, Message } from "@lm15/lm15";
const router = new LMRouter({ credentials: { azure: "environment" },});const request = { model: "azure:gpt-4.1-mini", system: "You are the field assistant for a wildlife research station. " + "Answer in two sentences.", messages: [Message.user( "What might be eating the acorns under our oak trees at night?", )],};const response = await router.complete(request);console.log(response.text);use lm15::{LMRouter, Message, Request, RouterConfig};
// Dependencies: lm15, tokio (macros, rt-multi-thread)#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let config = RouterConfig::new().credential("azure", "environment"); let router = LMRouter::with_config(config)?; let request = Request { model: "azure:gpt-4.1-mini".into(), system: Some(concat!( "You are the field assistant for a wildlife research ", "station. Answer in two sentences.", ).into()), messages: vec![Message::user(concat!( "What might be eating the acorns under our oak trees at ", "night?", ))?], ..Default::default() }; let response = router.complete(&request).await?; println!("{}", response.text().unwrap_or_default()); Ok(())}package main
import ( "context" "fmt" lm15 "github.com/lm15-dev/lm15-go/v2")
func main() { router, err := lm15.NewRouterWithConfig(lm15.RouterConfig{ Credentials: map[string]string{"azure": "environment"}, }) if err != nil { panic(err) } request := &lm15.Request{ Model: "azure:gpt-4.1-mini", System: lm15.System( "You are the field assistant for a wildlife research " + "station. Answer in two sentences.", ), Messages: []lm15.Message{ lm15.UserMessage( "What might be eating the acorns under our oak trees " + "at night?", ), }, } response, err := router.Complete(context.Background(), request) if err != nil { panic(err) } fmt.Println(response.TextOr(""))}This page has no R example yet: choose Python, TypeScript, Rust or Go above.
This page has no Julia example yet: choose Python, TypeScript, Rust or Go above.
We ran this with the service principal’s variables and the endpoint, and nothing else; it answered in all four languages.
A secret in the environment is a password that someone could copy. Where you
can, avoid it. On AKS, workload identity gives your pods a token instead:
name the sign-in "workload". On GitHub Actions, Microsoft’s azure/login
action signs az in with a token that GitHub issues for each run, and no
secret is stored: name the sign-in "cli".
Use a credential from the Azure library
Section titled “Use a credential from the Azure library”Some teams require every program to sign in through Microsoft’s own
azure-identity library, with one credential class chosen on purpose, so
that security reviews look at one piece of code. LM15 fits in with that. Give
the router a function that returns a token, and LM15 asks it before each
request. The Azure library makes that function for you:
from azure.identity import AzureCliCredential, get_bearer_token_providerfrom lm15 import LMRouter, Message, Request, RouterConfig
token = get_bearer_token_provider( AzureCliCredential(), "https://ai.azure.com/.default")router = LMRouter(RouterConfig( api_keys={"azure": token},))request = Request( model="azure:gpt-4.1-mini", system=( "You are the field assistant for a wildlife research " "station. Answer in two sentences." ), messages=[Message.user( "What might be eating the acorns under our oak trees at " "night?" )],)print(router.complete(request).text)Nocturnal animals like squirrels, raccoons, and deer often forage for acorns under oak trees at night. Owls and other small mammals might also be attracted to the area as they hunt for insects or rodents feeding on the acorns.
import { AzureCliCredential, getBearerTokenProvider,} from "@azure/identity";import { LMRouter, Message } from "@lm15/lm15";
const token = getBearerTokenProvider( new AzureCliCredential(), "https://ai.azure.com/.default",);const router = new LMRouter({ apiKeys: { azure: token },});const request = { model: "azure:gpt-4.1-mini", system: "You are the field assistant for a wildlife research station. " + "Answer in two sentences.", messages: [Message.user( "What might be eating the acorns under our oak trees at night?", )],};const response = await router.complete(request);console.log(response.text);At night, animals like squirrels, mice, raccoons, and deer are common acorn eaters. Owls and other nocturnal predators may also be nearby, attracted by these small mammals.
This example has no Rust version. LM15’s Rust credential functions return their value at once, while the Azure SDK for Rust fetches tokens asynchronously, so the two don’t fit together cleanly yet; in Rust, use one of the four names on this page. Choose Python, TypeScript or Go above to see the example.
package main
import ( "context" "fmt" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/azidentity" lm15 "github.com/lm15-dev/lm15-go/v2")
func main() { azure, err := azidentity.NewAzureCLICredential(nil) if err != nil { panic(err) } scope := policy.TokenRequestOptions{ Scopes: []string{"https://ai.azure.com/.default"}, } token := func(ctx context.Context) (lm15.Credential, error) { t, err := azure.GetToken(ctx, scope) return lm15.BearerToken{Value: t.Token}, err } router, err := lm15.NewRouterWithConfig(lm15.RouterConfig{ APIKeys: map[string]lm15.CredentialLike{"azure": token}, }) if err != nil { panic(err) } request := &lm15.Request{ Model: "azure:gpt-4.1-mini", System: lm15.System( "You are the field assistant for a wildlife research " + "station. Answer in two sentences.", ), Messages: []lm15.Message{ lm15.UserMessage( "What might be eating the acorns under our oak trees " + "at night?", ), }, } response, err := router.Complete(context.Background(), request) if err != nil { panic(err) } fmt.Println(response.TextOr(""))}At night, acorns are commonly eaten by nocturnal wildlife such as squirrels, mice, and raccoons. Additionally, some species of owls and other small mammals might forage for acorns as part of their diet.
This page has no R example yet: choose Python, TypeScript, Rust or Go above.
This page has no Julia example yet: choose Python, TypeScript, Rust or Go above.
The token is for the scope https://ai.azure.com/.default, the same one the
doctor showed. AzureCliCredential uses your az sign-in, which suits a
laptop. In production, swap in the class your team chose, such as
ManagedIdentityCredential, and nothing else changes. The Azure library
decides when to fetch a new token, and LM15 sends whatever it returns.
Other models on Foundry
Section titled “Other models on Foundry”azure sends requests the way OpenAI’s current API does, and it’s the
provider to use for OpenAI’s models; it also carries files, batches, speech
and realtime sessions. Two more providers reach the rest of Foundry.
azure-chatspeaks the older Chat Completions API, which Foundry also offers for models from other makers, such as DeepSeek and Kimi. It reads the same variables asazure. Use theservices.ai.azure.comendpoint for these models.azure-anthropicreaches Claude, with the deployment name as the model. It reads its own variable,ANTHROPIC_FOUNDRY_BASE_URL, and signs in the same way as the others.
We’ve checked azure-chat with OpenAI’s models, which answered, and
azure-anthropic as far as our test subscription allows: Azure accepted our
credentials, then reported that no Claude deployment exists, because the
subscription has no quota for Claude. Claude and the other makers’ models
have been confirmed independently, with real answers, on another Foundry
account.
Mistakes that cost the most time
Section titled “Mistakes that cost the most time”- “The API deployment for this resource does not exist.” The model string must be the deployment’s name. A new deployment takes a few minutes to answer.
- 401 or 403 with
az login, even though you own the resource. Owning a resource doesn’t let you call its models. Give yourself the Cognitive Services OpenAI User role, then wait a few minutes. - The wrong resource or the wrong account. A leftover
AZURE_OPENAI_API_KEYor service-principal variable comes beforeaz. The doctor shows which one wins. - Keys that suddenly stop working. Your organization may have turned keys off for the resource. Sign in instead.
Try it yourself
Section titled “Try it yourself”Predict the answer before you run the doctor.
- You’re signed in with
az, and a colleague’s shell script exportsAZURE_TENANT_ID,AZURE_CLIENT_IDandAZURE_CLIENT_SECRETfor a test service principal. Which identity does your next request use? - The same program, now with
"cli"as its named sign-in. Which identity? - You change the model string from
azure:gpt-4.1-minitoazure-chat:gpt-4.1-mini. What happens?
What to expect
- The service principal. Its variables come before
azin the doctor’s list, just as the key did in Keys, and the one you forgot. - Your own
azsign-in. With a name, LM15 looks nowhere else. - It works, and answers from the same deployment.
azure-chatreaches the same resource through the Chat Completions API instead of the newer one.
Use a cloud provider compares Azure with AWS and Google Cloud, and lists the providers LM15 has for each. Handle errors and retries shows how to catch a refused request in a long-running program. Set up authentication explains the rule behind this page: LM15 always says which credential it used.