From 2b14762d0ffdfca8a6c62fffde32d7df977d2a55 Mon Sep 17 00:00:00 2001 From: jesus Date: Mon, 7 Sep 2026 09:55:04 +0000 Subject: [PATCH] fix(http_handler): surface BaseLLMException raised while streaming a request body Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/llms/custom_httpx/llm_http_handler.py | 26 ++++++++++-- .../custom_httpx/test_llm_http_handler.py | 41 +++++++++++++++++++ 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/litellm/llms/custom_httpx/llm_http_handler.py b/litellm/llms/custom_httpx/llm_http_handler.py index f281c249c72..e1142a6e4a1 100644 --- a/litellm/llms/custom_httpx/llm_http_handler.py +++ b/litellm/llms/custom_httpx/llm_http_handler.py @@ -44,7 +44,7 @@ from litellm.llms.base_llm.base_model_iterator import ( MockResponseIterator, ) from litellm.llms.base_llm.batches.transformation import BaseBatchesConfig -from litellm.llms.base_llm.chat.transformation import BaseConfig +from litellm.llms.base_llm.chat.transformation import BaseConfig, BaseLLMException from litellm.llms.base_llm.containers.transformation import BaseContainerConfig from litellm.llms.base_llm.embedding.transformation import BaseEmbeddingConfig from litellm.llms.base_llm.evals.transformation import BaseEvalsAPIConfig @@ -311,6 +311,24 @@ def _collect_ws_project_quota_callbacks() -> tuple[ProjectQuotaCallback, ...]: ) +def _find_base_llm_exception(error: BaseException, depth: int = 0) -> BaseLLMException | None: + if depth >= 10: + return None + if isinstance(error, BaseLLMException): + return error + + cause: Final = error.__cause__ + if cause is not None: + provider_exception: Final = _find_base_llm_exception(cause, depth + 1) + if provider_exception is not None: + return provider_exception + + context: Final = error.__context__ + if context is not None: + return _find_base_llm_exception(context, depth + 1) + return None + + class BaseLLMHTTPHandler: async def _make_common_async_call( self, @@ -5980,6 +5998,10 @@ class BaseLLMHTTPHandler: BaseEvalsAPIConfig, ], ): + provider_exception: Final = _find_base_llm_exception(e) + if provider_exception is not None: + raise provider_exception + received_status_code: Final = ( e.response.status_code if isinstance(e, httpx.HTTPStatusError) else getattr(e, "status_code", None) ) @@ -6000,8 +6022,6 @@ class BaseLLMHTTPHandler: error_headers = {} if provider_config is None: - from litellm.llms.base_llm.chat.transformation import BaseLLMException - raise BaseLLMException( status_code=status_code, message=error_text, diff --git a/tests/test_litellm/llms/custom_httpx/test_llm_http_handler.py b/tests/test_litellm/llms/custom_httpx/test_llm_http_handler.py index 023e2d8843f..15cf01d4705 100644 --- a/tests/test_litellm/llms/custom_httpx/test_llm_http_handler.py +++ b/tests/test_litellm/llms/custom_httpx/test_llm_http_handler.py @@ -2110,6 +2110,47 @@ def test_sync_retrieve_file_content_raises_on_http_error(): assert exc_info.value.status_code == 404 +def test_handle_error_preserves_base_llm_exception_message_and_status(): + from litellm.llms.vertex_ai.common_utils import VertexAIError + from litellm.llms.vertex_ai.files.transformation import VertexAIFilesConfig + + message = "Invalid JSON on line 3 of batch input file: Expecting value" + error = VertexAIError(status_code=400, message=message) + + with pytest.raises(VertexAIError) as exc_info: + BaseLLMHTTPHandler()._handle_error(e=error, provider_config=VertexAIFilesConfig()) + + assert exc_info.value.status_code == 400 + assert exc_info.value.message == message + + +def test_handle_error_preserves_base_llm_exception_from_wrapper(): + from litellm.llms.vertex_ai.common_utils import VertexAIError + from litellm.llms.vertex_ai.files.transformation import VertexAIFilesConfig + + message = "Invalid JSON on line 3 of batch input file: Expecting value" + error = VertexAIError(status_code=400, message=message) + wrapper = httpx.ConnectError("Failed to send bytes") + wrapper.__cause__ = error + + with pytest.raises(VertexAIError) as exc_info: + BaseLLMHTTPHandler()._handle_error(e=wrapper, provider_config=VertexAIFilesConfig()) + + assert exc_info.value.status_code == 400 + assert exc_info.value.message == message + + +def test_handle_error_maps_plain_exception_to_provider_error(): + from litellm.llms.vertex_ai.common_utils import VertexAIError + from litellm.llms.vertex_ai.files.transformation import VertexAIFilesConfig + + with pytest.raises(VertexAIError) as exc_info: + BaseLLMHTTPHandler()._handle_error(e=ValueError("boom"), provider_config=VertexAIFilesConfig()) + + assert exc_info.value.status_code == 500 + assert exc_info.value.message == "boom" + + _UPSTREAM_NOT_FOUND_BODY = { "error": { "message": "Response with id 'resp_abc' not found.",