Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Getting Started

This guide walks you through installing naaf and running your first workflow.

Installation

Add the crates you need to your Cargo.toml:

[dependencies]
naaf-core = "0.1.0"    # Core traits and Step builder
naaf-llm = "0.1.0"     # LLM-backed Task, Check, Materialiser, RepairPlanner
naaf-process = "0.1.0"  # Shell-command-backed adapters

The naaf-llm crate has an optional openai feature for the built-in OpenAI client:

naaf-llm = { version = "0.1.0", features = ["openai"] }

Your First Workflow

This example creates a step that generates a code patch, validates it with cargo test, and retries on failure.

Dependencies

[dependencies]
naaf-core = "0.1.0"
naaf-process = "0.1.0"
tokio = { version = "1", features = ["full"] }

Code

use naaf_core::{Check, RetryPolicy, Step, Task};
use naaf_process::ProcessAgent;
use serde::{Deserialize, Serialize};
use std::process::Stdio;
use tokio::process::Command;

#[derive(Debug, Clone, Serialize, Deserialize)]
struct Patch(String);

#[derive(Debug, Clone, Serialize, Deserialize)]
struct PatchFindings {
    failed: Vec<String>,
}

struct GeneratePatch;

#[async_trait::async_trait]
impl Task for GeneratePatch {
    type Input = String;
    type Output = Patch;
    type Error = std::io::Error;

    async fn run(&self, _: &naaf_core::Runtime, input: &String) -> Result<Patch, Self::Error> {
        Ok(Patch(format!("// Generated patch for: {}\nfn solution() {{ }}\n", input)))
    }
}

struct ValidatePatch;

#[async_trait::async_trait]
impl Check for ValidatePatch {
    type Subject = Patch;
    type Findings = PatchFindings;
    type Error = std::io::Error;

    async fn check(
        &self,
        _: &naaf_core::Runtime,
        subject: &Patch,
    ) -> Result<Vec<Self::Findings>, Self::Error> {
        let output = Command::new("sh")
            .arg("-c")
            .arg(format!("echo '{}' | cargo check --message-format=json 2>&1 || true", subject.0))
            .stdout(Stdio::piped())
            .output()
            .await?;

        let output_str = String::from_utf8_lossy(&output.stdout);
        if output_str.contains("error") {
            Ok(vec![PatchFindings {
                failed: vec![output_str.to_string()],
            }])
        } else {
            Ok(vec![])
        }
    }
}

struct RepairPatch;

#[async_trait::async_trait]
impl naaf_core::RepairPlanner for RepairPatch {
    type Input = String;
    type Output = String;
    type Findings = PatchFindings;

    async fn repair(
        &self,
        _: &naaf_core::Runtime,
        attempts: &[naaf_core::Attempt<PatchFindings, Patch>],
    ) -> Result<String, Self::Error> {
        let last_input = attempts.last().map(|a| a.input()).unwrap_or("");
        let findings = attempts.last().map(|a| a.findings()).unwrap_or(&vec![]);

        Ok(format!("retry with: {} failures: {:?}", last_input, findings))
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let runtime = naaf_core::Runtime::new();

    let step = Step::builder(GeneratePatch)
        .validate(ValidatePatch)
        .repair_with(RepairPatch)
        .retry_policy(RetryPolicy::new(3))
        .build();

    let result = step.run(&runtime, &"test input".to_string()).await;

    match result {
        Ok(output) => println!("Success: {:?}", output),
        Err(e) => println!("Failed: {:?}", e),
    }

    Ok(())
}

Running Examples

Each example is a standalone binary in the examples/ directory:

# Run the step-retry example
cargo run -p step-retry

# Run the materialiser example
cargo run -p materialiser

# Run the dynamic-workflow example
cargo run -p dynamic-workflow

Next Steps