From 3803f484d34572b3c27592dd8c6fc04174fa3bda Mon Sep 17 00:00:00 2001 From: Nathan Morin <317676377+nhmorin@users.noreply.github.com> Date: Mon, 14 Sep 2026 11:52:05 +0800 Subject: [PATCH] fix(client): translate APIConnectionError and ServiceUnavailableError distinctly Network-level failures (DNS resolution, connection refused, TCP reset) currently fall through to the generic upstream_error catch-all in _translate_error, making them indistinguishable from other 503s in request_log and the dashboard. Add explicit handlers for litellm.APIConnectionError (network failures) and litellm.ServiceUnavailableError (explicit 503 from the provider) so operators can tell connection problems from generic upstream errors at a glance. --- packages/litellm_adapter/client.py | 4 ++ tests/unit/test_error_translation.py | 80 ++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 tests/unit/test_error_translation.py diff --git a/packages/litellm_adapter/client.py b/packages/litellm_adapter/client.py index 1e3f357..6dec440 100644 --- a/packages/litellm_adapter/client.py +++ b/packages/litellm_adapter/client.py @@ -74,6 +74,10 @@ def _translate_error(exc: Exception) -> UpstreamProviderError: return UpstreamProviderError(msg, http_status=503, error_type="upstream_auth_error") if isinstance(exc, litellm.Timeout): return UpstreamProviderError(msg, http_status=503, error_type="upstream_timeout") + if isinstance(exc, litellm.APIConnectionError): + return UpstreamProviderError(msg, http_status=503, error_type="upstream_connection_error") + if isinstance(exc, litellm.ServiceUnavailableError): + return UpstreamProviderError(msg, http_status=503, error_type="upstream_unavailable") return UpstreamProviderError(msg, http_status=503, error_type="upstream_error") diff --git a/tests/unit/test_error_translation.py b/tests/unit/test_error_translation.py new file mode 100644 index 0000000..bb640d3 --- /dev/null +++ b/tests/unit/test_error_translation.py @@ -0,0 +1,80 @@ +"""Verify _translate_error maps each LiteLLM exception to a distinct error_type. + +Operators debugging routing failures need to distinguish network-level +connection failures (DNS, refused connection) from generic upstream +errors and explicit 503s. Without this distinction every 503 looks the +same in request_log and the dashboard. +""" + +from __future__ import annotations + +import pytest + +litellm = pytest.importorskip("litellm") + +from packages.litellm_adapter.client import _translate_error +from packages.litellm_adapter.types import UpstreamProviderError + + +class TestErrorTranslation: + def test_context_window_exceeded(self): + exc = litellm.ContextWindowExceededError( + message="too long", model="gpt-4o", llm_provider="openai" + ) + result = _translate_error(exc) + assert isinstance(result, UpstreamProviderError) + assert result.http_status == 422 + assert result.error_type == "context_length_exceeded" + + def test_not_found_error(self): + exc = litellm.NotFoundError( + message="model not found", model="fake-model", llm_provider="openai" + ) + result = _translate_error(exc) + assert result.http_status == 422 + assert result.error_type == "model_not_found" + + def test_rate_limit_error(self): + exc = litellm.RateLimitError( + message="rate limited", model="gpt-4o", llm_provider="openai" + ) + result = _translate_error(exc) + assert result.http_status == 429 + assert result.error_type == "rate_limit_error" + + def test_authentication_error(self): + exc = litellm.AuthenticationError( + message="bad key", model="gpt-4o", llm_provider="openai" + ) + result = _translate_error(exc) + assert result.http_status == 503 + assert result.error_type == "upstream_auth_error" + + def test_timeout_error(self): + exc = litellm.Timeout( + message="timed out", model="gpt-4o", llm_provider="openai" + ) + result = _translate_error(exc) + assert result.http_status == 503 + assert result.error_type == "upstream_timeout" + + def test_api_connection_error(self): + exc = litellm.APIConnectionError( + message="connection refused", model="gpt-4o", llm_provider="openai" + ) + result = _translate_error(exc) + assert result.http_status == 503 + assert result.error_type == "upstream_connection_error" + + def test_service_unavailable_error(self): + exc = litellm.ServiceUnavailableError( + message="service down", model="gpt-4o", llm_provider="openai" + ) + result = _translate_error(exc) + assert result.http_status == 503 + assert result.error_type == "upstream_unavailable" + + def test_generic_exception(self): + result = _translate_error(RuntimeError("something else")) + assert result.http_status == 503 + assert result.error_type == "upstream_error"