Skip to content

feat(speechmatics): expose end_of_turn_config and vad_config STT options - #6925

Open
saad-py wants to merge 1 commit into
livekit:mainfrom
saad-py:speechmatics-expose-eot-vad-config
Open

feat(speechmatics): expose end_of_turn_config and vad_config STT options#6925
saad-py wants to merge 1 commit into
livekit:mainfrom
saad-py:speechmatics-expose-eot-vad-config

Conversation

@saad-py

@saad-py saad-py commented Aug 20, 2026

Copy link
Copy Markdown

Addresses #6924

Problem

In ADAPTIVE and SMART_TURN modes, the effective end-of-turn wait is not the configured end_of_utterance_silence_trigger: it is the trigger multiplied by the EndOfTurnConfig penalty table, minus recent TTFB, floored at min_end_of_turn_delay (speechmatics.voice._client.VoiceAgentClient._calculate_finalize_delay). The SDK-default penalties make this collapse far below any configured value:

  • The preset-enabled client-side Silero VAD reports speech_ended after only 0.18s of silence (VoiceActivityConfig.silence_duration default).
  • With Smart Turn not enabled (ADAPTIVE), every VAD stop matches the VAD_STOPPED + SMART_TURN_INACTIVE penalty: ×0.2.
  • A fragment the STT itself punctuated as sentence-final matches ENDS_WITH_FINAL + ENDS_WITH_EOS: ×0.5, and simultaneously loses the ×2.0 "not end-of-sentence" protection.

With end_of_utterance_silence_trigger=0.6, the effective wait after the VAD fires is 0.6 × 0.2 × 0.5 = 0.06s (punctuated) or 0.6 × 0.2 × 2.0 = 0.24s (unpunctuated), then floored as low as min_end_of_turn_delay=0.01s after TTFB subtraction. Total real-world pause tolerance is roughly 0.25-0.45s, regardless of the configured trigger (even 1.5s yields ≤0.6s). On telephony this splits normal mid-sentence pauses (a caller recalling a date of birth or spelling out an email address) into multiple committed user turns, with the agent replying to each fragment.

Because the trigger only scales the base linearly, no currently exposed plugin parameter can prevent this: EndOfTurnConfig and VoiceActivityConfig are not in the constructor and not in _prepare_config()'s advanced_params whitelist.

Change

Expose both objects through the existing preset-override mechanism, exactly like the other advanced parameters:

  • STT(...) gains typed optional kwargs end_of_turn_config: NotGivenOr[EndOfTurnConfig] and vad_config: NotGivenOr[VoiceActivityConfig] (both types are already public exports of speechmatics-voice, which this plugin already depends on).
  • Both are stored on STTOptions and added to the advanced_params whitelist in _prepare_config(), so a provided value replaces the preset's object entirely and an omitted kwarg changes nothing.
  • Docstrings added per the project's pdoc conventions.

No behavior change for existing users: both kwargs default to NOT_GIVEN.

Usage example

from speechmatics.voice import EndOfTurnConfig, VoiceActivityConfig
from livekit.plugins import speechmatics
from livekit.plugins.speechmatics.stt import TurnDetectionMode

stt = speechmatics.STT(
    turn_detection_mode=TurnDetectionMode.ADAPTIVE,
    end_of_utterance_silence_trigger=0.6,
    # soften the silence fast-path for telephony
    end_of_turn_config=EndOfTurnConfig(min_end_of_turn_delay=0.15, use_forced_eou=True),
    vad_config=VoiceActivityConfig(enabled=True, silence_duration=0.3),
)

Testing

tests/test_plugin_speechmatics_stt.py (marked pytest.mark.plugin("speechmatics"), hermetic, no network):

  • test_end_of_turn_config_passthrough: a provided EndOfTurnConfig reaches the prepared VoiceAgentConfig.
  • test_vad_config_passthrough: same for VoiceActivityConfig.
  • test_preset_defaults_kept_when_not_given: omitting both kwargs leaves the ADAPTIVE preset's values untouched.

ruff check and ruff format pass on both files.

Notes

Happy to adjust the API shape if maintainers prefer granular kwargs over passing the config objects.

In ADAPTIVE and SMART_TURN modes the effective end-of-turn wait is
end_of_utterance_silence_trigger multiplied by EndOfTurnConfig penalty
multipliers, with VoiceActivityConfig driving when predictions are
scheduled. Neither object was reachable through the plugin, so the
hardcoded defaults (x0.2 on VAD stop, 0.18s VAD silence, 0.01s floor)
could not be tuned: any configured trigger collapses to a ~60-240ms
effective wait, splitting phone callers' sentences on natural pauses.

Pass both through the existing preset-override mechanism as typed,
optional kwargs. Omitted kwargs leave preset behavior unchanged.
@saad-py
saad-py requested a review from a team as a code owner August 20, 2026 14:06

@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 bugs or issues to report.

Open in Devin Review

@CLAassistant

CLAassistant commented Aug 20, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

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