Skip to content

Commit ea3b668

Browse files
committed
fix(stepfun): reset consumed audio per reconnect attempt
A reconnect with nothing left to resend has no upload to wait for, so an empty attempt must not report a missing transcription. Replayed frames set the flag again, keeping the finalization wait when audio really is resent.
1 parent 5e2354c commit ea3b668

2 files changed

Lines changed: 36 additions & 0 deletions

File tree

‎livekit-plugins/livekit-plugins-stepfun/livekit/plugins/stepfun/stt.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,9 @@ async def _run_impl(self) -> None:
537537
self._commits.clear()
538538
self._expected_finals = 0
539539
self._finals_received = 0
540+
# Per attempt: a reconnect with nothing to resend has no upload to wait
541+
# for. Replayed or new frames set it again.
542+
self._consumed_audio = False
540543
self._input_finished_at = 0.0
541544
self._last_event_at = 0.0
542545
self._uncommitted_audio = False

‎tests/test_plugin_stepfun_stt.py‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1334,6 +1334,39 @@ async def send_str(self, data: str) -> None:
13341334
await stream.aclose()
13351335

13361336

1337+
async def test_reconnect_with_nothing_to_resend_ends_cleanly() -> None:
1338+
"""A finalized turn, then a drop, then end_input with no further audio."""
1339+
1340+
class Failing(_ReadyPeer):
1341+
async def send_str(self, data: str) -> None:
1342+
await super().send_str(data)
1343+
if self.sent[-1]["type"] == "input_audio_buffer.append":
1344+
for event in (
1345+
{"type": "input_audio_buffer.speech_started", "item_id": "item-1"},
1346+
{"type": "input_audio_buffer.speech_stopped", "item_id": "item-1"},
1347+
{"type": _COMPLETED, "item_id": "item-1", "transcript": "hello"},
1348+
):
1349+
self.queue_server_event(event)
1350+
self.queue_error()
1351+
1352+
pool = _SequencedPool([Failing(), _ReadyPeer()])
1353+
s = stepfun.STT()
1354+
s._pool = pool # type: ignore[assignment]
1355+
stream = s.stream(conn_options=APIConnectOptions(max_retry=1, retry_interval=0, timeout=1))
1356+
stream.push_frame(_frame())
1357+
try:
1358+
for _ in range(100):
1359+
if pool.handed_out == 2:
1360+
break
1361+
await asyncio.sleep(0.01)
1362+
assert pool.handed_out == 2
1363+
stream.end_input() # nothing was sent on the new session
1364+
events = await asyncio.wait_for(_collect(stream), 3)
1365+
assert _texts(events, stt.SpeechEventType.FINAL_TRANSCRIPT) == ["hello"]
1366+
finally:
1367+
await stream.aclose()
1368+
1369+
13371370
async def test_connection_loss_beyond_the_replay_cap_is_fatal() -> None:
13381371
"""Unbounded resend is not attempted; past the cap the loss is reported."""
13391372

0 commit comments

Comments
 (0)