Skip to content

feat: add voice guardrails (focus, custom input/output, deterministic… - #980

Open
raghvendradhakar wants to merge 1 commit into
juspay:releasefrom
raghvendradhakar:feat/voice-guardrails
Open

feat: add voice guardrails (focus, custom input/output, deterministic…#980
raghvendradhakar wants to merge 1 commit into
juspay:releasefrom
raghvendradhakar:feat/voice-guardrails

Conversation

@raghvendradhakar

@raghvendradhakar raghvendradhakar commented Aug 6, 2026

Copy link
Copy Markdown

This change adds three complementary guardrail controls to the Breeze Buddy voice pipeline:

Focus guardrail — a platform-owned system instruction that reinforces the active agent’s trusted goal, prevents prompt disclosure, and redirects unrelated requests. It does not make an additional model call.
Custom input guardrail — a customer-authored rule evaluated against each finalized caller turn. High-confidence attacks are blocked locally; other decisions run through a platform-routed classifier in parallel with the main LLM.
Custom output guardrail — a separate customer-authored rule evaluated against each complete assistant sentence before it reaches TTS.

Summary by CodeRabbit

  • New Features

    • Added configurable Focus protection to help keep voice interactions within their intended scope.
    • Added optional input and output safeguards to detect prompt injection, sensitive credentials, and payment-card information.
    • Blocked unsafe tool and MCP actions before execution, with configurable responses for blocked requests.
    • Added supported guardrail model options to configuration settings.
  • Bug Fixes

    • Improved handling of interruptions, cancellations, and partially completed voice responses during safety checks.
    • Realtime configurations now clearly reject unsupported custom safeguards.

DOC

Copilot AI lite review requested due to automatic review settings August 6, 2026 12:26
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f02b99d9-f7c7-460b-8b27-4a5ff2601db0

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

The change adds Focus and custom guardrails for Breeze Buddy voice agents. It introduces deterministic and model-based evaluation, per-generation coordination, input and response gating, tool protection, configuration validation, model options, and comprehensive tests.

Changes

Guardrail contracts and detection

Layer / File(s) Summary
Guardrail contracts and deterministic detection
app/ai/voice/agents/breeze_buddy/guardrails/*, app/ai/voice/agents/breeze_buddy/template/types.py, app/ai/voice/agents/breeze_buddy/template/focus_guardrail.py
Adds Focus and custom guardrail configuration, model metadata, prompt injection, deterministic detection, and realtime compatibility validation.
Configuration exposure and validation coverage
app/api/routers/breeze_buddy/playground/handlers.py, app/ai/voice/agents/breeze_buddy/template/field_reference.json, tests/test_voice_focus_guardrail.py, tests/test_voice_deterministic_guardrails.py
Exposes sanitized guardrail model options and tests configuration serialization, Focus injection, prompt-injection detection, secret detection, and payment-card handling.

Evaluation and response gating

Layer / File(s) Summary
Guardrail evaluation and coordination
app/ai/voice/agents/breeze_buddy/guardrails/evaluator.py, app/ai/voice/agents/breeze_buddy/guardrails/tool_gate.py
Adds bounded semantic evaluation, deterministic precedence, per-turn state, context redaction, redirects, cancellation, fail-closed handling, and tool-gating helpers.
Voice input and output processors
app/ai/voice/agents/breeze_buddy/processors/custom_guardrails.py, app/ai/voice/agents/breeze_buddy/processors/__init__.py, tests/test_voice_custom_guardrails.py
Adds input blocking and asynchronous response gating with sentence aggregation, interruption handling, redirects, and lifecycle tests.

Agent pipeline and tool integration

Layer / File(s) Summary
Agent and pipeline wiring
app/ai/voice/agents/breeze_buddy/agent/*
Creates and cleans up per-generation coordinators, seeds Focus messages, rejects realtime custom guardrails, and places input and response processors around the main LLM.
Tool and transition protection
app/ai/voice/agents/breeze_buddy/mcp/__init__.py, app/ai/voice/agents/breeze_buddy/template/context.py, app/ai/voice/agents/breeze_buddy/template/global_function.py
Gates MCP tools, transition handlers, and global functions when input guardrails block the current turn.
Integration validation
tests/test_daily_initial_context.py, tests/test_voice_custom_guardrails.py
Tests initial-context ordering, disabled and enabled pipeline topology, tool blocking, realtime rejection, service reuse, and evaluator failure behavior.

Estimated code review effort: 5 (Critical) | ~120 minutes

Suggested reviewers: copilot, swaroopvarma2359

Poem

A rabbit checks each whispered phrase,
Blocks unsafe hops through guarded maze.
Focus blooms where prompts begin,
Safe words pass, unsafe ones thin.
TTS carries the cleared refrain.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 19.31% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the pull request's main change by identifying the addition of voice guardrails and their key types.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (9)
tests/test_voice_deterministic_guardrails.py (1)

84-85: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the invalid-Luhn case on the output direction.

Lines 84 and 85 are parametrized with direction="input". Payment-card detection runs only on the output direction, so these two cases pass even if the Luhn check is removed. Add an output-direction assertion for the Luhn-invalid number. This makes the negative case actually exercise _contains_payment_card.

♻️ Proposed additional test
`@pytest.mark.parametrize`(
    "candidate",
    [
        "Use order number 4242424242424241.",
        "My phone number is 9876543210.",
    ],
)
def test_luhn_invalid_numbers_are_not_blocked_on_output(candidate):
    result = evaluate_deterministic_guardrails(
        direction="output",
        candidate=candidate,
    )

    assert result.blocked is False
🤖 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 `@tests/test_voice_deterministic_guardrails.py` around lines 84 - 85, Extend
the tests around the existing invalid-number parametrization to also evaluate
these candidates with direction="output", ensuring the invalid-Luhn case
exercises _contains_payment_card. Preserve the expected unblocked assertion for
both candidates and keep the existing input-direction coverage unchanged.
app/ai/voice/agents/breeze_buddy/guardrails/evaluator.py (1)

136-153: 🩺 Stability & Availability | 🔵 Trivial

Add a metric for fail-closed guardrail evaluations.

The evaluator blocks content when the provider fails or exceeds _GUARDRAIL_TIMEOUT_SECONDS. A provider outage therefore converts normal answers into the fixed redirect message for every sentence. The warning log is the only signal today. Emit a counter or Langfuse event keyed on evaluation_failed and direction, and alert on a sustained rate. This lets operators detect a degraded guard model before callers report repeated redirects.

🤖 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/guardrails/evaluator.py` around lines 136 -
153, The fail-closed path in the guardrail evaluator needs an operational metric
in addition to its warning log. Update the exception handling that returns
GuardrailVerdict with evaluation_failed=True to emit a counter or Langfuse event
labeled by evaluation_failed and direction, preserving the existing block
verdict and redirect behavior.
tests/test_voice_custom_guardrails.py (1)

602-609: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test for the blocked path of the MCP guardrail wrapper.

This test covers only the no-op case, where no coordinator exists. No test asserts that _guardrail_wrap_mcp_handler returns the blocked envelope and skips the wrapped handler when the input guardrail blocks the turn. That path prevents an external MCP side effect, so it is the security-relevant branch. test_input_block_prevents_tool_side_effect exercises blocked_tool_result directly, not the MCP wrapper.

♻️ Proposed additional test
async def test_mcp_handler_is_blocked_after_input_guardrail_block():
    evaluator = _Evaluator(input_decision=GuardrailDecision.BLOCK)
    coordinator = GuardrailCoordinator(
        evaluator=evaluator,
        input_config=_config(),
        output_config=None,
    )
    coordinator.begin_turn(_context())
    calls = []

    async def _handler(args, flow_manager):
        calls.append(args)
        return {"status": "ok"}

    class _Bot:
        guardrail_coordinator = coordinator

    wrapped = _guardrail_wrap_mcp_handler(_handler, _Bot(), "lookup")
    assert wrapped is not _handler

    result = await wrapped({"id": "1"}, None)

    assert calls == []
    assert result == {
        "status": "error",
        "data": "Tool execution was blocked by the input guardrail.",
    }
🤖 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 `@tests/test_voice_custom_guardrails.py` around lines 602 - 609, Add a test
alongside test_mcp_handler_is_unchanged_without_input_guardrail that configures
a GuardrailCoordinator with an input decision of GuardrailDecision.BLOCK, wraps
a handler with _guardrail_wrap_mcp_handler, and awaits it. Assert the wrapped
handler is not called, and verify the result matches the blocked MCP envelope
with status "error" and the expected input-guardrail message.
app/ai/voice/agents/breeze_buddy/template/context.py (1)

551-557: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Drop the redundant coordinator lookup.

blocked_tool_result calls get_input_guardrail_coordinator itself and returns None when no coordinator is active. The outer check duplicates that lookup.

♻️ Proposed simplification
-                if get_input_guardrail_coordinator(bot_instance) is not None:
-                    blocked = await blocked_tool_result(
-                        bot_instance,
-                        function_name or handler_func.__name__,
-                    )
-                    if blocked is not None:
-                        return blocked
+                blocked = await blocked_tool_result(
+                    bot_instance,
+                    function_name or handler_func.__name__,
+                )
+                if blocked is not None:
+                    return blocked

Remove the now-unused get_input_guardrail_coordinator import at Line 20 if no other call site remains in this file.

🤖 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/context.py` around lines 551 - 557,
Remove the outer get_input_guardrail_coordinator check around
blocked_tool_result in the affected handler flow, call blocked_tool_result
directly, and preserve the existing return when it yields a non-None result.
Remove the get_input_guardrail_coordinator import if no other references remain
in the file.
app/ai/voice/agents/breeze_buddy/processors/custom_guardrails.py (2)

280-290: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Merge the identical EndFrame and interruption branches.

Both branches run _cancel_output_work(), _reset_response(), and push the frame. Combine them into one isinstance(frame, (InterruptionFrame, CancelFrame, EndFrame)) check.

♻️ Proposed simplification
-        if isinstance(frame, (InterruptionFrame, CancelFrame)):
-            await self._cancel_output_work()
-            self._reset_response()
-            await self.push_frame(frame, direction)
-            return
-
-        if isinstance(frame, EndFrame):
+        if isinstance(frame, (InterruptionFrame, CancelFrame, EndFrame)):
             await self._cancel_output_work()
             self._reset_response()
             await self.push_frame(frame, direction)
             return
🤖 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/processors/custom_guardrails.py` around
lines 280 - 290, Merge the separate EndFrame branch into the existing
interruption check in the frame-processing method by testing InterruptionFrame,
CancelFrame, and EndFrame together. Preserve the shared _cancel_output_work(),
_reset_response(), push_frame(), and return behavior.

110-155: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Use Pipecat task helpers for guardrail output tasks.

GuardrailResponseGateProcessor creates evaluation and sentence-delivery tasks with asyncio.create_task() and tracks them manually. Use FrameProcessor.create_task()/cancel_task() so the pipeline task manager owns these tasks and includes them in teardown diagnostics.

🤖 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/processors/custom_guardrails.py` around
lines 110 - 155, Update GuardrailResponseGateProcessor’s output-task management
in _queue_delivery and the evaluation task creation paths to use the inherited
FrameProcessor.create_task() helper instead of asyncio.create_task(), and cancel
them through cancel_task() in _cancel_output_work. Remove the manual
_background_tasks tracking and related bookkeeping while preserving ordered
delivery, response-id checks, and teardown behavior.
tests/test_daily_initial_context.py (1)

66-71: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the complete seeded message list.

The assertions check only messages[0] and messages[1], which are both role messages. A regression that seeds the messages twice, drops the task messages, or reorders role and task messages would still pass. Assert the full list so the seeding contract from app/ai/voice/agents/breeze_buddy/agent/__init__.py Lines 1179-1189 is pinned.

💚 Proposed stronger assertion
     async def initialize(_node: Any) -> None:
         events.append("initialize_flow")
         assert agent.context is not None
         messages = cast(list[dict[str, Any]], agent.context.get_messages())
-        assert messages[0]["content"] == FOCUS_GUARDRAIL_SYSTEM_PROMPT
-        assert messages[1]["content"] == "Confirm the COD order."
+        assert messages == [
+            {"role": "system", "content": FOCUS_GUARDRAIL_SYSTEM_PROMPT},
+            {"role": "system", "content": "Confirm the COD order."},
+            {"role": "assistant", "content": "Hello"},
+            {"role": "system", "content": "Verify the order details."},
+        ]
🤖 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 `@tests/test_daily_initial_context.py` around lines 66 - 71, Update the
initialize test callback to assert the complete messages list returned by
agent.context.get_messages(), including every seeded role and task message in
the exact order defined by the Breeze Buddy agent seeding contract. Replace the
partial messages[0]/messages[1] checks while preserving the existing context
assertion.
app/ai/voice/agents/breeze_buddy/agent/pipeline.py (1)

277-281: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the focus_enabled predicate into one helper. The same three-part expression bool(configurations and configurations.guardrails and configurations.guardrails.focus.enabled) is written in three files. Focus is a security control, so all three sites must agree. A future schema change (for example, making guardrails.focus optional) would need three coordinated edits, and one missed site would silently disable or enable the policy in part of the pipeline. Add is_focus_enabled(configurations) -> bool to app/ai/voice/agents/breeze_buddy/template/focus_guardrail.py and call it from each site.

  • app/ai/voice/agents/breeze_buddy/agent/pipeline.py#L277-L281: replace the inline expression with focus_enabled = is_focus_enabled(configurations) and add the import next to the existing inject_focus_guardrail import at Lines 59-61.
  • app/ai/voice/agents/breeze_buddy/agent/flow.py#L198-L202: replace the inline expression with focus_enabled = is_focus_enabled(configurations) and add the import next to the existing inject_focus_guardrail import at Lines 13-15.
  • app/ai/voice/agents/breeze_buddy/agent/__init__.py#L1173-L1177: replace the inline expression with focus_enabled = is_focus_enabled(self.configurations) and import the helper from template/focus_guardrail.py.
♻️ Proposed helper
# app/ai/voice/agents/breeze_buddy/template/focus_guardrail.py

def is_focus_enabled(configurations: Any) -> bool:
    """Return whether the platform Focus policy applies to this template."""
    guardrails = getattr(configurations, "guardrails", None)
    focus = getattr(guardrails, "focus", None)
    return bool(focus and focus.enabled)
🤖 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/agent/pipeline.py` around lines 277 - 281,
Add is_focus_enabled(configurations) to focus_guardrail.py, safely checking
optional guardrails and focus values while returning their enabled state. In
app/ai/voice/agents/breeze_buddy/agent/pipeline.py lines 277-281, replace the
inline predicate and import the helper; make the same replacement and import in
agent/flow.py lines 198-202, and in agent/__init__.py lines 1173-1177 use
self.configurations and import the helper.
app/ai/voice/agents/breeze_buddy/template/utils.py (1)

115-140: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reuse the shared guardrail check.

has_enabled_custom_guardrails(configurations) matches this predicate in app/ai/voice/agents/breeze_buddy/guardrails/evaluator.py. Importing and reusing it avoids duplicate enablement logic and keeps the realtime-template validation aligned with the evaluator.

🤖 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/utils.py` around lines 115 - 140,
The realtime validation currently duplicates the custom guardrail enablement
predicate. Import and use has_enabled_custom_guardrails(configurations) in place
of the local guardrails/custom_guardrail_enabled logic, while preserving the
existing ValueError behavior and message in the realtime configuration
validation.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@app/ai/voice/agents/breeze_buddy/agent/__init__.py`:
- Around line 1359-1367: Update the guardrail coordinator initialization in the
surrounding generation flow to explicitly implement fail-open behavior: catch
failures from build_guardrail_coordinator, record the error with track_error
using the existing errors collection, log the failed guardrail model or
configuration when available, and leave guardrail_coordinator as None so
generation continues.
- Around line 1178-1193: The Focus-enabled initialization block in the agent
setup should not call self.context.set_messages before FlowManager.initialize.
Remove the initial_messages construction and pre-seeding around self.context,
allowing flow_manager.initialize(initial_node_config) to install the
role_messages and task_messages once without duplication.

In `@app/ai/voice/agents/breeze_buddy/template/field_reference.json`:
- Line 42: Update the guardrails field description in the template reference to
document that realtime LLM templates cannot enable guardrails.input or
guardrails.output because validate_template_compat rejects them, while
guardrails.focus remains allowed. Keep the existing behavior and example
unchanged.

---

Nitpick comments:
In `@app/ai/voice/agents/breeze_buddy/agent/pipeline.py`:
- Around line 277-281: Add is_focus_enabled(configurations) to
focus_guardrail.py, safely checking optional guardrails and focus values while
returning their enabled state. In
app/ai/voice/agents/breeze_buddy/agent/pipeline.py lines 277-281, replace the
inline predicate and import the helper; make the same replacement and import in
agent/flow.py lines 198-202, and in agent/__init__.py lines 1173-1177 use
self.configurations and import the helper.

In `@app/ai/voice/agents/breeze_buddy/guardrails/evaluator.py`:
- Around line 136-153: The fail-closed path in the guardrail evaluator needs an
operational metric in addition to its warning log. Update the exception handling
that returns GuardrailVerdict with evaluation_failed=True to emit a counter or
Langfuse event labeled by evaluation_failed and direction, preserving the
existing block verdict and redirect behavior.

In `@app/ai/voice/agents/breeze_buddy/processors/custom_guardrails.py`:
- Around line 280-290: Merge the separate EndFrame branch into the existing
interruption check in the frame-processing method by testing InterruptionFrame,
CancelFrame, and EndFrame together. Preserve the shared _cancel_output_work(),
_reset_response(), push_frame(), and return behavior.
- Around line 110-155: Update GuardrailResponseGateProcessor’s output-task
management in _queue_delivery and the evaluation task creation paths to use the
inherited FrameProcessor.create_task() helper instead of asyncio.create_task(),
and cancel them through cancel_task() in _cancel_output_work. Remove the manual
_background_tasks tracking and related bookkeeping while preserving ordered
delivery, response-id checks, and teardown behavior.

In `@app/ai/voice/agents/breeze_buddy/template/context.py`:
- Around line 551-557: Remove the outer get_input_guardrail_coordinator check
around blocked_tool_result in the affected handler flow, call
blocked_tool_result directly, and preserve the existing return when it yields a
non-None result. Remove the get_input_guardrail_coordinator import if no other
references remain in the file.

In `@app/ai/voice/agents/breeze_buddy/template/utils.py`:
- Around line 115-140: The realtime validation currently duplicates the custom
guardrail enablement predicate. Import and use
has_enabled_custom_guardrails(configurations) in place of the local
guardrails/custom_guardrail_enabled logic, while preserving the existing
ValueError behavior and message in the realtime configuration validation.

In `@tests/test_daily_initial_context.py`:
- Around line 66-71: Update the initialize test callback to assert the complete
messages list returned by agent.context.get_messages(), including every seeded
role and task message in the exact order defined by the Breeze Buddy agent
seeding contract. Replace the partial messages[0]/messages[1] checks while
preserving the existing context assertion.

In `@tests/test_voice_custom_guardrails.py`:
- Around line 602-609: Add a test alongside
test_mcp_handler_is_unchanged_without_input_guardrail that configures a
GuardrailCoordinator with an input decision of GuardrailDecision.BLOCK, wraps a
handler with _guardrail_wrap_mcp_handler, and awaits it. Assert the wrapped
handler is not called, and verify the result matches the blocked MCP envelope
with status "error" and the expected input-guardrail message.

In `@tests/test_voice_deterministic_guardrails.py`:
- Around line 84-85: Extend the tests around the existing invalid-number
parametrization to also evaluate these candidates with direction="output",
ensuring the invalid-Luhn case exercises _contains_payment_card. Preserve the
expected unblocked assertion for both candidates and keep the existing
input-direction coverage unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 627de68d-00cb-4832-9540-89db19cb08c5

📥 Commits

Reviewing files that changed from the base of the PR and between edd9519 and 162fed2.

📒 Files selected for processing (22)
  • app/ai/voice/agents/breeze_buddy/agent/__init__.py
  • app/ai/voice/agents/breeze_buddy/agent/flow.py
  • app/ai/voice/agents/breeze_buddy/agent/pipeline.py
  • app/ai/voice/agents/breeze_buddy/guardrails/__init__.py
  • app/ai/voice/agents/breeze_buddy/guardrails/deterministic.py
  • app/ai/voice/agents/breeze_buddy/guardrails/evaluator.py
  • app/ai/voice/agents/breeze_buddy/guardrails/models.py
  • app/ai/voice/agents/breeze_buddy/guardrails/tool_gate.py
  • app/ai/voice/agents/breeze_buddy/mcp/__init__.py
  • app/ai/voice/agents/breeze_buddy/processors/__init__.py
  • app/ai/voice/agents/breeze_buddy/processors/custom_guardrails.py
  • app/ai/voice/agents/breeze_buddy/template/context.py
  • app/ai/voice/agents/breeze_buddy/template/field_reference.json
  • app/ai/voice/agents/breeze_buddy/template/focus_guardrail.py
  • app/ai/voice/agents/breeze_buddy/template/global_function.py
  • app/ai/voice/agents/breeze_buddy/template/types.py
  • app/ai/voice/agents/breeze_buddy/template/utils.py
  • app/api/routers/breeze_buddy/playground/handlers.py
  • tests/test_daily_initial_context.py
  • tests/test_voice_custom_guardrails.py
  • tests/test_voice_deterministic_guardrails.py
  • tests/test_voice_focus_guardrail.py

Comment thread app/ai/voice/agents/breeze_buddy/agent/__init__.py
Comment thread app/ai/voice/agents/breeze_buddy/agent/__init__.py Outdated
Comment thread app/ai/voice/agents/breeze_buddy/template/field_reference.json Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds a three-layer “voice guardrails” system to the Breeze Buddy standard voice pipeline: a platform-owned Focus prompt policy, deterministic local detectors for high-confidence violations, and optional customer-authored semantic input/output guardrails evaluated via a platform-routed model and enforced before tools/TTS.

Changes:

  • Introduces template schema + prompt injection for Focus guardrail, and seeds the initial LLM context earlier to close a pre-FlowManager timing window.
  • Adds deterministic detectors (prompt-injection, secrets, payment cards) plus a per-turn coordinator that runs semantic guardrail evaluation in parallel and fails closed.
  • Inserts new pipeline processors to gate user turns, tool side effects (global functions / transition handlers / MCP tools), and sentence-level output to TTS; exposes guardrail model options to the playground UI and adds comprehensive tests.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated no comments.

Show a summary per file
File Description
tests/test_voice_focus_guardrail.py Verifies Focus guardrail schema round-trip and idempotent prompt injection + initial-node ordering.
tests/test_voice_deterministic_guardrails.py Tests deterministic detectors and verifies definite blocks bypass semantic model calls.
tests/test_voice_custom_guardrails.py End-to-end contract tests for custom input/output guardrails, tool gating, and pipeline topology.
tests/test_daily_initial_context.py Ensures Daily greeting/init ordering is preserved while Focus context is seeded earlier when enabled.
app/api/routers/breeze_buddy/playground/handlers.py Exposes sanitized guardrail model catalog to playground configuration options.
app/ai/voice/agents/breeze_buddy/template/utils.py Adds realtime-LLM compatibility rejection for custom input/output guardrails.
app/ai/voice/agents/breeze_buddy/template/types.py Adds guardrails to template configuration schema (Focus + optional input/output guardrails) with validation.
app/ai/voice/agents/breeze_buddy/template/global_function.py Wraps global function execution to block tool side effects when input guardrails block the turn.
app/ai/voice/agents/breeze_buddy/template/focus_guardrail.py Implements platform Focus guardrail system prompt + injection helper.
app/ai/voice/agents/breeze_buddy/template/field_reference.json Documents new guardrails configuration field with examples.
app/ai/voice/agents/breeze_buddy/template/context.py Blocks transition-handler side effects when the input guardrail blocks the current turn.
app/ai/voice/agents/breeze_buddy/processors/custom_guardrails.py Adds InputGuardrailProcessor and GuardrailResponseGateProcessor (sentence-level pre-TTS gating).
app/ai/voice/agents/breeze_buddy/processors/init.py Exports the new guardrail processors for pipeline wiring.
app/ai/voice/agents/breeze_buddy/mcp/init.py Wraps MCP tool handlers with input-guardrail side-effect barrier.
app/ai/voice/agents/breeze_buddy/guardrails/tool_gate.py Shared “wait for input verdict before side effects” helper returning a blocked-tool envelope.
app/ai/voice/agents/breeze_buddy/guardrails/models.py Defines the backend-owned guardrail model catalog + sanitized options output.
app/ai/voice/agents/breeze_buddy/guardrails/evaluator.py Implements semantic guardrail evaluator (tool-based decision) and per-turn coordinator with deterministic short-circuit + redaction.
app/ai/voice/agents/breeze_buddy/guardrails/deterministic.py Implements fast local detectors for prompt injection, secrets, and output payment cards (Luhn).
app/ai/voice/agents/breeze_buddy/guardrails/init.py Adds package initializer with explicit import-cycle avoidance note.
app/ai/voice/agents/breeze_buddy/agent/pipeline.py Seeds Focus prompt in initial LLMContext, inserts guardrail processors around the main LLM, and rejects custom guardrails in realtime mode.
app/ai/voice/agents/breeze_buddy/agent/flow.py Injects Focus guardrail into initial node role messages ahead of other template messages.
app/ai/voice/agents/breeze_buddy/agent/init.py Builds/stops guardrail coordinator per generation, and seeds initial context directly (Focus-enabled) before FlowManager init.

@raghvendradhakar
raghvendradhakar force-pushed the feat/voice-guardrails branch 3 times, most recently from 9935732 to 8d4151b Compare August 7, 2026 08:03
Comment thread app/ai/voice/agents/breeze_buddy/agent/__init__.py
Comment thread app/ai/voice/agents/breeze_buddy/agent/pipeline.py Outdated
@manas-narra

manas-narra commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Adds three template-level guardrails to the Breeze Buddy voice agent: a prompt-only "Focus" policy injected into the initial LLM context, plus optional customer-authored input and output guardrails evaluated by a platform-pinned Azure model behind fast local regex/Luhn checks. Two new pipeline processors sit around the main LLM — one holds each finalized caller turn until the check passes (blocked turns skip KB retrieval, the LLM and tools, and get a fixed redirect), the other gates completed assistant sentences before TTS. Blocked caller text is swapped for an opaque marker in live LLM history and restored only when the persisted transcript is built at call end; custom guardrails are rejected on realtime LLM templates and are not enforced on the DAILY_STREAM chat path.

1 new issue found.

Walkthrough complete — 1 posted, 0 declined.

Comment thread app/ai/voice/agents/breeze_buddy/template/utils.py Outdated
Comment thread app/ai/voice/agents/breeze_buddy/template/types.py Outdated
self._delivery_tail = None

async def _drain_output_work(self) -> None:
"""Wait for ordered verdicts and downstream sentence delivery."""

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

check if pipecat provide any gaurdail mechanisms / gaurdrail processors

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Checked against our pinned pipecat-ai==1.1.0. Pipecat provides generic primitives such as GatedLLMContextAggregator, GatedAggregator, and FunctionFilter, but no built-in semantic guardrail/moderation processor.

@raghvendradhakar
raghvendradhakar force-pushed the feat/voice-guardrails branch 4 times, most recently from 66e2af5 to 21d5225 Compare August 18, 2026 12:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants