The Anatomy of an LLM Guardrail: What Runs on the Input, What Runs on the Output

The Anatomy of an LLM Guardrail: What Runs on the Input, What Runs on the Output

Not every safety check belongs in the same place. Some only make sense on the user's question, some only on the model's answer, and some on both. Here's how we structure the guardrails on our veterinary chatbot, and why the input and output split matters.

A guardrail isn't one thing, it's two moments

When people talk about "putting a guardrail on the model," they usually picture a single gate. In practice, a guardrail fires at two distinct moments in a request, and the checks running at each moment aren't the same. The input pass sees only the user's message and runs before the model generates anything. The output pass sees the model's generated response and runs after generation, before the text reaches the user.

This matters because a check can only run where its data actually exists. You can't grounding-check an answer that hasn't been written yet, and you can't catch a prompt injection in a reply the model wrote itself. So each policy lives on the input, the output, or both, and getting that placement right is most of the design work.

The policies we apply

Our chatbot runs on managed cloud guardrails, and we enable five policy families, each attached to the input, the output, or both.

Two of these are worth calling out because they show the input/output split most clearly.

Input-specific: prompt-attack detection

The prompt-attack filter runs at high strength on input and is off on output, and that's deliberate. A prompt injection, something like "ignore your instructions and…", is something a user does to the model. The model's reply isn't going to attack anyone, so running this filter on the output would only add latency and false positives. This check belongs exclusively on the input.

Output-specific: contextual grounding

Grounding is the mirror image. It asks whether the model's answer is actually supported by the reference material we gave it, and whether it's relevant to what was asked. Both of those inputs, the answer and the source, only exist after generation, so grounding runs as a separate output-only step, tagging three pieces of content for the guardrail: the source material, the original question, and the model's answer. It's meaningless on the input side; there's nothing to ground yet.

Two actions: block vs. redact

Most policies, when triggered, block, replacing the entire message with a safe refusal like "I can only help with questions about your dog…". Block is binary and easy to reason about. But the PII policy on names, emails, phones, and addresses uses a different action: anonymize, or redact. Here the message goes through, but the sensitive span gets masked in place. That's a fundamentally different operation. Blocking is a decision about the whole message, keep it or swap it. Redacting is an edit inside the message, you have to find the span and rewrite it. That distinction has real consequences once you're streaming.

Diagram contrasting a block action versus a redact action on flagged content
Diagram contrasting a block action versus a redact action on flagged content

How the two passes are actually wired

We enforce all of this with two mechanisms. First, an attached guardrail on the model call itself: when we invoke the model, we pass the guardrail along, and the platform automatically evaluates the input before generation and the output after generation against the content, topic, word, and PII policies, one attachment covering both directions. Second, a separate grounding call after generation, since grounding needs the answer plus the source tagged in a specific way, we run it as an explicit second step on the output only, and if it fails, we swap in a safe fallback response like "I don't have enough information to answer that."

So the full lifecycle runs: input screened, model generates, output screened for content, topic, word, and PII, output grounding-checked, then delivered.

Why streaming forces the question

Streaming a response token by token, for that typing effect, is where the input/output distinction becomes unavoidable, because the two sides behave completely differently. Input checks can run before a single token is emitted, since the user's message is fully known up front. We screen it before opening the stream, and if it's blocked, the user gets a clean refusal and the model never even gets called. Cheap and deterministic.

Output checks collide with streaming, though. The model's response doesn't exist until it's generated, and once a token has streamed to the user, you can't take it back. That's fine for a block, you can stop and swap the final message, but it's a real problem for redaction and grounding. To mask an email, you have to recognize the whole span before it's shown; token by token, you'd leak the raw email before the guardrail even catches it. And grounding needs the complete answer to score it, so a strict grounding gate means buffering the response instead of streaming it live.

The practical fix is making the output guardrail synchronous: buffer each chunk, let the guardrail evaluate and redact it, then release it, at the cost of some latency. The looser alternative, stream first and check after, means unsafe or unredacted content can flash in front of the user before it's caught. The rule of thumb: input guardrails are close to free to run early and hard, while output guardrails, especially redaction and grounding, force a real latency-versus-safety trade-off the moment you start streaming.

Design principles we settled on

Place each check where its data actually lives. Prompt-attack goes on input only, grounding goes on output only, and anything that could appear in either direction goes on both. Don't pay for checks that can't possibly fire.

Prefer blocking for categories, redaction for spans. Block when the whole message is the problem, a forbidden topic, an injection. Redact when only a fragment is, a name, an email, and the rest of the message is still useful.

Screen the input before spending a generation. A blocked question shouldn't cost a model call. Gating on input is cheaper and more predictable than catching it mid-stream.

Treat the output guardrail as the expensive one. It's the side that fights with streaming, adds latency, and has to handle in-place edits. Budget for it accordingly, and decide deliberately whether a given response path needs synchronous, safer checking or asynchronous, faster checking.

Match the action to the risk. Anonymize what's recoverable, like contact details. Block what isn't, like a payment card, SSN, or password. One policy family, two possible actions, chosen per entity.

A broad costume category can help organise choices for events, group shoots and themed projects. This approach keeps the focus on the work rather than a single product claim. When comparing designs from the same work, Demon Slayer cosplay costumes(鬼滅の刃 コスプレ衣装) provides the matching reference point. This keeps related characters together without reducing them to one costume. The final decision can balance visual accuracy with practical event or photography needs. Careful comparison helps preserve the distinctions between related character designs.

In one sentence

Guardrails aren't a single wall around the model, they're a set of checks placed at two moments in a request, and the real design work is knowing which checks belong on the user's question, which belong on the model's answer, and which belong on both.