fix(bedrock): drop client_metadata for every converse model

This commit is contained in:
mateo-berri 2026-09-02 10:29:44 -07:00
parent c05aef159f
commit 53da9bca8e
3 changed files with 15 additions and 89 deletions

View file

@ -86,7 +86,6 @@ from litellm.utils import (
from ..common_utils import (
BedrockError,
BedrockModelInfo,
bedrock_arn_hides_model_family,
bedrock_converse_supports_parallel_tool_use_config,
bedrock_model_accepts_cache_points,
get_anthropic_beta_from_headers,
@ -1335,14 +1334,7 @@ class AmazonConverseConfig(BaseConfig):
)
additional_request_params.pop("parallel_tool_calls", 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'",
model,
)
additional_request_params.pop("client_metadata", None)
# Only set the topK value in for models that support it
additional_request_params.update(self._handle_top_k_value(model, inference_params, drop_params))

View file

@ -809,15 +809,6 @@ 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

View file

@ -979,16 +979,19 @@ def test_config_blocks_do_not_leak_into_inference_config():
assert data["serviceTier"] == {"type": "priority"}
@pytest.mark.parametrize("model", ["anthropic.claude-opus-4-8", "us.anthropic.claude-opus-4-8"])
def test_client_metadata_stripped_for_anthropic_converse_request(model):
"""``client_metadata`` sent by codex must not reach Anthropic as a passthrough model field.
Converse forwards ``additionalModelRequestFields`` verbatim to the model, and Anthropic
rejects the request with "client_metadata: Extra inputs are not permitted".
"""
config = AmazonConverseConfig()
data = config._transform_request_helper(
@pytest.mark.parametrize(
"model",
[
"anthropic.claude-opus-4-8",
"us.anthropic.claude-opus-4-8",
"amazon.nova-pro-v1:0",
"us.meta.llama4-maverick-17b-instruct-v1:0",
"arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/abcdef123456",
"arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.amazon.nova-pro-v1:0",
],
)
def test_client_metadata_stripped_from_converse_request(model):
data = AmazonConverseConfig()._transform_request_helper(
model=model,
system_content_blocks=[],
optional_params={
@ -999,71 +1002,11 @@ def test_client_metadata_stripped_for_anthropic_converse_request(model):
messages=None,
)
fields = data.get("additionalModelRequestFields", {})
fields = data["additionalModelRequestFields"]
assert "client_metadata" not in fields
assert fields["anthropic_beta"] == ["computer-use-2025-01-24"]
def test_client_metadata_kept_for_non_anthropic_converse_request():
"""Only Anthropic is known to reject ``client_metadata``, so other families keep the passthrough."""
config = AmazonConverseConfig()
data = config._transform_request_helper(
model="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"}
@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