From 8fb7627bb525d080795d7bf2bfad1442a8ab59a2 Mon Sep 17 00:00:00 2001 From: Yucheng Zhu Date: Wed, 12 Aug 2026 13:41:09 -0700 Subject: [PATCH] fix(guardrails): deliver an Anthropic block through the shared block-SSE builder --- .../guardrails/guardrail_hooks/bedrock_guardrails.py | 12 ++++++++++++ .../guardrail_hooks/test_bedrock_guardrails.py | 3 ++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py b/litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py index d3c3a61201b..abb5a185c1e 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py +++ b/litellm/proxy/guardrails/guardrail_hooks/bedrock_guardrails.py @@ -30,6 +30,7 @@ from litellm.constants import BEDROCK_APPLY_GUARDRAIL_CHUNK_BUDGET_CHARS from litellm.exceptions import ModifyResponseException from litellm.integrations.custom_guardrail import CustomGuardrail from litellm.litellm_core_utils.core_helpers import redact_nested_match_and_regex_keys +from litellm.llms.anthropic.chat.guardrail_translation.handler import AnthropicMessagesHandler from litellm.llms.base_llm.guardrail_translation.utils import ( effective_scan_only_tool_results_for_guardrail, ) @@ -2632,6 +2633,17 @@ class BedrockGuardrail(CustomGuardrail, BaseAWSLLM): yield error_frame return except ModifyResponseException as e: + if raw_sse: + # the shared per-endpoint block builder the unified guardrail path uses; it + # mints a fresh message id because the block is not the upstream message, but + # it reports exc.model, which defaults to the guardrail name rather than the model + e.model = _pre_block_response.model or e.model # rebind-ok: local synthetic block payload + if e.original_response is None: + # the builder reads usage off this; the upstream call was already paid for + e.original_response = _pre_block_response # rebind-ok: local synthetic block payload + for block_chunk in AnthropicMessagesHandler().build_block_sse_chunks(e, stream_started=False): + yield block_chunk + return # Preserve upstream usage from the LLM call we already # consumed. Non-streaming blocks carry it via # ModifyResponseException.original_response + diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_bedrock_guardrails.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_bedrock_guardrails.py index 32af8c628fe..8ba8aa1373d 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_bedrock_guardrails.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_bedrock_guardrails.py @@ -2948,7 +2948,8 @@ async def test_streaming_hook_block_stream_keeps_upstream_identity(): delivered = await _drain_streaming_hook(guardrail) body = b"".join(delivered) - assert b'"id": "msg_1"' in body + # the shared block builder mints a new message id: the block is not the upstream message + assert b'"id": "msg_' in body assert b'"model": "claude"' in body assert b"my-proxy-alias" not in body