Skip to main content

langchain_rust/tools/calculator/
mod.rs

1//! A calculator tool: evaluates arithmetic expressions without calling out to anything.
2
3use std::error::Error;
4
5use async_trait::async_trait;
6use serde_json::{json, Value};
7
8use super::Tool;
9
10/// Why an expression couldn't be evaluated.
11#[derive(Debug, Clone, PartialEq, thiserror::Error)]
12#[non_exhaustive]
13pub enum CalculatorError {
14    #[error("unexpected {found} at position {position} in `{expression}`")]
15    Syntax {
16        expression: String,
17        position: usize,
18        found: String,
19    },
20    #[error("unknown name `{0}`; available functions: {FUNCTIONS}; constants: pi, e, tau")]
21    UnknownName(String),
22    #[error("{function}() takes {expected} argument(s), got {got}")]
23    Arity {
24        function: String,
25        expected: &'static str,
26        got: usize,
27    },
28    #[error("division by zero")]
29    DivisionByZero,
30    #[error("the result is not a finite number")]
31    NotFinite,
32    #[error("expression is nested too deeply")]
33    TooDeep,
34}
35
36const FUNCTIONS: &str = "sqrt, cbrt, abs, exp, ln, log (natural, or log(x, base)), log10, log2, \
37    sin, cos, tan, asin, acos, atan, atan2, sinh, cosh, tanh, floor, ceil, round, trunc, \
38    pow, min, max, hypot, factorial";
39
40// One paren level costs two (expression + unary): about 100 nested parentheses.
41const MAX_DEPTH: usize = 200;
42
43/// Evaluates an arithmetic expression.
44///
45/// Supports `+ - * / % //` (floor division), `^` and `**` (power, right associative),
46/// unary signs, parentheses, scientific notation (`1.5e3`), `×`/`÷`, the constants `pi`,
47/// `e` and `tau`, and the functions listed in [`CalculatorError::UnknownName`]. A `math.`
48/// prefix (`math.sqrt(2)`) is accepted, since models often write Python.
49///
50/// ```rust
51/// use langchain_rust::tools::evaluate_expression;
52/// assert_eq!(evaluate_expression("2 + 3 * 4").unwrap(), 14.0);
53/// assert_eq!(evaluate_expression("2 ^ 3 ^ 2").unwrap(), 512.0);
54/// assert_eq!(evaluate_expression("sqrt(16) + max(1, 5, 3)").unwrap(), 9.0);
55/// ```
56pub fn evaluate_expression(expression: &str) -> Result<f64, CalculatorError> {
57    let mut parser = Parser {
58        expression,
59        tokens: tokenize(expression)?,
60        position: 0,
61        depth: 0,
62    };
63    let value = parser.expression()?;
64    if let Some((position, token)) = parser.tokens.get(parser.position) {
65        return Err(parser.unexpected(*position, token));
66    }
67    if value.is_finite() {
68        Ok(value)
69    } else {
70        Err(CalculatorError::NotFinite)
71    }
72}
73
74/// Formats a result the way a person would write it: integers without a decimal point (up
75/// to 1e21, like JavaScript), otherwise at most 12 significant digits so float noise
76/// (`0.1 + 0.2`) doesn't show.
77pub fn format_number(value: f64) -> String {
78    if value == value.trunc() && value.abs() < 1e21 {
79        // `+ 0.0` turns -0 into 0.
80        return format!("{:.0}", value + 0.0);
81    }
82    let rounded: f64 = format!("{value:.11e}").parse().unwrap_or(value);
83    rounded.to_string()
84}
85
86#[derive(Debug, Clone, PartialEq)]
87enum Token {
88    Number(f64),
89    Name(String),
90    Op(&'static str),
91    Open,
92    Close,
93    Comma,
94}
95
96impl std::fmt::Display for Token {
97    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98        match self {
99            Token::Number(n) => write!(f, "number {n}"),
100            Token::Name(n) => write!(f, "`{n}`"),
101            Token::Op(op) => write!(f, "`{op}`"),
102            Token::Open => write!(f, "`(`"),
103            Token::Close => write!(f, "`)`"),
104            Token::Comma => write!(f, "`,`"),
105        }
106    }
107}
108
109fn tokenize(expression: &str) -> Result<Vec<(usize, Token)>, CalculatorError> {
110    let syntax = |position: usize, found: String| CalculatorError::Syntax {
111        expression: expression.to_string(),
112        position,
113        found,
114    };
115    let chars: Vec<(usize, char)> = expression.char_indices().collect();
116    let mut tokens = Vec::new();
117    let mut i = 0;
118    while i < chars.len() {
119        let (position, c) = chars[i];
120        let next = chars.get(i + 1).map(|(_, c)| *c);
121        let (token, len) = match c {
122            c if c.is_whitespace() => {
123                i += 1;
124                continue;
125            }
126            '0'..='9' | '.' => {
127                let mut end = i;
128                while end < chars.len() && (chars[end].1.is_ascii_digit() || chars[end].1 == '.') {
129                    end += 1;
130                }
131                // Exponent: e/E, optional sign, digits.
132                if end < chars.len() && matches!(chars[end].1, 'e' | 'E') {
133                    let mut exp = end + 1;
134                    if exp < chars.len() && matches!(chars[exp].1, '+' | '-') {
135                        exp += 1;
136                    }
137                    if exp < chars.len() && chars[exp].1.is_ascii_digit() {
138                        while exp < chars.len() && chars[exp].1.is_ascii_digit() {
139                            exp += 1;
140                        }
141                        end = exp;
142                    }
143                }
144                let text: String = chars[i..end].iter().map(|(_, c)| c).collect();
145                let number = text
146                    .parse()
147                    .map_err(|_| syntax(position, format!("`{text}`")))?;
148                (Token::Number(number), end - i)
149            }
150            c if c.is_alphabetic() || c == '_' => {
151                let mut end = i;
152                while end < chars.len()
153                    && (chars[end].1.is_alphanumeric() || matches!(chars[end].1, '_' | '.'))
154                {
155                    end += 1;
156                }
157                let text: String = chars[i..end].iter().map(|(_, c)| c).collect();
158                let name = text.strip_prefix("math.").unwrap_or(&text).to_lowercase();
159                (Token::Name(name), end - i)
160            }
161            '*' if next == Some('*') => (Token::Op("^"), 2),
162            '/' if next == Some('/') => (Token::Op("//"), 2),
163            '+' => (Token::Op("+"), 1),
164            '-' | '−' => (Token::Op("-"), 1),
165            '*' | '×' => (Token::Op("*"), 1),
166            '/' | '÷' => (Token::Op("/"), 1),
167            '%' => (Token::Op("%"), 1),
168            '^' => (Token::Op("^"), 1),
169            '(' => (Token::Open, 1),
170            ')' => (Token::Close, 1),
171            ',' => (Token::Comma, 1),
172            other => return Err(syntax(position, format!("`{other}`"))),
173        };
174        tokens.push((position, token));
175        i += len;
176    }
177    Ok(tokens)
178}
179
180struct Parser<'a> {
181    expression: &'a str,
182    tokens: Vec<(usize, Token)>,
183    position: usize,
184    depth: usize,
185}
186
187impl Parser<'_> {
188    fn peek(&self) -> Option<&Token> {
189        self.tokens.get(self.position).map(|(_, t)| t)
190    }
191
192    fn eat_op(&mut self, ops: &[&'static str]) -> Option<&'static str> {
193        match self.peek() {
194            Some(Token::Op(op)) if ops.contains(op) => {
195                let op = *op;
196                self.position += 1;
197                Some(op)
198            }
199            _ => None,
200        }
201    }
202
203    fn unexpected(&self, position: usize, token: &Token) -> CalculatorError {
204        CalculatorError::Syntax {
205            expression: self.expression.to_string(),
206            position,
207            found: token.to_string(),
208        }
209    }
210
211    fn unexpected_here(&self) -> CalculatorError {
212        match self.tokens.get(self.position) {
213            Some((position, token)) => self.unexpected(*position, token),
214            None => CalculatorError::Syntax {
215                expression: self.expression.to_string(),
216                position: self.expression.len(),
217                found: "end of expression".to_string(),
218            },
219        }
220    }
221
222    fn expect(&mut self, token: Token) -> Result<(), CalculatorError> {
223        if self.peek() == Some(&token) {
224            self.position += 1;
225            Ok(())
226        } else {
227            Err(self.unexpected_here())
228        }
229    }
230
231    /// expression := term (("+" | "-") term)*
232    fn expression(&mut self) -> Result<f64, CalculatorError> {
233        self.depth += 1;
234        if self.depth > MAX_DEPTH {
235            return Err(CalculatorError::TooDeep);
236        }
237        let mut value = self.term()?;
238        while let Some(op) = self.eat_op(&["+", "-"]) {
239            let rhs = self.term()?;
240            value = if op == "+" { value + rhs } else { value - rhs };
241        }
242        self.depth -= 1;
243        Ok(value)
244    }
245
246    /// term := unary (("*" | "/" | "//" | "%") unary)*
247    fn term(&mut self) -> Result<f64, CalculatorError> {
248        let mut value = self.unary()?;
249        while let Some(op) = self.eat_op(&["*", "/", "//", "%"]) {
250            let rhs = self.unary()?;
251            if op != "*" && rhs == 0.0 {
252                return Err(CalculatorError::DivisionByZero);
253            }
254            value = match op {
255                "*" => value * rhs,
256                "/" => value / rhs,
257                "//" => (value / rhs).floor(),
258                // Python semantics: the result has the sign of the divisor.
259                _ => value - rhs * (value / rhs).floor(),
260            };
261        }
262        Ok(value)
263    }
264
265    /// unary := ("+" | "-") unary | power. `-2^2` is -(2^2), as in maths and Python.
266    fn unary(&mut self) -> Result<f64, CalculatorError> {
267        self.depth += 1;
268        if self.depth > MAX_DEPTH {
269            return Err(CalculatorError::TooDeep);
270        }
271        let value = match self.eat_op(&["+", "-"]) {
272            Some("-") => -self.unary()?,
273            Some(_) => self.unary()?,
274            None => self.power()?,
275        };
276        self.depth -= 1;
277        Ok(value)
278    }
279
280    /// power := primary ("^" unary)?   (right associative: 2^3^2 = 2^9)
281    fn power(&mut self) -> Result<f64, CalculatorError> {
282        let base = self.primary()?;
283        if self.eat_op(&["^"]).is_some() {
284            let exponent = self.unary()?;
285            return Ok(base.powf(exponent));
286        }
287        Ok(base)
288    }
289
290    /// primary := number | name | name "(" args ")" | "(" expression ")"
291    fn primary(&mut self) -> Result<f64, CalculatorError> {
292        let Some((_, token)) = self.tokens.get(self.position).cloned() else {
293            return Err(self.unexpected_here());
294        };
295        match token {
296            Token::Number(n) => {
297                self.position += 1;
298                Ok(n)
299            }
300            Token::Open => {
301                self.position += 1;
302                let value = self.expression()?;
303                self.expect(Token::Close)?;
304                Ok(value)
305            }
306            Token::Name(name) => {
307                self.position += 1;
308                if self.peek() == Some(&Token::Open) {
309                    self.position += 1;
310                    let mut args = Vec::new();
311                    if self.peek() != Some(&Token::Close) {
312                        args.push(self.expression()?);
313                        while self.peek() == Some(&Token::Comma) {
314                            self.position += 1;
315                            args.push(self.expression()?);
316                        }
317                    }
318                    self.expect(Token::Close)?;
319                    call(&name, &args)
320                } else {
321                    constant(&name)
322                }
323            }
324            _ => Err(self.unexpected_here()),
325        }
326    }
327}
328
329fn constant(name: &str) -> Result<f64, CalculatorError> {
330    match name {
331        "pi" => Ok(std::f64::consts::PI),
332        "e" => Ok(std::f64::consts::E),
333        "tau" => Ok(std::f64::consts::TAU),
334        _ => Err(CalculatorError::UnknownName(name.to_string())),
335    }
336}
337
338fn call(name: &str, args: &[f64]) -> Result<f64, CalculatorError> {
339    let arity = |expected: &'static str| CalculatorError::Arity {
340        function: name.to_string(),
341        expected,
342        got: args.len(),
343    };
344    let one = |f: fn(f64) -> f64| match args {
345        [x] => Ok(f(*x)),
346        _ => Err(arity("1")),
347    };
348    let two = |f: fn(f64, f64) -> f64| match args {
349        [x, y] => Ok(f(*x, *y)),
350        _ => Err(arity("2")),
351    };
352    match name {
353        "sqrt" => one(f64::sqrt),
354        "cbrt" => one(f64::cbrt),
355        "abs" => one(f64::abs),
356        "exp" => one(f64::exp),
357        "ln" => one(f64::ln),
358        "log" => match args {
359            [x] => Ok(x.ln()),
360            [x, base] => Ok(x.log(*base)),
361            _ => Err(arity("1 or 2")),
362        },
363        "log10" => one(f64::log10),
364        "log2" => one(f64::log2),
365        "sin" => one(f64::sin),
366        "cos" => one(f64::cos),
367        "tan" => one(f64::tan),
368        "asin" => one(f64::asin),
369        "acos" => one(f64::acos),
370        "atan" => one(f64::atan),
371        "atan2" => two(f64::atan2),
372        "sinh" => one(f64::sinh),
373        "cosh" => one(f64::cosh),
374        "tanh" => one(f64::tanh),
375        "floor" => one(f64::floor),
376        "ceil" => one(f64::ceil),
377        // Half away from zero, like most calculators.
378        "round" => match args {
379            [x] => Ok(x.round()),
380            [x, digits] => {
381                let scale = 10f64.powi(*digits as i32);
382                Ok((x * scale).round() / scale)
383            }
384            _ => Err(arity("1 or 2")),
385        },
386        "trunc" => one(f64::trunc),
387        "pow" => two(f64::powf),
388        "hypot" => two(f64::hypot),
389        "min" | "max" if args.is_empty() => Err(arity("at least 1")),
390        "min" => Ok(args.iter().copied().fold(f64::INFINITY, f64::min)),
391        "max" => Ok(args.iter().copied().fold(f64::NEG_INFINITY, f64::max)),
392        "factorial" => match args {
393            [n] if *n >= 0.0 && n.fract() == 0.0 && *n <= 170.0 => {
394                Ok((1..=*n as u32).map(f64::from).product())
395            }
396            [_] => Err(CalculatorError::NotFinite),
397            _ => Err(arity("1")),
398        },
399        _ => Err(CalculatorError::UnknownName(name.to_string())),
400    }
401}
402
403/// A tool that evaluates math expressions, so agents don't do arithmetic in their heads.
404///
405/// The input is an expression such as `(1234.5 * 3) / sqrt(2)`; see
406/// [`evaluate_expression`] for the syntax. Invalid expressions come back as tool errors
407/// that say what's wrong, so the model can fix the expression and retry.
408#[derive(Debug, Clone, Default)]
409pub struct Calculator;
410
411impl Calculator {
412    pub fn new() -> Self {
413        Self
414    }
415}
416
417#[async_trait]
418impl Tool for Calculator {
419    fn name(&self) -> String {
420        "calculator".to_string()
421    }
422
423    fn description(&self) -> String {
424        "Evaluates a math expression and returns the result. Use it for any arithmetic. \
425         Supports + - * / % ^ (power), parentheses, the constants pi and e, and functions \
426         like sqrt, abs, ln, log10, sin, cos, round, min, max and factorial. \
427         Example input: (1234.5 * 3) / sqrt(2)"
428            .to_string()
429    }
430
431    fn parameters(&self) -> Value {
432        json!({
433            "type": "object",
434            "properties": {
435                "input": {
436                    "type": "string",
437                    "description": "The math expression to evaluate, e.g. (1234.5 * 3) / sqrt(2)"
438                }
439            },
440            "required": ["input"]
441        })
442    }
443
444    async fn run(&self, input: Value) -> Result<String, Box<dyn Error>> {
445        // The default `parse_input` hands over a string; anything else is tried as text.
446        let expression = input
447            .as_str()
448            .map(str::to_string)
449            .unwrap_or_else(|| input.to_string());
450        let expression = expression.trim().trim_matches('`').trim();
451        Ok(format_number(evaluate_expression(expression)?))
452    }
453}
454
455#[cfg(test)]
456mod tests;