diff --git a/changelog/4183.fixed.md b/changelog/4183.fixed.md new file mode 100644 index 000000000..a1f5ad4c8 --- /dev/null +++ b/changelog/4183.fixed.md @@ -0,0 +1 @@ +- Fixed a timing issue where turn detection timer tasks (idle controller, speech timeout, turn analyzer, and turn completion) could miss their first tick because the newly created asyncio task was not yet scheduled when the caller continued. diff --git a/src/pipecat/turns/user_idle_controller.py b/src/pipecat/turns/user_idle_controller.py index b3b7e8074..188daa327 100644 --- a/src/pipecat/turns/user_idle_controller.py +++ b/src/pipecat/turns/user_idle_controller.py @@ -143,6 +143,8 @@ class UserIdleController(BaseObject): self._idle_timer_expired(), f"{self}::idle_timer", ) + # Make sure the task is scheduled. + await asyncio.sleep(0) async def _cancel_idle_timer(self): """Cancel the idle timer if running.""" diff --git a/src/pipecat/turns/user_stop/speech_timeout_user_turn_stop_strategy.py b/src/pipecat/turns/user_stop/speech_timeout_user_turn_stop_strategy.py index 1e127248f..db6ef3c00 100644 --- a/src/pipecat/turns/user_stop/speech_timeout_user_turn_stop_strategy.py +++ b/src/pipecat/turns/user_stop/speech_timeout_user_turn_stop_strategy.py @@ -153,6 +153,8 @@ class SpeechTimeoutUserTurnStopStrategy(BaseUserTurnStopStrategy): self._timeout_task = self.task_manager.create_task( self._timeout_handler(timeout), f"{self}::_timeout_handler" ) + # Make sure the task is scheduled. + await asyncio.sleep(0) async def _handle_transcription(self, frame: TranscriptionFrame): """Handle user transcription.""" @@ -174,6 +176,8 @@ class SpeechTimeoutUserTurnStopStrategy(BaseUserTurnStopStrategy): self._timeout_task = self.task_manager.create_task( self._timeout_handler(timeout), f"{self}::_timeout_handler" ) + # Make sure the task is scheduled. + await asyncio.sleep(0) def _calculate_timeout(self) -> float: """Calculate the timeout value based on current state. diff --git a/src/pipecat/turns/user_stop/turn_analyzer_user_turn_stop_strategy.py b/src/pipecat/turns/user_stop/turn_analyzer_user_turn_stop_strategy.py index 895d0976f..2931965aa 100644 --- a/src/pipecat/turns/user_stop/turn_analyzer_user_turn_stop_strategy.py +++ b/src/pipecat/turns/user_stop/turn_analyzer_user_turn_stop_strategy.py @@ -193,6 +193,8 @@ class TurnAnalyzerUserTurnStopStrategy(BaseUserTurnStopStrategy): self._timeout_task = self.task_manager.create_task( self._timeout_handler(timeout), f"{self}::_timeout_handler" ) + # Make sure the task is scheduled. + await asyncio.sleep(0) async def _handle_transcription(self, frame: TranscriptionFrame): """Handle user transcription.""" @@ -217,6 +219,8 @@ class TurnAnalyzerUserTurnStopStrategy(BaseUserTurnStopStrategy): self._timeout_task = self.task_manager.create_task( self._timeout_handler(timeout), f"{self}::_timeout_handler" ) + # Make sure the task is scheduled. + await asyncio.sleep(0) async def _handle_prediction_result(self, result: Optional[MetricsData]): """Handle a prediction result event from the turn analyzer.""" diff --git a/src/pipecat/turns/user_turn_completion_mixin.py b/src/pipecat/turns/user_turn_completion_mixin.py index 40ae551b4..fc421e411 100644 --- a/src/pipecat/turns/user_turn_completion_mixin.py +++ b/src/pipecat/turns/user_turn_completion_mixin.py @@ -254,6 +254,8 @@ class UserTurnCompletionLLMServiceMixin: self._incomplete_timeout_handler(incomplete_type, timeout), f"_incomplete_timeout_{incomplete_type}", ) + # Make sure the task is scheduled. + await asyncio.sleep(0) async def _cancel_incomplete_timeout(self): """Cancel any pending incomplete timeout task."""