RUNLOCALAIv38
->Will it run?Best GPUCompareTroubleshootStartLearnPulseModelsHardwareToolsBench
Run check
RUNLOCALAI

Independently operated catalog for local-AI hardware and software. Hand-written verdicts. Source-cited claims. Reproducible commands when we have them.

OP·Fredoline Eruo
DIR
  • Models
  • Hardware
  • Tools
  • Benchmarks
TOOLS
  • Will it run?
  • Compare hardware
  • Cost vs cloud
  • Choose my GPU
  • Prompting kits
  • Quick answers
REF
  • All buyer guides
  • Learn local AI
  • Methodology
  • Glossary
  • Errors KB
  • Trust
EDITOR
  • About
  • Author
  • How we make money
  • Editorial policy
  • Contact
LEGAL
  • Privacy
  • Terms
  • Sitemap
MAIL · MONTHLY DIGEST
Get monthly local AI changes
Monthly recap. No spam.
DISCLOSURE

Some links on this site are affiliate links (Amazon Associates and other first-class retailers). When you buy through them, we earn a small commission at no extra cost to you. Affiliate links do not influence our verdicts — there are cards we rate highly that we don't have affiliate relationships with, and cards that sell well that we refuse to recommend. Read more →

© 2026 runlocalai.coIndependently operated
RUNLOCALAI · v38
  1. >
  2. Home
  3. /Learn
  4. /Courses
  5. /Introduction to AI Agents
  6. /Ch. 8
Introduction to AI Agents

08. Building a Calculator Tool

Chapter 8 of 16 · 15 min
KEY INSIGHT

Safe evaluation is non-negotiable. Never allow arbitrary Python execution from model output. Whitelist functions and block `__import__`, `eval`, `exec`, and attribute access to prevent code injection.

The calculator tool is the hello-world of agent tools. It lets the model perform precise arithmetic that is error-prone when done in floating point by hand.

Safe evaluation

Never pass user input directly to eval() in a production system. Use a restricted executor that blocks dangerous builtins:

import ast
import operator
import sys

class CalculatorTool(Tool):
    ALLOWED_MODULES = {"math": ["sqrt", "pow", "log10", "sin", "cos", "tan", "pi", "e"],
                       "statistics": ["mean", "median", "stdev"]}
    
    def __init__(self):
        super().__init__(
            name="calculator",
            description=(
                "Evaluate a mathematical expression. Supports basic arithmetic (+, -, *, /, **), "
                "parentheses, and functions from the math module (e.g., sqrt, pow, sin, cos). "
                "Input must be a valid Python expression as a string."
            ),
            input_schema={
                "type": "object",
                "properties": {
                    "expression": {
                        "type": "string",
                        "description": "A valid Python mathematical expression. Example: '(15 + 7) ** 2 / 3'"
                    }
                },
                "required": ["expression"]
            }
        )
    
    def invoke(self, expression: str) -> str:
        # Disallow imports, attribute access, and function calls outside whitelist
        allowed_names = {
            "abs": abs, "min": min, "max": max, "round": round,
            "sqrt": __import__("math").sqrt,
            "pow": pow, "sin": __import__("math").sin,
            "cos": __import__("math").cos, "tan": __import__("math").tan,
            "log10": __import__("math").log10, "pi": __import__("math").pi,
            "e": __import__("math").e,
            **({k: v for k, v in vars(operator).items() if not k.startswith("_")})
        }
        
        try:
            result = eval(expression, {"__builtins__": {}}, allowed_names)
            # Convert numpy types to native Python types for JSON serialization
            if hasattr(result, 'item'):
                result = result.item()
            return str(result)
        except SyntaxError:
            return f"Error: Invalid syntax in expression '{expression}'"
        except NameError as e:
            return f"Error: Unknown function or constant in expression - {e}"
        except ZeroDivisionError:
            return "Error: Division by zero"
        except Exception as e:
            return f"Error: {e}"

Handling edge cases

The calculator must handle:

  • Division by zero → return clear error message
  • Non-numeric input → return syntax error with the problematic string
  • Overflow → return inf instead of crashing

Testing the tool

calc = CalculatorTool()
assert calc.invoke("2**10") == "1024"
assert calc.invoke("(14 + 8) / 2") == "11.0"
assert "division by zero" in calc.invoke("10 / 0").lower()
assert "invalid syntax" in calc.invoke("10 ++ 3").lower()
print("Calculator test suite passed")
EXERCISE

Add support for the statistics.mean function to the calculator. Write tests verifying that valid inputs produce correct results and that invalid inputs (like non-numeric data) fail gracefully.

← Chapter 7
Building a Web Search Tool
Chapter 9 →
Multi-Tool Agents