From 1ca87aa4cca1360d9b42a044e38887386bebf204 Mon Sep 17 00:00:00 2001 From: Aditya Aggarwal <42476079+aditya-786@users.noreply.github.com> Date: Sun, 28 Jun 2026 17:33:55 +0530 Subject: [PATCH 1/6] fix: enable model discovery for the custom_openai provider get_valid_models(check_provider_endpoint=True) discovers a provider's models via ProviderConfigManager.get_provider_model_info(provider).get_models(...), but that method returned None for custom_openai, so the discovery branch was skipped and the /v1/models endpoint was never queried for an OpenAI-compatible custom provider. Return OpenAIGPTConfig for CUSTOM_OPENAI, the same config used for openai, so custom_openai discovers models like openai does. --- litellm/utils.py | 2 ++ tests/litellm_utils_tests/test_utils.py | 30 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/litellm/utils.py b/litellm/utils.py index 5dbff5070aa..4bbaba83d2f 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -8358,6 +8358,8 @@ class ProviderConfigManager: return litellm.FireworksAIConfig() elif LlmProviders.OPENAI == provider: return litellm.OpenAIGPTConfig() + elif LlmProviders.CUSTOM_OPENAI == provider: + return litellm.OpenAIGPTConfig() elif LlmProviders.GEMINI == provider: return litellm.GeminiModelInfo() elif LlmProviders.VERTEX_AI == provider: diff --git a/tests/litellm_utils_tests/test_utils.py b/tests/litellm_utils_tests/test_utils.py index 697c3837602..be5d1fc324e 100644 --- a/tests/litellm_utils_tests/test_utils.py +++ b/tests/litellm_utils_tests/test_utils.py @@ -1656,6 +1656,36 @@ def test_get_valid_models_openai_proxy(monkeypatch): assert "litellm_proxy/gpt-5.5" in valid_models +def test_get_valid_models_custom_openai(monkeypatch): + from litellm.utils import get_valid_models + import litellm + + mock_response_data = { + "object": "list", + "data": [ + { + "id": "my-custom-model", + "object": "model", + "created": 1686935002, + "owned_by": "organization-owner", + }, + ], + } + + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = mock_response_data + + with patch.object(litellm.module_level_client, "get", return_value=mock_response): + valid_models = get_valid_models( + check_provider_endpoint=True, + custom_llm_provider="custom_openai", + api_key="sk-1234", + api_base="https://my-openai-compatible-endpoint/v1", + ) + assert "my-custom-model" in valid_models + + def test_get_valid_models_fireworks_ai(monkeypatch): from litellm.utils import get_valid_models import litellm From 8aea8c46203d0e9ed0850ed60fd5b83f9f588051 Mon Sep 17 00:00:00 2001 From: Aditya Aggarwal <42476079+aditya-786@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:53:09 +0530 Subject: [PATCH 2/6] fix(custom_openai): require api_base and prefix discovered model ids --- litellm/llms/openai_like/chat/transformation.py | 8 ++++++++ litellm/utils.py | 6 +++++- tests/litellm_utils_tests/test_utils.py | 9 ++++++++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/litellm/llms/openai_like/chat/transformation.py b/litellm/llms/openai_like/chat/transformation.py index 895d5a99971..96a6c3172ea 100644 --- a/litellm/llms/openai_like/chat/transformation.py +++ b/litellm/llms/openai_like/chat/transformation.py @@ -166,3 +166,11 @@ class OpenAILikeChatConfig(OpenAIGPTConfig): mapped_params.pop("max_completion_tokens", None) return mapped_params + + +class CustomOpenAIChatConfig(OpenAILikeChatConfig): + def get_models(self, api_key: Optional[str] = None, api_base: Optional[str] = None) -> List[str]: + if api_base is None: + raise ValueError("api_base must be set to discover models for the custom_openai provider") + models = super().get_models(api_key=api_key, api_base=api_base) + return [f"custom_openai/{model}" for model in models] diff --git a/litellm/utils.py b/litellm/utils.py index 4bbaba83d2f..02a14c7ad56 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -8359,7 +8359,11 @@ class ProviderConfigManager: elif LlmProviders.OPENAI == provider: return litellm.OpenAIGPTConfig() elif LlmProviders.CUSTOM_OPENAI == provider: - return litellm.OpenAIGPTConfig() + from litellm.llms.openai_like.chat.transformation import ( + CustomOpenAIChatConfig, + ) + + return CustomOpenAIChatConfig() elif LlmProviders.GEMINI == provider: return litellm.GeminiModelInfo() elif LlmProviders.VERTEX_AI == provider: diff --git a/tests/litellm_utils_tests/test_utils.py b/tests/litellm_utils_tests/test_utils.py index be5d1fc324e..71e743f2dee 100644 --- a/tests/litellm_utils_tests/test_utils.py +++ b/tests/litellm_utils_tests/test_utils.py @@ -1683,7 +1683,14 @@ def test_get_valid_models_custom_openai(monkeypatch): api_key="sk-1234", api_base="https://my-openai-compatible-endpoint/v1", ) - assert "my-custom-model" in valid_models + assert "custom_openai/my-custom-model" in valid_models + + +def test_custom_openai_get_models_requires_api_base(): + from litellm.llms.openai_like.chat.transformation import CustomOpenAIChatConfig + + with pytest.raises(ValueError): + CustomOpenAIChatConfig().get_models(api_base=None) def test_get_valid_models_fireworks_ai(monkeypatch): From cf1445448b1fb8f47c9bc553012943a288ed3103 Mon Sep 17 00:00:00 2001 From: Aditya Aggarwal <42476079+aditya-786@users.noreply.github.com> Date: Fri, 3 Jul 2026 10:40:48 +0530 Subject: [PATCH 3/6] refactor(custom_openai): use list[str] and a shared openai-family model-info branch to satisfy the strict lint budget --- .../llms/openai_like/chat/transformation.py | 2 +- litellm/utils.py | 20 +++++++++++-------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/litellm/llms/openai_like/chat/transformation.py b/litellm/llms/openai_like/chat/transformation.py index 96a6c3172ea..5b406fea70f 100644 --- a/litellm/llms/openai_like/chat/transformation.py +++ b/litellm/llms/openai_like/chat/transformation.py @@ -169,7 +169,7 @@ class OpenAILikeChatConfig(OpenAIGPTConfig): class CustomOpenAIChatConfig(OpenAILikeChatConfig): - def get_models(self, api_key: Optional[str] = None, api_base: Optional[str] = None) -> List[str]: + def get_models(self, api_key: Optional[str] = None, api_base: Optional[str] = None) -> list[str]: if api_base is None: raise ValueError("api_base must be set to discover models for the custom_openai provider") models = super().get_models(api_key=api_key, api_base=api_base) diff --git a/litellm/utils.py b/litellm/utils.py index 02a14c7ad56..d3cce47a7de 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -8349,6 +8349,16 @@ class ProviderConfigManager: return litellm.InceptionTextCompletionConfig() return litellm.OpenAITextCompletionConfig() + @staticmethod + def _openai_family_model_info(provider: LlmProviders) -> BaseLLMModelInfo: + if provider == LlmProviders.CUSTOM_OPENAI: + from litellm.llms.openai_like.chat.transformation import ( + CustomOpenAIChatConfig, + ) + + return CustomOpenAIChatConfig() + return litellm.OpenAIGPTConfig() + @staticmethod def get_provider_model_info( model: str | None, @@ -8356,14 +8366,8 @@ class ProviderConfigManager: ) -> BaseLLMModelInfo | None: if LlmProviders.FIREWORKS_AI == provider: return litellm.FireworksAIConfig() - elif LlmProviders.OPENAI == provider: - return litellm.OpenAIGPTConfig() - elif LlmProviders.CUSTOM_OPENAI == provider: - from litellm.llms.openai_like.chat.transformation import ( - CustomOpenAIChatConfig, - ) - - return CustomOpenAIChatConfig() + elif provider in (LlmProviders.OPENAI, LlmProviders.CUSTOM_OPENAI): + return ProviderConfigManager._openai_family_model_info(provider) elif LlmProviders.GEMINI == provider: return litellm.GeminiModelInfo() elif LlmProviders.VERTEX_AI == provider: From db61d672b1a9ea2c560ec08531b88a92c1d08885 Mon Sep 17 00:00:00 2001 From: Aditya Aggarwal <42476079+aditya-786@users.noreply.github.com> Date: Sun, 26 Jul 2026 16:09:54 +0530 Subject: [PATCH 4/6] test(custom_openai): cover model discovery branches --- tests/litellm_utils_tests/test_utils.py | 36 ------------ .../test_openai_like_chat_transformation.py | 58 ++++++++++++++++++- 2 files changed, 57 insertions(+), 37 deletions(-) diff --git a/tests/litellm_utils_tests/test_utils.py b/tests/litellm_utils_tests/test_utils.py index 71e743f2dee..8f38dbe1107 100644 --- a/tests/litellm_utils_tests/test_utils.py +++ b/tests/litellm_utils_tests/test_utils.py @@ -1656,42 +1656,6 @@ def test_get_valid_models_openai_proxy(monkeypatch): assert "litellm_proxy/gpt-5.5" in valid_models -def test_get_valid_models_custom_openai(monkeypatch): - from litellm.utils import get_valid_models - import litellm - - mock_response_data = { - "object": "list", - "data": [ - { - "id": "my-custom-model", - "object": "model", - "created": 1686935002, - "owned_by": "organization-owner", - }, - ], - } - - mock_response = MagicMock() - mock_response.status_code = 200 - mock_response.json.return_value = mock_response_data - - with patch.object(litellm.module_level_client, "get", return_value=mock_response): - valid_models = get_valid_models( - check_provider_endpoint=True, - custom_llm_provider="custom_openai", - api_key="sk-1234", - api_base="https://my-openai-compatible-endpoint/v1", - ) - assert "custom_openai/my-custom-model" in valid_models - - -def test_custom_openai_get_models_requires_api_base(): - from litellm.llms.openai_like.chat.transformation import CustomOpenAIChatConfig - - with pytest.raises(ValueError): - CustomOpenAIChatConfig().get_models(api_base=None) - def test_get_valid_models_fireworks_ai(monkeypatch): from litellm.utils import get_valid_models diff --git a/tests/test_litellm/llms/openai_like/chat/test_openai_like_chat_transformation.py b/tests/test_litellm/llms/openai_like/chat/test_openai_like_chat_transformation.py index 88382b80d16..1fc2c52bdd5 100644 --- a/tests/test_litellm/llms/openai_like/chat/test_openai_like_chat_transformation.py +++ b/tests/test_litellm/llms/openai_like/chat/test_openai_like_chat_transformation.py @@ -1,5 +1,14 @@ +from unittest.mock import MagicMock, patch + +import litellm import pytest -from litellm.llms.openai_like.chat.transformation import OpenAILikeChatConfig +from litellm.llms.openai_like.chat.transformation import ( + CustomOpenAIChatConfig, + OpenAILikeChatConfig, +) +from litellm.llms.openai.chat.gpt_transformation import OpenAIGPTConfig +from litellm.types.utils import LlmProviders +from litellm.utils import ProviderConfigManager, get_valid_models def test_sanitize_usage_obj_handles_null_tokens(): @@ -47,3 +56,50 @@ def test_sanitize_usage_obj_valid_usage(): # Assert assert sanitized_json == original_json # The object should be unchanged + + +def test_get_valid_models_custom_openai(): + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = { + "object": "list", + "data": [ + { + "id": "my-custom-model", + "object": "model", + "created": 1686935002, + "owned_by": "organization-owner", + }, + ], + } + + with patch.object(litellm.module_level_client, "get", return_value=mock_response) as mock_get: + valid_models = get_valid_models( + check_provider_endpoint=True, + custom_llm_provider="custom_openai", + api_key="sk-1234", + api_base="https://my-openai-compatible-endpoint/v1", + ) + + assert valid_models == ["custom_openai/my-custom-model"] + mock_get.assert_called_once_with( + url="https://my-openai-compatible-endpoint/v1/models", + headers={"Authorization": "Bearer sk-1234"}, + ) + + +def test_custom_openai_get_models_requires_api_base(): + with pytest.raises( + ValueError, + match="api_base must be set to discover models for the custom_openai provider", + ): + CustomOpenAIChatConfig().get_models(api_base=None) + + +def test_openai_model_info_uses_openai_config(): + config = ProviderConfigManager.get_provider_model_info( + model=None, + provider=LlmProviders.OPENAI, + ) + + assert type(config) is OpenAIGPTConfig From 44062f382b56863421dba4e73024d7bff241be39 Mon Sep 17 00:00:00 2001 From: Aditya Aggarwal <42476079+aditya-786@users.noreply.github.com> Date: Mon, 3 Aug 2026 21:57:28 +0530 Subject: [PATCH 5/6] fix(custom-openai): avoid OpenAI API key fallback during discovery --- .../llms/openai_like/chat/transformation.py | 2 +- .../test_openai_like_chat_transformation.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/litellm/llms/openai_like/chat/transformation.py b/litellm/llms/openai_like/chat/transformation.py index 5b406fea70f..cea1713ca61 100644 --- a/litellm/llms/openai_like/chat/transformation.py +++ b/litellm/llms/openai_like/chat/transformation.py @@ -172,5 +172,5 @@ class CustomOpenAIChatConfig(OpenAILikeChatConfig): def get_models(self, api_key: Optional[str] = None, api_base: Optional[str] = None) -> list[str]: if api_base is None: raise ValueError("api_base must be set to discover models for the custom_openai provider") - models = super().get_models(api_key=api_key, api_base=api_base) + models = super().get_models(api_key=api_key or "", api_base=api_base) return [f"custom_openai/{model}" for model in models] diff --git a/tests/test_litellm/llms/openai_like/chat/test_openai_like_chat_transformation.py b/tests/test_litellm/llms/openai_like/chat/test_openai_like_chat_transformation.py index 1fc2c52bdd5..e7c71ec91be 100644 --- a/tests/test_litellm/llms/openai_like/chat/test_openai_like_chat_transformation.py +++ b/tests/test_litellm/llms/openai_like/chat/test_openai_like_chat_transformation.py @@ -96,6 +96,24 @@ def test_custom_openai_get_models_requires_api_base(): CustomOpenAIChatConfig().get_models(api_base=None) +def test_custom_openai_get_models_does_not_use_openai_api_key(monkeypatch): + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = {"data": [{"id": "my-custom-model"}]} + monkeypatch.setenv("OPENAI_API_KEY", "unrelated-openai-api-key") + + with patch.object(litellm.module_level_client, "get", return_value=mock_response) as mock_get: + models = CustomOpenAIChatConfig().get_models( + api_base="https://my-openai-compatible-endpoint/v1" + ) + + assert models == ["custom_openai/my-custom-model"] + mock_get.assert_called_once_with( + url="https://my-openai-compatible-endpoint/v1/models", + headers={"Authorization": "Bearer "}, + ) + + def test_openai_model_info_uses_openai_config(): config = ProviderConfigManager.get_provider_model_info( model=None, From ecb74d491e3c878541e1e7e76669cfc240eae983 Mon Sep 17 00:00:00 2001 From: Aditya Aggarwal <42476079+aditya-786@users.noreply.github.com> Date: Tue, 4 Aug 2026 19:42:09 +0530 Subject: [PATCH 6/6] fix(custom-openai): align discovery annotations with staging --- litellm/llms/openai_like/chat/transformation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/llms/openai_like/chat/transformation.py b/litellm/llms/openai_like/chat/transformation.py index cea1713ca61..36b78a74347 100644 --- a/litellm/llms/openai_like/chat/transformation.py +++ b/litellm/llms/openai_like/chat/transformation.py @@ -169,7 +169,7 @@ class OpenAILikeChatConfig(OpenAIGPTConfig): class CustomOpenAIChatConfig(OpenAILikeChatConfig): - def get_models(self, api_key: Optional[str] = None, api_base: Optional[str] = None) -> list[str]: + def get_models(self, api_key: str | None = None, api_base: str | None = None) -> list[str]: if api_base is None: raise ValueError("api_base must be set to discover models for the custom_openai provider") models = super().get_models(api_key=api_key or "", api_base=api_base)