LLM Guardrails
LLM guardrails check the prompts applications send and the replies providers return, at the gateway. They are configured on llm · proxy for each model, and each check applies a model, such as classify, to one section of the conversation. A prompt that fails a check is rejected before it reaches the provider.
Configuration
classifiers:
secrets0:
type: patterns
options:
entropy: 3.0
labels:
secret.aws_key: "AKIA[0-9A-Z]{16}"
secret.github_token: "ghp_[A-Za-z0-9]{36}"
pii0:
type: presidio
options:
endpoint: http://presidio-analyzer:3000
labels:
email: EMAIL_ADDRESS
bindings:
north_llm_proxy:
type: llm
kind: proxy
options:
guardrails:
gpt-4o-mini:
request:
- section: user-text
value:
model: string
maxLength: 2000
- section: user-text
value:
model: classify
reject:
secrets0:
- secret.aws_key
- secret.github_token
pii0:
- email
routes:
- when:
- model:
- gpt-*
exit: openai_llm_clientEvery prompt for gpt-4o-mini has its user text checked twice: it must be no longer than 2000 bytes, and it must not contain an AWS key, a GitHub token, or an email address.
Per-Model Guardrails
options.guardrails maps a model name to its checks. The name is the exact model callers ask for: patterns such as gpt-* aren't accepted here. A model with no entry has no guardrails.
Each model has two ordered lists of stages:
| List | Checks |
|---|---|
request | The prompt, before it is sent to the provider |
response | The reply, before it is returned to the caller |
Stages run in the order they are listed, and a stage rejects as soon as its model rejects the section.
Sections
Each stage names the section it checks, or a list of sections. The sections are the same for the OpenAI and Anthropic APIs:
| Section | Content |
|---|---|
system-instruction | The system prompt |
user-text | Text from the user |
user-image, user-document, user-audio, user-video | Media from the user |
assistant-text | Text from the model |
assistant-reasoning | The model's reasoning |
assistant-refusal | A refusal from the model |
tool-definition | A tool the request makes available |
tool-call | A call to a tool |
tool-result | A tool's result |
server-tool-result | The result of a tool the provider runs |
citations | Citations in the reply |
Models
A stage's value is any model:
- A validating model, such as
stringwithmaxLength, checks the section's shape. classifyrejects the section when a classifier detects one of the listed labels, such as a secret with thepatternsclassifier, personal data withpresidio, or a jailbreak attempt by meaning withsemantic.
classify fails closed: when a classifier can't check a section, the section is rejected.
Rejected Prompts
When a request stage rejects, Zilla doesn't send the prompt to the provider. The caller receives 400 in the error format of its API, naming the rejected section.
OpenAI API:
{
"error": {
"message": "user-text rejected",
"type": "invalid_request_error",
"param": "user-text[0]",
"code": null
}
}Anthropic API:
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "user-text rejected"
}
}Rejected Replies
When a response stage rejects, Zilla stops the reply and abandons the provider's response.
- For a non-streaming request, the reply is cut off and the SDK reports a failed connection.
- For a streaming request, the stream ends with an error.
Checks on the reply have two limits to plan for:
- Text that a stage accepts is forwarded to the caller straight away, so part of a reply can reach the caller before a later part is rejected.
- In a streaming reply, each streamed piece of text is checked on its own. Content split across pieces doesn't match a check that needs it whole.
Checks on the prompt don't have these limits.
Guardrails and Fallback
When a request falls back to another model, the guardrails of the fallback model run in addition to those of the model the caller asked for.
Next Steps
- Classify: the classifiers guardrails use to detect secrets, personal data, and jailbreaks.
- LLM Guardrails: why guardrails at the gateway.
- llm · proxy: the full
llm · proxyreference.

