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
| 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
- The AI generates a response.
- 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.
- If blocked, the
replacement text is sent to TTS instead.
- 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.