Skip to content

fix(elevenlabs): send keyterms on the realtime STT WebSocket - #6746

Open
pawlowskipawel wants to merge 1 commit into
livekit:mainfrom
pawlowskipawel:fix/elevenlabs-realtime-keyterms
Open

fix(elevenlabs): send keyterms on the realtime STT WebSocket#6746
pawlowskipawel wants to merge 1 commit into
livekit:mainfrom
pawlowskipawel:fix/elevenlabs-realtime-keyterms

Conversation

@pawlowskipawel

Copy link
Copy Markdown

Problem

keyterms is accepted by elevenlabs.STT(...) but only ever sent on the batch
request. It is dropped on the realtime WebSocket path, so scribe_v2_realtime
sessions get no keyterm biasing even when the caller passes them.

ElevenLabs does support it there, as repeated keyterms query params on the connect URL:

  • Realtime STT API reference lists keyterms β€” "List of keyterms the model is biased towards."
  • Keyterm prompting guide β€” "also available for the realtime Speech to Text WebSocket API." Batch allows 1000 keyterms Γ— 50 chars, realtime 50 Γ— 20.

The docstring claims the opposite. It was accurate when written, before ElevenLabs
shipped realtime keyterms β€” no_verbatim, released alongside them, was wired into
_connect_ws, but keyterms was missed and the docstring never updated.

Changes

  • _connect_ws appends repeated keyterms= query params, percent-encoded.
  • SpeechStream.update_options accepts keyterms and triggers a reconnect, mirroring
    no_verbatim. STT.update_options forwards them to active streams.
  • Docstring corrected with the real per-mode limits.
  • 5 tests, following the existing enable_logging / no_verbatim patterns.

Keyterms are free-form text, unlike the other params in that URL (model ids, enums,
numbers, language codes), so they need encoding: verified against yarl.URL β€” the parser
aiohttp.ws_connect uses β€” an unescaped & splits Smith & Sons into keyterms='Smith '
plus a bogus ' Sons' param, and # truncates C# to C.
test_connect_ws_escapes_query_delimiters_in_keyterms pins this via
URL(...).query.getall("keyterms").

Verification

make check clean (ruff, mypy over 629 files). pytest tests/test_plugin_elevenlabs_stt.py β€” 25 passed.

Those mock the socket, so they only prove the URL is built correctly. Checked separately
against the live endpoint: one TTS-synthesized clip per term, transcribed twice over
scribe_v2_realtime β€” identical audio bytes in both arms, so any difference isolates
the keyterm.

term without keyterms with keyterms
Sonic3000 Sonic 3000 Sonic3000
GPT4o GPT-4o GPT4o
K2Turbo K2 Turbo K2Turbo
nova-3 Nova 3 nova-3
scribe-v2 Scribe V2 scribe-v2

@pawlowskipawel
pawlowskipawel requested a review from a team as a code owner August 7, 2026 15: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

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.

1 participant