mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-12 08:42:18 +00:00
fix(guardrails): keep the stream error message a string and drop an unreachable branch
This commit is contained in:
parent
4ab01f24d5
commit
249733e71c
3 changed files with 23 additions and 6 deletions
|
|
@ -102,10 +102,13 @@ def anthropic_sse_error_frames(message: str | Mapping[str, object]) -> tuple[byt
|
|||
"""Anthropic error event, for a failure discovered after the response headers were flushed.
|
||||
|
||||
Once a keepalive ping has been sent a raise cannot reach the client, so the failure has to
|
||||
travel as a frame. A structured message is emitted as JSON rather than stringified, so the
|
||||
guardrail's own detail stays machine readable.
|
||||
travel as a frame. A structured detail is JSON-serialized into the string so it stays machine
|
||||
readable without violating the string-typed contract.
|
||||
"""
|
||||
body: Final = json.dumps(message, default=str)
|
||||
# AnthropicErrorDetail.message is a str, so a structured detail is serialized into the string
|
||||
# rather than nested as an object
|
||||
text: Final = message if isinstance(message, str) else json.dumps(message, default=str)
|
||||
body: Final = json.dumps(text)
|
||||
return (
|
||||
f'event: error\ndata: {{"type": "error", "error": {{"type": "guardrail_error", '
|
||||
f'"message": {body}}}}}\n\n'.encode(),
|
||||
|
|
|
|||
|
|
@ -2664,9 +2664,6 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM):
|
|||
)
|
||||
if _original_usage is not None:
|
||||
assembled_model_response.usage = _original_usage
|
||||
if raw_sse:
|
||||
assembled_model_response.id = _pre_block_response.id
|
||||
assembled_model_response.model = _pre_block_response.model
|
||||
output_guardrail_response = None
|
||||
|
||||
#########################################################################
|
||||
|
|
|
|||
|
|
@ -2954,6 +2954,23 @@ async def test_streaming_hook_block_stream_keeps_upstream_identity():
|
|||
assert b"my-proxy-alias" not in body
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_streaming_block_error_frame_message_is_a_string():
|
||||
"""AnthropicErrorDetail.message is typed str, so a structured guardrail detail is serialized."""
|
||||
guardrail = _sse_guardrail()
|
||||
|
||||
with patch.object(guardrail, "make_bedrock_api_request", new_callable=AsyncMock) as mock_api:
|
||||
mock_api.side_effect = HTTPException(
|
||||
status_code=400, detail={"error": "Violated guardrail policy", "guardrailIdentifier": "gid"}
|
||||
)
|
||||
delivered = await _drain_streaming_hook(guardrail)
|
||||
|
||||
frame = next(line for line in b"".join(delivered).decode().splitlines() if line.startswith("data: "))
|
||||
message = json.loads(frame[6:])["error"]["message"]
|
||||
assert isinstance(message, str)
|
||||
assert json.loads(message)["error"] == "Violated guardrail policy"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_streaming_hook_fails_closed_when_raw_sse_cannot_be_assembled():
|
||||
"""An unscannable stream must not be delivered: forwarding it silently disables the guardrail."""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue