STTMuteFilter: use FunctionCallsStartedFrame and support multiple function calls

This commit is contained in:
Aleix Conchillo Flaqué
2025-12-27 13:47:58 -08:00
parent 43fc26cf0e
commit a962c4eeba
3 changed files with 61 additions and 51 deletions

1
changelog/3292.fixed.md Normal file
View File

@@ -0,0 +1 @@
- Fixed a bug in `STTMuteFilter` where the user was not always muted during function calls, especially when there were multiple simultaneous calls.

View File

@@ -21,8 +21,9 @@ from pipecat.frames.frames import (
BotStartedSpeakingFrame, BotStartedSpeakingFrame,
BotStoppedSpeakingFrame, BotStoppedSpeakingFrame,
Frame, Frame,
FunctionCallInProgressFrame, FunctionCallCancelFrame,
FunctionCallResultFrame, FunctionCallResultFrame,
FunctionCallsStartedFrame,
InputAudioRawFrame, InputAudioRawFrame,
InterimTranscriptionFrame, InterimTranscriptionFrame,
InterruptionFrame, InterruptionFrame,
@@ -116,7 +117,7 @@ class STTMuteFilter(FrameProcessor):
self._config = config self._config = config
self._first_speech_handled = False self._first_speech_handled = False
self._bot_is_speaking = False self._bot_is_speaking = False
self._function_call_in_progress = False self._function_call_in_progress = set()
self._is_muted = False self._is_muted = False
async def _handle_mute_state(self, should_mute: bool): async def _handle_mute_state(self, should_mute: bool):
@@ -176,11 +177,12 @@ class STTMuteFilter(FrameProcessor):
# Process frames to determine mute state # Process frames to determine mute state
if isinstance(frame, StartFrame): if isinstance(frame, StartFrame):
should_mute = await self._should_mute() should_mute = await self._should_mute()
elif isinstance(frame, FunctionCallInProgressFrame): elif isinstance(frame, FunctionCallsStartedFrame):
self._function_call_in_progress = True for f in frame.function_calls:
self._function_call_in_progress.add(f.tool_call_id)
should_mute = await self._should_mute() should_mute = await self._should_mute()
elif isinstance(frame, FunctionCallResultFrame): elif isinstance(frame, (FunctionCallCancelFrame, FunctionCallResultFrame)):
self._function_call_in_progress = False self._function_call_in_progress.remove(frame.tool_call_id)
should_mute = await self._should_mute() should_mute = await self._should_mute()
elif isinstance(frame, BotStartedSpeakingFrame): elif isinstance(frame, BotStartedSpeakingFrame):
self._bot_is_speaking = True self._bot_is_speaking = True

View File

@@ -9,8 +9,10 @@ import unittest
from pipecat.frames.frames import ( from pipecat.frames.frames import (
BotStartedSpeakingFrame, BotStartedSpeakingFrame,
BotStoppedSpeakingFrame, BotStoppedSpeakingFrame,
FunctionCallFromLLM,
FunctionCallInProgressFrame, FunctionCallInProgressFrame,
FunctionCallResultFrame, FunctionCallResultFrame,
FunctionCallsStartedFrame,
InputAudioRawFrame, InputAudioRawFrame,
InterimTranscriptionFrame, InterimTranscriptionFrame,
TranscriptionFrame, TranscriptionFrame,
@@ -148,54 +150,59 @@ class TestSTTMuteFilter(unittest.IsolatedAsyncioTestCase):
expected_down_frames=expected_returned_frames, expected_down_frames=expected_returned_frames,
) )
# TODO: Revisit once we figure out how to test SystemFrames and DataFrames async def test_function_call_strategy(self):
# async def test_function_call_strategy(self): filter = STTMuteFilter(config=STTMuteConfig(strategies={STTMuteStrategy.FUNCTION_CALL}))
# filter = STTMuteFilter(config=STTMuteConfig(strategies={STTMuteStrategy.FUNCTION_CALL}))
# frames_to_send = [ frames_to_send = [
# VADUserStartedSpeakingFrame(), # Should pass through initially VADUserStartedSpeakingFrame(), # Should pass through initially
# UserStartedSpeakingFrame(), # Should pass through initially UserStartedSpeakingFrame(), # Should pass through initially
# VADUserStoppedSpeakingFrame(), VADUserStoppedSpeakingFrame(), # Should pass through initially
# UserStoppedSpeakingFrame(), UserStoppedSpeakingFrame(), # Should pass through initially
# FunctionCallInProgressFrame( FunctionCallsStartedFrame(
# function_name="get_weather", function_calls=[
# tool_call_id="call_123", FunctionCallFromLLM(
# arguments='{"location": "San Francisco"}', function_name="get_weather",
# ), # Start function call tool_call_id="call_123",
# VADUserStartedSpeakingFrame(), # Should be suppressed arguments='{"location": "San Francisco"}',
# UserStartedSpeakingFrame(), # Should be suppressed context=None,
# VADUserStoppedSpeakingFrame(), # Should be suppressed )
# UserStoppedSpeakingFrame(), # Should be suppressed ]
# FunctionCallResultFrame( ), # Start function call
# function_name="get_weather", VADUserStartedSpeakingFrame(), # Should be suppressed
# tool_call_id="call_123", UserStartedSpeakingFrame(), # Should be suppressed
# arguments='{"location": "San Francisco"}', VADUserStoppedSpeakingFrame(), # Should be suppressed
# result={"temperature": 22}, UserStoppedSpeakingFrame(), # Should be suppressed
# ), # End function call FunctionCallResultFrame(
# VADUserStartedSpeakingFrame(), # Should pass through again function_name="get_weather",
# UserStartedSpeakingFrame(), # Should pass through again tool_call_id="call_123",
# VADUserStoppedSpeakingFrame(), arguments='{"location": "San Francisco"}',
# UserStoppedSpeakingFrame(), result={"temperature": 22},
# ] ), # End function call
SleepFrame(),
VADUserStartedSpeakingFrame(), # Should pass through again
UserStartedSpeakingFrame(), # Should pass through again
VADUserStoppedSpeakingFrame(),
UserStoppedSpeakingFrame(),
]
# expected_returned_frames = [ expected_returned_frames = [
# VADUserStartedSpeakingFrame, VADUserStartedSpeakingFrame,
# UserStartedSpeakingFrame, UserStartedSpeakingFrame,
# VADUserStoppedSpeakingFrame, VADUserStoppedSpeakingFrame,
# UserStoppedSpeakingFrame, UserStoppedSpeakingFrame,
# FunctionCallInProgressFrame, FunctionCallsStartedFrame,
# FunctionCallResultFrame, FunctionCallResultFrame,
# VADUserStartedSpeakingFrame, VADUserStartedSpeakingFrame,
# UserStartedSpeakingFrame, UserStartedSpeakingFrame,
# VADUserStoppedSpeakingFrame, VADUserStoppedSpeakingFrame,
# UserStoppedSpeakingFrame, UserStoppedSpeakingFrame,
# ] ]
# await run_test( await run_test(
# filter, filter,
# frames_to_send=frames_to_send, frames_to_send=frames_to_send,
# expected_down_frames=expected_returned_frames, expected_down_frames=expected_returned_frames,
# ) )
async def test_mute_until_first_bot_complete_strategy(self): async def test_mute_until_first_bot_complete_strategy(self):
filter = STTMuteFilter( filter = STTMuteFilter(