pub struct Graph<T: State> { /* private fields */ }Expand description
The execution graph for the agent
A graph represents the complete workflow structure:
- Which nodes exist and what they do
- How nodes are connected (edges)
- Which nodes are entry and exit points
- Optional: Loop configurations for repeated execution
§Performance Features
- Adjacency List Cache: Maintains HashMap<NodeId, Vec
> for O(1) edge lookups - Lazy Evaluation: Validation deferred until explicitly called
- Memory Efficient: Single edge vec with indexed access
Implementations§
Source§impl<T: State> Graph<T>
impl<T: State> Graph<T>
Sourcepub fn new() -> Self
pub fn new() -> Self
Create a new empty graph. Cycles are allowed by default.
Call set_allow_cycles(false) to enforce strict DAG mode.
Sourcepub fn with_loops() -> Self
pub fn with_loops() -> Self
Create a new graph that allows loops (cycles)
Use this for workflows where nodes can intentionally feed back to earlier nodes.
Remember to configure set_max_loop_iterations() to prevent infinite loops.
§Example
use flowgentra_ai::core::graph::Graph;
let mut graph = Graph::with_loops();
graph.set_max_loop_iterations(5);
// graph allows cycles up to 5 iterationsSourcepub fn add_node(&mut self, node: Node<T>)
pub fn add_node(&mut self, node: Node<T>)
Add a node to the graph
Nodes should be added before edges are created that reference them.
§Arguments
node- The node to add to the graph
Sourcepub fn add_edge(&mut self, edge: Edge<T>)
pub fn add_edge(&mut self, edge: Edge<T>)
Add an edge to the graph and update adjacency list cache
Both the source and target nodes should already exist in the graph. The adjacency list is automatically updated for O(1) future lookups.
§Arguments
edge- The edge to add to the graph
Sourcepub fn set_start_nodes(&mut self, nodes: Vec<NodeId>)
pub fn set_start_nodes(&mut self, nodes: Vec<NodeId>)
Set the entry nodes (those connected from START)
§Arguments
nodes- Vector of node IDs that act as entry points
Sourcepub fn set_end_nodes(&mut self, nodes: Vec<NodeId>)
pub fn set_end_nodes(&mut self, nodes: Vec<NodeId>)
Set the exit nodes (those connected to END)
§Arguments
nodes- Vector of node IDs that act as exit points
Sourcepub fn set_allow_cycles(&mut self, allow: bool) -> &mut Self
pub fn set_allow_cycles(&mut self, allow: bool) -> &mut Self
Enable or disable loop support (allow cycles in the graph)
When enabled, the graph validation will not enforce DAG property.
You should configure set_max_loop_iterations() to prevent infinite loops.
§Arguments
allow- Whether to allow cycles in the graph
§Returns
&mut Self- For builder pattern chaining
Sourcepub fn allows_cycles(&self) -> bool
pub fn allows_cycles(&self) -> bool
Check if cycles are allowed in this graph
§Returns
trueif the graph allows cycles,falseif it enforces DAG property
Sourcepub fn allow_loops(&mut self, allow: bool) -> &mut Self
👎Deprecated since 0.2.0: Use set_max_loop_iterations instead
pub fn allow_loops(&mut self, allow: bool) -> &mut Self
Use set_max_loop_iterations instead
Set maximum iterations for loops (builder-style, deprecated name)
⚠️ Deprecated: Use set_max_loop_iterations() instead.
This method is kept for backwards compatibility.
Sourcepub fn loops_allowed(&self) -> bool
👎Deprecated since 0.2.0: Use allows_cycles instead
pub fn loops_allowed(&self) -> bool
Use allows_cycles instead
Check if loops are allowed (deprecated name)
⚠️ Deprecated: Use allows_cycles() instead.
This method is kept for backwards compatibility.
Sourcepub fn set_max_loop_iterations(&mut self, max: usize) -> &mut Self
pub fn set_max_loop_iterations(&mut self, max: usize) -> &mut Self
Set maximum iterations for loops
This prevents infinite loops by limiting the number of iterations the runtime will execute for any loop structure.
§Arguments
max- Maximum number of loop iterations (0 = unlimited)
§Returns
&mut Self- For builder pattern chaining
§Example
use flowgentra_ai::core::graph::Graph;
let mut graph = Graph::with_loops();
graph.set_max_loop_iterations(5); // Allow max 5 iterationsSourcepub fn max_loop_iterations(&self) -> usize
pub fn max_loop_iterations(&self) -> usize
Get maximum iterations for loops
Sourcepub fn start_nodes(&self) -> &[NodeId] ⓘ
pub fn start_nodes(&self) -> &[NodeId] ⓘ
Get the entry nodes (START nodes)
Sourcepub fn get_next_nodes(&self, node_id: &str) -> Vec<&Edge<T>>
pub fn get_next_nodes(&self, node_id: &str) -> Vec<&Edge<T>>
Get all outgoing edges from a node (O(1) average case using adjacency list)
§Arguments
node_id- ID of the source node (borrowed reference)
§Returns
- Vector of references to edges originating from this node
§Performance
- Time: O(1) average case + O(k) where k is number of outgoing edges to copy vec refs
- Space: O(k) for the returned vector
§Example
let edges = graph.get_next_nodes("my_node");
for edge in edges {
println!("Can go to: {}", edge.to);
}Sourcepub fn get_reachable_node_ids(&self, from: &str) -> Vec<String>
pub fn get_reachable_node_ids(&self, from: &str) -> Vec<String>
Get the IDs of nodes reachable in one step from the given node
Returns unique target node names from outgoing edges (includes “END” if present).
§Arguments
from- ID of the source node
§Returns
- Vector of unique target node IDs
§Performance
- Time: O(k) where k is the number of outgoing edges
- Space: O(k) for result vector + O(k) for HashSet
Sourcepub fn get_prev_nodes(&self, node_id: &str) -> Vec<&Edge<T>>
pub fn get_prev_nodes(&self, node_id: &str) -> Vec<&Edge<T>>
Get all incoming edges to a node (O(E) linear scan - consider caching if frequently used)
§Arguments
node_id- ID of the target node
§Returns
- Vector of references to edges ending at this node
§Performance
- Time: O(E) where E is total number of edges
- Space: O(k) where k is number of incoming edges
§Note
If this operation is called frequently, consider maintaining a reverse adjacency list.
Sourcepub fn validate(&self) -> Result<()>
pub fn validate(&self) -> Result<()>
Validate the graph structure for correctness
Checks:
- No cycles exist (DAG property) - unless loops are explicitly allowed
- All referenced nodes exist
- START and END nodes are properly connected
- No orphaned nodes
§Errors
Returns FlowgentraError if validation fails with detailed reason
§Performance
- Time: O(V + E) for cycle detection using iterative DFS
- Additional O(E) for edge validation
§Example
let mut graph = Graph::new();
// ... add nodes and edges ...
graph.validate()?; // Will return Err if graph is invalidSourcepub fn topological_sort(&self) -> Result<Vec<NodeId>>
pub fn topological_sort(&self) -> Result<Vec<NodeId>>
Topological sort of the graph
Returns nodes in execution order (respecting dependencies). Useful for analysis, optimization, and execution planning.
§Algorithm
Uses Kahn’s algorithm (BFS-based):
- Compute in-degree for each node
- Enqueue all nodes with in-degree 0
- Dequeue node, add to result, decrement in-degree of neighbors
- Repeat until queue empty
- If result size != nodes size, graph has cycles
§Returns
Ok(Vec<NodeId>)- Nodes in topological orderErr(FlowgentraError::CycleDetected)- If graph has cycles
§Performance
- Time: O(V + E)
- Space: O(V)
§Example
let sorted = graph.topological_sort()?;
println!("Execution order: {:?}", sorted);