Skip to content

fix(voice/realtime): add tool_reply_after_audio to prevent duplicate speech#6480

Open
Utkarshsteve wants to merge 2 commits into
livekit:mainfrom
Utkarshsteve:fix/realtime-duplicate-tool-reply
Open

fix(voice/realtime): add tool_reply_after_audio to prevent duplicate speech#6480
Utkarshsteve wants to merge 2 commits into
livekit:mainfrom
Utkarshsteve:fix/realtime-duplicate-tool-reply

Conversation

@Utkarshsteve

Copy link
Copy Markdown

Summary

  • Adds a configurable tool_reply_after_audio policy to ToolHandlingOptions that controls whether the framework generates a post-tool reply when the realtime response already produced audible audio
  • Default is "always" (backwards-compatible); setting "skip" suppresses the duplicate reply
  • Exposes response_had_audio on FunctionToolsExecutedEvent so user handlers can inspect the signal

Problem

When a realtime model (e.g. xAI Grok) bundles audio output and a function_call in one response, the framework fires a post-tool reply after executing the tool — causing the agent to audibly answer twice (#6411).

Solution

Detection: after tool execution, check whether the triggering response produced audio that was actually played (started_speaking_at is not None and at least one message output wasn't skipped).

Guard: when tool_reply_after_audio == "skip" and audio was produced, skip the post-tool reply generation.

AgentSession(
    tool_handling={"tool_reply_after_audio": "skip"},
)

Test plan

  • Unit tests for event property defaults and mutation
  • Unit tests for ToolHandlingOptions config dictionary
  • Unit tests for AgentSession config storage (default, skip, always, empty dict)
  • make check passes (format, lint, mypy strict)
  • uv run pytest --unit — all 1132 + 10 new tests pass
  • Manual verification with xAI Grok realtime model (requires API key)

Closes #6411

The `with_azure` factory set `model="gpt-4o"` by default, but Azure
OpenAI uses `azure_deployment` to select models — the `model` parameter
is not used by the API. This caused the `.model` property to return
"gpt-4o" regardless of the actual deployment.

Change `model` to default to `None` and fall back to `azure_deployment`
so that `.model` reflects the real deployment name.

Closes livekit#6209
…licate speech

When a realtime model bundles audio output and a function_call in one
response, the framework's post-tool reply fires and the agent answers
twice. This adds a configurable `tool_reply_after_audio` policy on
`ToolHandlingOptions`:
- "always" (default): backwards-compatible, always generate reply
- "skip": suppress post-tool reply when audio was already delivered

Closes livekit#6411
@Utkarshsteve
Utkarshsteve requested a review from a team as a code owner July 18, 2026 12:46

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 2 additional findings.

Open in Devin Review

if len(tool_output.output) > 0:
speech_handle._num_steps += 1

response_had_audio = started_speaking_at is not None and any(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

started_speaking_at is also set by _TextOutput.first_text_fut in _on_first_frame, so this becomes true for a text-only response with no audio at all. With transcription enabled and tool_reply_after_audio="skip", that suppresses the required post-tool reply even though nothing was spoken.

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

Labels

None yet

Projects

None yet

2 participants