Skip to content

fix(deepgram): guard empty languages list in multilingual STT decoding - #7976

Open
Speedyzyzz wants to merge 3 commits into
livekit:mainfrom
Speedyzyzz:fix/deepgram-multilingual-empty-languages-and-channels
Open

Speedyzyzz wants to merge 3 commits into
livekit:mainfrom
Speedyzyzz:fix/deepgram-multilingual-empty-languages-and-channels

Conversation

@Speedyzyzz

@Speedyzyzz Speedyzyzz commented Oct 20, 2026 •

Copy link
Copy Markdown

Problem

When using Deepgram multilingual live transcription (language="multi"), Deepgram can return an empty languages: [] list in alternatives (e.g. during low confidence audio, non speech segments, or interim silence). The check if language == "multi" and "languages" in alt: evaluates to True, but indexing alt["languages"][0] immediately raises IndexError: list index out of range, which abruptly aborts the _run() background receive task.

Solution

Check truthiness alt.get("languages") instead of key containment "languages" in alt in live_transcription_to_speech_data.

Tests

  • Added test_live_transcription_empty_languages_multilingual in tests/test_plugin_deepgram_stt.py verifying that an empty languages: [] list processes without raising IndexError.
  • All 42 tests in tests/test_plugin_deepgram_stt.py pass cleanly in 0.59s.
  • ruff check and ruff format are clean.

… decoding

- In live_transcription_to_speech_data: check alt.get('languages') instead of 'languages' in alt to avoid IndexError when Deepgram returns an empty languages list for multilingual live transcription.
- In prerecorded_transcription_to_speech_event: guard against empty results.channels by returning an empty SpeechEvent with alternatives=[] instead of crashing on channel 0 indexing. Guard nested word timing dictionary access with safe get() defaults.
- Add unit test coverage in tests/test_plugin_deepgram_stt.py for empty languages list in live transcription and empty channels list in prerecorded transcription.
@Speedyzyzz
Speedyzyzz requested a review from a team as a code owner October 20, 2026 08:28
Copilot AI balanced review requested due to automatic review settings October 20, 2026 08:28

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@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.

Note

Newer findings are available below. Devin Review posted a newer report on this PR, in addition to the findings presented here.

🔍 Devin Review: 1 flag

Not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

request_id = data["metadata"]["request_id"]
channel: dict = data["results"]["channels"][0]
dg_alts = channel["alternatives"]
request_id = data.get("metadata", {}).get("request_id", "")

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.

_recognize_impl does not check the response status, so today an error body (for example a 401) fails on data["metadata"] and becomes an APIConnectionError. With the .get() defaults, that error body returns an empty FINAL_TRANSCRIPT, and a bad API key looks like silence to the caller and to FallbackAdapter.

Could you:

  1. Keep the languages fix.
  2. Revert the prerecorded_transcription_to_speech_event changes. If you want to handle errors there, check res.status in _recognize_impl and raise APIStatusError with the body instead.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch. Masking a 401 error payload as an empty FINAL_TRANSCRIPT would break FallbackAdapter failover. I have reverted the prerecorded_transcription_to_speech_event changes entirely and kept only the defensive check on the empty languages list for multilingual live transcription.

@Speedyzyzz Speedyzyzz changed the title fix(deepgram): guard empty languages and channels in STT speech event decoding fix(deepgram): guard empty languages list in multilingual STT decoding Oct 20, 2026

@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 found 3 new potential issues.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

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.

🔴 Silent recordings fail prerecorded recognition

When Deepgram returns no channels, prerecorded_transcription_to_speech_event raises IndexError. The exception becomes APIConnectionError, so silent recordings fail instead of returning an empty transcript.

(Refers to this code)

Learn more

The prerecorded converter reads the first channel before it builds a speech event. A response with no channels raises IndexError, which _recognize_impl converts to APIConnectionError instead of returning a final transcript.

Example: For {"metadata": {"request_id": "req-1"}, "results": {"channels": []}}, recognition fails rather than returning FINAL_TRANSCRIPT with no alternatives.

Recommended fix: Check for an empty channel list before selecting the first channel; return a final speech event with the request ID and empty alternatives. Restore coverage for this response.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

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.

🟡 Null detected language aborts recognition

When Deepgram sends detected_language: null, LanguageCode(None) raises before prerecorded_transcription_to_speech_event processes alternatives. Even an empty alternatives list becomes a failed recognition request.

(Refers to this code)

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

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.

🟡 Missing word timing aborts recognition

When a prerecorded word lacks start or end, prerecorded_transcription_to_speech_event raises KeyError. TimedString accepts missing timings, but the alternative's direct lookups fail the entire request.

(Refers to this code)

Learn more

The converter uses default timing values for individual TimedString words, but its speech-level timing accesses the first and last word directly. A missing endpoint raises KeyError, and _recognize_impl reports it as a connection error.

Example: An alternative with words: [{"word": "hello", "end": 0.5}], confidence: 0.8, and transcript: "hello" fails because its first word has no start; the word-level decoder would otherwise use zero.

Recommended fix: Use the same .get(..., 0) defaults for speech-level timing as for each TimedString and cover missing first/last word timings in tests.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

This branch has not been deployed

No deployments
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.

3 participants