From 1e15ff50adef1c44e9eb91628a783e18463451bc Mon Sep 17 00:00:00 2001 From: Soukyu <168991065+ducanhnguyen223@users.noreply.github.com> Date: Fri, 16 Oct 2026 00:23:54 +0700 Subject: [PATCH] fix(azure-anthropic): strip auth params from body --- .../llms/azure_ai/anthropic/transformation.py | 12 +++--- ...st_azure_ad_token_credential_resolution.py | 40 +++++++++++++++++-- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/litellm/llms/azure_ai/anthropic/transformation.py b/litellm/llms/azure_ai/anthropic/transformation.py index 18a4eeb7c0a..0b588b6cc52 100644 --- a/litellm/llms/azure_ai/anthropic/transformation.py +++ b/litellm/llms/azure_ai/anthropic/transformation.py @@ -6,9 +6,14 @@ from typing import Final from litellm.llms.anthropic.chat.transformation import AnthropicConfig from litellm.llms.azure.common_utils import BaseAzureLLM +from litellm.llms.azure_ai.common_utils import AZURE_ENTRA_LITELLM_PARAM_KEYS from litellm.types.llms.openai import AllMessageValues from litellm.types.router import GenericLiteLLMParams +_AZURE_ANTHROPIC_NON_BODY_PARAMS: Final = AZURE_ENTRA_LITELLM_PARAM_KEYS | frozenset( + {"api_key", "azure_ad_token_provider", "extra_body", "max_retries", "stream_options"} +) + def _promote_extra_body_to_optional_params(optional_params: dict) -> None: """Promote anthropic-native passthrough keys out of ``extra_body``. @@ -128,9 +133,4 @@ class AzureAnthropicConfig(AnthropicConfig): headers=headers, ) - # Remove unsupported parameters for Azure AI Anthropic - data.pop("extra_body", None) - data.pop("max_retries", None) - data.pop("stream_options", None) - - return data + return {key: value for key, value in data.items() if key not in _AZURE_ANTHROPIC_NON_BODY_PARAMS} diff --git a/tests/unit/test_azure_ad_token_credential_resolution.py b/tests/unit/test_azure_ad_token_credential_resolution.py index f6f47b3f4c4..07106582d0e 100644 --- a/tests/unit/test_azure_ad_token_credential_resolution.py +++ b/tests/unit/test_azure_ad_token_credential_resolution.py @@ -28,9 +28,7 @@ Tests below pin two things: ``azure_ad_token`` from a deployment's ``litellm_params``. """ -from unittest.mock import MagicMock, patch - -import pytest +from typing import Final class TestCredentialLiteLLMParamsAzureAdToken: @@ -211,3 +209,39 @@ class TestRouterCredentialResolutionS3OutputBucket: assert credentials is not None assert credentials.get("s3_bucket_name") == "in-bucket" assert "s3_output_bucket_name" not in credentials + + +def test_azure_anthropic_request_body_does_not_include_azure_credentials() -> None: + from litellm.llms.azure_ai.anthropic.transformation import AzureAnthropicConfig + + credential_params: Final = { + "api_key": "test-api-key", + "azure_ad_token": "test-access-token", + "azure_ad_token_provider": "test-token-provider", + "client_id": "test-client-id", + "client_secret": "test-client-secret", + "tenant_id": "test-tenant-id", + "azure_scope": "test-scope", + "azure_username": "test-username", + "azure_password": "test-password", + } + output_config: Final = {"format": {"type": "json_object"}} + extra_body_params: Final = { + key: value + for key, value in credential_params.items() + if key not in {"api_key", "azure_ad_token"} + } + body: Final = AzureAnthropicConfig().transform_request( + model="claude-3-5-sonnet-20241022", + messages=[{"role": "user", "content": "hello"}], + optional_params={ + "api_key": credential_params["api_key"], + "azure_ad_token": credential_params["azure_ad_token"], + "extra_body": {**extra_body_params, "output_config": output_config}, + }, + litellm_params={}, + headers={}, + ) + + assert set(credential_params).isdisjoint(body) + assert body["output_config"] == output_config