mojentic/llm/tools/
current_datetime_tool.rs1use crate::error::Result;
2use crate::llm::tools::{FunctionDescriptor, LlmTool, ToolDescriptor};
3use chrono::Local;
4use serde_json::{json, Value};
5use std::collections::HashMap;
6
7#[derive(Clone)]
24pub struct CurrentDatetimeTool;
25
26impl CurrentDatetimeTool {
27 pub fn new() -> Self {
29 Self
30 }
31}
32
33impl Default for CurrentDatetimeTool {
34 fn default() -> Self {
35 Self::new()
36 }
37}
38
39impl LlmTool for CurrentDatetimeTool {
40 fn run(&self, args: &HashMap<String, Value>) -> Result<Value> {
41 let format_string = args
42 .get("format_string")
43 .and_then(|v| v.as_str())
44 .unwrap_or("%Y-%m-%d %H:%M:%S");
45
46 let now = Local::now();
47 let formatted_time = now.format(format_string).to_string();
48 let timestamp = now.timestamp();
49 let timezone = format!("{}", now.offset());
50
51 Ok(json!({
52 "current_datetime": formatted_time,
53 "timestamp": timestamp,
54 "timezone": timezone
55 }))
56 }
57
58 fn descriptor(&self) -> ToolDescriptor {
59 ToolDescriptor {
60 r#type: "function".to_string(),
61 function: FunctionDescriptor {
62 name: "get_current_datetime".to_string(),
63 description: "Get the current date and time. Useful when you need to know the current time or date.".to_string(),
64 parameters: json!({
65 "type": "object",
66 "properties": {
67 "format_string": {
68 "type": "string",
69 "description": "Format string for the datetime (e.g., '%Y-%m-%d %H:%M:%S', '%A, %B %d, %Y'). Default is ISO format."
70 }
71 },
72 "required": []
73 }),
74 },
75 }
76 }
77
78 fn clone_box(&self) -> Box<dyn LlmTool> {
79 Box::new(self.clone())
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn test_descriptor() {
89 let tool = CurrentDatetimeTool::new();
90 let descriptor = tool.descriptor();
91
92 assert_eq!(descriptor.r#type, "function");
93 assert_eq!(descriptor.function.name, "get_current_datetime");
94 assert!(descriptor.function.description.contains("current date and time"));
95 }
96
97 #[test]
98 fn test_run_with_default_format() {
99 let tool = CurrentDatetimeTool::new();
100 let args = HashMap::new();
101
102 let result = tool.run(&args).unwrap();
103
104 assert!(result.is_object());
105 assert!(result.get("current_datetime").is_some());
106 assert!(result.get("timestamp").is_some());
107 assert!(result.get("timezone").is_some());
108
109 let datetime_str = result.get("current_datetime").unwrap().as_str().unwrap();
111 let re = regex::Regex::new(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$").unwrap();
112 assert!(re.is_match(datetime_str));
113 }
114
115 #[test]
116 fn test_run_with_custom_format() {
117 let tool = CurrentDatetimeTool::new();
118 let mut args = HashMap::new();
119 args.insert("format_string".to_string(), json!("%Y-%m-%d"));
120
121 let result = tool.run(&args).unwrap();
122
123 let datetime_str = result.get("current_datetime").unwrap().as_str().unwrap();
124 let re = regex::Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap();
125 assert!(re.is_match(datetime_str));
126 }
127
128 #[test]
129 fn test_timestamp_is_reasonable() {
130 let tool = CurrentDatetimeTool::new();
131 let args = HashMap::new();
132
133 let result = tool.run(&args).unwrap();
134 let timestamp = result.get("timestamp").unwrap().as_i64().unwrap();
135
136 assert!(timestamp > 1_577_836_800);
138 assert!(timestamp < 1_893_456_000);
139 }
140
141 #[test]
142 fn test_timezone_is_present() {
143 let tool = CurrentDatetimeTool::new();
144 let args = HashMap::new();
145
146 let result = tool.run(&args).unwrap();
147 let timezone = result.get("timezone").unwrap().as_str().unwrap();
148
149 assert!(!timezone.is_empty());
150 }
151
152 #[test]
153 fn test_tool_matches() {
154 let tool = CurrentDatetimeTool::new();
155 assert!(tool.matches("get_current_datetime"));
156 assert!(!tool.matches("other_tool"));
157 }
158
159 #[test]
160 fn test_format_with_day_name() {
161 let tool = CurrentDatetimeTool::new();
162 let mut args = HashMap::new();
163 args.insert("format_string".to_string(), json!("%A"));
164
165 let result = tool.run(&args).unwrap();
166 let day_name = result.get("current_datetime").unwrap().as_str().unwrap();
167
168 let valid_days = [
169 "Monday",
170 "Tuesday",
171 "Wednesday",
172 "Thursday",
173 "Friday",
174 "Saturday",
175 "Sunday",
176 ];
177 assert!(valid_days.contains(&day_name));
178 }
179
180 #[test]
181 fn test_format_with_month_name() {
182 let tool = CurrentDatetimeTool::new();
183 let mut args = HashMap::new();
184 args.insert("format_string".to_string(), json!("%B"));
185
186 let result = tool.run(&args).unwrap();
187 let month_name = result.get("current_datetime").unwrap().as_str().unwrap();
188
189 let valid_months = [
190 "January",
191 "February",
192 "March",
193 "April",
194 "May",
195 "June",
196 "July",
197 "August",
198 "September",
199 "October",
200 "November",
201 "December",
202 ];
203 assert!(valid_months.contains(&month_name));
204 }
205}