Skip to content

fix(deepgram): use punctuated_word for word-level transcripts - #6699

Open
mdylan2 wants to merge 3 commits into
livekit:mainfrom
mdylan2:fix/deepgram-punctuated-words
Open

fix(deepgram): use punctuated_word for word-level transcripts#6699
mdylan2 wants to merge 3 commits into
livekit:mainfrom
mdylan2:fix/deepgram-punctuated-words

Conversation

@mdylan2

@mdylan2mdylan2 commented Aug 4, 2026

Copy link
Copy Markdown

Summary

SpeechData.text and SpeechData.words are built from different Deepgram fields, so the two disagree on the same object.

text comes from the transcript, which honours the punctuate option. The word list was built from the raw per-word field, which is lowercase and unpunctuated regardless of any option:

text=alt["transcript"], # honours `punctuate`
...
words=[
TimedString(
text=word.get("word", ""), # never punctuated

Deepgram returns punctuated_word alongside word, in the very dict that comprehension iterates:

keys : ['confidence', 'end', 'punctuated_word', 'start', 'word']
word : hello my name is alex
punctuated_word : Hello. My name is Alex.

Field selection across the four cases:

_word_text({"word": "alex", "punctuated_word": "Alex."}, punctuate=True) # -> "Alex."_word_text({"word": "alex"}, punctuate=True) # -> "alex"_word_text({"word": "alex", "punctuated_word": ""}, punctuate=True) # -> "alex"_word_text({"word": "alex", "punctuated_word": "Alex."}, punctuate=False) # -> "alex"

SpeechData.text comes from alt["transcript"], which Deepgram punctuates. The word
list was built from alt["words"][].word, which is always lowercase and
unpunctuated, so the two fields of the same SpeechData disagreed:
.text "We are so excited for you to join the Ribbon team."
.words[].text "we are so excited for you to join the ribbon team"
Deepgram returns punctuated_word alongside word whenever punctuate is enabled
(it is on by default), so the punctuated form was already in the payload the
comprehension iterates. Prefer it, falling back to word when punctuation is
disabled or the key is absent.
This matters for consumers that render or align on the word list — clickable
transcripts, captions, time-aligned analysis — where the words are the display
text and currently read as an uncased, unpunctuated wall of text.
Applied to both the streaming and prerecorded paths. Word timing is untouched.
@CLAassistant

CLAassistant commented Aug 4, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Follows the `punctuate` setting rather than inferring from key presence, so the
word list matches SpeechData.text in both configurations:
punctuate=True text and words both punctuated
punctuate=False text and words both raw
Threads the option from STTOptions through both conversion helpers. The new
keyword defaults to True, matching STT(punctuate=True), so existing callers of
these module-level functions are unaffected.
`word` is an untyped dict, so `.get()` returns Any and returning it directly from
a `-> str` function trips mypy's strict no-any-return. The original code was
inline inside the TimedString(...) call, where there was no declared return type
to violate; extracting the helper is what surfaced it.
Bind to str-annotated locals so the narrowing happens once. Behaviour unchanged.
@mdylan2
mdylan2 marked this pull request as ready for review August 4, 2026 23:14
@mdylan2
mdylan2 requested a review from a team as a code ownerAugust 4, 2026 23:14

@devin-ai-integrationdevin-ai-integrationBot 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 1 additional finding.

Open in Devin Review

Sign up for freeto 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

@mdylan2@CLAassistant