Validate LLM Outputs Before They Ship
Your LLM will eventually return garbage – malformed JSON, leaked PII, toxic language, or just wrong answers. Guardrails AI gives you a validation layer that sits between your LLM call and your users. You define validators, attach them to a Guard, and the library handles checking (and optionally fixing) every response.
Install it and grab a couple of hub validators:
| |
Here’s the simplest possible guard – validate that an LLM response matches a regex pattern:
| |
If the output doesn’t start with a capital letter and end with a period, the guard raises an exception. That’s the core pattern: create a Guard, attach validators with .use(), and call .validate() on any string.
Wrap OpenAI Calls with a Guard
The real power shows up when you wrap your LLM calls directly. Instead of calling OpenAI and then validating separately, the Guard handles both steps – and can automatically re-ask the LLM if validation fails.
| |
The guard() call sends the messages to OpenAI, gets the response, runs it through both validators, and returns a ValidationOutcome. With on_fail=OnFailAction.FIX, the toxic language validator scrubs flagged sentences and the PII detector redacts emails, phone numbers, and SSNs instead of raising an error.
Structured Output with Pydantic Models
When your LLM needs to return structured data, combine Guardrails with Pydantic to enforce both schema and content rules:
| |
Guard.from_pydantic() reads the model’s fields and validators. When the LLM returns a rating of 7, ValidRange with on_fail=OnFailAction.FIX clamps it to the valid range. When the summary doesn’t match the regex, OnFailAction.REASK sends the validation error back to the LLM and asks it to try again – up to num_reasks times.
Build Custom Validators
Hub validators cover common cases, but you’ll eventually need custom logic. Guardrails supports both function-based and class-based validators.
Function-based, for simple checks:
| |
Class-based, for validators that need configuration:
| |
Use them the same way as hub validators:
| |
The response gets both competitor names scrubbed and excess sentences trimmed in a single pass.
On-Fail Actions Explained
Every validator takes an on_fail parameter that controls what happens when validation fails. Pick the right one for each use case:
| Action | Behavior | Best For |
|---|---|---|
OnFailAction.EXCEPTION | Raises ValidationError | Hard requirements where bad output is unacceptable |
OnFailAction.REASK | Sends error back to LLM for retry | Format issues the LLM can self-correct |
OnFailAction.FIX | Uses the validator’s fix_value | PII redaction, competitor scrubbing |
OnFailAction.FIX_REASK | Tries fix first, reasks if fix still fails | Complex corrections |
OnFailAction.NOOP | Logs the failure but passes output through | Monitoring without blocking |
OnFailAction.FILTER | Removes the failing value entirely | List items that fail validation |
OnFailAction.REFRAIN | Returns None instead of output | When no output is better than bad output |
You can also pass a custom function:
| |
Common Errors and Fixes
guardrails hub install fails with “validator not found”. You need to authenticate first. Run guardrails configure and enter your API key from the Guardrails Hub dashboard. Some validators are community-contributed and may have different names than expected – search at hub.guardrailsai.com.
ValidationError raised unexpectedly in production. Don’t use OnFailAction.EXCEPTION unless you wrap the guard call in try/except. For production services, OnFailAction.FIX or OnFailAction.NOOP are safer defaults – they degrade gracefully instead of crashing your API.
num_reasks causes slow responses. Each reask is a full LLM round-trip. Set num_reasks=1 for latency-sensitive endpoints. For batch processing, num_reasks=3 is reasonable. Never set it higher than 5 – if the LLM can’t get it right in 5 tries, your prompt needs work.
Pydantic validation errors on nested models. Guard.from_pydantic() works with nested Pydantic models, but validators on nested fields need to be specified in the Field definition, not via .use(). Use .use() for top-level string validators and Field validators for property-level checks.
DetectPII misses domain-specific PII. The hub PII validator uses standard NER patterns. For custom identifiers like internal account numbers or proprietary IDs, write a custom validator with regex patterns specific to your data format.
Related Guides
- How to Build Automated PII Redaction Testing for LLM Outputs
- How to Implement Content Filtering for LLM Applications
- How to Build Input and Output Guardrails for Multi-Agent Systems
- How to Build Automated Bias Audits for LLM Outputs
- How to Build Rate Limiting and Abuse Prevention for LLM APIs
- How to Build Automated Fairness Testing for LLM-Generated Content
- How to Build Hallucination Scoring and Grounding Verification for LLMs
- How to Build Automated Prompt Leakage Detection for LLM Apps
- How to Build Output Grounding and Fact-Checking for LLM Apps
- How to Build Automated Jailbreak Detection for LLM Applications