Skip to content
C Codeloom
AI

AI Guardrails and Content Filtering

How to design guardrails and content filters for AI applications, including input checks, output checks, layered defenses, and trade-offs between safety and usefulness.

·4 min read · By Codeloom
Intermediate 10 min read

What you'll learn

  • What guardrails actually protect against
  • Where to place input and output filters
  • How layered defenses work
  • Trade-offs in strictness
  • Tools and patterns to start with

Prerequisites

  • Familiar with APIs

Guardrails are the set of checks that sit around your model calls to stop bad things from going in and worse things from coming out. They are the difference between a demo and a product that you can hand to real users. This post lays out the categories, the placement, and the trade-offs.

What guardrails really are

A guardrail is a deterministic or model-based check that runs before or after an LLM call and either blocks, modifies, or flags the content. The categories overlap, but the useful taxonomy is: input safety, input integrity, output safety, output quality, and policy enforcement.

Input safety catches prompts that ask for disallowed content. Input integrity catches injection attempts that try to override your system prompt. Output safety catches harmful generations. Output quality catches malformed or off-topic responses. Policy enforcement applies your business rules, like refusing to discuss competitors.

Mental model

Picture two doors with a model in between. The first door inspects what comes in. The second door inspects what goes out. Either door can ask the model again, ask a smaller classifier, or apply a regex. The goal is layered defense, not a single magic check.

user input
 |
 v
[input filter] -> block? -> safe refusal
 |
 v
LLM call
 |
 v
[output filter] -> block? -> safe refusal
 |
 v
final response
Guardrail layout around an LLM call

Hands-on example

A simple but effective stack uses fast checks first and slower ones only when needed.

def guarded_chat(user_msg: str) -> str:
    if len(user_msg) > 4000:
        return refuse("too long")
    if contains_pii(user_msg):
        user_msg = redact_pii(user_msg)
    if injection_detector(user_msg):
        return refuse("prompt injection suspected")

    raw = llm.generate(system=SYSTEM, user=user_msg)

    if not is_valid_json(raw):
        raw = llm.repair(raw)
    if moderation.flag(raw):
        return refuse("policy violation")
    return raw

The order matters. Regex and length checks cost microseconds. PII detection might be a small model. Moderation is another call. You want cheap checks first so that the expensive ones run only when the cheap ones pass.

For input integrity, dedicated tools like Anthropic’s prompt injection classifier, Llama Guard, or Lakera Guard work well as drop-in services. For structural output checks, libraries like Guardrails AI, Instructor, and Outlines enforce schemas at generation time.

Trade-offs

Strict guardrails reduce harm but also reduce usefulness. A medical assistant that refuses every drug question is safe but worthless. A coding assistant that blocks any mention of cryptography misses half its use cases. Tune the thresholds against real traffic, not imagined worst cases.

Model-based filters add latency and cost. A 200ms moderation call on every output doubles a fast response time. Decide which calls actually need a check. Internal admin tools probably need less filtering than a public chatbot.

Layered checks catch more, but they also stack false positives. If each layer has a 1 percent false positive rate and you have five layers, roughly 5 percent of legitimate traffic gets blocked. Measure this and report it as a first-class metric.

Hard blocks are easier than soft rewrites. Telling the model to refuse is simpler than asking it to rephrase a borderline response. Start with blocks, add rewrites only where you can measure they help.

Practical tips

Always log blocked requests with the reason. You need this data both to tune thresholds and to defend against complaints. Strip sensitive parts before storing.

Make refusal messages friendly and specific. A canned “I cannot help with that” frustrates users and tells attackers nothing useful. A short, specific reason for the refusal performs better in both directions.

Test your guardrails like code. Build a red-team dataset of known bad inputs and known good inputs that look suspicious. Run it on every prompt change. If your block rate on the good set jumps, you tuned too strict.

Trust nothing the model says about itself. Asking the LLM whether its own output is safe is a weak signal. Use a separate model or a separate prompt for the check, with its own system prompt.

Layer policies, not just safety. Business rules belong in their own filter, separate from harm checks. This makes them easier to update without retesting safety behavior.

Wrap-up

Guardrails are not a product you buy. They are a set of small checks placed around model calls, tuned with data, and reviewed when traffic shifts. Start simple, measure both blocks and breakthroughs, and add layers only when the data shows you need them.