Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,14 @@ def _add_cache_control_if_applicable(
if isinstance(source, dict)
else getattr(source, "cache_control", None)
)
if cache_control and model and self.is_anthropic_claude_model(model):
if (
cache_control
and model
and (
self.is_anthropic_claude_model(model)
or self.is_bedrock_arn_model(model)
)
):
# TypedDict objects support dict operations at runtime
# Use type ignore consistent with codebase pattern (see anthropic/chat/transformation.py:432)
if isinstance(target, dict):
Expand Down Expand Up @@ -752,6 +759,18 @@ def is_anthropic_claude_model(model: str) -> bool:
model_lower = model.lower()
return "anthropic" in model_lower or "claude" in model_lower

@staticmethod
def is_bedrock_arn_model(model: str) -> bool:
"""
Check if the model string is a Bedrock ARN, such as an Application
Inference Profile (e.g. arn:aws:bedrock:us-east-1:123:application-inference-profile/id).

These ARNs contain neither "anthropic" nor "claude", so is_anthropic_claude_model
cannot identify them even though, on the /v1/messages endpoint, they point at Claude.
"""
model_lower = model.lower()
return "arn:" in model_lower and "bedrock" in model_lower
Comment thread
mateo-berri marked this conversation as resolved.
Resolved

@staticmethod
def translate_thinking_for_model(
thinking: Dict[str, Any],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1295,6 +1295,12 @@ def test_streaming_chunk_with_text_and_empty_tool_calls_returns_text_delta():
"bedrock/converse/global.anthropic.claude-opus-4-5-20251101-v1:0"
)
CACHE_CONTROL_NON_ANTHROPIC_MODEL = "gpt-4"
# Bedrock Application Inference Profile ARN: the string contains neither
# "anthropic" nor "claude", so the model can only be recognized via its ARN shape
CACHE_CONTROL_BEDROCK_ARN_MODEL = (
"bedrock/converse/arn:aws:bedrock:us-east-1:123456789012:"
"application-inference-profile/abcdef123456"
)


def test_should_add_cache_control_for_anthropic_model():
Expand Down Expand Up @@ -1411,6 +1417,67 @@ def test_cache_control_not_preserved_for_non_claude_model():
assert "cache_control" not in result[0]["content"][0]


@pytest.mark.parametrize(
"model, expected",
[
(CACHE_CONTROL_BEDROCK_ARN_MODEL, True),
(
"arn:aws-us-gov:bedrock:us-gov-west-1:123:application-inference-profile/x",
True,
),
("bedrock/amazon.titan-text-express-v1", False),
("arn:aws:sagemaker:us-east-1:123:endpoint/my-endpoint", False),
(CACHE_CONTROL_NON_ANTHROPIC_MODEL, False),
],
)
def test_is_bedrock_arn_model(model, expected):
"""is_bedrock_arn_model requires both an ARN and the bedrock service in the string."""
assert LiteLLMAnthropicMessagesAdapter.is_bedrock_arn_model(model) is expected


def test_cache_control_preserved_for_bedrock_arn_inference_profile():
"""
Regression for https://github.com/BerriAI/litellm/issues/26625

Bedrock Application Inference Profile ARNs hide the underlying Claude model
name, so cache_control must still be preserved through the /v1/messages adapter.
"""
anthropic_messages = [
AnthropicMessagesUserMessageParam(
role="user",
content=[
{
"type": "text",
"text": "This is cached content",
"cache_control": {"type": "ephemeral"},
}
],
)
]

adapter = LiteLLMAnthropicMessagesAdapter()
result = adapter.translate_anthropic_messages_to_openai(
messages=anthropic_messages, model=CACHE_CONTROL_BEDROCK_ARN_MODEL
)

assert len(result) == 1
assert result[0]["content"][0]["cache_control"] == {"type": "ephemeral"}


def test_cache_control_fix_does_not_broaden_claude_detection():
"""
The cache_control fix is scoped to _add_cache_control_if_applicable; it must not
make is_anthropic_claude_model treat ARN profiles as Claude, which would route
thinking params through unmodified and break non-Claude Bedrock profiles.
"""
assert (
LiteLLMAnthropicMessagesAdapter.is_anthropic_claude_model(
CACHE_CONTROL_BEDROCK_ARN_MODEL
)
is False
)


def test_cache_control_preserved_in_image_content_for_claude():
"""Cache control should be preserved in image content for Claude models."""
anthropic_messages = [
Expand Down
Loading