diff --git a/litellm/litellm_core_utils/health_check_helpers.py b/litellm/litellm_core_utils/health_check_helpers.py index b19bcd941fa..3977e461f99 100644 --- a/litellm/litellm_core_utils/health_check_helpers.py +++ b/litellm/litellm_core_utils/health_check_helpers.py @@ -307,7 +307,9 @@ class HealthCheckHelpers: ), "responses": lambda: litellm.aresponses( **_filter_model_params(model_params=model_params), - input=prompt or "test", + input=[ + {"role": "user", "content": prompt or "test"}, + ], ), "anthropic_messages": lambda: litellm.anthropic_messages( **{ diff --git a/litellm/llms/base_llm/chat/transformation.py b/litellm/llms/base_llm/chat/transformation.py index b2eb4cde5ce..c13b588e0c6 100644 --- a/litellm/llms/base_llm/chat/transformation.py +++ b/litellm/llms/base_llm/chat/transformation.py @@ -92,6 +92,9 @@ class BaseConfig(ABC): def __init__(self): pass + def get_health_check_mode(self) -> str | None: + return None + @classmethod def get_config(cls): return { diff --git a/litellm/llms/chatgpt/chat/transformation.py b/litellm/llms/chatgpt/chat/transformation.py index a1aac7f315b..193897b946d 100644 --- a/litellm/llms/chatgpt/chat/transformation.py +++ b/litellm/llms/chatgpt/chat/transformation.py @@ -26,6 +26,9 @@ class ChatGPTConfig(OpenAIConfig): def api_base_without_login(self) -> str: return self.authenticator.get_api_base() + def get_health_check_mode(self) -> str: + return "responses" + def _get_openai_compatible_provider_info( self, model: str, diff --git a/litellm/main.py b/litellm/main.py index 27700cf9def..84fb4cafb9c 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -9100,8 +9100,16 @@ async def ahealth_check( api_base=api_base_from_params, api_key=api_key_from_params, ) - model_params["cache"] = {"no-cache": True} # don't used cached responses for making health check calls + if mode is None and custom_llm_provider in {provider.value for provider in LlmProviders}: + provider_config: Final = ProviderConfigManager.get_provider_chat_config( + model=model, + provider=LlmProviders(custom_llm_provider), + ) + if provider_config is not None: + mode = ( # rebind-ok: resolve provider-config default + provider_config.get_health_check_mode() + ) mode = mode or default_health_check_mode( requested_model=requested_model, model=model, custom_llm_provider=custom_llm_provider ) diff --git a/tests/unit/litellm_core_utils/test_health_check_helpers.py b/tests/unit/litellm_core_utils/test_health_check_helpers.py index b584a72855a..0dc71b0681f 100644 --- a/tests/unit/litellm_core_utils/test_health_check_helpers.py +++ b/tests/unit/litellm_core_utils/test_health_check_helpers.py @@ -150,6 +150,68 @@ async def test_ahealth_check_supports_image_edit_mode(): assert "error" not in result assert "Mode image_edit not supported" not in str(result) +@pytest.mark.asyncio +async def test_ahealth_check_uses_provider_mode_and_responses_message_input(): + mock_response = MagicMock() + mock_response._hidden_params = {} + mock_aresponses = AsyncMock(return_value=mock_response) + mock_acompletion = AsyncMock() + + with ( + patch( # test-quality-ok: isolate provider and handler calls for focused health-check routing + "litellm.main.get_llm_provider", + return_value=("gpt-5.6-sol", "chatgpt", None, None), + ), + patch.object( # test-quality-ok: isolate health-check metadata plumbing for routing assertion + HealthCheckHelpers, + "_update_model_params_with_health_check_tracking_information", + side_effect=lambda model_params: model_params, + ), + patch("litellm.aresponses", new=mock_aresponses), # test-quality-ok: verify provider-mode dispatch + patch("litellm.acompletion", new=mock_acompletion), # test-quality-ok: verify chat fallback dispatch + ): + result = await ahealth_check( + model_params={"model": "chatgpt/gpt-5.6-sol"}, + mode=None, + prompt="health check", + ) + + assert "error" not in result + mock_aresponses.assert_awaited_once() + assert mock_aresponses.await_args.kwargs["input"] == [ + {"role": "user", "content": "health check"} + ] + mock_acompletion.assert_not_awaited() + + +@pytest.mark.asyncio +async def test_ahealth_check_provider_without_default_mode_keeps_chat(): + mock_response = MagicMock() + mock_response._hidden_params = {} + mock_aresponses = AsyncMock() + mock_acompletion = AsyncMock(return_value=mock_response) + + with ( + patch( # test-quality-ok: isolate provider and handler calls for focused health-check routing + "litellm.main.get_llm_provider", + return_value=("custom-model", "custom", None, None), + ), + patch.object( # test-quality-ok: isolate health-check metadata plumbing for routing assertion + HealthCheckHelpers, + "_update_model_params_with_health_check_tracking_information", + side_effect=lambda model_params: model_params, + ), + patch("litellm.aresponses", new=mock_aresponses), # test-quality-ok: verify provider-mode dispatch + patch("litellm.acompletion", new=mock_acompletion), # test-quality-ok: verify chat fallback dispatch + ): + result = await ahealth_check( + model_params={"model": "custom/custom-model"}, + mode=None, + ) + + assert "error" not in result + mock_acompletion.assert_awaited_once() + mock_aresponses.assert_not_awaited() @pytest.mark.asyncio @@ -469,7 +531,7 @@ async def test_realtime_health_check_uses_model_level_vertex_params(): "websockets.connect", lambda url, **kwargs: _FakeWebsocketConnect(connect_calls, url, **kwargs), ), - patch.object( + patch.object( # test-quality-ok: isolate health-check metadata plumbing for routing assertion HealthCheckHelpers, "update_model_params_with_health_check_tracking_information", staticmethod(lambda model_params: model_params),