diff --git a/litellm/llms/vertex_ai/vertex_llm_base.py b/litellm/llms/vertex_ai/vertex_llm_base.py index 1d5cbe09ba0..5c66db39e7c 100644 --- a/litellm/llms/vertex_ai/vertex_llm_base.py +++ b/litellm/llms/vertex_ai/vertex_llm_base.py @@ -667,7 +667,9 @@ class VertexBase: # For Gemini (Google AI Studio), construct the full path like other providers if model is None: raise ValueError("Model parameter is required for Gemini custom API base URLs") - url = f"{api_base}/models/{model}:{endpoint}" + # A trailing slash on api_base is a common copy-paste from the docs; without + # stripping it the joined path carries `//`, which Google answers with a 404. + url = f"{api_base.rstrip('/')}/models/{model}:{endpoint}" if gemini_api_key is None: raise ValueError( "Missing Gemini API key. Set the GEMINI_API_KEY or GOOGLE_API_KEY environment variable." diff --git a/tests/unit/llms/vertex_ai/test_vertex_llm_base.py b/tests/unit/llms/vertex_ai/test_vertex_llm_base.py index b5a24ab02d9..f8233e31537 100644 --- a/tests/unit/llms/vertex_ai/test_vertex_llm_base.py +++ b/tests/unit/llms/vertex_ai/test_vertex_llm_base.py @@ -875,6 +875,47 @@ class TestVertexBase: result_url == expected_url ), f"Expected {expected_url}, got {result_url} for model {model}" + def test_check_custom_proxy_gemini_url_strips_trailing_slash(self): + """A trailing slash on api_base must not double up in the joined path. + + Copy-pasting the api_base from Google's docs leaves the slash in place, + and the resulting `//` is answered with a 404. #43828 + """ + vertex_base = VertexBase() + + _, url = vertex_base._check_custom_proxy( + api_base="https://generativelanguage.googleapis.com/v1beta/openai/", + custom_llm_provider="gemini", + gemini_api_key="test-api-key", + endpoint="generateContent", + stream=False, + auth_header=None, + url="https://generativelanguage.googleapis.com/v1beta/models/gemini-3.8-flash:generateContent", + model="gemini-3.8-flash", + ) + + assert url == ( + "https://generativelanguage.googleapis.com/v1beta/openai/models/gemini-3.8-flash:generateContent" + ) + assert "//models" not in url + + def test_check_custom_proxy_gemini_url_without_trailing_slash_is_unchanged(self): + """The slash-free form keeps producing the same URL it always did.""" + vertex_base = VertexBase() + + _, url = vertex_base._check_custom_proxy( + api_base="https://proxy.example.com/v1beta", + custom_llm_provider="gemini", + gemini_api_key="test-api-key", + endpoint="generateContent", + stream=False, + auth_header=None, + url="https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro:generateContent", + model="gemini-2.5-pro", + ) + + assert url == "https://proxy.example.com/v1beta/models/gemini-2.5-pro:generateContent" + def test_check_custom_proxy_streaming_parameter(self): """Test that streaming parameter correctly adds ?alt=sse to URLs""" vertex_base = VertexBase()