From 8cffdb8de43dfc1b6e58103389107ba3833df53f Mon Sep 17 00:00:00 2001 From: Mihidum Hettiyahandi <55163074+mihidumh@users.noreply.github.com> Date: Thu, 17 Sep 2026 09:57:00 +1000 Subject: [PATCH] fix(health): probe test_connection without custom-pricing fields completion() registers request pricing under the shared {provider}/{model} cost-map key when the call carries no router deployment id, which a connection test never does. A probe that carried input/output_cost_per_token (from the request, or from the configured deployment) therefore re-priced every sibling deployment of the same backend model. A connection test needs no prices, so strip CustomPricingLiteLLMParams fields from the probe params. Two end-to-end tests fail without this change. --- .../health_endpoints/_health_endpoints.py | 10 ++- .../health_endpoints/test_health_endpoints.py | 75 ++++++++++++++++++- 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/health_endpoints/_health_endpoints.py b/litellm/proxy/health_endpoints/_health_endpoints.py index b5ca3ad9ec4..2477041cd45 100644 --- a/litellm/proxy/health_endpoints/_health_endpoints.py +++ b/litellm/proxy/health_endpoints/_health_endpoints.py @@ -2375,12 +2375,20 @@ async def test_model_connection( or _string_mode_or_bad_request(raw_params_mode) or resolve_health_check_mode(probe_model_info, _OBJECT_MAPPING.validate_python(litellm_params)) ) + # A connection test needs no prices, and ``completion`` registers any + # request pricing under the shared ``{provider}/{model}`` cost-map key + # (a probe carries no router deployment id), which would re-price every + # sibling deployment of the same backend model. Probe without them, + # whether they came from the request or from the configuration. + probe_params: Final = CustomPricingLiteLLMParams.strip_custom_pricing_fields( + dict(_OBJECT_MAPPING.validate_python(litellm_params)) + ) raw_headers: Final[dict[str, str]] = TypeAdapter(dict[str, str]).validate_python( http_parsing_utils.safe_get_request_headers(request) ) health_check_params: Final = { - **_OBJECT_MAPPING.validate_python(litellm_params), + **probe_params, **_admin_api_key_from_request( _OBJECT_MAPPING.validate_python(request_litellm_params), _OBJECT_MAPPING.validate_python(litellm_params), diff --git a/tests/unit/proxy/health_endpoints/test_health_endpoints.py b/tests/unit/proxy/health_endpoints/test_health_endpoints.py index b8e6e81a884..af428e51007 100644 --- a/tests/unit/proxy/health_endpoints/test_health_endpoints.py +++ b/tests/unit/proxy/health_endpoints/test_health_endpoints.py @@ -4629,7 +4629,7 @@ class TestConfigBaseForHealthCheck: shared model-cost registry, not because they describe a connection. A connection test that carries one still gets the configured credentials — otherwise a healthy deployment reports "Missing credentials".""" - base = self._base(self.CONFIG, {"model": "openai/gpt-4o", pricing_field: value}) + base = self._base(self.CONFIG, {"model": "openai/gpt-4o", pricing_field: value}, selected_by_id=False) assert base["api_key"] == "sk-configured" assert base["api_base"] == "https://configured.example/v1" @@ -4638,6 +4638,7 @@ class TestConfigBaseForHealthCheck: base = self._base( self.CONFIG, {"api_base": "https://caller.example/v1", "input_cost_per_token": 1e-9}, + selected_by_id=False, ) assert "api_key" not in base assert "sk-configured" not in str(base) @@ -5101,6 +5102,78 @@ class TestTestConnectionUsesTheNamedCredential: assert probe.headers["authorization"] == f"Bearer {self.CREDENTIAL_KEY}" +class TestTestConnectionLeavesSharedPricingAlone: + """A connection test must not write prices into ``litellm.model_cost``: a probe + carries no router deployment id, so ``completion`` would register them under + the shared ``{provider}/{model}`` key that sibling deployments price from.""" + + MODEL = "openai/test-connection-pricing-probe" + SHARED_KEY = MODEL + CONFIG_KEY = "sk-configured" + COMPLETION = TestTestConnectionUsesTheNamedCredential.COMPLETION + + def _probe(self, monkeypatch, config_litellm_params: dict, request_litellm_params: dict) -> httpx.Request: + monkeypatch.setattr(litellm, "disable_aiohttp_transport", True) + monkeypatch.setattr(litellm, "model_cost", dict(litellm.model_cost)) + litellm.in_memory_llm_clients_cache.flush_cache() + + app = FastAPI() + app.include_router(_health_endpoints_module.router) + app.dependency_overrides[user_api_key_auth] = lambda: UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN) + + router = MagicMock() + router.get_deployment.return_value = None + router.get_model_list.return_value = [ + {"model_name": "pricing-probe", "litellm_params": config_litellm_params, "model_info": {}} + ] + + with ( + patch( # test-quality-ok: the endpoint reads the proxy-global DB client and 500s when it is None; it has no injection seam + "litellm.proxy.proxy_server.prisma_client", MagicMock() + ), + patch( # test-quality-ok: the deployment the probe is matched against is a proxy global; it has no injection seam + "litellm.proxy.proxy_server.llm_router", router + ), + respx.mock(assert_all_called=True) as respx_mock, + ): + respx_mock.post(path__regex=r".*/chat/completions").respond(json=self.COMPLETION) + response = TestClient(app).post( + "/health/test_connection", + json={"mode": "chat", "litellm_params": request_litellm_params, "model_info": {"mode": "chat"}}, + ) + probe = respx_mock.calls.last.request + + assert response.status_code == 200, response.text + assert response.json()["status"] == "success", response.text + return probe + + def test_request_pricing_is_not_registered_under_the_shared_key(self, monkeypatch): + probe = self._probe( + monkeypatch, + {"model": self.MODEL, "api_key": self.CONFIG_KEY, "api_base": "https://configured.example/v1"}, + {"model": self.MODEL, "input_cost_per_token": 1.0, "output_cost_per_token": 2.0}, + ) + + assert probe.headers["authorization"] == f"Bearer {self.CONFIG_KEY}" + assert self.SHARED_KEY not in litellm.model_cost + + def test_configured_pricing_is_not_registered_under_the_shared_key(self, monkeypatch): + probe = self._probe( + monkeypatch, + { + "model": self.MODEL, + "api_key": self.CONFIG_KEY, + "api_base": "https://configured.example/v1", + "input_cost_per_token": 3.0, + "output_cost_per_token": 4.0, + }, + {"model": self.MODEL}, + ) + + assert probe.url.host == "configured.example" + assert self.SHARED_KEY not in litellm.model_cost + + class TestNoRedisWarning: """`show_no_redis_warning` drives the Admin UI's default-on "no Redis" banner."""