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(&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 #[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}