[Bug fix] - Fix /messages fallback from Anthropic API -> Bedrock API (#13946)

* use helper get_provider_specific_headers

* fix get_provider_specific_headers

* test_anthropic_messages_fallbacks

* bedrock/us.anthropic.claude-sonnet-4

* fix: get_provider_specific_headers

* TestProviderSpecificHeaderUtils

* test_anthropic_messages_fallbacks
This commit is contained in:
Ishaan Jaff
2025-08-25 13:44:54 -07:00
committed by GitHub
parent c1ee8c26af
commit 433d1a4947
6 changed files with 148 additions and 18 deletions
@@ -0,0 +1,23 @@
from typing import Dict, Optional
from litellm.types.utils import ProviderSpecificHeader
class ProviderSpecificHeaderUtils:
@staticmethod
def get_provider_specific_headers(
provider_specific_header: Optional[ProviderSpecificHeader],
custom_llm_provider: Optional[str],
) -> Dict:
"""
Get the provider specific headers for the given custom llm provider
Returns:
Optional[Dict]: The provider specific headers for the given custom llm provider
"""
if (
provider_specific_header is not None
and provider_specific_header.get("custom_llm_provider") == custom_llm_provider
):
return provider_specific_header.get("extra_headers", {})
return {}
@@ -1257,6 +1257,10 @@ class BaseLLMHTTPHandler:
stream: Optional[bool] = False,
kwargs: Optional[Dict[str, Any]] = None,
) -> Union[AnthropicMessagesResponse, AsyncIterator]:
from litellm.litellm_core_utils.get_provider_specific_headers import (
ProviderSpecificHeaderUtils,
)
if client is None or not isinstance(client, AsyncHTTPHandler):
async_httpx_client = get_async_httpx_client(
llm_provider=litellm.LlmProviders.ANTHROPIC
@@ -1270,10 +1274,9 @@ class BaseLLMHTTPHandler:
Optional[litellm.types.utils.ProviderSpecificHeader],
kwargs.get("provider_specific_header", None),
)
extra_headers = (
provider_specific_header.get("extra_headers", {})
if provider_specific_header
else {}
extra_headers = ProviderSpecificHeaderUtils.get_provider_specific_headers(
provider_specific_header=provider_specific_header,
custom_llm_provider=custom_llm_provider,
)
(
headers,
+8 -5
View File
@@ -61,6 +61,9 @@ from litellm.exceptions import LiteLLMUnknownProvider
from litellm.integrations.custom_logger import CustomLogger
from litellm.litellm_core_utils.audio_utils.utils import get_audio_file_for_health_check
from litellm.litellm_core_utils.dd_tracing import tracer
from litellm.litellm_core_utils.get_provider_specific_headers import (
ProviderSpecificHeaderUtils,
)
from litellm.litellm_core_utils.health_check_utils import (
_create_health_check_response,
_filter_model_params,
@@ -1107,11 +1110,11 @@ def completion( # type: ignore # noqa: PLR0915
api_key=api_key,
)
if (
provider_specific_header is not None
and provider_specific_header["custom_llm_provider"] == custom_llm_provider
):
headers.update(provider_specific_header["extra_headers"])
if provider_specific_header is not None:
headers.update(ProviderSpecificHeaderUtils.get_provider_specific_headers(
provider_specific_header=provider_specific_header,
custom_llm_provider=custom_llm_provider,
))
if model_response is not None and hasattr(model_response, "_hidden_params"):
model_response._hidden_params["custom_llm_provider"] = custom_llm_provider
+15 -9
View File
@@ -1,17 +1,23 @@
model_list:
- model_name: bedrock/converse/us.anthropic.claude-sonnet-4-20250514-v1:0
- model_name: anthropic/*
litellm_params:
model: bedrock/converse/us.anthropic.claude-sonnet-4-20250514-v1:0
model: anthropic/*
api_key: os.environ/OPENAI_API_KEY_IJ
- model_name: bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0
litellm_params:
model: bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0
- model_name: bedrock/converse/us.anthropic.claude-sonnet-4-20250514-v1:0
litellm_params:
model: bedrock/converse/us.anthropic.claude-sonnet-4-20250514-v1:0
router_settings:
fallbacks: [
{"anthropic/claude-opus-4-20250514":
{
"bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0"
}
}
]
litellm_settings:
callbacks: ["datadog_llm_observability"]
guardrails:
- guardrail_name: "bedrock-pre-guard"
litellm_params:
guardrail: bedrock # supported values: "aporia", "bedrock", "lakera"
mode: "during_call"
guardrailIdentifier: ff6ujrregl1q
guardrailVersion: "DRAFT"
@@ -273,6 +273,58 @@ async def test_anthropic_messages_litellm_router_routing_strategy():
print(f"Non-streaming response: {json.dumps(response, indent=2)}")
return response
@pytest.mark.asyncio
async def test_anthropic_messages_fallbacks():
"""
E2E test the anthropic_messages fallbacks from Anthropic API to Bedrock
"""
litellm._turn_on_debug()
router = Router(
model_list=[
{
"model_name": "anthropic/claude-opus-4-20250514",
"litellm_params": {
"model": "anthropic/claude-opus-4-20250514",
"api_key": "bad-key",
},
},
{
"model_name": "bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0",
"litellm_params": {
"model": "bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0",
},
}
],
fallbacks=[
{
"anthropic/claude-opus-4-20250514":
["bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0"]
}
]
)
# Set up test parameters
messages = [{"role": "user", "content": "Hello, can you tell me a short joke?"}]
# Call the handler
response = await router.aanthropic_messages(
messages=messages,
model="anthropic/claude-opus-4-20250514",
max_tokens=100,
metadata={
"user_id": "hello",
},
)
# Verify response
assert "id" in response
assert "content" in response
assert "model" in response
assert response["role"] == "assistant"
print(f"Non-streaming response: {json.dumps(response, indent=2)}")
return response
@pytest.mark.asyncio
async def test_anthropic_messages_litellm_router_latency_metadata_tracking():
@@ -0,0 +1,43 @@
import pytest
from litellm.litellm_core_utils.get_provider_specific_headers import (
ProviderSpecificHeaderUtils,
)
from litellm.types.utils import ProviderSpecificHeader
class TestProviderSpecificHeaderUtils:
def test_get_provider_specific_headers_matching_provider(self):
"""Test that the method returns extra_headers when custom_llm_provider matches."""
provider_specific_header: ProviderSpecificHeader = {
"custom_llm_provider": "openai",
"extra_headers": {"Authorization": "Bearer token123", "Custom-Header": "value"}
}
custom_llm_provider = "openai"
result = ProviderSpecificHeaderUtils.get_provider_specific_headers(
provider_specific_header, custom_llm_provider
)
expected = {"Authorization": "Bearer token123", "Custom-Header": "value"}
assert result == expected
def test_get_provider_specific_headers_no_match_or_none(self):
"""Test that the method returns empty dict when provider doesn't match or is None."""
# Test case 1: Provider doesn't match
provider_specific_header: ProviderSpecificHeader = {
"custom_llm_provider": "anthropic",
"extra_headers": {"Authorization": "Bearer token123"}
}
custom_llm_provider = "openai"
result = ProviderSpecificHeaderUtils.get_provider_specific_headers(
provider_specific_header, custom_llm_provider
)
assert result == {}
# Test case 2: provider_specific_header is None
result = ProviderSpecificHeaderUtils.get_provider_specific_headers(
None, "openai"
)
assert result == {}