Skip to main content

lc_tools/
calculator.rs

1// lc-tools/src/calculator.rs
2//! Calculator tool
3//!
4//! A math expression calculator using the meval crate.
5
6use async_trait::async_trait;
7use lc_core::tools::{BaseTool, Tool, ToolError};
8use schemars::JsonSchema;
9use serde::{Deserialize, Serialize};
10
11/// Calculator input
12#[derive(Debug, Deserialize, JsonSchema)]
13pub struct CalculatorInput {
14    /// Math expression (e.g., "2 + 3", "sqrt(16)", "3.14 * 10", "2 + 3 * 4")
15    pub expression: String,
16}
17
18/// Calculator output
19#[derive(Debug, Serialize)]
20pub struct CalculatorOutput {
21    /// Calculation result
22    pub result: f64,
23
24    /// Original expression
25    pub expression: String,
26}
27
28/// Calculator tool
29///
30/// Evaluates math expressions using the meval crate.
31/// Supports: basic arithmetic (+, -, *, /), power (^), functions (sin, cos, tan, sqrt, log, exp, abs), constants (pi, e).
32pub struct Calculator;
33
34impl Calculator {
35    pub fn new() -> Self {
36        Self
37    }
38}
39
40impl Default for Calculator {
41    fn default() -> Self {
42        Self::new()
43    }
44}
45
46/// Implement Tool trait (type-safe version)
47#[async_trait]
48impl Tool for Calculator {
49    type Input = CalculatorInput;
50    type Output = CalculatorOutput;
51
52    async fn invoke(&self, input: Self::Input) -> Result<Self::Output, ToolError> {
53        let result = Self::evaluate_expression(&input.expression)?;
54
55        Ok(CalculatorOutput {
56            result,
57            expression: input.expression,
58        })
59    }
60}
61
62/// Implement BaseTool trait (string version, for Agent)
63#[async_trait]
64impl BaseTool for Calculator {
65    fn name(&self) -> &str {
66        "calculator"
67    }
68
69    fn description(&self) -> &str {
70        "Calculate math expressions. Supports basic arithmetic, power, trig functions, sqrt, log, exp, abs, and constants (pi, e).
71
72Examples:
73- '2 + 3' -> 5
74- '2 + 3 * 4' -> 14
75- 'sqrt(16)' -> 4
76- '3.14 * 10' -> 31.4
77- 'sin(pi/2)' -> 1
78- '2^10' -> 1024
79- 'log(e)' -> 1
80
81Input format: JSON object with 'expression' field
82Example: {\"expression\": \"2 + 3\"}"
83    }
84
85    async fn run(&self, input: String) -> Result<String, ToolError> {
86        // Parse input
87        let parsed: CalculatorInput = serde_json::from_str(&input)
88            .map_err(|e| ToolError::InvalidInput(format!("JSON parse error: {}", e)))?;
89
90        // Execute calculation
91        let output = self.invoke(parsed).await?;
92
93        // Return result string
94        Ok(format!("{} = {}", output.expression, output.result))
95    }
96
97    fn args_schema(&self) -> Option<serde_json::Value> {
98        use schemars::schema_for;
99        serde_json::to_value(schema_for!(CalculatorInput)).ok()
100    }
101}
102
103impl Calculator {
104    /// Evaluate a math expression using meval
105    fn evaluate_expression(expr: &str) -> Result<f64, ToolError> {
106        let expr = expr.trim();
107
108        // Try parsing as a plain number first
109        if let Ok(num) = expr.parse::<f64>() {
110            return Ok(num);
111        }
112
113        // Use meval to parse and evaluate
114        meval::eval_str(expr).map_err(|e| {
115            ToolError::ExecutionFailed(format!("Failed to evaluate expression '{}': {}", expr, e))
116        })
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    #[test]
125    fn test_basic_addition() {
126        assert_eq!(Calculator::evaluate_expression("2 + 3").unwrap(), 5.0);
127    }
128
129    #[test]
130    fn test_operator_precedence() {
131        // 2 + 3 * 4 = 14 (not 20)
132        assert_eq!(Calculator::evaluate_expression("2 + 3 * 4").unwrap(), 14.0);
133    }
134
135    #[test]
136    fn test_chained_addition() {
137        assert_eq!(Calculator::evaluate_expression("1 + 2 + 3").unwrap(), 6.0);
138    }
139
140    #[test]
141    fn test_subtraction() {
142        assert_eq!(Calculator::evaluate_expression("10 - 3").unwrap(), 7.0);
143    }
144
145    #[test]
146    fn test_multiplication() {
147        let result = Calculator::evaluate_expression("3.14 * 10").unwrap();
148        assert!((result - 31.4).abs() < 1e-10);
149    }
150
151    #[test]
152    fn test_division() {
153        let result = Calculator::evaluate_expression("10 / 3").unwrap();
154        assert!((result - 3.3333333333333335).abs() < 1e-10);
155    }
156
157    #[test]
158    fn test_power() {
159        assert_eq!(Calculator::evaluate_expression("2^10").unwrap(), 1024.0);
160    }
161
162    #[test]
163    fn test_sqrt() {
164        assert_eq!(Calculator::evaluate_expression("sqrt(16)").unwrap(), 4.0);
165    }
166
167    #[test]
168    fn test_sin_pi() {
169        let result = Calculator::evaluate_expression("sin(pi/2)").unwrap();
170        assert!((result - 1.0).abs() < 1e-10);
171    }
172
173    #[test]
174    fn test_plain_number() {
175        assert_eq!(Calculator::evaluate_expression("42").unwrap(), 42.0);
176    }
177
178    #[test]
179    fn test_invalid_expression() {
180        assert!(Calculator::evaluate_expression("hello").is_err());
181    }
182
183    #[tokio::test]
184    async fn test_tool_run() {
185        let tool = Calculator::new();
186        let result = tool
187            .run(r#"{"expression": "2 + 3"}"#.to_string())
188            .await
189            .unwrap();
190        assert!(result.contains("5"));
191    }
192}