simple-agents-workflow 0.2.35

Workflow IR and validation for SimpleAgents
Documentation
use async_trait::async_trait;
use serde_json::{json, Value};
use simple_agents_workflow::{
    inspect_replay_trace, node_timeline, retry_reason_summary, LlmExecutionError,
    LlmExecutionInput, LlmExecutionOutput, LlmExecutor, Node, NodeExecutionPolicy, NodeKind,
    ToolExecutionError, ToolExecutionInput, ToolExecutor, WorkflowDefinition, WorkflowReplayMode,
    WorkflowRuntime, WorkflowRuntimeOptions,
};
use std::sync::atomic::{AtomicBool, Ordering};

struct FlakyLlm {
    failed_once: AtomicBool,
}

#[async_trait]
impl LlmExecutor for FlakyLlm {
    async fn execute(
        &self,
        _input: LlmExecutionInput,
    ) -> Result<LlmExecutionOutput, LlmExecutionError> {
        if !self.failed_once.swap(true, Ordering::Relaxed) {
            return Err(LlmExecutionError::Client(
                "transient upstream error".to_string(),
            ));
        }

        Ok(LlmExecutionOutput {
            content: "debug-success".to_string(),
        })
    }
}

struct DemoTool;

#[async_trait]
impl ToolExecutor for DemoTool {
    async fn execute_tool(&self, _input: ToolExecutionInput) -> Result<Value, ToolExecutionError> {
        Ok(json!({"status": "ok"}))
    }
}

fn workflow() -> WorkflowDefinition {
    WorkflowDefinition {
        version: "v0".to_string(),
        name: "debug-demo".to_string(),
        nodes: vec![
            Node {
                id: "start".to_string(),
                kind: NodeKind::Start {
                    next: "llm".to_string(),
                },
            },
            Node {
                id: "llm".to_string(),
                kind: NodeKind::Llm {
                    model: "demo-model".to_string(),
                    prompt: "Generate one line".to_string(),
                    next: Some("tool".to_string()),
                },
            },
            Node {
                id: "tool".to_string(),
                kind: NodeKind::Tool {
                    tool: "demo-tool".to_string(),
                    input: json!({"strict": true}),
                    next: Some("end".to_string()),
                },
            },
            Node {
                id: "end".to_string(),
                kind: NodeKind::End,
            },
        ],
    }
}

#[tokio::main]
async fn main() {
    let llm = FlakyLlm {
        failed_once: AtomicBool::new(false),
    };
    let tool = DemoTool;
    let runtime = WorkflowRuntime::new(
        workflow(),
        &llm,
        Some(&tool),
        WorkflowRuntimeOptions {
            replay_mode: WorkflowReplayMode::ValidateRecordedTrace,
            llm_node_policy: NodeExecutionPolicy {
                timeout: None,
                max_retries: 1,
            },
            ..WorkflowRuntimeOptions::default()
        },
    );

    let result = runtime
        .execute(json!({"request_id": "debug-1"}), None)
        .await
        .expect("debug workflow should succeed");

    let timeline = node_timeline(&result);
    let retries = retry_reason_summary(&result.retry_events);

    println!("timeline entries: {}", timeline.len());
    println!("retry groups: {}", retries.len());

    if let Some(trace) = result.trace.as_ref() {
        let replay = inspect_replay_trace(trace);
        println!("replay valid: {}", replay.valid);
        println!("replay events: {}", replay.total_events);
    }
}