From de817fb1c5b788ca0ce0bb3c36e6d5b7f7b91202 Mon Sep 17 00:00:00 2001 From: 45TR10N <288678733+45TR10N@users.noreply.github.com> Date: Wed, 7 Oct 2026 11:55:54 +0200 Subject: [PATCH] feat: add LLM Broker as JSON-configured OpenAI-compatible provider - providers.json entry (LLM_BROKER_API_KEY, LLM_BROKER_API_BASE) - LlmProviders.LLM_BROKER, OpenAI-compatible endpoint/provider lists - endpoint support matrix: chat completions only - Add Model form entry (api_key) - tests: prefix resolution, explicit key/base priority, env base override, api_base autodetection keeps the caller key, support matrix Co-Authored-By: claude-flow --- litellm/constants.py | 3 + litellm/llms/openai_like/providers.json | 5 ++ .../provider_endpoints_support_backup.json | 17 ++++ .../provider_create_fields.json | 18 +++++ litellm/types/utils.py | 1 + provider_endpoints_support.json | 17 ++++ .../openai_like/test_llm_broker_provider.py | 80 +++++++++++++++++++ 7 files changed, 141 insertions(+) create mode 100644 tests/unit/llms/openai_like/test_llm_broker_provider.py diff --git a/litellm/constants.py b/litellm/constants.py index 6343c0675e2..2c0014f55ee 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -951,6 +951,7 @@ openai_compatible_endpoints: Final[list] = [ "https://api.stima.tech/v1", "https://nano-gpt.com/api/v1", "https://api.poe.com/v1", + "https://api.llm-broker.net/api/v1", "https://llm.chutes.ai/v1/", "https://api.v0.dev/v1", "https://api.morphllm.com/v1", @@ -1014,6 +1015,7 @@ openai_compatible_providers: Final[list] = [ "apertis", # Apertis - JSON-configured provider "nano-gpt", # Nano-GPT - JSON-configured provider "poe", # Poe - JSON-configured provider + "llm_broker", "chutes", # Chutes - JSON-configured provider "parasail", # Parasail - JSON-configured provider "libertai", # LibertAI - JSON-configured provider @@ -1071,6 +1073,7 @@ openai_text_completion_compatible_providers: Final[list] = [ # providers that s "apertis", "nano-gpt", "poe", + "llm_broker", "chutes", "v0", "lambda_ai", diff --git a/litellm/llms/openai_like/providers.json b/litellm/llms/openai_like/providers.json index f3aa7768928..994fbfdeecb 100644 --- a/litellm/llms/openai_like/providers.json +++ b/litellm/llms/openai_like/providers.json @@ -51,6 +51,11 @@ "max_completion_tokens": "max_tokens" } }, + "llm_broker": { + "base_url": "https://api.llm-broker.net/api/v1", + "api_key_env": "LLM_BROKER_API_KEY", + "api_base_env": "LLM_BROKER_API_BASE" + }, "poe": { "base_url": "https://api.poe.com/v1", "api_key_env": "POE_API_KEY", diff --git a/litellm/provider_endpoints_support_backup.json b/litellm/provider_endpoints_support_backup.json index 0e325bb61fe..18372b4b33f 100644 --- a/litellm/provider_endpoints_support_backup.json +++ b/litellm/provider_endpoints_support_backup.json @@ -1908,6 +1908,23 @@ "a2a": false } }, + "llm_broker": { + "display_name": "LLM Broker (`llm_broker`)", + "url": "https://llm-broker.net/quickstart", + "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 + } + }, "publicai": { "display_name": "PublicAI (`publicai`)", "url": "https://docs.litellm.ai/docs/providers/publicai", diff --git a/litellm/proxy/public_endpoints/provider_create_fields.json b/litellm/proxy/public_endpoints/provider_create_fields.json index 2dd9f50bec8..6ad03c39a92 100644 --- a/litellm/proxy/public_endpoints/provider_create_fields.json +++ b/litellm/proxy/public_endpoints/provider_create_fields.json @@ -2023,6 +2023,24 @@ ], "default_model_placeholder": "gpt-3.5-turbo" }, + { + "provider": "LLM_BROKER", + "provider_display_name": "LLM Broker", + "litellm_provider": "llm_broker", + "credential_fields": [ + { + "key": "api_key", + "label": "API Key", + "placeholder": null, + "tooltip": null, + "required": true, + "field_type": "password", + "options": null, + "default_value": null + } + ], + "default_model_placeholder": "llm_broker/taylor" + }, { "provider": "LM_STUDIO", "provider_display_name": "Lm Studio", diff --git a/litellm/types/utils.py b/litellm/types/utils.py index f8f8388b6f7..6e50c8b6c54 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -4235,6 +4235,7 @@ class LlmProviders(str, Enum): APERTIS = "apertis" NANOGPT = "nano-gpt" POE = "poe" + LLM_BROKER = "llm_broker" CHUTES = "chutes" NEOSANTARA = "neosantara" PARASAIL = "parasail" diff --git a/provider_endpoints_support.json b/provider_endpoints_support.json index 00904219b81..75103a053c2 100644 --- a/provider_endpoints_support.json +++ b/provider_endpoints_support.json @@ -2175,6 +2175,23 @@ "a2a": false } }, + "llm_broker": { + "display_name": "LLM Broker (`llm_broker`)", + "url": "https://llm-broker.net/quickstart", + "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 + } + }, "publicai": { "display_name": "PublicAI (`publicai`)", "url": "https://docs.litellm.ai/docs/providers/publicai", diff --git a/tests/unit/llms/openai_like/test_llm_broker_provider.py b/tests/unit/llms/openai_like/test_llm_broker_provider.py new file mode 100644 index 00000000000..e6501889714 --- /dev/null +++ b/tests/unit/llms/openai_like/test_llm_broker_provider.py @@ -0,0 +1,80 @@ +""" +Tests for the LLM Broker provider configuration. +""" + +import json +import os + +import litellm + + +class TestLLMBrokerProviderConfig: + def test_in_provider_list(self): + from litellm import LlmProviders + + assert LlmProviders.LLM_BROKER.value == "llm_broker" + assert "llm_broker" in litellm.provider_list + + def test_json_config(self): + from litellm.llms.openai_like.json_loader import JSONProviderRegistry + + cfg = JSONProviderRegistry.get("llm_broker") + assert cfg is not None + assert cfg.base_url == "https://api.llm-broker.net/api/v1" + assert cfg.api_key_env == "LLM_BROKER_API_KEY" + assert cfg.api_base_env == "LLM_BROKER_API_BASE" + + def test_prefix_resolution(self, monkeypatch): + from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider + + monkeypatch.setenv("LLM_BROKER_API_KEY", "env-key") + monkeypatch.delenv("LLM_BROKER_API_BASE", raising=False) + model, provider, api_key, api_base = get_llm_provider(model="llm_broker/taylor") + + assert (model, provider) == ("taylor", "llm_broker") + assert api_base == "https://api.llm-broker.net/api/v1" + assert api_key == "env-key" + + def test_explicit_key_and_base_win_over_env(self, monkeypatch): + from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider + + monkeypatch.setenv("LLM_BROKER_API_KEY", "env-key") + _, provider, api_key, api_base = get_llm_provider( + model="llm_broker/taylor", + api_base="https://custom.example.com/v1", + api_key="caller-key", + ) + + assert provider == "llm_broker" + assert api_key == "caller-key" + assert api_base == "https://custom.example.com/v1" + + def test_api_base_env_override(self, monkeypatch): + from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider + + monkeypatch.setenv("LLM_BROKER_API_BASE", "https://eu.example.com/v1") + _, _, _, api_base = get_llm_provider(model="llm_broker/taylor") + + assert api_base == "https://eu.example.com/v1" + + def test_api_base_autodetection_keeps_caller_key(self, monkeypatch): + from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider + + monkeypatch.setenv("LLM_BROKER_API_KEY", "env-key") + _, provider, api_key, _ = get_llm_provider( + model="taylor", + api_base="https://api.llm-broker.net/api/v1", + api_key="caller-key", + ) + + assert provider == "llm_broker" + assert api_key == "caller-key" + + def test_support_matrix_lists_chat_only(self): + root = os.path.dirname(os.path.dirname(litellm.__file__)) + with open(os.path.join(root, "provider_endpoints_support.json")) as f: + providers = json.load(f)["providers"] + + endpoints = providers["llm_broker"]["endpoints"] + assert endpoints["chat_completions"] is True + assert not any(v for k, v in endpoints.items() if k != "chat_completions")