-
Notifications
You must be signed in to change notification settings - Fork 3.7k
fix(voice): stop recreating STT stream on non-retryable errors #7173
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b082516
e57d8b4
e27442b
6ad6a72
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -188,8 +188,9 @@ async def _stt_pump(self) -> None: | |
| """Iterate the STT node and forward events into *event_ch*. | ||
|
|
||
| Owns the generator lifecycle — never cancelled during handoff, only the | ||
| consumer is swapped. On a connection failure the long-lived stream is | ||
| recreated after a backoff; the session tolerance is what closes it. | ||
| consumer is swapped. On a retryable failure the long-lived stream is | ||
| recreated after a backoff; the session tolerance is what closes it. A | ||
| non-retryable failure closes *audio_ch*, since nothing will read it again. | ||
| """ | ||
| from .agent import ModelSettings | ||
|
|
||
|
|
@@ -208,13 +209,21 @@ async def _stt_pump(self) -> None: | |
| f"STT node must yield SpeechEvent, got: {type(ev)}" | ||
| ) | ||
| self._event_ch.send_nowait(ev) | ||
| except APIError: | ||
| # only a connection failure is retried (it was emitted and counted by the | ||
| # session); any other error propagates and stops the pump | ||
| except APIError as e: | ||
| # only a retryable failure is recreated (it was emitted and counted by | ||
| # the session); a non-retryable one fails the same way on every stream | ||
| if self._is_closing(): | ||
| return | ||
| if not e.retryable: | ||
| # the stream already emitted this as an unrecoverable STTError and | ||
| # the session counted it; recreating would just hot-loop the failure | ||
| logger.warning("STT stream ended on a non-retryable error, not recreating") | ||
| # nothing will read audio_ch again, so close it rather than let | ||
| # _push_audio keep filling an unbounded queue for the whole session | ||
| self._audio_ch.close() | ||
| return | ||
| logger.warning( | ||
| "STT stream ended on an unrecoverable error, recreating", | ||
| "STT stream ended on a retryable error, recreating", | ||
| exc_info=True, | ||
| ) | ||
|
Comment on lines
225
to
228
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟨 STT exceptions bypass log redaction Retryable failures log Was this helpful? React with 👍 or 👎 to provide feedback.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not a regression introduced by this PR, so I'd leave this as is. |
||
| await asyncio.sleep(_STT_RECONNECT_INTERVAL) | ||
|
|
@@ -737,7 +746,7 @@ def _push_audio( | |
| speech). VAD, AMD and the interruption channel always receive ``frame``. | ||
| """ | ||
| self._sample_rate = frame.sample_rate | ||
| if self._stt_pipeline is not None: | ||
| if self._stt_pipeline is not None and not self._stt_pipeline.audio_ch.closed: | ||
| # stamp the wall-clock anchor on the first frame to reach the pipeline | ||
| if self._stt_pipeline.input_started_at is None: | ||
| self._stt_pipeline.input_started_at = time.time() - frame.duration | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.