fix(guardrails): emit the guardrail block detail as JSON in the stream error frame

This commit is contained in:
Yucheng Zhu 2026-08-11 20:17:25 -07:00
parent f9e4f9f8a6
commit c2fa42c8b6
2 changed files with 9 additions and 5 deletions

View file

@ -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(),
)

View file

@ -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: