mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-11 08:38:24 +00:00
fix(guardrails): key the streamed block decision on status, not detail shape
InvokeGuardrailChecks details a Mapping on its 500 for an unparseable response, so a detail-shape test read that outage as a policy block and framed it as a 200 guardrail_error. Both block sites raise 400, so gate on the status too.
This commit is contained in:
parent
ab9bb303cf
commit
f0da259f66
2 changed files with 21 additions and 5 deletions
|
|
@ -2626,10 +2626,10 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM):
|
|||
)
|
||||
except HTTPException as block_exc:
|
||||
block_detail: Final = block_exc.detail
|
||||
# Only a policy block carries a dict detail; every API/transport failure details a
|
||||
# plain string (see _is_input_too_large_error). Re-raising those keeps their real
|
||||
# status instead of reporting an outage as a guardrail decision
|
||||
if not raw_sse or not isinstance(block_detail, Mapping):
|
||||
# A policy block is the only 400 carrying a structured detail; a service failure
|
||||
# either details a plain string or reports a non-400 status, and re-raising those
|
||||
# keeps their real status instead of reporting an outage as a guardrail decision
|
||||
if not raw_sse or block_exc.status_code != 400 or not isinstance(block_detail, Mapping):
|
||||
raise
|
||||
# past the keepalive ping the headers are already flushed, so a raise cannot reach
|
||||
# the client; the block travels as a frame
|
||||
|
|
|
|||
|
|
@ -2958,7 +2958,7 @@ async def test_streaming_hook_block_stream_keeps_upstream_identity():
|
|||
async def test_streaming_hook_reraises_guardrail_service_failures():
|
||||
"""A Bedrock outage must keep its status, not be reported to the caller as a guardrail decision.
|
||||
|
||||
Only a policy block details a Mapping; every API/transport failure details a plain string.
|
||||
A policy block is the only 400 detailing a Mapping.
|
||||
"""
|
||||
guardrail = _sse_guardrail()
|
||||
|
||||
|
|
@ -2972,6 +2972,22 @@ async def test_streaming_hook_reraises_guardrail_service_failures():
|
|||
assert exc.value.status_code == 500
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_streaming_hook_reraises_a_service_failure_that_details_a_mapping():
|
||||
"""InvokeGuardrailChecks details a Mapping on its 500, so detail shape alone cannot mean "block"."""
|
||||
guardrail = _sse_guardrail()
|
||||
|
||||
with patch.object(guardrail, "make_bedrock_api_request", new_callable=AsyncMock) as mock_api:
|
||||
mock_api.side_effect = HTTPException(
|
||||
status_code=500,
|
||||
detail={"error": "Bedrock InvokeGuardrailChecks returned an unexpected response shape"},
|
||||
)
|
||||
with pytest.raises(HTTPException) as exc:
|
||||
await _drain_streaming_hook(guardrail)
|
||||
|
||||
assert exc.value.status_code == 500
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_streaming_block_error_frame_message_is_a_string():
|
||||
"""AnthropicErrorDetail.message is typed str, built by the proxy's own detail serializer."""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue