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
parent 5850b1130e
commit a756424192
No known key found for this signature in database
4 changed files with 28 additions and 7 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 build_deployment_health_states(
@ -413,7 +421,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,
@ -445,4 +457,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

@ -2287,6 +2287,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,
@ -2300,6 +2301,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,
@ -2310,6 +2312,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,
@ -2352,7 +2355,9 @@ async def _run_background_health_check():
)
# Write health state to router cache for health-check-driven routing
_write_health_state_to_router_cache(healthy_endpoints, unhealthy_endpoints)
_write_health_state_to_router_cache(
healthy_endpoints, unhealthy_endpoints, _exceptions_by_model_id
)
await asyncio.sleep(health_check_interval)