From 0e94893cd9679ea28a4ae607cd7b01b8c9c8daf2 Mon Sep 17 00:00:00 2001 From: shrey kharbanda Date: Thu, 24 Sep 2026 02:00:03 +0000 Subject: [PATCH] feat(sail): route Anthropic Messages through Sail's /v1/messages --- litellm/llms/openai_like/providers.json | 2 +- .../provider_endpoints_support_backup.json | 2 +- provider_endpoints_support.json | 2 +- .../llms/openai_like/test_sail_provider.py | 49 +++++++++++++++++++ 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/litellm/llms/openai_like/providers.json b/litellm/llms/openai_like/providers.json index 194636da7ff..3d496047334 100644 --- a/litellm/llms/openai_like/providers.json +++ b/litellm/llms/openai_like/providers.json @@ -205,7 +205,7 @@ "base_url": "https://api.sailresearch.com/v1", "api_key_env": "SAIL_API_KEY", "api_base_env": "SAIL_API_BASE", - "supported_endpoints": ["/v1/chat/completions", "/v1/responses"], + "supported_endpoints": ["/v1/chat/completions", "/v1/responses", "/v1/messages"], "param_mappings": { "max_tokens": "max_completion_tokens" } diff --git a/litellm/provider_endpoints_support_backup.json b/litellm/provider_endpoints_support_backup.json index eb3b380caca..1e4765e7195 100644 --- a/litellm/provider_endpoints_support_backup.json +++ b/litellm/provider_endpoints_support_backup.json @@ -2034,7 +2034,7 @@ "url": "https://docs.litellm.ai/docs/providers/sail", "endpoints": { "chat_completions": true, - "messages": false, + "messages": true, "responses": true, "embeddings": false, "image_generations": false, diff --git a/provider_endpoints_support.json b/provider_endpoints_support.json index 49902c17736..f2cf7477ed8 100644 --- a/provider_endpoints_support.json +++ b/provider_endpoints_support.json @@ -2268,7 +2268,7 @@ "url": "https://docs.litellm.ai/docs/providers/sail", "endpoints": { "chat_completions": true, - "messages": false, + "messages": true, "responses": true, "embeddings": false, "image_generations": false, diff --git a/tests/test_litellm/llms/openai_like/test_sail_provider.py b/tests/test_litellm/llms/openai_like/test_sail_provider.py index 724342f9e27..e7a3c2005ea 100644 --- a/tests/test_litellm/llms/openai_like/test_sail_provider.py +++ b/tests/test_litellm/llms/openai_like/test_sail_provider.py @@ -15,6 +15,7 @@ from litellm.types.utils import PromptTokensDetailsWrapper, Usage SAIL_BASE_URL = "https://api.sailresearch.com/v1" SAIL_CHAT_COMPLETIONS = f"{SAIL_BASE_URL}/chat/completions" SAIL_RESPONSES = f"{SAIL_BASE_URL}/responses" +SAIL_MESSAGES = f"{SAIL_BASE_URL}/messages" MODEL = "sail/zai-org/GLM-5.3" @@ -76,6 +77,19 @@ def _responses_payload() -> dict: } +def _messages_payload() -> dict: + return { + "id": "msg_sail", + "type": "message", + "role": "assistant", + "model": "zai-org/GLM-5.3-Flash", + "content": [{"type": "text", "text": "sail response"}], + "stop_reason": "end_turn", + "stop_sequence": None, + "usage": {"input_tokens": 2, "output_tokens": 2}, + } + + @pytest.fixture(autouse=True) def _sail_env(monkeypatch: pytest.MonkeyPatch): monkeypatch.setattr(litellm, "disable_aiohttp_transport", True) @@ -236,6 +250,41 @@ class TestSailRequestShape: assert not route.called + @pytest.mark.asyncio + @pytest.mark.respx() + async def test_sail_anthropic_messages_posts_to_messages_endpoint(self, respx_mock: respx.Router): + respx_mock.post(SAIL_MESSAGES).respond(json=_messages_payload()) + + await litellm.anthropic_messages( + model="sail/zai-org/GLM-5.3-Flash", + messages=[{"role": "user", "content": "hi"}], + max_tokens=50, + ) + + assert len(respx_mock.calls) == 1 + request = respx_mock.calls[0].request + assert request.url == SAIL_MESSAGES + assert request.headers["Authorization"] == "Bearer sk-sail-test" + body = json.loads(request.content) + assert body["model"] == "zai-org/GLM-5.3-Flash" + assert body["max_tokens"] == 50 + + @pytest.mark.asyncio + @pytest.mark.respx() + async def test_sail_anthropic_messages_honors_api_base_env( + self, respx_mock: respx.Router, monkeypatch: pytest.MonkeyPatch + ): + monkeypatch.setenv("SAIL_API_BASE", "https://sail.internal.example/v2") + route = respx_mock.post("https://sail.internal.example/v2/v1/messages").respond(json=_messages_payload()) + + await litellm.anthropic_messages( + model="sail/zai-org/GLM-5.3-Flash", + messages=[{"role": "user", "content": "hi"}], + max_tokens=50, + ) + + assert route.called + class TestSailCostTracking: def test_cached_tokens_billed_at_sail_cache_read_rate(self, monkeypatch: pytest.MonkeyPatch):