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
Local LLMs in Your IDE: Connecting Ollama to Coding Agents and Autocomplete
Wire local models into VS Code, JetBrains, Cline, Continue, and Aider via the OpenAI-compatible API. Covers model routing, context budgets, tool calling with small models, and when a local model is the right choice for the job.
15 min readBuilding a Self-Hosted AI Stack: Ollama, Open WebUI, and Local RAG
Stand up a fully self-hosted AI stack on a single machine: Ollama for inference, Open WebUI as the chat interface, local embeddings for RAG, and a reverse proxy for secure access. No cloud dependency, no data leaving your network.
17 min readTop 5 Open-Source Coding Models to Run on Your Mac (2026)
The best local coding models for Apple Silicon in 2026, ranked by quality per gigabyte of unified memory. Covers qwen3-coder, devstral, gpt-oss, and more with real pull tags, sizes, and context windows.
14 min readRunning LLMs Locally: GGUF, Quantization, and Memory Planning
Learn the GGUF format, the quantization ladder from Q2 to FP16, and the exact memory math for running models on Apple Silicon and NVIDIA GPUs. Includes Ollama and llama.cpp tuning for KV cache and context.
15 min readOllama vs vLLM vs llama.cpp: Choosing the Right Local LLM Runtime
Compare the three dominant local LLM runtimes on architecture, throughput, hardware, and deployment context. Includes benchmark data, a decision framework, and a migration path from Ollama to vLLM.
16 min readContinue Reading
Local LLMs in Your IDE: Connecting Ollama to Coding Agents and Autocomplete
Wire local models into VS Code, JetBrains, Cline, Continue, and Aider via the OpenAI-compatible API. Covers model routing, context budgets, tool calling with small models, and when a local model is the right choice for the job.
15 min readBuilding a Self-Hosted AI Stack: Ollama, Open WebUI, and Local RAG
Stand up a fully self-hosted AI stack on a single machine: Ollama for inference, Open WebUI as the chat interface, local embeddings for RAG, and a reverse proxy for secure access. No cloud dependency, no data leaving your network.
17 min readTop 5 Open-Source Coding Models to Run on Your Mac (2026)
The best local coding models for Apple Silicon in 2026, ranked by quality per gigabyte of unified memory. Covers qwen3-coder, devstral, gpt-oss, and more with real pull tags, sizes, and context windows.
14 min readShip Faster. Ship Safer.
Join thousands of engineering teams using MatterAI to autonomously build, review, and deploy code with enterprise-grade precision.
