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)

(cherry picked from commit 1fe911d89d)
This commit is contained in:
oss-agent-shin 2026-05-27 12:28:11 -07:00 committed by Yuneng Jiang
parent d0f29513b7
commit 2f33a86e2f
No known key found for this signature in database
2 changed files with 78 additions and 0 deletions

View file

@ -3695,6 +3695,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

View file

@ -87,3 +87,76 @@ def test_user_api_key_auth_hashes_authorization_header_form_of_key():
assert from_header.api_key == baseline.api_key
assert from_header.token == baseline.token
assert not from_header.api_key.lower().startswith("bearer")
# === 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",)