diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 18714256a8f..f3d052c384a 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -2526,6 +2526,10 @@ class ConfigGeneralSettings(LiteLLMPydanticObjectBase): description="for `/models` endpoint, infers available model based on environment keys (e.g. OPENAI_API_KEY)", ) background_health_checks: bool | None = Field(None, description="run health checks in background") + persist_background_health_check_results: bool = Field( + True, + description="persist background health check results to the database", + ) health_check_interval: int = Field(300, description="background health check interval in seconds") health_check_concurrency: int | None = Field( None, diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 77a80ea0052..b3cf74b8c17 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -3580,9 +3580,10 @@ def _schedule_background_health_check_db_save( model_list: list, healthy_endpoints: list, unhealthy_endpoints: list, + persist_results: bool = True, ): """Fire-and-forget: persist health check results to DB if prisma is available.""" - if prisma_client is None: + if prisma_client is None or not persist_results: return import time as time_module @@ -3764,11 +3765,12 @@ async def _run_background_health_check(): ) background_health_check_loop_active = True verbose_proxy_logger.info( - "background_health_check_loop_started interval_seconds=%s max_concurrency=%s shared=%s details=%s thread_count=%d rss_mb=%s", + "background_health_check_loop_started interval_seconds=%s max_concurrency=%s shared=%s details=%s persist_results=%s thread_count=%d rss_mb=%s", health_check_interval, health_check_concurrency, use_shared_health_check, health_check_details, + general_settings.get("persist_background_health_check_results", True), threading.active_count(), _rss_mb_for_log(), ) @@ -3908,6 +3910,7 @@ async def _run_background_health_check(): _llm_model_list, healthy_endpoints, unhealthy_endpoints, + persist_results=general_settings.get("persist_background_health_check_results", True), ) # Write health state to router cache for health-check-driven routing diff --git a/tests/test_litellm/proxy/proxy_server/test_background_health.py b/tests/test_litellm/proxy/proxy_server/test_background_health.py index 990844369f7..c270c899b11 100644 --- a/tests/test_litellm/proxy/proxy_server/test_background_health.py +++ b/tests/test_litellm/proxy/proxy_server/test_background_health.py @@ -19,14 +19,15 @@ from unittest.mock import AsyncMock, MagicMock import pytest -import litellm.proxy.proxy_server as proxy_server +from litellm.proxy import proxy_server +from litellm.proxy._types import ConfigGeneralSettings from litellm.proxy.proxy_server import ( _adaptive_router_flusher_loop, _get_endpoint_exception_status, _get_process_rss_mb, + _rss_mb_for_log, _run_background_health_check, _run_direct_health_check_with_instrumentation, - _rss_mb_for_log, _schedule_background_health_check_db_save, _write_health_state_to_router_cache, ) @@ -159,6 +160,18 @@ async def test_run_direct_health_check_raises_non_kwarg_typeerror(monkeypatch): # --------------------------------------------------------------------------- +def test_background_health_check_db_persistence_config_defaults_enabled(): + assert ConfigGeneralSettings().persist_background_health_check_results is True + + +def test_background_health_check_db_persistence_config_can_be_disabled(): + settings = ConfigGeneralSettings.model_validate( + {"persist_background_health_check_results": False} + ) + + assert settings.persist_background_health_check_results is False + + @pytest.mark.asyncio async def test_schedule_background_health_check_db_save_creates_task(monkeypatch): captured = {} @@ -219,6 +232,24 @@ def test_schedule_background_health_check_db_save_noop_when_prisma_none(): ) +def test_schedule_background_health_check_db_save_noop_when_persistence_disabled( + monkeypatch, +): + create_task = MagicMock() + monkeypatch.setattr(asyncio, "create_task", create_task) + + _schedule_background_health_check_db_save( + prisma_client=MagicMock(), + shared_health_manager=None, + model_list=[], + healthy_endpoints=[], + unhealthy_endpoints=[], + persist_results=False, + ) + + create_task.assert_not_called() + + @pytest.mark.asyncio async def test_schedule_background_health_check_db_save_invalid_no_event_loop_raises( monkeypatch, @@ -527,6 +558,11 @@ async def test_run_background_health_check_runs_one_cycle_then_cancels(monkeypat monkeypatch.setattr(proxy_server, "use_shared_health_check", False) monkeypatch.setattr(proxy_server, "redis_usage_cache", None) monkeypatch.setattr(proxy_server, "prisma_client", None) + monkeypatch.setattr( + proxy_server, + "general_settings", + {"persist_background_health_check_results": False}, + ) monkeypatch.setattr(proxy_server, "background_health_check_loop_active", False) monkeypatch.setattr( proxy_server, @@ -547,8 +583,9 @@ async def test_run_background_health_check_runs_one_cycle_then_cancels(monkeypat "_run_direct_health_check_with_instrumentation", _fake_direct, ) + scheduled = MagicMock() monkeypatch.setattr( - proxy_server, "_schedule_background_health_check_db_save", lambda *a, **kw: None + proxy_server, "_schedule_background_health_check_db_save", scheduled ) monkeypatch.setattr( proxy_server, "_write_health_state_to_router_cache", lambda *a, **kw: None @@ -574,11 +611,13 @@ async def test_run_background_health_check_runs_one_cycle_then_cancels(monkeypat { "healthy_count": proxy_server.health_check_results["healthy_count"], "unhealthy_count": proxy_server.health_check_results["unhealthy_count"], + "persist_results": scheduled.call_args.kwargs["persist_results"], "sleep_invoked": sleep_calls["n"] >= 1, } ) == { "healthy_count": 1, "unhealthy_count": 1, + "persist_results": False, "sleep_invoked": True, } diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts index 6f044fec3f3..59132d7c3a7 100644 --- a/ui/litellm-dashboard/src/lib/http/schema.d.ts +++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts @@ -25663,6 +25663,12 @@ export interface components { * @description Default upstream request timeout in seconds for native and custom pass-through endpoints that use pass_through_request. Defaults to 600 when unset. */ pass_through_request_timeout?: number | null; + /** + * Persist Background Health Check Results + * @description persist background health check results to the database + * @default true + */ + persist_background_health_check_results: boolean; /** * Plugins * @description external services registered as embeddable UI plugins