From c59b321621421b7f4759b93073dac3f559cdad81 Mon Sep 17 00:00:00 2001 From: curiousbox Date: Tue, 8 Sep 2026 22:13:56 -0700 Subject: [PATCH] feat(tium): add Tium as a JSON-configured OpenAI-compatible provider Tium serves open-weight models (GLM, DeepSeek, Kimi) over an OpenAI-compatible API from a gateway operated in Germany. Callers currently have to route through openai/ with a custom api_base Registered in providers.json with TIUM_API_KEY, the TIUM_API_BASE override, the max_completion_tokens to max_tokens mapping, and chat completions as the only supported endpoint, since POST /v1/responses answers 404 on this host. The slug is also added to openai_compatible_providers and LlmProviders, and the base URL to openai_compatible_endpoints for api_base autodetection The openai_compatible_providers entry is load-bearing. JSONProviderRegistry does not register into that list, and a provider missing from it takes the non-OpenAI-compatible branch of add_provider_specific_params_to_optional_params, so provider-specific body params arrive as top-level kwargs instead of nesting in extra_body, and tools are stripped before the request is sent. See #26443, still open against three JSON-only providers Five chat models go into the cost map, with each capability flag measured rather than defaulted. All five support function calling and prompt caching, so each carries a cache read rate. The two DeepSeek models refuse response_format with a JSON schema; image input works on glm-5.3-flash and kimi-k3. max_input_tokens and max_output_tokens are the host's policy ceilings. No constraints block is needed, since all five accept temperature from 0.0 to 2.0 Costs are USD per token, derived from the published subscription rate of $2.83 per million weighted tokens times each model's multiplier and weights. Tium bills weighted tokens rather than per-token, and prepaid packs cost more; the docs page states both The provider is registered in the Add Model form and the Admin UI provider list, which test_every_backend_provider_is_listed_in_add_model_or_frozen_as_unlisted requires of any new entry in LlmProviders Tests drive real completion calls through respx, asserting the request URL, the bearer auth header, the message body, that tools and tool_choice reach the wire and tool calls come back, and that max_completion_tokens is sent as max_tokens. They use the local_model_cost_map fixture, since the network-fetched cost map lags this branch until merge and capability lookups would otherwise read stale data --- litellm/constants.py | 2 + litellm/llms/openai_like/providers.json | 11 + ...odel_prices_and_context_window_backup.json | 85 +++++ .../provider_endpoints_support_backup.json | 17 + .../provider_create_fields.json | 28 ++ litellm/types/utils.py | 1 + model_prices_and_context_window.json | 85 +++++ provider_endpoints_support.json | 17 + .../llms/openai_like/test_tium_provider.py | 321 ++++++++++++++++++ .../public/assets/logos/tium.svg | 4 + .../components/provider_info_helpers.test.tsx | 15 + .../src/components/provider_info_helpers.tsx | 5 + 12 files changed, 591 insertions(+) create mode 100644 tests/test_litellm/llms/openai_like/test_tium_provider.py create mode 100644 ui/litellm-dashboard/public/assets/logos/tium.svg diff --git a/litellm/constants.py b/litellm/constants.py index 6f2384c8c6c..9154a6c7437 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -879,6 +879,7 @@ openai_compatible_endpoints: Final[list] = [ "https://api.cognition.ai/v1", "https://api.scx.ai/v1", "https://gigachat.devices.sberbank.ru/api/v1", + "https://api.tium.ai/v1", ] @@ -950,6 +951,7 @@ openai_compatible_providers: Final[list] = [ "meta", # Meta Model API (Muse Spark) - JSON-configured provider "cognition", "scx-ai", + "tium", ] openai_text_completion_compatible_providers: Final[list] = [ # providers that support `/v1/completions` "together_ai", diff --git a/litellm/llms/openai_like/providers.json b/litellm/llms/openai_like/providers.json index a458a209ea9..931f44b4a4f 100644 --- a/litellm/llms/openai_like/providers.json +++ b/litellm/llms/openai_like/providers.json @@ -200,5 +200,16 @@ "temperature_max": 1.99 }, "supported_endpoints": ["/v1/chat/completions"] + }, + "tium": { + "base_url": "https://api.tium.ai/v1", + "api_key_env": "TIUM_API_KEY", + "api_base_env": "TIUM_API_BASE", + "param_mappings": { + "max_completion_tokens": "max_tokens" + }, + "supported_endpoints": [ + "/v1/chat/completions" + ] } } diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 54ebdc85be9..2c6a0d96273 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -42785,6 +42785,91 @@ "output_cost_per_token": 2.8e-05, "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, + "tium/glm-5.3-flash": { + "cache_read_input_token_cost": 3.03e-08, + "input_cost_per_token": 1.52e-07, + "litellm_provider": "tium", + "max_input_tokens": 128000, + "max_output_tokens": 32768, + "max_tokens": 32768, + "mode": "chat", + "output_cost_per_token": 5.05e-07, + "source": "https://tium.ai/pricing", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true + }, + "tium/deepseek-v4-flash": { + "cache_read_input_token_cost": 2.83e-08, + "input_cost_per_token": 8.89e-07, + "litellm_provider": "tium", + "max_input_tokens": 128000, + "max_output_tokens": 32768, + "max_tokens": 32768, + "mode": "chat", + "output_cost_per_token": 2.668e-06, + "source": "https://tium.ai/pricing", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": false, + "supports_tool_choice": true, + "supports_vision": false + }, + "tium/deepseek-v4-pro": { + "cache_read_input_token_cost": 8.89e-08, + "input_cost_per_token": 2.668e-06, + "litellm_provider": "tium", + "max_input_tokens": 128000, + "max_output_tokens": 32768, + "max_tokens": 32768, + "mode": "chat", + "output_cost_per_token": 8.005e-06, + "source": "https://tium.ai/pricing", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": false, + "supports_tool_choice": true, + "supports_vision": false + }, + "tium/glm-5.3": { + "cache_read_input_token_cost": 5.26e-07, + "input_cost_per_token": 2.83e-06, + "litellm_provider": "tium", + "max_input_tokens": 128000, + "max_output_tokens": 32768, + "max_tokens": 32768, + "mode": "chat", + "output_cost_per_token": 8.895e-06, + "source": "https://tium.ai/pricing", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": false + }, + "tium/kimi-k3": { + "cache_read_input_token_cost": 6.06e-07, + "input_cost_per_token": 6.065e-06, + "litellm_provider": "tium", + "max_input_tokens": 128000, + "max_output_tokens": 32768, + "max_tokens": 32768, + "mode": "chat", + "output_cost_per_token": 3.0323e-05, + "source": "https://tium.ai/pricing", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true + }, "together-ai-21.1b-41b": { "input_cost_per_token": 8e-07, "litellm_provider": "together_ai", diff --git a/litellm/provider_endpoints_support_backup.json b/litellm/provider_endpoints_support_backup.json index dbeaccdda2d..650a51c642c 100644 --- a/litellm/provider_endpoints_support_backup.json +++ b/litellm/provider_endpoints_support_backup.json @@ -2152,6 +2152,23 @@ "interactions": true } }, + "tium": { + "display_name": "Tium (`tium`)", + "url": "https://docs.litellm.ai/docs/providers/tium", + "endpoints": { + "chat_completions": true, + "messages": false, + "responses": false, + "embeddings": false, + "image_generations": false, + "audio_transcriptions": false, + "audio_speech": false, + "moderations": false, + "batches": false, + "rerank": false, + "a2a": false + } + }, "together_ai": { "display_name": "Together AI (`together_ai`)", "url": "https://docs.litellm.ai/docs/providers/togetherai", diff --git a/litellm/proxy/public_endpoints/provider_create_fields.json b/litellm/proxy/public_endpoints/provider_create_fields.json index cd781abee26..ba4ae585d45 100644 --- a/litellm/proxy/public_endpoints/provider_create_fields.json +++ b/litellm/proxy/public_endpoints/provider_create_fields.json @@ -2953,6 +2953,34 @@ ], "default_model_placeholder": "gpt-3.5-turbo" }, + { + "provider": "TIUM", + "provider_display_name": "Tium", + "litellm_provider": "tium", + "credential_fields": [ + { + "key": "api_base", + "label": "API Base", + "placeholder": "https://api.tium.ai/v1", + "tooltip": null, + "required": false, + "field_type": "text", + "options": null, + "default_value": null + }, + { + "key": "api_key", + "label": "API Key", + "placeholder": null, + "tooltip": null, + "required": true, + "field_type": "password", + "options": null, + "default_value": null + } + ], + "default_model_placeholder": "tium/glm-5.3" + }, { "provider": "TogetherAI", "provider_display_name": "TogetherAI", diff --git a/litellm/types/utils.py b/litellm/types/utils.py index d62f00f3676..22d1322ce77 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -4003,6 +4003,7 @@ class LlmProviders(str, Enum): PINSTRIPES = "pinstripes" COGNITION = "cognition" SCX_AI = "scx-ai" + TIUM = "tium" DARKBLOOM = "darkbloom" META = "meta" LITELLM_AGENT = "litellm_agent" diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 54ebdc85be9..2c6a0d96273 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -42785,6 +42785,91 @@ "output_cost_per_token": 2.8e-05, "source": "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models#foundation_models" }, + "tium/glm-5.3-flash": { + "cache_read_input_token_cost": 3.03e-08, + "input_cost_per_token": 1.52e-07, + "litellm_provider": "tium", + "max_input_tokens": 128000, + "max_output_tokens": 32768, + "max_tokens": 32768, + "mode": "chat", + "output_cost_per_token": 5.05e-07, + "source": "https://tium.ai/pricing", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true + }, + "tium/deepseek-v4-flash": { + "cache_read_input_token_cost": 2.83e-08, + "input_cost_per_token": 8.89e-07, + "litellm_provider": "tium", + "max_input_tokens": 128000, + "max_output_tokens": 32768, + "max_tokens": 32768, + "mode": "chat", + "output_cost_per_token": 2.668e-06, + "source": "https://tium.ai/pricing", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": false, + "supports_tool_choice": true, + "supports_vision": false + }, + "tium/deepseek-v4-pro": { + "cache_read_input_token_cost": 8.89e-08, + "input_cost_per_token": 2.668e-06, + "litellm_provider": "tium", + "max_input_tokens": 128000, + "max_output_tokens": 32768, + "max_tokens": 32768, + "mode": "chat", + "output_cost_per_token": 8.005e-06, + "source": "https://tium.ai/pricing", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": false, + "supports_tool_choice": true, + "supports_vision": false + }, + "tium/glm-5.3": { + "cache_read_input_token_cost": 5.26e-07, + "input_cost_per_token": 2.83e-06, + "litellm_provider": "tium", + "max_input_tokens": 128000, + "max_output_tokens": 32768, + "max_tokens": 32768, + "mode": "chat", + "output_cost_per_token": 8.895e-06, + "source": "https://tium.ai/pricing", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": false + }, + "tium/kimi-k3": { + "cache_read_input_token_cost": 6.06e-07, + "input_cost_per_token": 6.065e-06, + "litellm_provider": "tium", + "max_input_tokens": 128000, + "max_output_tokens": 32768, + "max_tokens": 32768, + "mode": "chat", + "output_cost_per_token": 3.0323e-05, + "source": "https://tium.ai/pricing", + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_tool_choice": true, + "supports_vision": true + }, "together-ai-21.1b-41b": { "input_cost_per_token": 8e-07, "litellm_provider": "together_ai", diff --git a/provider_endpoints_support.json b/provider_endpoints_support.json index c71f4a82a4a..c428ae435ee 100644 --- a/provider_endpoints_support.json +++ b/provider_endpoints_support.json @@ -2438,6 +2438,23 @@ "interactions": true } }, + "tium": { + "display_name": "Tium (`tium`)", + "url": "https://docs.litellm.ai/docs/providers/tium", + "endpoints": { + "chat_completions": true, + "messages": false, + "responses": false, + "embeddings": false, + "image_generations": false, + "audio_transcriptions": false, + "audio_speech": false, + "moderations": false, + "batches": false, + "rerank": false, + "a2a": false + } + }, "together_ai": { "display_name": "Together AI (`together_ai`)", "url": "https://docs.litellm.ai/docs/providers/togetherai", diff --git a/tests/test_litellm/llms/openai_like/test_tium_provider.py b/tests/test_litellm/llms/openai_like/test_tium_provider.py new file mode 100644 index 00000000000..57a40658696 --- /dev/null +++ b/tests/test_litellm/llms/openai_like/test_tium_provider.py @@ -0,0 +1,321 @@ +""" +Tests for Tium provider configuration and integration. +""" + +import json + +import httpx +import pytest +import respx + +import litellm +from litellm import completion + +TIUM_URL = "https://api.tium.ai/v1/chat/completions" + +CHAT_PAYLOAD = { + "id": "chatcmpl-1", + "object": "chat.completion", + "created": 1, + "model": "glm-5.3", + "choices": [ + { + "index": 0, + "message": {"role": "assistant", "content": "ok"}, + "finish_reason": "stop", + } + ], + "usage": {"prompt_tokens": 5, "completion_tokens": 1, "total_tokens": 6}, +} + +TOOL_PAYLOAD = { + "id": "chatcmpl-2", + "object": "chat.completion", + "created": 1, + "model": "glm-5.3", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": None, + "tool_calls": [ + { + "id": "call_1", + "type": "function", + "function": { + "name": "get_weather", + "arguments": '{"city": "Berlin"}', + }, + } + ], + }, + "finish_reason": "tool_calls", + } + ], + "usage": {"prompt_tokens": 5, "completion_tokens": 1, "total_tokens": 6}, +} + +WEATHER_TOOL = { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get the current weather in a location", + "parameters": { + "type": "object", + "properties": {"city": {"type": "string"}}, + "required": ["city"], + }, + }, +} + + +class TestTiumProviderConfig: + def test_tium_in_provider_list(self): + from litellm import LlmProviders + + assert hasattr(LlmProviders, "TIUM") + assert LlmProviders.TIUM.value == "tium" + assert "tium" in litellm.provider_list + + def test_tium_json_config_exists(self): + from litellm.llms.openai_like.json_loader import JSONProviderRegistry + + assert JSONProviderRegistry.exists("tium") + + tium = JSONProviderRegistry.get("tium") + assert tium is not None + assert tium.base_url == "https://api.tium.ai/v1" + assert tium.api_key_env == "TIUM_API_KEY" + assert tium.api_base_env == "TIUM_API_BASE" + assert tium.param_mappings.get("max_completion_tokens") == "max_tokens" + + def test_tium_in_openai_compatible_providers(self): + from litellm.constants import openai_compatible_providers + + assert "tium" in openai_compatible_providers + + def test_tium_provider_resolution(self): + from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider + + model, provider, api_key, api_base = get_llm_provider( + model="tium/glm-5.3", + custom_llm_provider=None, + api_base=None, + api_key=None, + ) + + assert model == "glm-5.3" + assert provider == "tium" + assert api_base == "https://api.tium.ai/v1" + + def test_tium_api_base_override(self): + from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider + + model, provider, api_key, api_base = get_llm_provider( + model="tium/glm-5.3", + custom_llm_provider=None, + api_base="https://custom.example.com/v1", + api_key="sk-test", + ) + + assert provider == "tium" + assert api_base == "https://custom.example.com/v1" + assert api_key == "sk-test" + + def test_tium_url_autodetection(self): + from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider + + model, provider, api_key, api_base = get_llm_provider( + model="glm-5.3", + custom_llm_provider=None, + api_base="https://api.tium.ai/v1", + api_key=None, + ) + + assert provider == "tium" + assert api_base == "https://api.tium.ai/v1" + + def test_tium_router_config(self): + from litellm import Router + + router = Router( + model_list=[ + { + "model_name": "tium-chat", + "litellm_params": { + "model": "tium/glm-5.3", + "api_key": "test-key", + }, + } + ] + ) + + assert len(router.model_list) == 1 + assert router.model_list[0]["model_name"] == "tium-chat" + + +@pytest.mark.usefixtures("local_model_cost_map") +class TestTiumCompletion: + @respx.mock + def test_tium_completion_hits_tium_url_with_bearer_auth(self): + route = respx.post(TIUM_URL).mock( + return_value=httpx.Response(200, json=CHAT_PAYLOAD) + ) + + response = completion( + model="tium/glm-5.3", + messages=[{"role": "user", "content": "Hello"}], + api_key="sk-tium-test", + ) + + assert route.called + request = route.calls.last.request + assert str(request.url) == TIUM_URL + assert request.headers["authorization"] == "Bearer sk-tium-test" + + body = json.loads(request.content) + assert body["model"] == "glm-5.3" + assert body["messages"] == [{"role": "user", "content": "Hello"}] + + assert response.choices[0].message.content == "ok" + + @respx.mock + def test_tium_completion_honours_api_base_override(self): + route = respx.post("https://custom.example.com/v1/chat/completions").mock( + return_value=httpx.Response(200, json=CHAT_PAYLOAD) + ) + + completion( + model="tium/glm-5.3", + messages=[{"role": "user", "content": "Hello"}], + api_key="sk-tium-test", + api_base="https://custom.example.com/v1", + ) + + assert route.called + + @respx.mock + def test_tium_completion_sends_tools_and_returns_tool_calls(self): + route = respx.post(TIUM_URL).mock( + return_value=httpx.Response(200, json=TOOL_PAYLOAD) + ) + + response = completion( + model="tium/glm-5.3", + messages=[{"role": "user", "content": "Weather in Berlin?"}], + tools=[WEATHER_TOOL], + tool_choice="auto", + api_key="sk-tium-test", + ) + + assert route.called + body = json.loads(route.calls.last.request.content) + assert body["tools"] == [WEATHER_TOOL] + assert body["tool_choice"] == "auto" + + tool_calls = response.choices[0].message.tool_calls + assert tool_calls is not None + assert tool_calls[0].function.name == "get_weather" + + @respx.mock + def test_tium_completion_maps_max_completion_tokens_on_the_wire(self): + route = respx.post(TIUM_URL).mock( + return_value=httpx.Response(200, json=CHAT_PAYLOAD) + ) + + completion( + model="tium/glm-5.3", + messages=[{"role": "user", "content": "Hello"}], + max_completion_tokens=256, + api_key="sk-tium-test", + ) + + assert route.called + body = json.loads(route.calls.last.request.content) + assert body["max_tokens"] == 256 + assert "max_completion_tokens" not in body + + +class TestTiumModelMetadata: + TIUM_MODELS = ( + "tium/glm-5.3-flash", + "tium/deepseek-v4-flash", + "tium/deepseek-v4-pro", + "tium/glm-5.3", + "tium/kimi-k3", + ) + VISION_MODELS = ("tium/glm-5.3-flash", "tium/kimi-k3") + NO_RESPONSE_SCHEMA = ("tium/deepseek-v4-flash", "tium/deepseek-v4-pro") + + @staticmethod + def _load(path_parts): + from pathlib import Path + + json_path = Path(__file__).parents[4].joinpath(*path_parts) + with open(json_path) as f: + return json.load(f) + + def test_tium_models_registered_with_correct_metadata(self): + model_cost = self._load(("model_prices_and_context_window.json",)) + for model in self.TIUM_MODELS: + info = model_cost.get(model) + assert info is not None, f"{model} missing from model_prices_and_context_window.json" + assert info["litellm_provider"] == "tium" + assert info["mode"] == "chat" + assert info["input_cost_per_token"] > 0 + assert info["output_cost_per_token"] > 0 + + assert info["supports_function_calling"] is True + assert info["supports_tool_choice"] is True + assert info["supports_reasoning"] is True + + assert info["supports_response_schema"] is (model not in self.NO_RESPONSE_SCHEMA) + assert info.get("supports_vision", False) is (model in self.VISION_MODELS) + + assert info["supports_prompt_caching"] is True + assert 0 < info["cache_read_input_token_cost"] < info["input_cost_per_token"] + + assert info["max_input_tokens"] == 128000 + assert info["max_output_tokens"] == 32768 + assert info["max_tokens"] == info["max_output_tokens"] + + def test_tium_models_synced_to_backup(self): + model_cost = self._load(("model_prices_and_context_window.json",)) + backup = self._load(("litellm", "model_prices_and_context_window_backup.json")) + for model in self.TIUM_MODELS: + assert model in backup, f"{model} missing from backup json" + assert backup[model] == model_cost[model], f"{model} differs between root and backup json" + + def test_tium_supported_endpoints_matrix(self): + from pathlib import Path + + import litellm as _litellm + + backup_path = ( + Path(_litellm.__file__).parent / "provider_endpoints_support_backup.json" + ) + matrix = json.loads(backup_path.read_text()) + + assert "tium" in matrix["providers"] + endpoints = matrix["providers"]["tium"]["endpoints"] + assert endpoints["chat_completions"] is True + assert endpoints["responses"] is False + assert endpoints["embeddings"] is False + + def test_tium_listed_in_add_model_form(self): + entries = self._load( + ("litellm", "proxy", "public_endpoints", "provider_create_fields.json") + ) + tium = [e for e in entries if e["litellm_provider"] == "tium"] + assert len(tium) == 1 + + entry = tium[0] + assert entry["provider"] == "TIUM" + assert entry["provider_display_name"] == "Tium" + assert entry["default_model_placeholder"].startswith("tium/") + + fields = {f["key"]: f for f in entry["credential_fields"]} + assert fields["api_key"]["required"] is True + assert fields["api_key"]["field_type"] == "password" + assert fields["api_base"]["required"] is False diff --git a/ui/litellm-dashboard/public/assets/logos/tium.svg b/ui/litellm-dashboard/public/assets/logos/tium.svg new file mode 100644 index 00000000000..d5cdf642e36 --- /dev/null +++ b/ui/litellm-dashboard/public/assets/logos/tium.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx b/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx index 4c68e302267..925ebb5b4f8 100644 --- a/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx +++ b/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx @@ -73,6 +73,17 @@ describe("provider_info_helpers", () => { expect(fromEnumKey.logo).toBe(providerLogoMap[Providers.SCX_AI]); }); + it("should map tium slug and TIUM enum key to the Tium display name and logo", () => { + const fromSlug = getProviderLogoAndName("tium"); + expect(fromSlug.displayName).toBe(Providers.TIUM); + expect(fromSlug.logo).toBe(providerLogoMap[Providers.TIUM]); + expect(fromSlug.logo).toBeTruthy(); + + const fromEnumKey = getProviderLogoAndName("TIUM"); + expect(fromEnumKey.displayName).toBe(Providers.TIUM); + expect(fromEnumKey.logo).toBe(providerLogoMap[Providers.TIUM]); + }); + it("should map bedrock_mantle slug to Bedrock Mantle display name and logo", () => { const result = getProviderLogoAndName("bedrock_mantle"); expect(result.displayName).toBe(Providers.BedrockMantle); @@ -205,6 +216,10 @@ describe("provider_info_helpers", () => { expect(getPlaceholder(Providers.SCX_AI)).toBe("scx-ai/GLM-5.2"); }); + it("should return a tium model placeholder for TIUM provider", () => { + expect(getPlaceholder(Providers.TIUM)).toBe("tium/glm-5.3"); + }); + it("should return claude-3-opus placeholder for Anthropic provider", () => { expect(getPlaceholder(Providers.Anthropic)).toBe("claude-3-opus"); }); diff --git a/ui/litellm-dashboard/src/components/provider_info_helpers.tsx b/ui/litellm-dashboard/src/components/provider_info_helpers.tsx index 72ec5990557..18a91cfb3aa 100644 --- a/ui/litellm-dashboard/src/components/provider_info_helpers.tsx +++ b/ui/litellm-dashboard/src/components/provider_info_helpers.tsx @@ -54,6 +54,7 @@ import sapLogo from "../../public/assets/logos/sap.png"; import scxAiLogo from "../../public/assets/logos/scx_ai.svg"; import snowflakeLogo from "../../public/assets/logos/snowflake.svg"; import sonioxLogo from "../../public/assets/logos/soniox.svg"; +import tiumLogo from "../../public/assets/logos/tium.svg"; import togetheraiLogo from "../../public/assets/logos/togetherai.svg"; import topazLogo from "../../public/assets/logos/topaz.svg"; import v0Logo from "../../public/assets/logos/v0.svg"; @@ -163,6 +164,7 @@ export enum Providers { Snowflake = "Snowflake", Soniox = "Soniox", TEXT_COMPLETION_CODESTRAL = "Text-Completion-Codestral", + TIUM = "Tium", TogetherAI = "TogetherAI", TOPAZ = "Topaz", Triton = "Triton", @@ -279,6 +281,7 @@ export const provider_map: Record = { Snowflake: "snowflake", Soniox: "soniox", TEXT_COMPLETION_CODESTRAL: "text-completion-codestral", + TIUM: "tium", TogetherAI: "together_ai", TOPAZ: "topaz", Triton: "triton", @@ -376,6 +379,7 @@ export const providerLogoMap: Partial> = { [Providers.Snowflake]: snowflakeLogo.src, [Providers.Soniox]: sonioxLogo.src, [Providers.TEXT_COMPLETION_CODESTRAL]: mistralLogo.src, + [Providers.TIUM]: tiumLogo.src, [Providers.TogetherAI]: togetheraiLogo.src, [Providers.TOPAZ]: topazLogo.src, [Providers.Triton]: nvidiaTritonLogo.src, @@ -441,6 +445,7 @@ const providerPlaceholderMap: Partial> = { [Providers.SageMaker]: "sagemaker/jumpstart-dft-meta-textgeneration-llama-2-7b", [Providers.SCX_AI]: "scx-ai/GLM-5.2", [Providers.Snowflake]: "snowflake/mistral-7b", + [Providers.TIUM]: "tium/glm-5.3", [Providers.Vertex_AI]: "gemini-pro", [Providers.VolcEngine]: "volcengine/", [Providers.Voyage]: "voyage/",