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