From 98efb067a33e9de3d5ca1b8d3bfe182a9380aeb1 Mon Sep 17 00:00:00 2001 From: DennyHo0917 Date: Sun, 6 Sep 2026 14:37:39 +0800 Subject: [PATCH 1/6] feat: add API Route provider support --- litellm/__init__.py | 3 + litellm/_lazy_imports_registry.py | 5 + litellm/constants.py | 1 + .../get_llm_provider_logic.py | 5 + litellm/llms/api_route/chat/transformation.py | 19 ++ .../provider_endpoints_support_backup.json | 17 ++ litellm/types/utils.py | 1 + litellm/utils.py | 1 + provider_endpoints_support.json | 17 ++ .../llms/api_route/test_api_route.py | 177 ++++++++++++++++++ 10 files changed, 246 insertions(+) create mode 100644 litellm/llms/api_route/chat/transformation.py create mode 100644 tests/test_litellm/llms/api_route/test_api_route.py diff --git a/litellm/__init__.py b/litellm/__init__.py index 42c0ea881fd..e48efed4f5b 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -1965,6 +1965,9 @@ if TYPE_CHECKING: from .llms.xai.chat.transformation import XAIChatConfig as XAIChatConfig from .llms.zai.chat.transformation import ZAIChatConfig as ZAIChatConfig from .llms.aiml.chat.transformation import AIMLChatConfig as AIMLChatConfig + from .llms.api_route.chat.transformation import ( + APIRouteChatConfig as APIRouteChatConfig, + ) from .llms.volcengine.chat.transformation import ( VolcEngineChatConfig as VolcEngineChatConfig, VolcEngineChatConfig as VolcEngineConfig, diff --git a/litellm/_lazy_imports_registry.py b/litellm/_lazy_imports_registry.py index e9199e1ec80..0db3b7d4c95 100644 --- a/litellm/_lazy_imports_registry.py +++ b/litellm/_lazy_imports_registry.py @@ -270,6 +270,7 @@ LLM_CONFIG_NAMES: Final = ( "XAIChatConfig", "ZAIChatConfig", "AIMLChatConfig", + "APIRouteChatConfig", "VolcEngineChatConfig", "CodestralTextCompletionConfig", "InceptionTextCompletionConfig", @@ -1057,6 +1058,10 @@ _LLM_CONFIGS_IMPORT_MAP: Final = { "XAIChatConfig": (".llms.xai.chat.transformation", "XAIChatConfig"), "ZAIChatConfig": (".llms.zai.chat.transformation", "ZAIChatConfig"), "AIMLChatConfig": (".llms.aiml.chat.transformation", "AIMLChatConfig"), + "APIRouteChatConfig": ( + ".llms.api_route.chat.transformation", + "APIRouteChatConfig", + ), "VolcEngineChatConfig": ( ".llms.volcengine.chat.transformation", "VolcEngineChatConfig", diff --git a/litellm/constants.py b/litellm/constants.py index ce744e9c58a..097b06df2d2 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -936,6 +936,7 @@ openai_compatible_providers: Final[list] = [ "hyperbolic", "vercel_ai_gateway", "aiml", + "api_route", "wandb", "cometapi", "clarifai", diff --git a/litellm/litellm_core_utils/get_llm_provider_logic.py b/litellm/litellm_core_utils/get_llm_provider_logic.py index ce51fb19970..2f07cdd1a6f 100644 --- a/litellm/litellm_core_utils/get_llm_provider_logic.py +++ b/litellm/litellm_core_utils/get_llm_provider_logic.py @@ -849,6 +849,11 @@ def _get_openai_compatible_provider_info( api_base, dynamic_api_key, ) = litellm.AIMLChatConfig()._get_openai_compatible_provider_info(api_base, api_key) + elif custom_llm_provider == "api_route": + ( + api_base, + dynamic_api_key, + ) = litellm.APIRouteChatConfig()._get_openai_compatible_provider_info(api_base, api_key) elif custom_llm_provider == "wandb": api_base = api_base or get_secret("WANDB_API_BASE") or "https://api.inference.wandb.ai/v1" dynamic_api_key = api_key or get_secret_str("WANDB_API_KEY") diff --git a/litellm/llms/api_route/chat/transformation.py b/litellm/llms/api_route/chat/transformation.py new file mode 100644 index 00000000000..0fa715ad7e3 --- /dev/null +++ b/litellm/llms/api_route/chat/transformation.py @@ -0,0 +1,19 @@ +from typing import Final + +from litellm.llms.openai.chat.gpt_transformation import OpenAIGPTConfig +from litellm.secret_managers.main import get_secret_str + + +class APIRouteChatConfig(OpenAIGPTConfig): + @property + def custom_llm_provider(self) -> str | None: + return "api_route" + + def _get_openai_compatible_provider_info( + self, api_base: str | None, api_key: str | None + ) -> tuple[str | None, str | None]: + resolved_api_base: Final = api_base or get_secret_str("API_ROUTE_BASE_URL") + if not resolved_api_base: + raise ValueError("API Route requires API_ROUTE_BASE_URL or api_base parameter.") + resolved_api_key: Final = api_key or get_secret_str("API_ROUTE_API_KEY") + return resolved_api_base, resolved_api_key diff --git a/litellm/provider_endpoints_support_backup.json b/litellm/provider_endpoints_support_backup.json index dbeaccdda2d..4ac5ee6c3a5 100644 --- a/litellm/provider_endpoints_support_backup.json +++ b/litellm/provider_endpoints_support_backup.json @@ -84,6 +84,23 @@ "interactions": true } }, + "api_route": { + "display_name": "API Route (`api_route`)", + "url": "https://www.api-route.com/docs/overview", + "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 + } + }, "ai21": { "display_name": "AI21 (`ai21`)", "url": "https://docs.litellm.ai/docs/providers/ai21", diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 78ef6edfb19..c57881d4526 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -3949,6 +3949,7 @@ class LlmProviders(str, Enum): STABILITY = "stability" HEROKU = "heroku" AIML = "aiml" + API_ROUTE = "api_route" COMETAPI = "cometapi" OCI = "oci" AUTO_ROUTER = "auto_router" diff --git a/litellm/utils.py b/litellm/utils.py index bc2f4a86f12..7ef33f603a2 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -8072,6 +8072,7 @@ class ProviderConfigManager: LlmProviders.HUGGINGFACE: (lambda: litellm.HuggingFaceChatConfig(), False), LlmProviders.TOGETHER_AI: (lambda: litellm.TogetherAIChatConfig(), False), LlmProviders.OPENROUTER: (lambda: litellm.OpenrouterConfig(), False), + LlmProviders.API_ROUTE: (lambda: litellm.APIRouteChatConfig(), False), LlmProviders.VERCEL_AI_GATEWAY: ( lambda: litellm.VercelAIGatewayConfig(), False, diff --git a/provider_endpoints_support.json b/provider_endpoints_support.json index c71f4a82a4a..2580a75467a 100644 --- a/provider_endpoints_support.json +++ b/provider_endpoints_support.json @@ -84,6 +84,23 @@ "interactions": true } }, + "api_route": { + "display_name": "API Route (`api_route`)", + "url": "https://www.api-route.com/docs/overview", + "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 + } + }, "ai21": { "display_name": "AI21 (`ai21`)", "url": "https://docs.litellm.ai/docs/providers/ai21", diff --git a/tests/test_litellm/llms/api_route/test_api_route.py b/tests/test_litellm/llms/api_route/test_api_route.py new file mode 100644 index 00000000000..137ff500b73 --- /dev/null +++ b/tests/test_litellm/llms/api_route/test_api_route.py @@ -0,0 +1,177 @@ +import json +from unittest.mock import patch + +import httpx +import pytest + +import litellm +from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider +from litellm.llms.api_route.chat.transformation import APIRouteChatConfig +from litellm.llms.openai.openai import OpenAIChatCompletion + + +def test_api_route_provider_routing(): + model, provider, api_key, api_base = get_llm_provider( + model="api_route/model-name", + custom_llm_provider=None, + api_key="test-key", + api_base="https://example.com/v1", + ) + + assert model == "model-name" + assert provider == "api_route" + assert api_key == "test-key" + assert api_base == "https://example.com/v1" + + +def test_api_route_credentials_from_environment(monkeypatch): + monkeypatch.setenv("API_ROUTE_API_KEY", "env-key") + monkeypatch.setenv("API_ROUTE_BASE_URL", "https://env.example.com/v1") + + _, provider, api_key, api_base = get_llm_provider( + model="api_route/model-name", + custom_llm_provider=None, + api_key=None, + api_base=None, + ) + + assert provider == "api_route" + assert api_key == "env-key" + assert api_base == "https://env.example.com/v1" + + +def test_api_route_requires_base_url(monkeypatch): + monkeypatch.delenv("API_ROUTE_BASE_URL", raising=False) + + with pytest.raises( + ValueError, + match=r"API Route requires API_ROUTE_BASE_URL or api_base parameter\.", + ): + APIRouteChatConfig()._get_openai_compatible_provider_info( + api_base=None, + api_key="test-key", + ) + + +def test_api_route_chat_completion_request(monkeypatch): + captured = {} + monkeypatch.setenv("API_ROUTE_BASE_URL", "https://env.example.com/v1") + + def handler(request: httpx.Request) -> httpx.Response: + captured["request"] = request + return httpx.Response( + status_code=200, + json={ + "id": "chatcmpl-test", + "object": "chat.completion", + "created": 1, + "model": "model-name", + "choices": [ + { + "index": 0, + "message": {"role": "assistant", "content": "Hello!"}, + "finish_reason": "stop", + } + ], + "usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2}, + }, + request=request, + ) + + tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get the weather", + "parameters": {"type": "object", "properties": {}}, + }, + } + ] + + with httpx.Client(transport=httpx.MockTransport(handler)) as http_client: + with ( + patch.object( + OpenAIChatCompletion, + "get_cached_openai_client", + return_value=None, + ), + patch.object( + OpenAIChatCompletion, + "set_cached_openai_client", + ), + patch.object( + OpenAIChatCompletion, + "_get_sync_http_client", + return_value=http_client, + ), + ): + response = litellm.completion( + model="api_route/model-name", + messages=[{"role": "user", "content": "Hello!"}], + api_key="test-key", + tools=tools, + ) + + request = captured["request"] + body = json.loads(request.content) + assert str(request.url) == "https://env.example.com/v1/chat/completions" + assert request.headers["Authorization"] == "Bearer test-key" + assert body["model"] == "model-name" + assert body["messages"] == [{"role": "user", "content": "Hello!"}] + assert body["tools"] == tools + assert response.choices[0].message.content == "Hello!" + + +def test_api_route_streaming_request(): + captured = {} + + def handler(request: httpx.Request) -> httpx.Response: + captured["request"] = request + content = """data: {"id":"chatcmpl-stream","object":"chat.completion.chunk","created":1,"model":"model-name","choices":[{"index":0,"delta":{"role":"assistant","content":"Hello"},"finish_reason":null}]} + +data: {"id":"chatcmpl-stream","object":"chat.completion.chunk","created":1,"model":"model-name","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]} + +data: [DONE] + +""" + return httpx.Response( + status_code=200, + headers={"content-type": "text/event-stream"}, + content=content, + request=request, + ) + + with httpx.Client(transport=httpx.MockTransport(handler)) as http_client: + with ( + patch.object( + OpenAIChatCompletion, + "get_cached_openai_client", + return_value=None, + ), + patch.object( + OpenAIChatCompletion, + "set_cached_openai_client", + ), + patch.object( + OpenAIChatCompletion, + "_get_sync_http_client", + return_value=http_client, + ), + ): + response = litellm.completion( + model="api_route/model-name", + messages=[{"role": "user", "content": "Hello!"}], + api_key="test-key", + api_base="https://stream.example.com/v1", + stream=True, + ) + chunks = list(response) + + request = captured["request"] + body = json.loads(request.content) + text = "".join(chunk.choices[0].delta.content or "" for chunk in chunks if chunk.choices) + assert str(request.url) == "https://stream.example.com/v1/chat/completions" + assert body["model"] == "model-name" + assert body["stream"] is True + assert text == "Hello" From faef75a7d3ab4d6ac08b2e595b55fb1fbb1bcbf3 Mon Sep 17 00:00:00 2001 From: DennyHo0917 Date: Sun, 6 Sep 2026 15:05:51 +0800 Subject: [PATCH 2/6] fix: reduce api route test quality budget --- .../llms/api_route/test_api_route.py | 128 ++++-------------- 1 file changed, 24 insertions(+), 104 deletions(-) diff --git a/tests/test_litellm/llms/api_route/test_api_route.py b/tests/test_litellm/llms/api_route/test_api_route.py index 137ff500b73..e2d339e9fff 100644 --- a/tests/test_litellm/llms/api_route/test_api_route.py +++ b/tests/test_litellm/llms/api_route/test_api_route.py @@ -1,13 +1,10 @@ import json -from unittest.mock import patch -import httpx import pytest import litellm from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider from litellm.llms.api_route.chat.transformation import APIRouteChatConfig -from litellm.llms.openai.openai import OpenAIChatCompletion def test_api_route_provider_routing(): @@ -53,30 +50,24 @@ def test_api_route_requires_base_url(monkeypatch): ) -def test_api_route_chat_completion_request(monkeypatch): - captured = {} +def test_api_route_chat_completion_request(monkeypatch, respx_mock): monkeypatch.setenv("API_ROUTE_BASE_URL", "https://env.example.com/v1") - - def handler(request: httpx.Request) -> httpx.Response: - captured["request"] = request - return httpx.Response( - status_code=200, - json={ - "id": "chatcmpl-test", - "object": "chat.completion", - "created": 1, - "model": "model-name", - "choices": [ - { - "index": 0, - "message": {"role": "assistant", "content": "Hello!"}, - "finish_reason": "stop", - } - ], - "usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2}, - }, - request=request, - ) + route = respx_mock.post("https://env.example.com/v1/chat/completions").respond( + json={ + "id": "chatcmpl-test", + "object": "chat.completion", + "created": 1, + "model": "model-name", + "choices": [ + { + "index": 0, + "message": {"role": "assistant", "content": "Hello!"}, + "finish_reason": "stop", + } + ], + "usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2}, + } + ) tools = [ { @@ -89,31 +80,14 @@ def test_api_route_chat_completion_request(monkeypatch): } ] - with httpx.Client(transport=httpx.MockTransport(handler)) as http_client: - with ( - patch.object( - OpenAIChatCompletion, - "get_cached_openai_client", - return_value=None, - ), - patch.object( - OpenAIChatCompletion, - "set_cached_openai_client", - ), - patch.object( - OpenAIChatCompletion, - "_get_sync_http_client", - return_value=http_client, - ), - ): - response = litellm.completion( - model="api_route/model-name", - messages=[{"role": "user", "content": "Hello!"}], - api_key="test-key", - tools=tools, - ) + response = litellm.completion( + model="api_route/model-name", + messages=[{"role": "user", "content": "Hello!"}], + api_key="test-key", + tools=tools, + ) - request = captured["request"] + request = route.calls[0].request body = json.loads(request.content) assert str(request.url) == "https://env.example.com/v1/chat/completions" assert request.headers["Authorization"] == "Bearer test-key" @@ -121,57 +95,3 @@ def test_api_route_chat_completion_request(monkeypatch): assert body["messages"] == [{"role": "user", "content": "Hello!"}] assert body["tools"] == tools assert response.choices[0].message.content == "Hello!" - - -def test_api_route_streaming_request(): - captured = {} - - def handler(request: httpx.Request) -> httpx.Response: - captured["request"] = request - content = """data: {"id":"chatcmpl-stream","object":"chat.completion.chunk","created":1,"model":"model-name","choices":[{"index":0,"delta":{"role":"assistant","content":"Hello"},"finish_reason":null}]} - -data: {"id":"chatcmpl-stream","object":"chat.completion.chunk","created":1,"model":"model-name","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]} - -data: [DONE] - -""" - return httpx.Response( - status_code=200, - headers={"content-type": "text/event-stream"}, - content=content, - request=request, - ) - - with httpx.Client(transport=httpx.MockTransport(handler)) as http_client: - with ( - patch.object( - OpenAIChatCompletion, - "get_cached_openai_client", - return_value=None, - ), - patch.object( - OpenAIChatCompletion, - "set_cached_openai_client", - ), - patch.object( - OpenAIChatCompletion, - "_get_sync_http_client", - return_value=http_client, - ), - ): - response = litellm.completion( - model="api_route/model-name", - messages=[{"role": "user", "content": "Hello!"}], - api_key="test-key", - api_base="https://stream.example.com/v1", - stream=True, - ) - chunks = list(response) - - request = captured["request"] - body = json.loads(request.content) - text = "".join(chunk.choices[0].delta.content or "" for chunk in chunks if chunk.choices) - assert str(request.url) == "https://stream.example.com/v1/chat/completions" - assert body["model"] == "model-name" - assert body["stream"] is True - assert text == "Hello" From 45c0e2f4cc3b3a4ae7f2d015752ba19428716db3 Mon Sep 17 00:00:00 2001 From: DennyHo0917 Date: Sun, 6 Sep 2026 15:22:23 +0800 Subject: [PATCH 3/6] test: improve API Route provider coverage --- .../llms/api_route/test_api_route.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/test_litellm/llms/api_route/test_api_route.py b/tests/test_litellm/llms/api_route/test_api_route.py index e2d339e9fff..b3362047ce0 100644 --- a/tests/test_litellm/llms/api_route/test_api_route.py +++ b/tests/test_litellm/llms/api_route/test_api_route.py @@ -8,6 +8,8 @@ from litellm.llms.api_route.chat.transformation import APIRouteChatConfig def test_api_route_provider_routing(): + assert APIRouteChatConfig().custom_llm_provider == "api_route" + model, provider, api_key, api_base = get_llm_provider( model="api_route/model-name", custom_llm_provider=None, @@ -95,3 +97,25 @@ def test_api_route_chat_completion_request(monkeypatch, respx_mock): assert body["messages"] == [{"role": "user", "content": "Hello!"}] assert body["tools"] == tools assert response.choices[0].message.content == "Hello!" + + +def test_api_route_streaming_request(respx_mock): + route = respx_mock.post("https://stream.example.com/v1/chat/completions").respond( + headers={"content-type": "text/event-stream"}, + text='data: {"choices":[{"delta":{"content":"Hello"},"index":0}]}\n\ndata: [DONE]\n\n', + ) + + chunks = list( + litellm.completion( + model="api_route/model-name", + messages=[{"role": "user", "content": "Hello!"}], + api_key="stream-key", + api_base="https://stream.example.com/v1", + stream=True, + ) + ) + + body = json.loads(route.calls[0].request.content) + assert body["model"] == "model-name" + assert body["stream"] is True + assert "".join(chunk.choices[0].delta.content or "" for chunk in chunks) == "Hello" From 8c608b311a23d723462d07304b414e2de6eb0269 Mon Sep 17 00:00:00 2001 From: DennyHo0917 Date: Sun, 6 Sep 2026 19:45:49 +0800 Subject: [PATCH 4/6] fix(api_route): add default API base --- litellm/llms/api_route/chat/transformation.py | 4 +-- .../llms/api_route/test_api_route.py | 30 ++++++++++++------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/litellm/llms/api_route/chat/transformation.py b/litellm/llms/api_route/chat/transformation.py index 0fa715ad7e3..4b9424dacbc 100644 --- a/litellm/llms/api_route/chat/transformation.py +++ b/litellm/llms/api_route/chat/transformation.py @@ -12,8 +12,6 @@ class APIRouteChatConfig(OpenAIGPTConfig): def _get_openai_compatible_provider_info( self, api_base: str | None, api_key: str | None ) -> tuple[str | None, str | None]: - resolved_api_base: Final = api_base or get_secret_str("API_ROUTE_BASE_URL") - if not resolved_api_base: - raise ValueError("API Route requires API_ROUTE_BASE_URL or api_base parameter.") + resolved_api_base: Final = api_base or get_secret_str("API_ROUTE_BASE_URL") or "https://global.api-route.com/v1" resolved_api_key: Final = api_key or get_secret_str("API_ROUTE_API_KEY") return resolved_api_base, resolved_api_key diff --git a/tests/test_litellm/llms/api_route/test_api_route.py b/tests/test_litellm/llms/api_route/test_api_route.py index b3362047ce0..e476fbfd54b 100644 --- a/tests/test_litellm/llms/api_route/test_api_route.py +++ b/tests/test_litellm/llms/api_route/test_api_route.py @@ -1,7 +1,5 @@ import json -import pytest - import litellm from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider from litellm.llms.api_route.chat.transformation import APIRouteChatConfig @@ -39,17 +37,27 @@ def test_api_route_credentials_from_environment(monkeypatch): assert api_base == "https://env.example.com/v1" -def test_api_route_requires_base_url(monkeypatch): +def test_api_route_uses_default_base_url(monkeypatch): monkeypatch.delenv("API_ROUTE_BASE_URL", raising=False) - with pytest.raises( - ValueError, - match=r"API Route requires API_ROUTE_BASE_URL or api_base parameter\.", - ): - APIRouteChatConfig()._get_openai_compatible_provider_info( - api_base=None, - api_key="test-key", - ) + api_base, api_key = APIRouteChatConfig()._get_openai_compatible_provider_info( + api_base=None, + api_key="test-key", + ) + + assert api_base == "https://global.api-route.com/v1" + assert api_key == "test-key" + + +def test_api_route_explicit_api_base_overrides_default(monkeypatch): + monkeypatch.delenv("API_ROUTE_BASE_URL", raising=False) + + api_base, _ = APIRouteChatConfig()._get_openai_compatible_provider_info( + api_base="https://custom.example.com/v1", + api_key="test-key", + ) + + assert api_base == "https://custom.example.com/v1" def test_api_route_chat_completion_request(monkeypatch, respx_mock): From 6f46550c183dc40b12a8913260339852c8b825f4 Mon Sep 17 00:00:00 2001 From: DennyHo0917 Date: Mon, 7 Sep 2026 18:00:51 +0800 Subject: [PATCH 5/6] test: avoid private API route config access --- tests/test_litellm/llms/api_route/test_api_route.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/test_litellm/llms/api_route/test_api_route.py b/tests/test_litellm/llms/api_route/test_api_route.py index e476fbfd54b..218f0590711 100644 --- a/tests/test_litellm/llms/api_route/test_api_route.py +++ b/tests/test_litellm/llms/api_route/test_api_route.py @@ -40,9 +40,11 @@ def test_api_route_credentials_from_environment(monkeypatch): def test_api_route_uses_default_base_url(monkeypatch): monkeypatch.delenv("API_ROUTE_BASE_URL", raising=False) - api_base, api_key = APIRouteChatConfig()._get_openai_compatible_provider_info( - api_base=None, + _, _, api_key, api_base = get_llm_provider( + model="api_route/model-name", + custom_llm_provider=None, api_key="test-key", + api_base=None, ) assert api_base == "https://global.api-route.com/v1" @@ -52,9 +54,11 @@ def test_api_route_uses_default_base_url(monkeypatch): def test_api_route_explicit_api_base_overrides_default(monkeypatch): monkeypatch.delenv("API_ROUTE_BASE_URL", raising=False) - api_base, _ = APIRouteChatConfig()._get_openai_compatible_provider_info( - api_base="https://custom.example.com/v1", + _, _, _, api_base = get_llm_provider( + model="api_route/model-name", + custom_llm_provider=None, api_key="test-key", + api_base="https://custom.example.com/v1", ) assert api_base == "https://custom.example.com/v1" From ef4e641a0a9974855932b67bd0c85d646f5f4548 Mon Sep 17 00:00:00 2001 From: DennyHo0917 Date: Mon, 7 Sep 2026 20:12:06 +0800 Subject: [PATCH 6/6] fix(api_route): expose provider info helper --- litellm/litellm_core_utils/get_llm_provider_logic.py | 2 +- litellm/llms/api_route/chat/transformation.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/litellm/litellm_core_utils/get_llm_provider_logic.py b/litellm/litellm_core_utils/get_llm_provider_logic.py index 2f07cdd1a6f..eb81eff78fc 100644 --- a/litellm/litellm_core_utils/get_llm_provider_logic.py +++ b/litellm/litellm_core_utils/get_llm_provider_logic.py @@ -853,7 +853,7 @@ def _get_openai_compatible_provider_info( ( api_base, dynamic_api_key, - ) = litellm.APIRouteChatConfig()._get_openai_compatible_provider_info(api_base, api_key) + ) = litellm.APIRouteChatConfig().get_openai_compatible_provider_info(api_base, api_key) elif custom_llm_provider == "wandb": api_base = api_base or get_secret("WANDB_API_BASE") or "https://api.inference.wandb.ai/v1" dynamic_api_key = api_key or get_secret_str("WANDB_API_KEY") diff --git a/litellm/llms/api_route/chat/transformation.py b/litellm/llms/api_route/chat/transformation.py index 4b9424dacbc..b19a14636cf 100644 --- a/litellm/llms/api_route/chat/transformation.py +++ b/litellm/llms/api_route/chat/transformation.py @@ -9,7 +9,7 @@ class APIRouteChatConfig(OpenAIGPTConfig): def custom_llm_provider(self) -> str | None: return "api_route" - def _get_openai_compatible_provider_info( + def get_openai_compatible_provider_info( self, api_base: str | None, api_key: str | None ) -> tuple[str | None, str | None]: resolved_api_base: Final = api_base or get_secret_str("API_ROUTE_BASE_URL") or "https://global.api-route.com/v1"