mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-21 00:21:49 +00:00
added functionality to propagate bedrock guardrail errors down to litellm
This commit is contained in:
parent
d267c69086
commit
0e434f5e36
2 changed files with 69 additions and 10 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue