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