mirror of
https://github.com/jambonz/jambonz-feature-server.git
synced 2026-10-04 02:04:12 +00:00
fix(gather): clear a finalized deepgram interim word so UtteranceEnd is not deferred forever (#1588)
* fix(gather): clear a finalized deepgram interim word so UtteranceEnd is not deferred forever The #1088 check defers UtteranceEnd while an interim word newer than last_word_end is pending. The marker was only cleared by a non-empty is_final, so an interim word that Deepgram later dropped (e.g. line noise, followed by an empty final) left it set: the gather waited for the caller to speak again and merged both utterances. Clear the marker on any Deepgram is_final result whose window (start + duration) covers it. A word that Deepgram carries into the next segment still defers UtteranceEnd. Port of jambonz/feature-server#223. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(gather): return the buffer when a deferred deepgram UtteranceEnd is satisfied Clearing the unprocessed-word marker only helps if the covering final arrives before UtteranceEnd. If it arrives after, UtteranceEnd has already been deferred and Deepgram sends no further UtteranceEnd until new speech, so the next utterance is still merged in. Remember a deferred UtteranceEnd, and when a later final clears the marker, return the buffered transcript once that final has been handled (setImmediate, since _onTranscription has early returns). Port of jambonz/feature-server#225. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(gather): only an empty final ends a gather early after a deferred UtteranceEnd A final with words means the caller resumed talking after the pause; Deepgram will send a new UtteranceEnd after those words, so keep the existing flow instead of cutting the caller off mid-sentence (or skipping the asrTimeout wait). Only a final that drops the pending word (empty) leaves nothing else to end the gather. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ebcc3e3a8a
commit
baf97dabc2
@@ -901,6 +901,29 @@ class TaskGather extends SttTask {
|
||||
this.playComplete = true;
|
||||
}
|
||||
|
||||
/* a deepgram is_final result finalizes the audio in [start, start + duration]; if that window
|
||||
covers the last unprocessed interim word then the word has been finalized or dropped (e.g. an
|
||||
empty final after line noise), so it is no longer pending and must not hold off UtteranceEnd */
|
||||
_clearFinalizedUnprocessedWord(evt) {
|
||||
if (!evt.is_final || !this._dgTimeOfLastUnprocessedWord) return false;
|
||||
if (typeof evt.start !== 'number' || typeof evt.duration !== 'number') return false;
|
||||
if (evt.start + evt.duration < this._dgTimeOfLastUnprocessedWord) return false;
|
||||
this.logger.debug(`Gather:_onTranscription - deepgram finalized audio through ${evt.start + evt.duration}, ` +
|
||||
`clearing unprocessed word end time ${this._dgTimeOfLastUnprocessedWord}`);
|
||||
this._dgTimeOfLastUnprocessedWord = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* UtteranceEnd was deferred for a word that deepgram has now finalized as empty (dropped); with no
|
||||
new words deepgram will not send another UtteranceEnd, so return the buffer now */
|
||||
_resolveDeferredUtteranceEnd() {
|
||||
if (this.resolved || this.killed || this._bufferedTranscripts.length === 0) return;
|
||||
this.logger.info('Gather:_onTranscription - deferred UtteranceEnd satisfied, return buffered transcript');
|
||||
const evt = this.consolidateTranscripts(this._bufferedTranscripts, 1, this.language, this.vendor);
|
||||
this._bufferedTranscripts = [];
|
||||
this._resolve('speech', evt);
|
||||
}
|
||||
|
||||
_onTranscription(cs, ep, evt, fsEvent) {
|
||||
// check if we are in graceful shutdown mode
|
||||
if (ep.gracefulShutdownResolver) {
|
||||
@@ -928,6 +951,7 @@ class TaskGather extends SttTask {
|
||||
// eslint-disable-next-line max-len
|
||||
if (utteranceTime && this._dgTimeOfLastUnprocessedWord && utteranceTime < this._dgTimeOfLastUnprocessedWord && utteranceTime != -1) {
|
||||
this.logger.info('Gather:_onTranscription - got UtteranceEnd with unprocessed words, continue listening');
|
||||
this._dgUtteranceEndDeferred = true;
|
||||
}
|
||||
else {
|
||||
this.logger.info('Gather:_onTranscription - got UtteranceEnd from deepgram, return buffered transcript');
|
||||
@@ -942,6 +966,14 @@ class TaskGather extends SttTask {
|
||||
this.logger.debug('Gather:_onTranscription - discarding Metadata event from deepgram');
|
||||
return;
|
||||
}
|
||||
if (this.vendor === 'deepgram' && this._clearFinalizedUnprocessedWord(evt) && this._dgUtteranceEndDeferred) {
|
||||
/* a final with words means the caller is still talking; deepgram will send a new UtteranceEnd */
|
||||
this._dgUtteranceEndDeferred = false;
|
||||
if (!evt.channel?.alternatives?.[0]?.transcript) {
|
||||
/* resolve once the rest of this method has handled this final */
|
||||
setImmediate(() => this._resolveDeferredUtteranceEnd());
|
||||
}
|
||||
}
|
||||
|
||||
evt = this.normalizeTranscription(evt, this.vendor, 1, this.language,
|
||||
this.shortUtterance, this.data.recognizer.punctuation);
|
||||
|
||||
Reference in New Issue
Block a user