Google Colab
Build a layered guardrail and measure coverage, latency, and false positives from the traces
- Input guardrails see the user’s message before the model does — PII, prompt-injection and jailbreak attempts, abuse, off-topic requests. They can stop a bad request before you pay for a single model token.
- Output guardrails see the model’s reply before the user does — a leaked system prompt, unsafe advice, disallowed content the model generated on its own.
- Latency vs. coverage — a stricter guardrail catches more, but every check is on the critical path, and an LLM judge catches subtle attacks a regex misses at 100×–1000× the latency.
- The cost of false positives — a guardrail that blocks a real user is often worse than the harm it prevents. Coverage is easy; coverage without blocking good traffic is the hard part.
- Layering without breaking UX — cheap deterministic checks first, escalate the ambiguous cases to an expensive judge, and redact rather than block wherever you can.
GUARDRAIL span, run a labeled mix of benign and adversarial traffic through it, and read coverage, latency, and false-positive rate straight off the traces — using a customer-support assistant as the worked example.
This guide shows examples of:
- Instrumenting each guardrail check as a first-class
GUARDRAILspan - Layering a fast deterministic input filter with an LLM judge that only runs on the ambiguous cases
- Redacting PII instead of blocking, and guarding the model’s output for what the input side can’t catch
- Comparing three guardrail designs — strict deterministic, lenient deterministic, and layered — on identical traffic
Notebook Walkthrough
We will go through key code snippets on this page. To follow the full tutorial, check out the notebook:Designing realtime guardrails
arize.otel.register(...) and instrumenting OpenAI, every guardrail check is wrapped so it emits as its own span.
Instrument every guardrail as a GUARDRAIL span
GUARDRAIL is a first-class OpenInference span kind, so these checks render as a distinct step in Arize AX — not as LLM or TOOL spans. The helper runs one check, records its decision (pass / block / redact / escalate) and its latency, and emits the span. Because the latency is an explicit attribute, aggregating how much time each layer adds later is one groupby.
Input layer 1 — fast and deterministic
The cheapest checks run first, on every request, with two different responses. PII is redacted, not blocked — the user still gets an answer and the sensitive token never reaches the model. Injection detection returns a three-way decision: strong, unambiguous attacks are blocked outright; clearly benign text passes; and the ambiguous middle — text that merely mentions “ignore” or “override” — is markedescalate, because a cheap regex shouldn’t be the final word on intent.
Input layer 2 — the LLM judge (escalation only)
Only the ambiguous inputs reach this layer, so most requests never pay its latency. The judge is itself an LLM call, so it’s wrapped insuppress_tracing() — its own OpenAI span stays out of the project, and the trace shows a single GUARDRAIL span carrying the verdict and how long it took.
CHAIN root with GUARDRAIL children and, when the request gets that far, the assistant’s LLM span — with the request label on the root so every decision can be joined to the kind of traffic it came from. See the notebook for the full guarded_chat and output check.
Measuring the trade-off from the spans
Export the project from Arize AX, then join theGUARDRAIL spans to their CHAIN roots on trace_id, reduce to one row per request, and score three different guardrail designs on the exact same traffic. Note that the export already has a built-in latency_ms column, so the guardrail latency is renamed to guardrail_latency_ms to avoid a collision.
benign_tricky messages that say “ignore”/“override”/“bypass” but are real support questions) wrongly blocked; added latency is the time the guardrails put on the critical path. Read across the row and the three tensions fall out of the same traffic:
Strict deterministic is cheap and safe but drives real users away. Lenient deterministic stops blocking good traffic but misses the subtler injection. Layered passes the ambiguous middle to the judge — it recovers the missed attack and clears the tricky-but-benign requests — at the price of latency, paid only where it changes the decision, which is why the mean added latency stays modest even though the judge is slow.
The latency figures are read from the spans this single (layered) run produced, so the two deterministic columns are an approximation of a true counterfactual — they exclude the judge but still count the sub-millisecond downstream checks for requests a strict policy would have short-circuited earlier. Because those deterministic checks are ~0 ms, the comparison is unaffected; for an exact number, time each policy with real short-circuiting.
GUARDRAIL span in Arize AX — distinct from the assistant’s LLM span — carrying its decision and latency, which is what makes the table above reproducible from real traffic.
A guardrail decision checklist
Because the topic is architectural, it helps to reduce it to a few questions you can ask of any candidate check:Takeaway
We built a layered guardrail around a support assistant and measured it the way you’d measure any production control:- Input guardrails stopped harm before it reached the model (and before it cost a token); the output guardrail caught what the input side couldn’t.
- Instrumenting each check as a
GUARDRAILspan turned three abstract trade-offs — latency vs. coverage, the cost of false positives, and layering — into a single table read off real traffic. - The winning design wasn’t the strictest or the cheapest. It redacted instead of blocking, escalated the ambiguous cases, and spent expensive latency only where it changed the decision.
