fix(proxy): ProxyException.__str__() returns empty string

ProxyException never called super().__init__(message), causing
str(e) to return '' in all exception logging throughout the proxy.
HTTP responses were unaffected (they use to_dict()), but every log
line that stringified the exception produced blank error messages.

Adds super().__init__() call and __str__ method.

Fixes #22644
This commit is contained in:
gavksingh 2026-03-03 16:20:29 -05:00
parent 92407ec0d4
commit bdb0a78ce9
2 changed files with 118 additions and 0 deletions

View file

@ -3208,6 +3208,7 @@ class ProxyException(Exception):
provider_specific_fields: Optional[dict] = None,
):
self.message = str(message)
super().__init__(self.message)
self.type = type
self.param = param
self.openai_code = openai_code or code
@ -3232,6 +3233,9 @@ class ProxyException(Exception):
elif RouterErrors.no_deployments_with_tag_routing.value in self.message:
self.code = "401"
def __str__(self) -> str:
return self.message
def to_dict(self) -> dict:
"""Converts the ProxyException instance to a dictionary."""
error_dict: Dict[str, Optional[Union[str, Dict]]] = {

View file

@ -0,0 +1,114 @@
"""
Tests for ProxyException string representation.
Verifies that str(ProxyException(...)) returns the exception message
instead of an empty string. See https://github.com/BerriAI/litellm/issues/22644
"""
from litellm.proxy._types import ProxyException
class TestProxyExceptionStr:
"""Tests for ProxyException.__str__() behavior."""
def test_proxy_exception_str_returns_message(self):
"""str(ProxyException(...)) should return the message, not empty string."""
exc = ProxyException(
message="auth failed",
type="bad_request_error",
param="key_alias",
code=400,
)
assert str(exc) == "auth failed"
def test_proxy_exception_str_empty_message(self):
"""str() should work correctly with an empty message."""
exc = ProxyException(
message="",
type="bad_request_error",
param="key_alias",
code=400,
)
assert str(exc) == ""
def test_proxy_exception_str_unicode(self):
"""str() should handle unicode messages correctly."""
msg = "Error: \u2018invalid key\u2019 \u2014 please retry \U0001f512"
exc = ProxyException(
message=msg,
type="bad_request_error",
param="key_alias",
code=400,
)
assert str(exc) == msg
def test_proxy_exception_repr(self):
"""repr() should include the message text."""
exc = ProxyException(
message="something broke",
type="internal_error",
param=None,
code=500,
)
assert "something broke" in repr(exc)
def test_proxy_exception_to_dict_unchanged(self):
"""to_dict() output format must remain exactly the same after the fix."""
exc = ProxyException(
message="model not found",
type="invalid_request_error",
param="model",
code=404,
)
result = exc.to_dict()
assert result == {
"message": "model not found",
"type": "invalid_request_error",
"param": "model",
"code": "404",
}
def test_proxy_exception_to_dict_with_provider_fields(self):
"""to_dict() should include provider_specific_fields when present."""
exc = ProxyException(
message="rate limited",
type="rate_limit_error",
param=None,
code=429,
provider_specific_fields={"retry_after": 30},
)
result = exc.to_dict()
assert result["provider_specific_fields"] == {"retry_after": 30}
assert result["message"] == "rate limited"
def test_proxy_exception_chaining(self):
"""raise ProxyException from ValueError should preserve __cause__."""
inner = ValueError("inner error")
try:
try:
raise inner
except ValueError:
raise ProxyException(
message="outer error",
type="bad_request_error",
param="test",
code=400,
) from inner
except ProxyException as exc:
assert str(exc) == "outer error"
assert exc.__cause__ is inner
def test_proxy_exception_catch_and_stringify(self):
"""Simulates the real-world pattern: try/except that formats str(e) into a log."""
log_output = ""
try:
raise ProxyException(
message="key expired",
type="authentication_error",
param="api_key",
code=401,
)
except ProxyException as exc:
log_output = "Error - {}".format(str(exc))
assert log_output == "Error - key expired"