Skip to main content

lc_tools/
math.rs

1// lc-tools/src/math.rs
2//! Advanced math tool for agents.
3//!
4//! Provides complex mathematical operations including exponent, logarithm, trigonometry, etc.
5
6use async_trait::async_trait;
7use schemars::JsonSchema;
8use serde::{Deserialize, Serialize};
9
10use lc_core::tools::{BaseTool, Tool, ToolError};
11
12/// Math tool input parameters.
13#[derive(Debug, Deserialize, JsonSchema)]
14pub struct MathInput {
15    /// Operation type: "power", "sqrt", "log", "ln", "sin", "cos", "tan", "abs", "factorial", "mod", "gcd", "lcm".
16    pub operation: String,
17
18    /// First value for calculation.
19    pub value: Option<f64>,
20
21    /// Second value for operations requiring two parameters (power, mod, gcd, lcm).
22    pub value2: Option<f64>,
23
24    /// Logarithm base for log operation (default: 10).
25    pub base: Option<f64>,
26}
27
28/// Math tool output result.
29#[derive(Debug, Serialize)]
30pub struct MathOutput {
31    /// Calculation result.
32    pub result: f64,
33
34    /// Operation type.
35    pub operation: String,
36
37    /// Additional details.
38    pub details: Option<String>,
39}
40
41/// Advanced math tool for agents.
42pub struct SimpleMathTool;
43
44impl SimpleMathTool {
45    /// Creates a new SimpleMathTool instance.
46    pub fn new() -> Self {
47        Self
48    }
49
50    fn power(&self, base: f64, exponent: f64) -> Result<MathOutput, ToolError> {
51        let result = base.powf(exponent);
52        Ok(MathOutput {
53            result,
54            operation: "power".to_string(),
55            details: Some(format!("{}^{} = {}", base, exponent, result)),
56        })
57    }
58
59    fn sqrt(&self, value: f64) -> Result<MathOutput, ToolError> {
60        if value < 0.0 {
61            return Err(ToolError::InvalidInput(
62                "square root requires a non-negative number".to_string(),
63            ));
64        }
65        let result = value.sqrt();
66        Ok(MathOutput {
67            result,
68            operation: "sqrt".to_string(),
69            details: Some(format!("√{} = {}", value, result)),
70        })
71    }
72
73    fn log(&self, value: f64, base: f64) -> Result<MathOutput, ToolError> {
74        if value <= 0.0 || base <= 0.0 || base == 1.0 {
75            return Err(ToolError::InvalidInput(
76                "logarithm requires positive values and a base other than 1".to_string(),
77            ));
78        }
79        let result = value.log(base);
80        Ok(MathOutput {
81            result,
82            operation: "log".to_string(),
83            details: Some(format!("log_{}({}) = {}", base, value, result)),
84        })
85    }
86
87    fn ln(&self, value: f64) -> Result<MathOutput, ToolError> {
88        if value <= 0.0 {
89            return Err(ToolError::InvalidInput(
90                "natural logarithm requires a positive number".to_string(),
91            ));
92        }
93        let result = value.ln();
94        Ok(MathOutput {
95            result,
96            operation: "ln".to_string(),
97            details: Some(format!("ln({}) = {}", value, result)),
98        })
99    }
100
101    fn sin(&self, value: f64) -> Result<MathOutput, ToolError> {
102        let result = value.sin();
103        Ok(MathOutput {
104            result,
105            operation: "sin".to_string(),
106            details: Some(format!("sin({}弧度) = {}", value, result)),
107        })
108    }
109
110    fn cos(&self, value: f64) -> Result<MathOutput, ToolError> {
111        let result = value.cos();
112        Ok(MathOutput {
113            result,
114            operation: "cos".to_string(),
115            details: Some(format!("cos({}弧度) = {}", value, result)),
116        })
117    }
118
119    fn tan(&self, value: f64) -> Result<MathOutput, ToolError> {
120        let result = value.tan();
121        Ok(MathOutput {
122            result,
123            operation: "tan".to_string(),
124            details: Some(format!("tan({}弧度) = {}", value, result)),
125        })
126    }
127
128    fn abs(&self, value: f64) -> Result<MathOutput, ToolError> {
129        let result = value.abs();
130        Ok(MathOutput {
131            result,
132            operation: "abs".to_string(),
133            details: Some(format!("|{}| = {}", value, result)),
134        })
135    }
136
137    fn factorial(&self, value: f64) -> Result<MathOutput, ToolError> {
138        if value < 0.0 {
139            return Err(ToolError::InvalidInput(
140                "factorial requires a non-negative integer".to_string(),
141            ));
142        }
143        if value.is_nan() {
144            return Err(ToolError::InvalidInput(
145                "factorial requires a valid number, NaN is not allowed".to_string(),
146            ));
147        }
148        if value != value.floor() {
149            return Err(ToolError::InvalidInput(
150                "factorial requires an integer, not a decimal".to_string(),
151            ));
152        }
153        let n = value as u64;
154        if n > 20 {
155            return Err(ToolError::InvalidInput(
156                "factorial value too large, maximum supported is 20".to_string(),
157            ));
158        }
159        let result = self.compute_factorial(n);
160        Ok(MathOutput {
161            result: result as f64,
162            operation: "factorial".to_string(),
163            details: Some(format!("{}! = {}", n, result)),
164        })
165    }
166
167    fn compute_factorial(&self, n: u64) -> u64 {
168        if n == 0 || n == 1 {
169            1
170        } else {
171            n * self.compute_factorial(n - 1)
172        }
173    }
174
175    fn mod_op(&self, a: f64, b: f64) -> Result<MathOutput, ToolError> {
176        if b == 0.0 {
177            return Err(ToolError::InvalidInput(
178                "modulo divisor must not be zero".to_string(),
179            ));
180        }
181        let result = a % b;
182        Ok(MathOutput {
183            result,
184            operation: "mod".to_string(),
185            details: Some(format!("{} mod {} = {}", a, b, result)),
186        })
187    }
188
189    fn gcd(&self, a: f64, b: f64) -> Result<MathOutput, ToolError> {
190        let a_int = a as i64;
191        let b_int = b as i64;
192
193        if a_int < 0 || b_int < 0 {
194            return Err(ToolError::InvalidInput(
195                "GCD operation requires positive integers".to_string(),
196            ));
197        }
198
199        let result = self.compute_gcd(a_int.abs(), b_int.abs());
200        Ok(MathOutput {
201            result: result as f64,
202            operation: "gcd".to_string(),
203            details: Some(format!("gcd({}, {}) = {}", a_int, b_int, result)),
204        })
205    }
206
207    // clippy::only_used_in_recursion 误报(stable 1.86+ 报,1.85 不报):
208    // 基准分支 `b == 0 => return a` 实际使用了参数 `a`,并非"只在递归中用到"。
209    #[allow(clippy::only_used_in_recursion)]
210    fn compute_gcd(&self, a: i64, b: i64) -> i64 {
211        if b == 0 {
212            a
213        } else {
214            self.compute_gcd(b, a % b)
215        }
216    }
217
218    fn lcm(&self, a: f64, b: f64) -> Result<MathOutput, ToolError> {
219        let a_int = a as i64;
220        let b_int = b as i64;
221
222        if a_int <= 0 || b_int <= 0 {
223            return Err(ToolError::InvalidInput(
224                "LCM operation requires positive integers".to_string(),
225            ));
226        }
227
228        let gcd = self.compute_gcd(a_int, b_int);
229        let result = (a_int / gcd)
230            .checked_mul(b_int)
231            .ok_or_else(|| ToolError::InvalidInput("LCM result overflows i64 range".to_string()))?;
232        Ok(MathOutput {
233            result: result as f64,
234            operation: "lcm".to_string(),
235            details: Some(format!("lcm({}, {}) = {}", a_int, b_int, result)),
236        })
237    }
238
239    fn pi(&self) -> MathOutput {
240        MathOutput {
241            result: std::f64::consts::PI,
242            operation: "pi".to_string(),
243            details: Some("π ≈ 3.141592653589793".to_string()),
244        }
245    }
246
247    fn e(&self) -> MathOutput {
248        MathOutput {
249            result: std::f64::consts::E,
250            operation: "e".to_string(),
251            details: Some("e ≈ 2.718281828459045".to_string()),
252        }
253    }
254}
255
256impl Default for SimpleMathTool {
257    fn default() -> Self {
258        Self::new()
259    }
260}
261
262#[async_trait]
263impl Tool for SimpleMathTool {
264    type Input = MathInput;
265    type Output = MathOutput;
266
267    async fn invoke(&self, input: Self::Input) -> Result<Self::Output, ToolError> {
268        match input.operation.as_str() {
269            "power" => {
270                let base = input.value.ok_or_else(||
271                    ToolError::InvalidInput("power operation requires a value parameter as the base".to_string()))?;
272                let exp = input.value2.ok_or_else(||
273                    ToolError::InvalidInput("power operation requires a value2 parameter as the exponent".to_string()))?;
274                self.power(base, exp)
275            }
276            "sqrt" => {
277                let value = input.value.ok_or_else(||
278                    ToolError::InvalidInput("sqrt operation requires a value parameter".to_string()))?;
279                self.sqrt(value)
280            }
281            "log" => {
282                let value = input.value.ok_or_else(||
283                    ToolError::InvalidInput("log operation requires a value parameter".to_string()))?;
284                let base = input.base.unwrap_or(10.0);
285                self.log(value, base)
286            }
287            "ln" => {
288                let value = input.value.ok_or_else(||
289                    ToolError::InvalidInput("ln operation requires a value parameter".to_string()))?;
290                self.ln(value)
291            }
292            "sin" => {
293                let value = input.value.ok_or_else(||
294                    ToolError::InvalidInput("sin operation requires a value parameter (radians)".to_string()))?;
295                self.sin(value)
296            }
297            "cos" => {
298                let value = input.value.ok_or_else(||
299                    ToolError::InvalidInput("cos operation requires a value parameter (radians)".to_string()))?;
300                self.cos(value)
301            }
302            "tan" => {
303                let value = input.value.ok_or_else(||
304                    ToolError::InvalidInput("tan operation requires a value parameter (radians)".to_string()))?;
305                self.tan(value)
306            }
307            "abs" => {
308                let value = input.value.ok_or_else(||
309                    ToolError::InvalidInput("abs operation requires a value parameter".to_string()))?;
310                self.abs(value)
311            }
312            "factorial" => {
313                let value = input.value.ok_or_else(||
314                    ToolError::InvalidInput("factorial operation requires a value parameter".to_string()))?;
315                self.factorial(value)
316            }
317            "mod" => {
318                let a = input.value.ok_or_else(||
319                    ToolError::InvalidInput("mod operation requires a value parameter".to_string()))?;
320                let b = input.value2.ok_or_else(||
321                    ToolError::InvalidInput("mod operation requires a value2 parameter".to_string()))?;
322                self.mod_op(a, b)
323            }
324            "gcd" => {
325                let a = input.value.ok_or_else(||
326                    ToolError::InvalidInput("gcd operation requires a value parameter".to_string()))?;
327                let b = input.value2.ok_or_else(||
328                    ToolError::InvalidInput("gcd operation requires a value2 parameter".to_string()))?;
329                self.gcd(a, b)
330            }
331            "lcm" => {
332                let a = input.value.ok_or_else(||
333                    ToolError::InvalidInput("lcm operation requires a value parameter".to_string()))?;
334                let b = input.value2.ok_or_else(||
335                    ToolError::InvalidInput("lcm operation requires a value2 parameter".to_string()))?;
336                self.lcm(a, b)
337            }
338            "pi" => Ok(self.pi()),
339            "e" => Ok(self.e()),
340            _ => Err(ToolError::InvalidInput(
341                format!("unsupported operation: {}, use: power, sqrt, log, ln, sin, cos, tan, abs, factorial, mod, gcd, lcm, pi, e", input.operation)
342            )),
343        }
344    }
345}
346
347#[async_trait]
348impl BaseTool for SimpleMathTool {
349    fn name(&self) -> &str {
350        "math"
351    }
352
353    fn description(&self) -> &str {
354        "高级数学工具。支持多种数学运算:
355
356操作类型:
357- power: 幂运算 (value^value2)
358- sqrt: 平方根
359- log: 对数(可指定底数,默认为10)
360- ln: 自然对数
361- sin, cos, tan: 三角函数(参数为弧度)
362- abs: 绝对值
363- factorial: 阶乘(最大支持20)
364- mod: 取模运算
365- gcd: 最大公约数
366- lcm: 最小公倍数
367- pi: 圆周率
368- e: 自然常数
369
370示例:
371- 幂运算: {\"operation\": \"power\", \"value\": 2, \"value2\": 10}
372- 平方根: {\"operation\": \"sqrt\", \"value\": 16}
373- 对数: {\"operation\": \"log\", \"value\": 100, \"base\": 10}
374- 三角函数: {\"operation\": \"sin\", \"value\": 1.5708}
375- GCD: {\"operation\": \"gcd\", \"value\": 12, \"value2\": 18}"
376    }
377
378    async fn run(&self, input: String) -> Result<String, ToolError> {
379        let parsed: MathInput = serde_json::from_str(&input)
380            .map_err(|e| ToolError::InvalidInput(format!("JSON parse failed: {}", e)))?;
381
382        let output = self.invoke(parsed).await?;
383
384        Ok(format!(
385            "结果: {}\n详细信息: {}",
386            output.result,
387            output.details.unwrap_or_default()
388        ))
389    }
390
391    fn args_schema(&self) -> Option<serde_json::Value> {
392        use schemars::schema_for;
393        serde_json::to_value(schema_for!(MathInput)).ok()
394    }
395}
396
397#[cfg(test)]
398mod tests {
399    use super::*;
400
401    #[tokio::test]
402    async fn test_math_power() {
403        let tool = SimpleMathTool::new();
404
405        let input = MathInput {
406            operation: "power".to_string(),
407            value: Some(2.0),
408            value2: Some(10.0),
409            base: None,
410        };
411
412        let result = tool.invoke(input).await.unwrap();
413        assert_eq!(result.result, 1024.0);
414    }
415
416    #[tokio::test]
417    async fn test_math_sqrt() {
418        let tool = SimpleMathTool::new();
419
420        let input = MathInput {
421            operation: "sqrt".to_string(),
422            value: Some(16.0),
423            value2: None,
424            base: None,
425        };
426
427        let result = tool.invoke(input).await.unwrap();
428        assert_eq!(result.result, 4.0);
429    }
430
431    #[tokio::test]
432    async fn test_math_log() {
433        let tool = SimpleMathTool::new();
434
435        let input = MathInput {
436            operation: "log".to_string(),
437            value: Some(100.0),
438            value2: None,
439            base: Some(10.0),
440        };
441
442        let result = tool.invoke(input).await.unwrap();
443        assert_eq!(result.result, 2.0);
444    }
445
446    #[tokio::test]
447    async fn test_math_ln() {
448        let tool = SimpleMathTool::new();
449
450        let input = MathInput {
451            operation: "ln".to_string(),
452            value: Some(std::f64::consts::E),
453            value2: None,
454            base: None,
455        };
456
457        let result = tool.invoke(input).await.unwrap();
458        assert!((result.result - 1.0).abs() < 0.0001);
459    }
460
461    #[tokio::test]
462    async fn test_math_sin() {
463        let tool = SimpleMathTool::new();
464
465        let input = MathInput {
466            operation: "sin".to_string(),
467            value: Some(std::f64::consts::PI / 2.0),
468            value2: None,
469            base: None,
470        };
471
472        let result = tool.invoke(input).await.unwrap();
473        assert!((result.result - 1.0).abs() < 0.0001);
474    }
475
476    #[tokio::test]
477    async fn test_math_factorial() {
478        let tool = SimpleMathTool::new();
479
480        let input = MathInput {
481            operation: "factorial".to_string(),
482            value: Some(5.0),
483            value2: None,
484            base: None,
485        };
486
487        let result = tool.invoke(input).await.unwrap();
488        assert_eq!(result.result, 120.0);
489    }
490
491    #[tokio::test]
492    async fn test_math_gcd() {
493        let tool = SimpleMathTool::new();
494
495        let input = MathInput {
496            operation: "gcd".to_string(),
497            value: Some(12.0),
498            value2: Some(18.0),
499            base: None,
500        };
501
502        let result = tool.invoke(input).await.unwrap();
503        assert_eq!(result.result, 6.0);
504    }
505
506    #[tokio::test]
507    async fn test_math_lcm() {
508        let tool = SimpleMathTool::new();
509
510        let input = MathInput {
511            operation: "lcm".to_string(),
512            value: Some(4.0),
513            value2: Some(6.0),
514            base: None,
515        };
516
517        let result = tool.invoke(input).await.unwrap();
518        assert_eq!(result.result, 12.0);
519    }
520
521    #[tokio::test]
522    async fn test_math_pi() {
523        let tool = SimpleMathTool::new();
524
525        let input = MathInput {
526            operation: "pi".to_string(),
527            value: None,
528            value2: None,
529            base: None,
530        };
531
532        let result = tool.invoke(input).await.unwrap();
533        assert!((result.result - std::f64::consts::PI).abs() < 0.0001);
534    }
535
536    #[tokio::test]
537    async fn test_math_abs() {
538        let tool = SimpleMathTool::new();
539
540        let input = MathInput {
541            operation: "abs".to_string(),
542            value: Some(-5.0),
543            value2: None,
544            base: None,
545        };
546
547        let result = tool.invoke(input).await.unwrap();
548        assert_eq!(result.result, 5.0);
549    }
550
551    #[tokio::test]
552    async fn test_math_sqrt_negative_error() {
553        let tool = SimpleMathTool::new();
554
555        let input = MathInput {
556            operation: "sqrt".to_string(),
557            value: Some(-4.0),
558            value2: None,
559            base: None,
560        };
561
562        let result = tool.invoke(input).await;
563        assert!(result.is_err());
564    }
565
566    #[tokio::test]
567    async fn test_math_factorial_overflow_error() {
568        let tool = SimpleMathTool::new();
569
570        let input = MathInput {
571            operation: "factorial".to_string(),
572            value: Some(25.0),
573            value2: None,
574            base: None,
575        };
576
577        let result = tool.invoke(input).await;
578        assert!(result.is_err());
579    }
580
581    #[tokio::test]
582    async fn test_math_base_tool_run() {
583        let tool = SimpleMathTool::new();
584
585        let input = "{\"operation\": \"power\", \"value\": 3, \"value2\": 4}".to_string();
586        let result = tool.run(input).await.unwrap();
587
588        assert!(result.contains("81"));
589        assert!(result.contains("3^4"));
590    }
591}