Custom guardrails

Train a binary guardrail on your own labeled examples and serve it like a built-in one

When the built-in guardrails do not cover a check you need, you can train your own. You provide labeled examples and a short description of what you want to catch, and Opik fine-tunes a small classifier (the same architecture as the built-in prompt injection guardrail) that runs on the guardrails server.

This is the trained counterpart to the LLM judge: use the LLM judge to prototype a check with a natural-language policy, and once it matters, train a custom guardrail so it runs fast and cheap inline at high volume. It’s also the trained counterpart to a manual guardrail: reach for a manual guardrail for a one-off heuristic, and train a custom guardrail once you want a real model backing that same check.

How it works

  1. You call create_custom_guardrail with labeled examples and a description of the metric.
  2. The guardrails server trains a small LoRA adapter on its GPU and stores it under the name you chose.
  3. You reference that name from the CustomGuardrail guard, and the same server runs it.

Train the guardrail

Pass your labeled examples and a description to create_custom_guardrail. Each example is text plus a binary label, where 1 means the metric holds (the guardrail should fail) and 0 means it does not. The description completes the sentence “Determine whether it …”.

1from opik.guardrails import create_custom_guardrail
2
3result = create_custom_guardrail(
4 name="toxicity-v1",
5 description="contains toxic or abusive language",
6 examples=[
7 {"text": "Get lost, nobody wants you around here.", "label": 1},
8 {"text": "Thanks so much for your help today!", "label": 0},
9 # ... more labeled examples
10 ],
11)
12
13print(result["eval_metrics"])

Training runs on the guardrails server and can take a few minutes. By default the call blocks until it finishes and returns evaluation metrics (accuracy, precision, recall, F1, AUROC) on held-out validation and test splits, so you can judge quality before using it. Pass wait=False to return immediately instead.

Retraining under a name that already exists is rejected unless you pass overwrite=True, so you don’t clobber an existing guardrail by accident.

Track progress

Pass a callback to follow the run as it trains. It is called on each poll with the current status, which carries a progress dict (percent complete, epoch, latest training loss, and the latest validation metrics):

1def on_progress(status):
2 p = status.get("progress", {})
3 print(
4 f"{status['status']} {p.get('percent', 0)}% "
5 f"epoch={p.get('epoch')} "
6 f"val_f1={p.get('latest_eval', {}).get('eval_validation_f1')}"
7 )
8
9create_custom_guardrail(
10 name="toxicity-v1",
11 description="contains toxic or abusive language",
12 examples=[...],
13 callback=on_progress,
14)

Aim for a balanced set with enough examples of each class to represent your production traffic.

Use it in a guardrail

Reference the model name in a CustomGuardrail guard:

1from opik.guardrails import Guardrail, CustomGuardrail
2from opik import exceptions
3
4guardrail = Guardrail(
5 guards=[
6 CustomGuardrail(model_name="toxicity-v1", threshold=0.5),
7 ]
8)
9
10try:
11 guardrail.validate("Get lost, nobody wants you around here.")
12except exceptions.GuardrailValidationFailed as e:
13 print(e)

The guard fails when the model’s score is at or above threshold. Lower it to be stricter, raise it to be more permissive. If the named model is not found in the server’s adapters directory, validation fails closed with a clear error.

Next steps