How to build AI LLM guardrails with classifiers

Say you're building an AI gateway with a chat interface, something like OpenRouter, and regex-based guardrails aren't enough. Here is the full design: where the checks sit, what happens when one fires, which classifiers to train, how to stay under 15 ms at 10,000 requests per second, and how to keep it working in production.

How to Build AI LLM Guardrails: Classifiers for a 10k RPS Gateway

Image: METAHEURISTIC

Alexander Myasoedov

+Alexander Myasoedov Alexander writes about the operational side of shipping production AI - agents, retrieval, evals, and the guardrails that keep them from going sideways.

How to build AI LLM guardrails? I’d take a comprehensive approach: run checks on the input before the model, on the output after it, on the input and output together, and on the chat history. Decide per security policy whether a flagged request passes, gets a warning or gets blocked. Detect with layers of regex rules, trained classifiers and an LLM judge, with a separate policy engine making the final call. All of that is application dependent. It depends on your domain, the type of traffic you have and how exposed your application is in production.

The scenario I’m working through: you’re in a company that wants to build an AI gateway layer behind some sort of chat interface, something similar to OpenRouter. They only have regex-based guardrails, and you want something more advanced. Here is how I’d sketch the solution, with the trade-offs for each step.

How do you build AI LLM guardrails? The short answer

Where
Input, output, pair, history
Check inputs for jailbreaks and prompt injection, outputs for PII and sensitive data, then the pair and the whole chat.
Enforce
Pass, warn, block
Pick the mode per security policy and log everything for calibration. Redaction only when you implement it as a policy.
Detect
Regex, classifier, LLM judge
Regex for known patterns, a compact encoder for paraphrases, a judge only for the hard cases.
Decide
Policy engine
Models emit signals. A separate engine applies per-customer rules and catches combinations no single model flags.
Budget
15 ms at 10,000 rps
Tier the checks: CPU first, an inference service second, the LLM judge on a small fraction of traffic.
Operate
Trace, drift, retrain
Track attack success, not only blocks. Shadow and canary every new model, and decide fail open or closed per policy.

Where should guardrails sit in the request flow?

There’s a trade-off here on how much you trust the input, and on your security restrictions and policies. Generally, you can run guardrail checks on the inputs and you can run checks on the output. For the input, it’s to make sure there are no jailbreaks or prompt injection, and all the other vectors of attack. On the output, it’s to make sure you don’t expose any PII or any information that might be sensitive to your domain, application or database.

You can also scan the input and output together as a pair, to get broader context over what’s going on. And there’s another layer of guardrails: scanning in the context of the chat history. In a small context, a request might look like a pretty normal message in a chat. In the broad picture, you can spot vectors of attack, especially if your context also pulls in messages from the history.

Figure - Four scan points over the same conversation (toy model)
scan
Pick a scan point and press Play. The highlighted cells are what that check reads on each turn. Watch the third turn: the input check sees one mild message, the output check only fires after the model already wrote the key, and the chat history check sees the setup across all three turns and blocks before the model runs.

What about images, audio and files?

Take into account that you’re not only dealing with traditional text generation models. You might also run a multimodal application, where inputs come as text and also as audio, images or any file format. You’d very likely normalize those to some textual or feature representation that goes to the LLM. Nevertheless, an attacker can embed attacks in those files too. On an image, they can just print some characters that form a prompt attack, or they can embed a metadata header in a PNG or JPEG to run a prompt injection. The same applies to PDF files and similar formats.

What should happen when a guardrail flags something?

That also depends on the security policy and the application. I think we can do all of the above, and also keep the logs for further calibration and improvement over time. So basically we can have three different modes.

Pass
No stop
The request goes through, and the decision is still logged for calibration.
Warn
Suspicion
Issue a warning when there's some suspicion that the request violates the security policy.
Block
Violation
Stop the request completely. My default for anything that tries to expose secrets.

I’ve never done editing of the responses, and I don’t think it works well in general. I’ve seen some applications where it makes sense. For instance, you might be concerned about exposing sensitive information from your development team’s environment, credentials or API keys, and the guardrail application is supposed to prevent exactly those attacks. For those cases, I agree you can redact. Still, I’d rather take the radical approach and just block those requests, since they’re trying to expose information. The user sending that to the chat is probably not using the application or the LLM as intended.

Redaction is a policy Editing responses is possible, but generic guardrail interfaces usually don’t do it unless you implement it as a policy. When something tries to pull out credentials, I block.

How do you evaluate whether guardrails are working?

Since I’m describing a multi-layer architecture for guardrails, with different levels of checks, we should have a separate dataset per layer: a golden evaluation set. What goes into it always depends on the implementation details. You might use rule-based guardrails, a classifier trained on an encoder transformer, or a more classical ML classifier to reach your latency goals or SLA. Either way, I’d keep a golden dataset of the things I want each of those three layers to catch.

I’d also measure the full path end to end, to make sure we’re not exposed to attacks. And I’d keep tracking and collecting the long tail of what we see in production, obviously with respect for the privacy of the customer. I’d also test the classifier on data it was never exposed to during training. I covered how a golden set catches regressions in a retrieval pipeline in RAG beyond the tutorial. Here it’s one set per layer plus the end-to-end run.

Three more things belong in the evaluation. First, be explicit about false positives versus false negatives. Blocking too much can make the product unusable, so you report both, per layer. Second, adversarial red teaming that evolves over time, because static test sets get stale fast and attackers adapt. Third, track the latency impact of each layer. That number decides whether you can afford a check in the hot path or whether it has to run async.

Regex, classifier or LLM judge: which detection should you use?

Let’s evaluate all of these approaches. They trade simplicity and latency against accuracy and resilience.

Regex
Fast to add
Linear-time engines make it cheap. Best for structured secrets and known signatures.
Classifier
More accurate
Much better accuracy and precision than regex. Costs training time, latency and constant retraining.
LLM judge
Multilingual
Catches attacks in languages the others never saw. Slow, expensive, and an attack surface itself.

Regular expression rules

Regex rules are simple, and they scale roughly linearly with the number of rules you add. That still requires some performance optimization, because some regexes are expensive to run and carry overhead from backtracking and other factors. They give you a surface of protection. I wouldn’t call it ideal. It’s a trade-off between simplicity and overall resilience, and it’s probably the layer you can add fairly quickly.

The backtracking cost depends on the engine. Engines like RE2 guarantee time linear in the input length for a given pattern, while backtracking engines can hit catastrophic, exponential blowups. So I’d use RE2 or the Rust regex crate, and I wouldn’t assume regex is always the weak layer. For structured secrets and known signatures, regex is probably better than a probabilistic classifier.

A trained classifier

The next layer is a classifier. You can label data and train a binary classifier, whether it’s an encoder or encoder-decoder transformer, something else, or even a naive Bayes classifier, to decide whether a message is, say, a prompt injection. This takes time to train, and there’s another penalty in latency to run the classifier. The encoder option is the one I wrote about in BERT’s revenge.

Where it runs matters. I’d design the guardrails so they don’t run as part of the router. A router is an IO-heavy application with many connections, threads or coroutines, whatever your technology uses. You might build it in Go, Rust, JavaScript or Python, that’s up to you, but embedding a CPU-heavy classifier into that service isn’t a wise decision. It introduces CPU spikes that would totally mess up your IO, even with threads. So that’s a good reason to move the classifier to a separate service. You pay extra latency for the network hop and extra latency for running the classifier itself.

It isn’t mandatory in every case, though. For small models, in-process inference can be fine if you isolate execution from the event loop. For transformer inference, a dedicated service makes sense for batching, GPU use and multi-replica setups.

Keep heavy inference out of the router The router is IO-bound. A CPU-heavy model inside it causes spikes that stall every other connection. Small models can stay in-process off the event loop; transformers get their own service.

I think a classifier gives you much better accuracy and precision than regex rules. It still requires constant maintenance, updates and retraining, which might be expensive or might not, depending on the technology and approach. Or it could be an ensemble classifier that you gradually add to for certain use cases. Instead of retraining the same classifier over and over, you add one slim model and aggregate the signals from all of them.

The hard part of an ensemble is arbitrating disagreement. If one model flags prompt injection at 92% and another says 15%, what does the policy engine do? Calibration across models is hard. You need either a meta classifier on top or well calibrated per-model thresholds, otherwise your policies become really brittle.

An LLM as a judge

The last layer is an LLM judge. Its advantage is that it can spot things like attacks in other languages. Regex and classifier solutions are exposed to multi-language attacks. If I write a prompt injection in Ukrainian, that’s a blind spot for any classifier that was never trained on Ukrainian or didn’t have enough exposure to it. The same applies to regex rules, since they’re usually written as patterns in the most common languages. And you don’t know which language an attacker will choose.

There’s another option: you can prohibit using the application in languages that aren’t spoken by the primary majority of your customers. That’s fine as well, it’s also a security policy, and it’s easy to detect from the text in question, from certain words, or from the Unicode range of the characters.

Figure - Which detection layer catches which attack (toy model)
layers
Toggle the layers and watch which attacks get through. Regex covers known patterns, the classifier adds paraphrases but is blind to a language it never trained on, the LLM judge reads Ukrainian but can fall for an injection aimed at itself. The language policy closes the multilingual gap for almost nothing. Cost dots are relative, not measured.

The LLM judge also exposes a surface of attack. The judge is another place where prompt injection can happen, and it can fall for the injection itself and let the request bypass. Structured outputs and a strict separation between instructions and untrusted content help, but they’re not a silver bullet. Generally the judge gives you better confidence in the results, but its latency is all over the place, up to a second per response if you use a very high parameter model. The judge, and to a lesser degree the transformer classifier, can also be much more expensive for your budget. I compared a judge with a cheaper setup on a real client in quality gates without LLM-as-a-judge.

Language is a policy too Prohibiting languages outside your primary customer base is a valid security policy, and it’s easy to detect from words or the Unicode range of the text.

Which classifier models should you use for guardrails?

Here are the options I’d consider for the classifier layer, from the cheapest baseline to the most expensive encoder, with the trade-offs that decide between them.

TF-IDF + LR
First baseline
Text becomes sparse features, you learn weights, you get a probability-like score. Very fast on CPU, cheap to train, easy to deploy. Brittle under paraphrase and meaning shifts.
Linear SVM
Same speed
Similar speed, often strong on sparse text features. Calibrating its scores into probabilities is trickier.
Naive Bayes
Cheapest
Even cheaper, but it assumes feature independence, so it misses the interactions between words that matter.
fastText
Multilingual
Lightweight, subword aware, fast inference. Still pretty limited on context.
MiniLM, DistilBERT
Compact encoders
Contextual representations, so they hold up better under paraphrases and unseen wording. Costs CPU or GPU and some deployment complexity.
ModernBERT
Long context
Longer context windows, but serving cost rises and most attack prompts are short. Check that you truly benefit.
DeBERTa
Strong
Can be strong, but serving cost grows fast with model size. Larger isn't always better; training data often matters more than size.
XLM-R
Multilingual encoder
For multilingual coverage, this can matter more than scaling up an English model.
LLM judge
Hard cases
Flexible and reasons about nuanced instructions, but costly, slow, with its own injection surface. Reserve for hard cases or async audit.

I wouldn’t trust TF-IDF with logistic regression alone. And rather than standing up five transformers in an ensemble, I’d benchmark three things: a cheap baseline like TF-IDF plus logistic regression, a compact encoder like MiniLM, and an LLM judge. Then measure, and not just accuracy. What you want is the incremental lift each layer adds over the one before it.

How do you handle a new attack type?

Six months in, a new attack type shows up. Do you add another specialized classifier or retrain the general one? I’d go hybrid. A general multi-head classifier is more efficient at inference, because you run one encoder instead of three separate transformers. Specialized classifiers give you modular updates without retraining everything.

So I’d start with a shared encoder and a multi-head setup, and keep the option to bolt on a specialist when a new threat appears. Once the threat is stable and there’s enough data, fold it back into the general model on the next training cycle. That hedges between agility and efficiency.

What about attacks that look benign in every category?

A multi-head design has a gap: an attack can be benign under each single category and malicious in combination. I’d handle it in two ways. First, a shared encoder with multi-label outputs. Second, a policy layer that looks at combinations of signals. If none of the heads fires high enough on its own, but several are moderately suspicious, the policy can trigger a higher risk treatment.

Don’t let the models make access decisions. They emit signals, and a policy engine decides.

Figure - No head fires alone; the policy engine reads them together (toy model)
injection secrets jailbreak
Raise all three heads to around 0.6. Each stays under its own 0.8 threshold, so per-head thresholds pass the request. The policy engine sees two or more moderately suspicious heads and escalates. Thresholds are placeholders.

Models emit, policy decides Per-head thresholds miss an attack spread thin across categories. A policy engine that reads all the scores together can escalate it, and can apply different rules per customer.

How do you keep guardrails under 15 ms at 10,000 requests per second?

Say the gateway handles 10,000 requests per second and you want under 15 milliseconds added at P99. That target also answers which requests hit which classifiers without turning the pipeline into a latency and cost bomb. I’d split it into three tiers.

Tier 1
CPU, in the gateway
A Go or Rust gateway for fast detection: regex, secret patterns, maybe TF-IDF plus logistic regression. Stateless, scaled horizontally.
Tier 2
Inference service
MiniLM or DistilBERT on a dedicated service. CPU first with ONNX or OpenVINO, then benchmark GPU with ONNX or Triton.
Tier 3
LLM judge
Only a small fraction of traffic. Sync for high-risk flows with a separate SLA, or async for audit and training data.

At 10,000 requests per second, GPU batching in tier 2 can be cheaper than CPU, but watch the queueing. Keep batch windows tiny and use dynamic batching. For tier 3, don’t count on an external LLM staying under 50 milliseconds on the hot path. Beside the tiers, add a separate policy engine that takes the signals and applies per-customer rules.

Figure - Every request hits tier 1; the share you escalate sets the bill (toy model)
to encoder
to judge
Slide to encoder down from 100% and watch the relative cost fall while tier 1 still sees every request. The judge share stays small on purpose. Unit costs are made up for illustration; measure your own before deciding.

For metrics, I’d track the added P99 latency per layer, cost per million requests, attack success rate on held-out sets, recall at a fixed false positive rate, and queue depth. And before building any of it, I’d estimate what percent of your traffic really needs a transformer at all. That one number could swing your cost by 10x.

How do you monitor LLM guardrails in production?

For production, I think in five threads, plus a cadence that keeps them running.

Observe
Tracing
OpenTelemetry traces and Prometheus metrics with Grafana dashboards, across gateway, regex, transformer, policy engine and LLM output.
Security
Attack success
Track attack success, not only blocks, and watch shifts in language mix, length and attack types.
Drift
Data and concept
Models drift. Shadow deploy, then canary, before you fully roll out any new model.
Failure
Open or closed
Decide per policy what happens when the transformer service is down: allow with logging, or block.
Loop
Improve
Sample and label production traffic with privacy controls, feed the eval sets, retrain, shadow, canary, ramp up.
Cadence
Reviews
Daily incident review, weekly drift review, monthly retraining, or sooner if something critical shows up.

Observability first. Trace every request across the whole path: gateway, regex, transformer, policy engine and the LLM output, and attach the policy decisions to the trace. Don’t store raw prompts by default.

Security monitoring means tracking attack success, not just blocks. If your block rate drops from 8% to 2%, maybe attacks stopped, or maybe your detector broke. You can’t tell from the block rate alone, so you also monitor distribution shifts in language mix, prompt length and attack types.

On failure modes, decide fail open versus fail closed per policy. If the transformer service is down, do you allow with logging, or do you block? A public chatbot might lean toward fail open. An AI agent with production credentials should fail closed.

The improvement loop samples and labels production traffic with privacy controls, feeds it back into the eval sets, retrains, then goes through shadow, canary and ramp-up. Don’t train on every flagged request. Attackers will poison you.

Don't train on every flag Flagged traffic is partly written by attackers. Feed it into retraining unfiltered and they get to shape your detector.

How do you detect an attack that actually succeeded?

Separate detection from confirmation. For live detection, run your usual classifiers and add conversation-level signals. If a request gets a high prompt injection score and you block it, that’s not a successful attack. It’s a prevented attempt.

To spot actual success, you need ground truth signals: tool call logs showing that a prohibited action was attempted, or secrets detected in the output.

What if the customer says no data, no training?

There’s a tension between customer privacy and the need to collect production examples for retraining. If privacy prevents storing prompts, keep derived signals only: scores, decision types, language, model version and latency, and not the content itself. That lets you alert in real time without retaining sensitive text. For retraining without data retention, lean on synthetic adversarial data.

So, how do you build AI LLM guardrails?

Check the input for jailbreaks and prompt injection, the output for PII and anything sensitive to your domain, the pair together, and the chat history, including whatever comes in through images, audio and files. Let each policy choose pass, warn or block, and keep redaction as an explicit policy. Detect in tiers: linear-time regex and a cheap baseline in the gateway, a compact encoder like MiniLM in its own service, and an LLM judge only for the small slice of traffic that needs it, with a policy engine combining the signals per customer. Keep a golden set per layer, report false positives and false negatives, red team continuously, and in production track attack success, drift and fail-open rules per policy. If you’re building this into a gateway and want a second pair of eyes on the design, that’s the kind of work a fractional AI CTO does.

Follow in Google

Make Metaheuristic a preferred source.

One tap and posts like this one surface higher in your Top Stories.

Work with us

Production AI, with guardrails.

Start with a fixed-scope AI Workflow Audit. We map the opportunity and quote a build.

Start a Discovery Sprint →