fix(elevenlabs): send keyterms on the realtime STT WebSocket - #2283
Merged
Conversation
🦋 Changeset detectedLatest commit: 99217fd The changes in this PR will be included in the next version bump. This PR includes changesets to release 39 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
chenghao-mou
approved these changes
Aug 13, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ports livekit/agents#6746 to the ElevenLabs JavaScript plugin.
Realtime Scribe v2 connections now send repeated, percent-encoded
keytermsquery parameters. Updating keyterms on either the STT instance or an active speech stream triggers a reconnect so the new biasing takes effect.Adds a patch changeset for
@livekit/agents-plugin-elevenlabs.Verification
pnpm test plugins/elevenlabs(24 passed, 2 credential-gated integration tests skipped)pnpm build(40/40 packages passed)pnpm lint(40/40 packages passed; existing warnings only)pnpm exec prettier --check plugins/elevenlabs/src/stt.ts plugins/elevenlabs/src/stt.test.ts .changeset/elevenlabs-realtime-keyterms.mdSource diff coverage
Authoritative source: livekit/agents#6746
livekit-plugins/livekit-plugins-elevenlabs/livekit/plugins/elevenlabs/stt.py->plugins/elevenlabs/src/stt.ts. Ported constructor option documentation, repeated percent-encoded realtime WebSocket keyterms, STT-to-stream option forwarding, and reconnect-on-keyterm-update behavior. JavaScriptencodeURIComponentreplaces Pythonurllib.parse.quote.tests/test_plugin_elevenlabs_stt.py->plugins/elevenlabs/src/stt.test.ts. Ported all five source tests for inclusion, query-delimiter escaping and URL round trips, omission when unspecified, STT-level forwarding, and stream-level reconnects. The target tests observe URLs through its local WebSocket test server because TypeScript private fields and methods are inaccessible.Ported from livekit/agents#6746
Original PR description
Problem
keytermsis accepted byelevenlabs.STT(...)but only ever sent on the batchrequest. It is dropped on the realtime WebSocket path, so
scribe_v2_realtimesessions get no keyterm biasing even when the caller passes them.
ElevenLabs does support it there, as repeated
keytermsquery params on the connect URL:keyterms— "List of keyterms the model is biased towards."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, butkeytermswas missed and the docstring never updated.Changes
_connect_wsappends repeatedkeyterms=query params, percent-encoded.SpeechStream.update_optionsacceptskeytermsand triggers a reconnect, mirroringno_verbatim.STT.update_optionsforwards them to active streams.enable_logging/no_verbatimpatterns.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 parseraiohttp.ws_connectuses — an unescaped&splitsSmith & Sonsintokeyterms='Smith 'plus a bogus
' Sons'param, and#truncatesC#toC.test_connect_ws_escapes_query_delimiters_in_keytermspins this viaURL(...).query.getall("keyterms").Verification
make checkclean (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 isolatesthe keyterm.
Sonic3000Sonic 3000Sonic3000GPT4oGPT-4oGPT4oK2TurboK2 TurboK2Turbonova-3Nova 3nova-3scribe-v2Scribe V2scribe-v2