LLM Guardrails
The LLM Guardrails use case describes checking prompts at the gateway for every application. This guide adds guardrails to gpt-4o-mini that reject a prompt longer than 2000 bytes, or one that contains an AWS key, a GitHub token, or an email address, before it reaches OpenAI.
Prerequisites
- Docker Compose
- An OpenAI API key, exported as
OPENAI_API_KEY
docker-compose.yaml
name: zilla-llm-guardrails
services:
presidio-analyzer:
image: mcr.microsoft.com/presidio-analyzer:latest
restart: unless-stopped
environment:
GUNICORN_CMD_ARGS: --no-control-socket
healthcheck:
interval: 10s
timeout: 3s
retries: 60
start_period: 30s
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:3000/health')"]
zilla:
image: ghcr.io/aklivity/zilla:${ZILLA_VERSION:-2.5.0-rc5}
restart: unless-stopped
ports:
- 7161:7161
healthcheck:
interval: 5s
timeout: 3s
retries: 15
start_period: 15s
start_interval: 250ms
test: ["CMD", "sh", "-c", "test -f /var/run/zilla/ready"]
volumes:
- ./zilla.yaml:/etc/zilla/zilla.yaml:ro
command: start -v -e
depends_on:
presidio-analyzer:
condition: service_healthyzilla.yaml
name: llm-guardrails
#region classifiers
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
language: en
threshold: 0.6
timeout: PT5S
labels:
email: EMAIL_ADDRESS
#endregion classifiers
guards:
api_key:
type: inline
bindings:
north_tcp_server:
type: tcp
kind: server
options:
host: 0.0.0.0
port: 7161
exit: north_http_server
north_http_server:
type: http
kind: server
exit: north_llm_server
north_llm_server:
type: llm
kind: server
options:
dialect: openai
authorization:
api_key:
credentials: "Bearer {credentials}"
exit: north_llm_proxy
#region llm_proxy
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: south_llm_client_openai
#endregion llm_proxy
south_llm_client_openai:
type: llm
kind: client
options:
dialect: openai
server: https://api.openai.com
authorization:
api_key:
credentials: "Bearer {credentials}"
exit: sys:http_clientTwo classifiers detect the content to reject: patterns for secrets, and presidio for email addresses:
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
language: en
threshold: 0.6
timeout: PT5S
labels:
email: EMAIL_ADDRESSThe guardrails on gpt-4o-mini check the user's text twice: its length, then its content:
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: south_llm_client_openaiStart the Stack
docker compose up -d --waitThe Presidio analyzer can take a minute to become healthy.
Send a Clean Prompt
curl -s http://localhost:7161/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o-mini","max_tokens":60,"messages":[{"role":"user","content":"In one sentence, what is an LLM gateway?"}]}'The prompt passes both checks and OpenAI answers.
Send a Secret
curl -s http://localhost:7161/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o-mini","max_tokens":60,"messages":[{"role":"user","content":"Is this AWS key valid: AKIAZ7QW3RT9XK2MPLB4 ?"}]}'{"error":{"message":"user-text rejected","type":"invalid_request_error","param":"user-text[0]","code":null}}The patterns classifier matched an AWS access key, so Zilla rejected the prompt with 400 and never sent it to OpenAI.
Send Personal Data
curl -s http://localhost:7161/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o-mini","max_tokens":60,"messages":[{"role":"user","content":"Write a haiku for my colleague, her email is jane.doe@example.com"}]}'This time the Presidio analyzer found an email address, and the prompt gets the same 400.
Add a Jailbreak Check
To also reject jailbreak attempts, add a semantic classifier. It needs an embedding service; the LLM Gateway demo runs one.
Stop the Stack
docker compose downNext Steps
- LLM Guardrails: sections, reply checks, and their limits.
- Classify: the classifiers in detail.

