From 23ccff69a105bf70499c6003393e3390d6985aeb Mon Sep 17 00:00:00 2001 From: shrey-berri Date: Fri, 23 Oct 2026 16:34:55 -0700 Subject: [PATCH] fix(azure_ai): forward the thinking-binding-controls beta header to Foundry (#48033) --- .../src/providers/anthropic/beta.rs | 6 +- litellm/anthropic_beta_headers_config.json | 2 +- litellm/proxy/litellm_pre_call_utils.py | 5 +- .../unit/proxy/test_litellm_pre_call_utils.py | 61 +++++++++++++++++++ .../test_anthropic_beta_headers_filtering.py | 4 +- 5 files changed, 72 insertions(+), 6 deletions(-) diff --git a/litellm-rust/crates/llms-types/src/providers/anthropic/beta.rs b/litellm-rust/crates/llms-types/src/providers/anthropic/beta.rs index b1509313b88..8429c98a88a 100644 --- a/litellm-rust/crates/llms-types/src/providers/anthropic/beta.rs +++ b/litellm-rust/crates/llms-types/src/providers/anthropic/beta.rs @@ -235,7 +235,10 @@ impl AnthropicBeta { BetaProvider::Databricks, ], Self::Compact20260112 => &[BetaProvider::AzureAi, BetaProvider::BedrockConverse], - Self::ComputerUse20250124 | Self::ComputerUse20251124 | Self::Context1m20250807 => &[], + Self::ComputerUse20250124 + | Self::ComputerUse20251124 + | Self::Context1m20250807 + | Self::ThinkingBindingControls20260801 => &[], Self::DangerousToolUse20260903 => &[BetaProvider::BedrockConverse], Self::Effort20251124 => &[BetaProvider::VertexAi], Self::FastMode20260201 | Self::Oauth20250420 => &[ @@ -272,7 +275,6 @@ impl AnthropicBeta { BetaProvider::Databricks, ], Self::StructuredOutputs20251113 => &[BetaProvider::Bedrock, BetaProvider::VertexAi], - Self::ThinkingBindingControls20260801 => &[BetaProvider::AzureAi], Self::ThinkingDisplayUpdates20260818 => &[BetaProvider::Databricks], Self::ToolSearchTool20251019 => &[ BetaProvider::Anthropic, diff --git a/litellm/anthropic_beta_headers_config.json b/litellm/anthropic_beta_headers_config.json index 236b18fe428..d68de6c9d4d 100644 --- a/litellm/anthropic_beta_headers_config.json +++ b/litellm/anthropic_beta_headers_config.json @@ -67,7 +67,7 @@ "structured-outputs-2025-11-13": "structured-outputs-2025-11-13", "text_editor_20241022": null, "text_editor_20250124": null, - "thinking-binding-controls-2026-08-01": null, + "thinking-binding-controls-2026-08-01": "thinking-binding-controls-2026-08-01", "thinking-display-updates-2026-08-18": "thinking-display-updates-2026-08-18", "token-efficient-tools-2025-02-19": null, "web-fetch-2025-09-10": "web-fetch-2025-09-10", diff --git a/litellm/proxy/litellm_pre_call_utils.py b/litellm/proxy/litellm_pre_call_utils.py index da849ee4fad..8dc7501bc3b 100644 --- a/litellm/proxy/litellm_pre_call_utils.py +++ b/litellm/proxy/litellm_pre_call_utils.py @@ -4278,6 +4278,9 @@ _ANTHROPIC_API_HEADER_PROVIDERS: Final = ",".join( LlmProviders.VERTEX_AI.value, ) ) +_NATIVE_MESSAGES_ANTHROPIC_API_HEADER_PROVIDERS: Final = ",".join( + (LlmProviders.AZURE_AI.value, LlmProviders.DATABRICKS.value) +) _ANTHROPIC_OAUTH_CREDENTIAL_PROVIDERS: Final = ",".join(sorted(ANTHROPIC_OAUTH_FORWARD_PROVIDERS)) @@ -4306,7 +4309,7 @@ def add_provider_specific_headers_to_request( custom_llm_provider=_ANTHROPIC_API_HEADER_PROVIDERS, extra_headers=anthropic_api_headers ), ProviderSpecificHeader( - custom_llm_provider=LlmProviders.DATABRICKS.value, + custom_llm_provider=_NATIVE_MESSAGES_ANTHROPIC_API_HEADER_PROVIDERS, extra_headers=anthropic_api_headers, native_anthropic_messages_only=True, ), diff --git a/tests/unit/proxy/test_litellm_pre_call_utils.py b/tests/unit/proxy/test_litellm_pre_call_utils.py index 04f6381f32f..64ff8392c02 100644 --- a/tests/unit/proxy/test_litellm_pre_call_utils.py +++ b/tests/unit/proxy/test_litellm_pre_call_utils.py @@ -8111,6 +8111,67 @@ def test_client_anthropic_api_headers_stay_off_openai_compatible_providers(): assert forwarded == {} +def test_client_anthropic_api_headers_stay_off_azure_ai_outside_native_messages(): + forwarded = _headers_forwarded_to({"anthropic-beta": "thinking-binding-controls-2026-08-01"}, "azure_ai") + + assert forwarded == {} + + +@pytest.mark.asyncio +@pytest.mark.usefixtures("local_beta_headers_config") +async def test_client_thinking_binding_beta_reaches_azure_ai_foundry_claude(): + import httpx + + from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler + + data: Final[dict] = {"model": "claude-opus-4-8"} # mutable-ok: the function under test writes into it + add_provider_specific_headers_to_request( + data=data, + headers={ + "content-type": "application/json", + "anthropic-beta": "thinking-binding-controls-2026-08-01", + "anthropic-version": "2026-08-01", + }, + ) + received: Final[list[httpx.Request]] = [] # mutable-ok: the fake Foundry endpoint records what the proxy sent + + def foundry_answers(request: httpx.Request) -> httpx.Response: + received.append(request) + return httpx.Response( + status_code=200, + json={ + "id": "msg_01", + "type": "message", + "role": "assistant", + "model": "claude-opus-4-8", + "content": [{"type": "text", "text": "hello"}], + "stop_reason": "end_turn", + "stop_sequence": None, + "usage": {"input_tokens": 12, "output_tokens": 6}, + }, + request=request, + ) + + upstream: Final = AsyncHTTPHandler(transport=httpx.MockTransport(foundry_answers)) + try: + await litellm.anthropic.messages.acreate( + max_tokens=16, + messages=[{"role": "user", "content": "hi"}], + thinking={"type": "adaptive", "block_binding": {"prefix_mismatch_behavior": "drop_block"}}, + model="azure_ai/claude-opus-4-8", + api_key="foundry-key", + api_base="https://my-resource.services.ai.azure.com/anthropic", + client=upstream, + provider_specific_header=data["provider_specific_header"], + ) + finally: + await upstream.close() + + assert received[0].headers["anthropic-beta"] == "thinking-binding-controls-2026-08-01" + assert received[0].headers["anthropic-version"] == "2026-08-01" + assert json.loads(received[0].content)["thinking"]["block_binding"] == {"prefix_mismatch_behavior": "drop_block"} + + TYPESAFE_DANGER_ZONE_HEADERS = { "TypeSafe-Danger-Zone-Images": "true", "typesafe-danger-zone-long-context": "true", diff --git a/tests/unit/test_anthropic_beta_headers_filtering.py b/tests/unit/test_anthropic_beta_headers_filtering.py index 2a85122d91c..4d5ba1c5897 100644 --- a/tests/unit/test_anthropic_beta_headers_filtering.py +++ b/tests/unit/test_anthropic_beta_headers_filtering.py @@ -429,13 +429,13 @@ class TestAnthropicBetaHeadersFiltering: assert filtered == ["fine-grained-tool-streaming-2025-05-14"] @pytest.mark.parametrize( - "provider", ["anthropic", "bedrock", "bedrock_converse", "vertex_ai", "databricks"] + "provider", ["anthropic", "azure_ai", "bedrock", "bedrock_converse", "vertex_ai", "databricks"] ) def test_thinking_binding_controls_forwarded(self, provider): """`thinking.block_binding` (preserved thinking, Claude Fable 5.1) is only accepted alongside thinking-binding-controls-2026-08-01. The body field is forwarded untouched, so stripping the header (previously unknown, hence - dropped) makes Bedrock and Vertex reject the request with + dropped) makes Bedrock, Vertex and Azure AI Foundry reject the request with "thinking.adaptive.block_binding: Extra inputs are not permitted".""" filtered = filter_and_transform_beta_headers( beta_headers=["thinking-binding-controls-2026-08-01"],