# guardflow
[](https://crates.io/crates/guardflow)
[](https://docs.rs/guardflow)
[](https://github.com/thaicn1712/guardflow/actions/workflows/ci.yml)
[](LICENSE)
Validators and a retry-until-valid guard for LLM output — the core pattern behind Python's Guardrails AI. No Rust equivalent as of 2026.
## Install
```bash
cargo add guardflow
```
## The pattern: format checks are free, real checks need a model call
`Guard` runs cheap sync checks (length, regex, PII shape, JSON schema). `AsyncGuard` adds checks that need a network call — an LLM-as-judge, a moderation API — and only makes that call once the free checks already pass:
```rust,ignore
use guardflow::validators::NoPii;
use guardflow::{AsyncGuard, CustomAsyncValidator, Guard, guard_with_retry_async};
let guard = AsyncGuard::from_guard(Guard::new().with(NoPii::default()))
.with_async(CustomAsyncValidator::new("refund_policy", |text| async move {
if text.to_lowercase().contains("guaranteed refund") {
Err("promises a refund outside policy".into())
} else {
Ok(())
}
}));
Some(f) => format!("Fix this: {:?}. Try again.", f.failures),
None => "Reply to the customer.".to_string(),
};
call_llm(prompt) // returns a Future<Output = String>
}).await;
```
Run it: `cargo run --example support_bot` — a fake LLM leaks an email (rejected by the free `no_pii` check), then over-promises a refund (rejected by the async policy check, which only ran because the free check passed first), then succeeds on the third try.
## Built-in validators
`NotEmpty`, `MaxLength`, `MinLength`, `MatchesRegex`, `OneOf`, `ValidJson`, `NoPii` (heuristic regex, not ML), `Profanity` (bring your own word list), `JsonSchema` (feature `json-schema`). Implement `Validator` (sync) or `AsyncValidator` for your own checks.
**Auto-fix instead of reject**: some validators can repair input instead of just failing it — `Truncate` shortens instead of rejecting on length. `Guard::fix(text)` applies every fixable validator in order and returns the corrected text; `Guard::check` never rewrites anything, so `Truncate` counts as a failure there.
## Rule files
Load a rule set from YAML or JSON instead of Rust code (feature `spec`):
```yaml
# rules.yaml
rules:
- rule: not_empty
- rule: max_length
max: 500
- rule: no_pii
```
```rust,ignore
let guard = guardflow::spec::guard_from_file("rules.yaml")?;
```
## graph-flow integration
With the `graphflow` feature, `GuardedTask<T>` wraps any [`graph_flow::Task`](https://crates.io/crates/graph-flow), retrying until its response passes:
```rust,ignore
use guardflow::graphflow::GuardedTask;
let guarded = GuardedTask::new(my_task, guard).with_max_attempts(3);
```
Works with [`graphflow-stream`](https://crates.io/crates/graphflow-stream) — wrap a task before passing it to `spawn_task`/`spawn_graph`.
## Examples
```bash
cargo run --example support_bot # sync + async guard, retry-until-valid, full story
```
## Benchmarks
`cargo bench` ([`benches/overhead.rs`](benches/overhead.rs)):
| `Guard::check`, 1 validator | ~27 ns |
| `Guard::check`, 5 validators (incl. `NoPii`) | ~1.2 µs |
## License
MIT