diff --git a/src/agents/realtime/model_inputs.py b/src/agents/realtime/model_inputs.py index 9d7ab143d..e1fb32ce4 100644 --- a/src/agents/realtime/model_inputs.py +++ b/src/agents/realtime/model_inputs.py @@ -93,7 +93,17 @@ class RealtimeModelSendToolOutput: @dataclass class RealtimeModelSendInterrupt: - """Send an interrupt to the model.""" + """Send an interrupt to the model. + + Args: + force_cancel: If True, always send response.cancel regardless of + interrupt_response setting. This should be True for SDK-side interrupts + (e.g., guardrails) where the API doesn't know about the interrupt. + For user voice interrupts, this should be False (default) to allow + the API's automatic response cancellation when interrupt_response=True. + """ + + force_cancel: bool = False @dataclass diff --git a/src/agents/realtime/openai_realtime.py b/src/agents/realtime/openai_realtime.py index 50aaf3c4b..2bb59945d 100644 --- a/src/agents/realtime/openai_realtime.py +++ b/src/agents/realtime/openai_realtime.py @@ -433,7 +433,10 @@ async def _send_interrupt(self, event: RealtimeModelSendInterrupt) -> None: and session.audio.input.turn_detection is not None and session.audio.input.turn_detection.interrupt_response is True, ) - if not automatic_response_cancellation_enabled: + # Always cancel for force_cancel=True (e.g., guardrail interrupts) + # For user voice interrupts (force_cancel=False), only cancel if automatic + # cancellation is disabled + if event.force_cancel or not automatic_response_cancellation_enabled: await self._cancel_response() self._audio_state_tracker.on_interrupted() diff --git a/src/agents/realtime/session.py b/src/agents/realtime/session.py index 42dcf531a..2a3da8df2 100644 --- a/src/agents/realtime/session.py +++ b/src/agents/realtime/session.py @@ -704,7 +704,9 @@ async def _run_output_guardrails(self, text: str, response_id: str) -> bool: ) # Interrupt the model - await self._model.send_event(RealtimeModelSendInterrupt()) + # Use force_cancel=True for guardrail interrupts because they are SDK-side logic + # and the API doesn't know about them (unlike user voice interrupts) + await self._model.send_event(RealtimeModelSendInterrupt(force_cancel=True)) # Send guardrail triggered message guardrail_names = [result.guardrail.get_name() for result in triggered_results]