From 9578efef61c7efcd762a95fe181051e7033af500 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Wed, 25 Feb 2026 20:53:29 -0800 Subject: [PATCH] refactor(realtime-guardrails): reuse pre_call/post_call hooks instead of realtime-specific hooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes realtime_input_transcription and realtime_output_text from GuardrailEventHooks. Realtime input guardrails now fire on pre_call, realtime output guardrails now fire on post_call. pre_call and post_call are cross-modality promises — a guardrail registered with mode: pre_call runs on regular completions AND realtime input; post_call runs on completions AND realtime output text. --- litellm/litellm_core_utils/realtime_streaming.py | 12 ++++++------ .../litellm_content_filter/content_filter.py | 2 -- litellm/types/guardrails.py | 2 -- .../test_realtime_streaming.py | 6 +++--- .../realtime/test_output_guardrail.py | 16 ++++++++-------- 5 files changed, 17 insertions(+), 21 deletions(-) diff --git a/litellm/litellm_core_utils/realtime_streaming.py b/litellm/litellm_core_utils/realtime_streaming.py index b370a837390..749cf468444 100644 --- a/litellm/litellm_core_utils/realtime_streaming.py +++ b/litellm/litellm_core_utils/realtime_streaming.py @@ -214,7 +214,7 @@ class RealTimeStreaming: executor.submit(self.logging_obj.success_handler(self.messages)) def _has_realtime_guardrails(self) -> bool: - """Return True if any callback is registered for realtime_input_transcription.""" + """Return True if any callback is registered for pre_call (runs across all modalities).""" from litellm.integrations.custom_guardrail import CustomGuardrail from litellm.types.guardrails import GuardrailEventHooks @@ -222,7 +222,7 @@ class RealTimeStreaming: isinstance(cb, CustomGuardrail) and cb.should_run_guardrail( data={}, - event_type=GuardrailEventHooks.realtime_input_transcription, + event_type=GuardrailEventHooks.pre_call, ) for cb in litellm.callbacks ) @@ -247,7 +247,7 @@ class RealTimeStreaming: if ( callback.should_run_guardrail( data={"transcript": transcript}, - event_type=GuardrailEventHooks.realtime_input_transcription, + event_type=GuardrailEventHooks.pre_call, ) is not True ): @@ -302,7 +302,7 @@ class RealTimeStreaming: return False def _has_realtime_output_guardrails(self) -> bool: - """Return True if any callback is registered for realtime_output_text.""" + """Return True if any callback is registered for post_call (runs across all modalities).""" from litellm.integrations.custom_guardrail import CustomGuardrail from litellm.types.guardrails import GuardrailEventHooks @@ -310,7 +310,7 @@ class RealTimeStreaming: isinstance(cb, CustomGuardrail) and cb.should_run_guardrail( data={}, - event_type=GuardrailEventHooks.realtime_output_text, + event_type=GuardrailEventHooks.post_call, ) for cb in litellm.callbacks ) @@ -332,7 +332,7 @@ class RealTimeStreaming: if ( callback.should_run_guardrail( data={"text": text}, - event_type=GuardrailEventHooks.realtime_output_text, + event_type=GuardrailEventHooks.post_call, ) is not True ): diff --git a/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py b/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py index e9dfa96e371..df71217c34c 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py +++ b/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py @@ -193,8 +193,6 @@ class ContentFilterGuardrail(CustomGuardrail): GuardrailEventHooks.pre_call, GuardrailEventHooks.post_call, GuardrailEventHooks.during_call, - GuardrailEventHooks.realtime_input_transcription, - GuardrailEventHooks.realtime_output_text, ], event_hook=event_hook or GuardrailEventHooks.pre_call, default_on=default_on, diff --git a/litellm/types/guardrails.py b/litellm/types/guardrails.py index 37326bcd8cd..c14bb62fa28 100644 --- a/litellm/types/guardrails.py +++ b/litellm/types/guardrails.py @@ -773,8 +773,6 @@ class GuardrailEventHooks(str, Enum): logging_only = "logging_only" pre_mcp_call = "pre_mcp_call" during_mcp_call = "during_mcp_call" - realtime_input_transcription = "realtime_input_transcription" - realtime_output_text = "realtime_output_text" class DynamicGuardrailParams(TypedDict): diff --git a/tests/test_litellm/litellm_core_utils/test_realtime_streaming.py b/tests/test_litellm/litellm_core_utils/test_realtime_streaming.py index aaaab95ce6f..ae1a961548e 100644 --- a/tests/test_litellm/litellm_core_utils/test_realtime_streaming.py +++ b/tests/test_litellm/litellm_core_utils/test_realtime_streaming.py @@ -383,7 +383,7 @@ async def test_realtime_guardrail_blocks_prompt_injection(): guardrail = PromptInjectionGuardrail( guardrail_name="test_injection_guard", - event_hook=GuardrailEventHooks.realtime_input_transcription, + event_hook=GuardrailEventHooks.pre_call, default_on=True, ) litellm.callbacks = [guardrail] @@ -466,7 +466,7 @@ async def test_realtime_guardrail_allows_clean_transcript(): guardrail = PromptInjectionGuardrail( guardrail_name="test_injection_guard", - event_hook=GuardrailEventHooks.realtime_input_transcription, + event_hook=GuardrailEventHooks.pre_call, default_on=True, ) litellm.callbacks = [guardrail] @@ -530,7 +530,7 @@ async def test_realtime_session_created_injects_create_response_false(): guardrail = DummyGuardrail( guardrail_name="dummy", - event_hook=GuardrailEventHooks.realtime_input_transcription, + event_hook=GuardrailEventHooks.pre_call, default_on=True, ) litellm.callbacks = [guardrail] diff --git a/tests/test_litellm/realtime/test_output_guardrail.py b/tests/test_litellm/realtime/test_output_guardrail.py index 5e5878f3597..007589c1b05 100644 --- a/tests/test_litellm/realtime/test_output_guardrail.py +++ b/tests/test_litellm/realtime/test_output_guardrail.py @@ -47,8 +47,8 @@ class _BlockingGuardrail(CustomGuardrail): def __init__(self): super().__init__( guardrail_name="test-content-filter", - supported_event_hooks=[GuardrailEventHooks.realtime_output_text], - event_hook=GuardrailEventHooks.realtime_output_text, + supported_event_hooks=[GuardrailEventHooks.post_call], + event_hook=GuardrailEventHooks.post_call, default_on=True, ) @@ -71,8 +71,8 @@ class _PassthroughGuardrail(CustomGuardrail): def __init__(self): super().__init__( guardrail_name="test-passthrough", - supported_event_hooks=[GuardrailEventHooks.realtime_output_text], - event_hook=GuardrailEventHooks.realtime_output_text, + supported_event_hooks=[GuardrailEventHooks.post_call], + event_hook=GuardrailEventHooks.post_call, default_on=True, ) @@ -287,11 +287,11 @@ class TestOutputGuardrailNoGuardrail: class TestGuardrailEventHook: - """realtime_output_text is a valid GuardrailEventHooks enum value.""" + """post_call runs across all modalities including realtime output.""" def test_hook_exists(self): from litellm.types.guardrails import GuardrailEventHooks - assert hasattr(GuardrailEventHooks, "realtime_output_text") - assert GuardrailEventHooks.realtime_output_text == "realtime_output_text" - print(f"\nāœ… PASS: GuardrailEventHooks.realtime_output_text = {GuardrailEventHooks.realtime_output_text!r}") + assert hasattr(GuardrailEventHooks, "post_call") + assert GuardrailEventHooks.post_call == "post_call" + print(f"\nāœ… PASS: GuardrailEventHooks.post_call = {GuardrailEventHooks.post_call!r}")