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.
This commit is contained in:
voidborne-d 2026-04-15 07:11:44 +00:00 committed by voidborne-d
parent 72a461ba4a
commit 750da1309f
2 changed files with 57 additions and 11 deletions

View file

@ -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"

View file

@ -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"