Skip to content

Commit 1e11609

Browse files
committed
Update on_user_idle to on_user_turn_idle
1 parent 473d397 commit 1e11609

6 files changed

Lines changed: 44 additions & 41 deletions

File tree

changelog/3482.added.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +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.
1+
- 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.

examples/foundational/17-detect-user-idle.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
from pipecat.audio.vad.silero import SileroVADAnalyzer
1515
from pipecat.audio.vad.vad_analyzer import VADParams
1616
from pipecat.frames.frames import (
17-
EndFrame,
1817
EndTaskFrame,
1918
LLMMessagesAppendFrame,
2019
LLMRunFrame,
@@ -155,12 +154,12 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
155154
# Set up idle handling with retry logic
156155
idle_handler = IdleHandler()
157156

158-
@user_aggregator.event_handler("on_user_idle")
159-
async def handle_user_idle(aggregator):
157+
@user_aggregator.event_handler("on_user_turn_idle")
158+
async def on_user_turn_idle(aggregator):
160159
await idle_handler.handle_idle(aggregator)
161160

162161
@user_aggregator.event_handler("on_user_turn_started")
163-
async def handle_user_turn_started(aggregator, strategy):
162+
async def on_user_turn_started(aggregator, strategy):
164163
idle_handler.reset()
165164

166165
@transport.event_handler("on_client_connected")

src/pipecat/processors/aggregators/llm_response_universal.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ class LLMUserAggregatorParams:
8282
user_turn_stop_timeout: Time in seconds to wait before considering the
8383
user's turn finished.
8484
user_idle_timeout: Optional timeout in seconds for detecting user idle state.
85-
If set, the aggregator will emit an `on_user_idle` event when the user
85+
If set, the aggregator will emit an `on_user_turn_idle` event when the user
8686
has been idle (not speaking) for this duration. Set to None to disable
8787
idle detection.
8888
"""
@@ -297,7 +297,7 @@ class LLMUserAggregator(LLMContextAggregator):
297297
- on_user_turn_started: Called when the user turn starts
298298
- on_user_turn_stopped: Called when the user turn ends
299299
- on_user_turn_stop_timeout: Called when no user turn stop strategy triggers
300-
- on_user_idle: Called when the user has been idle for the configured timeout
300+
- on_user_turn_idle: Called when the user has been idle for the configured timeout
301301
302302
Example::
303303
@@ -313,8 +313,8 @@ async def on_user_turn_stopped(aggregator, strategy: BaseUserTurnStopStrategy, m
313313
async def on_user_turn_stop_timeout(aggregator):
314314
...
315315
316-
@aggregator.event_handler("on_user_idle")
317-
async def on_user_idle(aggregator):
316+
@aggregator.event_handler("on_user_turn_idle")
317+
async def on_user_turn_idle(aggregator):
318318
...
319319
320320
"""
@@ -339,7 +339,7 @@ def __init__(
339339
self._register_event_handler("on_user_turn_started")
340340
self._register_event_handler("on_user_turn_stopped")
341341
self._register_event_handler("on_user_turn_stop_timeout")
342-
self._register_event_handler("on_user_idle")
342+
self._register_event_handler("on_user_turn_idle")
343343

344344
user_turn_strategies = self._params.user_turn_strategies or UserTurnStrategies()
345345

@@ -368,7 +368,9 @@ def __init__(
368368
self._user_idle_controller = UserIdleController(
369369
user_idle_timeout=self._params.user_idle_timeout
370370
)
371-
self._user_idle_controller.add_event_handler("on_user_idle", self._on_user_idle)
371+
self._user_idle_controller.add_event_handler(
372+
"on_user_turn_idle", self._on_user_turn_idle
373+
)
372374

373375
async def cleanup(self):
374376
"""Clean up processor resources."""
@@ -594,8 +596,8 @@ async def _on_user_turn_stopped(
594596
async def _on_user_turn_stop_timeout(self, controller):
595597
await self._call_event_handler("on_user_turn_stop_timeout")
596598

597-
async def _on_user_idle(self, controller):
598-
await self._call_event_handler("on_user_idle")
599+
async def _on_user_turn_idle(self, controller):
600+
await self._call_event_handler("on_user_turn_idle")
599601

600602

601603
class LLMAssistantAggregator(LLMContextAggregator):

src/pipecat/turns/user_idle_controller.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,12 @@ class UserIdleController(BaseObject):
3636
3737
Event handlers available:
3838
39-
- on_user_idle: Emitted when the user has been idle for the timeout period.
39+
- on_user_turn_idle: Emitted when the user has been idle for the timeout period.
4040
4141
Example::
4242
43-
@controller.event_handler("on_user_idle")
44-
async def on_user_idle(controller):
43+
@controller.event_handler("on_user_turn_idle")
44+
async def on_user_turn_idle(controller):
4545
# Handle user idle - send reminder, prompt, etc.
4646
...
4747
"""
@@ -68,7 +68,7 @@ def __init__(
6868
self.user_idle_event = asyncio.Event()
6969
self.user_idle_task: Optional[asyncio.Task] = None
7070

71-
self._register_event_handler("on_user_idle", sync=True)
71+
self._register_event_handler("on_user_turn_idle", sync=True)
7272

7373
@property
7474
def task_manager(self) -> BaseTaskManager:
@@ -161,7 +161,7 @@ async def user_idle_task_handler(self):
161161
frames are received (UserSpeakingFrame or BotSpeakingFrame). Function calls
162162
are tracked via state since they only send start/end frames. If no activity
163163
is detected for the configured timeout period and no function call is in
164-
progress, the on_user_idle event is triggered.
164+
progress, the on_user_turn_idle event is triggered.
165165
"""
166166
while True:
167167
try:
@@ -170,4 +170,4 @@ async def user_idle_task_handler(self):
170170
except asyncio.TimeoutError:
171171
# Only trigger if conversation has started and no function call is in progress
172172
if self._conversation_started and not self._function_call_in_progress:
173-
await self._call_event_handler("on_user_idle")
173+
await self._call_event_handler("on_user_turn_idle")

src/pipecat/turns/user_turn_processor.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class UserTurnProcessor(FrameProcessor):
3939
- on_user_turn_started: Emitted when a user turn starts.
4040
- on_user_turn_stopped: Emitted when a user turn stops.
4141
- on_user_turn_stop_timeout: Emitted if no stop strategy triggers before timeout.
42-
- on_user_idle: Emitted when the user has been idle for the configured timeout.
42+
- on_user_turn_idle: Emitted when the user has been idle for the configured timeout.
4343
4444
Example::
4545
@@ -55,8 +55,8 @@ async def on_user_turn_stopped(processor, strategy: BaseUserTurnStopStrategy):
5555
async def on_user_turn_stop_timeout(processor):
5656
...
5757
58-
@processor.event_handler("on_user_idle")
59-
async def on_user_idle(processor):
58+
@processor.event_handler("on_user_turn_idle")
59+
async def on_user_turn_idle(processor):
6060
...
6161
6262
"""
@@ -76,7 +76,7 @@ def __init__(
7676
user_turn_stop_timeout: Timeout in seconds to automatically stop a user turn
7777
if no activity is detected.
7878
user_idle_timeout: Optional timeout in seconds for detecting user idle state.
79-
If set, the processor will emit an `on_user_idle` event when the user
79+
If set, the processor will emit an `on_user_turn_idle` event when the user
8080
has been idle (not speaking) for this duration. Set to None to disable
8181
idle detection.
8282
**kwargs: Additional keyword arguments.
@@ -86,7 +86,7 @@ def __init__(
8686
self._register_event_handler("on_user_turn_started")
8787
self._register_event_handler("on_user_turn_stopped")
8888
self._register_event_handler("on_user_turn_stop_timeout")
89-
self._register_event_handler("on_user_idle")
89+
self._register_event_handler("on_user_turn_idle")
9090

9191
self._user_turn_controller = UserTurnController(
9292
user_turn_strategies=user_turn_strategies or UserTurnStrategies(),
@@ -108,7 +108,9 @@ def __init__(
108108
self._user_idle_controller: Optional[UserIdleController] = None
109109
if user_idle_timeout:
110110
self._user_idle_controller = UserIdleController(user_idle_timeout=user_idle_timeout)
111-
self._user_idle_controller.add_event_handler("on_user_idle", self._on_user_idle)
111+
self._user_idle_controller.add_event_handler(
112+
"on_user_turn_idle", self._on_user_turn_idle
113+
)
112114

113115
async def cleanup(self):
114116
"""Clean up processor resources."""
@@ -208,5 +210,5 @@ async def _on_user_turn_stopped(
208210
async def _on_user_turn_stop_timeout(self, controller):
209211
await self._call_event_handler("on_user_turn_stop_timeout")
210212

211-
async def _on_user_idle(self, controller):
212-
await self._call_event_handler("on_user_idle")
213+
async def _on_user_turn_idle(self, controller):
214+
await self._call_event_handler("on_user_turn_idle")

tests/test_user_idle_controller.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ async def test_basic_idle_detection(self):
3232

3333
idle_triggered = False
3434

35-
@controller.event_handler("on_user_idle")
36-
async def on_user_idle(controller):
35+
@controller.event_handler("on_user_turn_idle")
36+
async def on_user_turn_idle(controller):
3737
nonlocal idle_triggered
3838
idle_triggered = True
3939

@@ -54,8 +54,8 @@ async def test_user_speaking_resets_idle_timer(self):
5454

5555
idle_triggered = False
5656

57-
@controller.event_handler("on_user_idle")
58-
async def on_user_idle(controller):
57+
@controller.event_handler("on_user_turn_idle")
58+
async def on_user_turn_idle(controller):
5959
nonlocal idle_triggered
6060
idle_triggered = True
6161

@@ -78,8 +78,8 @@ async def test_bot_speaking_resets_idle_timer(self):
7878

7979
idle_triggered = False
8080

81-
@controller.event_handler("on_user_idle")
82-
async def on_user_idle(controller):
81+
@controller.event_handler("on_user_turn_idle")
82+
async def on_user_turn_idle(controller):
8383
nonlocal idle_triggered
8484
idle_triggered = True
8585

@@ -102,8 +102,8 @@ async def test_function_call_prevents_idle(self):
102102

103103
idle_triggered = False
104104

105-
@controller.event_handler("on_user_idle")
106-
async def on_user_idle(controller):
105+
@controller.event_handler("on_user_turn_idle")
106+
async def on_user_turn_idle(controller):
107107
nonlocal idle_triggered
108108
idle_triggered = True
109109

@@ -143,8 +143,8 @@ async def test_no_idle_before_conversation_starts(self):
143143

144144
idle_triggered = False
145145

146-
@controller.event_handler("on_user_idle")
147-
async def on_user_idle(controller):
146+
@controller.event_handler("on_user_turn_idle")
147+
async def on_user_turn_idle(controller):
148148
nonlocal idle_triggered
149149
idle_triggered = True
150150

@@ -162,8 +162,8 @@ async def test_idle_starts_with_bot_speaking(self):
162162

163163
idle_triggered = False
164164

165-
@controller.event_handler("on_user_idle")
166-
async def on_user_idle(controller):
165+
@controller.event_handler("on_user_turn_idle")
166+
async def on_user_turn_idle(controller):
167167
nonlocal idle_triggered
168168
idle_triggered = True
169169

@@ -184,8 +184,8 @@ async def test_multiple_idle_events(self):
184184

185185
idle_count = 0
186186

187-
@controller.event_handler("on_user_idle")
188-
async def on_user_idle(controller):
187+
@controller.event_handler("on_user_turn_idle")
188+
async def on_user_turn_idle(controller):
189189
nonlocal idle_count
190190
idle_count += 1
191191

0 commit comments

Comments
 (0)