1use std::{
4 sync::Arc,
5 time::{Duration, Instant},
6};
7
8use dashmap::DashMap;
9use serde::{Deserialize, Serialize};
10use tokio::{task::JoinSet, time::timeout};
11use tracing::warn;
12
13use super::{
14 cache::{CacheKey, ToolCallCache},
15 core::ToolHandler,
16};
17use crate::{
18 model::{
19 chat_base_response::ToolCallMessage,
20 chat_message_types::TextMessage,
21 tools::{Function, Tools},
22 },
23 toolkits::{
24 core::DynTool,
25 error::{ToolError, ToolResult, error_context},
26 },
27};
28
29#[derive(Debug, Clone)]
31pub struct RetryConfig {
32 pub max_retries: u32,
33 pub initial_delay: Duration,
34 pub max_delay: Duration,
35 pub backoff_multiplier: f64,
36}
37
38impl Default for RetryConfig {
39 fn default() -> Self {
40 Self {
41 max_retries: 3,
42 initial_delay: Duration::from_millis(100),
43 max_delay: Duration::from_secs(30),
44 backoff_multiplier: 2.0,
45 }
46 }
47}
48
49impl RetryConfig {
50 pub fn calculate_delay(&self, attempt: u32) -> Duration {
51 if attempt == 0 {
52 return Duration::ZERO;
53 }
54
55 let delay_ms = self.initial_delay.as_millis() as f64
56 * self.backoff_multiplier.powi((attempt - 1) as i32);
57 let delay_ms = delay_ms.min(self.max_delay.as_millis() as f64) as u64;
58
59 Duration::from_millis(delay_ms)
60 }
61}
62
63#[derive(Debug, Clone)]
65pub struct ExecutionConfig {
66 pub timeout: Option<Duration>,
67 pub retry_config: RetryConfig,
68 pub validate_parameters: bool,
69 pub enable_logging: bool,
70}
71
72impl Default for ExecutionConfig {
73 fn default() -> Self {
74 Self {
75 timeout: Some(Duration::from_secs(30)),
76 retry_config: RetryConfig::default(),
77 validate_parameters: true,
78 enable_logging: false,
79 }
80 }
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct ExecutionResult {
86 pub tool_name: String,
87 pub result: serde_json::Value,
88 pub duration: Duration,
89 pub success: bool,
90 pub error: Option<String>,
91 pub retries: u32,
92 pub timestamp: std::time::SystemTime,
93 pub metadata: std::collections::HashMap<String, serde_json::Value>,
94}
95
96impl ExecutionResult {
97 pub fn success(
98 tool_name: String,
99 result: serde_json::Value,
100 duration: Duration,
101 retries: u32,
102 ) -> Self {
103 Self {
104 tool_name,
105 result,
106 duration,
107 success: true,
108 error: None,
109 retries,
110 timestamp: std::time::SystemTime::now(),
111 metadata: std::collections::HashMap::new(),
112 }
113 }
114
115 pub fn failure(tool_name: String, error: String, duration: Duration, retries: u32) -> Self {
116 Self {
117 tool_name,
118 result: serde_json::Value::Null,
119 duration,
120 success: false,
121 error: Some(error),
122 retries,
123 timestamp: std::time::SystemTime::now(),
124 metadata: std::collections::HashMap::new(),
125 }
126 }
127
128 pub fn with_metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
129 self.metadata.insert(key.into(), value);
130 self
131 }
132}
133
134#[derive(Clone)]
136pub struct ToolExecutor {
137 tools: Arc<DashMap<String, Arc<dyn DynTool>>>,
138 config: ExecutionConfig,
139 cache: ToolCallCache,
140}
141
142impl std::fmt::Debug for ToolExecutor {
143 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144 let tool_count = self.tools.len();
145 let cache_enabled = self.cache.stats().total_entries > 0;
146 f.debug_struct("ToolExecutor")
147 .field("tool_count", &tool_count)
148 .field("config", &self.config)
149 .field("cache_enabled", &cache_enabled)
150 .finish()
151 }
152}
153
154impl Default for ToolExecutor {
155 fn default() -> Self {
156 Self::new()
157 }
158}
159
160impl ToolExecutor {
161 pub fn new() -> Self {
163 Self {
164 tools: Arc::new(DashMap::new()),
165 config: ExecutionConfig::default(),
166 cache: ToolCallCache::new(),
167 }
168 }
169
170 pub fn builder() -> ExecutorBuilder {
172 ExecutorBuilder::new()
173 }
174
175 pub fn with_cache_enabled(mut self, enabled: bool) -> Self {
177 self.cache = self.cache.with_enabled(enabled);
178 self
179 }
180
181 pub fn with_cache_ttl(mut self, ttl: Duration) -> Self {
183 self.cache = self.cache.with_ttl(ttl);
184 self
185 }
186
187 pub fn with_cache_max_size(mut self, size: usize) -> Self {
189 self.cache = self.cache.with_max_size(size);
190 self
191 }
192
193 pub fn clear_cache(&self) {
195 self.cache.clear();
196 }
197
198 pub fn invalidate_cache_for_tool(&self, tool_name: &str) {
200 self.cache.invalidate_tool(tool_name);
201 }
202
203 pub fn cache_stats(&self) -> super::cache::CacheStats {
205 self.cache.stats()
206 }
207
208 pub fn add_dyn_tool(&self, tool: Box<dyn DynTool>) -> ToolResult<&Self> {
210 let name = tool.name().to_string();
211 if self.tools.contains_key(&name) {
212 return Err(ToolError::RegistrationError {
213 message: format!("Tool '{}' is already registered", name).into(),
214 });
215 }
216 self.tools.insert(name, Arc::from(tool));
217 Ok(self)
218 }
219
220 pub fn try_add_dyn_tool(&self, tool: Box<dyn DynTool>) -> &Self {
222 let name = tool.name().to_string();
223 self.tools.entry(name).or_insert_with(|| Arc::from(tool));
224 self
225 }
226
227 pub fn unregister(&self, name: &str) -> ToolResult<()> {
229 if self.tools.remove(name).is_none() {
230 return Err(error_context().tool_not_found());
231 }
232 Ok(())
233 }
234
235 pub fn input_schema(&self, name: &str) -> Option<serde_json::Value> {
237 self.tools.get(name).map(|t| t.input_schema())
238 }
239
240 pub fn has_tool(&self, name: &str) -> bool {
242 self.tools.contains_key(name)
243 }
244
245 pub fn tool_names(&self) -> Vec<String> {
247 self.tools.iter().map(|entry| entry.key().clone()).collect()
248 }
249
250 fn get_tool(&self, name: &str) -> Option<Arc<dyn DynTool>> {
251 self.tools.get(name).map(|t| Arc::clone(t.value()))
252 }
253
254 #[tracing::instrument(skip(self, input))]
256 pub async fn execute(
257 &self,
258 tool_name: &str,
259 input: serde_json::Value,
260 ) -> ToolResult<ExecutionResult> {
261 let start_time = Instant::now();
262 let mut retries = 0;
263 let retry_config = &self.config.retry_config;
264
265 let cache_key = CacheKey::new(tool_name.to_string(), input.clone());
267 if let Some(cached_result) = self.cache.get(&cache_key) {
268 let duration = start_time.elapsed();
269 return Ok(ExecutionResult::success(
270 tool_name.to_string(),
271 cached_result,
272 duration,
273 retries,
274 )
275 .with_metadata("cache_hit", serde_json::Value::Bool(true)));
276 }
277
278 loop {
279 match self.execute_once(tool_name, &input).await {
280 Ok(result) => {
281 let duration = start_time.elapsed();
282 self.cache.insert(cache_key, result.clone(), None);
284
285 return Ok(ExecutionResult::success(
286 tool_name.to_string(),
287 result,
288 duration,
289 retries,
290 )
291 .with_metadata("cache_hit", serde_json::Value::Bool(false)));
292 },
293 Err(error) => {
294 if !error.is_retryable() {
296 let duration = start_time.elapsed();
297 return Ok(ExecutionResult::failure(
298 tool_name.to_string(),
299 error.to_string(),
300 duration,
301 retries,
302 ));
303 }
304
305 if retries >= retry_config.max_retries {
306 let duration = start_time.elapsed();
307 return Ok(ExecutionResult::failure(
308 tool_name.to_string(),
309 error.to_string(),
310 duration,
311 retries,
312 ));
313 }
314
315 retries += 1;
316
317 warn!(attempt = retries, error = %error, "Tool execution failed, retrying");
318
319 let delay = retry_config.calculate_delay(retries);
321 tokio::time::sleep(delay).await;
322 },
323 }
324 }
325 }
326
327 pub async fn execute_simple(
329 &self,
330 tool_name: &str,
331 input: serde_json::Value,
332 ) -> ToolResult<serde_json::Value> {
333 let result = self.execute(tool_name, input).await?;
334 if result.success {
335 Ok(result.result)
336 } else {
337 Err(error_context()
338 .with_tool(tool_name)
339 .execution_failed(result.error.unwrap_or_else(|| "Unknown error".to_string())))
340 }
341 }
342
343 pub fn add_functions_from_dir_with_registry(
356 &self,
357 dir: impl AsRef<std::path::Path>,
358 handlers: &std::collections::HashMap<String, ToolHandler>,
359 strict: bool,
360 ) -> ToolResult<Vec<String>> {
361 use std::fs;
362
363 use serde_json::Value;
364 let dir = dir.as_ref();
365 let mut added = Vec::new();
366 let read_dir = fs::read_dir(dir).map_err(|e| {
367 error_context().invalid_parameters(format!(
368 "Failed to read dir {}: {}",
369 dir.display(),
370 e
371 ))
372 })?;
373 for entry in read_dir {
374 let entry = match entry {
375 Ok(e) => e,
376 Err(e) => {
377 return Err(
378 error_context().invalid_parameters(format!("Dir entry error: {}", e))
379 );
380 },
381 };
382 let path = entry.path();
383 if !path.is_file() {
384 continue;
385 }
386 if path.extension().and_then(|s| s.to_str()) != Some("json") {
387 continue;
388 }
389 let content = fs::read_to_string(&path).map_err(|e| {
390 error_context().invalid_parameters(format!(
391 "Failed to read {}: {}",
392 path.display(),
393 e
394 ))
395 })?;
396 let spec: Value = serde_json::from_str(&content).map_err(|e| {
397 error_context().invalid_parameters(format!(
398 "Invalid JSON in {}: {}",
399 path.display(),
400 e
401 ))
402 })?;
403
404 let (name, description, parameters) =
406 crate::toolkits::core::parse_function_spec_details(&spec).map_err(|e| {
407 error_context().invalid_parameters(format!(
408 "Failed to parse spec {}: {}",
409 path.display(),
410 e
411 ))
412 })?;
413
414 let handler = match handlers.get(&name) {
415 Some(h) => h.clone(),
416 None => {
417 if strict {
418 return Err(error_context().invalid_parameters(format!(
419 "No handler registered for function '{}' (file {})",
420 name,
421 path.display()
422 )));
423 } else {
424 continue;
426 }
427 },
428 };
429
430 let mut builder =
433 crate::toolkits::core::FunctionTool::builder(name.clone(), description);
434 if let Some(p) = parameters {
435 builder = builder.schema(p);
436 }
437 let tool = builder
438 .handler(move |args| {
439 let h = handler.clone();
440 h(args)
441 })
442 .build()?;
443
444 self.add_dyn_tool(Box::new(tool))?;
445 added.push(name);
446 }
447 Ok(added)
448 }
449
450 async fn execute_single_tool_call(&self, tc: &ToolCallMessage) -> TextMessage {
466 let id_opt = tc.id().map(|s| s.to_string());
467 let func_opt = tc.function();
468
469 if let Some(func) = func_opt {
470 let name = func.name().unwrap_or("").to_string();
471 let args_str = func.arguments().unwrap_or("{}");
472 let args_json: serde_json::Value = serde_json::from_str(args_str)
473 .unwrap_or_else(|_| serde_json::json!({ "_raw": args_str }));
474
475 let content_json = match self.execute_simple(&name, args_json).await {
476 Ok(v) => v,
477 Err(err) => serde_json::json!({
478 "error": { "type": "execution_failed", "message": err.to_string() }
479 }),
480 };
481
482 let s = serde_json::to_string(&content_json).unwrap_or_else(|_| "{}".to_string());
483
484 if let Some(id) = id_opt {
485 TextMessage::tool_with_id(s, id)
486 } else {
487 TextMessage::tool(s)
488 }
489 } else {
490 let s = serde_json::json!({
491 "error": { "type": "missing_function", "message": "tool_call.function is missing" }
492 })
493 .to_string();
494
495 if let Some(id) = id_opt {
496 TextMessage::tool_with_id(s, id)
497 } else {
498 TextMessage::tool(s)
499 }
500 }
501 }
502
503 pub async fn execute_tool_calls_parallel(&self, calls: &[ToolCallMessage]) -> Vec<TextMessage> {
504 let mut set = JoinSet::new();
505
506 let calls_vec = calls.to_vec();
508 for tc in calls_vec {
509 let this = self.clone();
510 set.spawn(async move { this.execute_single_tool_call(&tc).await });
511 }
512
513 let mut messages = Vec::with_capacity(calls.len());
514 while let Some(res) = set.join_next().await {
515 if let Ok(msg) = res {
516 messages.push(msg);
517 }
518 }
519 messages
520 }
521
522 pub async fn execute_tool_calls_ordered(&self, calls: &[ToolCallMessage]) -> Vec<TextMessage> {
541 use futures::future::join_all;
542
543 let calls_vec = calls.to_vec();
544 let futures: Vec<_> = calls_vec
545 .into_iter()
546 .map(|tc| {
547 let this = self.clone();
548 async move { this.execute_single_tool_call(&tc).await }
549 })
550 .collect();
551
552 join_all(futures).await
553 }
554
555 pub fn export_tool_as_function(&self, name: &str) -> Option<Tools> {
558 let tool = self.tools.get(name)?;
559 let meta = tool.metadata();
560 let schema = tool.input_schema();
561 let func = Function::new(meta.name.clone(), meta.description.clone(), schema);
562 Some(Tools::Function { function: func })
563 }
564
565 pub fn export_all_tools_as_functions(&self) -> Vec<Tools> {
567 self.tools
568 .iter()
569 .map(|entry| {
570 let tool = entry.value();
571 let meta = tool.metadata();
572 let schema = tool.input_schema();
573 let func = Function::new(meta.name.clone(), meta.description.clone(), schema);
574 Tools::Function { function: func }
575 })
576 .collect()
577 }
578 pub fn export_tools_filtered<F>(&self, mut filter: F) -> Vec<Tools>
580 where
581 F: FnMut(&crate::toolkits::core::ToolMetadata) -> bool,
582 {
583 self.tools
584 .iter()
585 .filter(|entry| filter(entry.value().metadata()))
586 .map(|entry| {
587 let tool = entry.value();
588 let meta = tool.metadata();
589 let schema = tool.input_schema();
590 let func = Function::new(meta.name.clone(), meta.description.clone(), schema);
591 Tools::Function { function: func }
592 })
593 .collect()
594 }
595
596 #[tracing::instrument(skip(self, input))]
597 async fn execute_once(
598 &self,
599 tool_name: &str,
600 input: &serde_json::Value,
601 ) -> ToolResult<serde_json::Value> {
602 let tool = self
603 .get_tool(tool_name)
604 .ok_or_else(|| error_context().with_tool(tool_name).tool_not_found())?;
605 let execution_future = tool.execute_json(input.clone());
606
607 match self.config.timeout {
608 Some(timeout_duration) => match timeout(timeout_duration, execution_future).await {
609 Ok(result) => result,
610 Err(_) => Err(error_context()
611 .with_tool(tool_name)
612 .timeout_error(timeout_duration)),
613 },
614 None => execution_future.await,
615 }
616 }
617
618 pub fn config(&self) -> &ExecutionConfig {
620 &self.config
621 }
622}
623
624pub struct ExecutorBuilder {
626 config: ExecutionConfig,
627 cache_config: Option<CacheConfig>,
628}
629
630#[derive(Clone)]
631struct CacheConfig {
632 enabled: bool,
633 ttl: Duration,
634 max_size: usize,
635}
636
637impl Default for ExecutorBuilder {
638 fn default() -> Self {
639 Self::new()
640 }
641}
642
643impl ExecutorBuilder {
644 pub fn new() -> Self {
646 Self {
647 config: ExecutionConfig::default(),
648 cache_config: None,
649 }
650 }
651
652 pub fn timeout(mut self, timeout: Duration) -> Self {
654 self.config.timeout = Some(timeout);
655 self
656 }
657
658 pub fn retries(mut self, retries: u32) -> Self {
660 self.config.retry_config.max_retries = retries;
661 self
662 }
663
664 pub fn logging(mut self, enabled: bool) -> Self {
666 self.config.enable_logging = enabled;
667 self
668 }
669
670 pub fn enable_cache(mut self) -> Self {
672 self.cache_config
673 .get_or_insert(CacheConfig {
674 enabled: true,
675 ttl: Duration::from_secs(300),
676 max_size: 1000,
677 })
678 .enabled = true;
679 self
680 }
681
682 pub fn disable_cache(mut self) -> Self {
684 self.cache_config
685 .get_or_insert(CacheConfig {
686 enabled: false,
687 ttl: Duration::from_secs(300),
688 max_size: 1000,
689 })
690 .enabled = false;
691 self
692 }
693
694 pub fn cache_ttl(mut self, ttl: Duration) -> Self {
696 let cfg = self.cache_config.get_or_insert(CacheConfig {
697 enabled: true,
698 ttl: Duration::from_secs(300),
699 max_size: 1000,
700 });
701 cfg.ttl = ttl;
702 self
703 }
704
705 pub fn cache_max_size(mut self, size: usize) -> Self {
707 let cfg = self.cache_config.get_or_insert(CacheConfig {
708 enabled: true,
709 ttl: Duration::from_secs(300),
710 max_size: 1000,
711 });
712 cfg.max_size = size;
713 self
714 }
715
716 pub fn build(self) -> ToolExecutor {
718 let cache = match self.cache_config {
719 Some(cfg) => ToolCallCache::new()
720 .with_enabled(cfg.enabled)
721 .with_ttl(cfg.ttl)
722 .with_max_size(cfg.max_size),
723 None => ToolCallCache::new(),
724 };
725
726 ToolExecutor {
727 tools: Arc::new(DashMap::new()),
728 config: self.config,
729 cache,
730 }
731 }
732}
733
734#[cfg(test)]
735mod tests {
736 use super::*;
737 use crate::toolkits::core::FunctionTool;
738
739 #[test]
740 fn test_retry_config_default() {
741 let config = RetryConfig::default();
742 assert_eq!(config.max_retries, 3);
743 assert_eq!(config.initial_delay, Duration::from_millis(100));
744 assert_eq!(config.max_delay, Duration::from_secs(30));
745 assert_eq!(config.backoff_multiplier, 2.0);
746 }
747
748 #[test]
749 fn test_retry_config_calculate_delay() {
750 let config = RetryConfig::default();
751
752 assert_eq!(config.calculate_delay(0), Duration::ZERO);
754
755 assert_eq!(config.calculate_delay(1), Duration::from_millis(100));
757
758 assert_eq!(config.calculate_delay(2), Duration::from_millis(200));
760
761 assert_eq!(config.calculate_delay(3), Duration::from_millis(400));
763
764 let config = RetryConfig {
766 max_retries: 10,
767 initial_delay: Duration::from_millis(500),
768 max_delay: Duration::from_secs(1),
769 backoff_multiplier: 3.0,
770 };
771 assert_eq!(config.calculate_delay(1), Duration::from_millis(500));
773 assert_eq!(config.calculate_delay(2), Duration::from_secs(1));
774 assert_eq!(config.calculate_delay(3), Duration::from_secs(1));
775 }
776
777 #[test]
778 fn test_execution_config_default() {
779 let config = ExecutionConfig::default();
780 assert_eq!(config.timeout, Some(Duration::from_secs(30)));
781 assert!(config.validate_parameters);
782 assert!(!config.enable_logging);
783 assert_eq!(config.retry_config.max_retries, 3);
784 }
785
786 #[test]
787 fn test_execution_result_success() {
788 let result = ExecutionResult::success(
789 "test_tool".to_string(),
790 serde_json::json!({"value": 42}),
791 Duration::from_millis(100),
792 2,
793 );
794
795 assert_eq!(result.tool_name, "test_tool");
796 assert_eq!(result.result, serde_json::json!({"value": 42}));
797 assert_eq!(result.duration, Duration::from_millis(100));
798 assert!(result.success);
799 assert!(result.error.is_none());
800 assert_eq!(result.retries, 2);
801 assert!(result.metadata.is_empty());
802 }
803
804 #[test]
805 fn test_execution_result_failure() {
806 let result = ExecutionResult::failure(
807 "test_tool".to_string(),
808 "Something went wrong".to_string(),
809 Duration::from_millis(50),
810 1,
811 );
812
813 assert_eq!(result.tool_name, "test_tool");
814 assert_eq!(result.result, serde_json::Value::Null);
815 assert_eq!(result.duration, Duration::from_millis(50));
816 assert!(!result.success);
817 assert_eq!(result.error, Some("Something went wrong".to_string()));
818 assert_eq!(result.retries, 1);
819 assert!(result.metadata.is_empty());
820 }
821
822 #[test]
823 fn test_execution_result_with_metadata() {
824 let mut result = ExecutionResult::success(
825 "test_tool".to_string(),
826 serde_json::json!({"value": 42}),
827 Duration::from_millis(100),
828 0,
829 );
830
831 result = result.with_metadata("key1", serde_json::json!("value1"));
832 result = result.with_metadata("key2", serde_json::json!({"nested": true}));
833
834 assert_eq!(result.metadata.len(), 2);
835 assert_eq!(
836 result.metadata.get("key1"),
837 Some(&serde_json::json!("value1"))
838 );
839 assert_eq!(
840 result.metadata.get("key2"),
841 Some(&serde_json::json!({"nested": true}))
842 );
843 }
844
845 #[test]
846 fn test_execution_result_serialization() {
847 let result = ExecutionResult::success(
848 "test_tool".to_string(),
849 serde_json::json!({"value": 42}),
850 Duration::from_millis(100),
851 0,
852 );
853
854 let json = serde_json::to_string(&result).unwrap();
855 assert!(json.contains("\"tool_name\":\"test_tool\""));
856 assert!(json.contains("\"success\":true"));
857 assert!(json.contains("\"value\":42"));
858 }
859
860 #[test]
861 fn test_tool_executor_default() {
862 let executor = ToolExecutor::new();
863 assert_eq!(executor.tool_names().len(), 0);
864 assert_eq!(executor.config.timeout, Some(Duration::from_secs(30)));
865 }
866
867 #[test]
868 fn test_tool_executor_register_and_unregister() {
869 let executor = ToolExecutor::new();
870
871 let tool = FunctionTool::builder("test_tool", "A test tool")
873 .handler(|_args| async move { Ok(serde_json::json!({"result": "success"})) })
874 .build()
875 .unwrap();
876
877 executor.add_dyn_tool(Box::new(tool)).unwrap();
879 assert_eq!(executor.tool_names().len(), 1);
880 assert!(executor.has_tool("test_tool"));
881
882 assert!(executor.unregister("test_tool").is_ok());
884 assert_eq!(executor.tool_names().len(), 0);
885 assert!(!executor.has_tool("test_tool"));
886 }
887
888 #[test]
889 fn test_tool_executor_duplicate_tool_returns_error() {
890 let executor = ToolExecutor::new();
891
892 let tool1 = FunctionTool::builder("duplicate_tool", "First tool")
893 .handler(|_args| async move { Ok(serde_json::json!({})) })
894 .build()
895 .unwrap();
896
897 let tool2 = FunctionTool::builder("duplicate_tool", "Second tool")
898 .handler(|_args| async move { Ok(serde_json::json!({})) })
899 .build()
900 .unwrap();
901
902 executor.add_dyn_tool(Box::new(tool1)).unwrap();
903
904 let result = executor.add_dyn_tool(Box::new(tool2));
906 assert!(result.is_err());
907 }
908
909 #[test]
910 fn test_tool_executor_try_add_dyn_tool() {
911 let executor = ToolExecutor::new();
912
913 let tool1 = FunctionTool::builder("test_tool", "First tool")
914 .handler(|_args| async move { Ok(serde_json::json!({})) })
915 .build()
916 .unwrap();
917
918 let tool2 = FunctionTool::builder("test_tool", "Second tool")
919 .handler(|_args| async move { Ok(serde_json::json!({})) })
920 .build()
921 .unwrap();
922
923 executor.try_add_dyn_tool(Box::new(tool1));
924 executor.try_add_dyn_tool(Box::new(tool2));
925
926 assert_eq!(executor.tool_names().len(), 1);
928 assert!(executor.has_tool("test_tool"));
929 }
930
931 #[test]
932 fn test_tool_executor_unregister_nonexistent_tool() {
933 let executor = ToolExecutor::new();
934 let result = executor.unregister("nonexistent_tool");
935 assert!(result.is_err());
936 }
937
938 #[test]
939 fn test_tool_executor_input_schema() {
940 let executor = ToolExecutor::new();
941
942 let schema = serde_json::json!({
943 "type": "object",
944 "properties": {
945 "name": {"type": "string"}
946 }
947 });
948
949 let tool = FunctionTool::builder("test_tool", "A test tool")
950 .schema(schema.clone())
951 .handler(|_args| async move { Ok(serde_json::json!({})) })
952 .build()
953 .unwrap();
954
955 executor.add_dyn_tool(Box::new(tool)).unwrap();
956
957 let retrieved_schema = executor.input_schema("test_tool");
958 assert!(retrieved_schema.is_some());
959 let retrieved = retrieved_schema.unwrap();
960
961 assert_eq!(retrieved["type"], "object");
963 assert_eq!(retrieved["properties"]["name"]["type"], "string");
964 assert_eq!(retrieved["additionalProperties"], false);
966 }
967
968 #[test]
969 fn test_tool_executor_input_schema_nonexistent() {
970 let executor = ToolExecutor::new();
971 let schema = executor.input_schema("nonexistent");
972 assert!(schema.is_none());
973 }
974
975 #[test]
976 fn test_tool_executor_tool_names() {
977 let executor = ToolExecutor::new();
978
979 let tool1 = FunctionTool::builder("tool1", "First tool")
980 .handler(|_args| async move { Ok(serde_json::json!({})) })
981 .build()
982 .unwrap();
983
984 let tool2 = FunctionTool::builder("tool2", "Second tool")
985 .handler(|_args| async move { Ok(serde_json::json!({})) })
986 .build()
987 .unwrap();
988
989 let tool3 = FunctionTool::builder("tool3", "Third tool")
990 .handler(|_args| async move { Ok(serde_json::json!({})) })
991 .build()
992 .unwrap();
993
994 executor.add_dyn_tool(Box::new(tool1)).unwrap();
995 executor.add_dyn_tool(Box::new(tool2)).unwrap();
996 executor.add_dyn_tool(Box::new(tool3)).unwrap();
997
998 let names = executor.tool_names();
999 assert_eq!(names.len(), 3);
1000 assert!(names.contains(&"tool1".to_string()));
1001 assert!(names.contains(&"tool2".to_string()));
1002 assert!(names.contains(&"tool3".to_string()));
1003 }
1004
1005 #[tokio::test]
1006 async fn test_tool_executor_execute_success() {
1007 let executor = ToolExecutor::new();
1008
1009 let tool = FunctionTool::builder("add_tool", "Add two numbers")
1010 .property("a", serde_json::json!({"type": "number"}))
1011 .property("b", serde_json::json!({"type": "number"}))
1012 .handler(|args| async move {
1013 let a = args.get("a").and_then(|v| v.as_i64()).unwrap_or(0);
1014 let b = args.get("b").and_then(|v| v.as_i64()).unwrap_or(0);
1015 Ok(serde_json::json!({"result": a + b}))
1016 })
1017 .build()
1018 .unwrap();
1019
1020 executor.add_dyn_tool(Box::new(tool)).unwrap();
1021
1022 let input = serde_json::json!({"a": 5, "b": 3});
1023 let result = executor.execute("add_tool", input).await.unwrap();
1024
1025 assert!(result.success);
1026 assert_eq!(result.tool_name, "add_tool");
1027 assert_eq!(result.result, serde_json::json!({"result": 8}));
1028 assert_eq!(result.retries, 0);
1029 }
1030
1031 #[tokio::test]
1032 async fn test_tool_executor_execute_failure() {
1033 let executor = ToolExecutor::new();
1034
1035 let tool = FunctionTool::builder("failing_tool", "Always fails")
1036 .handler(|_args| async move {
1037 Err(error_context()
1038 .with_tool("failing_tool")
1039 .execution_failed("Intentional failure"))
1040 })
1041 .build()
1042 .unwrap();
1043
1044 executor.add_dyn_tool(Box::new(tool)).unwrap();
1045
1046 let input = serde_json::json!({});
1047 let result = executor.execute("failing_tool", input).await.unwrap();
1048
1049 assert!(!result.success);
1050 assert_eq!(result.tool_name, "failing_tool");
1051 assert!(result.error.is_some());
1052 }
1053
1054 #[tokio::test]
1055 async fn test_tool_executor_execute_nonexistent_tool() {
1056 let executor = ToolExecutor::new();
1057 let input = serde_json::json!({});
1058 let result = executor.execute("nonexistent_tool", input).await.unwrap();
1059
1060 assert!(!result.success);
1061 assert!(result.error.is_some());
1062 }
1063
1064 #[tokio::test]
1065 async fn test_tool_executor_execute_simple_success() {
1066 let executor = ToolExecutor::new();
1067
1068 let tool = FunctionTool::builder("echo_tool", "Echo input")
1069 .property("message", serde_json::json!({"type": "string"}))
1070 .handler(|args| async move { Ok(args) })
1071 .build()
1072 .unwrap();
1073
1074 executor.add_dyn_tool(Box::new(tool)).unwrap();
1075
1076 let input = serde_json::json!({"message": "hello"});
1077 let result = executor.execute_simple("echo_tool", input).await.unwrap();
1078
1079 assert_eq!(result, serde_json::json!({"message": "hello"}));
1080 }
1081
1082 #[tokio::test]
1083 async fn test_tool_executor_execute_simple_failure() {
1084 let executor = ToolExecutor::new();
1085
1086 let tool = FunctionTool::builder("failing_tool", "Always fails")
1087 .handler(|_args| async move {
1088 Err(error_context()
1089 .with_tool("failing_tool")
1090 .execution_failed("Intentional failure"))
1091 })
1092 .build()
1093 .unwrap();
1094
1095 executor.add_dyn_tool(Box::new(tool)).unwrap();
1096
1097 let input = serde_json::json!({});
1098 let result = executor.execute_simple("failing_tool", input).await;
1099
1100 assert!(result.is_err());
1101 }
1102
1103 #[tokio::test]
1104 async fn test_tool_executor_timeout() {
1105 let executor = ToolExecutor::builder()
1106 .timeout(Duration::from_millis(100))
1107 .build();
1108
1109 let tool = FunctionTool::builder("slow_tool", "Slow tool")
1110 .handler(|_args| async move {
1111 tokio::time::sleep(Duration::from_secs(1)).await;
1112 Ok(serde_json::json!({"done": true}))
1113 })
1114 .build()
1115 .unwrap();
1116
1117 executor.add_dyn_tool(Box::new(tool)).unwrap();
1118
1119 let input = serde_json::json!({});
1120 let result = executor.execute("slow_tool", input).await.unwrap();
1121
1122 assert!(!result.success);
1123 assert!(result.error.is_some());
1124 assert!(result.error.unwrap().contains("Timeout"));
1125 }
1126
1127 #[tokio::test]
1128 async fn test_tool_executor_retry() {
1129 let executor = ToolExecutor::builder()
1130 .retries(2)
1131 .timeout(Duration::from_secs(30))
1132 .build();
1133
1134 let attempt_counter = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
1135 let counter_clone = attempt_counter.clone();
1136
1137 let tool = FunctionTool::builder("flaky_tool", "Flaky tool")
1138 .handler(move |_args| {
1139 let counter = counter_clone.clone();
1140 async move {
1141 let attempts = counter.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
1142 if attempts < 2 {
1143 Err(error_context()
1144 .with_tool("flaky_tool")
1145 .execution_failed("Temporary failure"))
1146 } else {
1147 Ok(serde_json::json!({"attempts": attempts + 1}))
1148 }
1149 }
1150 })
1151 .build()
1152 .unwrap();
1153
1154 executor.add_dyn_tool(Box::new(tool)).unwrap();
1155
1156 let input = serde_json::json!({});
1157 let result = executor.execute("flaky_tool", input).await.unwrap();
1158
1159 assert!(result.success);
1160 assert_eq!(result.retries, 2);
1161 }
1162
1163 #[test]
1164 fn test_executor_builder_default() {
1165 let builder = ExecutorBuilder::new();
1166 assert_eq!(builder.config.timeout, Some(Duration::from_secs(30)));
1167 assert_eq!(builder.config.retry_config.max_retries, 3);
1168 }
1169
1170 #[test]
1171 fn test_executor_builder_timeout() {
1172 let builder = ExecutorBuilder::new().timeout(Duration::from_secs(60));
1173 assert_eq!(builder.config.timeout, Some(Duration::from_secs(60)));
1174 }
1175
1176 #[test]
1177 fn test_executor_builder_retries() {
1178 let builder = ExecutorBuilder::new().retries(5);
1179 assert_eq!(builder.config.retry_config.max_retries, 5);
1180 }
1181
1182 #[test]
1183 fn test_executor_builder_logging() {
1184 let builder = ExecutorBuilder::new().logging(true);
1185 assert!(builder.config.enable_logging);
1186 }
1187
1188 #[test]
1189 fn test_executor_builder_build() {
1190 let executor = ExecutorBuilder::new()
1191 .timeout(Duration::from_secs(60))
1192 .retries(5)
1193 .logging(true)
1194 .build();
1195
1196 assert_eq!(executor.config.timeout, Some(Duration::from_secs(60)));
1197 assert_eq!(executor.config.retry_config.max_retries, 5);
1198 assert!(executor.config.enable_logging);
1199 }
1200
1201 #[test]
1202 fn test_executor_builder_chainable() {
1203 let builder = ExecutorBuilder::new()
1204 .timeout(Duration::from_secs(45))
1205 .retries(3)
1206 .logging(false)
1207 .timeout(Duration::from_secs(50))
1208 .retries(4)
1209 .logging(true);
1210
1211 assert_eq!(builder.config.timeout, Some(Duration::from_secs(50)));
1212 assert_eq!(builder.config.retry_config.max_retries, 4);
1213 assert!(builder.config.enable_logging);
1214 }
1215
1216 #[test]
1217 fn test_export_tool_as_function() {
1218 let executor = ToolExecutor::new();
1219
1220 let tool = FunctionTool::builder("greet_tool", "Greet someone")
1221 .handler(|_args| async move { Ok(serde_json::json!({"greeting": "hello"})) })
1222 .build()
1223 .unwrap();
1224
1225 executor.add_dyn_tool(Box::new(tool)).unwrap();
1226
1227 let exported = executor.export_tool_as_function("greet_tool");
1228 assert!(exported.is_some());
1229
1230 if let Some(Tools::Function { function }) = exported {
1231 assert_eq!(function.name, "greet_tool");
1232 assert_eq!(function.description, "Greet someone");
1233 assert!(function.parameters.is_some());
1235 } else {
1236 panic!("Expected Tools::Function");
1237 }
1238 }
1239
1240 #[test]
1241 fn test_export_tool_as_function_nonexistent() {
1242 let executor = ToolExecutor::new();
1243 let exported = executor.export_tool_as_function("nonexistent");
1244 assert!(exported.is_none());
1245 }
1246
1247 #[test]
1248 fn test_export_all_tools_as_functions() {
1249 let executor = ToolExecutor::new();
1250
1251 let tool1 = FunctionTool::builder("tool1", "First tool")
1252 .handler(|_args| async move { Ok(serde_json::json!({})) })
1253 .build()
1254 .unwrap();
1255
1256 let tool2 = FunctionTool::builder("tool2", "Second tool")
1257 .handler(|_args| async move { Ok(serde_json::json!({})) })
1258 .build()
1259 .unwrap();
1260
1261 executor.add_dyn_tool(Box::new(tool1)).unwrap();
1262 executor.add_dyn_tool(Box::new(tool2)).unwrap();
1263
1264 let exported = executor.export_all_tools_as_functions();
1265 assert_eq!(exported.len(), 2);
1266
1267 let names: Vec<_> = exported
1268 .iter()
1269 .filter_map(|t| match t {
1270 Tools::Function { function } => Some(function.name.clone()),
1271 _ => None,
1272 })
1273 .collect();
1274
1275 assert!(names.contains(&"tool1".to_string()));
1276 assert!(names.contains(&"tool2".to_string()));
1277 }
1278
1279 #[test]
1280 fn test_export_tools_filtered() {
1281 let executor = ToolExecutor::new();
1282
1283 let tool1 = FunctionTool::builder("math_tool", "Math operations")
1284 .metadata(|m| m.version("1.0.0"))
1285 .handler(|_args| async move { Ok(serde_json::json!({})) })
1286 .build()
1287 .unwrap();
1288
1289 let tool2 = FunctionTool::builder("text_tool", "Text operations")
1290 .metadata(|m| m.version("2.0.0"))
1291 .handler(|_args| async move { Ok(serde_json::json!({})) })
1292 .build()
1293 .unwrap();
1294
1295 executor.add_dyn_tool(Box::new(tool1)).unwrap();
1296 executor.add_dyn_tool(Box::new(tool2)).unwrap();
1297
1298 let exported = executor.export_tools_filtered(|meta| meta.version == "1.0.0");
1299 assert_eq!(exported.len(), 1);
1300
1301 if let Some(Tools::Function { function }) = exported.first() {
1302 assert_eq!(function.name, "math_tool");
1303 } else {
1304 panic!("Expected Tools::Function");
1305 }
1306 }
1307
1308 #[test]
1309 fn test_execution_result_metadata_serialization() {
1310 let result = ExecutionResult::success(
1311 "test_tool".to_string(),
1312 serde_json::json!({"value": 42}),
1313 Duration::from_millis(100),
1314 0,
1315 )
1316 .with_metadata("key1", serde_json::json!("value1"))
1317 .with_metadata("key2", serde_json::json!(123));
1318
1319 let json = serde_json::to_string(&result).unwrap();
1320 let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1321
1322 assert_eq!(parsed["metadata"]["key1"], "value1");
1323 assert_eq!(parsed["metadata"]["key2"], 123);
1324 }
1325
1326 #[test]
1327 fn test_execution_result_timestamp() {
1328 let before = std::time::SystemTime::now();
1329 let result = ExecutionResult::success(
1330 "test_tool".to_string(),
1331 serde_json::json!({"value": 42}),
1332 Duration::from_millis(100),
1333 0,
1334 );
1335 let after = std::time::SystemTime::now();
1336
1337 assert!(result.timestamp >= before && result.timestamp <= after);
1338 }
1339
1340 #[tokio::test]
1341 async fn test_tool_executor_no_retry_for_non_retryable_error() {
1342 let executor = ToolExecutor::builder()
1343 .retries(3)
1344 .timeout(Duration::from_secs(30))
1345 .build();
1346
1347 let attempt_counter = std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0));
1348 let counter_clone = attempt_counter.clone();
1349
1350 let tool = FunctionTool::builder("not_found_tool", "Not found tool")
1352 .handler(move |_args| {
1353 let counter = counter_clone.clone();
1354 async move {
1355 counter.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
1356 Err(error_context()
1357 .with_tool("not_found_tool")
1358 .invalid_parameters("Invalid parameters"))
1359 }
1360 })
1361 .build()
1362 .unwrap();
1363
1364 executor.add_dyn_tool(Box::new(tool)).unwrap();
1365
1366 let input = serde_json::json!({});
1367 let result = executor.execute("not_found_tool", input).await.unwrap();
1368
1369 assert!(!result.success);
1370 assert_eq!(result.retries, 0); assert_eq!(attempt_counter.load(std::sync::atomic::Ordering::SeqCst), 1);
1373 }
1374
1375 #[tokio::test]
1376 async fn test_execute_tool_calls_ordered_preserves_order() {
1377 use crate::model::chat_base_response::{ToolCallMessage, ToolFunction};
1378
1379 let executor = ToolExecutor::new();
1380
1381 let tool1 = FunctionTool::builder("tool_a", "First tool")
1383 .property("n", serde_json::json!({"type": "number"}))
1384 .handler(|args| async move {
1385 let n = args.get("n").and_then(|v| v.as_i64()).unwrap_or(0);
1386 Ok(serde_json::json!({"tool": "a", "n": n}))
1387 })
1388 .build()
1389 .unwrap();
1390
1391 let tool2 = FunctionTool::builder("tool_b", "Second tool")
1392 .property("n", serde_json::json!({"type": "number"}))
1393 .handler(|args| async move {
1394 let n = args.get("n").and_then(|v| v.as_i64()).unwrap_or(0);
1395 Ok(serde_json::json!({"tool": "b", "n": n}))
1396 })
1397 .build()
1398 .unwrap();
1399
1400 executor.add_dyn_tool(Box::new(tool1)).unwrap();
1401 executor.add_dyn_tool(Box::new(tool2)).unwrap();
1402
1403 let calls = vec![
1404 ToolCallMessage {
1405 id: Some("call_1".to_string()),
1406 type_: Some("function".to_string()),
1407 function: Some(ToolFunction {
1408 name: Some("tool_a".to_string()),
1409 arguments: Some(r#"{"n": 1}"#.to_string()),
1410 }),
1411 mcp: None,
1412 },
1413 ToolCallMessage {
1414 id: Some("call_2".to_string()),
1415 type_: Some("function".to_string()),
1416 function: Some(ToolFunction {
1417 name: Some("tool_b".to_string()),
1418 arguments: Some(r#"{"n": 2}"#.to_string()),
1419 }),
1420 mcp: None,
1421 },
1422 ];
1423
1424 let results = executor.execute_tool_calls_ordered(&calls).await;
1425 assert_eq!(results.len(), 2);
1426 let first = &results[0];
1428 let first_content = match first {
1429 crate::model::chat_message_types::TextMessage::Tool { content, .. } => content.clone(),
1430 _ => panic!("Expected Tool message"),
1431 };
1432 let parsed1: serde_json::Value = serde_json::from_str(&first_content).unwrap();
1433 assert!(parsed1.get("tool").is_some());
1435 assert!(parsed1["n"].as_i64() == Some(1));
1436
1437 let second = &results[1];
1438 let second_content = match second {
1439 crate::model::chat_message_types::TextMessage::Tool { content, .. } => content.clone(),
1440 _ => panic!("Expected Tool message"),
1441 };
1442 let parsed2: serde_json::Value = serde_json::from_str(&second_content).unwrap();
1443 assert!(parsed2.get("tool").is_some());
1444 assert!(parsed2["n"].as_i64() == Some(2));
1445 }
1446
1447 #[tokio::test]
1448 async fn test_execute_tool_calls_parallel_returns_all() {
1449 use crate::model::chat_base_response::{ToolCallMessage, ToolFunction};
1450
1451 let executor = ToolExecutor::new();
1452
1453 let tool1 = FunctionTool::builder("parallel_a", "First parallel tool")
1454 .property("n", serde_json::json!({"type": "number"}))
1455 .handler(|args| async move {
1456 let n = args.get("n").and_then(|v| v.as_i64()).unwrap_or(0);
1457 Ok(serde_json::json!({"tool": "a", "n": n}))
1458 })
1459 .build()
1460 .unwrap();
1461
1462 let tool2 = FunctionTool::builder("parallel_b", "Second parallel tool")
1463 .property("n", serde_json::json!({"type": "number"}))
1464 .handler(|args| async move {
1465 let n = args.get("n").and_then(|v| v.as_i64()).unwrap_or(0);
1466 Ok(serde_json::json!({"tool": "b", "n": n}))
1467 })
1468 .build()
1469 .unwrap();
1470
1471 executor.add_dyn_tool(Box::new(tool1)).unwrap();
1472 executor.add_dyn_tool(Box::new(tool2)).unwrap();
1473
1474 let calls = vec![
1475 ToolCallMessage {
1476 id: Some("call_1".to_string()),
1477 type_: Some("function".to_string()),
1478 function: Some(ToolFunction {
1479 name: Some("parallel_a".to_string()),
1480 arguments: Some(r#"{"n": 1}"#.to_string()),
1481 }),
1482 mcp: None,
1483 },
1484 ToolCallMessage {
1485 id: Some("call_2".to_string()),
1486 type_: Some("function".to_string()),
1487 function: Some(ToolFunction {
1488 name: Some("parallel_b".to_string()),
1489 arguments: Some(r#"{"n": 2}"#.to_string()),
1490 }),
1491 mcp: None,
1492 },
1493 ];
1494
1495 let results = executor.execute_tool_calls_parallel(&calls).await;
1496 assert_eq!(results.len(), 2);
1497 }
1498}