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}