feat: add WakePhraseUserFrameFilter for aggregator-level wake phrase gating

Add a new user frame filter concept that runs inside the aggregator,
before VAD processing. This blocks transcriptions, VAD events, and
interruptions until a wake phrase is detected, solving ordering
conflicts that prevented wake phrase detection from working as a
user turn start strategy.

- Add BaseUserFrameFilter base class and WakePhraseUserFrameFilter
  with LISTENING/INACTIVE state machine, timeout, and single_activation
- Add user_frame_filters param to LLMUserAggregatorParams with
  _maybe_filter_frame() running after mute check, before VAD
- Deprecate WakeCheckFilter in favor of the new filter
- Update 10-wake-phrase.py example
This commit is contained in:
Mark Backman
2026-03-18 09:58:11 -04:00
parent 53388e0426
commit ac0643f82f
7 changed files with 805 additions and 6 deletions

View File

@@ -19,7 +19,6 @@ from pipecat.processors.aggregators.llm_response_universal import (
LLMContextAggregatorPair,
LLMUserAggregatorParams,
)
from pipecat.processors.filters.wake_check_filter import WakeCheckFilter
from pipecat.runner.types import RunnerArguments
from pipecat.runner.utils import create_transport
from pipecat.services.cartesia.tts import CartesiaTTSService
@@ -28,6 +27,7 @@ from pipecat.services.openai.llm import OpenAILLMService
from pipecat.transports.base_transport import BaseTransport, TransportParams
from pipecat.transports.daily.transport import DailyParams
from pipecat.transports.websocket.fastapi import FastAPIWebsocketParams
from pipecat.turns.user_filter import WakePhraseUserFrameFilter
load_dotenv(override=True)
@@ -52,7 +52,12 @@ transport_params = {
async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
logger.info(f"Starting bot")
stt = DeepgramSTTService(api_key=os.getenv("DEEPGRAM_API_KEY"))
stt = DeepgramSTTService(
api_key=os.getenv("DEEPGRAM_API_KEY"),
settings=DeepgramSTTService.Settings(
keyterm=["pipecat"],
),
)
tts = CartesiaTTSService(
api_key=os.getenv("CARTESIA_API_KEY"),
@@ -68,19 +73,24 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
),
)
hey_robot_filter = WakeCheckFilter(["hey robot", "hey, robot"])
wake_filter = WakePhraseUserFrameFilter(
phrases=["pipecat"],
timeout=8.0,
)
context = LLMContext()
user_aggregator, assistant_aggregator = LLMContextAggregatorPair(
context,
user_params=LLMUserAggregatorParams(vad_analyzer=SileroVADAnalyzer()),
user_params=LLMUserAggregatorParams(
vad_analyzer=SileroVADAnalyzer(),
user_frame_filters=[wake_filter],
),
)
pipeline = Pipeline(
[
transport.input(), # Transport user input
stt, # STT
hey_robot_filter, # Filter out speech not directed at the robot
user_aggregator, # User responses
llm, # LLM
tts, # TTS
@@ -105,7 +115,7 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
context.add_message(
{
"role": "user",
"content": "Please introduce yourself. Tell the user they should say 'Hey Robot' before talking to you.",
"content": "Please introduce yourself. Tell the user they should say 'Pipecat' before talking to you.",
}
)
await task.queue_frames([LLMRunFrame()])