Skip to main content

lc_tools/
calculator.rs

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