fix(anthropic): dedup trailing /v1 in api_base before appending /v1/messages

AnthropicMessagesConfig.get_complete_url appended /v1/messages to any
api_base that didn't already end in it, so a base ending in /v1 (e.g. an
Anthropic-compatible gateway root) produced /v1/v1/messages and a 404.
The openai_like and deepseek messages configs already normalize this;
mirror their rstrip/removesuffix handling and add the matching
parametrized tests.

Fixes #36956
This commit is contained in:
lokesh75-kank 2026-08-14 10:11:23 -07:00
parent b77923fc87
commit d0a63b472a
2 changed files with 48 additions and 3 deletions

View file

@ -225,9 +225,13 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig):
stream: bool | None = None,
) -> str:
api_base = AnthropicModelInfo.get_api_base(api_base) or "https://api.anthropic.com"
if not api_base.endswith("/v1/messages"):
api_base = f"{api_base}/v1/messages"
return api_base
# Normalize like OpenAILikeAnthropicMessagesConfig: an api_base that
# already ends in /v1 must not become /v1/v1/messages.
base = api_base.rstrip("/")
if base.endswith("/v1/messages"):
return base
base = base.removesuffix("/v1")
return f"{base}/v1/messages"
def validate_anthropic_messages_environment(
self,

View file

@ -0,0 +1,41 @@
import pytest
from litellm.llms.anthropic.experimental_pass_through.messages.transformation import (
AnthropicMessagesConfig,
)
@pytest.fixture
def config() -> AnthropicMessagesConfig:
return AnthropicMessagesConfig()
@pytest.mark.parametrize(
"api_base, expected",
[
# default when no api_base is configured
(None, "https://api.anthropic.com/v1/messages"),
# bases without a version suffix
("https://host", "https://host/v1/messages"),
("https://host/", "https://host/v1/messages"),
# bases that already carry /v1 must not become /v1/v1/messages
("https://host/v1", "https://host/v1/messages"),
("https://host/v1/", "https://host/v1/messages"),
("https://api.groq.com/openai/v1", "https://api.groq.com/openai/v1/messages"),
# full endpoint passes through untouched
("https://host/v1/messages", "https://host/v1/messages"),
# provider-prefixed path segments are preserved
("https://gateway.example.com/anthropic", "https://gateway.example.com/anthropic/v1/messages"),
("https://gateway.example.com/anthropic/v1", "https://gateway.example.com/anthropic/v1/messages"),
],
)
def test_get_complete_url_deduplicates_trailing_v1(config, api_base, expected, monkeypatch):
monkeypatch.delenv("ANTHROPIC_API_BASE", raising=False)
url = config.get_complete_url(
api_base=api_base,
api_key="sk-test",
model="claude-sonnet-4-5",
optional_params={},
litellm_params={},
)
assert url == expected