---
title: "Guardrails"
description: "Filter and control AI responses before they reach the caller."
icon: "shield"
---

# Output Guardrails

Guardrails intercept AI responses before they are sent to text-to-speech. When a response matches a guardrail, it is replaced with a safe alternative. This prevents the agent from saying things it should not.

## Creating a Guardrail

Use the `guardrail()` static method:

```python
guardrail = guardrail(
    name="No medical advice",
    blocked_terms=["diagnosis", "prescription", "medication"],
    replacement="I'm not qualified to give medical advice. Please consult a doctor.",
)
```

## Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `name` | `str` | *required* | Identifier used in log warnings when the guardrail fires. |
| `blocked_terms` | `list[str] \| None` | `None` | List of words or phrases. Any case-insensitive match blocks the response. |
| `check` | `Callable[[str], bool] \| None` | `None` | Custom function that receives the response text and returns `True` to block it. Evaluated after `blocked_terms`. |
| `replacement` | `str` | `"I'm sorry, I can't respond to that."` | What the agent says instead when the response is blocked. |

## How Guardrails Work

1. The AI generates a response.
2. Each guardrail is evaluated in order:
   - **blocked_terms**: If any term appears in the response (case-insensitive substring match), the response is blocked.
   - **check**: If the callable returns `True`, the response is blocked.
3. If blocked, the `replacement` text is sent to TTS instead.
4. A warning is logged with the guardrail `name`.

## Blocked Terms

The simplest way to filter responses. Terms are matched as case-insensitive substrings:

```python
guardrail(
    name="No competitor mentions",
    blocked_terms=["CompetitorCo", "RivalCorp", "OtherBrand"],
    replacement="I can only speak about our own products and services.",
)
```

A response containing *"CompetitorCo has a similar feature"* would be blocked and replaced.

## Custom Check Function

For more complex logic, provide a `check` callable:

```python
guardrail(
    name="No phone numbers in responses",
    check=lambda text: bool(__import__("re").search(r"\b\d{3}[-.]?\d{3}[-.]?\d{4}\b", text)),
    replacement="I can't share phone numbers directly. Let me transfer you instead.",
)
```

The `check` function receives the full response text and should return `True` to block.

## Combining Terms and Checks

You can use both `blocked_terms` and `check` on the same guardrail. The blocked terms are evaluated first; if they don't match, the `check` function is called:

```python
guardrail(
    name="Content filter",
    blocked_terms=["profanity1", "profanity2"],
    check=lambda text: len(text) > 500,  # Block overly long responses
    replacement="Let me give you a more concise answer.",
)
```

## Multiple Guardrails

Pass a list of guardrails to the agent. They are evaluated in order -- the first match wins:

```python
agent = phone.agent(
    system_prompt="You are a financial advisor assistant.",
    guardrails=[
        guardrail(
            name="No specific investment advice",
            blocked_terms=["buy", "sell", "invest in", "stock tip"],
            replacement="I can provide general information, but please consult a licensed financial advisor for specific investment decisions.",
        ),
        guardrail(
            name="No personal data disclosure",
            check=lambda text: "SSN" in text.upper() or "social security" in text.lower(),
            replacement="I cannot share personal identification information.",
        ),
        guardrail(
            name="No competitor comparisons",
            blocked_terms=["better than", "worse than", "compared to"],
            replacement="I'd prefer to focus on what we can offer you directly.",
        ),
    ],
)
```

## Complete Example

```python
import os
import asyncio
from dotenv import load_dotenv
from getpatter import Patter, Twilio, OpenAIRealtime, Guardrail

load_dotenv()

phone = Patter(
    carrier=Twilio(),                               # TWILIO_* from env
    phone_number=os.environ["PHONE_NUMBER"],
    webhook_url=os.environ["WEBHOOK_URL"],
)

agent = phone.agent(
    engine=OpenAIRealtime(voice="nova"),            # OPENAI_API_KEY from env
    system_prompt="""You are a customer service agent for a healthcare company.
Answer questions about services and scheduling.
Never provide medical diagnoses or treatment recommendations.""",
    first_message="Hello! How can I help you today?",
    guardrails=[
        Guardrail(
            name="No medical advice",
            blocked_terms=["diagnosis", "prescription", "dosage", "treatment plan"],
            replacement="That's a medical question I'm not qualified to answer. Would you like me to connect you with a nurse?",
        ),
        Guardrail(
            name="No pricing promises",
            check=lambda text: any(
                word in text.lower() for word in ["guaranteed price", "price match", "free"]
            ),
            replacement="For pricing details, I'll need to transfer you to our billing team.",
        ),
    ],
)

async def main():
    await phone.serve(agent, port=8000)

asyncio.run(main())
```

<Note>
  Guardrails are a defense-in-depth measure. Always include behavioral constraints in the `system_prompt` as well. Guardrails catch cases where the AI ignores prompt instructions.
</Note>
