Only start monitoring for user idleness once a User or Bot StartedSpeakingFrame is processed

This commit is contained in:
Mark Backman
2025-01-17 10:28:58 -05:00
parent 195fd43712
commit ddf3a940cb

View File

@@ -13,7 +13,6 @@ from pipecat.frames.frames import (
CancelFrame, CancelFrame,
EndFrame, EndFrame,
Frame, Frame,
StartFrame,
UserStartedSpeakingFrame, UserStartedSpeakingFrame,
UserStoppedSpeakingFrame, UserStoppedSpeakingFrame,
) )
@@ -26,11 +25,15 @@ class UserIdleProcessor(FrameProcessor):
If the timeout is reached before any interaction occurred the provided callback will be called. If the timeout is reached before any interaction occurred the provided callback will be called.
The callback can be either: The callback can be either:
- async def callback(processor: UserIdleProcessor) -> None # Legacy - async def callback(processor: UserIdleProcessor) -> None # Old
- async def callback(processor: UserIdleProcessor, retry_count: int) -> bool # New - async def callback(processor: UserIdleProcessor, retry_count: int) -> bool # New
The new style callback receives the current retry count and should return True The new style callback receives the current retry count and should return True
to continue monitoring or False to stop. to continue monitoring or False to stop.
The processor starts monitoring for idle time only after receiving the first
UserStartedSpeakingFrame or BotSpeakingFrame, ensuring that idle detection
begins when the actual conversation starts.
""" """
def __init__( def __init__(
@@ -48,6 +51,9 @@ class UserIdleProcessor(FrameProcessor):
self._timeout = timeout self._timeout = timeout
self._retry_count = 0 self._retry_count = 0
self._interrupted = False self._interrupted = False
self._conversation_started = False
self._idle_task = None
self._idle_event = asyncio.Event()
def _wrap_callback( def _wrap_callback(
self, self,
@@ -74,9 +80,17 @@ class UserIdleProcessor(FrameProcessor):
return wrapper return wrapper
def _create_idle_task(self):
"""Create the idle task if it hasn't been created yet."""
if self._idle_task is None:
self._idle_task = self.get_event_loop().create_task(self._idle_task_handler())
async def _stop(self): async def _stop(self):
"""Stop the idle task if it exists"""
if self._idle_task is not None:
self._idle_task.cancel() self._idle_task.cancel()
await self._idle_task await self._idle_task
self._idle_task = None
async def process_frame(self, frame: Frame, direction: FrameDirection): async def process_frame(self, frame: Frame, direction: FrameDirection):
await super().process_frame(frame, direction) await super().process_frame(frame, direction)
@@ -84,11 +98,21 @@ class UserIdleProcessor(FrameProcessor):
# Check for end frames before processing # Check for end frames before processing
if isinstance(frame, (EndFrame, CancelFrame)): if isinstance(frame, (EndFrame, CancelFrame)):
await self.push_frame(frame, direction) # Push the frame down the pipeline await self.push_frame(frame, direction) # Push the frame down the pipeline
await self._stop() # Then stop the idle task if self._idle_task:
return # Return early since we're ending await self._stop() # Stop the idle task, if it exists
return
await self.push_frame(frame, direction) await self.push_frame(frame, direction)
# Start monitoring on first conversation activity
if not self._conversation_started and isinstance(
frame, (UserStartedSpeakingFrame, BotSpeakingFrame)
):
self._conversation_started = True
self._create_idle_task()
# Only process these events if conversation has started
if self._conversation_started:
# We shouldn't call the idle callback if the user or the bot are speaking # We shouldn't call the idle callback if the user or the bot are speaking
if isinstance(frame, UserStartedSpeakingFrame): if isinstance(frame, UserStartedSpeakingFrame):
self._interrupted = True self._interrupted = True
@@ -100,12 +124,9 @@ class UserIdleProcessor(FrameProcessor):
self._idle_event.set() self._idle_event.set()
async def cleanup(self): async def cleanup(self):
if self._idle_task: # Only stop if task exists
await self._stop() await self._stop()
def _create_idle_task(self):
self._idle_event = asyncio.Event()
self._idle_task = self.get_event_loop().create_task(self._idle_task_handler())
async def _idle_task_handler(self): async def _idle_task_handler(self):
while True: while True:
try: try: