guardflow 0.2.0

Validators and a retry-until-valid guard for LLM output, in Rust
Documentation
# guardflow

[![crates.io](https://img.shields.io/crates/v/guardflow.svg?style=flat)](https://crates.io/crates/guardflow)
[![docs.rs](https://docs.rs/guardflow/badge.svg?style=flat)](https://docs.rs/guardflow)
[![CI](https://github.com/thaicn1712/guardflow/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/thaicn1712/guardflow/actions/workflows/ci.yml)
[![license](https://img.shields.io/crates/l/guardflow.svg?style=flat)](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(())
        }
    }));

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`):

```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)):

| Scenario | Time |
|---|---|
| `Guard::check`, 1 validator | ~27 ns |
| `Guard::check`, 5 validators (incl. `NoPii`) | ~1.2 µs |

## License

MIT