fix(stt): keep providers available after fallback stream close - #7296
rosetta-livekit-bot[bot] wants to merge 1 commit into
Conversation
| await asyncio.sleep(0) | ||
| if self._closing: | ||
| return |
There was a problem hiding this comment.
🔴 Failover drops incoming audio frames
When audio arrives during a provider switch, _forward_input_task still sends it to the failed main_stream. Push failures are discarded, so the fallback provider misses that speech.
Learn more
The failed child remains in main_stream while the failure path yields to the event loop. The concurrent input-forwarding task can run during that yield and push new frames into the failed or closing child. Its exception handler discards the push failure, and no replay buffer exists for the next provider.
Example: The primary stream fails, then a caller pushes a frame containing the start of “hello” while this path yields. The primary rejects the frame. The secondary starts afterward and receives only the remaining audio, producing an incomplete transcript.
Recommended fix: Clear main_stream before the first yield after child termination. Serialize provider replacement with input forwarding, or buffer frames received while no live child is assigned and drain them into the replacement stream.
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
Ports livekit/agents-js#2381 to the Python STT fallback adapter.
Testing
uv run pytest tests/test_stt_fallback.py --unit -q(36 passed)make checkuv build --package livekit-agentsmake unit-testsattempted twice: the first run reached3486 passedbefore Docker-backed room fixtures failed because Docker is unavailable and configured inference credentials returned 401; with LiveKit credentials unset, the run reached3090 passedbefore existing concurrent OpenAI realtime tests closed their shared event loop (9 errors).cue-cliruntime validation attempted in voice mode, but the configured Cue LiveKit endpoint rejected the API key with 401 before an agent could join.No Changeset was added because this repository does not use Changesets.
Source diff coverage
.changeset/clean-fallback-teardown.mdagents/etc/agents.api.mdagents/src/stt/fallback_adapter.test.tstests/test_stt_fallback.pyusing Python async stream controls andaclose().agents/src/stt/fallback_adapter.tslivekit-agents/livekit/agents/stt/fallback_adapter.py; JS abort controllers, queues, and promises map to asyncio tasks, channels, bounded cancellation, and recovery waiters.agents/src/stt/fallback_adapter_lifecycle.test.tstests/test_stt_fallback.py. AgentTask handoff behavior is exercised through direct concurrent stream ownership because Python reuses its STT pipeline across compatible handoffs.agents/src/stt/stt.tsRecognizeStream.__anext__()already rethrows each child task exception, providing the stream-local terminal failure state that the source adds as_failed.Ported from livekit/agents-js#2381
Original PR description
Summary
Treat a closed parent queue during
FallbackSpeechStreamteardown as a local lifecycle event. This prevents a late child transcript from marking a healthy STT provider unavailable.Closes #2349.
Testing
vitest run agents/src/stttsc -p agents/tsconfig.typecheck.json