Guardrails vs evaluators: what runs inline and what runs later

PlayLast reviewed Oct 16, 2026By Himanshu Ramchandani
Short answer

Guardrails block clear failures before users see them, and evaluators measure quality after a response is made. Guardrails run inside the request with a tight latency budget, so they're fast fixed rules: regexes, block lists, schema checks and small classifiers. Evaluators run afterwards, often in batches, so they can use slower methods like LLM judges, and their results feed dashboards and your improvement loop instead of blocking the answer.

Two jobs

The evals FAQ puts the difference simply: guardrails block clear failures before users see them, and evaluators measure quality after a response is made.

Guardrail Evaluator
When Inside the request After the response
Latency budget Typically a few milliseconds Seconds to minutes
Methods Regex, block lists, schema checks, small classifiers LLM judges, code checks, human review
Catches Clear, high-impact failures Correctness, completeness, policy
Action Redact, refuse, regenerate Score, alert, add to test set
Runs on Every request Every request or a sample

Guardrails

Guardrails run before the answer reaches the user, on the input, the output or both. The FAQ describes their latency budget as typically a few milliseconds, and your own latency target sets the exact number.

Because of that budget, guardrails are fast, fixed rules that catch clear failures with high impact:

  • Personal data in the output (card numbers, phone numbers, ID numbers).
  • Profanity or content from a block list.
  • Broken JSON or missing required fields.
  • SQL injection patterns or dangerous code in generated output.
import re

RULES = {
    "card_number": re.compile(r"\b(?:\d[ -]?){12,15}\d\b"),
    "email": re.compile(r"[\w.+-]+@[\w-]+\.[\w.]+"),
    "dangerous_call": re.compile(r"\b(os\.remove|shutil\.rmtree|eval|exec)\s*\("),
}


def output_guardrail(text: str) -> tuple[str, list[str]]:
    fired = []
    for name, pattern in RULES.items():
        if pattern.search(text):
            fired.append(name)
            text = pattern.sub(f"[{name} removed]", text)
    return text, fired


safe, fired = output_guardrail("Charge 4111 1111 1111 1111 and email ravi@example.com")
print(safe)
print("fired:", fired)

When a guardrail fires, it redacts, refuses or regenerates. Every false alarm hits a real user, so version your rules, log every trigger with the trace ID, and watch the trigger rate. A sudden jump usually means a rule broke or the traffic changed.

Evaluators

Evaluators run after the response, on every request or a sample, often in batches. They can afford slower methods like an LLM judge, because nobody is waiting.

They check things a rule can't:

  • Is the answer correct?
  • Is it complete?
  • Does it follow the policy?
  • Did it hand off when it should have?

Evaluator results feed dashboards, regression tests and your improvement loop. They don't block the answer.

Cascades: when a judge needs to run inline

Sometimes a failure is serious enough that you want a judge before the user sees the answer, for example an explanation of a refund policy. An LLM judge can run inline only if latency allows.

The sensible pattern is a cascade:

  1. A cheap check handles most traffic.
  2. Only borderline or high-risk cases go to a slower judge.
  3. If the judge fails the answer, regenerate or hand off to a person.

The guardrail sandwich pattern shows where these checks sit in the architecture.

Read ready-made guardrails before using them

Don't use ready-made LLM guardrails without reading their prompts and rules. You'd be putting someone else's idea of "unsafe" in front of your users, with false alarms you didn't choose.

A real incident

In February 2024, a Canadian tribunal ruled on Air Canada's chatbot, which had told Jake Moffatt he could claim a bereavement discount within 90 days of buying his ticket. That contradicted the airline's real policy. Air Canada argued the chatbot was responsible for its own actions, and the tribunal rejected that and ordered the airline to pay $812.02. A millisecond guardrail can't judge whether a policy explanation is right. An evaluator sampling chatbot answers would have flagged it, and for refund questions, a cascade is worth the extra latency.

From the floor

In DevFix, the safety guard was a set of rule-based filters on the generated code: it shouldn't contain calls that remove files, execute arbitrary commands or evaluate strings, and I said these rules could later be replaced with real compliance APIs. A human approval step came at the end, so the run only moved forward when we said yes.

Sources