turns: rename bot turn start to user turn stop strategies
This commit is contained in:
@@ -63,10 +63,10 @@ from pipecat.processors.aggregators.llm_context import (
|
||||
NotGiven,
|
||||
)
|
||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||
from pipecat.turns.bot import BaseBotTurnStartStrategy, BotTurnStartedParams
|
||||
from pipecat.turns.mute import BaseUserMuteStrategy
|
||||
from pipecat.turns.turn_start_strategies import ExternalTurnStartStrategies, TurnStartStrategies
|
||||
from pipecat.turns.user import BaseUserTurnStartStrategy, UserTurnStartedParams
|
||||
from pipecat.turns.user_start import BaseUserTurnStartStrategy, UserTurnStartedParams
|
||||
from pipecat.turns.user_stop import BaseUserTurnStopStrategy, UserTurnStoppedParams
|
||||
from pipecat.turns.user_turn_strategies import ExternalUserTurnStrategies, UserTurnStrategies
|
||||
from pipecat.utils.string import TextPartForConcatenation, concatenate_aggregated_text
|
||||
from pipecat.utils.time import time_now_iso8601
|
||||
|
||||
@@ -76,15 +76,15 @@ class LLMUserAggregatorParams:
|
||||
"""Parameters for configuring LLM user aggregation behavior.
|
||||
|
||||
Parameters:
|
||||
turn_start_strategies: User and bot turn start strategies.
|
||||
user_turn_strategies: User turn start and stop strategies.
|
||||
user_mute_strategies: List of user mute strategies.
|
||||
user_turn_end_timeout: Time in seconds to wait before considering the
|
||||
user's turn finished and starting the bot turn.
|
||||
user_turn_stop_timeout: Time in seconds to wait before considering the
|
||||
user's turn finished.
|
||||
"""
|
||||
|
||||
turn_start_strategies: Optional[TurnStartStrategies] = None
|
||||
user_turn_strategies: Optional[UserTurnStrategies] = None
|
||||
user_mute_strategies: List[BaseUserMuteStrategy] = field(default_factory=list)
|
||||
user_turn_end_timeout: float = 5.0
|
||||
user_turn_stop_timeout: float = 5.0
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -232,8 +232,8 @@ class LLMUserAggregator(LLMContextAggregator):
|
||||
Event handlers available:
|
||||
|
||||
- on_user_turn_started: Called when the user turn starts
|
||||
- on_bot_turn_started: Called when the user turn ends and it is now the bot’s turn
|
||||
- on_user_turn_end_timeout: Called when no bot turn start strategy triggers
|
||||
- on_user_turn_stopped: Called when the user turn ends
|
||||
- on_user_turn_stop_timeout: Called when no user turn stop strategy triggers
|
||||
|
||||
Example::
|
||||
|
||||
@@ -241,12 +241,12 @@ class LLMUserAggregator(LLMContextAggregator):
|
||||
async def on_user_turn_started(aggregator, Optional[strategy]):
|
||||
...
|
||||
|
||||
@aggregator.event_handler("on_bot_turn_started")
|
||||
async def on_bot_turn_started(aggregator, Optional[strategy]):
|
||||
@aggregator.event_handler("on_user_turn_stopped")
|
||||
async def on_user_turn_stopped(aggregator, Optional[strategy]):
|
||||
...
|
||||
|
||||
@aggregator.event_handler("on_user_turn_end_timeout")
|
||||
async def on_user_turn_end_timeout(aggregator):
|
||||
@aggregator.event_handler("on_user_turn_stop_timeout")
|
||||
async def on_user_turn_stop_timeout(aggregator):
|
||||
...
|
||||
|
||||
"""
|
||||
@@ -268,19 +268,19 @@ class LLMUserAggregator(LLMContextAggregator):
|
||||
super().__init__(context=context, role="user", **kwargs)
|
||||
self._params = params or LLMUserAggregatorParams()
|
||||
|
||||
# Initialize default user and bot turn start strategies.
|
||||
self._turn_start_strategies = self._params.turn_start_strategies or TurnStartStrategies()
|
||||
# Initialize default user turn strategies.
|
||||
self._user_turn_strategies = self._params.user_turn_strategies or UserTurnStrategies()
|
||||
|
||||
self._vad_user_speaking = False
|
||||
|
||||
self._user_turn = False
|
||||
self._user_is_muted = False
|
||||
self._user_turn_end_timeout_event = asyncio.Event()
|
||||
self._user_turn_end_timeout_task: Optional[asyncio.Task] = None
|
||||
self._user_turn_stop_timeout_event = asyncio.Event()
|
||||
self._user_turn_stop_timeout_task: Optional[asyncio.Task] = None
|
||||
|
||||
self._register_event_handler("on_user_turn_started")
|
||||
self._register_event_handler("on_user_turn_end_timeout")
|
||||
self._register_event_handler("on_bot_turn_started")
|
||||
self._register_event_handler("on_user_turn_stopped")
|
||||
self._register_event_handler("on_user_turn_stop_timeout")
|
||||
|
||||
async def cleanup(self):
|
||||
"""Clean up processor resources."""
|
||||
@@ -341,7 +341,7 @@ class LLMUserAggregator(LLMContextAggregator):
|
||||
else:
|
||||
await self.push_frame(frame, direction)
|
||||
|
||||
await self._turn_start_strategies_process_frame(frame)
|
||||
await self._user_turn_strategies_process_frame(frame)
|
||||
|
||||
async def push_aggregation(self):
|
||||
"""Push the current aggregation."""
|
||||
@@ -354,32 +354,32 @@ class LLMUserAggregator(LLMContextAggregator):
|
||||
await self.push_context_frame()
|
||||
|
||||
async def _start(self, frame: StartFrame):
|
||||
if not self._user_turn_end_timeout_task:
|
||||
self._user_turn_end_timeout_task = self.create_task(
|
||||
self._user_turn_end_timeout_task_handler()
|
||||
if not self._user_turn_stop_timeout_task:
|
||||
self._user_turn_stop_timeout_task = self.create_task(
|
||||
self._user_turn_stop_timeout_task_handler()
|
||||
)
|
||||
|
||||
await self._setup_turn_start_strategies()
|
||||
await self._setup_user_turn_strategies()
|
||||
await self._setup_user_mute_strategies()
|
||||
|
||||
async def _setup_user_mute_strategies(self):
|
||||
for s in self._params.user_mute_strategies:
|
||||
await s.setup(self.task_manager)
|
||||
|
||||
async def _setup_turn_start_strategies(self):
|
||||
if self._turn_start_strategies.user:
|
||||
for s in self._turn_start_strategies.user:
|
||||
async def _setup_user_turn_strategies(self):
|
||||
if self._user_turn_strategies.start:
|
||||
for s in self._user_turn_strategies.start:
|
||||
await s.setup(self.task_manager)
|
||||
s.add_event_handler("on_push_frame", self._on_push_frame)
|
||||
s.add_event_handler("on_broadcast_frame", self._on_broadcast_frame)
|
||||
s.add_event_handler("on_user_turn_started", self._on_user_turn_started)
|
||||
|
||||
if self._turn_start_strategies.bot:
|
||||
for s in self._turn_start_strategies.bot:
|
||||
if self._user_turn_strategies.stop:
|
||||
for s in self._user_turn_strategies.stop:
|
||||
await s.setup(self.task_manager)
|
||||
s.add_event_handler("on_push_frame", self._on_push_frame)
|
||||
s.add_event_handler("on_broadcast_frame", self._on_broadcast_frame)
|
||||
s.add_event_handler("on_bot_turn_started", self._on_bot_turn_started)
|
||||
s.add_event_handler("on_user_turn_stopped", self._on_user_turn_stopped)
|
||||
|
||||
async def _stop(self, frame: EndFrame):
|
||||
await self._cleanup()
|
||||
@@ -388,20 +388,20 @@ class LLMUserAggregator(LLMContextAggregator):
|
||||
await self._cleanup()
|
||||
|
||||
async def _cleanup(self):
|
||||
if self._user_turn_end_timeout_task:
|
||||
await self.cancel_task(self._user_turn_end_timeout_task)
|
||||
self._user_turn_end_timeout_task = None
|
||||
if self._user_turn_stop_timeout_task:
|
||||
await self.cancel_task(self._user_turn_stop_timeout_task)
|
||||
self._user_turn_stop_timeout_task = None
|
||||
|
||||
await self._cleanup_turn_start_strategies()
|
||||
await self._cleanup_user_turn_strategies()
|
||||
await self._cleanup_user_mute_strategies()
|
||||
|
||||
async def _cleanup_turn_start_strategies(self):
|
||||
if self._turn_start_strategies.user:
|
||||
for s in self._turn_start_strategies.user:
|
||||
async def _cleanup_user_turn_strategies(self):
|
||||
if self._user_turn_strategies.start:
|
||||
for s in self._user_turn_strategies.start:
|
||||
await s.cleanup()
|
||||
|
||||
if self._turn_start_strategies.bot:
|
||||
for s in self._turn_start_strategies.bot:
|
||||
if self._user_turn_strategies.stop:
|
||||
for s in self._user_turn_strategies.stop:
|
||||
await s.cleanup()
|
||||
|
||||
async def _cleanup_user_mute_strategies(self):
|
||||
@@ -436,13 +436,13 @@ class LLMUserAggregator(LLMContextAggregator):
|
||||
|
||||
return should_mute_frame
|
||||
|
||||
async def _turn_start_strategies_process_frame(self, frame: Frame):
|
||||
if self._turn_start_strategies.user:
|
||||
for strategy in self._turn_start_strategies.user:
|
||||
async def _user_turn_strategies_process_frame(self, frame: Frame):
|
||||
if self._user_turn_strategies.start:
|
||||
for strategy in self._user_turn_strategies.start:
|
||||
await strategy.process_frame(frame)
|
||||
|
||||
if self._turn_start_strategies.bot:
|
||||
for strategy in self._turn_start_strategies.bot:
|
||||
if self._user_turn_strategies.stop:
|
||||
for strategy in self._user_turn_strategies.stop:
|
||||
await strategy.process_frame(frame)
|
||||
|
||||
async def _handle_llm_run(self, frame: LLMRunFrame):
|
||||
@@ -465,15 +465,15 @@ class LLMUserAggregator(LLMContextAggregator):
|
||||
logger.warning(
|
||||
f"{self}: `turn_analyzer` in base input transport is deprecated and "
|
||||
"might result in unexpected behavior. Use `LLMUserAggregator`'s new `turn_start_strategies` "
|
||||
"parameter with `TurnAnalyzerBotTurnStartStrategy` instead:\n"
|
||||
"parameter with `TurnAnalyzerUserTurnStopStrategy` instead:\n"
|
||||
"\n"
|
||||
" context_aggregator = LLMContextAggregatorPair(\n"
|
||||
" context,\n"
|
||||
" user_params=LLMUserAggregatorParams(\n"
|
||||
" ...,\n"
|
||||
" turn_start_strategies=TurnStartStrategies(\n"
|
||||
" bot=[\n"
|
||||
" TurnAnalyzerBotTurnStartStrategy(\n"
|
||||
" user_turn_strategies=UserTurnStrategies(\n"
|
||||
" stop=[\n"
|
||||
" TurnAnalyzerUserTurnStopStrategy(\n"
|
||||
" turn_analyzer=LocalSmartTurnAnalyzerV3(params=SmartTurnParams())\n"
|
||||
" )\n"
|
||||
" ],\n"
|
||||
@@ -482,21 +482,21 @@ class LLMUserAggregator(LLMContextAggregator):
|
||||
" )"
|
||||
)
|
||||
|
||||
await self._cleanup_turn_start_strategies()
|
||||
self._turn_start_strategies = ExternalTurnStartStrategies()
|
||||
await self._setup_turn_start_strategies()
|
||||
await self._cleanup_user_turn_strategies()
|
||||
self._turn_strategies = ExternalUserTurnStrategies()
|
||||
await self._setup_user_turn_strategies()
|
||||
|
||||
async def _handle_vad_user_started_speaking(self, frame: VADUserStartedSpeakingFrame):
|
||||
self._vad_user_speaking = True
|
||||
|
||||
# The user started talking, let's reset the user turn timeout.
|
||||
self._user_turn_end_timeout_event.set()
|
||||
self._user_turn_stop_timeout_event.set()
|
||||
|
||||
async def _handle_vad_user_stopped_speaking(self, frame: VADUserStoppedSpeakingFrame):
|
||||
self._vad_user_speaking = False
|
||||
|
||||
# The user stopped talking, let's reset the user turn timeout.
|
||||
self._user_turn_end_timeout_event.set()
|
||||
self._user_turn_stop_timeout_event.set()
|
||||
|
||||
async def _handle_transcription(self, frame: TranscriptionFrame):
|
||||
text = frame.text
|
||||
@@ -506,7 +506,7 @@ class LLMUserAggregator(LLMContextAggregator):
|
||||
return
|
||||
|
||||
# We have creceived a transcription, let's reset the user turn timeout.
|
||||
self._user_turn_end_timeout_event.set()
|
||||
self._user_turn_stop_timeout_event.set()
|
||||
|
||||
# Transcriptions never include inter-part spaces (so far).
|
||||
self._aggregation.append(
|
||||
@@ -522,14 +522,14 @@ class LLMUserAggregator(LLMContextAggregator):
|
||||
):
|
||||
await self._trigger_user_turn_start(strategy, params)
|
||||
|
||||
async def _on_bot_turn_started(
|
||||
self, strategy: BaseBotTurnStartStrategy, params: BotTurnStartedParams
|
||||
async def _on_user_turn_stopped(
|
||||
self, strategy: BaseUserTurnStopStrategy, params: UserTurnStoppedParams
|
||||
):
|
||||
await self._trigger_bot_turn_start(strategy, params)
|
||||
await self._trigger_user_turn_stop(strategy, params)
|
||||
|
||||
async def _on_push_frame(
|
||||
self,
|
||||
strategy: BaseUserTurnStartStrategy | BaseBotTurnStartStrategy,
|
||||
strategy: BaseUserTurnStartStrategy | BaseUserTurnStopStrategy,
|
||||
frame: Frame,
|
||||
direction: FrameDirection = FrameDirection.DOWNSTREAM,
|
||||
):
|
||||
@@ -537,7 +537,7 @@ class LLMUserAggregator(LLMContextAggregator):
|
||||
|
||||
async def _on_broadcast_frame(
|
||||
self,
|
||||
strategy: BaseUserTurnStartStrategy | BaseBotTurnStartStrategy,
|
||||
strategy: BaseUserTurnStartStrategy | BaseUserTurnStopStrategy,
|
||||
frame_cls: Type[Frame],
|
||||
**kwargs,
|
||||
):
|
||||
@@ -553,11 +553,11 @@ class LLMUserAggregator(LLMContextAggregator):
|
||||
logger.debug(f"User started speaking (user turn start strategy: {strategy})")
|
||||
|
||||
self._user_turn = True
|
||||
self._user_turn_end_timeout_event.set()
|
||||
self._user_turn_stop_timeout_event.set()
|
||||
|
||||
# Reset all user turn start strategies to start fresh.
|
||||
if self._turn_start_strategies.user:
|
||||
for s in self._turn_start_strategies.user:
|
||||
if self._user_turn_strategies.start:
|
||||
for s in self._user_turn_strategies.start:
|
||||
await s.reset()
|
||||
|
||||
if params.enable_user_speaking_frames:
|
||||
@@ -570,45 +570,45 @@ class LLMUserAggregator(LLMContextAggregator):
|
||||
|
||||
await self._call_event_handler("on_user_turn_started", strategy)
|
||||
|
||||
async def _trigger_bot_turn_start(
|
||||
self, strategy: Optional[BaseBotTurnStartStrategy], params: BotTurnStartedParams
|
||||
async def _trigger_user_turn_stop(
|
||||
self, strategy: Optional[BaseUserTurnStopStrategy], params: UserTurnStoppedParams
|
||||
):
|
||||
# Prevent two consecutive bot turn starts.
|
||||
# Prevent two consecutive user turn stops.
|
||||
if not self._user_turn:
|
||||
return
|
||||
|
||||
logger.debug(f"User stopped speaking (bot turn start strategy: {strategy})")
|
||||
logger.debug(f"User stopped speaking (user turn stop strategy: {strategy})")
|
||||
|
||||
self._user_turn = False
|
||||
self._user_turn_end_timeout_event.set()
|
||||
self._user_turn_stop_timeout_event.set()
|
||||
|
||||
# Reset all bot turn start strategies to start fresh.
|
||||
if self._turn_start_strategies.bot:
|
||||
for s in self._turn_start_strategies.bot:
|
||||
# Reset all user turn stop strategies to start fresh.
|
||||
if self._user_turn_strategies.stop:
|
||||
for s in self._user_turn_strategies.stop:
|
||||
await s.reset()
|
||||
|
||||
if params.enable_user_speaking_frames:
|
||||
# TODO(aleix): This frame should really come from the top of the pipeline.
|
||||
await self.broadcast_frame(UserStoppedSpeakingFrame)
|
||||
|
||||
await self._call_event_handler("on_bot_turn_started", strategy)
|
||||
await self._call_event_handler("on_user_turn_stopped", strategy)
|
||||
|
||||
# Always push context frame.
|
||||
await self.push_aggregation()
|
||||
|
||||
async def _user_turn_end_timeout_task_handler(self):
|
||||
async def _user_turn_stop_timeout_task_handler(self):
|
||||
while True:
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
self._user_turn_end_timeout_event.wait(),
|
||||
timeout=self._params.user_turn_end_timeout,
|
||||
self._user_turn_stop_timeout_event.wait(),
|
||||
timeout=self._params.user_turn_stop_timeout,
|
||||
)
|
||||
self._user_turn_end_timeout_event.clear()
|
||||
self._user_turn_stop_timeout_event.clear()
|
||||
except asyncio.TimeoutError:
|
||||
if self._user_turn and not self._vad_user_speaking:
|
||||
await self._call_event_handler("on_user_turn_end_timeout")
|
||||
await self._trigger_bot_turn_start(
|
||||
None, BotTurnStartedParams(enable_user_speaking_frames=True)
|
||||
await self._call_event_handler("on_user_turn_stop_timeout")
|
||||
await self._trigger_user_turn_stop(
|
||||
None, UserTurnStoppedParams(enable_user_speaking_frames=True)
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user