Update on_user_idle to on_user_turn_idle

This commit is contained in:
Mark Backman
2026-01-17 06:56:08 -05:00
parent 473d39791b
commit 1e1160906e
6 changed files with 44 additions and 41 deletions

View File

@@ -1 +1 @@
- Added `UserIdleController` for detecting user idle state, integrated into `LLMUserAggregator` and `UserTurnProcessor` via optional `user_idle_timeout` parameter. Emits `on_user_idle` event for application-level handling. Deprecated `UserIdleProcessor` in favor of the new compositional approach.
- Added `UserIdleController` for detecting user idle state, integrated into `LLMUserAggregator` and `UserTurnProcessor` via optional `user_idle_timeout` parameter. Emits `on_user_turn_idle` event for application-level handling. Deprecated `UserIdleProcessor` in favor of the new compositional approach.

View File

@@ -14,7 +14,6 @@ from pipecat.audio.turn.smart_turn.local_smart_turn_v3 import LocalSmartTurnAnal
from pipecat.audio.vad.silero import SileroVADAnalyzer
from pipecat.audio.vad.vad_analyzer import VADParams
from pipecat.frames.frames import (
EndFrame,
EndTaskFrame,
LLMMessagesAppendFrame,
LLMRunFrame,
@@ -155,12 +154,12 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
# Set up idle handling with retry logic
idle_handler = IdleHandler()
@user_aggregator.event_handler("on_user_idle")
async def handle_user_idle(aggregator):
@user_aggregator.event_handler("on_user_turn_idle")
async def on_user_turn_idle(aggregator):
await idle_handler.handle_idle(aggregator)
@user_aggregator.event_handler("on_user_turn_started")
async def handle_user_turn_started(aggregator, strategy):
async def on_user_turn_started(aggregator, strategy):
idle_handler.reset()
@transport.event_handler("on_client_connected")

View File

@@ -82,7 +82,7 @@ class LLMUserAggregatorParams:
user_turn_stop_timeout: Time in seconds to wait before considering the
user's turn finished.
user_idle_timeout: Optional timeout in seconds for detecting user idle state.
If set, the aggregator will emit an `on_user_idle` event when the user
If set, the aggregator will emit an `on_user_turn_idle` event when the user
has been idle (not speaking) for this duration. Set to None to disable
idle detection.
"""
@@ -297,7 +297,7 @@ class LLMUserAggregator(LLMContextAggregator):
- on_user_turn_started: Called when the user turn starts
- on_user_turn_stopped: Called when the user turn ends
- on_user_turn_stop_timeout: Called when no user turn stop strategy triggers
- on_user_idle: Called when the user has been idle for the configured timeout
- on_user_turn_idle: Called when the user has been idle for the configured timeout
Example::
@@ -313,8 +313,8 @@ class LLMUserAggregator(LLMContextAggregator):
async def on_user_turn_stop_timeout(aggregator):
...
@aggregator.event_handler("on_user_idle")
async def on_user_idle(aggregator):
@aggregator.event_handler("on_user_turn_idle")
async def on_user_turn_idle(aggregator):
...
"""
@@ -339,7 +339,7 @@ class LLMUserAggregator(LLMContextAggregator):
self._register_event_handler("on_user_turn_started")
self._register_event_handler("on_user_turn_stopped")
self._register_event_handler("on_user_turn_stop_timeout")
self._register_event_handler("on_user_idle")
self._register_event_handler("on_user_turn_idle")
user_turn_strategies = self._params.user_turn_strategies or UserTurnStrategies()
@@ -368,7 +368,9 @@ class LLMUserAggregator(LLMContextAggregator):
self._user_idle_controller = UserIdleController(
user_idle_timeout=self._params.user_idle_timeout
)
self._user_idle_controller.add_event_handler("on_user_idle", self._on_user_idle)
self._user_idle_controller.add_event_handler(
"on_user_turn_idle", self._on_user_turn_idle
)
async def cleanup(self):
"""Clean up processor resources."""
@@ -594,8 +596,8 @@ class LLMUserAggregator(LLMContextAggregator):
async def _on_user_turn_stop_timeout(self, controller):
await self._call_event_handler("on_user_turn_stop_timeout")
async def _on_user_idle(self, controller):
await self._call_event_handler("on_user_idle")
async def _on_user_turn_idle(self, controller):
await self._call_event_handler("on_user_turn_idle")
class LLMAssistantAggregator(LLMContextAggregator):

View File

@@ -36,12 +36,12 @@ class UserIdleController(BaseObject):
Event handlers available:
- on_user_idle: Emitted when the user has been idle for the timeout period.
- on_user_turn_idle: Emitted when the user has been idle for the timeout period.
Example::
@controller.event_handler("on_user_idle")
async def on_user_idle(controller):
@controller.event_handler("on_user_turn_idle")
async def on_user_turn_idle(controller):
# Handle user idle - send reminder, prompt, etc.
...
"""
@@ -68,7 +68,7 @@ class UserIdleController(BaseObject):
self.user_idle_event = asyncio.Event()
self.user_idle_task: Optional[asyncio.Task] = None
self._register_event_handler("on_user_idle", sync=True)
self._register_event_handler("on_user_turn_idle", sync=True)
@property
def task_manager(self) -> BaseTaskManager:
@@ -161,7 +161,7 @@ class UserIdleController(BaseObject):
frames are received (UserSpeakingFrame or BotSpeakingFrame). Function calls
are tracked via state since they only send start/end frames. If no activity
is detected for the configured timeout period and no function call is in
progress, the on_user_idle event is triggered.
progress, the on_user_turn_idle event is triggered.
"""
while True:
try:
@@ -170,4 +170,4 @@ class UserIdleController(BaseObject):
except asyncio.TimeoutError:
# Only trigger if conversation has started and no function call is in progress
if self._conversation_started and not self._function_call_in_progress:
await self._call_event_handler("on_user_idle")
await self._call_event_handler("on_user_turn_idle")

View File

@@ -39,7 +39,7 @@ class UserTurnProcessor(FrameProcessor):
- on_user_turn_started: Emitted when a user turn starts.
- on_user_turn_stopped: Emitted when a user turn stops.
- on_user_turn_stop_timeout: Emitted if no stop strategy triggers before timeout.
- on_user_idle: Emitted when the user has been idle for the configured timeout.
- on_user_turn_idle: Emitted when the user has been idle for the configured timeout.
Example::
@@ -55,8 +55,8 @@ class UserTurnProcessor(FrameProcessor):
async def on_user_turn_stop_timeout(processor):
...
@processor.event_handler("on_user_idle")
async def on_user_idle(processor):
@processor.event_handler("on_user_turn_idle")
async def on_user_turn_idle(processor):
...
"""
@@ -76,7 +76,7 @@ class UserTurnProcessor(FrameProcessor):
user_turn_stop_timeout: Timeout in seconds to automatically stop a user turn
if no activity is detected.
user_idle_timeout: Optional timeout in seconds for detecting user idle state.
If set, the processor will emit an `on_user_idle` event when the user
If set, the processor will emit an `on_user_turn_idle` event when the user
has been idle (not speaking) for this duration. Set to None to disable
idle detection.
**kwargs: Additional keyword arguments.
@@ -86,7 +86,7 @@ class UserTurnProcessor(FrameProcessor):
self._register_event_handler("on_user_turn_started")
self._register_event_handler("on_user_turn_stopped")
self._register_event_handler("on_user_turn_stop_timeout")
self._register_event_handler("on_user_idle")
self._register_event_handler("on_user_turn_idle")
self._user_turn_controller = UserTurnController(
user_turn_strategies=user_turn_strategies or UserTurnStrategies(),
@@ -108,7 +108,9 @@ class UserTurnProcessor(FrameProcessor):
self._user_idle_controller: Optional[UserIdleController] = None
if user_idle_timeout:
self._user_idle_controller = UserIdleController(user_idle_timeout=user_idle_timeout)
self._user_idle_controller.add_event_handler("on_user_idle", self._on_user_idle)
self._user_idle_controller.add_event_handler(
"on_user_turn_idle", self._on_user_turn_idle
)
async def cleanup(self):
"""Clean up processor resources."""
@@ -208,5 +210,5 @@ class UserTurnProcessor(FrameProcessor):
async def _on_user_turn_stop_timeout(self, controller):
await self._call_event_handler("on_user_turn_stop_timeout")
async def _on_user_idle(self, controller):
await self._call_event_handler("on_user_idle")
async def _on_user_turn_idle(self, controller):
await self._call_event_handler("on_user_turn_idle")

View File

@@ -32,8 +32,8 @@ class TestUserIdleController(unittest.IsolatedAsyncioTestCase):
idle_triggered = False
@controller.event_handler("on_user_idle")
async def on_user_idle(controller):
@controller.event_handler("on_user_turn_idle")
async def on_user_turn_idle(controller):
nonlocal idle_triggered
idle_triggered = True
@@ -54,8 +54,8 @@ class TestUserIdleController(unittest.IsolatedAsyncioTestCase):
idle_triggered = False
@controller.event_handler("on_user_idle")
async def on_user_idle(controller):
@controller.event_handler("on_user_turn_idle")
async def on_user_turn_idle(controller):
nonlocal idle_triggered
idle_triggered = True
@@ -78,8 +78,8 @@ class TestUserIdleController(unittest.IsolatedAsyncioTestCase):
idle_triggered = False
@controller.event_handler("on_user_idle")
async def on_user_idle(controller):
@controller.event_handler("on_user_turn_idle")
async def on_user_turn_idle(controller):
nonlocal idle_triggered
idle_triggered = True
@@ -102,8 +102,8 @@ class TestUserIdleController(unittest.IsolatedAsyncioTestCase):
idle_triggered = False
@controller.event_handler("on_user_idle")
async def on_user_idle(controller):
@controller.event_handler("on_user_turn_idle")
async def on_user_turn_idle(controller):
nonlocal idle_triggered
idle_triggered = True
@@ -143,8 +143,8 @@ class TestUserIdleController(unittest.IsolatedAsyncioTestCase):
idle_triggered = False
@controller.event_handler("on_user_idle")
async def on_user_idle(controller):
@controller.event_handler("on_user_turn_idle")
async def on_user_turn_idle(controller):
nonlocal idle_triggered
idle_triggered = True
@@ -162,8 +162,8 @@ class TestUserIdleController(unittest.IsolatedAsyncioTestCase):
idle_triggered = False
@controller.event_handler("on_user_idle")
async def on_user_idle(controller):
@controller.event_handler("on_user_turn_idle")
async def on_user_turn_idle(controller):
nonlocal idle_triggered
idle_triggered = True
@@ -184,8 +184,8 @@ class TestUserIdleController(unittest.IsolatedAsyncioTestCase):
idle_count = 0
@controller.event_handler("on_user_idle")
async def on_user_idle(controller):
@controller.event_handler("on_user_turn_idle")
async def on_user_turn_idle(controller):
nonlocal idle_count
idle_count += 1