From 14eed8aff72c57ca83a7ff96e4527f98b5a4ef1c Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Sat, 13 Dec 2025 16:08:03 -0800 Subject: [PATCH] [Fixes] A2a Gateway - ensure azure foundry agents work (#17943) * add agents v2 fixes azure * fix auth * get_azure_ad_token fix * docs foundry --- .../docs/providers/azure_ai_agents.md | 88 ++++++++++++++----- litellm/llms/azure/common_utils.py | 20 ++--- litellm/llms/azure_ai/agents/handler.py | 36 ++++++-- .../llms/azure_ai/agents/transformation.py | 66 +++++++++++--- .../public_endpoints/agent_create_fields.json | 40 +++++++-- tests/llm_translation/test_azure_agents.py | 75 ++++++++++------ 6 files changed, 234 insertions(+), 91 deletions(-) diff --git a/docs/my-website/docs/providers/azure_ai_agents.md b/docs/my-website/docs/providers/azure_ai_agents.md index 4a428f893d0..219d3597f23 100644 --- a/docs/my-website/docs/providers/azure_ai_agents.md +++ b/docs/my-website/docs/providers/azure_ai_agents.md @@ -9,7 +9,47 @@ Call Azure AI Foundry Agents in the OpenAI Request/Response format. |----------|---------| | Description | Azure AI Foundry Agents provides hosted agent runtimes that can execute agentic workflows with foundation models, tools, and code interpreters. | | Provider Route on LiteLLM | `azure_ai/agents/{AGENT_ID}` | -| Provider Doc | [Azure AI Foundry Agents ↗](https://learn.microsoft.com/en-us/rest/api/aifoundry/aiagents/create-thread-and-run/create-thread-and-run) | +| Provider Doc | [Azure AI Foundry Agents ↗](https://learn.microsoft.com/en-us/azure/ai-foundry/agents/quickstart) | + +## Authentication + +Azure AI Foundry Agents require **Azure AD authentication** (not API keys). You can authenticate using: + +### Option 1: Service Principal (Recommended for Production) + +Set these environment variables: + +```bash +export AZURE_TENANT_ID="your-tenant-id" +export AZURE_CLIENT_ID="your-client-id" +export AZURE_CLIENT_SECRET="your-client-secret" +``` + +LiteLLM will automatically obtain an Azure AD token using these credentials. + +### Option 2: Azure AD Token (Manual) + +Pass a token directly via `api_key`: + +```bash +# Get token via Azure CLI +az account get-access-token --resource "https://ai.azure.com" --query accessToken -o tsv +``` + +### Required Azure Role + +Your Service Principal or user must have the **Azure AI Developer** or **Azure AI User** role on your Azure AI Foundry project. + +To assign via Azure CLI: +```bash +az role assignment create \ + --assignee-object-id "" \ + --assignee-principal-type "ServicePrincipal" \ + --role "Azure AI Developer" \ + --scope "/subscriptions//resourceGroups//providers/Microsoft.CognitiveServices/accounts/" +``` + +Or add via **Azure AI Foundry Portal** → Your Project → **Project users** → **+ New user**. ## Quick Start @@ -34,6 +74,7 @@ You can find the Agent ID in your Azure AI Foundry portal under Agents. import litellm # Make a completion request to your Azure AI Foundry Agent +# Uses AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET env vars for auth response = litellm.completion( model="azure_ai/agents/asst_abc123", messages=[ @@ -42,8 +83,7 @@ response = litellm.completion( "content": "Explain machine learning in simple terms" } ], - api_base="https://your-project.services.ai.azure.com", - api_key="your-api-key", + api_base="https://your-resource.services.ai.azure.com/api/projects/your-project", ) print(response.choices[0].message.content) @@ -62,8 +102,7 @@ response = await litellm.acompletion( "content": "What are the key principles of software architecture?" } ], - api_base="https://your-project.services.ai.azure.com", - api_key="your-api-key", + api_base="https://your-resource.services.ai.azure.com/api/projects/your-project", stream=True, ) @@ -84,14 +123,18 @@ model_list: - model_name: azure-agent-1 litellm_params: model: azure_ai/agents/asst_abc123 - api_base: https://your-project.services.ai.azure.com - api_key: os.environ/AZURE_API_KEY + api_base: https://your-resource.services.ai.azure.com/api/projects/your-project + # Service Principal auth (recommended) + tenant_id: os.environ/AZURE_TENANT_ID + client_id: os.environ/AZURE_CLIENT_ID + client_secret: os.environ/AZURE_CLIENT_SECRET - model_name: azure-agent-math-tutor litellm_params: model: azure_ai/agents/asst_def456 - api_base: https://your-project.services.ai.azure.com - api_key: os.environ/AZURE_API_KEY + api_base: https://your-resource.services.ai.azure.com/api/projects/your-project + # Or pass Azure AD token directly + api_key: os.environ/AZURE_AD_TOKEN ``` @@ -196,16 +239,16 @@ for chunk in stream: ## Environment Variables -You can set the following environment variables to configure Azure AI Foundry Agents: - | Variable | Description | |----------|-------------| -| `AZURE_API_BASE` | The Azure AI Foundry project endpoint (e.g., `https://your-project.services.ai.azure.com`) | -| `AZURE_API_KEY` | Your Azure AI Foundry API key | +| `AZURE_TENANT_ID` | Azure AD tenant ID for Service Principal auth | +| `AZURE_CLIENT_ID` | Application (client) ID of your Service Principal | +| `AZURE_CLIENT_SECRET` | Client secret for your Service Principal | ```bash -export AZURE_API_BASE="https://your-project.services.ai.azure.com" -export AZURE_API_KEY="your-api-key" +export AZURE_TENANT_ID="your-tenant-id" +export AZURE_CLIENT_ID="your-client-id" +export AZURE_CLIENT_SECRET="your-client-secret" ``` ## Conversation Continuity (Thread Management) @@ -219,8 +262,7 @@ import litellm response1 = await litellm.acompletion( model="azure_ai/agents/asst_abc123", messages=[{"role": "user", "content": "My name is Alice"}], - api_base="https://your-project.services.ai.azure.com", - api_key="your-api-key", + api_base="https://your-resource.services.ai.azure.com/api/projects/your-project", ) # Get the thread_id from the response @@ -230,8 +272,7 @@ thread_id = response1._hidden_params.get("thread_id") response2 = await litellm.acompletion( model="azure_ai/agents/asst_abc123", messages=[{"role": "user", "content": "What's my name?"}], - api_base="https://your-project.services.ai.azure.com", - api_key="your-api-key", + api_base="https://your-resource.services.ai.azure.com/api/projects/your-project", thread_id=thread_id, # Pass the thread_id to continue conversation ) @@ -256,8 +297,7 @@ response = litellm.completion( "content": "Analyze this data and provide insights", } ], - api_base="https://your-project.services.ai.azure.com", - api_key="your-api-key", + api_base="https://your-resource.services.ai.azure.com/api/projects/your-project", thread_id="thread_abc123", # Optional: Continue existing conversation instructions="Be concise and focus on key insights", # Optional: Override agent instructions ) @@ -271,8 +311,10 @@ model_list: - model_name: azure-agent-analyst litellm_params: model: azure_ai/agents/asst_abc123 - api_base: https://your-project.services.ai.azure.com - api_key: os.environ/AZURE_API_KEY + api_base: https://your-resource.services.ai.azure.com/api/projects/your-project + tenant_id: os.environ/AZURE_TENANT_ID + client_id: os.environ/AZURE_CLIENT_ID + client_secret: os.environ/AZURE_CLIENT_SECRET instructions: "Be concise and focus on key insights" ``` diff --git a/litellm/llms/azure/common_utils.py b/litellm/llms/azure/common_utils.py index 74520942619..85596a628da 100644 --- a/litellm/llms/azure/common_utils.py +++ b/litellm/llms/azure/common_utils.py @@ -294,20 +294,18 @@ def get_azure_ad_token( Azure AD token as string if successful, None otherwise """ # Extract parameters + # Use `or` instead of default parameter to handle cases where key exists but value is None azure_ad_token_provider = litellm_params.get("azure_ad_token_provider") - azure_ad_token = litellm_params.get("azure_ad_token", None) or get_secret_str( + azure_ad_token = litellm_params.get("azure_ad_token") or get_secret_str( "AZURE_AD_TOKEN" ) - tenant_id = litellm_params.get("tenant_id", os.getenv("AZURE_TENANT_ID")) - client_id = litellm_params.get("client_id", os.getenv("AZURE_CLIENT_ID")) - client_secret = litellm_params.get( - "client_secret", os.getenv("AZURE_CLIENT_SECRET") - ) - azure_username = litellm_params.get("azure_username", os.getenv("AZURE_USERNAME")) - azure_password = litellm_params.get("azure_password", os.getenv("AZURE_PASSWORD")) - scope = litellm_params.get( - "azure_scope", - os.getenv("AZURE_SCOPE", "https://cognitiveservices.azure.com/.default"), + tenant_id = litellm_params.get("tenant_id") or os.getenv("AZURE_TENANT_ID") + client_id = litellm_params.get("client_id") or os.getenv("AZURE_CLIENT_ID") + client_secret = litellm_params.get("client_secret") or os.getenv("AZURE_CLIENT_SECRET") + azure_username = litellm_params.get("azure_username") or os.getenv("AZURE_USERNAME") + azure_password = litellm_params.get("azure_password") or os.getenv("AZURE_PASSWORD") + scope = litellm_params.get("azure_scope") or os.getenv( + "AZURE_SCOPE", "https://cognitiveservices.azure.com/.default" ) if scope is None: scope = "https://cognitiveservices.azure.com/.default" diff --git a/litellm/llms/azure_ai/agents/handler.py b/litellm/llms/azure_ai/agents/handler.py index e67e72b676b..379dc1e1c55 100644 --- a/litellm/llms/azure_ai/agents/handler.py +++ b/litellm/llms/azure_ai/agents/handler.py @@ -1,5 +1,5 @@ """ -Handler for Azure AI Agent Service API. +Handler for Azure Foundry Agent Service API. This handler executes the multi-step agent flow: 1. Create thread (or use existing) @@ -8,8 +8,14 @@ This handler executes the multi-step agent flow: 4. Retrieve the assistant's response messages Model format: azure_ai/agents/ +API Base format: https://.services.ai.azure.com/api/projects/ + +Authentication: Uses Azure AD Bearer tokens (not API keys) + Get token via: az account get-access-token --resource 'https://ai.azure.com' Supports both polling-based and native streaming (SSE) modes. + +See: https://learn.microsoft.com/en-us/azure/ai-foundry/agents/quickstart """ import asyncio @@ -60,24 +66,27 @@ class AzureAIAgentsHandler: # ------------------------------------------------------------------------- # URL Builders # ------------------------------------------------------------------------- + # Azure Foundry Agents API uses /assistants, /threads, etc. directly + # See: https://learn.microsoft.com/en-us/azure/ai-foundry/agents/quickstart + # ------------------------------------------------------------------------- def _build_thread_url(self, api_base: str, api_version: str) -> str: - return f"{api_base}/openai/threads?api-version={api_version}" + return f"{api_base}/threads?api-version={api_version}" def _build_messages_url(self, api_base: str, thread_id: str, api_version: str) -> str: - return f"{api_base}/openai/threads/{thread_id}/messages?api-version={api_version}" + return f"{api_base}/threads/{thread_id}/messages?api-version={api_version}" def _build_runs_url(self, api_base: str, thread_id: str, api_version: str) -> str: - return f"{api_base}/openai/threads/{thread_id}/runs?api-version={api_version}" + return f"{api_base}/threads/{thread_id}/runs?api-version={api_version}" def _build_run_status_url(self, api_base: str, thread_id: str, run_id: str, api_version: str) -> str: - return f"{api_base}/openai/threads/{thread_id}/runs/{run_id}?api-version={api_version}" + return f"{api_base}/threads/{thread_id}/runs/{run_id}?api-version={api_version}" def _build_list_messages_url(self, api_base: str, thread_id: str, api_version: str) -> str: - return f"{api_base}/openai/threads/{thread_id}/messages?api-version={api_version}" + return f"{api_base}/threads/{thread_id}/messages?api-version={api_version}" def _build_create_thread_and_run_url(self, api_base: str, api_version: str) -> str: """URL for the create-thread-and-run endpoint (supports streaming).""" - return f"{api_base}/openai/threads/runs?api-version={api_version}" + return f"{api_base}/threads/runs?api-version={api_version}" # ------------------------------------------------------------------------- # Response Helpers @@ -140,12 +149,21 @@ class AzureAIAgentsHandler: optional_params: dict, headers: Optional[dict], ) -> tuple: - """Prepare common parameters for completion.""" + """Prepare common parameters for completion. + + Azure Foundry Agents API uses Bearer token authentication: + - Authorization: Bearer (Azure AD token from 'az account get-access-token --resource https://ai.azure.com') + + See: https://learn.microsoft.com/en-us/azure/ai-foundry/agents/quickstart + """ if headers is None: headers = {} headers["Content-Type"] = "application/json" + + # Azure Foundry Agents uses Bearer token authentication + # The api_key here is expected to be an Azure AD token if api_key: - headers["api-key"] = api_key + headers["Authorization"] = f"Bearer {api_key}" api_version = optional_params.get("api_version", self.config.DEFAULT_API_VERSION) agent_id = self.config._get_agent_id(model, optional_params) diff --git a/litellm/llms/azure_ai/agents/transformation.py b/litellm/llms/azure_ai/agents/transformation.py index af49ac32bc1..01945aad323 100644 --- a/litellm/llms/azure_ai/agents/transformation.py +++ b/litellm/llms/azure_ai/agents/transformation.py @@ -1,17 +1,24 @@ """ -Transformation for Azure AI Agent Service API. +Transformation for Azure Foundry Agent Service API. -Azure AI Agent Service provides an Assistants-like API for running agents. +Azure Foundry Agent Service provides an Assistants-like API for running agents. This follows the OpenAI Assistants pattern: create thread -> add messages -> create/poll run. Model format: azure_ai/agents/ +API Base format: https://.services.ai.azure.com/api/projects/ + +Authentication: Uses Azure AD Bearer tokens (not API keys) + Get token via: az account get-access-token --resource 'https://ai.azure.com' + The API uses these endpoints: -- POST /openai/threads - Create a thread -- POST /openai/threads/{thread_id}/messages - Add message to thread -- POST /openai/threads/{thread_id}/runs - Create a run -- GET /openai/threads/{thread_id}/runs/{run_id} - Poll run status -- GET /openai/threads/{thread_id}/messages - List messages in thread +- POST /threads - Create a thread +- POST /threads/{thread_id}/messages - Add message to thread +- POST /threads/{thread_id}/runs - Create a run +- GET /threads/{thread_id}/runs/{run_id} - Poll run status +- GET /threads/{thread_id}/messages - List messages in thread + +See: https://learn.microsoft.com/en-us/azure/ai-foundry/agents/quickstart """ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union @@ -59,8 +66,10 @@ class AzureAIAgentsConfig(BaseConfig): 4. Retrieve the assistant's response messages """ - # Default API version for Azure AI Agent Service - DEFAULT_API_VERSION = "2024-07-01-preview" + # Default API version for Azure Foundry Agent Service + # GA version: 2025-05-01, Preview: 2025-05-15-preview + # See: https://learn.microsoft.com/en-us/azure/ai-foundry/agents/quickstart + DEFAULT_API_VERSION = "2025-05-01" # Polling configuration MAX_POLL_ATTEMPTS = 60 @@ -236,13 +245,19 @@ class AzureAIAgentsConfig(BaseConfig): api_base: Optional[str] = None, ) -> dict: """ - Validate and set up environment for Azure Agents requests. + Validate and set up environment for Azure Foundry Agents requests. + + Azure Foundry Agents uses Bearer token authentication with Azure AD tokens. + Get token via: az account get-access-token --resource 'https://ai.azure.com' + + See: https://learn.microsoft.com/en-us/azure/ai-foundry/agents/quickstart """ headers["Content-Type"] = "application/json" - # Add API key if provided + # Azure Foundry Agents uses Bearer token authentication + # The api_key here is expected to be an Azure AD token if api_key: - headers["api-key"] = api_key + headers["Authorization"] = f"Bearer {api_key}" return headers @@ -310,15 +325,38 @@ class AzureAIAgentsConfig(BaseConfig): headers: Optional[dict] = None, ) -> Any: """ - Dispatch method for Azure AI Agents completion. + Dispatch method for Azure Foundry Agents completion. Routes to sync or async completion based on acompletion flag. Supports native streaming via SSE when stream=True and acompletion=True. + + Authentication: Uses Azure AD Bearer tokens. + - Pass api_key directly as an Azure AD token + - Or set up Azure AD credentials via environment variables for automatic token retrieval: + - AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET (Service Principal) + + See: https://learn.microsoft.com/en-us/azure/ai-foundry/agents/quickstart """ + from litellm.llms.azure.common_utils import get_azure_ad_token from litellm.llms.azure_ai.agents.handler import azure_ai_agents_handler + from litellm.types.router import GenericLiteLLMParams + # If no api_key is provided, try to get Azure AD token if api_key is None: - raise ValueError("api_key is required for Azure AI Agents") + # Try to get Azure AD token using the existing Azure auth mechanisms + # This uses the scope for Azure AI (ai.azure.com) instead of cognitive services + # Create a GenericLiteLLMParams with the scope override for Azure Foundry Agents + azure_auth_params = dict(litellm_params) if litellm_params else {} + azure_auth_params["azure_scope"] = "https://ai.azure.com/.default" + api_key = get_azure_ad_token(GenericLiteLLMParams(**azure_auth_params)) + + if api_key is None: + raise ValueError( + "api_key (Azure AD token) is required for Azure Foundry Agents. " + "Either pass api_key directly, or set AZURE_TENANT_ID, AZURE_CLIENT_ID, " + "and AZURE_CLIENT_SECRET environment variables for Service Principal auth. " + "Manual token: az account get-access-token --resource 'https://ai.azure.com'" + ) if acompletion: if stream: # Native async streaming via SSE - return the async generator directly diff --git a/litellm/proxy/public_endpoints/agent_create_fields.json b/litellm/proxy/public_endpoints/agent_create_fields.json index 9ac1b3deebd..347a58a7675 100644 --- a/litellm/proxy/public_endpoints/agent_create_fields.json +++ b/litellm/proxy/public_endpoints/agent_create_fields.json @@ -93,8 +93,8 @@ { "key": "api_base", "label": "Azure AI API Base", - "placeholder": "https://your-project.services.ai.azure.com", - "tooltip": "The base URL for your Azure AI Foundry project endpoint", + "placeholder": "https://your-resource.services.ai.azure.com/api/projects/your-project", + "tooltip": "The base URL for your Azure AI Foundry project endpoint (e.g., https://your-resource.services.ai.azure.com/api/projects/your-project)", "required": true, "field_type": "text", "default_value": null, @@ -102,10 +102,40 @@ }, { "key": "api_key", - "label": "Azure AI API Key", + "label": "Azure AD Token", "placeholder": null, - "tooltip": "API key for authenticating with your Azure AI Foundry project", - "required": true, + "tooltip": "Azure AD Bearer token for authentication. Optional if using Service Principal credentials below. Get via: az account get-access-token --resource https://ai.azure.com", + "required": false, + "field_type": "password", + "default_value": null, + "include_in_litellm_params": true + }, + { + "key": "tenant_id", + "label": "Azure Tenant ID", + "placeholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "tooltip": "Azure AD Tenant ID for Service Principal authentication. Find in Azure Portal > Azure Active Directory > Overview", + "required": false, + "field_type": "text", + "default_value": null, + "include_in_litellm_params": true + }, + { + "key": "client_id", + "label": "Azure Client ID", + "placeholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "tooltip": "Application (client) ID of your Service Principal. Find in Azure Portal > App registrations > your app", + "required": false, + "field_type": "text", + "default_value": null, + "include_in_litellm_params": true + }, + { + "key": "client_secret", + "label": "Azure Client Secret", + "placeholder": null, + "tooltip": "Client secret for your Service Principal. Create in Azure Portal > App registrations > your app > Certificates & secrets", + "required": false, "field_type": "password", "default_value": null, "include_in_litellm_params": true diff --git a/tests/llm_translation/test_azure_agents.py b/tests/llm_translation/test_azure_agents.py index 84fac21d0d7..66a46d53383 100644 --- a/tests/llm_translation/test_azure_agents.py +++ b/tests/llm_translation/test_azure_agents.py @@ -1,9 +1,9 @@ """ -Tests for Azure AI Agent Service integration. +Tests for Azure Foundry Agent Service integration. -These tests require an Azure AI Agent Service endpoint and a pre-configured agent. +These tests require an Azure Foundry Agent Service endpoint and a pre-configured agent. -The Azure AI Agent Service uses the Assistants API pattern: +The Azure Foundry Agent Service uses the Assistants API pattern: 1. Create a thread 2. Add messages to the thread 3. Create and poll a run @@ -11,9 +11,16 @@ The Azure AI Agent Service uses the Assistants API pattern: Model format: azure_ai/agents/ +API Base format: https://.services.ai.azure.com/api/projects/ + +Authentication: Uses Azure AD Bearer tokens (not API keys) + Get token via: az account get-access-token --resource 'https://ai.azure.com' + Example environment variables: - AZURE_AI_API_BASE=https://your-project.services.ai.azure.com - AZURE_AI_API_KEY=your-api-key + AZURE_AGENTS_API_BASE=https://litellm-ci-cd-prod.services.ai.azure.com/api/projects/litellm-ci-cd + AZURE_AGENTS_API_KEY= + +See: https://learn.microsoft.com/en-us/azure/ai-foundry/agents/quickstart """ import os @@ -29,13 +36,15 @@ import litellm @pytest.mark.asyncio async def test_azure_ai_agents_acompletion_non_streaming(): """ - Test non-streaming acompletion call to Azure AI Agent Service. + Test non-streaming acompletion call to Azure Foundry Agent Service. Uses the multi-step flow: create thread -> add messages -> create/poll run -> get messages """ - api_base = os.environ.get("AZURE_API_BASE") - api_key = os.environ.get("AZURE_API_KEY") - agent_id = "asst_shNRIVxMPuvSRVWP5WvVe4jE" + api_base = os.environ.get("AZURE_AGENTS_API_BASE") + api_key = os.environ.get("AZURE_AGENTS_API_KEY") + agent_id = os.environ.get("AZURE_AGENTS_AGENT_ID", "asst_hbnoK9BOCcHhC3lC4MDroVGG") + if not api_base or not api_key: + pytest.skip("AZURE_AGENTS_API_BASE and AZURE_AGENTS_API_KEY environment variables required") response = await litellm.acompletion( model=f"azure_ai/agents/{agent_id}", @@ -62,12 +71,15 @@ async def test_azure_ai_agents_acompletion_non_streaming(): @pytest.mark.asyncio async def test_azure_ai_agents_acompletion_streaming(): """ - Test native streaming acompletion call to Azure AI Agent Service. + Test native streaming acompletion call to Azure Foundry Agent Service. Uses the create-thread-and-run endpoint with stream=True for SSE streaming. """ - api_base = os.environ.get("AZURE_API_BASE") - api_key = os.environ.get("AZURE_API_KEY") - agent_id = os.environ.get("AZURE_AGENTS_AGENT_ID", "asst_shNRIVxMPuvSRVWP5WvVe4jE") + api_base = os.environ.get("AZURE_AGENTS_API_BASE") + api_key = os.environ.get("AZURE_AGENTS_API_KEY") + agent_id = os.environ.get("AZURE_AGENTS_AGENT_ID", "asst_hbnoK9BOCcHhC3lC4MDroVGG") + + if not api_base or not api_key: + pytest.skip("AZURE_AGENTS_API_BASE and AZURE_AGENTS_API_KEY environment variables required") response = await litellm.acompletion( model=f"azure_ai/agents/{agent_id}", @@ -223,7 +235,7 @@ def test_azure_ai_agents_config_transform_request(): assert request["messages"][0]["role"] == "system" assert request["messages"][1]["role"] == "user" assert "api_version" in request - assert request["api_version"] == "2024-07-01-preview" + assert request["api_version"] == "2025-05-01" def test_azure_ai_agents_provider_detection(): @@ -243,7 +255,9 @@ def test_azure_ai_agents_provider_detection(): def test_azure_ai_agents_validate_environment(): """ - Test that headers are correctly set up. + Test that headers are correctly set up with Bearer token authentication. + + Azure Foundry Agents uses Bearer token authentication (Azure AD tokens). """ from litellm.llms.azure_ai.agents.transformation import AzureAIAgentsConfig @@ -255,41 +269,44 @@ def test_azure_ai_agents_validate_environment(): messages=[], optional_params={}, litellm_params={}, - api_key="test-api-key", - api_base="https://test.services.ai.azure.com", + api_key="test-azure-ad-token", + api_base="https://test.services.ai.azure.com/api/projects/test-project", ) assert headers["Content-Type"] == "application/json" - assert headers["api-key"] == "test-api-key" + assert headers["Authorization"] == "Bearer test-azure-ad-token" def test_azure_ai_agents_handler_url_builders(): """ Test the URL building methods in the handler. + + Azure Foundry Agents API uses direct paths without /openai/ prefix. + See: https://learn.microsoft.com/en-us/azure/ai-foundry/agents/quickstart """ from litellm.llms.azure_ai.agents.handler import AzureAIAgentsHandler handler = AzureAIAgentsHandler() - api_base = "https://test.services.ai.azure.com" - api_version = "2024-07-01-preview" + api_base = "https://test.services.ai.azure.com/api/projects/test-project" + api_version = "2025-05-01" thread_id = "thread_abc123" run_id = "run_xyz789" - # Test thread URL - uses /openai/ prefix + # Test thread URL - direct path without /openai/ prefix thread_url = handler._build_thread_url(api_base, api_version) - assert thread_url == f"{api_base}/openai/threads?api-version={api_version}" + assert thread_url == f"{api_base}/threads?api-version={api_version}" # Test messages URL messages_url = handler._build_messages_url(api_base, thread_id, api_version) - assert messages_url == f"{api_base}/openai/threads/{thread_id}/messages?api-version={api_version}" + assert messages_url == f"{api_base}/threads/{thread_id}/messages?api-version={api_version}" # Test runs URL runs_url = handler._build_runs_url(api_base, thread_id, api_version) - assert runs_url == f"{api_base}/openai/threads/{thread_id}/runs?api-version={api_version}" + assert runs_url == f"{api_base}/threads/{thread_id}/runs?api-version={api_version}" # Test run status URL status_url = handler._build_run_status_url(api_base, thread_id, run_id, api_version) - assert status_url == f"{api_base}/openai/threads/{thread_id}/runs/{run_id}?api-version={api_version}" + assert status_url == f"{api_base}/threads/{thread_id}/runs/{run_id}?api-version={api_version}" def test_azure_ai_agents_extract_content_from_messages(): @@ -340,12 +357,12 @@ async def test_azure_ai_agents_conversation_continuity(): """ Test that thread_id can be used for conversation continuity. """ - api_base = os.environ.get("AZURE_AI_API_BASE") - api_key = os.environ.get("AZURE_AI_API_KEY") - agent_id = os.environ.get("AZURE_AI_AGENTS_AGENT_ID", "asst_shNRIVxMPuvSRVWP5WvVe4jE") + api_base = os.environ.get("AZURE_AGENTS_API_BASE") + api_key = os.environ.get("AZURE_AGENTS_API_KEY") + agent_id = os.environ.get("AZURE_AGENTS_AGENT_ID", "asst_hbnoK9BOCcHhC3lC4MDroVGG") if not api_base or not api_key: - pytest.skip("AZURE_AI_API_BASE and AZURE_AI_API_KEY environment variables required") + pytest.skip("AZURE_AGENTS_API_BASE and AZURE_AGENTS_API_KEY environment variables required") try: # First message