From 750da1309f737f4cfbb84c63504d97c8bc4d2b9a Mon Sep 17 00:00:00 2001 From: voidborne-d Date: Wed, 15 Apr 2026 07:11:44 +0000 Subject: [PATCH] fix(vertex): always append endpoint suffix to custom api_base for Anthropic models Fixes #25748. When a custom `api_base` is provided in `litellm_params` for Vertex AI Anthropic models, the `if api_base is None:` guard introduced in PR #21658 (commit cdb4917185) causes `get_complete_vertex_url()` to be skipped entirely. This means the required endpoint suffix (`:streamRawPredict` for streaming, `:rawPredict` for non-streaming) is never appended, resulting in a 404 from the Vertex AI endpoint. The fix removes the guard so `get_complete_vertex_url()` is always called. It already handles custom `api_base` correctly via `_check_custom_proxy()`, which constructs `{api_base}:{endpoint}`. Includes a regression test that verifies `get_complete_vertex_url()` is invoked even when `api_base` is provided. --- .../transformation.py | 25 ++++++----- ...artner_models_anthropic_messages_config.py | 43 +++++++++++++++++++ 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/experimental_pass_through/transformation.py b/litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/experimental_pass_through/transformation.py index 5c3bbf61ee2..cddced62ae4 100644 --- a/litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/experimental_pass_through/transformation.py +++ b/litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/experimental_pass_through/transformation.py @@ -51,17 +51,20 @@ class VertexAIPartnerModelsAnthropicMessagesConfig(AnthropicMessagesConfig, Vert # Authorization already in headers, but we still need project_id project_id = vertex_ai_project - # Always calculate api_base if not provided, regardless of Authorization header - if api_base is None: - api_base = self.get_complete_vertex_url( - custom_api_base=api_base, - vertex_location=vertex_ai_location, - vertex_project=vertex_ai_project, - project_id=project_id or "", - partner=VertexPartnerProvider.claude, - stream=optional_params.get("stream", False), - model=model, - ) + # Always calculate api_base regardless of whether one was provided. + # get_complete_vertex_url handles custom api_base by appending the + # required endpoint suffix (:streamRawPredict / :rawPredict) via + # _check_custom_proxy. Without this, custom api_base values are + # used as-is and the Vertex AI endpoint returns 404. + api_base = self.get_complete_vertex_url( + custom_api_base=api_base, + vertex_location=vertex_ai_location, + vertex_project=vertex_ai_project, + project_id=project_id or "", + partner=VertexPartnerProvider.claude, + stream=optional_params.get("stream", False), + model=model, + ) headers["content-type"] = "application/json" diff --git a/tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/test_vertex_ai_partner_models_anthropic_messages_config.py b/tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/test_vertex_ai_partner_models_anthropic_messages_config.py index 391daa24f47..a58fe3bb5c2 100644 --- a/tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/test_vertex_ai_partner_models_anthropic_messages_config.py +++ b/tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/test_vertex_ai_partner_models_anthropic_messages_config.py @@ -293,3 +293,46 @@ def test_transform_anthropic_messages_request_removes_scope_from_cache_control() # scope removed from message content assert "scope" not in result["messages"][0]["content"][0]["cache_control"] assert result["messages"][0]["content"][0]["cache_control"]["type"] == "ephemeral" + + +def test_validate_environment_custom_api_base_calls_get_complete_vertex_url(): + """Regression test for #25748: custom api_base must still go through + get_complete_vertex_url so that the required endpoint suffix + (:streamRawPredict / :rawPredict) is appended.""" + config = VertexAIPartnerModelsAnthropicMessagesConfig() + headers = {} + litellm_params = { + "vertex_ai_project": "test-project", + "vertex_ai_location": "us-central1", + "vertex_credentials": "{}", + } + custom_base = ( + "https://aiplatform.us.rep.googleapis.com/v1/projects/my-project" + "/locations/us/publishers/anthropic/models/claude-sonnet-4-5@20250929" + ) + optional_params = {"stream": True} + + with patch.object( + config, "_ensure_access_token", return_value=("token", "test-project") + ), patch.object( + config, + "get_complete_vertex_url", + return_value=f"{custom_base}:streamRawPredict", + ) as mock_get_url: + updated_headers, api_base = config.validate_anthropic_messages_environment( + headers=headers, + model="claude-sonnet-4-5@20250929", + messages=[], + optional_params=optional_params, + litellm_params=litellm_params, + api_base=custom_base, + ) + + # get_complete_vertex_url MUST be called even with a custom api_base + assert mock_get_url.called, ( + "get_complete_vertex_url should be called when api_base is provided " + "so that the endpoint suffix (:streamRawPredict) is appended" + ) + assert mock_get_url.call_args.kwargs["custom_api_base"] == custom_base + # The returned api_base should have the suffix + assert api_base == f"{custom_base}:streamRawPredict"