diff --git a/litellm/constants.py b/litellm/constants.py index e21751c7fd1..86eee9deddf 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -1079,6 +1079,7 @@ openai_compatible_endpoints: Final[list[str]] = [ "https://api.meta.ai/v1", "https://api.sailresearch.com/v1", "https://api.cognition.ai/v1", + "https://api.mixroute.ai/v1", "https://inference.coralbricks.ai/v1", "https://api.cortecs.ai/v1", "https://api.scx.ai/v1", @@ -1163,6 +1164,7 @@ openai_compatible_providers: Final[list[str]] = [ "darkbloom", "meta", # Meta Model API (Muse Spark) - JSON-configured provider "cognition", + "mixroute", "coralbricks", "cortecs", "scx-ai", diff --git a/litellm/llms/openai_like/providers.json b/litellm/llms/openai_like/providers.json index a71a9c3bb9b..0bef53376fc 100644 --- a/litellm/llms/openai_like/providers.json +++ b/litellm/llms/openai_like/providers.json @@ -302,5 +302,16 @@ "preserve_cache_control": true }, "supported_endpoints": ["/v1/chat/completions"] + }, + "mixroute": { + "base_url": "https://api.mixroute.ai/v1", + "api_key_env": "MIXROUTE_API_KEY", + "api_base_env": "MIXROUTE_API_BASE", + "supported_endpoints": ["/v1/chat/completions", "/v1/embeddings"], + "model_supported_endpoints": { + "claude-opus-5-5": ["/v1/messages"], + "claude-sonnet-5-5": ["/v1/messages"], + "gpt-6.1-sol": ["/v1/responses"] + } } } diff --git a/litellm/provider_endpoints_support_backup.json b/litellm/provider_endpoints_support_backup.json index 700a35edb06..6be206401c3 100644 --- a/litellm/provider_endpoints_support_backup.json +++ b/litellm/provider_endpoints_support_backup.json @@ -905,6 +905,32 @@ "generateContent": true } }, + "mixroute": { + "display_name": "MixRoute (`mixroute`)", + "url": "https://mixroute.ai/docs/api-reference/endpoint/chat-openai", + "endpoints": { + "chat_completions": true, + "responses": true, + "text_completion": false, + "embeddings": true, + "image_generations": false, + "image_edits": false, + "audio_transcriptions": false, + "audio_speech": false, + "rerank": false, + "ocr": false, + "search": false, + "moderations": false, + "realtime": false, + "video_generations": false, + "batches": false, + "messages": true, + "interactions": false, + "systemone": false, + "a2a": false, + "generateContent": false + } + }, "cognition": { "display_name": "Cognition (`cognition`)", "url": "https://docs.litellm.ai/docs/providers/cognition", diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 24f4acaf5df..974981a1460 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -4677,6 +4677,7 @@ class LlmProviders(str, Enum): LIBERTAI = "libertai" PINSTRIPES = "pinstripes" COGNITION = "cognition" + MIXROUTE = "mixroute" CORALBRICKS = "coralbricks" CORTECS = "cortecs" SCX_AI = "scx-ai" diff --git a/provider_endpoints_support.json b/provider_endpoints_support.json index 700a35edb06..6be206401c3 100644 --- a/provider_endpoints_support.json +++ b/provider_endpoints_support.json @@ -905,6 +905,32 @@ "generateContent": true } }, + "mixroute": { + "display_name": "MixRoute (`mixroute`)", + "url": "https://mixroute.ai/docs/api-reference/endpoint/chat-openai", + "endpoints": { + "chat_completions": true, + "responses": true, + "text_completion": false, + "embeddings": true, + "image_generations": false, + "image_edits": false, + "audio_transcriptions": false, + "audio_speech": false, + "rerank": false, + "ocr": false, + "search": false, + "moderations": false, + "realtime": false, + "video_generations": false, + "batches": false, + "messages": true, + "interactions": false, + "systemone": false, + "a2a": false, + "generateContent": false + } + }, "cognition": { "display_name": "Cognition (`cognition`)", "url": "https://docs.litellm.ai/docs/providers/cognition", diff --git a/tests/unit/llms/openai_like/test_mixroute_provider.py b/tests/unit/llms/openai_like/test_mixroute_provider.py new file mode 100644 index 00000000000..dd91c712186 --- /dev/null +++ b/tests/unit/llms/openai_like/test_mixroute_provider.py @@ -0,0 +1,51 @@ +from typing import Final + +import pytest + +from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider +from litellm.llms.openai_like.dynamic_config import create_config_class +from litellm.llms.openai_like.json_loader import JSONProviderRegistry + + +def test_mixroute_prefix_preserves_model_and_resolves_credentials(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("MIXROUTE_API_KEY", "mixroute-test-key") + monkeypatch.delenv("MIXROUTE_API_BASE", raising=False) + config: Final = JSONProviderRegistry.get("mixroute") + assert config is not None + model, provider, key, base = get_llm_provider(model="mixroute/example-model") + assert (model, provider, key, base) == ("example-model", "mixroute", "mixroute-test-key", config.base_url) + + +def test_mixroute_endpoint_detection_preserves_explicit_key(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("MIXROUTE_API_KEY", "environment-key") + config: Final = JSONProviderRegistry.get("mixroute") + assert config is not None + model, provider, key, base = get_llm_provider( + model="example-model", api_base=config.base_url, api_key="caller-key" + ) + assert (model, provider, key, base) == ("example-model", "mixroute", "caller-key", config.base_url) + + +def test_mixroute_base_override_and_chat_url(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("MIXROUTE_API_KEY", "environment-key") + monkeypatch.setenv("MIXROUTE_API_BASE", "https://mixroute.example.test/v1") + config: Final = JSONProviderRegistry.get("mixroute") + assert config is not None + handler: Final = create_config_class(config)() + base, key = handler.get_openai_compatible_provider_info(None, None) + assert (base, key) == ("https://mixroute.example.test/v1", "environment-key") + assert handler.get_complete_url(base, key, "example-model", {}, {}) == ( + "https://mixroute.example.test/v1/chat/completions" + ) + assert handler.get_openai_compatible_provider_info("https://override.example.test/v1", "explicit-key") == ( + "https://override.example.test/v1", "explicit-key" + ) + + +def test_mixroute_native_endpoints_do_not_apply_to_other_models() -> None: + assert JSONProviderRegistry.serves_endpoint("mixroute", "claude-opus-5-5", "/v1/messages") + assert JSONProviderRegistry.serves_endpoint("mixroute", "mixroute/claude-sonnet-5-5", "/v1/messages") + assert JSONProviderRegistry.serves_endpoint("mixroute", "gpt-6.1-sol", "/v1/responses") + assert not JSONProviderRegistry.serves_endpoint("mixroute", "unlisted-model", "/v1/messages") + assert not JSONProviderRegistry.serves_endpoint("mixroute", "unlisted-model", "/v1/responses") + assert JSONProviderRegistry.supports_embeddings("mixroute")