From 248090a14425df46a29023f3636c99888ae20e6e Mon Sep 17 00:00:00 2001 From: Balys Kriksciunas Date: Mon, 2 Nov 2026 00:40:07 +0200 Subject: [PATCH 1/2] feat(providers): add Turion decisions and extraction provider Turion hosts Turion Jev 31B, a System One decision model, and Turion Extract alpha, an extraction model that answers Cloudflare Clairic's field format. TurionDecisionsConfig sends decisions to /v1/systemone and TurionExtractionConfig sends extractions to /v1/extract on https://api.turion.ai (TURION_API_BASE overrides), authenticated with TURION_API_KEY or the Turion CLI's TURION_API_TOKEN. Adds the turion provider, the cost map entries for turion/turion-jev-31b (evaluation) and turion/turion-extract-alpha (extraction), the provider endpoint support entry, the Add Model credential fields, the dashboard provider metadata and the README row. --- README.md | 1 + litellm/__init__.py | 6 + litellm/_lazy_imports_registry.py | 4 + .../llms/turion/decisions/transformation.py | 9 ++ .../llms/turion/extraction/transformation.py | 13 ++ ...odel_prices_and_context_window_backup.json | 16 ++ .../provider_endpoints_support_backup.json | 26 +++ .../provider_create_fields.json | 28 ++++ litellm/types/utils.py | 1 + litellm/utils.py | 11 +- model_prices_and_context_window.json | 16 ++ provider_endpoints_support.json | 26 +++ .../providers/test_decisions_wire.py | 3 + .../test_base_decisions_transformation.py | 6 +- tests/unit/llms/turion/__init__.py | 0 .../llms/turion/test_turion_transformation.py | 149 ++++++++++++++++++ .../public_endpoints/test_public_endpoints.py | 1 + .../components/provider_info_helpers.test.tsx | 15 ++ .../src/components/provider_info_helpers.tsx | 3 + 19 files changed, 330 insertions(+), 4 deletions(-) create mode 100644 litellm/llms/turion/decisions/transformation.py create mode 100644 litellm/llms/turion/extraction/transformation.py create mode 100644 tests/unit/llms/turion/__init__.py create mode 100644 tests/unit/llms/turion/test_turion_transformation.py diff --git a/README.md b/README.md index 3f1f21f903e..fd6f9f4a982 100644 --- a/README.md +++ b/README.md @@ -463,6 +463,7 @@ Set `LITELLM_PROXY_API_BASE` and `LITELLM_PROXY_API_KEY` and every model call th | [Together AI (`together_ai`)](https://docs.litellm.ai/docs/providers/togetherai) | ✅ | ✅ | ✅ | | | | | | | | | [Topaz (`topaz`)](https://docs.litellm.ai/docs/providers/topaz) | ✅ | ✅ | ✅ | | | | | | | | | [Triton (`triton`)](https://docs.litellm.ai/docs/providers/triton-inference-server) | ✅ | ✅ | ✅ | | | | | | | | +| [Turion (`turion`)](https://turion.ai/docs/guides/garden/) | | | | | | | | | | | | [Typesafe Decisions API (`typesafe`)](https://docs.litellm.ai/docs/providers) | | | | | | | | | | | | [V0 (`v0`)](https://docs.litellm.ai/docs/providers/v0) | ✅ | ✅ | ✅ | | | | | | | | | [Vercel AI Gateway (`vercel_ai_gateway`)](https://docs.litellm.ai/docs/providers/vercel_ai_gateway) | ✅ | ✅ | ✅ | | | | | | | | diff --git a/litellm/__init__.py b/litellm/__init__.py index f64531f86fb..d1717754c6b 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -1750,6 +1750,9 @@ if TYPE_CHECKING: from .llms.nace.decisions.transformation import ( NaceDecisionsConfig as NaceDecisionsConfig, ) + from .llms.turion.decisions.transformation import ( + TurionDecisionsConfig as TurionDecisionsConfig, + ) from .llms.typesafe.decisions.transformation import ( TypeSafeDecisionsConfig as TypeSafeDecisionsConfig, ) @@ -1765,6 +1768,9 @@ if TYPE_CHECKING: from .llms.cloudflare.extraction.transformation import ( CloudflareExtractionConfig as CloudflareExtractionConfig, ) + from .llms.turion.extraction.transformation import ( + TurionExtractionConfig as TurionExtractionConfig, + ) from .llms.strands_decider.decisions.transformation import ( StrandsDeciderDecisionsConfig as StrandsDeciderDecisionsConfig, ) diff --git a/litellm/_lazy_imports_registry.py b/litellm/_lazy_imports_registry.py index de290070ed3..2be8c2366ac 100644 --- a/litellm/_lazy_imports_registry.py +++ b/litellm/_lazy_imports_registry.py @@ -163,10 +163,12 @@ LLM_CONFIG_NAMES: Final = ( "PerplexityDecisionsConfig", "TypeSafeDecisionsConfig", "NaceDecisionsConfig", + "TurionDecisionsConfig", "OpenRouterDecisionsConfig", "FireworksAIDecisionsConfig", "CloudflareDecisionsConfig", "CloudflareExtractionConfig", + "TurionExtractionConfig", "StrandsDeciderDecisionsConfig", "DatabricksDecisionsConfig", "HostedVLLMDecisionsConfig", @@ -737,11 +739,13 @@ _LLM_CONFIGS_IMPORT_MAP: Final = { ), "PerplexityDecisionsConfig": (".llms.perplexity.decisions.transformation", "PerplexityDecisionsConfig"), "NaceDecisionsConfig": (".llms.nace.decisions.transformation", "NaceDecisionsConfig"), + "TurionDecisionsConfig": (".llms.turion.decisions.transformation", "TurionDecisionsConfig"), "TypeSafeDecisionsConfig": (".llms.typesafe.decisions.transformation", "TypeSafeDecisionsConfig"), "OpenRouterDecisionsConfig": (".llms.openrouter.decisions.transformation", "OpenRouterDecisionsConfig"), "FireworksAIDecisionsConfig": (".llms.fireworks_ai.decisions.transformation", "FireworksAIDecisionsConfig"), "CloudflareDecisionsConfig": (".llms.cloudflare.decisions.transformation", "CloudflareDecisionsConfig"), "CloudflareExtractionConfig": (".llms.cloudflare.extraction.transformation", "CloudflareExtractionConfig"), + "TurionExtractionConfig": (".llms.turion.extraction.transformation", "TurionExtractionConfig"), "StrandsDeciderDecisionsConfig": ( ".llms.strands_decider.decisions.transformation", "StrandsDeciderDecisionsConfig", diff --git a/litellm/llms/turion/decisions/transformation.py b/litellm/llms/turion/decisions/transformation.py new file mode 100644 index 00000000000..91a0d7e55d8 --- /dev/null +++ b/litellm/llms/turion/decisions/transformation.py @@ -0,0 +1,9 @@ +from litellm.llms.base_llm.decisions.transformation import BaseDecisionsConfig + + +class TurionDecisionsConfig(BaseDecisionsConfig): + api_key_env = ("TURION_API_KEY", "TURION_API_TOKEN") + api_base_env = ("TURION_API_BASE",) + + def get_default_api_base(self) -> str | None: + return "https://api.turion.ai" diff --git a/litellm/llms/turion/extraction/transformation.py b/litellm/llms/turion/extraction/transformation.py new file mode 100644 index 00000000000..944ab209d46 --- /dev/null +++ b/litellm/llms/turion/extraction/transformation.py @@ -0,0 +1,13 @@ +from litellm.litellm_core_utils.url_utils import join_api_base +from litellm.llms.base_llm.extraction.transformation import BaseExtractionConfig + + +class TurionExtractionConfig(BaseExtractionConfig): + api_key_env = ("TURION_API_KEY", "TURION_API_TOKEN") + api_base_env = ("TURION_API_BASE",) + + def get_default_api_base(self) -> str | None: + return "https://api.turion.ai" + + def get_complete_url(self, api_base: str, model: str) -> str: + return join_api_base(api_base.rstrip("/").removesuffix("/v1"), "/v1/extract") diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 703621b60cf..916cd98d08a 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -72755,6 +72755,22 @@ "output_cost_per_token": 0.0, "source": "https://console.nace.ai/docs/drex/reference/models" }, + "turion/turion-extract-alpha": { + "input_cost_per_token": 5e-08, + "litellm_provider": "turion", + "max_input_tokens": 16384, + "mode": "extraction", + "output_cost_per_token": 0.0, + "source": "https://turion.ai/docs/guides/usage/#pricing" + }, + "turion/turion-jev-31b": { + "input_cost_per_token": 2.1e-08, + "litellm_provider": "turion", + "max_input_tokens": 16384, + "mode": "evaluation", + "output_cost_per_token": 0.0, + "source": "https://turion.ai/docs/guides/usage/#pricing" + }, "typesafe/jev-1.13.0": { "input_cost_per_token": 4.2e-08, "litellm_provider": "typesafe", diff --git a/litellm/provider_endpoints_support_backup.json b/litellm/provider_endpoints_support_backup.json index 700a35edb06..7dcea07e272 100644 --- a/litellm/provider_endpoints_support_backup.json +++ b/litellm/provider_endpoints_support_backup.json @@ -4046,6 +4046,32 @@ "generateContent": false } }, + "turion": { + "display_name": "Turion (`turion`)", + "url": "https://turion.ai/docs/guides/garden/", + "endpoints": { + "chat_completions": false, + "responses": false, + "text_completion": false, + "embeddings": false, + "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": false, + "interactions": false, + "systemone": false, + "a2a": false, + "generateContent": false + } + }, "typesafe": { "display_name": "TypeSafe (`typesafe`)", "url": "https://docs.typesafe.ai/models", diff --git a/litellm/proxy/public_endpoints/provider_create_fields.json b/litellm/proxy/public_endpoints/provider_create_fields.json index 044b8a1de6e..d5a807b2ed4 100644 --- a/litellm/proxy/public_endpoints/provider_create_fields.json +++ b/litellm/proxy/public_endpoints/provider_create_fields.json @@ -2641,6 +2641,34 @@ ], "default_model_placeholder": "nace/drex-latest" }, + { + "provider": "Turion", + "provider_display_name": "Turion", + "litellm_provider": "turion", + "credential_fields": [ + { + "key": "api_base", + "label": "API Base", + "placeholder": "https://api.turion.ai", + "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": "turion/turion-jev-31b" + }, { "provider": "NEBIUS", "provider_display_name": "Nebius", diff --git a/litellm/types/utils.py b/litellm/types/utils.py index da1d99957a7..2e57b3b4d2f 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -4567,6 +4567,7 @@ class LlmProviders(str, Enum): PERPLEXITY = "perplexity" TYPESAFE = "typesafe" NACE = "nace" + TURION = "turion" STRANDS_DECIDER = "strands_decider" LAYA = "laya" BESPOKE = "bespoke" diff --git a/litellm/utils.py b/litellm/utils.py index b0826f84006..ad867f1044d 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -10052,12 +10052,19 @@ class ProviderConfigManager: return litellm.LayaDecisionsConfig() if provider == LlmProviders.BESPOKE: return litellm.BespokeDecisionsConfig() - if provider in (LlmProviders.OPENAI, LlmProviders.AZURE, LlmProviders.AZURE_AI, LlmProviders.EU_JEV): + if provider in ( + LlmProviders.OPENAI, + LlmProviders.AZURE, + LlmProviders.AZURE_AI, + LlmProviders.EU_JEV, + LlmProviders.TURION, + ): configs: Final = { LlmProviders.OPENAI: litellm.OpenAIDecisionsConfig, LlmProviders.AZURE: litellm.AzureDecisionsConfig, LlmProviders.AZURE_AI: litellm.AzureAIDecisionsConfig, LlmProviders.EU_JEV: litellm.EuJevDecisionsConfig, + LlmProviders.TURION: litellm.TurionDecisionsConfig, } return configs[provider]() return None @@ -10066,6 +10073,8 @@ class ProviderConfigManager: def get_provider_extraction_config(model: str, provider: LlmProviders) -> BaseExtractionConfig | None: if provider == LlmProviders.CLOUDFLARE: return litellm.CloudflareExtractionConfig() + if provider == LlmProviders.TURION: + return litellm.TurionExtractionConfig() return None @staticmethod diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 703621b60cf..916cd98d08a 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -72755,6 +72755,22 @@ "output_cost_per_token": 0.0, "source": "https://console.nace.ai/docs/drex/reference/models" }, + "turion/turion-extract-alpha": { + "input_cost_per_token": 5e-08, + "litellm_provider": "turion", + "max_input_tokens": 16384, + "mode": "extraction", + "output_cost_per_token": 0.0, + "source": "https://turion.ai/docs/guides/usage/#pricing" + }, + "turion/turion-jev-31b": { + "input_cost_per_token": 2.1e-08, + "litellm_provider": "turion", + "max_input_tokens": 16384, + "mode": "evaluation", + "output_cost_per_token": 0.0, + "source": "https://turion.ai/docs/guides/usage/#pricing" + }, "typesafe/jev-1.13.0": { "input_cost_per_token": 4.2e-08, "litellm_provider": "typesafe", diff --git a/provider_endpoints_support.json b/provider_endpoints_support.json index 700a35edb06..7dcea07e272 100644 --- a/provider_endpoints_support.json +++ b/provider_endpoints_support.json @@ -4046,6 +4046,32 @@ "generateContent": false } }, + "turion": { + "display_name": "Turion (`turion`)", + "url": "https://turion.ai/docs/guides/garden/", + "endpoints": { + "chat_completions": false, + "responses": false, + "text_completion": false, + "embeddings": false, + "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": false, + "interactions": false, + "systemone": false, + "a2a": false, + "generateContent": false + } + }, "typesafe": { "display_name": "TypeSafe (`typesafe`)", "url": "https://docs.typesafe.ai/models", diff --git a/tests/integration/providers/test_decisions_wire.py b/tests/integration/providers/test_decisions_wire.py index 5af1532e9ed..247054a048c 100644 --- a/tests/integration/providers/test_decisions_wire.py +++ b/tests/integration/providers/test_decisions_wire.py @@ -91,6 +91,9 @@ _PROVIDERS: Final = ( ), _Provider("typesafe", "typesafe/jev-1.13.0", "/v1/systemone", "jev-1.13.0", _API_KEY, False, "typesafe/jev-1.13.0"), _Provider("nace", "nace/drex-v2.0", "/v1/systemone", "drex-v2.0", _API_KEY, False, "nace/drex-v2.0"), + _Provider( + "turion", "turion/turion-jev-31b", "/v1/systemone", "turion-jev-31b", _API_KEY, False, "turion/turion-jev-31b" + ), _Provider( "openrouter", "openrouter/typesafe/jev-1.13", diff --git a/tests/unit/llms/base_llm/decisions/test_base_decisions_transformation.py b/tests/unit/llms/base_llm/decisions/test_base_decisions_transformation.py index 041c625eaef..799e5ed2f14 100644 --- a/tests/unit/llms/base_llm/decisions/test_base_decisions_transformation.py +++ b/tests/unit/llms/base_llm/decisions/test_base_decisions_transformation.py @@ -347,7 +347,7 @@ def test_a_box_answer_without_confidence_reaches_the_caller_unchanged() -> None: assert response.model_dump(mode="json") == payload -@pytest.mark.parametrize("provider", ("nace", "openrouter", "fireworks_ai", "typesafe")) +@pytest.mark.parametrize("provider", ("nace", "openrouter", "fireworks_ai", "typesafe", "turion")) @pytest.mark.parametrize("legend", (["low", "high"], {"0": "low", "1": "high"})) def test_score_legends_preserve_labels_probabilities_and_metadata(provider: str, legend: object) -> None: config: Final = ProviderConfigManager.get_provider_decisions_config("drex-v2.0", LlmProviders(provider)) @@ -393,7 +393,7 @@ def test_score_legends_preserve_labels_probabilities_and_metadata(provider: str, @pytest.mark.parametrize( "payload", ([], {"answers": []}, {"answers": {"a": None}}, {"answers": {"a": {"type": "score", "legend": [1]}}}) ) -@pytest.mark.parametrize("provider", ("nace", "openrouter", "fireworks_ai", "typesafe")) +@pytest.mark.parametrize("provider", ("nace", "openrouter", "fireworks_ai", "typesafe", "turion")) def test_invalid_provider_response_is_an_api_error(payload: object, provider: str) -> None: config: Final = ProviderConfigManager.get_provider_decisions_config("drex-v2.0", LlmProviders(provider)) assert config is not None @@ -457,7 +457,7 @@ def test_score_probabilities_preserve_missing_entries_and_explicit_zeros( @pytest.mark.parametrize("raw_content", (b"", b"not-json", b'{"answers":')) -@pytest.mark.parametrize("provider", ("nace", "openrouter", "fireworks_ai", "typesafe", "openai")) +@pytest.mark.parametrize("provider", ("nace", "openrouter", "fireworks_ai", "typesafe", "turion", "openai")) def test_non_json_provider_response_is_an_api_error(raw_content: bytes, provider: str) -> None: config: Final = ProviderConfigManager.get_provider_decisions_config("drex-v2.0", LlmProviders(provider)) assert config is not None diff --git a/tests/unit/llms/turion/__init__.py b/tests/unit/llms/turion/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/unit/llms/turion/test_turion_transformation.py b/tests/unit/llms/turion/test_turion_transformation.py new file mode 100644 index 00000000000..79a294b25b6 --- /dev/null +++ b/tests/unit/llms/turion/test_turion_transformation.py @@ -0,0 +1,149 @@ +import json +from collections.abc import Mapping +from typing import Final + +import httpx +import pytest + +import litellm +from litellm.llms.base_llm.decisions.transformation import systemone_request_to_ir +from litellm.llms.base_llm.extraction.transformation import BaseExtractionConfig +from litellm.types.decisions import DecisionsIRChoiceAnswer, DecisionsRequestBody +from litellm.types.extraction import ExtractionRequestBody +from litellm.types.utils import LlmProviders +from litellm.utils import ProviderConfigManager + +# Bodies as https://api.turion.ai answered them (POST /v1/systemone and POST /v1/extract). +TURION_SYSTEMONE_RESPONSE: Final = { + "model": "turion-jev-31b", + "answers": { + "refund": {"type": "noul", "noul": 0.892483}, + "team": { + "type": "choice", + "choice": "billing", + "probabilities": {"billing": 0.991577, "technical": 0.008423}, + "confidence": 0.991577, + }, + "urgency": { + "type": "score", + "score": 1.128746, + "legend": {"0": "Can wait", "1": "This week", "2": "Right now"}, + "probabilities": {"0": 0.17257, "1": 0.526114, "2": 0.301316}, + "confidence": 0.526114, + }, + }, + "usage": {"input_tokens": 409, "output_tokens": 0}, +} +TURION_EXTRACT_RESPONSE: Final = { + "id": "01a193fd-bf10-742e-a4a4-8f1865b3b83d", + "model": "turion-extract-alpha", + "answers": { + "invoice_number": { + "type": "text", + "value": "INV-2291", + "p_null": 0.00012, + "confidence": 0.99988, + "span": [8, 16], + }, + "total": {"type": "number", "value": 1080, "p_null": 0.000008, "confidence": 0.999992}, + }, + "truncated": False, + "usage": {"input_tokens": 106, "output_tokens": 0, "passes": 1}, +} + + +def _extraction_config() -> BaseExtractionConfig: + config: Final = ProviderConfigManager.get_provider_extraction_config("turion-extract-alpha", LlmProviders.TURION) + assert config is not None + return config + + +def test_turion_serves_decisions_and_extraction() -> None: + assert isinstance( + ProviderConfigManager.get_provider_decisions_config("turion-jev-31b", LlmProviders.TURION), + litellm.TurionDecisionsConfig, + ) + assert isinstance(_extraction_config(), litellm.TurionExtractionConfig) + + +@pytest.mark.parametrize("api_base", ("https://api.turion.ai", "https://api.turion.ai/", "https://api.turion.ai/v1")) +def test_routes_to_the_turion_api(monkeypatch: pytest.MonkeyPatch, api_base: str) -> None: + monkeypatch.delenv("TURION_API_BASE", raising=False) + decisions: Final = ProviderConfigManager.get_provider_decisions_config("turion-jev-31b", LlmProviders.TURION) + assert decisions is not None + assert decisions.resolve_api_base(None) == "https://api.turion.ai" + assert _extraction_config().resolve_api_base(None) == "https://api.turion.ai" + assert decisions.get_complete_url(api_base, "turion-jev-31b") == "https://api.turion.ai/v1/systemone" + assert _extraction_config().get_complete_url(api_base, "turion-extract-alpha") == "https://api.turion.ai/v1/extract" + + +def test_reads_turion_api_key_then_the_turion_cli_token(monkeypatch: pytest.MonkeyPatch) -> None: + config: Final = _extraction_config() + monkeypatch.delenv("TURION_API_KEY", raising=False) + monkeypatch.setenv("TURION_API_TOKEN", "tk_from_the_cli") + assert config.resolve_api_key(None) == "tk_from_the_cli" + monkeypatch.setenv("TURION_API_KEY", "tk_litellm") + assert config.resolve_api_key(None) == "tk_litellm" + assert config.resolve_api_key("tk_explicit") == "tk_explicit" + + +def test_extraction_request_keeps_the_clairic_body() -> None: + body: Final = _extraction_config().transform_extraction_request( + "turion-extract-alpha", + ExtractionRequestBody.model_validate( + { + "state": "Invoice INV-2291 issued 2026-09-02. Total $1080.", + "fields": {"invoice_number": {"type": "text"}, "total": {"type": "number"}}, + "reference_date": "2026-10-20", + } + ), + ) + fields: Final = body["fields"] + assert body["model"] == "turion-extract-alpha" + assert body["reference_date"] == "2026-10-20" + assert isinstance(fields, Mapping) + assert list(fields) == ["invoice_number", "total"] + + +def test_extraction_response_parses_turion_answers() -> None: + response: Final = _extraction_config().transform_extraction_response( + "turion-extract-alpha", + "turion", + httpx.Response(status_code=200, content=json.dumps(TURION_EXTRACT_RESPONSE).encode()), + ) + assert response.truncated is False + assert response.answers["invoice_number"].value == "INV-2291" + assert response.answers["invoice_number"].span == (8, 16) + assert response.answers["total"].value == 1080 + assert response.usage.input_tokens == 106 + + +def test_systemone_response_parses_turion_answers() -> None: + config: Final = ProviderConfigManager.get_provider_decisions_config("turion-jev-31b", LlmProviders.TURION) + assert config is not None + request: Final = systemone_request_to_ir( + DecisionsRequestBody.model_validate( + { + "state": "I was charged twice for my subscription.", + "questions": { + "refund": {"type": "noul", "instructions": "Is the customer asking for money back?"}, + "team": { + "type": "choice", + "instructions": "Which team should handle this?", + "criteria": {"billing": "Charges and refunds", "technical": "Bugs and outages"}, + }, + "urgency": { + "type": "score", + "instructions": "How urgent is this?", + "criteria": ["Can wait", "This week", "Right now"], + }, + }, + } + ) + ) + response: Final = config.parse_response(TURION_SYSTEMONE_RESPONSE, request) + assert len(response.answers) == 3 + team: Final = next(answer for answer in response.answers if isinstance(answer, DecisionsIRChoiceAnswer)) + assert team.choice == "billing" + assert response.usage is not None + assert response.usage.input_tokens == 409 diff --git a/tests/unit/proxy/public_endpoints/test_public_endpoints.py b/tests/unit/proxy/public_endpoints/test_public_endpoints.py index 72755633376..8e5c17dfc98 100644 --- a/tests/unit/proxy/public_endpoints/test_public_endpoints.py +++ b/tests/unit/proxy/public_endpoints/test_public_endpoints.py @@ -536,6 +536,7 @@ def _decisions_provider_entry(provider: str) -> ProviderCreateInfo: ( ("TypeSafe", "TypeSafe", LlmProviders.TYPESAFE), ("Nace", "Nace", LlmProviders.NACE), + ("Turion", "Turion", LlmProviders.TURION), ("EU_JEV", "eu/jev", LlmProviders.EU_JEV), ), ) 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 4c483434831..39701363724 100644 --- a/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx +++ b/ui/litellm-dashboard/src/components/provider_info_helpers.test.tsx @@ -263,6 +263,7 @@ describe("provider_info_helpers", () => { Providers.PREDIBASE, Providers.Sail, Providers.StreamLake, + Providers.Turion, Providers.WANDB, Providers.ZAI, Providers.ZeroGPU, @@ -587,6 +588,20 @@ describe("provider_info_helpers", () => { expect(getPlaceholder("Nace")).toBe("nace/drex-latest"); }); + it("lists Turion's decision and extraction models and resolves the provider name and model placeholder", () => { + const modelMap = { + "turion/turion-jev-31b": { litellm_provider: "turion", mode: "evaluation" }, + "turion/turion-extract-alpha": { litellm_provider: "turion", mode: "extraction" }, + "typesafe/jev-latest": { litellm_provider: "typesafe", mode: "evaluation" }, + }; + expect(getProviderModels(Providers.Turion, modelMap)).toEqual([ + "turion/turion-jev-31b", + "turion/turion-extract-alpha", + ]); + expect(getProviderLogoAndName("turion").displayName).toBe("Turion"); + expect(getPlaceholder("Turion")).toBe("turion/turion-jev-31b"); + }); + it("should list only typesafe decision models for the 'TypeSafe' provider key, not the OpenRouter-hosted one", () => { const modelMap = { "typesafe/jev-latest": { litellm_provider: "typesafe", mode: "evaluation" }, diff --git a/ui/litellm-dashboard/src/components/provider_info_helpers.tsx b/ui/litellm-dashboard/src/components/provider_info_helpers.tsx index a8879eb570b..6169c663be3 100644 --- a/ui/litellm-dashboard/src/components/provider_info_helpers.tsx +++ b/ui/litellm-dashboard/src/components/provider_info_helpers.tsx @@ -191,6 +191,7 @@ export enum Providers { Triton = "Triton", TypeSafe = "TypeSafe", Nace = "Nace", + Turion = "Turion", V0 = "V0", VERCEL_AI_GATEWAY = "Vercel Ai Gateway", Vertex_AI = "Vertex AI (Anthropic, Gemini, etc.)", @@ -325,6 +326,7 @@ export const provider_map: Record = { Triton: "triton", TypeSafe: "typesafe", Nace: "nace", + Turion: "turion", V0: "v0", VERCEL_AI_GATEWAY: "vercel_ai_gateway", Vertex_AI: "vertex_ai", @@ -514,6 +516,7 @@ const providerPlaceholderMap: Partial> = { [Providers.Tencent]: "tencent/deepseek-v4-pro", [Providers.TypeSafe]: "typesafe/jev-latest", [Providers.Nace]: "nace/drex-latest", + [Providers.Turion]: "turion/turion-jev-31b", [Providers.StreamLake]: "streamlake/", [Providers.Vertex_AI]: "gemini-pro", [Providers.VolcEngine]: "volcengine/", From c171bd3ec251fa454ab3b88761edfea228677078 Mon Sep 17 00:00:00 2001 From: Balys Kriksciunas Date: Tue, 3 Nov 2026 21:38:47 +0200 Subject: [PATCH 2/2] chore(cost-map): declare the Decisions routes on turion/turion-jev-31b The provider endpoint files are generated from the cost map, so the row names both routes the proxy serves for Turion, and the generated System One flag for turion is true --- litellm/model_prices_and_context_window_backup.json | 6 +++++- litellm/provider_endpoints_support_backup.json | 2 +- model_prices_and_context_window.json | 6 +++++- provider_endpoints_support.json | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 8769eb26278..08a521d8403 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -73201,7 +73201,11 @@ "max_input_tokens": 16384, "mode": "evaluation", "output_cost_per_token": 0.0, - "source": "https://turion.ai/docs/guides/usage/#pricing" + "source": "https://turion.ai/docs/guides/usage/#pricing", + "supported_endpoints": [ + "/v1/decisions", + "/v1/systemone" + ] }, "typesafe/jev-1.13.0": { "input_cost_per_token": 4.2e-08, diff --git a/litellm/provider_endpoints_support_backup.json b/litellm/provider_endpoints_support_backup.json index 74d9ca966dc..034f8324e54 100644 --- a/litellm/provider_endpoints_support_backup.json +++ b/litellm/provider_endpoints_support_backup.json @@ -4067,7 +4067,7 @@ "batches": false, "messages": false, "interactions": false, - "systemone": false, + "systemone": true, "a2a": false, "generateContent": false } diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 8769eb26278..08a521d8403 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -73201,7 +73201,11 @@ "max_input_tokens": 16384, "mode": "evaluation", "output_cost_per_token": 0.0, - "source": "https://turion.ai/docs/guides/usage/#pricing" + "source": "https://turion.ai/docs/guides/usage/#pricing", + "supported_endpoints": [ + "/v1/decisions", + "/v1/systemone" + ] }, "typesafe/jev-1.13.0": { "input_cost_per_token": 4.2e-08, diff --git a/provider_endpoints_support.json b/provider_endpoints_support.json index 74d9ca966dc..034f8324e54 100644 --- a/provider_endpoints_support.json +++ b/provider_endpoints_support.json @@ -4067,7 +4067,7 @@ "batches": false, "messages": false, "interactions": false, - "systemone": false, + "systemone": true, "a2a": false, "generateContent": false }