From bcf53c6cebd7c3d512a9644ae96a26361ca20f9f Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Thu, 16 Oct 2025 15:40:42 -0700 Subject: [PATCH] working - errors from bedrock through pass throughs --- litellm/passthrough/main.py | 13 ++-- .../llm_passthrough_endpoints.py | 59 +++++++++++++------ litellm/proxy/proxy_config.yaml | 5 ++ .../test_llm_pass_through_endpoints.py | 20 ++++--- 4 files changed, 64 insertions(+), 33 deletions(-) diff --git a/litellm/passthrough/main.py b/litellm/passthrough/main.py index 207dd5667d6..cc57ceac50e 100644 --- a/litellm/passthrough/main.py +++ b/litellm/passthrough/main.py @@ -112,20 +112,19 @@ async def allm_passthrough_route( # Only call raise_for_status if it's a Response object (not a generator) if isinstance(response, httpx.Response): - try: - response.raise_for_status() - except httpx.HTTPStatusError as e: - error_text = await e.response.aread() - error_text_str = error_text.decode("utf-8") - raise Exception(error_text_str) + response.raise_for_status() return response else: # This shouldn't happen when allm_passthrough_route=True, but handle it for type safety raise Exception("Expected coroutine from async passthrough route") + except httpx.HTTPStatusError as e: + # For HTTP errors, re-raise as-is to preserve the original error details + # The caller (e.g., proxy layer) can handle conversion to appropriate response format + raise e except Exception as e: - # For passthrough routes, we need to get the provider config to properly handle errors + # For other exceptions, use provider-specific error handling from litellm.types.utils import LlmProviders from litellm.utils import ProviderConfigManager diff --git a/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py b/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py index c6cd29ffdeb..11fc3babd66 100644 --- a/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py @@ -570,25 +570,46 @@ async def handle_bedrock_passthrough_router_model( ) # Call router passthrough - result = await llm_router.allm_passthrough_route( - model=model, - method=request.method, - endpoint=endpoint, - request_query_params=request.query_params, - request_headers=dict(request.headers), - stream=is_streaming, - content=None, - data=None, - files=None, - json=( - request_body - if request.headers.get("content-type") == "application/json" - else None - ), - params=None, - headers=None, - cookies=None, - ) + try: + result = await llm_router.allm_passthrough_route( + model=model, + method=request.method, + endpoint=endpoint, + request_query_params=request.query_params, + request_headers=dict(request.headers), + stream=is_streaming, + content=None, + data=None, + files=None, + json=( + request_body + if request.headers.get("content-type") == "application/json" + else None + ), + params=None, + headers=None, + cookies=None, + ) + except httpx.HTTPStatusError as e: + # Handle HTTP errors from the provider by converting to HTTPException + error_body = await e.response.aread() + error_text = error_body.decode("utf-8") + + raise HTTPException( + status_code=e.response.status_code, + detail={"error": error_text}, + ) + except Exception as e: + from litellm.llms.base_llm.chat.transformation import BaseLLMException + + # If it's a BaseLLMException (from non-HTTP errors), convert to HTTPException + if isinstance(e, BaseLLMException): + raise HTTPException( + status_code=e.status_code, + detail={"error": e.message}, + ) + # Re-raise any other exceptions + raise e # Handle streaming response if is_streaming: diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml index 1d4fc0937ec..13532fbe7fe 100644 --- a/litellm/proxy/proxy_config.yaml +++ b/litellm/proxy/proxy_config.yaml @@ -3,6 +3,11 @@ model_list: litellm_params: model: mistral/* - model_name: special-bedrock-model + litellm_params: + model: bedrock/us.anthropic.claude-3-5-sonnet-20240620-v1:0 + aws_region_name: us-west-2 + custom_llm_provider: bedrock + - model_name: aws/anthropic/bedrock-claude-3-5-sonnet-v1 litellm_params: model: bedrock/us.anthropic.claude-3-5-sonnet-20240620-v1:0 aws_region_name: us-west-2 diff --git a/tests/test_litellm/proxy/pass_through_endpoints/test_llm_pass_through_endpoints.py b/tests/test_litellm/proxy/pass_through_endpoints/test_llm_pass_through_endpoints.py index 38d5c5cdb6d..6eeca946190 100644 --- a/tests/test_litellm/proxy/pass_through_endpoints/test_llm_pass_through_endpoints.py +++ b/tests/test_litellm/proxy/pass_through_endpoints/test_llm_pass_through_endpoints.py @@ -1004,7 +1004,6 @@ class TestBedrockLLMProxyRoute: """ from fastapi import HTTPException - from litellm.llms.base_llm.chat.transformation import BaseLLMException from litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints import ( handle_bedrock_passthrough_router_model, ) @@ -1025,14 +1024,21 @@ class TestBedrockLLMProxyRoute: bedrock_error_message = '{"message":"ContentBlock object at messages.0.content.0 must set one of the following keys: text, image, toolUse, toolResult, document, video."}' - mock_llm_router = Mock() - mock_llm_router.allm_passthrough_route = AsyncMock( - side_effect=BaseLLMException( - status_code=400, - message=bedrock_error_message - ) + # Create a mock httpx.Response for the error + mock_error_response = Mock(spec=httpx.Response) + mock_error_response.status_code = 400 + mock_error_response.aread = AsyncMock(return_value=bedrock_error_message.encode('utf-8')) + + # Create the HTTPStatusError + mock_http_error = httpx.HTTPStatusError( + message="Bad Request", + request=Mock(spec=httpx.Request), + response=mock_error_response, ) + mock_llm_router = Mock() + mock_llm_router.allm_passthrough_route = AsyncMock(side_effect=mock_http_error) + endpoint = "model/test-model/converse" model = "test-model"