Skip to content

Commit 9a6b165

Browse files
Merge pull request #14707 from ARajan1084/bedrock-guardrail-silent-failure-correction
fix: Bedrock guardrail silent failure correction
2 parents 2249bad + 14b3cc2 commit 9a6b165

File tree

3 files changed

+33
-109
lines changed

3 files changed

+33
-109
lines changed

litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -384,9 +384,6 @@ async def make_bedrock_api_request(
384384
)
385385
#########################################################
386386
if response.status_code == 200:
387-
# check if the response contains an error
388-
if self._check_bedrock_response_for_exception(response=response):
389-
raise self._get_http_exception_for_failed_guardrail(response)
390387
# check if the response was flagged
391388
_json_response = response.json()
392389
redacted_response = _redact_pii_matches(_json_response)
@@ -452,19 +449,6 @@ def _get_bedrock_guardrail_response_status(
452449
return "success"
453450
return "failure"
454451

455-
def _get_http_exception_for_failed_guardrail(
456-
self, response: httpx.Response
457-
) -> HTTPException:
458-
return HTTPException(
459-
status_code=400,
460-
detail={
461-
"error": "Guardrail application failed.",
462-
"bedrock_guardrail_response": json.loads(
463-
response.content.decode("utf-8")
464-
).get("Output", {}),
465-
},
466-
)
467-
468452
def _get_http_exception_for_blocked_guardrail(
469453
self, response: BedrockGuardrailResponse
470454
) -> HTTPException:

tests/test_litellm/proxy/guardrails/guardrail_hooks/test_bedrock_guardrails.py

Lines changed: 0 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,76 +1049,3 @@ async def test_bedrock_guardrail_parameter_takes_precedence_over_env(monkeypatch
10491049
), f"Expected parameter endpoint to take precedence. Got: {prepped_request.url}"
10501050

10511051
print(f"Parameter precedence test passed. URL: {prepped_request.url}")
1052-
1053-
@pytest.mark.asyncio
1054-
async def test_bedrock_guardrail_200_with_exception_in_output_raises_and_logs_failure():
1055-
"""
1056-
When Bedrock returns HTTP 200 but the body contains Output.__type with 'Exception',
1057-
the guardrail should:
1058-
- raise an HTTPException(400) with the Output payload in detail
1059-
- log the request trace with guardrail_status='failure'
1060-
"""
1061-
guardrail = BedrockGuardrail(
1062-
guardrailIdentifier="test-guardrail", guardrailVersion="DRAFT"
1063-
)
1064-
1065-
# Mock a Bedrock "success" HTTP status but an Exception embedded in the body
1066-
payload = {
1067-
"Output": {
1068-
"__type": "com.amazonaws#InternalServerException",
1069-
"message": "Something went wrong upstream",
1070-
},
1071-
"action": "NONE",
1072-
}
1073-
mock_resp = MagicMock()
1074-
mock_resp.status_code = 200
1075-
mock_resp.content = json.dumps(payload).encode("utf-8")
1076-
mock_resp.text = json.dumps(payload)
1077-
mock_resp.json.return_value = payload
1078-
1079-
# Minimal request data
1080-
request_data = {
1081-
"model": "gpt-4o",
1082-
"messages": [{"role": "user", "content": "hello"}],
1083-
}
1084-
1085-
# Mock creds and request prep
1086-
mock_credentials = MagicMock()
1087-
mock_credentials.access_key = "ak"
1088-
mock_credentials.secret_key = "sk"
1089-
mock_credentials.token = None
1090-
1091-
with patch.object(
1092-
guardrail.async_handler, "post", new_callable=AsyncMock
1093-
) as mock_post, patch.object(
1094-
guardrail, "_load_credentials", return_value=(mock_credentials, "us-east-1")
1095-
), patch.object(
1096-
guardrail,
1097-
"_prepare_request",
1098-
return_value=MagicMock(url="http://example", headers={}, body=b""),
1099-
), patch.object(
1100-
guardrail, "add_standard_logging_guardrail_information_to_request_data"
1101-
) as mock_add_trace:
1102-
mock_post.return_value = mock_resp
1103-
1104-
with pytest.raises(HTTPException) as excinfo:
1105-
await guardrail.make_bedrock_api_request(
1106-
source="INPUT",
1107-
messages=request_data["messages"],
1108-
request_data=request_data,
1109-
)
1110-
1111-
# 1) Raised HTTPException with 400 status
1112-
err = excinfo.value
1113-
assert err.status_code == 400
1114-
assert err.detail["error"] == "Guardrail application failed."
1115-
1116-
# 2) Detail includes the Output object from the Bedrock body
1117-
assert err.detail["bedrock_guardrail_response"] == payload["Output"]
1118-
1119-
# 3) Trace logging received a 'failure' status
1120-
assert mock_add_trace.called
1121-
_, kwargs = mock_add_trace.call_args
1122-
assert kwargs["guardrail_status"] == "failure"
1123-
# And the JSON passed to tracing is the same response we received
1124-
assert kwargs["guardrail_json_response"] == payload

ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.tsx

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import React, { useState } from "react";
2+
import { Tooltip } from "antd";
23
import PresidioDetectedEntities from "./PresidioDetectedEntities";
34
import BedrockGuardrailDetails, {
45
BedrockGuardrailResponse,
5-
} from "@/components/view_logs/GuardrailViewer/BedrockGuardrailDetails"
6+
} from "@/components/view_logs/GuardrailViewer/BedrockGuardrailDetails";
67

78
interface RecognitionMetadata {
89
recognizer_name: string;
@@ -44,9 +45,13 @@ const GuardrailViewer = ({ data }: GuardrailViewerProps) => {
4445
// Default to presidio for backwards compatibility
4546
const guardrailProvider = data.guardrail_provider ?? "presidio";
4647

47-
if (!data) {
48-
return null;
49-
}
48+
if (!data) return null;
49+
50+
const isSuccess =
51+
typeof data.guardrail_status === "string" &&
52+
data.guardrail_status.toLowerCase() === "success";
53+
54+
const tooltipTitle = isSuccess ? null : "Guardrail failed to run.";
5055

5156
// Calculate total masked entities
5257
const totalMaskedEntities = data.masked_entity_count ?
@@ -73,13 +78,18 @@ const GuardrailViewer = ({ data }: GuardrailViewerProps) => {
7378
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
7479
</svg>
7580
<h3 className="text-lg font-medium">Guardrail Information</h3>
76-
<span className={`ml-3 px-2 py-1 rounded-md text-xs font-medium inline-block ${
77-
data.guardrail_status === "success"
78-
? 'bg-green-100 text-green-800'
79-
: 'bg-red-100 text-red-800'
80-
}`}>
81-
{data.guardrail_status}
82-
</span>
81+
82+
{/* Header status chip with tooltip */}
83+
<Tooltip title={tooltipTitle} placement="top" arrow destroyTooltipOnHide>
84+
<span
85+
className={`ml-3 px-2 py-1 rounded-md text-xs font-medium inline-block ${
86+
isSuccess ? "bg-green-100 text-green-800" : "bg-red-100 text-red-800 cursor-help"
87+
}`}
88+
>
89+
{data.guardrail_status}
90+
</span>
91+
</Tooltip>
92+
8393
{totalMaskedEntities > 0 && (
8494
<span className="ml-3 px-2 py-1 bg-blue-50 text-blue-700 rounded-md text-xs font-medium">
8595
{totalMaskedEntities} masked {totalMaskedEntities === 1 ? 'entity' : 'entities'}
@@ -104,15 +114,18 @@ const GuardrailViewer = ({ data }: GuardrailViewerProps) => {
104114
</div>
105115
<div className="flex">
106116
<span className="font-medium w-1/3">Status:</span>
107-
<span className={`px-2 py-1 rounded-md text-xs font-medium inline-block ${
108-
data.guardrail_status === "success"
109-
? 'bg-green-100 text-green-800'
110-
: 'bg-red-100 text-red-800'
111-
}`}>
112-
{data.guardrail_status}
113-
</span>
117+
<Tooltip title={tooltipTitle} placement="top" arrow destroyTooltipOnHide>
118+
<span
119+
className={`px-2 py-1 rounded-md text-xs font-medium inline-block ${
120+
isSuccess ? "bg-green-100 text-green-800" : "bg-red-100 text-red-800 cursor-help"
121+
}`}
122+
>
123+
{data.guardrail_status}
124+
</span>
125+
</Tooltip>
114126
</div>
115127
</div>
128+
116129
<div className="space-y-2">
117130
<div className="flex">
118131
<span className="font-medium w-1/3">Start Time:</span>
@@ -158,6 +171,6 @@ const GuardrailViewer = ({ data }: GuardrailViewerProps) => {
158171
)}
159172
</div>
160173
);
161-
}
174+
};
162175

163-
export default GuardrailViewer;
176+
export default GuardrailViewer;

0 commit comments

Comments
 (0)