Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions app/ai/voice/agents/breeze_buddy/agent/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
MinWordsUserTurnStartStrategy,
TranscriptionUserTurnStartStrategy,
VADUserTurnStartStrategy,
WakePhraseUserTurnStartStrategy,
)
from pipecat.turns.user_stop import (
BaseUserTurnStopStrategy,
Expand Down Expand Up @@ -354,6 +355,27 @@ async def build_pipeline(

# --- User turn start strategies ---
start_strategies: list[BaseUserTurnStartStrategy] = []

# Wake phrase: prepended first so it gates all subsequent strategies.
wake_cfg = getattr(configurations, "wake_phrase", None)
if wake_cfg and wake_cfg.enabled:
if is_realtime:
logger.warning(
"Wake phrase is not supported in realtime mode. wake_phrase.enabled will be ignored."
)
elif wake_cfg.phrases:
start_strategies.append(
WakePhraseUserTurnStartStrategy(
phrases=wake_cfg.phrases,
timeout=wake_cfg.timeout,
single_activation=wake_cfg.single_activation,
)
)
logger.info(
f"WakePhrase: enabled with {len(wake_cfg.phrases)} phrase(s), "
f"single_activation={wake_cfg.single_activation}, timeout={wake_cfg.timeout}s"
)

if vad_analyzer is not None:
start_strategies.append(VADUserTurnStartStrategy())

Expand Down
14 changes: 14 additions & 0 deletions app/ai/voice/agents/breeze_buddy/agent/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,20 @@ def validate_template_compat(template: TemplateModel) -> None:
"realtime."
)

wake_phrase = (
getattr(configurations, "wake_phrase", None)
if configurations is not None
else None
)
if wake_phrase is not None and getattr(wake_phrase, "enabled", False):
raise ValueError(
"Realtime LLMs (llm_configurations.realtime set) cannot be "
"combined with configurations.wake_phrase — the realtime pipeline "
"builds its own turn strategies and WakePhraseUserTurnStartStrategy "
f"is never installed. Disable wake_phrase on template {template.id} "
"or disable realtime."
)

logger.info(
f"Template {template.id} validated: realtime LLM + direct mode "
f"(provider={realtime.provider.value})"
Expand Down
34 changes: 34 additions & 0 deletions app/ai/voice/agents/breeze_buddy/template/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,36 @@ class KeywordMatchType(str, Enum):
INCLUDES = "includes" # Transcription must contain the keyword (case-insensitive)


class WakePhraseConfig(BaseModel):
"""Require a wake phrase before the bot responds.

Wraps pipecat's WakePhraseUserTurnStartStrategy. Placed first in start
strategies so no other strategy evaluates until the phrase is heard.

Example::

{"enabled": true, "phrases": ["yes", "haan"], "single_activation": true}
"""

enabled: bool = False
phrases: List[str] = Field(default_factory=list, min_length=1)
timeout: float = Field(
10.0, ge=0.0, le=300.0, description="Seconds to stay awake after phrase."
)
single_activation: bool = Field(
False,
description="If true, wake phrase required only once per session; if false, required before every turn.",
)
Comment on lines +417 to +425
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate wake-phrase inputs at schema boundary.

WakePhraseConfig currently permits blank phrases and negative timeout. When enabled=True, this can produce hard-to-debug no-op/invalid configs that are only discovered at runtime.

Suggested fix
 class WakePhraseConfig(BaseModel):
@@
     enabled: bool = False
     phrases: List[str] = Field(default_factory=list)
-    timeout: float = Field(10.0, description="Seconds to stay awake after phrase.")
+    timeout: float = Field(
+        10.0, ge=0.0, description="Seconds to stay awake after phrase."
+    )
     single_activation: bool = Field(
         False, description="Require phrase before every turn (not just once)."
     )
+
+    `@model_validator`(mode="after")
+    def _validate_wake_phrase(self) -> "WakePhraseConfig":
+        self.phrases = [p.strip() for p in self.phrases if p and p.strip()]
+        if self.enabled and not self.phrases:
+            raise ValueError(
+                "wake_phrase.phrases must include at least one non-empty phrase when enabled=true"
+            )
+        return self
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/ai/voice/agents/breeze_buddy/template/types.py` around lines 417 - 422,
WakePhraseConfig allows blank phrases and non-positive timeout when enabled,
causing invalid runtime configs; add Pydantic validators on WakePhraseConfig
(use `@root_validator` or field validators) so that when enabled is True: (1)
phrases is a non-empty list and each entry is a non-blank string (strip and
reject ""), and (2) timeout is > 0 (reject <= 0); raise clear ValueError
messages indicating which field is invalid so invalid configs fail at schema
validation instead of at runtime.


@model_validator(mode="after")
def validate_phrases_when_enabled(self):
if self.enabled and not self.phrases:
raise ValueError(
"phrases must contain at least one item when enabled is true"
)
return self


class KeywordFilterConfig(BaseModel):
"""Configuration for filtering out specific transcriptions during bot activity.

Expand Down Expand Up @@ -886,6 +916,10 @@ def _pre_validate(cls, data: Any) -> Any:
None,
description="Keyword filter to suppress specific transcriptions while bot is active",
)
wake_phrase: Optional[WakePhraseConfig] = Field(
None,
description="Wake phrase config — bot only responds after hearing a trigger phrase",
)
mcp: Optional[McpConfig] = Field(
None,
description="MCP tool server configuration for dynamic tool discovery",
Expand Down
Loading