From bba951c5ebe20871caab9848c474585c91fa3535 Mon Sep 17 00:00:00 2001 From: mateo Date: Tue, 1 Sep 2026 19:26:19 +0000 Subject: [PATCH] fix(bedrock): drop client_metadata for ARNs that hide the model family Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../bedrock/chat/converse_transformation.py | 4 +- litellm/llms/bedrock/common_utils.py | 9 ++++ .../chat/test_converse_transformation.py | 43 +++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/litellm/llms/bedrock/chat/converse_transformation.py b/litellm/llms/bedrock/chat/converse_transformation.py index 8d1d905a129..99d45bfc94b 100644 --- a/litellm/llms/bedrock/chat/converse_transformation.py +++ b/litellm/llms/bedrock/chat/converse_transformation.py @@ -86,6 +86,7 @@ from litellm.utils import ( from ..common_utils import ( BedrockError, BedrockModelInfo, + bedrock_arn_hides_model_family, bedrock_converse_supports_parallel_tool_use_config, get_anthropic_beta_from_headers, get_bedrock_tool_name, @@ -1325,7 +1326,8 @@ class AmazonConverseConfig(BaseConfig): additional_request_params.pop("parallel_tool_calls", None) - if base_model.startswith("anthropic") and additional_request_params.pop("client_metadata", None) is not None: + drops_client_metadata: Final = base_model.startswith("anthropic") or bedrock_arn_hides_model_family(model) + if drops_client_metadata and additional_request_params.pop("client_metadata", None) is not None: litellm.verbose_logger.debug( "Bedrock Converse: dropping `client_metadata` for model=%s, Anthropic rejects it with " "'client_metadata: Extra inputs are not permitted'", diff --git a/litellm/llms/bedrock/common_utils.py b/litellm/llms/bedrock/common_utils.py index 9cbceb4880c..3b82d98ceee 100644 --- a/litellm/llms/bedrock/common_utils.py +++ b/litellm/llms/bedrock/common_utils.py @@ -720,6 +720,15 @@ def get_bedrock_base_model(model: str) -> str: return model +def bedrock_arn_hides_model_family(model: str) -> bool: + """ + True for an ARN-addressed model whose base name carries no ``provider.model`` + id, such as an application inference profile or a provisioned throughput ARN. + Callers that gate behavior on the model family cannot resolve one here. + """ + return "arn:" in model.lower() and "." not in get_bedrock_base_model(model) + + def bedrock_converse_supports_parallel_tool_use_config(model: str) -> bool: return any( (litellm.model_cost.get(candidate) or {}).get("supports_parallel_tool_use_config") is True diff --git a/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py b/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py index 3600e366b5a..423e3a5a7c3 100644 --- a/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py +++ b/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py @@ -1021,6 +1021,49 @@ def test_client_metadata_kept_for_non_anthropic_converse_request(): assert data["additionalModelRequestFields"]["client_metadata"] == {"originator": "codex_cli_rs"} +@pytest.mark.parametrize( + "model", + [ + "arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/abcdef123456", + "arn:aws:bedrock:us-east-1:123456789012:provisioned-model/abcdef123456", + ], +) +def test_client_metadata_stripped_for_arn_models_converse(model): + """An ARN hides which family serves the request, and pointing one at Claude is how + teams route codex traffic, so the field has to go there too or the 400 comes back.""" + config = AmazonConverseConfig() + + data = config._transform_request_helper( + model=model, + system_content_blocks=[], + optional_params={ + "maxTokens": 16, + "client_metadata": {"originator": "codex_cli_rs"}, + }, + messages=None, + ) + + assert "client_metadata" not in data.get("additionalModelRequestFields", {}) + + +def test_client_metadata_kept_for_arn_naming_a_non_anthropic_family(): + """An inference profile ARN that still spells out the family is resolvable, so a + non-Anthropic one keeps its passthrough.""" + config = AmazonConverseConfig() + + data = config._transform_request_helper( + model="arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.amazon.nova-pro-v1:0", + system_content_blocks=[], + optional_params={ + "maxTokens": 16, + "client_metadata": {"originator": "codex_cli_rs"}, + }, + messages=None, + ) + + assert data["additionalModelRequestFields"]["client_metadata"] == {"originator": "codex_cli_rs"} + + def test_parallel_tool_calls_config_kept_for_sonnet_5(monkeypatch): old_env = os.environ.get("LITELLM_LOCAL_MODEL_COST_MAP") old_cost = litellm.model_cost