From 1fe911d89db85bc29f88f4269a78ed63b8f23a00 Mon Sep 17 00:00:00 2001 From: oss-agent-shin Date: Wed, 27 May 2026 12:28:11 -0700 Subject: [PATCH] fix(proxy): populate Exception.args so str(ProxyException) returns message (LIT-3094) (#29015) * fix(proxy): populate Exception.args so str(ProxyException) returns message Adds super().__init__(self.message) to ProxyException.__init__ so that str(exc) returns the stored message instead of empty string. Fixes LIT-3094. * test(proxy): regression tests for ProxyException.args (LIT-3094) * fix(proxy): populate Exception.args so str(ProxyException) returns message (LIT-3094) * fix(proxy): clean up unintended drift; keep only ProxyException.args fix (LIT-3094) --- litellm/proxy/_types.py | 5 ++ tests/test_litellm/proxy/test_proxy_types.py | 74 ++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 004f33e630a..099d3c33aa9 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -3651,6 +3651,11 @@ class ProxyException(Exception): provider_specific_fields: Optional[dict] = None, ): self.message = str(message) + # Populate Exception.args so str(self) returns the message. + # Without this, logging paths that call str(original_exception) + # (e.g. StandardLoggingPayloadSetup.get_error_information) record an + # empty error_message for ProxyException-based failures. See LIT-3094. + super().__init__(self.message) self.type = type self.param = param self.openai_code = openai_code or code diff --git a/tests/test_litellm/proxy/test_proxy_types.py b/tests/test_litellm/proxy/test_proxy_types.py index 0fa86798999..6f728fe5ef5 100644 --- a/tests/test_litellm/proxy/test_proxy_types.py +++ b/tests/test_litellm/proxy/test_proxy_types.py @@ -69,3 +69,77 @@ def test_internal_jobs_user_has_proxy_admin_role(): assert system_user.user_id == "system" assert system_user.team_id == "system" assert system_user.team_alias == "system" + + + +# === Regression tests for LIT-3094: ProxyException must populate Exception.args +# so logging integrations using str(exc) record a non-empty error_message. === + + +def test_proxy_exception_str_returns_message(): + """str(ProxyException) must return the stored message, not '' (LIT-3094).""" + from litellm.proxy._types import ProxyException + + msg = "key not allowed to access model" + exc = ProxyException(message=msg, type="auth_error", param=None, code=401) + assert str(exc) == msg + assert exc.args == (msg,) + assert exc.message == msg + + +def test_proxy_exception_populates_standard_logging_error_message(): + """The full logging path used by proxy callbacks must capture the message + instead of recording an empty error_message (LIT-3094 report).""" + from litellm.litellm_core_utils.litellm_logging import ( + StandardLoggingPayloadSetup, + ) + from litellm.proxy._types import ProxyException + + msg = "Authentication Error, Invalid proxy server token passed." + exc = ProxyException(message=msg, type="auth_error", param=None, code=401) + info = StandardLoggingPayloadSetup.get_error_information(original_exception=exc) + assert info["error_message"] == msg + assert info["error_class"] == "ProxyException" + assert info["error_code"] == "401" + + +def test_proxy_exception_to_dict_unchanged(): + """to_dict() shape must remain backwards-compatible after the fix.""" + from litellm.proxy._types import ProxyException + + exc = ProxyException( + message="boom", type="invalid_request_error", param="model", code=400 + ) + d = exc.to_dict() + assert d == { + "message": "boom", + "type": "invalid_request_error", + "param": "model", + "code": "400", + } + + +def test_proxy_exception_routing_code_override_still_works(): + """The 'No healthy deployment available' -> 429 remapping must survive + the super().__init__() addition.""" + from litellm.proxy._types import ProxyException + + exc = ProxyException( + message="No healthy deployment available for model=foo", + type="router_error", + param=None, + code=500, + ) + assert exc.code == "429" + assert str(exc) == "No healthy deployment available for model=foo" + + +def test_proxy_exception_non_string_message_coerced(): + """Non-string `message` must still be coerced to str via self.message = + str(message), and Exception.args must reflect the coerced value.""" + from litellm.proxy._types import ProxyException + + exc = ProxyException(message=42, type="x", param=None, code=400) + assert exc.message == "42" + assert str(exc) == "42" + assert exc.args == ("42",)