fix(health): attribute background health check results to their own deployment (#44982)

* fix(health): attribute background health check results to their own deployment

Co-authored-by: Dennis Pfisterer <302635+pfisterer@users.noreply.github.com>
Co-authored-by: Suhas Hanamannavar <hanamannavarsuhas17@gmail.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(health): annotate locals with Final and split nested comprehension

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Mubashir Osmani <mubashir@berri.ai>
Co-authored-by: Dennis Pfisterer <302635+pfisterer@users.noreply.github.com>
Co-authored-by: Suhas Hanamannavar <hanamannavarsuhas17@gmail.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
devin-ai-integration[bot] 2026-10-07 10:00:50 -04:00 • committed by GitHub
parent 91e9b1f06b
commit 736ff14f11
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 54 additions and 3 deletions

View file

@ -730,6 +730,19 @@ def _build_model_param_to_info_mapping(model_list: list) -> dict:
return model_param_to_info
def _model_infos_for_endpoint(
model_param_to_info: Mapping[str, list[Mapping[str, str | None]]], endpoint: Mapping[str, object]
) -> tuple[Mapping[str, str | None], ...]:
model_param: Final = endpoint.get("model")
if not isinstance(model_param, str):
return ()
model_infos: Final = model_param_to_info.get(model_param, [])
endpoint_model_id: Final = endpoint.get("model_id")
if not endpoint_model_id:
return tuple(model_infos)
return tuple(info for info in model_infos if info["model_id"] == endpoint_model_id)
def _aggregate_health_check_results(
model_param_to_info: dict,
healthy_endpoints: list,
@ -754,7 +767,7 @@ def _aggregate_health_check_results(
for endpoint in healthy_endpoints:
model_param = endpoint.get("model")
if model_param and model_param in model_param_to_info:
for model_info in model_param_to_info[model_param]:
for model_info in _model_infos_for_endpoint(model_param_to_info, endpoint):
key = (model_info["model_id"], model_info["model_name"])
if key not in model_results:
model_results[key] = {
@ -771,7 +784,7 @@ def _aggregate_health_check_results(
model_param = endpoint.get("model")
error_message = endpoint.get("error")
if model_param and model_param in model_param_to_info:
for model_info in model_param_to_info[model_param]:
for model_info in _model_infos_for_endpoint(model_param_to_info, endpoint):
key = (model_info["model_id"], model_info["model_name"])
if key not in model_results:
model_results[key] = {

View file

@ -1,11 +1,11 @@
import asyncio
import time
from datetime import datetime, timedelta, timezone
from typing import Final
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from litellm.proxy._types import UserAPIKeyAuth
from litellm.proxy.db.health_check_latest import LatestHealthCheckRow
from litellm.proxy.health_endpoints._health_endpoints import (
@ -220,6 +220,44 @@ def test_aggregate_health_check_results_multiple_endpoints():
assert result[key]["unhealthy_count"] == 0
def test_aggregate_health_check_results_attributes_each_result_to_its_own_deployment():
deployments: Final = (("a", "qwen"), ("a", "qwen-alias"), ("b", "qwen"), ("b", "qwen-alias"))
model_list: Final = [
{
"model_name": name,
"litellm_params": {"model": "openai/qwen", "api_base": f"http://{host}:11434/v1"},
"model_info": {"id": f"{host}-{name}"},
}
for host, name in deployments
]
healthy_endpoints: Final = [
{"model": "openai/qwen", "api_base": "http://a:11434/v1", "model_id": "a-qwen"},
{"model": "openai/qwen", "api_base": "http://a:11434/v1", "model_id": "a-qwen-alias"},
]
unhealthy_endpoints: Final = [
{"model": "openai/qwen", "api_base": "http://b:11434/v1", "model_id": "b-qwen", "error": "Connection error."},
{
"model": "openai/qwen",
"api_base": "http://b:11434/v1",
"model_id": "b-qwen-alias",
"error": "Connection error.",
},
]
result: Final = _aggregate_health_check_results(
_build_model_param_to_info_mapping(model_list), healthy_endpoints, unhealthy_endpoints
)
assert {
key: (value["healthy_count"], value["unhealthy_count"], value["error_message"]) for key, value in result.items()
} == {
("a-qwen", "qwen"): (1, 0, None),
("a-qwen-alias", "qwen-alias"): (1, 0, None),
("b-qwen", "qwen"): (0, 1, "Connection error."),
("b-qwen-alias", "qwen-alias"): (0, 1, "Connection error."),
}
@pytest.mark.asyncio
async def test_save_health_check_results_if_changed_status_changed():
"""Test saving when status changes"""