Skip to main content

Crate guardflow

Crate guardflow 

Source
Expand description

§guardflow

crates.io docs.rs CI 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

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_pii
let 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):

ScenarioTime
Guard::check, 1 validator~27 ns
Guard::check, 5 validators (incl. NoPii)~1.2 µs

§License

MIT

Modules§

validators

Structs§

AsyncGuard
Like crate::Guard, but also runs AsyncValidators. 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.
CustomAsyncValidator
Wraps an async closure as an AsyncValidator — the escape hatch for checks this crate can’t sensibly hardcode. Return Ok(()) 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.
GuardResult
The outcome of running a Guard against a candidate string.

Enums§

ValidationOutcome
Result of a single Validator check.

Traits§

AsyncValidator
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 generate until its output passes guard, or max_attempts is reached.
guard_with_retry_async
Like guard_with_retry, but checks against an AsyncGuard.