Skip to main content

enact_core/callable/
graph.rs

1//! GraphCallable - Wraps CompiledGraph to implement Callable trait
2//!
3//! This allows graphs to be registered in the CallableRegistry and executed
4//! through the unified agent/stream endpoint alongside other callable types.
5
6use super::Callable;
7use crate::graph::CompiledGraph;
8use async_trait::async_trait;
9use std::sync::Arc;
10
11/// A callable backed by a compiled graph
12///
13/// This wrapper allows `CompiledGraph` to be used as a `Callable`, enabling
14/// graphs to be registered in the `CallableRegistry` and executed through
15/// the unified agent/stream endpoint.
16pub struct GraphCallable {
17    name: String,
18    graph: Arc<CompiledGraph>,
19}
20
21impl GraphCallable {
22    /// Create a new GraphCallable
23    ///
24    /// # Arguments
25    /// * `name` - The name of this callable
26    /// * `graph` - The compiled graph to wrap
27    pub fn new(name: impl Into<String>, graph: Arc<CompiledGraph>) -> Self {
28        Self {
29            name: name.into(),
30            graph,
31        }
32    }
33
34    /// Get a reference to the underlying graph
35    pub fn graph(&self) -> &CompiledGraph {
36        &self.graph
37    }
38}
39
40#[async_trait]
41impl Callable for GraphCallable {
42    fn name(&self) -> &str {
43        &self.name
44    }
45
46    fn description(&self) -> Option<&str> {
47        None // Could be extended to store description
48    }
49
50    async fn run(&self, input: &str) -> anyhow::Result<String> {
51        // Run the graph with the input
52        let state = self.graph.run(input).await?;
53
54        // Convert NodeState to String
55        // NodeState contains a serde_json::Value, so we serialize it
56        if let Some(s) = state.as_str() {
57            Ok(s.to_string())
58        } else {
59            // If not a string, serialize the JSON value
60            Ok(serde_json::to_string(&state.data)?)
61        }
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68    use crate::graph::CompiledGraph;
69    use std::collections::HashMap;
70
71    #[tokio::test]
72    async fn test_graph_callable_name() {
73        // Create a minimal graph for testing
74        let graph = Arc::new(CompiledGraph {
75            nodes: HashMap::new(),
76            edges: vec![],
77            conditional_edges: vec![],
78            entry_point: "start".to_string(),
79        });
80
81        let callable = GraphCallable::new("test-graph", graph);
82        assert_eq!(callable.name(), "test-graph");
83    }
84
85    #[tokio::test]
86    async fn test_graph_callable_run() {
87        // Create a minimal graph that just returns the input
88        // Note: This is a simplified test - a real graph would have nodes
89        let graph = Arc::new(CompiledGraph {
90            nodes: HashMap::new(),
91            edges: vec![],
92            conditional_edges: vec![],
93            entry_point: "start".to_string(),
94        });
95
96        let callable = GraphCallable::new("test-graph", graph);
97
98        // The graph will fail because it has no nodes, but we can test the structure
99        let result = callable.run("test input").await;
100        // This will fail because the graph has no nodes, but that's expected
101        assert!(result.is_err());
102    }
103}