Merge pull request #38624 from BerriAI/litellm_/circleci-regressions-review-f0a542

fix(exceptions): keep a refused connection an APIConnectionError
This commit is contained in:
yuneng-jiang 2026-08-27 21:16:37 -07:00 committed by GitHub
commit bd7e9c1997
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 63 additions and 4 deletions

View file

@ -2222,6 +2222,8 @@ def _map_exception_by_status(
status_code: Final = original_exception.status_code if hasattr(original_exception, "status_code") else None
if not isinstance(status_code, int) or status_code < 400:
return
if getattr(original_exception, "status_code_is_synthesized", False):
return
message: Final = f"{exception_provider} - {error_str}"
response: Final = original_exception.response if hasattr(original_exception, "response") else None
match status_code:

View file

@ -46,8 +46,10 @@ class BaseLLMException(Exception):
request: httpx.Request | None = None,
response: httpx.Response | None = None,
body: dict | None = None,
status_code_is_synthesized: bool = False,
):
self.status_code = status_code
self.status_code_is_synthesized = status_code_is_synthesized
self.message: str = message
self.headers = headers
if request:

View file

@ -5947,11 +5947,13 @@ class BaseLLMHTTPHandler:
BaseEvalsAPIConfig,
],
):
status_code = getattr(e, "status_code", 500)
received_status_code: Final = (
e.response.status_code if isinstance(e, httpx.HTTPStatusError) else getattr(e, "status_code", None)
)
status_code = received_status_code if isinstance(received_status_code, int) else 500
error_headers = getattr(e, "headers", None)
if isinstance(e, httpx.HTTPStatusError):
error_text = e.response.text
status_code = e.response.status_code
else:
error_text = getattr(e, "text", str(e))
error_response: Final = getattr(e, "response", None)
@ -5971,13 +5973,17 @@ class BaseLLMHTTPHandler:
status_code=status_code,
message=error_text,
headers=error_headers,
status_code_is_synthesized=not isinstance(received_status_code, int),
)
raise provider_config.get_error_class(
provider_error: Final = provider_config.get_error_class(
error_message=error_text,
status_code=status_code,
headers=error_headers,
)
if not isinstance(received_status_code, int):
provider_error.status_code_is_synthesized = True
raise provider_error
@staticmethod
def _append_query_params(url: str, query_params: RealtimeQueryParams | None) -> str:

View file

@ -1163,3 +1163,52 @@ def test_bedrock_mantle_context_overflow_maps_to_context_window_exceeded():
assert excinfo.value.status_code == 400
assert "prompt is too long: 1055489 tokens > 1050000 maximum" in excinfo.value.message
def test_branchless_provider_transport_error_maps_to_api_connection_error():
from litellm.llms.base_llm.chat.transformation import BaseLLMException
original_exception = BaseLLMException(status_code=500, message="[Errno 111] Connection refused")
original_exception.status_code_is_synthesized = True
with pytest.raises(litellm.APIConnectionError):
exception_type(
model="test-agent",
original_exception=original_exception,
custom_llm_provider="a2a",
)
def test_branchless_provider_upstream_500_still_maps_to_internal_server_error():
from litellm.llms.base_llm.chat.transformation import BaseLLMException
original_exception = BaseLLMException(status_code=500, message="upstream exploded")
with pytest.raises(litellm.InternalServerError):
exception_type(
model="test-agent",
original_exception=original_exception,
custom_llm_provider="a2a",
)
def test_handle_error_marks_only_a_status_code_it_never_received():
from litellm.llms.custom_httpx.llm_http_handler import BaseLLMHTTPHandler
handler = BaseLLMHTTPHandler()
with pytest.raises(litellm.llms.base_llm.chat.transformation.BaseLLMException) as transport:
raise handler._handle_error(e=httpx.ConnectError("Connection refused"), provider_config=None)
assert transport.value.status_code == 500
assert transport.value.status_code_is_synthesized is True
request = httpx.Request(method="POST", url="https://example.invalid")
upstream = httpx.HTTPStatusError(
"server error",
request=request,
response=httpx.Response(status_code=500, request=request, text="upstream exploded"),
)
with pytest.raises(litellm.llms.base_llm.chat.transformation.BaseLLMException) as received:
raise handler._handle_error(e=upstream, provider_config=None)
assert received.value.status_code == 500
assert received.value.status_code_is_synthesized is False

View file

@ -322,7 +322,7 @@ async def test_query_param_key_not_leaked_with_dummy_caller_key(
"litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.get",
fake_get,
):
with pytest.raises(litellm.InternalServerError):
with pytest.raises(litellm.APIConnectionError):
await litellm.asearch(
query="secrets",
search_provider=provider,