fix(azure_ai): strip scope from cache_control for Anthropic messages

Azure AI Foundry's Anthropic endpoint does not support the scope field in
cache_control. Strip it from both system and messages before sending.

Made-with: Cursor
This commit is contained in:
Sameer Kankute 2026-03-05 10:49:37 +05:30
parent cc989b1171
commit 482bc93910
2 changed files with 95 additions and 1 deletions

View File

@ -1,7 +1,7 @@
"""
Azure Anthropic messages transformation config - extends AnthropicMessagesConfig with Azure authentication
"""
from typing import TYPE_CHECKING, Any, List, Optional, Tuple
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
from litellm.llms.anthropic.experimental_pass_through.messages.transformation import (
AnthropicMessagesConfig,
@ -114,3 +114,53 @@ class AzureAnthropicMessagesConfig(AnthropicMessagesConfig):
return api_base
def _remove_scope_from_cache_control(
self, anthropic_messages_request: Dict
) -> None:
"""
Remove `scope` field from cache_control for Azure AI Foundry.
Azure AI Foundry's Anthropic endpoint does not support the `scope` field
(e.g., "global" for cross-request caching). Only `type` and `ttl` are supported.
Processes both `system` and `messages` content blocks.
"""
def _sanitize(cache_control: Any) -> None:
if isinstance(cache_control, dict):
cache_control.pop("scope", None)
def _process_content_list(content: list) -> None:
for item in content:
if isinstance(item, dict) and "cache_control" in item:
_sanitize(item["cache_control"])
if "system" in anthropic_messages_request:
system = anthropic_messages_request["system"]
if isinstance(system, list):
_process_content_list(system)
if "messages" in anthropic_messages_request:
for message in anthropic_messages_request["messages"]:
if isinstance(message, dict) and "content" in message:
content = message["content"]
if isinstance(content, list):
_process_content_list(content)
def transform_anthropic_messages_request(
self,
model: str,
messages: List[Dict],
anthropic_messages_optional_request_params: Dict,
litellm_params: GenericLiteLLMParams,
headers: dict,
) -> Dict:
anthropic_messages_request = super().transform_anthropic_messages_request(
model=model,
messages=messages,
anthropic_messages_optional_request_params=anthropic_messages_optional_request_params,
litellm_params=litellm_params,
headers=headers,
)
self._remove_scope_from_cache_control(anthropic_messages_request)
return anthropic_messages_request

View File

@ -239,6 +239,50 @@ class TestAzureAnthropicMessagesConfig:
assert "tools" in params
assert "tool_choice" in params
def test_transform_anthropic_messages_request_removes_scope_from_cache_control(
self,
):
"""Test that scope is removed from cache_control (Azure AI Foundry doesn't support it)"""
config = AzureAnthropicMessagesConfig()
model = "claude-sonnet-4-5"
messages = [
{
"role": "user",
"content": [
{
"type": "text",
"text": "Hello",
"cache_control": {"type": "ephemeral", "scope": "global"},
}
],
}
]
anthropic_messages_optional_request_params = {
"max_tokens": 1024,
"system": [
{
"type": "text",
"text": "You are an AI assistant.",
"cache_control": {"type": "ephemeral", "scope": "global"},
}
],
}
litellm_params = GenericLiteLLMParams()
headers = {}
result = config.transform_anthropic_messages_request(
model=model,
messages=messages,
anthropic_messages_optional_request_params=anthropic_messages_optional_request_params,
litellm_params=litellm_params,
headers=headers,
)
assert "scope" not in result["system"][0]["cache_control"]
assert result["system"][0]["cache_control"]["type"] == "ephemeral"
assert "scope" not in result["messages"][0]["content"][0]["cache_control"]
assert result["messages"][0]["content"][0]["cache_control"]["type"] == "ephemeral"
class TestProviderConfigManagerAzureAnthropicMessages:
"""Test ProviderConfigManager returns correct config for Azure AI Anthropic Messages API"""