Guardrails and AI Safety
Input Guardrails — Patterns
Input guardrails › Patterns
Canonicalise before deciding
Problem. Rules are written against the text a human would read, and the text that arrives is one of many encodings of it. Visually identical characters from other scripts, combining marks, zero-width joiners, directional overrides, percent-encoding, and repeated escaping all produce byte sequences that no rule matches and that the model reads exactly as intended.
Approach. Put a single canonicalisation stage in front of every decision: apply a defined Unicode normalisation form, strip format and control characters that carry no linguistic content, fold confusable scripts where the product's languages allow it, and decode transport encodings exactly once. Every rule, classifier, and record then operates on that one form, and the original is retained only for display and investigation.
Trade-off. Normalisation is lossy and the losses are not evenly distributed: aggressive confusable folding damages languages that legitimately mix scripts, and stripping marks breaks text where they are meaningful. A stage that silently corrupts a tenth of your non-English traffic will be discovered by that tenth, not by your tests.
Cheap filter in front, expensive adjudicator behind
Problem. The judgement you want requires a model, and a model on every request costs more latency than the request it is protecting. The judgement you can afford catches only the obvious.
Approach. Run the deterministic and small-classifier checks on everything, and let them return three outcomes rather than two: clear, refuse, and uncertain. Only the uncertain band reaches the expensive adjudicator. Size the band from measured traffic so the adjudicator's load is a known fraction, and report its size as a first-class metric, because a widening band is how this design silently becomes a model call on every request.
Trade-off. The cheap stage now decides what the expensive stage never sees, so its false clears are permanent and invisible. The band is also the part that moves when traffic shifts, which means capacity for the adjudicator must be provisioned for a number that is not stable.
Narrow the request instead of rejecting it
Problem. Many requests are not unsafe in themselves but unsafe in the capability they would unlock: a question that is fine to answer from the product's own documentation and not fine to answer by browsing, or a task that is fine read-only and not fine with write tools attached. Refusing these loses a legitimate user; admitting them at full capability is the risk.
Approach. Make the gate's output a request envelope rather than a boolean — the tool subset, the data sources, the maximum spend, whether external fetches are permitted — and have it reduce that envelope on suspicion. The request proceeds and the model simply has less to work with. The reduction is recorded so the effect on answer quality can be measured against unreduced traffic.
Trade-off. The user is served a degraded answer with no explanation, which is a worse experience than an honest refusal when the degradation is the reason the answer is unhelpful. Every downstream component must also honour the envelope, and one that reads the original request instead reintroduces the full capability with no symptom.
Score the conversation, not the message
Problem. A gate that evaluates each turn independently is defeated by anyone willing to spend several turns. The individually innocuous decomposition is the dominant shape of real attempts, and no per-message threshold catches it without destroying ordinary multi-turn use.
Approach. Maintain a running assessment on the session: accumulate per-turn signals, decay them over time, and evaluate the sequence as well as the current turn. Bind the accumulator to something more durable than the conversation — the account, the key, the device — so opening a fresh thread does not reset it. Treat crossing the accumulated threshold as a different event from a single-turn refusal, with its own response.
Trade-off. State on the request path is an availability dependency and a privacy commitment: you are now retaining a behavioural profile of every user, including the ones you had no reason to suspect. It also creates a lagging control, where the decision that matters is taken several turns after the first signal that would have justified it.