portfolio-optimiser/docs/2026-07-15-foundry-auth-recipe.md

3.7 KiB

Azure AI Foundry auth recipe (S4.1)

Verified against Microsoft Learn and the pinned agent-framework-foundry==1.8.2 in this repo's .venv (see .claude/projects/2026-07-15-s41-azure-preflight/research/01-foundry-auth-recipe.md). This note is the operator's manual auth recipe for the single planned live Foundry run (M1). The offline python -m portfolio_optimiser.preflight --profile azure checks the config; this doc covers what preflight cannot: the actual authentication.

The recipe (local dev, Intel Mac)

  1. az login — sign in to the correct Entra tenant with the Azure CLI. This is a manual operator step; the code never runs it (no auto-login).
  2. The code passes a lazy AzureCliCredential() (azure.identity.aio) to FoundryChatClient (AzureFoundryBackend.create_chat_client). Constructing the credential acquires no token — the token is fetched from the az login session only on the first live call. So passing a credential object is not auto-login.
    • AzureCliCredential is preferred over DefaultAzureCredential on a non-Azure host: the latter probes the IMDS managed-identity endpoint (times out / stalls) and can pick a stray identity.
  3. Set the config the preflight validates:
    • PORTFOLIO_FOUNDRY_PROJECT_ENDPOINT — the Foundry project endpoint (below).
    • PORTFOLIO_MODEL_MAP — path to an out-of-tree model_map with real azure deployment names, so tenant-specific names are never committed. (Or replace the REPLACE-WITH-* placeholders.)

FoundryChatClient signature (pinned 1.8.2)

FoundryChatClient(*, project_endpoint, model, credential, ...) — all keyword-only. credential is REQUIRED on the project_endpoint path: the 1.8.2 client raises ValueError("Azure credential is required when using project_endpoint without a project_client.") at construction if omitted. There is no lazy DefaultAzureCredential default. The model parameter is model (the portal deployment name), not deployment_name.

Endpoint format

Canonical Foundry project endpoint:

https://<resource>.services.ai.azure.com/api/projects/<project>

The bare-host form https://<resource>.services.ai.azure.com also appears in official samples and is accepted. The preflight requires https:// + a host ending .services.ai.azure.com; it does not require the /api/projects/ path. A *.openai.azure.com or *.cognitiveservices.azure.com endpoint is a different client surface (use OpenAIChatClient, not FoundryChatClient).

RBAC role

Assign Foundry User (role GUID 53ca6127-db72-4b80-b1b0-d745d6d5456d) to the identity, at the Foundry resource/project scope. Do NOT use Azure AI Developer (scoped to hubs/ML workspaces, not Foundry projects) or Cognitive Services User (the classic Azure OpenAI surface). Note the recent rename: Foundry User was formerly Azure AI User — reference the GUID, not the display name, since older community answers use both.

Preflight is necessary-but-not-sufficient

A green preflight rules out the offline-detectable misconfiguration class: unset endpoint, malformed/wrong-surface endpoint URL, unresolved REPLACE-WITH-* placeholder deployments, and an inconsistent model-map. It cannot prove the paid live call will succeed. The following surface only at the live call and are out of scope for an offline check:

  • RBAC — missing/wrong role → 403 (propagation lag can cause 401-then-200).
  • Token / tenant / consent — expired or wrong-tenant credential → 401.
  • DeploymentNotFound — a well-formed deployment name that does not exist in the project → 404.
  • api-version skew and regional outages.

Treat a green preflight as "config is shaped correctly", never as "M1 will work".