From 1028789b4b95188712806309eef3e7f3770a8c10 Mon Sep 17 00:00:00 2001 From: Hamjaster <87142882+Hamjaster@users.noreply.github.com> Date: Tue, 4 Aug 2026 12:19:41 +0500 Subject: [PATCH] fix(anthropic): resolve capabilities for mantle-routed bedrock ids bedrock/mantle/anthropic.claude-opus-5 and bedrock/us.anthropic.claude-opus-5 are the same underlying model, but they did not resolve the same capabilities. The mantle route segment was missing from the prefixes the model-map candidate walk strips, so the lookup never reached the anthropic.claude-opus-5 entry: model id effort_param mantle/anthropic.claude-opus-5 False us.anthropic.claude-opus-5 True The Bedrock invoke path strips output_config when neither supports_output_config nor the effort check passes, so a mantle-routed Opus 5 lost its effort and went out with the legacy thinking.type=enabled shape, which Bedrock rejects. Only reasoning_effort="none" got through, because that drops the thinking block instead of fixing its shape. Add bedrock/mantle/ and mantle/ to the candidate prefixes. This only affects capability lookups; the wire model id still comes from strip_bedrock_routing_prefix, so mantle request routing is untouched. --- litellm/llms/anthropic/common_utils.py | 2 ++ ...ations_anthropic_claude3_transformation.py | 28 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/litellm/llms/anthropic/common_utils.py b/litellm/llms/anthropic/common_utils.py index 73d897c8d28..e84d7c25382 100644 --- a/litellm/llms/anthropic/common_utils.py +++ b/litellm/llms/anthropic/common_utils.py @@ -309,7 +309,9 @@ class AnthropicModelInfo(BaseLLMModelInfo): prefixes = ( "bedrock/converse/", "bedrock/invoke/", + "bedrock/mantle/", "bedrock/", + "mantle/", "vertex_ai/", ) deprefixed = tuple(model[len(p) :] for p in prefixes if model.startswith(p)) diff --git a/tests/test_litellm/llms/bedrock/chat/invoke_transformations/test_bedrock_chat_invoke_transformations_anthropic_claude3_transformation.py b/tests/test_litellm/llms/bedrock/chat/invoke_transformations/test_bedrock_chat_invoke_transformations_anthropic_claude3_transformation.py index 4c4c0e17a38..9a11683f364 100644 --- a/tests/test_litellm/llms/bedrock/chat/invoke_transformations/test_bedrock_chat_invoke_transformations_anthropic_claude3_transformation.py +++ b/tests/test_litellm/llms/bedrock/chat/invoke_transformations/test_bedrock_chat_invoke_transformations_anthropic_claude3_transformation.py @@ -430,6 +430,34 @@ def test_output_config_forwarded_for_bedrock_chat_invoke_request(): assert result["max_tokens"] == 100 +def test_output_config_forwarded_for_mantle_routed_model(): + """A mantle-routed id must resolve the same capabilities as its us.* twin. + + bedrock/mantle/anthropic.claude-opus-5 and bedrock/us.anthropic.claude-opus-5 + are the same underlying model, but the mantle route segment was not among the + prefixes capability lookups strip, so supports_output_config and the effort + check both came back False and this path stripped output_config. The request + then went out with the legacy thinking.type=enabled shape, which Bedrock + rejects. + """ + config = AmazonAnthropicClaudeConfig() + + optional_params = { + "max_tokens": 100, + "output_config": {"effort": "high"}, + } + + result = config.transform_request( + model="mantle/anthropic.claude-opus-5", + messages=[{"role": "user", "content": "test"}], + optional_params=optional_params, + litellm_params={}, + headers={}, + ) + + assert result.get("output_config") == {"effort": "high"} + + def test_output_config_format_converted_for_bedrock_chat_invoke_request(): """Bedrock Invoke chat path consumes ``output_config.format`` before forwarding.""" config = AmazonAnthropicClaudeConfig()