diff --git a/litellm/llms/bedrock/chat/converse_transformation.py b/litellm/llms/bedrock/chat/converse_transformation.py index d210f294c64..019e832439b 100644 --- a/litellm/llms/bedrock/chat/converse_transformation.py +++ b/litellm/llms/bedrock/chat/converse_transformation.py @@ -888,6 +888,16 @@ class AmazonConverseConfig(BaseConfig): is_thinking_enabled=is_thinking_enabled, ) if param == "max_tokens" or param == "max_completion_tokens": + try: + model_info = litellm.get_model_info(model=model) + model_max = model_info.get("max_output_tokens") + if model_max is not None and isinstance(model_max, int) and isinstance(value, int) and value > model_max: + verbose_logger.debug( + f"Capping max_tokens from {value} to model limit {model_max} for {model}" + ) + value = model_max + except Exception: + pass optional_params["maxTokens"] = value if param == "stream": optional_params["stream"] = value diff --git a/tests/llm_translation/test_bedrock_completion.py b/tests/llm_translation/test_bedrock_completion.py index 40ef2c32831..95dbe138a10 100644 --- a/tests/llm_translation/test_bedrock_completion.py +++ b/tests/llm_translation/test_bedrock_completion.py @@ -4235,3 +4235,46 @@ def test_bedrock_nova_grounding_request_transformation(): assert system_tool_found, "systemTool with nova_grounding should be present" print("✓ web_search_options correctly transformed to systemTool") + + +def test_bedrock_converse_max_tokens_capped_to_model_limit(): + """ + Nova Pro has a max_output_tokens of 10000. If a caller sends max_tokens=16000, + the Converse transformation should silently cap it to 10000 before the request + reaches Bedrock, preventing a 400 error. + """ + import os + os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" + litellm.model_cost = litellm.get_model_cost_map(url="") + os.environ.pop("LITELLM_LOCAL_MODEL_COST_MAP", None) + + from litellm.llms.bedrock.chat.converse_transformation import AmazonConverseConfig + + config = AmazonConverseConfig() + + # Over the limit — should be capped + result = config.map_openai_params( + non_default_params={"max_tokens": 16000}, + optional_params={}, + model="us.amazon.nova-pro-v1:0", + drop_params=False, + ) + assert result["maxTokens"] == 10000, f"Expected 10000, got {result['maxTokens']}" + + # Under the limit — should be unchanged + result2 = config.map_openai_params( + non_default_params={"max_tokens": 5000}, + optional_params={}, + model="us.amazon.nova-pro-v1:0", + drop_params=False, + ) + assert result2["maxTokens"] == 5000, f"Expected 5000, got {result2['maxTokens']}" + + # Model with no known limit — should be passed through unchanged + result3 = config.map_openai_params( + non_default_params={"max_tokens": 16000}, + optional_params={}, + model="anthropic.claude-3-sonnet-20240229-v1:0", + drop_params=False, + ) + assert result3["maxTokens"] == 16000, f"Expected 16000, got {result3['maxTokens']}"