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>
This commit is contained in:
jesus 2026-09-07 09:55:04 +00:00
parent 0b33da566a
commit 2b14762d0f
2 changed files with 64 additions and 3 deletions

View file

@ -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,

View file

@ -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.",