introduce new user and bot turn start strategies
This commit is contained in:
0
src/pipecat/turns/__init__.py
Normal file
0
src/pipecat/turns/__init__.py
Normal file
74
src/pipecat/turns/bot/base_bot_turn_start_strategy.py
Normal file
74
src/pipecat/turns/bot/base_bot_turn_start_strategy.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2024–2025, Daily
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: BSD 2-Clause License
|
||||||
|
#
|
||||||
|
|
||||||
|
"""Base turn start strategy for determining when the bot should start speaking."""
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from pipecat.frames.frames import Frame
|
||||||
|
from pipecat.utils.asyncio.task_manager import BaseTaskManager
|
||||||
|
from pipecat.utils.base_object import BaseObject
|
||||||
|
|
||||||
|
|
||||||
|
class BaseBotTurnStartStrategy(BaseObject):
|
||||||
|
"""Base class for strategies that determine when the bot should start speaking.
|
||||||
|
|
||||||
|
Subclasses should implement logic to detect when the bot should start
|
||||||
|
speaking. This could be based on analyzing incoming frames (such as
|
||||||
|
transcriptions), conversation state, or other heuristics.
|
||||||
|
|
||||||
|
Events triggered by bot turn start strategies:
|
||||||
|
|
||||||
|
- `on_push_frame`: Indicates the strategy wants to push a frame.
|
||||||
|
- `on_bot_turn_started`: Signals that the bot should start speaking.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
"""Initialize the base bot turn start strategy."""
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self._task_manager: Optional[BaseTaskManager] = None
|
||||||
|
self._register_event_handler("on_push_frame", sync=True)
|
||||||
|
self._register_event_handler("on_bot_turn_started", sync=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def task_manager(self) -> BaseTaskManager:
|
||||||
|
"""Returns the configured task manager."""
|
||||||
|
if not self._task_manager:
|
||||||
|
raise RuntimeError(f"{self} bot turn start strategy was not properly setup")
|
||||||
|
return self._task_manager
|
||||||
|
|
||||||
|
async def reset(self):
|
||||||
|
"""Reset the strategy to its initial state."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def setup(self, task_manager: BaseTaskManager):
|
||||||
|
"""Initialize the strategy with the given task manager.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
task_manager: The task manager to be associated with this instance.
|
||||||
|
"""
|
||||||
|
self._task_manager = task_manager
|
||||||
|
|
||||||
|
async def cleanup(self):
|
||||||
|
"""Cleanup the strategy."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def process_frame(self, frame: Frame):
|
||||||
|
"""Process an incoming frame to decide whether the bot should speak.
|
||||||
|
|
||||||
|
Subclasses should override this to implement logic that decides whether
|
||||||
|
the bot turn has started.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
frame: The frame to be analyzed.
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def trigger_bot_turn_started(self):
|
||||||
|
"""Trigger the `on_bot_turn_started` event."""
|
||||||
|
await self._call_event_handler("on_bot_turn_started")
|
||||||
111
src/pipecat/turns/bot/transcription_bot_turn_start_strategy.py
Normal file
111
src/pipecat/turns/bot/transcription_bot_turn_start_strategy.py
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2024–2025, Daily
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: BSD 2-Clause License
|
||||||
|
#
|
||||||
|
|
||||||
|
"""Transcription time-based speaking strategy."""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from pipecat.frames.frames import (
|
||||||
|
Frame,
|
||||||
|
TranscriptionFrame,
|
||||||
|
VADUserStartedSpeakingFrame,
|
||||||
|
VADUserStoppedSpeakingFrame,
|
||||||
|
)
|
||||||
|
from pipecat.turns.bot.base_bot_turn_start_strategy import BaseBotTurnStartStrategy
|
||||||
|
from pipecat.utils.asyncio.task_manager import BaseTaskManager
|
||||||
|
|
||||||
|
|
||||||
|
class TranscriptionBotTurnStartStrategy(BaseBotTurnStartStrategy):
|
||||||
|
"""Bot turn start strategy based on transcriptions.
|
||||||
|
|
||||||
|
This strategy assumes the bot should start speaking once a transcription
|
||||||
|
has been received and the user is not actively speaking. It handles
|
||||||
|
multiple or delayed transcription frames gracefully.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *, timeout: float = 0.5):
|
||||||
|
"""Initialize the transcription-based bot turn start strategy.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
timeout: A short delay used internally to handle consecutive or
|
||||||
|
slightly delayed transcriptions.
|
||||||
|
"""
|
||||||
|
super().__init__()
|
||||||
|
self._timeout = timeout
|
||||||
|
self._text = ""
|
||||||
|
self._vad_user_speaking = False
|
||||||
|
self._event = asyncio.Event()
|
||||||
|
self._task: Optional[asyncio.Task] = None
|
||||||
|
|
||||||
|
async def reset(self):
|
||||||
|
"""Reset the strategy to its initial state."""
|
||||||
|
await super().reset()
|
||||||
|
self._text = ""
|
||||||
|
self._vad_user_speaking = False
|
||||||
|
self._event.clear()
|
||||||
|
|
||||||
|
async def setup(self, task_manager: BaseTaskManager):
|
||||||
|
"""Initialize the strategy with the given task manager.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
task_manager: The task manager to be associated with this instance.
|
||||||
|
"""
|
||||||
|
await super().setup(task_manager)
|
||||||
|
self._task = task_manager.create_task(self._task_handler(), f"{self}::_task_handler")
|
||||||
|
|
||||||
|
async def cleanup(self):
|
||||||
|
"""Cleanup the strategy."""
|
||||||
|
await super().cleanup()
|
||||||
|
if self._task:
|
||||||
|
await self.task_manager.cancel_task(self._task)
|
||||||
|
self._task = None
|
||||||
|
|
||||||
|
async def process_frame(self, frame: Frame):
|
||||||
|
"""Process an incoming frame to update strategy state.
|
||||||
|
|
||||||
|
Updates internal transcription text and VAD state. The bot turn will be
|
||||||
|
triggered when appropriate based on the collected frames.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
frame: The frame to be analyzed.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if isinstance(frame, VADUserStartedSpeakingFrame):
|
||||||
|
await self._handle_vad_user_started_speaking(frame)
|
||||||
|
elif isinstance(frame, VADUserStoppedSpeakingFrame):
|
||||||
|
await self._handle_vad_user_stopped_speaking(frame)
|
||||||
|
elif isinstance(frame, TranscriptionFrame):
|
||||||
|
await self._handle_transcription(frame)
|
||||||
|
|
||||||
|
async def _handle_vad_user_started_speaking(self, _: VADUserStartedSpeakingFrame):
|
||||||
|
"""Handle when the VAD indicates the user is speaking."""
|
||||||
|
self._vad_user_speaking = True
|
||||||
|
|
||||||
|
async def _handle_vad_user_stopped_speaking(self, _: VADUserStoppedSpeakingFrame):
|
||||||
|
"""Handle when the VAD indicates the user has stopped speaking."""
|
||||||
|
self._vad_user_speaking = False
|
||||||
|
|
||||||
|
async def _handle_transcription(self, frame: TranscriptionFrame):
|
||||||
|
"""Handle user transcription."""
|
||||||
|
self._text += frame.text
|
||||||
|
self._event.set()
|
||||||
|
|
||||||
|
async def _task_handler(self):
|
||||||
|
"""Asynchronously monitor transcriptions and trigger bot turn when ready.
|
||||||
|
|
||||||
|
If transcription text exists and the user is not currently speaking,
|
||||||
|
triggers the bot turn. Handles multiple or delayed transcriptions
|
||||||
|
gracefully.
|
||||||
|
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
await asyncio.wait_for(self._event.wait(), timeout=self._timeout)
|
||||||
|
self._event.clear()
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
if self._text and not self._vad_user_speaking:
|
||||||
|
await self.trigger_bot_turn_started()
|
||||||
152
src/pipecat/turns/bot/turn_analyzer_bot_turn_start_strategy.py
Normal file
152
src/pipecat/turns/bot/turn_analyzer_bot_turn_start_strategy.py
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2024–2025, Daily
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: BSD 2-Clause License
|
||||||
|
#
|
||||||
|
|
||||||
|
"""Bot turn start strategy based on turn detection analyzers."""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from pipecat.audio.turn.base_turn_analyzer import BaseTurnAnalyzer, EndOfTurnState
|
||||||
|
from pipecat.frames.frames import (
|
||||||
|
Frame,
|
||||||
|
InputAudioRawFrame,
|
||||||
|
InterimTranscriptionFrame,
|
||||||
|
MetricsFrame,
|
||||||
|
StartFrame,
|
||||||
|
TranscriptionFrame,
|
||||||
|
VADUserStartedSpeakingFrame,
|
||||||
|
VADUserStoppedSpeakingFrame,
|
||||||
|
)
|
||||||
|
from pipecat.metrics.metrics import MetricsData
|
||||||
|
from pipecat.processors.frame_processor import FrameDirection
|
||||||
|
from pipecat.turns.bot.base_bot_turn_start_strategy import BaseBotTurnStartStrategy
|
||||||
|
from pipecat.utils.asyncio.task_manager import BaseTaskManager
|
||||||
|
|
||||||
|
|
||||||
|
class TurnAnalyzerBotTurnStartStrategy(BaseBotTurnStartStrategy):
|
||||||
|
"""Bot turn start strategy using a turn detection model to detect end of user turn.
|
||||||
|
|
||||||
|
This strategy uses the turn detection models to determine when the user has
|
||||||
|
finished speaking, combining audio, VAD, and transcription frames. Once the
|
||||||
|
turn is considered complete, the bot turn is triggered.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *, turn_analyzer: BaseTurnAnalyzer, timeout: float = 0.5):
|
||||||
|
"""Initialize the bot turn start strategy.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
turn_analyzer: The turn detection analyzer instance to detect end of user turn.
|
||||||
|
timeout: Short delay used internally to handle frame timing and event triggering.
|
||||||
|
"""
|
||||||
|
super().__init__()
|
||||||
|
self._turn_analyzer = turn_analyzer
|
||||||
|
self._timeout = timeout
|
||||||
|
self._text = ""
|
||||||
|
self._vad_user_speaking = False
|
||||||
|
self._event = asyncio.Event()
|
||||||
|
self._task: Optional[asyncio.Task] = None
|
||||||
|
|
||||||
|
async def reset(self):
|
||||||
|
"""Reset the strategy to its initial state."""
|
||||||
|
await super().reset()
|
||||||
|
self._text = ""
|
||||||
|
self._vad_user_speaking = False
|
||||||
|
self._event.set()
|
||||||
|
|
||||||
|
async def setup(self, task_manager: BaseTaskManager):
|
||||||
|
"""Initialize the strategy with the given task manager.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
task_manager: The task manager to be associated with this instance.
|
||||||
|
"""
|
||||||
|
await super().setup(task_manager)
|
||||||
|
self._task = task_manager.create_task(self._task_handler(), f"{self}::_task_handler")
|
||||||
|
|
||||||
|
async def cleanup(self):
|
||||||
|
"""Cleanup the strategy."""
|
||||||
|
await super().cleanup()
|
||||||
|
if self._task:
|
||||||
|
await self.task_manager.cancel_task(self._task)
|
||||||
|
self._task = None
|
||||||
|
|
||||||
|
async def process_frame(self, frame: Frame):
|
||||||
|
"""Process an incoming frame to update the turn analyzer and strategy state.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
frame: The frame to be analyzed.
|
||||||
|
"""
|
||||||
|
await super().process_frame(frame)
|
||||||
|
|
||||||
|
if isinstance(frame, StartFrame):
|
||||||
|
await self._start(frame)
|
||||||
|
elif isinstance(frame, VADUserStartedSpeakingFrame):
|
||||||
|
await self._handle_vad_user_started_speaking(frame)
|
||||||
|
elif isinstance(frame, VADUserStoppedSpeakingFrame):
|
||||||
|
await self._handle_vad_user_stopped_speaking(frame)
|
||||||
|
elif isinstance(frame, InputAudioRawFrame):
|
||||||
|
await self._handle_input_audio(frame)
|
||||||
|
elif isinstance(frame, (TranscriptionFrame, InterimTranscriptionFrame)):
|
||||||
|
await self._handle_transcription(frame)
|
||||||
|
|
||||||
|
async def _start(self, frame: StartFrame):
|
||||||
|
"""Process the start frame to configure the turn analyzer."""
|
||||||
|
self._turn_analyzer.set_sample_rate(frame.audio_in_sample_rate)
|
||||||
|
|
||||||
|
async def _handle_input_audio(self, frame: InputAudioRawFrame):
|
||||||
|
"""Handle input audio to check if the turn is completed."""
|
||||||
|
state = self._turn_analyzer.append_audio(frame.audio, self._vad_user_speaking)
|
||||||
|
await self._handle_end_of_turn(state)
|
||||||
|
|
||||||
|
async def _handle_vad_user_started_speaking(self, _: VADUserStartedSpeakingFrame):
|
||||||
|
"""Handle when the VAD indicates the user is speaking."""
|
||||||
|
self._vad_user_speaking = True
|
||||||
|
self._event.set()
|
||||||
|
|
||||||
|
async def _handle_vad_user_stopped_speaking(self, _: VADUserStoppedSpeakingFrame):
|
||||||
|
"""Handle when the VAD indicates the user has stopped speaking."""
|
||||||
|
self._vad_user_speaking = False
|
||||||
|
self._event.set()
|
||||||
|
|
||||||
|
state, prediction = await self._turn_analyzer.analyze_end_of_turn()
|
||||||
|
await self._handle_prediction_result(prediction)
|
||||||
|
await self._handle_end_of_turn(state)
|
||||||
|
|
||||||
|
async def _handle_transcription(self, frame: TranscriptionFrame | InterimTranscriptionFrame):
|
||||||
|
"""Handle user transcription."""
|
||||||
|
# We don't really care about the content.
|
||||||
|
self._text = frame.text
|
||||||
|
self._event.set()
|
||||||
|
|
||||||
|
async def _handle_end_of_turn(self, state: EndOfTurnState):
|
||||||
|
"""Handle completion of end-of-turn analysis."""
|
||||||
|
if state == EndOfTurnState.COMPLETE:
|
||||||
|
self._event.set()
|
||||||
|
|
||||||
|
async def _handle_prediction_result(self, result: Optional[MetricsData]):
|
||||||
|
"""Handle a prediction result event from the turn analyzer."""
|
||||||
|
if result:
|
||||||
|
await self._call_event_handler(
|
||||||
|
"on_push_frame",
|
||||||
|
MetricsFrame(data=[result]),
|
||||||
|
FrameDirection.DOWNSTREAM,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _task_handler(self):
|
||||||
|
"""Asynchronously monitor events and trigger bot turn when appropriate.
|
||||||
|
|
||||||
|
If we have not received a transcription in the specified amount of time
|
||||||
|
(and we initially received one) and the turn analyzer said the turn is
|
||||||
|
done, then the bot is ready to speak.
|
||||||
|
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
await asyncio.wait_for(self._event.wait(), timeout=self._timeout)
|
||||||
|
self._event.clear()
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
if self._text:
|
||||||
|
await self.trigger_bot_turn_started()
|
||||||
0
src/pipecat/turns/user/__init__.py
Normal file
0
src/pipecat/turns/user/__init__.py
Normal file
73
src/pipecat/turns/user/base_user_turn_start_strategy.py
Normal file
73
src/pipecat/turns/user/base_user_turn_start_strategy.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2024–2025, Daily
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: BSD 2-Clause License
|
||||||
|
#
|
||||||
|
|
||||||
|
"""Base turn start strategy for determining when the user starts speaking."""
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from pipecat.frames.frames import Frame
|
||||||
|
from pipecat.utils.asyncio.task_manager import BaseTaskManager
|
||||||
|
from pipecat.utils.base_object import BaseObject
|
||||||
|
|
||||||
|
|
||||||
|
class BaseUserTurnStartStrategy(BaseObject):
|
||||||
|
"""Base class for strategies that determine when a user starts speaking.
|
||||||
|
|
||||||
|
Subclasses should implement logic to detect the start of a user's turn.
|
||||||
|
This could be based on voice activity, number of words spoken, or other
|
||||||
|
heuristics.
|
||||||
|
|
||||||
|
Events triggered by user turn start strategies:
|
||||||
|
|
||||||
|
- `on_push_frame`: Indicates the strategy wants to push a frame.
|
||||||
|
- `on_user_turn_started`: Signals that a user turn has started.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
"""Initialize the base user turn start strategy."""
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self._task_manager: Optional[BaseTaskManager] = None
|
||||||
|
self._register_event_handler("on_push_frame", sync=True)
|
||||||
|
self._register_event_handler("on_user_turn_started", sync=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def task_manager(self) -> BaseTaskManager:
|
||||||
|
"""Returns the configured task manager."""
|
||||||
|
if not self._task_manager:
|
||||||
|
raise RuntimeError(f"{self} user turn start strategy was not properly setup")
|
||||||
|
return self._task_manager
|
||||||
|
|
||||||
|
async def reset(self):
|
||||||
|
"""Reset the strategy to its initial state."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def setup(self, task_manager: BaseTaskManager):
|
||||||
|
"""Initialize the strategy with the given task manager.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
task_manager: The task manager to be associated with this instance.
|
||||||
|
"""
|
||||||
|
self._task_manager = task_manager
|
||||||
|
|
||||||
|
async def cleanup(self):
|
||||||
|
"""Cleanup the strategy."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def process_frame(self, frame: Frame):
|
||||||
|
"""Process an incoming frame.
|
||||||
|
|
||||||
|
Subclasses should override this to implement logic that decides whether
|
||||||
|
the user turn has started.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
frame: The frame to be processed.
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def trigger_user_turn_started(self):
|
||||||
|
"""Trigger the `on_user_turn_started` event."""
|
||||||
|
await self._call_event_handler("on_user_turn_started")
|
||||||
91
src/pipecat/turns/user/min_words_user_turn_start_strategy.py
Normal file
91
src/pipecat/turns/user/min_words_user_turn_start_strategy.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2024–2025, Daily
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: BSD 2-Clause License
|
||||||
|
#
|
||||||
|
|
||||||
|
"""User turn start strategy based on a minimum number of words spoken by the user."""
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
from pipecat.frames.frames import Frame, InterimTranscriptionFrame, TranscriptionFrame
|
||||||
|
from pipecat.turns.user.base_user_turn_start_strategy import BaseUserTurnStartStrategy
|
||||||
|
|
||||||
|
|
||||||
|
class MinWordsUserTurnStartStrategy(BaseUserTurnStartStrategy):
|
||||||
|
"""User turn start strategy based on a minimum number of words spoken by the user.
|
||||||
|
|
||||||
|
This strategy signals the start of a user turn once the user has spoken at
|
||||||
|
least a specified number of words, as determined from transcription frames.
|
||||||
|
Optionally, interim transcriptions can be used for earlier detection.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *, min_words: int, use_interim: bool = True):
|
||||||
|
"""Initialize the minimum words bot turn start strategy.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
min_words: Minimum number of spoken words required to trigger the
|
||||||
|
start of a user turn.
|
||||||
|
use_interim: Whether to consider interim transcription frames for
|
||||||
|
earlier detection.
|
||||||
|
"""
|
||||||
|
super().__init__()
|
||||||
|
self._min_words = min_words
|
||||||
|
self._use_interim = use_interim
|
||||||
|
self._text = ""
|
||||||
|
|
||||||
|
async def reset(self):
|
||||||
|
"""Reset the strategy to its initial state."""
|
||||||
|
await super().reset()
|
||||||
|
self._text = ""
|
||||||
|
|
||||||
|
async def process_frame(self, frame: Frame):
|
||||||
|
"""Process an incoming frame to detect the start of a user turn.
|
||||||
|
|
||||||
|
This method updates internal state based on transcription frames and
|
||||||
|
triggers the user turn once the minimum word count is reached.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
frame: The frame to be analyzed.
|
||||||
|
"""
|
||||||
|
await super().process_frame(frame)
|
||||||
|
|
||||||
|
if isinstance(frame, TranscriptionFrame):
|
||||||
|
await self._handle_transcription(frame)
|
||||||
|
elif isinstance(frame, InterimTranscriptionFrame) and self._use_interim:
|
||||||
|
await self._handle_interim_transcription(frame)
|
||||||
|
|
||||||
|
async def _handle_transcription(self, frame: TranscriptionFrame):
|
||||||
|
"""Handle a completed transcription frame and check word count.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
frame: The transcription frame to be processed.
|
||||||
|
"""
|
||||||
|
self._text += frame.text
|
||||||
|
|
||||||
|
word_count = len(self._text.split())
|
||||||
|
should_trigger = word_count >= self._min_words
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"{self} should_trigger={should_trigger} num_spoken_words={word_count} min_words={self._min_words}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if should_trigger:
|
||||||
|
await self.trigger_user_turn_started()
|
||||||
|
|
||||||
|
async def _handle_interim_transcription(self, frame: InterimTranscriptionFrame):
|
||||||
|
"""Handle an interim transcription frame and check word count.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
frame: The interim transcription frame to be processed.
|
||||||
|
"""
|
||||||
|
word_count = len(frame.text.split())
|
||||||
|
should_trigger = word_count >= self._min_words
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"{self} interim=True should_trigger={should_trigger} num_spoken_words={word_count} min_words={self._min_words}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if should_trigger:
|
||||||
|
await self.trigger_user_turn_started()
|
||||||
30
src/pipecat/turns/user/vad_user_turn_start_strategy.py
Normal file
30
src/pipecat/turns/user/vad_user_turn_start_strategy.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#
|
||||||
|
# Copyright (c) 2024–2025, Daily
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: BSD 2-Clause License
|
||||||
|
#
|
||||||
|
|
||||||
|
"""User turn start strategy based on VAD events."""
|
||||||
|
|
||||||
|
from pipecat.frames.frames import Frame, VADUserStartedSpeakingFrame
|
||||||
|
from pipecat.turns.user.base_user_turn_start_strategy import BaseUserTurnStartStrategy
|
||||||
|
|
||||||
|
|
||||||
|
class VADUserTurnStartStrategy(BaseUserTurnStartStrategy):
|
||||||
|
"""User turn start strategy based on VAD (Voice Activity Detection).
|
||||||
|
|
||||||
|
This strategy assumes the user turn starts as soon as a VAD frame indicates
|
||||||
|
that the user has started speaking.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def process_frame(self, frame: Frame):
|
||||||
|
"""Process an incoming frame to detect user turn start.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
frame: The frame to be analyzed.
|
||||||
|
"""
|
||||||
|
await super().process_frame(frame)
|
||||||
|
|
||||||
|
if isinstance(frame, VADUserStartedSpeakingFrame):
|
||||||
|
await self.trigger_user_turn_started()
|
||||||
Reference in New Issue
Block a user