fix: extract human-readable message from dict detail in SSE error frame

This commit is contained in:
Nicholas Couture 2026-04-24 15:40:47 +10:00
parent 46dd136103
commit b99c44af93
No known key found for this signature in database
2 changed files with 31 additions and 5 deletions

View file

@ -1332,9 +1332,15 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM):
# An async generator cannot propagate an exception as an HTTP error response
# after the 200 header has been written. Convert hard-block exceptions to
# in-band SSE error frames so the client receives a structured error.
# _get_http_exception_for_blocked_guardrail raises with a dict detail;
# extract the human-readable "error" key
if isinstance(e.detail, dict):
message = e.detail.get("error", str(e.detail))
else:
message = str(e.detail)
error_data = {
"error": {
"message": str(e.detail),
"message": message,
"code": e.status_code,
"type": "guardrail_violation",
}

View file

@ -2218,7 +2218,16 @@ async def test_streaming_hook_converts_http_exception_to_sse_error_frame_paralle
for c in chunks:
yield c
hard_block = HTTPException(status_code=400, detail="Violated guardrail policy")
# Use the production dict-detail shape from _get_http_exception_for_blocked_guardrail
hard_block = HTTPException(
status_code=400,
detail={
"error": "Violated guardrail policy",
"bedrock_guardrail_response": "",
"guardrailIdentifier": "test-id",
"guardrailVersion": "DRAFT",
},
)
with patch.object(
guardrail, "make_bedrock_api_request", AsyncMock(side_effect=hard_block)
@ -2240,7 +2249,8 @@ async def test_streaming_hook_converts_http_exception_to_sse_error_frame_paralle
payload = _json.loads(error_frame[len("data: "):].rstrip())
assert payload["error"]["code"] == 400
assert payload["error"]["type"] == "guardrail_violation"
assert "Violated guardrail policy" in payload["error"]["message"]
# Must be a clean string, not a Python dict repr
assert payload["error"]["message"] == "Violated guardrail policy"
@pytest.mark.asyncio
@ -2271,7 +2281,16 @@ async def test_streaming_hook_converts_http_exception_to_sse_error_frame_output_
for c in chunks:
yield c
hard_block = HTTPException(status_code=400, detail="Violated guardrail policy")
# Use the production dict-detail shape from _get_http_exception_for_blocked_guardrail
hard_block = HTTPException(
status_code=400,
detail={
"error": "Violated guardrail policy",
"bedrock_guardrail_response": "",
"guardrailIdentifier": "test-id",
"guardrailVersion": "DRAFT",
},
)
with patch.object(
guardrail, "make_bedrock_api_request", AsyncMock(side_effect=hard_block)
@ -2292,4 +2311,5 @@ async def test_streaming_hook_converts_http_exception_to_sse_error_frame_output_
payload = _json.loads(error_frame[len("data: "):].rstrip())
assert payload["error"]["code"] == 400
assert payload["error"]["type"] == "guardrail_violation"
assert "Violated guardrail policy" in payload["error"]["message"]
# Must be a clean string, not a Python dict repr
assert payload["error"]["message"] == "Violated guardrail policy"