diff --git a/litellm/proxy/guardrails/anthropic_sse.py b/litellm/proxy/guardrails/anthropic_sse.py index 43a812cb4b5..3c7580a14a4 100644 --- a/litellm/proxy/guardrails/anthropic_sse.py +++ b/litellm/proxy/guardrails/anthropic_sse.py @@ -92,16 +92,17 @@ def model_response_text(response: ModelResponse) -> str: ) -def anthropic_sse_error_frames(message: str) -> tuple[bytes, ...]: +def anthropic_sse_error_frames(message: str | Mapping[str, object]) -> tuple[bytes, ...]: """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. + travel as a frame. A structured message is emitted as JSON rather than stringified, so the + guardrail's own detail stays machine readable. """ - detail: Final = json.dumps(message) + body: Final = json.dumps(message, default=str) return ( f'event: error\ndata: {{"type": "error", "error": {{"type": "guardrail_error", ' - f'"message": {detail}}}}}\n\n'.encode(), + f'"message": {body}}}}}\n\n'.encode(), ) diff --git a/litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py b/litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py index a6852b6fb0b..d3c3a61201b 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py +++ b/litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py @@ -2625,7 +2625,10 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM): raise # A keepalive ping may already have flushed the response headers, so a raise # cannot reach the client; deliver the block as an error frame instead - for error_frame in anthropic_sse_error_frames(str(block_exc.detail)): + block_detail: Final = block_exc.detail + for error_frame in anthropic_sse_error_frames( + block_detail if isinstance(block_detail, Mapping) else str(block_detail) + ): yield error_frame return except ModifyResponseException as e: