diff --git a/litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py b/litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py index d3e8a74945f..041f7c1ca85 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py +++ b/litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py @@ -461,12 +461,37 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM): data=prepared_request.body, # type: ignore headers=prepared_request.headers, # type: ignore ) + except HTTPException: + # Propagate HTTPException (e.g. from non-200 path) as-is + raise except Exception as e: + # If this is an HTTP error with a response body (e.g. httpx.HTTPStatusError), + # extract the AWS error message and propagate it + response = getattr(e, "response", None) + if isinstance(response, httpx.Response): + try: + status_code, detail_message = ( + self._parse_bedrock_guardrail_error_response(response) + ) + self.add_standard_logging_guardrail_information_to_request_data( + guardrail_provider=self.guardrail_provider, + guardrail_json_response={"error": detail_message}, + request_data=request_data or {}, + guardrail_status="guardrail_failed_to_respond", + start_time=start_time.timestamp(), + end_time=datetime.now().timestamp(), + duration=(datetime.now() - start_time).total_seconds(), + event_type=event_type, + ) + raise HTTPException( + status_code=status_code, detail=detail_message + ) from e + except HTTPException: + raise # Endpoint down, timeout, or other HTTP/network errors verbose_proxy_logger.error( "Bedrock AI: failed to make guardrail request: %s", str(e) ) - # Add guardrail information with failure status self.add_standard_logging_guardrail_information_to_request_data( guardrail_provider=self.guardrail_provider, guardrail_json_response={"error": str(e)}, @@ -477,7 +502,6 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM): duration=(datetime.now() - start_time).total_seconds(), event_type=event_type, ) - # Re-raise the exception to maintain existing behavior raise ######################################################### @@ -509,11 +533,15 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM): bedrock_guardrail_response ) else: + status_code, detail_message = self._parse_bedrock_guardrail_error_response( + httpx_response + ) verbose_proxy_logger.error( "Bedrock AI: error in response. Status code: %s, response: %s", httpx_response.status_code, httpx_response.text, ) + raise HTTPException(status_code=status_code, detail=detail_message) return bedrock_guardrail_response @@ -579,6 +607,34 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM): return "success" return "guardrail_failed_to_respond" + def _parse_bedrock_guardrail_error_response( + self, response: httpx.Response + ) -> Tuple[int, str]: + """ + Parse AWS Bedrock guardrail error response body to extract status code and message. + + AWS may return shapes like {"message": "..."} or {"error": {"message": "..."}}. + Returns (status_code, message) for use in HTTPException. + """ + status_code = response.status_code + message = "Bedrock guardrail request failed" + try: + body = response.json() + except Exception: + text = getattr(response, "text", None) or "" + if isinstance(text, str) and text.strip(): + return (status_code, text.strip()) + return (status_code, message) + if isinstance(body, dict): + if isinstance(body.get("message"), str): + return (status_code, body["message"]) + err = body.get("error") + if isinstance(err, dict) and isinstance(err.get("message"), str): + return (status_code, err["message"]) + if isinstance(err, str): + return (status_code, err) + return (status_code, message) + def _get_http_exception_for_blocked_guardrail( self, response: BedrockGuardrailResponse ) -> Union[HTTPException, GuardrailInterventionNormalStringError]: @@ -1392,6 +1448,9 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM): inputs["texts"] = masked_texts return inputs + except HTTPException: + # Propagate AWS guardrail error (e.g. "input is too long") to the client + raise except Exception as e: verbose_proxy_logger.error( "Bedrock Guardrail: Failed to apply guardrail: %s", str(e) diff --git a/tests/enterprise/litellm_enterprise/proxy/guardrails/test_bedrock_apply_guardrail.py b/tests/enterprise/litellm_enterprise/proxy/guardrails/test_bedrock_apply_guardrail.py index 60d4f479733..21e6765602d 100644 --- a/tests/enterprise/litellm_enterprise/proxy/guardrails/test_bedrock_apply_guardrail.py +++ b/tests/enterprise/litellm_enterprise/proxy/guardrails/test_bedrock_apply_guardrail.py @@ -75,17 +75,16 @@ async def test_bedrock_apply_guardrail_blocked(): }, ) - # Test the apply_guardrail method should raise an exception - with pytest.raises(Exception) as exc_info: + # Test the apply_guardrail method propagates HTTPException (AWS error) to the client + with pytest.raises(HTTPException) as exc_info: await guardrail.apply_guardrail( inputs={"texts": ["This is blocked content"]}, request_data={}, input_type="request", ) - # The apply_guardrail method wraps the original exception in a generic Exception - assert "Bedrock guardrail failed:" in str(exc_info.value) - assert "Violated guardrail policy" in str(exc_info.value) + assert exc_info.value.status_code == 400 + assert "Violated guardrail policy" in str(exc_info.value.detail) @pytest.mark.asyncio @@ -270,7 +269,7 @@ async def test_bedrock_apply_guardrail_filters_request_messages_when_flag_enable }, ) - with pytest.raises(Exception, match="policy") as exc_info: + with pytest.raises(HTTPException, match="policy") as exc_info: await guardrail.apply_guardrail( inputs={"texts": ["blocked"]}, request_data=request_data, @@ -280,8 +279,9 @@ async def test_bedrock_apply_guardrail_filters_request_messages_when_flag_enable assert mock_api.called _, kwargs = mock_api.call_args assert kwargs["messages"] == [request_messages[-1]] - # The apply_guardrail method wraps the original exception in a generic Exception - assert "Bedrock guardrail failed:" in str(exc_info.value) + # HTTPException from guardrail is propagated so the client gets the AWS message + assert exc_info.value.status_code == 400 + assert "policy" in str(exc_info.value.detail) def test_bedrock_guardrail_filters_latest_user_message_when_enabled():