Skip to main content

ai_agents_tools/builtin/
random.rs

1use async_trait::async_trait;
2use rand::{Rng, seq::SliceRandom};
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7use crate::generate_schema;
8use ai_agents_core::{Tool, ToolResult, ToolSafetyMetadata};
9
10pub struct RandomTool;
11
12impl RandomTool {
13    pub fn new() -> Self {
14        Self
15    }
16}
17
18impl Default for RandomTool {
19    fn default() -> Self {
20        Self::new()
21    }
22}
23
24#[derive(Debug, Deserialize, JsonSchema)]
25struct RandomInput {
26    /// Operation to perform: uuid, number, integer, choice, shuffle, bool, string
27    operation: String,
28    /// Minimum value (for number/integer operations)
29    #[serde(default)]
30    min: Option<f64>,
31    /// Maximum value (for number/integer operations)
32    #[serde(default)]
33    max: Option<f64>,
34    /// Items to choose from or shuffle (for choice/shuffle operations)
35    #[serde(default)]
36    items: Option<Vec<Value>>,
37    /// Number of items to choose (for choice operation, default: 1)
38    #[serde(default)]
39    count: Option<usize>,
40    /// Length of random string (for string operation, default: 16)
41    #[serde(default)]
42    length: Option<usize>,
43    /// Character set for string: alphanumeric, alpha, numeric, hex (default: alphanumeric)
44    #[serde(default)]
45    charset: Option<String>,
46}
47
48#[derive(Debug, Serialize, Deserialize)]
49struct UuidOutput {
50    uuid: String,
51}
52
53#[derive(Debug, Serialize, Deserialize)]
54struct NumberOutput {
55    value: f64,
56    min: f64,
57    max: f64,
58}
59
60#[derive(Debug, Serialize, Deserialize)]
61struct IntegerOutput {
62    value: i64,
63    min: i64,
64    max: i64,
65}
66
67#[derive(Debug, Serialize, Deserialize)]
68struct ChoiceOutput {
69    selected: Vec<Value>,
70    count: usize,
71}
72
73#[derive(Debug, Serialize, Deserialize)]
74struct ShuffleOutput {
75    shuffled: Vec<Value>,
76    count: usize,
77}
78
79#[derive(Debug, Serialize, Deserialize)]
80struct BoolOutput {
81    value: bool,
82}
83
84#[derive(Debug, Serialize, Deserialize)]
85struct StringOutput {
86    value: String,
87    length: usize,
88}
89
90#[async_trait]
91impl Tool for RandomTool {
92    fn id(&self) -> &str {
93        "random"
94    }
95
96    fn name(&self) -> &str {
97        "Random Generator"
98    }
99
100    fn description(&self) -> &str {
101        "Generate random values. Operations: uuid (generate UUID v4), number (random float), integer (random int), choice (pick from list), shuffle (randomize list order), bool (random true/false), string (random string)."
102    }
103
104    fn input_schema(&self) -> Value {
105        generate_schema::<RandomInput>()
106    }
107
108    fn safety_metadata(&self) -> ToolSafetyMetadata {
109        ToolSafetyMetadata::compute()
110    }
111
112    async fn execute(&self, args: Value, _ctx: ai_agents_core::ToolExecutionContext) -> ToolResult {
113        let input: RandomInput = match serde_json::from_value(args) {
114            Ok(input) => input,
115            Err(e) => return ToolResult::error(format!("Invalid input: {}", e)),
116        };
117
118        match input.operation.to_lowercase().as_str() {
119            "uuid" => self.handle_uuid(),
120            "number" => self.handle_number(&input),
121            "integer" | "int" => self.handle_integer(&input),
122            "choice" | "choose" | "pick" => self.handle_choice(&input),
123            "shuffle" => self.handle_shuffle(&input),
124            "bool" | "boolean" => self.handle_bool(),
125            "string" | "str" => self.handle_string(&input),
126            _ => ToolResult::error(format!(
127                "Unknown operation: {}. Valid operations: uuid, number, integer, choice, shuffle, bool, string",
128                input.operation
129            )),
130        }
131    }
132}
133
134impl RandomTool {
135    fn handle_uuid(&self) -> ToolResult {
136        let output = UuidOutput {
137            uuid: uuid::Uuid::new_v4().to_string(),
138        };
139        self.to_result(&output)
140    }
141
142    fn handle_number(&self, input: &RandomInput) -> ToolResult {
143        let min = input.min.unwrap_or(0.0);
144        let max = input.max.unwrap_or(1.0);
145
146        if min >= max {
147            return ToolResult::error("'min' must be less than 'max'");
148        }
149
150        let mut rng = rand::thread_rng();
151        let value: f64 = rng.gen_range(min..max);
152
153        let output = NumberOutput { value, min, max };
154        self.to_result(&output)
155    }
156
157    fn handle_integer(&self, input: &RandomInput) -> ToolResult {
158        let min = input.min.unwrap_or(0.0) as i64;
159        let max = input.max.unwrap_or(100.0) as i64;
160
161        if min >= max {
162            return ToolResult::error("'min' must be less than 'max'");
163        }
164
165        let mut rng = rand::thread_rng();
166        let value: i64 = rng.gen_range(min..=max);
167
168        let output = IntegerOutput { value, min, max };
169        self.to_result(&output)
170    }
171
172    fn handle_choice(&self, input: &RandomInput) -> ToolResult {
173        let items = match &input.items {
174            Some(i) if !i.is_empty() => i,
175            Some(_) => return ToolResult::error("'items' cannot be empty"),
176            None => return ToolResult::error("'items' is required for choice operation"),
177        };
178
179        let count = input.count.unwrap_or(1).min(items.len());
180
181        let mut rng = rand::thread_rng();
182        let selected: Vec<Value> = items.choose_multiple(&mut rng, count).cloned().collect();
183
184        let output = ChoiceOutput {
185            count: selected.len(),
186            selected,
187        };
188        self.to_result(&output)
189    }
190
191    fn handle_shuffle(&self, input: &RandomInput) -> ToolResult {
192        let items = match &input.items {
193            Some(i) => i.clone(),
194            None => return ToolResult::error("'items' is required for shuffle operation"),
195        };
196
197        let mut shuffled = items;
198        let mut rng = rand::thread_rng();
199        shuffled.shuffle(&mut rng);
200
201        let output = ShuffleOutput {
202            count: shuffled.len(),
203            shuffled,
204        };
205        self.to_result(&output)
206    }
207
208    fn handle_bool(&self) -> ToolResult {
209        let mut rng = rand::thread_rng();
210        let output = BoolOutput { value: rng.r#gen() };
211        self.to_result(&output)
212    }
213
214    fn handle_string(&self, input: &RandomInput) -> ToolResult {
215        let length = input.length.unwrap_or(16);
216        let charset = input.charset.as_deref().unwrap_or("alphanumeric");
217
218        let chars: Vec<char> = match charset.to_lowercase().as_str() {
219            "alphanumeric" | "alnum" => {
220                "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
221                    .chars()
222                    .collect()
223            }
224            "alpha" | "letters" => "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
225                .chars()
226                .collect(),
227            "numeric" | "digits" | "numbers" => "0123456789".chars().collect(),
228            "hex" | "hexadecimal" => "0123456789abcdef".chars().collect(),
229            "lower" | "lowercase" => "abcdefghijklmnopqrstuvwxyz".chars().collect(),
230            "upper" | "uppercase" => "ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars().collect(),
231            _ => {
232                return ToolResult::error(format!(
233                    "Unknown charset: {}. Valid: alphanumeric, alpha, numeric, hex, lower, upper",
234                    charset
235                ));
236            }
237        };
238
239        let mut rng = rand::thread_rng();
240        let value: String = (0..length)
241            .map(|_| chars[rng.gen_range(0..chars.len())])
242            .collect();
243
244        let output = StringOutput { value, length };
245        self.to_result(&output)
246    }
247
248    fn to_result<T: Serialize>(&self, output: &T) -> ToolResult {
249        match serde_json::to_string(output) {
250            Ok(json) => ToolResult::ok(json),
251            Err(e) => ToolResult::error(format!("Serialization error: {}", e)),
252        }
253    }
254}
255
256#[cfg(test)]
257mod tests {
258    use super::*;
259
260    #[tokio::test]
261    async fn test_uuid() {
262        let tool = RandomTool::new();
263        let result = tool
264            .execute(
265                serde_json::json!({"operation": "uuid"}),
266                ai_agents_core::ToolExecutionContext::test("test"),
267            )
268            .await;
269        assert!(result.success);
270
271        let output: UuidOutput = serde_json::from_str(&result.output).unwrap();
272        assert_eq!(output.uuid.len(), 36);
273        assert!(output.uuid.contains('-'));
274    }
275
276    #[tokio::test]
277    async fn test_number() {
278        let tool = RandomTool::new();
279        let result = tool
280            .execute(
281                serde_json::json!({
282                    "operation": "number",
283                    "min": 10.0,
284                    "max": 20.0
285                }),
286                ai_agents_core::ToolExecutionContext::test("test"),
287            )
288            .await;
289        assert!(result.success);
290
291        let output: NumberOutput = serde_json::from_str(&result.output).unwrap();
292        assert!(output.value >= 10.0 && output.value < 20.0);
293    }
294
295    #[tokio::test]
296    async fn test_number_invalid_range() {
297        let tool = RandomTool::new();
298        let result = tool
299            .execute(
300                serde_json::json!({
301                    "operation": "number",
302                    "min": 20.0,
303                    "max": 10.0
304                }),
305                ai_agents_core::ToolExecutionContext::test("test"),
306            )
307            .await;
308        assert!(!result.success);
309    }
310
311    #[tokio::test]
312    async fn test_integer() {
313        let tool = RandomTool::new();
314        let result = tool
315            .execute(
316                serde_json::json!({
317                    "operation": "integer",
318                    "min": 1,
319                    "max": 10
320                }),
321                ai_agents_core::ToolExecutionContext::test("test"),
322            )
323            .await;
324        assert!(result.success);
325
326        let output: IntegerOutput = serde_json::from_str(&result.output).unwrap();
327        assert!(output.value >= 1 && output.value <= 10);
328    }
329
330    #[tokio::test]
331    async fn test_choice() {
332        let tool = RandomTool::new();
333        let result = tool
334            .execute(
335                serde_json::json!({
336                    "operation": "choice",
337                    "items": ["a", "b", "c", "d"],
338                    "count": 2
339                }),
340                ai_agents_core::ToolExecutionContext::test("test"),
341            )
342            .await;
343        assert!(result.success);
344
345        let output: ChoiceOutput = serde_json::from_str(&result.output).unwrap();
346        assert_eq!(output.count, 2);
347        assert_eq!(output.selected.len(), 2);
348    }
349
350    #[tokio::test]
351    async fn test_choice_single() {
352        let tool = RandomTool::new();
353        let result = tool
354            .execute(
355                serde_json::json!({
356                    "operation": "choice",
357                    "items": [1, 2, 3]
358                }),
359                ai_agents_core::ToolExecutionContext::test("test"),
360            )
361            .await;
362        assert!(result.success);
363
364        let output: ChoiceOutput = serde_json::from_str(&result.output).unwrap();
365        assert_eq!(output.count, 1);
366    }
367
368    #[tokio::test]
369    async fn test_choice_empty() {
370        let tool = RandomTool::new();
371        let result = tool
372            .execute(
373                serde_json::json!({
374                    "operation": "choice",
375                    "items": []
376                }),
377                ai_agents_core::ToolExecutionContext::test("test"),
378            )
379            .await;
380        assert!(!result.success);
381    }
382
383    #[tokio::test]
384    async fn test_shuffle() {
385        let tool = RandomTool::new();
386        let result = tool
387            .execute(
388                serde_json::json!({
389                    "operation": "shuffle",
390                    "items": [1, 2, 3, 4, 5]
391                }),
392                ai_agents_core::ToolExecutionContext::test("test"),
393            )
394            .await;
395        assert!(result.success);
396
397        let output: ShuffleOutput = serde_json::from_str(&result.output).unwrap();
398        assert_eq!(output.count, 5);
399    }
400
401    #[tokio::test]
402    async fn test_bool() {
403        let tool = RandomTool::new();
404        let result = tool
405            .execute(
406                serde_json::json!({"operation": "bool"}),
407                ai_agents_core::ToolExecutionContext::test("test"),
408            )
409            .await;
410        assert!(result.success);
411
412        let output: BoolOutput = serde_json::from_str(&result.output).unwrap();
413        assert!(output.value == true || output.value == false);
414    }
415
416    #[tokio::test]
417    async fn test_string_default() {
418        let tool = RandomTool::new();
419        let result = tool
420            .execute(
421                serde_json::json!({"operation": "string"}),
422                ai_agents_core::ToolExecutionContext::test("test"),
423            )
424            .await;
425        assert!(result.success);
426
427        let output: StringOutput = serde_json::from_str(&result.output).unwrap();
428        assert_eq!(output.length, 16);
429        assert_eq!(output.value.len(), 16);
430    }
431
432    #[tokio::test]
433    async fn test_string_hex() {
434        let tool = RandomTool::new();
435        let result = tool
436            .execute(
437                serde_json::json!({
438                    "operation": "string",
439                    "length": 8,
440                    "charset": "hex"
441                }),
442                ai_agents_core::ToolExecutionContext::test("test"),
443            )
444            .await;
445        assert!(result.success);
446
447        let output: StringOutput = serde_json::from_str(&result.output).unwrap();
448        assert_eq!(output.length, 8);
449        assert!(output.value.chars().all(|c| c.is_ascii_hexdigit()));
450    }
451
452    #[tokio::test]
453    async fn test_string_numeric() {
454        let tool = RandomTool::new();
455        let result = tool
456            .execute(
457                serde_json::json!({
458                    "operation": "string",
459                    "length": 10,
460                    "charset": "numeric"
461                }),
462                ai_agents_core::ToolExecutionContext::test("test"),
463            )
464            .await;
465        assert!(result.success);
466
467        let output: StringOutput = serde_json::from_str(&result.output).unwrap();
468        assert!(output.value.chars().all(|c| c.is_ascii_digit()));
469    }
470
471    #[tokio::test]
472    async fn test_invalid_operation() {
473        let tool = RandomTool::new();
474        let result = tool
475            .execute(
476                serde_json::json!({"operation": "invalid"}),
477                ai_agents_core::ToolExecutionContext::test("test"),
478            )
479            .await;
480        assert!(!result.success);
481    }
482}