Merge pull request #3822 from pipecat-ai/fix/stt-ttfb-timeout-timestamp

Fix STT TTFB timeout measuring to use transcript arrival time
This commit is contained in:
Mark Backman
2026-02-24 19:45:15 -05:00
committed by GitHub
2 changed files with 12 additions and 3 deletions

1
changelog/3822.fixed.md Normal file
View File

@@ -0,0 +1 @@
- Fixed STT TTFB metrics measuring timeout expiry time instead of actual transcript arrival time.

View File

@@ -127,6 +127,7 @@ class STTService(AIService):
self._user_speaking: bool = False self._user_speaking: bool = False
self._finalize_pending: bool = False self._finalize_pending: bool = False
self._finalize_requested: bool = False self._finalize_requested: bool = False
self._last_transcript_time: float = 0
# Keepalive state # Keepalive state
self._keepalive_timeout = keepalive_timeout self._keepalive_timeout = keepalive_timeout
@@ -385,6 +386,9 @@ class STTService(AIService):
direction: The direction to push the frame. direction: The direction to push the frame.
""" """
if isinstance(frame, TranscriptionFrame): if isinstance(frame, TranscriptionFrame):
# Store the transcript time for TTFB calculation
self._last_transcript_time = time.time()
# Set finalized from pending state and auto-reset # Set finalized from pending state and auto-reset
if self._finalize_pending: if self._finalize_pending:
frame.finalized = True frame.finalized = True
@@ -438,6 +442,7 @@ class STTService(AIService):
self._user_speaking = True self._user_speaking = True
self._finalize_requested = False self._finalize_requested = False
self._finalize_pending = False self._finalize_pending = False
self._last_transcript_time = 0
async def _handle_vad_user_stopped_speaking(self, frame: VADUserStoppedSpeakingFrame): async def _handle_vad_user_stopped_speaking(self, frame: VADUserStoppedSpeakingFrame):
"""Handle VAD user stopped speaking frame. """Handle VAD user stopped speaking frame.
@@ -467,14 +472,17 @@ class STTService(AIService):
) )
async def _ttfb_timeout_handler(self): async def _ttfb_timeout_handler(self):
"""Wait for timeout then report TTFB. """Wait for timeout then report TTFB using the last transcript timestamp.
This timeout allows the final transcription to arrive before we calculate This timeout allows the final transcription to arrive before we calculate
and report TTFB. If no transcription arrived, no TTFB is reported. and report TTFB. Uses _last_transcript_time as the end time so we measure
to when the transcript actually arrived, not when the timeout fired.
If no transcription arrived, no TTFB is reported.
""" """
try: try:
await asyncio.sleep(self._stt_ttfb_timeout) await asyncio.sleep(self._stt_ttfb_timeout)
await self.stop_ttfb_metrics() if self._last_transcript_time > 0:
await self.stop_ttfb_metrics(end_time=self._last_transcript_time)
except asyncio.CancelledError: except asyncio.CancelledError:
# Task was cancelled (new utterance or interruption), which is expected behavior # Task was cancelled (new utterance or interruption), which is expected behavior
pass pass