Merge pull request #3488 from pipecat-ai/mb/on-user-turn-idle
Update on_user_idle to on_user_turn_idle
This commit is contained in:
@@ -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.
|
||||||
|
|||||||
@@ -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.silero import SileroVADAnalyzer
|
||||||
from pipecat.audio.vad.vad_analyzer import VADParams
|
from pipecat.audio.vad.vad_analyzer import VADParams
|
||||||
from pipecat.frames.frames import (
|
from pipecat.frames.frames import (
|
||||||
EndFrame,
|
|
||||||
EndTaskFrame,
|
EndTaskFrame,
|
||||||
LLMMessagesAppendFrame,
|
LLMMessagesAppendFrame,
|
||||||
LLMRunFrame,
|
LLMRunFrame,
|
||||||
@@ -155,12 +154,12 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
|
|||||||
# Set up idle handling with retry logic
|
# Set up idle handling with retry logic
|
||||||
idle_handler = IdleHandler()
|
idle_handler = IdleHandler()
|
||||||
|
|
||||||
@user_aggregator.event_handler("on_user_idle")
|
@user_aggregator.event_handler("on_user_turn_idle")
|
||||||
async def handle_user_idle(aggregator):
|
async def on_user_turn_idle(aggregator):
|
||||||
await idle_handler.handle_idle(aggregator)
|
await idle_handler.handle_idle(aggregator)
|
||||||
|
|
||||||
@user_aggregator.event_handler("on_user_turn_started")
|
@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()
|
idle_handler.reset()
|
||||||
|
|
||||||
@transport.event_handler("on_client_connected")
|
@transport.event_handler("on_client_connected")
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ class LLMUserAggregatorParams:
|
|||||||
user_turn_stop_timeout: Time in seconds to wait before considering the
|
user_turn_stop_timeout: Time in seconds to wait before considering the
|
||||||
user's turn finished.
|
user's turn finished.
|
||||||
user_idle_timeout: Optional timeout in seconds for detecting user idle state.
|
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
|
has been idle (not speaking) for this duration. Set to None to disable
|
||||||
idle detection.
|
idle detection.
|
||||||
"""
|
"""
|
||||||
@@ -297,7 +297,7 @@ class LLMUserAggregator(LLMContextAggregator):
|
|||||||
- on_user_turn_started: Called when the user turn starts
|
- on_user_turn_started: Called when the user turn starts
|
||||||
- on_user_turn_stopped: Called when the user turn ends
|
- 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_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::
|
Example::
|
||||||
|
|
||||||
@@ -313,8 +313,8 @@ class LLMUserAggregator(LLMContextAggregator):
|
|||||||
async def on_user_turn_stop_timeout(aggregator):
|
async def on_user_turn_stop_timeout(aggregator):
|
||||||
...
|
...
|
||||||
|
|
||||||
@aggregator.event_handler("on_user_idle")
|
@aggregator.event_handler("on_user_turn_idle")
|
||||||
async def on_user_idle(aggregator):
|
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_started")
|
||||||
self._register_event_handler("on_user_turn_stopped")
|
self._register_event_handler("on_user_turn_stopped")
|
||||||
self._register_event_handler("on_user_turn_stop_timeout")
|
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()
|
user_turn_strategies = self._params.user_turn_strategies or UserTurnStrategies()
|
||||||
|
|
||||||
@@ -368,7 +368,9 @@ class LLMUserAggregator(LLMContextAggregator):
|
|||||||
self._user_idle_controller = UserIdleController(
|
self._user_idle_controller = UserIdleController(
|
||||||
user_idle_timeout=self._params.user_idle_timeout
|
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):
|
async def cleanup(self):
|
||||||
"""Clean up processor resources."""
|
"""Clean up processor resources."""
|
||||||
@@ -594,8 +596,8 @@ class LLMUserAggregator(LLMContextAggregator):
|
|||||||
async def _on_user_turn_stop_timeout(self, controller):
|
async def _on_user_turn_stop_timeout(self, controller):
|
||||||
await self._call_event_handler("on_user_turn_stop_timeout")
|
await self._call_event_handler("on_user_turn_stop_timeout")
|
||||||
|
|
||||||
async def _on_user_idle(self, controller):
|
async def _on_user_turn_idle(self, controller):
|
||||||
await self._call_event_handler("on_user_idle")
|
await self._call_event_handler("on_user_turn_idle")
|
||||||
|
|
||||||
|
|
||||||
class LLMAssistantAggregator(LLMContextAggregator):
|
class LLMAssistantAggregator(LLMContextAggregator):
|
||||||
|
|||||||
@@ -36,12 +36,12 @@ class UserIdleController(BaseObject):
|
|||||||
|
|
||||||
Event handlers available:
|
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::
|
Example::
|
||||||
|
|
||||||
@controller.event_handler("on_user_idle")
|
@controller.event_handler("on_user_turn_idle")
|
||||||
async def on_user_idle(controller):
|
async def on_user_turn_idle(controller):
|
||||||
# Handle user idle - send reminder, prompt, etc.
|
# Handle user idle - send reminder, prompt, etc.
|
||||||
...
|
...
|
||||||
"""
|
"""
|
||||||
@@ -68,7 +68,7 @@ class UserIdleController(BaseObject):
|
|||||||
self.user_idle_event = asyncio.Event()
|
self.user_idle_event = asyncio.Event()
|
||||||
self.user_idle_task: Optional[asyncio.Task] = None
|
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
|
@property
|
||||||
def task_manager(self) -> BaseTaskManager:
|
def task_manager(self) -> BaseTaskManager:
|
||||||
@@ -161,7 +161,7 @@ class UserIdleController(BaseObject):
|
|||||||
frames are received (UserSpeakingFrame or BotSpeakingFrame). Function calls
|
frames are received (UserSpeakingFrame or BotSpeakingFrame). Function calls
|
||||||
are tracked via state since they only send start/end frames. If no activity
|
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
|
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:
|
while True:
|
||||||
try:
|
try:
|
||||||
@@ -170,4 +170,4 @@ class UserIdleController(BaseObject):
|
|||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
# Only trigger if conversation has started and no function call is in progress
|
# Only trigger if conversation has started and no function call is in progress
|
||||||
if self._conversation_started and not self._function_call_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")
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class UserTurnProcessor(FrameProcessor):
|
|||||||
- on_user_turn_started: Emitted when a user turn starts.
|
- on_user_turn_started: Emitted when a user turn starts.
|
||||||
- on_user_turn_stopped: Emitted when a user turn stops.
|
- 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_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::
|
Example::
|
||||||
|
|
||||||
@@ -55,8 +55,8 @@ class UserTurnProcessor(FrameProcessor):
|
|||||||
async def on_user_turn_stop_timeout(processor):
|
async def on_user_turn_stop_timeout(processor):
|
||||||
...
|
...
|
||||||
|
|
||||||
@processor.event_handler("on_user_idle")
|
@processor.event_handler("on_user_turn_idle")
|
||||||
async def on_user_idle(processor):
|
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
|
user_turn_stop_timeout: Timeout in seconds to automatically stop a user turn
|
||||||
if no activity is detected.
|
if no activity is detected.
|
||||||
user_idle_timeout: Optional timeout in seconds for detecting user idle state.
|
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
|
has been idle (not speaking) for this duration. Set to None to disable
|
||||||
idle detection.
|
idle detection.
|
||||||
**kwargs: Additional keyword arguments.
|
**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_started")
|
||||||
self._register_event_handler("on_user_turn_stopped")
|
self._register_event_handler("on_user_turn_stopped")
|
||||||
self._register_event_handler("on_user_turn_stop_timeout")
|
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(
|
self._user_turn_controller = UserTurnController(
|
||||||
user_turn_strategies=user_turn_strategies or UserTurnStrategies(),
|
user_turn_strategies=user_turn_strategies or UserTurnStrategies(),
|
||||||
@@ -108,7 +108,9 @@ class UserTurnProcessor(FrameProcessor):
|
|||||||
self._user_idle_controller: Optional[UserIdleController] = None
|
self._user_idle_controller: Optional[UserIdleController] = None
|
||||||
if user_idle_timeout:
|
if user_idle_timeout:
|
||||||
self._user_idle_controller = UserIdleController(user_idle_timeout=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):
|
async def cleanup(self):
|
||||||
"""Clean up processor resources."""
|
"""Clean up processor resources."""
|
||||||
@@ -208,5 +210,5 @@ class UserTurnProcessor(FrameProcessor):
|
|||||||
async def _on_user_turn_stop_timeout(self, controller):
|
async def _on_user_turn_stop_timeout(self, controller):
|
||||||
await self._call_event_handler("on_user_turn_stop_timeout")
|
await self._call_event_handler("on_user_turn_stop_timeout")
|
||||||
|
|
||||||
async def _on_user_idle(self, controller):
|
async def _on_user_turn_idle(self, controller):
|
||||||
await self._call_event_handler("on_user_idle")
|
await self._call_event_handler("on_user_turn_idle")
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ class TestUserIdleController(unittest.IsolatedAsyncioTestCase):
|
|||||||
|
|
||||||
idle_triggered = False
|
idle_triggered = False
|
||||||
|
|
||||||
@controller.event_handler("on_user_idle")
|
@controller.event_handler("on_user_turn_idle")
|
||||||
async def on_user_idle(controller):
|
async def on_user_turn_idle(controller):
|
||||||
nonlocal idle_triggered
|
nonlocal idle_triggered
|
||||||
idle_triggered = True
|
idle_triggered = True
|
||||||
|
|
||||||
@@ -54,8 +54,8 @@ class TestUserIdleController(unittest.IsolatedAsyncioTestCase):
|
|||||||
|
|
||||||
idle_triggered = False
|
idle_triggered = False
|
||||||
|
|
||||||
@controller.event_handler("on_user_idle")
|
@controller.event_handler("on_user_turn_idle")
|
||||||
async def on_user_idle(controller):
|
async def on_user_turn_idle(controller):
|
||||||
nonlocal idle_triggered
|
nonlocal idle_triggered
|
||||||
idle_triggered = True
|
idle_triggered = True
|
||||||
|
|
||||||
@@ -78,8 +78,8 @@ class TestUserIdleController(unittest.IsolatedAsyncioTestCase):
|
|||||||
|
|
||||||
idle_triggered = False
|
idle_triggered = False
|
||||||
|
|
||||||
@controller.event_handler("on_user_idle")
|
@controller.event_handler("on_user_turn_idle")
|
||||||
async def on_user_idle(controller):
|
async def on_user_turn_idle(controller):
|
||||||
nonlocal idle_triggered
|
nonlocal idle_triggered
|
||||||
idle_triggered = True
|
idle_triggered = True
|
||||||
|
|
||||||
@@ -102,8 +102,8 @@ class TestUserIdleController(unittest.IsolatedAsyncioTestCase):
|
|||||||
|
|
||||||
idle_triggered = False
|
idle_triggered = False
|
||||||
|
|
||||||
@controller.event_handler("on_user_idle")
|
@controller.event_handler("on_user_turn_idle")
|
||||||
async def on_user_idle(controller):
|
async def on_user_turn_idle(controller):
|
||||||
nonlocal idle_triggered
|
nonlocal idle_triggered
|
||||||
idle_triggered = True
|
idle_triggered = True
|
||||||
|
|
||||||
@@ -143,8 +143,8 @@ class TestUserIdleController(unittest.IsolatedAsyncioTestCase):
|
|||||||
|
|
||||||
idle_triggered = False
|
idle_triggered = False
|
||||||
|
|
||||||
@controller.event_handler("on_user_idle")
|
@controller.event_handler("on_user_turn_idle")
|
||||||
async def on_user_idle(controller):
|
async def on_user_turn_idle(controller):
|
||||||
nonlocal idle_triggered
|
nonlocal idle_triggered
|
||||||
idle_triggered = True
|
idle_triggered = True
|
||||||
|
|
||||||
@@ -162,8 +162,8 @@ class TestUserIdleController(unittest.IsolatedAsyncioTestCase):
|
|||||||
|
|
||||||
idle_triggered = False
|
idle_triggered = False
|
||||||
|
|
||||||
@controller.event_handler("on_user_idle")
|
@controller.event_handler("on_user_turn_idle")
|
||||||
async def on_user_idle(controller):
|
async def on_user_turn_idle(controller):
|
||||||
nonlocal idle_triggered
|
nonlocal idle_triggered
|
||||||
idle_triggered = True
|
idle_triggered = True
|
||||||
|
|
||||||
@@ -184,8 +184,8 @@ class TestUserIdleController(unittest.IsolatedAsyncioTestCase):
|
|||||||
|
|
||||||
idle_count = 0
|
idle_count = 0
|
||||||
|
|
||||||
@controller.event_handler("on_user_idle")
|
@controller.event_handler("on_user_turn_idle")
|
||||||
async def on_user_idle(controller):
|
async def on_user_turn_idle(controller):
|
||||||
nonlocal idle_count
|
nonlocal idle_count
|
||||||
idle_count += 1
|
idle_count += 1
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user