From b075502c4c6908b89359c7798a14dd8f38422c08 Mon Sep 17 00:00:00 2001 From: marcus-daily <111281783+marcus-daily@users.noreply.github.com> Date: Fri, 9 Jan 2026 13:44:44 +0000 Subject: [PATCH] Addressing code review comments --- src/pipecat/audio/turn/base_turn_analyzer.py | 12 ++++++++++++ .../audio/turn/smart_turn/base_smart_turn.py | 10 +++++----- .../turn_analyzer_user_turn_stop_strategy.py | 18 +++++------------- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/pipecat/audio/turn/base_turn_analyzer.py b/src/pipecat/audio/turn/base_turn_analyzer.py index 64510b518..942214738 100644 --- a/src/pipecat/audio/turn/base_turn_analyzer.py +++ b/src/pipecat/audio/turn/base_turn_analyzer.py @@ -116,6 +116,18 @@ class BaseTurnAnalyzer(ABC): """ pass + def on_vad_start_secs_updated(self, vad_start_secs: float): + """Invoked when the VAD start trigger time is updated. + + The turn analyzer may choose to change its buffer size depending + on this value. + + Args: + vad_start_secs (float): The number of seconds of voice activity + before triggering the user speaking event. + """ + pass + @abstractmethod def clear(self): """Reset the turn analyzer to its initial state.""" diff --git a/src/pipecat/audio/turn/smart_turn/base_smart_turn.py b/src/pipecat/audio/turn/smart_turn/base_smart_turn.py index 11d3573bf..e71731446 100644 --- a/src/pipecat/audio/turn/smart_turn/base_smart_turn.py +++ b/src/pipecat/audio/turn/smart_turn/base_smart_turn.py @@ -35,15 +35,11 @@ class SmartTurnParams(BaseTurnParams): Parameters: stop_secs: Maximum silence duration in seconds before ending turn. pre_speech_ms: Milliseconds of audio to include before speech starts. - vad_start_secs: Seconds VAD waits before confirming speech start (e.g. VAD STARTING window). - This is added to `pre_speech_ms` at inference slicing time so Smart Turn can include - the initial audio that occurred while VAD was still confirming speech. max_duration_secs: Maximum duration in seconds for audio segments. """ stop_secs: float = STOP_SECS pre_speech_ms: float = PRE_SPEECH_MS - vad_start_secs: float = 0.0 max_duration_secs: float = MAX_DURATION_SECONDS @@ -82,6 +78,7 @@ class BaseSmartTurn(BaseTurnAnalyzer): # Thread executor that will run the model. We only need one thread per # analyzer because one analyzer just handles one audio stream. self._executor = ThreadPoolExecutor(max_workers=1) + self._vad_start_secs: float = 0.0 @property def speech_triggered(self) -> bool: @@ -165,6 +162,9 @@ class BaseSmartTurn(BaseTurnAnalyzer): logger.debug(f"End of Turn result: {state}") return state, result + def on_vad_start_secs_updated(self, vad_start_secs: float): + self._vad_start_secs = vad_start_secs + def clear(self): """Reset the turn analyzer to its initial state.""" self._clear(EndOfTurnState.COMPLETE) @@ -185,7 +185,7 @@ class BaseSmartTurn(BaseTurnAnalyzer): return state, None # Extract recent audio segment for prediction - effective_pre_speech_ms = self._params.pre_speech_ms + (self._params.vad_start_secs * 1000) + effective_pre_speech_ms = self._params.pre_speech_ms + (self._vad_start_secs * 1000) start_time = self._speech_start_time - (effective_pre_speech_ms / 1000) start_index = 0 for i, (t, _) in enumerate(audio_buffer): diff --git a/src/pipecat/turns/user_stop/turn_analyzer_user_turn_stop_strategy.py b/src/pipecat/turns/user_stop/turn_analyzer_user_turn_stop_strategy.py index 28fa9e761..16802795a 100644 --- a/src/pipecat/turns/user_stop/turn_analyzer_user_turn_stop_strategy.py +++ b/src/pipecat/turns/user_stop/turn_analyzer_user_turn_stop_strategy.py @@ -108,20 +108,12 @@ class TurnAnalyzerUserTurnStopStrategy(BaseUserTurnStopStrategy): async def _handle_speech_control_params(self, frame: SpeechControlParamsFrame): """Sync Smart Turn pre-speech buffering with VAD start delay. - In the new user-turn-strategies pipeline, `VADUserStartedSpeakingFrame` - is emitted only once VAD has *confirmed* speech (after `vad_params.start_secs`). - Smart Turn should still include the initial audio collected during that - confirmation window, so we record it in `SmartTurnParams.vad_start_secs` and - add it at inference slicing time (preserving `pre_speech_ms` semantics). + `VADUserStartedSpeakingFrame` is emitted only once VAD has confirmed speech + (after `vad_params.start_secs`). Smart Turn should still include the initial + audio collected during that confirmation window, so we let the analyzer know + when this value has changed. """ - if not frame.vad_params: - return - - params = self._turn_analyzer.params - if not isinstance(params, SmartTurnParams): - return - - params.vad_start_secs = frame.vad_params.start_secs + self._turn_analyzer.on_vad_start_secs_updated(frame.vad_params.start_secs) async def _handle_input_audio(self, frame: InputAudioRawFrame): """Handle input audio to check if the turn is completed."""