added functionality to propagate bedrock guardrail errors down to litellm

This commit is contained in:
shivam 2026-02-03 21:57:56 -08:00
parent d267c69086
commit 0e434f5e36
2 changed files with 69 additions and 10 deletions

View file

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

View file

@ -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():