fix(health-check-routing): properly isolate exceptions from health response

Return exceptions_by_model_id as a separate third value from
_perform_health_check / perform_health_check so exception objects
(which contain non-JSON-serializable httpx URL types) never appear
in the endpoint dicts that get serialized by the /health response.

Callers updated: _health_endpoints.py, shared_health_check_manager.py,
proxy_server.py background loop. All use the exceptions dict only for
cooldown integration, not for display.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sameer Kankute 2026-04-02 18:50:40 +05:30 • committed by Yuneng Jiang
parent 9ed771c6a6
commit 22c2772c79
No known key found for this signature in database
4 changed files with 30 additions and 6 deletions

View file

@ -211,6 +211,10 @@ async def _perform_health_check(
healthy_endpoints = []
unhealthy_endpoints = []
# Exceptions keyed by model_id; returned separately so callers can use
# them for cooldown integration without risking JSON-serialization errors
# in the /health response.
exceptions_by_model_id: dict = {}
for is_healthy, model in zip(results, model_list):
litellm_params = model["litellm_params"]
@ -225,14 +229,18 @@ async def _perform_health_check(
cleaned = _clean_endpoint_data({**litellm_params, **is_healthy}, details)
if _model_id:
cleaned["model_id"] = _model_id
if "exception" in is_healthy:
exceptions_by_model_id[_model_id] = is_healthy["exception"]
unhealthy_endpoints.append(cleaned)
else:
cleaned = _clean_endpoint_data(litellm_params, details)
if _model_id:
cleaned["model_id"] = _model_id
if isinstance(is_healthy, Exception):
exceptions_by_model_id[_model_id] = is_healthy
unhealthy_endpoints.append(cleaned)
return healthy_endpoints, unhealthy_endpoints
return healthy_endpoints, unhealthy_endpoints, exceptions_by_model_id
def _update_litellm_params_for_health_check(
@ -375,7 +383,11 @@ async def perform_health_check(
)
try:
healthy_endpoints, unhealthy_endpoints = await _perform_health_check(
(
healthy_endpoints,
unhealthy_endpoints,
exceptions_by_model_id,
) = await _perform_health_check(
model_list,
details,
max_concurrency=max_concurrency,
@ -407,4 +419,4 @@ async def perform_health_check(
_rss_mb_for_log(),
)
return healthy_endpoints, unhealthy_endpoints
return healthy_endpoints, unhealthy_endpoints, exceptions_by_model_id

View file

@ -231,7 +231,11 @@ class SharedHealthCheckManager:
len(model_list),
)
healthy_endpoints, unhealthy_endpoints = await perform_health_check(
(
healthy_endpoints,
unhealthy_endpoints,
exceptions_by_model_id,
) = await perform_health_check(
model_list=model_list,
details=details,
max_concurrency=max_concurrency,
@ -242,7 +246,7 @@ class SharedHealthCheckManager:
healthy_endpoints, unhealthy_endpoints
)
return healthy_endpoints, unhealthy_endpoints
return healthy_endpoints, unhealthy_endpoints, exceptions_by_model_id
finally:
# Always release the lock

View file

@ -771,7 +771,7 @@ async def _perform_health_check_and_save(
max_concurrency=None,
):
"""Helper function to perform health check and save results to database"""
healthy_endpoints, unhealthy_endpoints = await perform_health_check(
healthy_endpoints, unhealthy_endpoints, _ = await perform_health_check(
model_list=model_list,
cli_model=cli_model,
model=target_model,

View file

@ -2427,6 +2427,7 @@ async def _run_background_health_check():
(
healthy_endpoints,
unhealthy_endpoints,
_exceptions_by_model_id,
) = await shared_health_manager.perform_shared_health_check(
model_list=_llm_model_list,
details=details_bool,
@ -2440,6 +2441,7 @@ async def _run_background_health_check():
(
healthy_endpoints,
unhealthy_endpoints,
_exceptions_by_model_id,
) = await _run_direct_health_check_with_instrumentation(
_llm_model_list,
health_check_details,
@ -2450,6 +2452,7 @@ async def _run_background_health_check():
(
healthy_endpoints,
unhealthy_endpoints,
_exceptions_by_model_id,
) = await _run_direct_health_check_with_instrumentation(
_llm_model_list,
health_check_details,
@ -2491,6 +2494,11 @@ async def _run_background_health_check():
unhealthy_endpoints,
)
# Write health state to router cache for health-check-driven routing
_write_health_state_to_router_cache(
healthy_endpoints, unhealthy_endpoints, _exceptions_by_model_id
)
await asyncio.sleep(health_check_interval)