Skip to content

Commit f5bfae1

Browse files
committed
fix(#2009): emit truncation on VAD interrupt
1 parent 553c1bf commit f5bfae1

File tree

2 files changed

+29
-5
lines changed

2 files changed

+29
-5
lines changed

src/agents/realtime/openai_realtime.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -621,10 +621,27 @@ async def _handle_ws_event(self, event: dict[str, Any]):
621621
last_audio = self._audio_state_tracker.get_last_audio_item()
622622
if last_audio is not None:
623623
item_id, content_index = last_audio
624+
playback_state = self._get_playback_state()
625+
playback_item_id = playback_state.get("current_item_id")
626+
playback_content_index = playback_state.get("current_item_content_index") or 0
627+
playback_elapsed_ms = playback_state.get("elapsed_ms")
624628
await self._emit_event(
625629
RealtimeModelAudioInterruptedEvent(item_id=item_id, content_index=content_index)
626630
)
627631

632+
if (
633+
playback_item_id
634+
and playback_elapsed_ms is not None
635+
):
636+
truncated_ms = max(int(playback_elapsed_ms), 0)
637+
await self._send_raw_message(
638+
_ConversionHelper.convert_interrupt(
639+
playback_item_id,
640+
playback_content_index,
641+
truncated_ms,
642+
)
643+
)
644+
628645
# Reset trackers so subsequent playback state queries don't
629646
# reference audio that has been interrupted client‑side.
630647
self._audio_state_tracker.on_interrupted()
@@ -643,9 +660,6 @@ async def _handle_ws_event(self, event: dict[str, Any]):
643660
)
644661
if not automatic_response_cancellation_enabled:
645662
await self._cancel_response()
646-
# Avoid sending conversation.item.truncate here. When the session's
647-
# turn_detection.interrupt_response is enabled (GA default), the server emits
648-
# conversation.item.truncated after the VAD start and takes care of history updates.
649663
elif parsed.type == "response.created":
650664
self._ongoing_response = True
651665
await self._emit_event(RealtimeModelTurnStartedEvent())

tests/realtime/test_openai_realtime.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
import pytest
77
import websockets
8-
98
from agents import Agent
109
from agents.exceptions import UserError
1110
from agents.handoffs import handoff
@@ -443,7 +442,7 @@ async def test_transcription_related_and_timeouts_and_speech_started(self, model
443442

444443
# Prepare tracker state to simulate ongoing audio
445444
model._audio_state_tracker.set_audio_format("pcm16")
446-
model._audio_state_tracker.on_audio_delta("i1", 0, b"aaaa")
445+
model._audio_state_tracker.on_audio_delta("i1", 0, b"a" * 48)
447446
model._ongoing_response = True
448447

449448
# Patch sending to avoid websocket dependency
@@ -464,6 +463,17 @@ async def test_transcription_related_and_timeouts_and_speech_started(self, model
464463
}
465464
)
466465

466+
truncate_events = [
467+
call.args[0]
468+
for call in model._send_raw_message.await_args_list
469+
if getattr(call.args[0], "type", None) == "conversation.item.truncate"
470+
]
471+
assert truncate_events
472+
truncate_event = truncate_events[0]
473+
assert truncate_event.item_id == "i1"
474+
assert truncate_event.content_index == 0
475+
assert truncate_event.audio_end_ms in (0, 1)
476+
467477
# Output transcript delta
468478
await model._handle_ws_event(
469479
{

0 commit comments

Comments
 (0)