This commit is contained in:
Florian Standhartinger 2026-11-06 02:33:36 +00:00 • committed by GitHub
commit 6c30712128
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 218 additions and 0 deletions

View file

@ -393,6 +393,7 @@ Set `LITELLM_PROXY_API_BASE` and `LITELLM_PROXY_API_KEY` and every model call th
| [Dashscope (`dashscope`)](https://docs.litellm.ai/docs/providers/dashscope) | ✅ | ✅ | ✅ | ✅ | | | | | | ✅ |
| [Databricks (`databricks`)](https://docs.litellm.ai/docs/providers/databricks) | ✅ | ✅ | ✅ | | | | | | | |
| [DataRobot (`datarobot`)](https://docs.litellm.ai/docs/providers/datarobot) | ✅ | ✅ | ✅ | | | | | | | |
| [Decision Models (`decisionmodels`)](https://decisionmodels.io) | | | | | | | | | | |
| [Deepgram (`deepgram`)](https://docs.litellm.ai/docs/providers/deepgram) | ✅ | ✅ | ✅ | | | ✅ | | | | |
| [DeepInfra (`deepinfra`)](https://docs.litellm.ai/docs/providers/deepinfra) | ✅ | ✅ | ✅ | | | | | | | |
| [Deepseek (`deepseek`)](https://docs.litellm.ai/docs/providers/deepseek) | ✅ | ✅ | ✅ | | | | | | | |

View file

@ -1763,6 +1763,9 @@ if TYPE_CHECKING:
from .llms.parallel_ai.decisions.transformation import (
ParallelAIDecisionsConfig as ParallelAIDecisionsConfig,
)
from .llms.decisionmodels.decisions.transformation import (
DecisionModelsDecisionsConfig as DecisionModelsDecisionsConfig,
)
from .llms.openrouter.decisions.transformation import (
OpenRouterDecisionsConfig as OpenRouterDecisionsConfig,
)

View file

@ -165,6 +165,7 @@ LLM_CONFIG_NAMES: Final = (
"TypeSafeDecisionsConfig",
"ParallelAIDecisionsConfig",
"NaceDecisionsConfig",
"DecisionModelsDecisionsConfig",
"OpenRouterDecisionsConfig",
"FireworksAIDecisionsConfig",
"CloudflareDecisionsConfig",
@ -743,6 +744,7 @@ _LLM_CONFIGS_IMPORT_MAP: Final = {
"NaceDecisionsConfig": (".llms.nace.decisions.transformation", "NaceDecisionsConfig"),
"TypeSafeDecisionsConfig": (".llms.typesafe.decisions.transformation", "TypeSafeDecisionsConfig"),
"ParallelAIDecisionsConfig": (".llms.parallel_ai.decisions.transformation", "ParallelAIDecisionsConfig"),
"DecisionModelsDecisionsConfig": (".llms.decisionmodels.decisions.transformation", "DecisionModelsDecisionsConfig"),
"OpenRouterDecisionsConfig": (".llms.openrouter.decisions.transformation", "OpenRouterDecisionsConfig"),
"FireworksAIDecisionsConfig": (".llms.fireworks_ai.decisions.transformation", "FireworksAIDecisionsConfig"),
"CloudflareDecisionsConfig": (".llms.cloudflare.decisions.transformation", "CloudflareDecisionsConfig"),

View file

@ -0,0 +1,9 @@
from litellm.llms.base_llm.decisions.transformation import BaseDecisionsConfig
class DecisionModelsDecisionsConfig(BaseDecisionsConfig):
api_key_env = ("DECISIONMODELS_API_KEY",)
api_base_env = ("DECISIONMODELS_API_BASE",)
def get_default_api_base(self) -> str | None:
return "https://api.decisionmodels.io"

View file

@ -74117,6 +74117,38 @@
"output_cost_per_token": 0.0,
"source": "https://console.nace.ai/docs/drex/reference/models"
},
"decisionmodels/s1-fast": {
"input_cost_per_token": 1.9e-08,
"litellm_provider": "decisionmodels",
"max_input_tokens": 16192,
"mode": "evaluation",
"output_cost_per_token": 0.0,
"source": "https://api.decisionmodels.io/v1/models"
},
"decisionmodels/s1-pro": {
"input_cost_per_token": 8e-08,
"litellm_provider": "decisionmodels",
"max_input_tokens": 8192,
"mode": "evaluation",
"output_cost_per_token": 0.0,
"source": "https://api.decisionmodels.io/v1/models"
},
"decisionmodels/reax-1": {
"input_cost_per_token": 8e-08,
"litellm_provider": "decisionmodels",
"max_input_tokens": 8192,
"mode": "evaluation",
"output_cost_per_token": 0.0,
"source": "https://api.decisionmodels.io/v1/models"
},
"decisionmodels/s1-llm-auto-router": {
"input_cost_per_token": 1e-09,
"litellm_provider": "decisionmodels",
"max_input_tokens": 8192,
"mode": "evaluation",
"output_cost_per_token": 0.0,
"source": "https://api.decisionmodels.io/v1/models"
},
"typesafe/jev-1.13.0": {
"input_cost_per_token": 4.2e-08,
"litellm_provider": "typesafe",

View file

@ -1397,6 +1397,34 @@
],
"default_model_placeholder": "gpt-3.5-turbo"
},
{
"provider": "DecisionModels",
"provider_display_name": "Decision Models",
"litellm_provider": "decisionmodels",
"credential_fields": [
{
"key": "api_base",
"label": "API Base",
"placeholder": "https://api.decisionmodels.io",
"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": "decisionmodels/s1-fast"
},
{
"provider": "Deepgram",
"provider_display_name": "Deepgram",

View file

@ -4654,6 +4654,7 @@ class LlmProviders(str, Enum):
TYPESAFE = "typesafe"
PARALLEL_AI = "parallel_ai"
NACE = "nace"
DECISIONMODELS = "decisionmodels"
STRANDS_DECIDER = "strands_decider"
LAYA = "laya"
BESPOKE = "bespoke"

View file

@ -10320,6 +10320,7 @@ class ProviderConfigManager:
LlmProviders.AZURE_AI,
LlmProviders.EU_JEV,
LlmProviders.PARALLEL_AI,
LlmProviders.DECISIONMODELS,
):
configs: Final = {
LlmProviders.EMPIRIOLABS: litellm.EmpirioLabsDecisionsConfig,
@ -10328,6 +10329,7 @@ class ProviderConfigManager:
LlmProviders.AZURE_AI: litellm.AzureAIDecisionsConfig,
LlmProviders.EU_JEV: litellm.EuJevDecisionsConfig,
LlmProviders.PARALLEL_AI: litellm.ParallelAIDecisionsConfig,
LlmProviders.DECISIONMODELS: litellm.DecisionModelsDecisionsConfig,
}
return configs[provider]()
return None

View file

@ -74117,6 +74117,38 @@
"output_cost_per_token": 0.0,
"source": "https://console.nace.ai/docs/drex/reference/models"
},
"decisionmodels/s1-fast": {
"input_cost_per_token": 1.9e-08,
"litellm_provider": "decisionmodels",
"max_input_tokens": 16192,
"mode": "evaluation",
"output_cost_per_token": 0.0,
"source": "https://api.decisionmodels.io/v1/models"
},
"decisionmodels/s1-pro": {
"input_cost_per_token": 8e-08,
"litellm_provider": "decisionmodels",
"max_input_tokens": 8192,
"mode": "evaluation",
"output_cost_per_token": 0.0,
"source": "https://api.decisionmodels.io/v1/models"
},
"decisionmodels/reax-1": {
"input_cost_per_token": 8e-08,
"litellm_provider": "decisionmodels",
"max_input_tokens": 8192,
"mode": "evaluation",
"output_cost_per_token": 0.0,
"source": "https://api.decisionmodels.io/v1/models"
},
"decisionmodels/s1-llm-auto-router": {
"input_cost_per_token": 1e-09,
"litellm_provider": "decisionmodels",
"max_input_tokens": 8192,
"mode": "evaluation",
"output_cost_per_token": 0.0,
"source": "https://api.decisionmodels.io/v1/models"
},
"typesafe/jev-1.13.0": {
"input_cost_per_token": 4.2e-08,
"litellm_provider": "typesafe",

View file

@ -4046,6 +4046,13 @@
"generateContent": false
}
},
"decisionmodels": {
"display_name": "Decision Models (`decisionmodels`)",
"url": "https://decisionmodels.io",
"endpoints": {
"systemone": true
}
},
"typesafe": {
"display_name": "TypeSafe (`typesafe`)",
"url": "https://docs.typesafe.ai/models",

View file

@ -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(
"decisionmodels", "decisionmodels/s1-fast", "/v1/systemone", "s1-fast", _API_KEY, False, "decisionmodels/s1-fast"
),
_Provider(
"openrouter",
"openrouter/typesafe/jev-1.13",

View file

@ -128,6 +128,7 @@ _PROVIDERS: Final[tuple[tuple[str, str, str, str], ...]] = (
("typesafe", "typesafe/jev-1.13", "https://api.typesafe.ai/v1/systemone", "jev-1.13"),
("nace", "nace/drex-v2.0", "https://console.nace.ai/v1/systemone", "drex-v2.0"),
("eu_jev", "eu_jev/eu-jeff-latest", "https://eu-jev.com/v1/systemone", "eu-jeff-latest"),
("decisionmodels", "decisionmodels/s1-fast", "https://api.decisionmodels.io/v1/systemone", "s1-fast"),
(
"openrouter",
"openrouter/typesafe/jev-1.13",
@ -4905,3 +4906,85 @@ def test_hosted_vllm_systemone_maps_image_rejection_to_bad_request(
assert caught.value.status_code == 400
assert route.call_count == 1
@pytest.mark.parametrize("model", ("s1-fast", "s1-pro", "reax-1", "s1-llm-auto-router"))
def test_decisionmodels_prices_are_input_only(model: str) -> None:
entry: Final = litellm.model_cost[f"decisionmodels/{model}"]
assert entry["litellm_provider"] == "decisionmodels"
assert entry["mode"] == "evaluation"
assert entry["max_input_tokens"] > 0
assert entry["input_cost_per_token"] > 0
assert entry["output_cost_per_token"] == 0
def test_decisionmodels_sync_call_uses_default_base_and_environment_key(
monkeypatch: pytest.MonkeyPatch,
respx_mock: respx.MockRouter,
) -> None:
monkeypatch.setenv("DECISIONMODELS_API_KEY", "environment-key")
monkeypatch.delenv("DECISIONMODELS_API_BASE", raising=False)
upstream: Final = respx_mock.post("https://api.decisionmodels.io/v1/systemone").respond(json=_RESPONSE)
response: Final = litellm.systemone(
model="decisionmodels/s1-fast",
state="damaged order",
questions={"is_defect": {"type": "noul", "instructions": "Is this a defect?"}},
)
assert response.answers["is_defect"] == NoulAnswer(type="noul", noul=0.9)
assert upstream.calls[0].request.headers["authorization"] == "Bearer environment-key"
assert json.loads(upstream.calls[0].request.content) == {
"model": "s1-fast",
"state": "damaged order",
"questions": {"is_defect": {"type": "noul", "instructions": "Is this a defect?"}},
}
def test_decisionmodels_environment_base_and_explicit_key_win(
monkeypatch: pytest.MonkeyPatch,
respx_mock: respx.MockRouter,
) -> None:
monkeypatch.setenv("DECISIONMODELS_API_BASE", "https://decisionmodels.example/")
monkeypatch.setenv("DECISIONMODELS_API_KEY", "environment-key")
upstream: Final = respx_mock.post("https://decisionmodels.example/v1/systemone").respond(json=_RESPONSE)
litellm.systemone(
model="decisionmodels/s1-pro",
state={"message": "damaged order"},
api_key="explicit-key",
questions={"is_defect": {"type": "noul", "criteria": {"true": "damaged", "false": "intact"}}},
)
assert upstream.calls[0].request.headers["authorization"] == "Bearer explicit-key"
assert json.loads(upstream.calls[0].request.content)["model"] == "s1-pro"
@pytest.mark.parametrize(
("status", "exception"),
(
(401, litellm.AuthenticationError),
(402, litellm.PaymentRequiredError),
(422, litellm.BadRequestError),
(429, litellm.RateLimitError),
),
)
def test_decisionmodels_upstream_failures_preserve_status_and_message(
status: int,
exception: type[Exception],
monkeypatch: pytest.MonkeyPatch,
respx_mock: respx.MockRouter,
) -> None:
monkeypatch.delenv("DECISIONMODELS_API_BASE", raising=False)
upstream: Final = respx_mock.post("https://api.decisionmodels.io/v1/systemone").respond(
status,
json={"detail": {"error_type": "test_error", "message": "decision rejected"}},
headers={"retry-after": "1"},
)
with pytest.raises(exception, match="decision rejected") as error:
litellm.systemone(
model="decisionmodels/s1-fast",
state="text",
api_key="test-key",
questions={"a": {"type": "noul", "instructions": "Is it urgent?"}},
num_retries=0,
)
assert error.value.status_code == status
assert upstream.call_count == 1

View file

@ -532,6 +532,7 @@ def _decisions_provider_entry(provider: str) -> ProviderCreateInfo:
("PARALLEL_AI", "Parallel AI", LlmProviders.PARALLEL_AI),
("EU_JEV", "eu/jev", LlmProviders.EU_JEV),
("EMPIRIOLABS", "EmpirioLabs AI", LlmProviders.EMPIRIOLABS),
("DecisionModels", "Decision Models", LlmProviders.DECISIONMODELS),
),
)
def test_decisions_provider_fields(provider_key: str, display_name: str, provider: LlmProviders) -> None:

View file

@ -250,6 +250,7 @@ describe("provider_info_helpers", () => {
Providers.COMPACTIFAI,
Providers.CORALBRICKS,
Providers.DATAROBOT,
Providers.DecisionModels,
Providers.DOCKER_MODEL_RUNNER,
Providers.DOTPROMPT,
Providers.EMPIRIOLABS,
@ -604,6 +605,16 @@ describe("provider_info_helpers", () => {
expect(getPlaceholder("Nace")).toBe("nace/drex-latest");
});
it("lists Decision Models decision models and resolves the provider name and model placeholder", () => {
const modelMap = {
"decisionmodels/s1-fast": { litellm_provider: "decisionmodels", mode: "evaluation" },
"typesafe/jev-latest": { litellm_provider: "typesafe", mode: "evaluation" },
};
expect(getProviderModels("DecisionModels" as Providers, modelMap)).toEqual(["decisionmodels/s1-fast"]);
expect(getProviderLogoAndName("decisionmodels").displayName).toBe("Decision Models");
expect(getPlaceholder("DecisionModels")).toBe("decisionmodels/s1-fast");
});
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" },

View file

@ -111,6 +111,7 @@ export enum Providers {
Dashscope = "Dashscope",
Databricks = "Databricks (Qwen API)",
DATAROBOT = "Datarobot",
DecisionModels = "Decision Models",
DeepInfra = "DeepInfra",
Deepgram = "Deepgram",
Deepseek = "Deepseek",
@ -246,6 +247,7 @@ export const provider_map: Record<string, string> = {
Dashscope: "dashscope",
Databricks: "databricks",
DATAROBOT: "datarobot",
DecisionModels: "decisionmodels",
DeepInfra: "deepinfra",
Deepgram: "deepgram",
Deepseek: "deepseek",
@ -499,6 +501,7 @@ const providerPlaceholderMap: Partial<Record<Providers, string>> = {
[Providers.Cognition]: "cognition/swe-1.7",
[Providers.CORALBRICKS]: "coralbricks/glm-5.3-fast",
[Providers.Cursor]: "cursor/claude-4-sonnet",
[Providers.DecisionModels]: "decisionmodels/s1-fast",
[Providers.DeepInfra]: "deepinfra/<any-model-on-deepinfra>",
[Providers.EDENAI]: "edenai/openai/gpt-mini-latest",
[Providers.EMPIRIOLABS]: "empiriolabs/aplomb-1",