Merge pull request #14707 from ARajan1084/bedrock-guardrail-silent-failure-correction

fix: Bedrock guardrail silent failure correction
This commit is contained in:
Krish Dholakia 2025-09-18 20:03:17 -07:00 committed by GitHub
commit 9a6b1651d2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 33 additions and 109 deletions

View file

@ -384,9 +384,6 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM):
)
#########################################################
if response.status_code == 200:
# check if the response contains an error
if self._check_bedrock_response_for_exception(response=response):
raise self._get_http_exception_for_failed_guardrail(response)
# check if the response was flagged
_json_response = response.json()
redacted_response = _redact_pii_matches(_json_response)
@ -452,19 +449,6 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM):
return "success"
return "failure"
def _get_http_exception_for_failed_guardrail(
self, response: httpx.Response
) -> HTTPException:
return HTTPException(
status_code=400,
detail={
"error": "Guardrail application failed.",
"bedrock_guardrail_response": json.loads(
response.content.decode("utf-8")
).get("Output", {}),
},
)
def _get_http_exception_for_blocked_guardrail(
self, response: BedrockGuardrailResponse
) -> HTTPException:

View file

@ -1049,76 +1049,3 @@ async def test_bedrock_guardrail_parameter_takes_precedence_over_env(monkeypatch
), f"Expected parameter endpoint to take precedence. Got: {prepped_request.url}"
print(f"Parameter precedence test passed. URL: {prepped_request.url}")
@pytest.mark.asyncio
async def test_bedrock_guardrail_200_with_exception_in_output_raises_and_logs_failure():
"""
When Bedrock returns HTTP 200 but the body contains Output.__type with 'Exception',
the guardrail should:
- raise an HTTPException(400) with the Output payload in detail
- log the request trace with guardrail_status='failure'
"""
guardrail = BedrockGuardrail(
guardrailIdentifier="test-guardrail", guardrailVersion="DRAFT"
)
# Mock a Bedrock "success" HTTP status but an Exception embedded in the body
payload = {
"Output": {
"__type": "com.amazonaws#InternalServerException",
"message": "Something went wrong upstream",
},
"action": "NONE",
}
mock_resp = MagicMock()
mock_resp.status_code = 200
mock_resp.content = json.dumps(payload).encode("utf-8")
mock_resp.text = json.dumps(payload)
mock_resp.json.return_value = payload
# Minimal request data
request_data = {
"model": "gpt-4o",
"messages": [{"role": "user", "content": "hello"}],
}
# Mock creds and request prep
mock_credentials = MagicMock()
mock_credentials.access_key = "ak"
mock_credentials.secret_key = "sk"
mock_credentials.token = None
with patch.object(
guardrail.async_handler, "post", new_callable=AsyncMock
) as mock_post, patch.object(
guardrail, "_load_credentials", return_value=(mock_credentials, "us-east-1")
), patch.object(
guardrail,
"_prepare_request",
return_value=MagicMock(url="http://example", headers={}, body=b""),
), patch.object(
guardrail, "add_standard_logging_guardrail_information_to_request_data"
) as mock_add_trace:
mock_post.return_value = mock_resp
with pytest.raises(HTTPException) as excinfo:
await guardrail.make_bedrock_api_request(
source="INPUT",
messages=request_data["messages"],
request_data=request_data,
)
# 1) Raised HTTPException with 400 status
err = excinfo.value
assert err.status_code == 400
assert err.detail["error"] == "Guardrail application failed."
# 2) Detail includes the Output object from the Bedrock body
assert err.detail["bedrock_guardrail_response"] == payload["Output"]
# 3) Trace logging received a 'failure' status
assert mock_add_trace.called
_, kwargs = mock_add_trace.call_args
assert kwargs["guardrail_status"] == "failure"
# And the JSON passed to tracing is the same response we received
assert kwargs["guardrail_json_response"] == payload

View file

@ -1,8 +1,9 @@
import React, { useState } from "react";
import { Tooltip } from "antd";
import PresidioDetectedEntities from "./PresidioDetectedEntities";
import BedrockGuardrailDetails, {
BedrockGuardrailResponse,
} from "@/components/view_logs/GuardrailViewer/BedrockGuardrailDetails"
} from "@/components/view_logs/GuardrailViewer/BedrockGuardrailDetails";
interface RecognitionMetadata {
recognizer_name: string;
@ -44,9 +45,13 @@ const GuardrailViewer = ({ data }: GuardrailViewerProps) => {
// Default to presidio for backwards compatibility
const guardrailProvider = data.guardrail_provider ?? "presidio";
if (!data) {
return null;
}
if (!data) return null;
const isSuccess =
typeof data.guardrail_status === "string" &&
data.guardrail_status.toLowerCase() === "success";
const tooltipTitle = isSuccess ? null : "Guardrail failed to run.";
// Calculate total masked entities
const totalMaskedEntities = data.masked_entity_count ?
@ -73,13 +78,18 @@ const GuardrailViewer = ({ data }: GuardrailViewerProps) => {
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
<h3 className="text-lg font-medium">Guardrail Information</h3>
<span className={`ml-3 px-2 py-1 rounded-md text-xs font-medium inline-block ${
data.guardrail_status === "success"
? 'bg-green-100 text-green-800'
: 'bg-red-100 text-red-800'
}`}>
{data.guardrail_status}
</span>
{/* Header status chip with tooltip */}
<Tooltip title={tooltipTitle} placement="top" arrow destroyTooltipOnHide>
<span
className={`ml-3 px-2 py-1 rounded-md text-xs font-medium inline-block ${
isSuccess ? "bg-green-100 text-green-800" : "bg-red-100 text-red-800 cursor-help"
}`}
>
{data.guardrail_status}
</span>
</Tooltip>
{totalMaskedEntities > 0 && (
<span className="ml-3 px-2 py-1 bg-blue-50 text-blue-700 rounded-md text-xs font-medium">
{totalMaskedEntities} masked {totalMaskedEntities === 1 ? 'entity' : 'entities'}
@ -104,15 +114,18 @@ const GuardrailViewer = ({ data }: GuardrailViewerProps) => {
</div>
<div className="flex">
<span className="font-medium w-1/3">Status:</span>
<span className={`px-2 py-1 rounded-md text-xs font-medium inline-block ${
data.guardrail_status === "success"
? 'bg-green-100 text-green-800'
: 'bg-red-100 text-red-800'
}`}>
{data.guardrail_status}
</span>
<Tooltip title={tooltipTitle} placement="top" arrow destroyTooltipOnHide>
<span
className={`px-2 py-1 rounded-md text-xs font-medium inline-block ${
isSuccess ? "bg-green-100 text-green-800" : "bg-red-100 text-red-800 cursor-help"
}`}
>
{data.guardrail_status}
</span>
</Tooltip>
</div>
</div>
<div className="space-y-2">
<div className="flex">
<span className="font-medium w-1/3">Start Time:</span>
@ -158,6 +171,6 @@ const GuardrailViewer = ({ data }: GuardrailViewerProps) => {
)}
</div>
);
}
};
export default GuardrailViewer;
export default GuardrailViewer;