mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-13 23:11:40 +00:00
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
This commit is contained in:
parent
ee7c7e14f3
commit
c59b321621
12 changed files with 591 additions and 0 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
321
tests/test_litellm/llms/openai_like/test_tium_provider.py
Normal file
321
tests/test_litellm/llms/openai_like/test_tium_provider.py
Normal file
|
|
@ -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
|
||||
4
ui/litellm-dashboard/public/assets/logos/tium.svg
Normal file
4
ui/litellm-dashboard/public/assets/logos/tium.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||
<rect width="32" height="32" fill="#08090A"/>
|
||||
<rect x="10" y="6" width="12" height="20" rx="1" fill="#FF9E2C"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 207 B |
|
|
@ -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");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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<string, string> = {
|
|||
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<Record<Providers, string>> = {
|
|||
[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<Record<Providers, string>> = {
|
|||
[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/<any-model-on-volcengine>",
|
||||
[Providers.Voyage]: "voyage/",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue