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                "平方根操作要求非负数值".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                "对数操作要求正数值且底数不为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                "自然对数操作要求正数值".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("阶乘操作要求非负整数".to_string()));
140        }
141        if value.is_nan() {
142            return Err(ToolError::InvalidInput(
143                "阶乘操作要求有效数值,不接受 NaN".to_string(),
144            ));
145        }
146        if value != value.floor() {
147            return Err(ToolError::InvalidInput(
148                "阶乘操作要求整数,不接受小数".to_string(),
149            ));
150        }
151        let n = value as u64;
152        if n > 20 {
153            return Err(ToolError::InvalidInput(
154                "阶乘值过大,最大支持20".to_string(),
155            ));
156        }
157        let result = self.compute_factorial(n);
158        Ok(MathOutput {
159            result: result as f64,
160            operation: "factorial".to_string(),
161            details: Some(format!("{}! = {}", n, result)),
162        })
163    }
164
165    fn compute_factorial(&self, n: u64) -> u64 {
166        if n == 0 || n == 1 {
167            1
168        } else {
169            n * self.compute_factorial(n - 1)
170        }
171    }
172
173    fn mod_op(&self, a: f64, b: f64) -> Result<MathOutput, ToolError> {
174        if b == 0.0 {
175            return Err(ToolError::InvalidInput(
176                "取模运算的除数不能为零".to_string(),
177            ));
178        }
179        let result = a % b;
180        Ok(MathOutput {
181            result,
182            operation: "mod".to_string(),
183            details: Some(format!("{} mod {} = {}", a, b, result)),
184        })
185    }
186
187    fn gcd(&self, a: f64, b: f64) -> Result<MathOutput, ToolError> {
188        let a_int = a as i64;
189        let b_int = b as i64;
190
191        if a_int < 0 || b_int < 0 {
192            return Err(ToolError::InvalidInput("GCD 操作要求正整数".to_string()));
193        }
194
195        let result = self.compute_gcd(a_int.abs(), b_int.abs());
196        Ok(MathOutput {
197            result: result as f64,
198            operation: "gcd".to_string(),
199            details: Some(format!("gcd({}, {}) = {}", a_int, b_int, result)),
200        })
201    }
202
203    fn compute_gcd(&self, a: i64, b: i64) -> i64 {
204        if b == 0 {
205            a
206        } else {
207            self.compute_gcd(b, a % b)
208        }
209    }
210
211    fn lcm(&self, a: f64, b: f64) -> Result<MathOutput, ToolError> {
212        let a_int = a as i64;
213        let b_int = b as i64;
214
215        if a_int <= 0 || b_int <= 0 {
216            return Err(ToolError::InvalidInput("LCM 操作要求正整数".to_string()));
217        }
218
219        let gcd = self.compute_gcd(a_int, b_int);
220        let result = (a_int / gcd)
221            .checked_mul(b_int)
222            .ok_or_else(|| ToolError::InvalidInput("LCM 计算结果溢出 i64 范围".to_string()))?;
223        Ok(MathOutput {
224            result: result as f64,
225            operation: "lcm".to_string(),
226            details: Some(format!("lcm({}, {}) = {}", a_int, b_int, result)),
227        })
228    }
229
230    fn pi(&self) -> MathOutput {
231        MathOutput {
232            result: std::f64::consts::PI,
233            operation: "pi".to_string(),
234            details: Some("π ≈ 3.141592653589793".to_string()),
235        }
236    }
237
238    fn e(&self) -> MathOutput {
239        MathOutput {
240            result: std::f64::consts::E,
241            operation: "e".to_string(),
242            details: Some("e ≈ 2.718281828459045".to_string()),
243        }
244    }
245}
246
247impl Default for SimpleMathTool {
248    fn default() -> Self {
249        Self::new()
250    }
251}
252
253#[async_trait]
254impl Tool for SimpleMathTool {
255    type Input = MathInput;
256    type Output = MathOutput;
257
258    async fn invoke(&self, input: Self::Input) -> Result<Self::Output, ToolError> {
259        match input.operation.as_str() {
260            "power" => {
261                let base = input.value.ok_or_else(||
262                    ToolError::InvalidInput("power 操作需要 value 参数作为底数".to_string()))?;
263                let exp = input.value2.ok_or_else(||
264                    ToolError::InvalidInput("power 操作需要 value2 参数作为指数".to_string()))?;
265                self.power(base, exp)
266            }
267            "sqrt" => {
268                let value = input.value.ok_or_else(||
269                    ToolError::InvalidInput("sqrt 操作需要 value 参数".to_string()))?;
270                self.sqrt(value)
271            }
272            "log" => {
273                let value = input.value.ok_or_else(||
274                    ToolError::InvalidInput("log 操作需要 value 参数".to_string()))?;
275                let base = input.base.unwrap_or(10.0);
276                self.log(value, base)
277            }
278            "ln" => {
279                let value = input.value.ok_or_else(||
280                    ToolError::InvalidInput("ln 操作需要 value 参数".to_string()))?;
281                self.ln(value)
282            }
283            "sin" => {
284                let value = input.value.ok_or_else(||
285                    ToolError::InvalidInput("sin 操作需要 value 参数(弧度)".to_string()))?;
286                self.sin(value)
287            }
288            "cos" => {
289                let value = input.value.ok_or_else(||
290                    ToolError::InvalidInput("cos 操作需要 value 参数(弧度)".to_string()))?;
291                self.cos(value)
292            }
293            "tan" => {
294                let value = input.value.ok_or_else(||
295                    ToolError::InvalidInput("tan 操作需要 value 参数(弧度)".to_string()))?;
296                self.tan(value)
297            }
298            "abs" => {
299                let value = input.value.ok_or_else(||
300                    ToolError::InvalidInput("abs 操作需要 value 参数".to_string()))?;
301                self.abs(value)
302            }
303            "factorial" => {
304                let value = input.value.ok_or_else(||
305                    ToolError::InvalidInput("factorial 操作需要 value 参数".to_string()))?;
306                self.factorial(value)
307            }
308            "mod" => {
309                let a = input.value.ok_or_else(||
310                    ToolError::InvalidInput("mod 操作需要 value 参数".to_string()))?;
311                let b = input.value2.ok_or_else(||
312                    ToolError::InvalidInput("mod 操作需要 value2 参数".to_string()))?;
313                self.mod_op(a, b)
314            }
315            "gcd" => {
316                let a = input.value.ok_or_else(||
317                    ToolError::InvalidInput("gcd 操作需要 value 参数".to_string()))?;
318                let b = input.value2.ok_or_else(||
319                    ToolError::InvalidInput("gcd 操作需要 value2 参数".to_string()))?;
320                self.gcd(a, b)
321            }
322            "lcm" => {
323                let a = input.value.ok_or_else(||
324                    ToolError::InvalidInput("lcm 操作需要 value 参数".to_string()))?;
325                let b = input.value2.ok_or_else(||
326                    ToolError::InvalidInput("lcm 操作需要 value2 参数".to_string()))?;
327                self.lcm(a, b)
328            }
329            "pi" => Ok(self.pi()),
330            "e" => Ok(self.e()),
331            _ => Err(ToolError::InvalidInput(
332                format!("不支持的操作: {},请使用: power, sqrt, log, ln, sin, cos, tan, abs, factorial, mod, gcd, lcm, pi, e", input.operation)
333            )),
334        }
335    }
336}
337
338#[async_trait]
339impl BaseTool for SimpleMathTool {
340    fn name(&self) -> &str {
341        "math"
342    }
343
344    fn description(&self) -> &str {
345        "高级数学工具。支持多种数学运算:
346
347操作类型:
348- power: 幂运算 (value^value2)
349- sqrt: 平方根
350- log: 对数(可指定底数,默认为10)
351- ln: 自然对数
352- sin, cos, tan: 三角函数(参数为弧度)
353- abs: 绝对值
354- factorial: 阶乘(最大支持20)
355- mod: 取模运算
356- gcd: 最大公约数
357- lcm: 最小公倍数
358- pi: 圆周率
359- e: 自然常数
360
361示例:
362- 幂运算: {\"operation\": \"power\", \"value\": 2, \"value2\": 10}
363- 平方根: {\"operation\": \"sqrt\", \"value\": 16}
364- 对数: {\"operation\": \"log\", \"value\": 100, \"base\": 10}
365- 三角函数: {\"operation\": \"sin\", \"value\": 1.5708}
366- GCD: {\"operation\": \"gcd\", \"value\": 12, \"value2\": 18}"
367    }
368
369    async fn run(&self, input: String) -> Result<String, ToolError> {
370        let parsed: MathInput = serde_json::from_str(&input)
371            .map_err(|e| ToolError::InvalidInput(format!("JSON 解析失败: {}", e)))?;
372
373        let output = self.invoke(parsed).await?;
374
375        Ok(format!(
376            "结果: {}\n详细信息: {}",
377            output.result,
378            output.details.unwrap_or_default()
379        ))
380    }
381
382    fn args_schema(&self) -> Option<serde_json::Value> {
383        use schemars::schema_for;
384        serde_json::to_value(schema_for!(MathInput)).ok()
385    }
386}
387
388#[cfg(test)]
389mod tests {
390    use super::*;
391
392    #[tokio::test]
393    async fn test_math_power() {
394        let tool = SimpleMathTool::new();
395
396        let input = MathInput {
397            operation: "power".to_string(),
398            value: Some(2.0),
399            value2: Some(10.0),
400            base: None,
401        };
402
403        let result = tool.invoke(input).await.unwrap();
404        assert_eq!(result.result, 1024.0);
405    }
406
407    #[tokio::test]
408    async fn test_math_sqrt() {
409        let tool = SimpleMathTool::new();
410
411        let input = MathInput {
412            operation: "sqrt".to_string(),
413            value: Some(16.0),
414            value2: None,
415            base: None,
416        };
417
418        let result = tool.invoke(input).await.unwrap();
419        assert_eq!(result.result, 4.0);
420    }
421
422    #[tokio::test]
423    async fn test_math_log() {
424        let tool = SimpleMathTool::new();
425
426        let input = MathInput {
427            operation: "log".to_string(),
428            value: Some(100.0),
429            value2: None,
430            base: Some(10.0),
431        };
432
433        let result = tool.invoke(input).await.unwrap();
434        assert_eq!(result.result, 2.0);
435    }
436
437    #[tokio::test]
438    async fn test_math_ln() {
439        let tool = SimpleMathTool::new();
440
441        let input = MathInput {
442            operation: "ln".to_string(),
443            value: Some(std::f64::consts::E),
444            value2: None,
445            base: None,
446        };
447
448        let result = tool.invoke(input).await.unwrap();
449        assert!((result.result - 1.0).abs() < 0.0001);
450    }
451
452    #[tokio::test]
453    async fn test_math_sin() {
454        let tool = SimpleMathTool::new();
455
456        let input = MathInput {
457            operation: "sin".to_string(),
458            value: Some(std::f64::consts::PI / 2.0),
459            value2: None,
460            base: None,
461        };
462
463        let result = tool.invoke(input).await.unwrap();
464        assert!((result.result - 1.0).abs() < 0.0001);
465    }
466
467    #[tokio::test]
468    async fn test_math_factorial() {
469        let tool = SimpleMathTool::new();
470
471        let input = MathInput {
472            operation: "factorial".to_string(),
473            value: Some(5.0),
474            value2: None,
475            base: None,
476        };
477
478        let result = tool.invoke(input).await.unwrap();
479        assert_eq!(result.result, 120.0);
480    }
481
482    #[tokio::test]
483    async fn test_math_gcd() {
484        let tool = SimpleMathTool::new();
485
486        let input = MathInput {
487            operation: "gcd".to_string(),
488            value: Some(12.0),
489            value2: Some(18.0),
490            base: None,
491        };
492
493        let result = tool.invoke(input).await.unwrap();
494        assert_eq!(result.result, 6.0);
495    }
496
497    #[tokio::test]
498    async fn test_math_lcm() {
499        let tool = SimpleMathTool::new();
500
501        let input = MathInput {
502            operation: "lcm".to_string(),
503            value: Some(4.0),
504            value2: Some(6.0),
505            base: None,
506        };
507
508        let result = tool.invoke(input).await.unwrap();
509        assert_eq!(result.result, 12.0);
510    }
511
512    #[tokio::test]
513    async fn test_math_pi() {
514        let tool = SimpleMathTool::new();
515
516        let input = MathInput {
517            operation: "pi".to_string(),
518            value: None,
519            value2: None,
520            base: None,
521        };
522
523        let result = tool.invoke(input).await.unwrap();
524        assert!((result.result - std::f64::consts::PI).abs() < 0.0001);
525    }
526
527    #[tokio::test]
528    async fn test_math_abs() {
529        let tool = SimpleMathTool::new();
530
531        let input = MathInput {
532            operation: "abs".to_string(),
533            value: Some(-5.0),
534            value2: None,
535            base: None,
536        };
537
538        let result = tool.invoke(input).await.unwrap();
539        assert_eq!(result.result, 5.0);
540    }
541
542    #[tokio::test]
543    async fn test_math_sqrt_negative_error() {
544        let tool = SimpleMathTool::new();
545
546        let input = MathInput {
547            operation: "sqrt".to_string(),
548            value: Some(-4.0),
549            value2: None,
550            base: None,
551        };
552
553        let result = tool.invoke(input).await;
554        assert!(result.is_err());
555    }
556
557    #[tokio::test]
558    async fn test_math_factorial_overflow_error() {
559        let tool = SimpleMathTool::new();
560
561        let input = MathInput {
562            operation: "factorial".to_string(),
563            value: Some(25.0),
564            value2: None,
565            base: None,
566        };
567
568        let result = tool.invoke(input).await;
569        assert!(result.is_err());
570    }
571
572    #[tokio::test]
573    async fn test_math_base_tool_run() {
574        let tool = SimpleMathTool::new();
575
576        let input = "{\"operation\": \"power\", \"value\": 3, \"value2\": 4}".to_string();
577        let result = tool.run(input).await.unwrap();
578
579        assert!(result.contains("81"));
580        assert!(result.contains("3^4"));
581    }
582}