diff --git a/litellm/rust_bridge/host/routes/messages.py b/litellm/rust_bridge/host/routes/messages.py index d893b029069..f15a4d616ee 100644 --- a/litellm/rust_bridge/host/routes/messages.py +++ b/litellm/rust_bridge/host/routes/messages.py @@ -186,6 +186,8 @@ def _upstream_failure( headers: Final = _UPSTREAM_HEADERS.validate_python(getattr(error, "headers", None)) except ValidationError: return None + if not headers: + return status, text, headers # Python's `MaskedHTTPStatusError` rebuilds the response from its decoded body without the # wire's content-encoding and content-length, so httpx states a length only for a non-empty body. rebuilt: Final = httpx.Response( diff --git a/tests/unit/rust_bridge/host/routes/test_messages.py b/tests/unit/rust_bridge/host/routes/test_messages.py index 7ecd18fa48e..80bbd8832f6 100644 --- a/tests/unit/rust_bridge/host/routes/test_messages.py +++ b/tests/unit/rust_bridge/host/routes/test_messages.py @@ -243,6 +243,19 @@ def test_upstream_failure_headers_match_the_python_clients_rebuilt_response( assert dict(_get_response_headers(mapped) or {}) == expected +@pytest.mark.parametrize("status", [200, 500]) +def test_upstream_failure_without_wire_headers_states_no_headers(status: int) -> None: + from litellm.litellm_core_utils.exception_mapping_utils import _get_response_headers + + upstream: Final = ValueError(status, "not a messages response") + upstream.headers = [] # pyright: ignore[reportAttributeAccessIssue] # set by the native host + + mapped: Final = messages_host.map_failure(upstream, {"model": _ADAPTIVE_OPUS}, "anthropic") + + assert isinstance(mapped, Exception) + assert not dict(_get_response_headers(mapped) or {}) + + def test_a_late_rejection_tells_the_logger_the_resolved_call_first() -> None: import datetime