Skip to main content

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 Patter.guardrail() static method:
guardrail = Patter.guardrail(
    name="No medical advice",
    blocked_terms=["diagnosis", "prescription", "medication"],
    replacement="I'm not qualified to give medical advice. Please consult a doctor.",
)

Parameters

ParameterTypeDefaultDescription
namestrrequiredIdentifier used in log warnings when the guardrail fires.
blocked_termslist[str] | NoneNoneList of words or phrases. Any case-insensitive match blocks the response.
checkCallable[[str], bool] | NoneNoneCustom function that receives the response text and returns True to block it. Evaluated after blocked_terms.
replacementstr"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:
Patter.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:
Patter.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:
Patter.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:
agent = phone.agent(
    system_prompt="You are a financial advisor assistant.",
    guardrails=[
        Patter.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.",
        ),
        Patter.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.",
        ),
        Patter.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

import os
import asyncio
from dotenv import load_dotenv
from patter import Patter

load_dotenv()

phone = Patter(
    twilio_sid=os.environ["TWILIO_SID"],
    twilio_token=os.environ["TWILIO_TOKEN"],
    openai_key=os.environ["OPENAI_KEY"],
    phone_number=os.environ["PHONE_NUMBER"],
    webhook_url=os.environ["WEBHOOK_URL"],
)

agent = phone.agent(
    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.""",
    voice="nova",
    first_message="Hello! How can I help you today?",
    guardrails=[
        Patter.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?",
        ),
        Patter.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())
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.