LLM Guardrails: Safety, Moderation, and Filtering Layers
LLM Guardrails: Safety, Moderation, and Filtering Layers
Guardrails are the validation layers around an LLM: they check inputs before the model sees them and outputs before the user does. A model without guardrails is a liability — it can leak data, produce harmful content, or follow instructions embedded in untrusted input. This guide builds a layered guardrail stack and shows where each layer belongs.
The Layered Model
Guardrails belong at every boundary, not just one:
user input ──► [input guardrails] ──► LLM ──► [output guardrails] ──► user
│ │
PII redaction moderation
prompt injection PII leakage
policy check format validation
topic allowlist hallucination check
Each layer fails closed: if a check cannot run, reject the request rather than pass it through.
Layer 1: Input Guardrails
PII Redaction
Never let sensitive data reach the model. Redact before the prompt is built:
import re
PII_PATTERNS = {
"email": re.compile(r"\b[\w.+-]+@[\w-]+\.[\w.]+\b"),
"phone": re.compile(r"\b\+?\d[\d\s-]{8,}\d\b"),
"ssn": re.compile(r"\b\d{3}-\d{2}-\d{4}\b"),
"credit_card": re.compile(r"\b(?:\d[ -]?){13,16}\b"),
}
def redact_pii(text: str) -> str:
for name, pattern in PII_PATTERNS.items():
text = pattern.sub(f"[REDACTED:{name}]", text)
return text
def build_prompt(user_input: str) -> str:
return f"Answer the user's question.\n\nUser: {redact_pii(user_input)}"
Redaction is a first line, not a guarantee. Named-entity models catch more than regexes; use a PII detection model for production, regexes for the cheap first pass.
Prompt Injection Defense
Treat all external content as untrusted. The two structural defenses are input classification and output validation:
def is_injection_attempt(text: str) -> bool:
"""Cheap heuristic layer; pair with a classifier model for production."""
signals = [
"ignore previous instructions",
"ignore all previous",
"system prompt",
"you are now",
"disregard",
"jailbreak",
"developer mode",
]
lowered = text.lower()
return any(s in lowered for s in signals)
Heuristics miss novel attacks. The robust pattern is to separate instructions from data structurally (system prompt vs user content), and to validate that the model's output follows the task, not injected instructions.
Topic and Policy Allowlists
For domain applications, gate the topic before generation:
ALLOWED_TOPICS = {"billing", "shipping", "returns", "account"}
def check_topic(query: str) -> bool:
topic = classify_topic(query) # small classifier or LLM call
return topic in ALLOWED_TOPICS
Layer 2: Output Guardrails
Moderation
Moderate every output before it reaches the user. Use a dedicated moderation model rather than asking the same LLM to police itself:
from openai import OpenAI
client = OpenAI()
def moderate_output(text: str) -> bool:
result = client.moderations.create(input=text)
categories = result.results[0].categories
flagged = result.results[0].flagged
if flagged:
print(f"Blocked categories: {[k for k, v in categories.items() if v]}")
return not flagged
PII Leakage Detection
The model can echo PII that slipped past input redaction, or invent it. Scan outputs with the same redaction patterns and block if anything leaks:
def check_output_leak(text: str) -> bool:
for name, pattern in PII_PATTERNS.items():
if pattern.search(text):
return False # leak detected, block
return True
Format Validation
If the model must return JSON, validate it before use. Never trust the model's claim that it produced valid JSON:
import json
from pydantic import BaseModel, ValidationError
class RefundDecision(BaseModel):
approved: bool
amount: float
reason: str
def parse_llm_json(raw: str) -> RefundDecision | None:
try:
data = json.loads(raw)
return RefundDecision(**data)
except (json.JSONDecodeError, ValidationError) as e:
print(f"Invalid output: {e}")
return None
Layer 3: Policy Enforcement with Guardrails Frameworks
For complex policies, use a guardrails framework instead of hand-rolled checks. Guardrails AI validates against a schema with validators:
from guardrails import Guard
from guardrails.hub import ToxicLanguage, PIILeakage, ValidJSON
guard = Guard().use_many(
ToxicLanguage(threshold=0.5, on_fail="exception"),
PIILeakage(on_fail="exception"),
ValidJSON(on_fail="exception"),
)
def safe_generate(prompt: str) -> str:
raw = llm_generate(prompt)
validated = guard.validate(raw) # raises if any validator fails
return validated.validated_output
NeMo Guardrails takes a different approach: rails defined as Colang rules that constrain the conversation flow:
# config/rails.co
define user ask about competitors
"who are your competitors?"
define flow
user ask about competitors
bot inform cannot discuss competitors
bot ask if anything else
from nemoguardrails import LLMRails, RailsConfig
config = RailsConfig.from_path("./config")
rails = LLMRails(config)
response = rails.generate(messages=[{"role": "user", "content": "Who are your competitors?"}])
# "I can't discuss competitors. Anything else I can help with?"
Layer 4: Hallucination Checks
For grounded systems, verify the output against the source before returning it:
def check_grounded(answer: str, sources: list[str]) -> bool:
"""Verify each factual claim in the answer appears in the sources."""
claims = extract_claims(answer) # LLM or NLI model
for claim in claims:
if not any(claim_supported(claim, src) for src in sources):
return False
return True
If grounding fails, return a fallback ("I couldn't verify that") instead of the unverified answer.
Failure Handling Strategy
Guardrails fail in two ways: false positives (blocking good content) and false negatives (passing bad content). Decide per layer:
- Hard fail: safety-critical checks (toxicity, PII, injection) block and log.
- Soft fail: quality checks (format, grounding) trigger a retry with the error fed back to the model.
- Fallback: when a retry fails, return a canned safe response.
def generate_with_guardrails(prompt: str) -> str:
if not check_topic(prompt) or is_injection_attempt(prompt):
return "I can't help with that."
for attempt in range(2):
raw = llm_generate(prompt)
if not moderate_output(raw):
return "I can't provide that response."
if not check_output_leak(raw):
return "I can't provide that response."
parsed = parse_llm_json(raw)
if parsed is not None:
return raw
return "I couldn't generate a valid response. Please try again."
Monitoring Guardrails
Guardrails need their own observability:
- Block rate per layer: a spike in moderation blocks means the model drifted or the input mix changed.
- False positive rate: sample blocked requests and check how many were actually fine.
- Guardrail latency budget: guardrails should add < 100ms; if they add seconds, they are the bottleneck.
Implementation Checklist
- Redact PII on input before the prompt is built
- Separate instructions from untrusted content structurally
- Moderate every output with a dedicated moderation model
- Scan outputs for PII leakage
- Validate structured outputs with a schema, never trust the model
- Use a guardrails framework for complex multi-policy systems
- Verify grounding for any system that cites sources
- Define hard-fail vs soft-fail per layer
- Monitor block rates and false positives per layer
MatterAI builds frontier AI infrastructure for engineering teams — from inference-optimized models to autonomous coding agents and agentic code reviews.
Explore what we're building:
- Orbital IDE — Autonomous AI coding agent with background agents and deep codebase memory
- AI Code Reviews — Agentic pre-commit reviews across GitHub, GitLab, and Bitbucket
- Axon Models — Frontier-grade reasoning models at 70% lower inference cost
Share this Guide:
More Guides
Model Context Protocol (MCP): Building MCP Servers from Scratch
Build production-grade MCP servers with the TypeScript and Python SDKs. Covers the MCP architecture, stdio and HTTP transports, tools, resources, prompts, and the security model every AI application needs.
16 min readRAG vs Fine-Tuning: When to Use Each for Your LLM Application
Decide between retrieval-augmented generation and fine-tuning with a practical decision framework. Compare cost, latency, freshness, and accuracy, and see working implementations of both approaches.
13 min readGrafana Stack vs SigNoz: Choosing an OpenTelemetry-Native Observability Platform
Compare the Grafana LGTM stack (Loki, Tempo, Mimir) with SigNoz for OpenTelemetry observability: architecture, storage engines, operational overhead, cost at scale, and migration paths.
10 min readLLM Fine-Tuning: LoRA vs QLoRA vs Full Fine-Tuning
Compare full fine-tuning, LoRA, and QLoRA for LLMs with memory requirements, training recipes, and code. Learn which method fits your GPU budget, dataset size, and quality requirements.
14 min readPrompt Injection Defense: Securing LLM Apps Against Jailbreaks, RAG Poisoning, and Tool-Use Exploits
Defend production LLM applications against direct jailbreaks, indirect injection via RAG pipelines, and tool-use exploits with layered defenses, input filtering, and least-privilege tool design.
10 min readContinue Reading
Model Context Protocol (MCP): Building MCP Servers from Scratch
Build production-grade MCP servers with the TypeScript and Python SDKs. Covers the MCP architecture, stdio and HTTP transports, tools, resources, prompts, and the security model every AI application needs.
16 min readRAG vs Fine-Tuning: When to Use Each for Your LLM Application
Decide between retrieval-augmented generation and fine-tuning with a practical decision framework. Compare cost, latency, freshness, and accuracy, and see working implementations of both approaches.
13 min readGrafana Stack vs SigNoz: Choosing an OpenTelemetry-Native Observability Platform
Compare the Grafana LGTM stack (Loki, Tempo, Mimir) with SigNoz for OpenTelemetry observability: architecture, storage engines, operational overhead, cost at scale, and migration paths.
10 min readShip Faster. Ship Safer.
Join thousands of engineering teams using MatterAI to autonomously build, review, and deploy code with enterprise-grade precision.
