Skip to main content

reson_agentic/tools/
execution.rs

1//! Tool execution logic
2//!
3//! Handles executing tools from ToolCall objects.
4
5use crate::error::{Error, Result};
6use crate::tools::ToolRegistry;
7use crate::types::ToolCall;
8
9/// Execute a tool from a ToolCall
10///
11/// # Arguments
12/// * `registry` - The tool registry containing registered tools
13/// * `tool_call` - The tool call from the LLM
14///
15/// # Returns
16/// The string result from tool execution
17pub async fn execute_tool(registry: &ToolRegistry, tool_call: &ToolCall) -> Result<String> {
18    let tool = registry
19        .get(&tool_call.tool_name)
20        .ok_or_else(|| Error::NonRetryable(format!("Tool '{}' not found", tool_call.tool_name)))?;
21
22    // Execute the tool (arguments are already in tool_call.args)
23    tool.execute().await
24}
25
26/// Execute multiple tools concurrently
27///
28/// # Arguments
29/// * `registry` - The tool registry containing registered tools
30/// * `tool_calls` - Multiple tool calls from the LLM
31///
32/// # Returns
33/// Results from all tool executions (in order)
34pub async fn execute_tools(
35    registry: &ToolRegistry,
36    tool_calls: &[ToolCall],
37) -> Vec<Result<String>> {
38    // Execute all tools concurrently
39    let futures: Vec<_> = tool_calls
40        .iter()
41        .map(|tc| execute_tool(registry, tc))
42        .collect();
43
44    futures::future::join_all(futures).await
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50    use crate::tools::Tool;
51    use async_trait::async_trait;
52    use futures::future::BoxFuture;
53
54    struct MockTool {
55        name: String,
56    }
57
58    #[async_trait]
59    impl Tool for MockTool {
60        fn tool_name(&self) -> &str {
61            &self.name
62        }
63
64        fn description(&self) -> &str {
65            "Mock tool"
66        }
67
68        fn execute(&self) -> BoxFuture<'_, Result<String>> {
69            Box::pin(async { Ok(format!("executed {}", self.name)) })
70        }
71
72        fn schema(&self, _generator: &dyn crate::schema::SchemaGenerator) -> serde_json::Value {
73            serde_json::json!({"name": self.name})
74        }
75    }
76
77    #[tokio::test]
78    async fn test_execute_tool() {
79        let mut registry = ToolRegistry::new();
80        registry
81            .register(MockTool {
82                name: "test_tool".to_string(),
83            })
84            .unwrap();
85
86        let tool_call = ToolCall {
87            tool_use_id: "call_123".to_string(),
88            tool_name: "test_tool".to_string(),
89            args: serde_json::json!({}),
90            raw_arguments: None,
91            signature: None,
92            tool_obj: None,
93        };
94
95        let result = execute_tool(&registry, &tool_call).await;
96        assert!(result.is_ok());
97        assert_eq!(result.unwrap(), "executed test_tool");
98    }
99
100    #[tokio::test]
101    async fn test_execute_tool_not_found() {
102        let registry = ToolRegistry::new();
103
104        let tool_call = ToolCall {
105            tool_use_id: "call_123".to_string(),
106            tool_name: "nonexistent".to_string(),
107            args: serde_json::json!({}),
108            raw_arguments: None,
109            signature: None,
110            tool_obj: None,
111        };
112
113        let result = execute_tool(&registry, &tool_call).await;
114        assert!(result.is_err());
115        assert!(result.unwrap_err().to_string().contains("not found"));
116    }
117
118    #[tokio::test]
119    async fn test_execute_tools_multiple() {
120        let mut registry = ToolRegistry::new();
121        registry
122            .register(MockTool {
123                name: "tool1".to_string(),
124            })
125            .unwrap();
126        registry
127            .register(MockTool {
128                name: "tool2".to_string(),
129            })
130            .unwrap();
131
132        let tool_calls = vec![
133            ToolCall {
134                tool_use_id: "call_1".to_string(),
135                tool_name: "tool1".to_string(),
136                args: serde_json::json!({}),
137                raw_arguments: None,
138                signature: None,
139                tool_obj: None,
140            },
141            ToolCall {
142                tool_use_id: "call_2".to_string(),
143                tool_name: "tool2".to_string(),
144                args: serde_json::json!({}),
145                raw_arguments: None,
146                signature: None,
147                tool_obj: None,
148            },
149        ];
150
151        let results = execute_tools(&registry, &tool_calls).await;
152        assert_eq!(results.len(), 2);
153        assert!(results[0].is_ok());
154        assert!(results[1].is_ok());
155        assert_eq!(results[0].as_ref().unwrap(), "executed tool1");
156        assert_eq!(results[1].as_ref().unwrap(), "executed tool2");
157    }
158
159    #[tokio::test]
160    async fn test_execute_tools_with_failures() {
161        let mut registry = ToolRegistry::new();
162        registry
163            .register(MockTool {
164                name: "tool1".to_string(),
165            })
166            .unwrap();
167
168        let tool_calls = vec![
169            ToolCall {
170                tool_use_id: "call_1".to_string(),
171                tool_name: "tool1".to_string(),
172                args: serde_json::json!({}),
173                raw_arguments: None,
174                signature: None,
175                tool_obj: None,
176            },
177            ToolCall {
178                tool_use_id: "call_2".to_string(),
179                tool_name: "nonexistent".to_string(),
180                args: serde_json::json!({}),
181                raw_arguments: None,
182                signature: None,
183                tool_obj: None,
184            },
185        ];
186
187        let results = execute_tools(&registry, &tool_calls).await;
188        assert_eq!(results.len(), 2);
189        assert!(results[0].is_ok());
190        assert!(results[1].is_err());
191    }
192}