Expand description
§guardflow
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
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:
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(())
}
}));
let reply = guard_with_retry_async(&guard, 3, |last_failure| {
let prompt = match last_failure {
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):
# rules.yaml
rules:
- rule: not_empty
- rule: max_length
max: 500
- rule: no_piilet guard = guardflow::spec::guard_from_file("rules.yaml")?;§graph-flow integration
With the graphflow feature, GuardedTask<T> wraps any graph_flow::Task, retrying until its response passes:
use guardflow::graphflow::GuardedTask;
let guarded = GuardedTask::new(my_task, guard).with_max_attempts(3);Works with graphflow-stream — wrap a task before passing it to spawn_task/spawn_graph.
§Examples
cargo run --example support_bot # sync + async guard, retry-until-valid, full story§Benchmarks
cargo bench (benches/overhead.rs):
| Scenario | Time |
|---|---|
Guard::check, 1 validator | ~27 ns |
Guard::check, 5 validators (incl. NoPii) | ~1.2 µs |
§License
MIT
Modules§
Structs§
- Async
Guard - Like
crate::Guard, but also runsAsyncValidators. Sync validators run first (cheap); async ones only run if those all pass, to avoid a network call on output that already fails a free local check. - Custom
Async Validator - Wraps an async closure as an
AsyncValidator— the escape hatch for checks this crate can’t sensibly hardcode. ReturnOk(())to pass,Err(reason)to fail. - Failure
- One validator’s failure, from a
GuardResult. - Guard
- A set of
Validators run together against a candidate string. - Guard
Result - The outcome of running a
Guardagainst a candidate string.
Enums§
- Validation
Outcome - Result of a single
Validatorcheck.
Traits§
- Async
Validator - An async check run against a candidate string — for validators that need a network call: an LLM-as-judge, a moderation API, a toxicity classifier.
- Validator
- A check run against a candidate string.
Functions§
- guard_
with_ retry - Call
generateuntil its output passesguard, ormax_attemptsis reached. - guard_
with_ retry_ async - Like
guard_with_retry, but checks against anAsyncGuard.