Skip to main content

serdes_ai_graph/
lib.rs

1//! # serdes-ai-graph
2//!
3//! Graph-based execution and multi-agent orchestration for serdes-ai.
4//!
5//! This crate provides a powerful graph execution engine for building
6//! complex, multi-step AI workflows with conditional branching and
7//! state management.
8//!
9//! ## Core Concepts
10//!
11//! - **[`Graph`]**: The main graph type, generic over state, deps, and end result
12//! - **[`BaseNode`]**: Trait for nodes that can execute within a graph
13//! - **[`NodeResult`]**: Enum indicating next step or termination
14//! - **[`Edge`]**: Conditional transitions between nodes
15//!
16//! ## Node Types
17//!
18//! - **[`FunctionNode`]**: Execute an async function
19//! - **[`AgentNode`]**: Run an agent and update state
20//! - **[`RouterNode`]**: Dynamic routing based on state
21//! - **[`ConditionalNode`]**: Branch based on condition
22//!
23//! ## State Persistence
24//!
25//! - **[`StatePersistence`]**: Trait for saving/loading state
26//! - **[`InMemoryPersistence`]**: In-memory state storage
27//! - **[`FilePersistence`]**: File-based state storage
28//!
29//! ## Example
30//!
31//! ```ignore
32//! use serdes_ai_graph::{Graph, BaseNode, NodeResult, GraphRunContext};
33//! use async_trait::async_trait;
34//!
35//! #[derive(Debug, Clone, Default)]
36//! struct WorkflowState {
37//!     query: String,
38//!     response: Option<String>,
39//! }
40//!
41//! struct ProcessNode;
42//!
43//! #[async_trait]
44//! impl BaseNode<WorkflowState, (), String> for ProcessNode {
45//!     fn name(&self) -> &str { "process" }
46//!
47//!     async fn run(
48//!         &self,
49//!         ctx: &mut GraphRunContext<WorkflowState, ()>,
50//!     ) -> Result<NodeResult<WorkflowState, (), String>, GraphError> {
51//!         ctx.state.response = Some(format!("Processed: {}", ctx.state.query));
52//!         Ok(NodeResult::end(ctx.state.response.clone().unwrap()))
53//!     }
54//! }
55//!
56//! let graph = Graph::new()
57//!     .node("process", ProcessNode)
58//!     .entry("process")
59//!     .build()?;
60//!
61//! let result = graph.run(WorkflowState::default(), ()).await?;
62//! ```
63
64#![warn(missing_docs)]
65#![deny(unsafe_code)]
66
67pub mod edge;
68pub mod error;
69pub mod executor;
70pub mod graph;
71pub mod iter;
72pub mod mermaid;
73pub mod node;
74pub mod persistence;
75pub mod state;
76
77// Re-exports
78pub use edge::{Edge, EdgeBuilder};
79pub use error::{GraphError, GraphResult};
80pub use executor::{ExecutionOptions, GraphExecutor, NoPersistence};
81pub use graph::{Graph, SimpleGraph};
82pub use iter::GraphIter;
83pub use iter::StepResult;
84pub use mermaid::{
85    generate_flowchart, generate_mermaid, MermaidBuilder, MermaidDirection, MermaidOptions,
86};
87pub use node::{
88    AgentNode, BaseNode, ConditionalNode, End, FunctionNode, Node, NodeDef, NodeResult, RouterNode,
89};
90pub use persistence::{FilePersistence, InMemoryPersistence, PersistenceError, StatePersistence};
91pub use state::{generate_run_id, GraphRunContext, GraphRunResult, GraphState, PersistableState};
92
93/// Prelude for common imports.
94pub mod prelude {
95    pub use crate::{
96        BaseNode, Edge, End, Graph, GraphError, GraphExecutor, GraphResult, GraphRunContext,
97        GraphRunResult, GraphState, NodeResult,
98    };
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104    use async_trait::async_trait;
105
106    #[derive(Debug, Clone, Default)]
107    struct TestState {
108        value: i32,
109    }
110
111    struct TestNode;
112
113    #[async_trait]
114    impl BaseNode<TestState, (), i32> for TestNode {
115        fn name(&self) -> &str {
116            "test"
117        }
118
119        async fn run(
120            &self,
121            ctx: &mut GraphRunContext<TestState, ()>,
122        ) -> GraphResult<NodeResult<TestState, (), i32>> {
123            ctx.state.value += 1;
124            Ok(NodeResult::end(ctx.state.value))
125        }
126    }
127
128    #[tokio::test]
129    async fn test_simple_graph() {
130        let graph = Graph::new()
131            .node("test", TestNode)
132            .entry("test")
133            .build()
134            .unwrap();
135
136        let result = graph.run(TestState::default(), ()).await.unwrap();
137        assert_eq!(result.result, 1);
138    }
139
140    #[test]
141    fn test_edge_builder() {
142        let edge = EdgeBuilder::<TestState>::from("a").to("b").always();
143
144        assert_eq!(edge.from, "a");
145        assert_eq!(edge.to, "b");
146    }
147
148    #[test]
149    fn test_mermaid_generation() {
150        let diagram = generate_flowchart(
151            &["start", "end"],
152            &[("start", "end", None)],
153            &MermaidOptions::new(),
154        );
155
156        assert!(diagram.contains("flowchart"));
157    }
158}