Skip to content

feat(voice): auto-disable realtime server-side turn detection#6495

Open
longcw wants to merge 6 commits into
mainfrom
longc/realtime-auto-disable-td
Open

feat(voice): auto-disable realtime server-side turn detection#6495
longcw wants to merge 6 commits into
mainfrom
longc/realtime-auto-disable-td

Conversation

@longcw

@longcw longcw commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Auto-disables a realtime model's server-side turn detection for the session when the user configured client-side turn-taking (a TurnDetector, vad, manual, or an explicit interruption mode) and the model supports it, so client-side EoT/barge-in works without also passing turn_detection=None to the RealtimeModel.

Adds RealtimeCapabilities.can_disable_turn_detection (OpenAI sets it from the ctor turn_detection given-ness) and a session(turn_detection_disabled=...) seam applied on the initial session.update via the per-session opts copy, so the model is never mutated and stays reusable. AgentActivity resolves the effective state once (_rt_turn_detection_enabled) and routes every server-side turn-detection check through it.

Other realtime plugins accept-and-ignore the flag (can_disable_turn_detection stays False): xAI (client turn-taking not stable in testing) and Gemini/others (no commit_audio/clear_audio; turns are activity-signal based).

Stacked on longc/realtime-barge-in; this PR targets that branch, not main.

@longcw
longcw requested a review from a team as a code owner July 21, 2026 08:57
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@longcw
longcw force-pushed the longc/realtime-auto-disable-td branch from 9c2cc63 to 9caf4ba Compare July 21, 2026 09:27
@chenghao-mou chenghao-mou self-assigned this Jul 21, 2026
@longcw
longcw force-pushed the longc/realtime-barge-in branch from a557e26 to 2926544 Compare July 22, 2026 01:49
@longcw
longcw force-pushed the longc/realtime-auto-disable-td branch from f9c7088 to b525a7a Compare July 22, 2026 01:49
devin-ai-integration[bot]

This comment was marked as resolved.

@longcw
longcw force-pushed the longc/realtime-auto-disable-td branch from 48359d0 to 9c988f3 Compare July 22, 2026 02:57
@longcw
longcw force-pushed the longc/realtime-barge-in branch from 82a241c to 2b70881 Compare July 23, 2026 06:01
@longcw
longcw force-pushed the longc/realtime-auto-disable-td branch from 9c988f3 to eca4e01 Compare July 23, 2026 06:01
# model to auto-generate a tool reply (auto_tool_reply_generation=True).
self._pending_auto_tool_reply_fut: asyncio.Future[None] | None = None

def _resolve_rt_turn_detection_enabled(self) -> bool:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we handle update_options with turn_detection for realtime too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toggle turn detection at runtime will need a restart of rt_session if the server-side turn detection is disabled. I added a warning for now.

Base automatically changed from longc/realtime-barge-in to main July 23, 2026 13:18
@longcw
longcw force-pushed the longc/realtime-auto-disable-td branch from eca4e01 to 9e9feca Compare July 24, 2026 03:03
longcw added 6 commits July 24, 2026 11:23
Disable a realtime model's server-side turn detection for the session when the
user configured client-side turn-taking (a TurnDetector, "vad", "manual", or an
explicit interruption mode) and the model supports it, so client-side EoT/barge-in
works without also passing turn_detection=None to the RealtimeModel.

- add RealtimeCapabilities.can_disable_turn_detection; OpenAI sets it from the
  ctor turn_detection given-ness
- add session(turn_detection_disabled=...), applied on the initial session.update
  via the per-session opts copy so the model is never mutated (stays reusable)
- AgentActivity resolves the effective state once (_rt_turn_detection_enabled) and
  routes every server-side turn-detection check through it

Other realtime plugins accept-and-ignore the flag (can_disable_turn_detection stays
False): xAI (client turn-taking not stable in testing) and Gemini/others (no
commit_audio/clear_audio; turns are activity-signal based).
RealtimeModel.update_options force-synced turn_detection=self._opts.turn_detection to
every session unconditionally, so changing an unrelated field (e.g. voice) re-enabled
server VAD on a session that opted out of server-side turn detection. Only propagate
turn_detection when the caller set it; an explicit value still propagates so callers
can re-enable it.
…tection

On an agent handoff the realtime session is reused as-is. If the new agent resolves
server-side turn detection differently (it can override turn_detection, interruption,
or vad), the reused session kept the previous agent's mode, causing duplicate or
missing replies. Refuse reuse on a mismatch so a fresh session is created with the
correct setting.
_resolve_interruption_detection and the on_end_of_turn backchannel-drop guard still read the model's static capabilities.turn_detection instead of the session-resolved _rt_turn_detection_enabled. When a realtime model with built-in turn detection has it auto-disabled for the session (client-side turn-taking configured), can_gatekeep stayed False so the adaptive detector was never created, and the backchannel-drop path was skipped — silently disabling the client-side barge-in this stack is meant to enable.
with_azure applied the Azure default turn detection before constructing the model, so can_disable_turn_detection was always False for Azure even when the caller passed nothing. Capture the caller's intent before defaulting and set the capability accordingly, so the framework can still auto-disable server-side turn detection.
A realtime model's server-side turn detection is resolved once at session start and isn't re-synced at runtime yet. Warn when an explicit update_options(turn_detection=...) would flip that resolved state, so the mismatch isn't silent. Reverting to None (automatic re-selection) is left unwarned.

Also move the auto-disable info log to the resolver's call site so the resolver stays a pure query, and mark the session turn detection explicit when set via update_options.
@longcw
longcw force-pushed the longc/realtime-auto-disable-td branch from 9e9feca to 24154ca Compare July 24, 2026 03:23
@longcw
longcw requested a review from chenghao-mou July 27, 2026 02:11
self._rt_session.update_options(tool_choice=self._tool_choice)

if utils.is_given(turn_detection):
# a realtime model's server-side turn detection is resolved once at session start;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should have a similar warning for RealtimeModel.update_options(turn_detection=...) too

@chenghao-mou

Copy link
Copy Markdown
Member

Another thing I noticed in testing: if the interruption is detected by the barge-in model, it leads to the false interruption path, and for realtime model, transcript isn't always available immediately for that interruption to take effect. Should we consider disable false interruption when no STT is configured with realtime model?

@chenghao-mou chenghao-mou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM overall. Tested locally with OAI Realtime and VAD or turn detector. Two nits and two issues from previous comments.

"""Whether the model can produce audio output directly"""
manual_function_calls: bool
"""Whether function call items already in the chat context can be resumed"""
can_disable_turn_detection: bool = False

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe optional_turn_detection so it matches other adj_noun patterns


@abstractmethod
def session(self) -> RealtimeSession: ...
def session(self, *, turn_detection_disabled: bool = False) -> RealtimeSession:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: should we rename it enable_turn_detection: bool = False instead?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants