"""
Enhanced Calculator Tool for Gemini LLM Integration.
Provides advanced mathematical calculation capabilities with improved natural language processing.
"""
import logging
import re
from typing import Dict, Any, Optional
try:
from src.tools import ToolDefinition
except ImportError:
from tools import ToolDefinition
logger = logging.getLogger(__name__)
class CalculatorTool:
"""Enhanced tool for performing mathematical calculations with natural language support."""
def __init__(self):
logger.info("Enhanced calculator tool initialized")
self._initialize_safe_operations()
def _initialize_safe_operations(self):
"""Initialize safe mathematical operations."""
self.safe_dict = {
'__builtins__': {},
'abs': abs,
'round': round,
'min': min,
'max': max,
'sum': sum,
'len': len,
'int': int,
'float': float,
'str': str,
'bool': bool,
'pow': pow,
'divmod': divmod,
}
# Additional safe mathematical functions
import math
safe_math_functions = [
'sqrt', 'ceil', 'floor', 'trunc', 'exp', 'log', 'log10',
'sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'degrees', 'radians',
'pi', 'e'
]
for func_name in safe_math_functions:
if hasattr(math, func_name):
self.safe_dict[func_name] = getattr(math, func_name)
async def calculate(self, expression: str) -> str:
"""Calculate mathematical expressions safely with enhanced natural language processing.
This tool can perform advanced mathematical calculations including:
- Basic arithmetic: addition, subtraction, multiplication, division
- Complex expressions with parentheses and order of operations
- Mathematical functions: abs(), round(), min(), max(), sqrt(), sin(), cos(), etc.
- Natural language math: "add 15 and 27", "what is 100 minus 45"
- Safe evaluation with comprehensive error handling
Args:
expression: Mathematical expression to evaluate (e.g., "2 + 3 * 4", "add 15 and 27")
Returns:
The calculated result or detailed error message
"""
logger.info(f"calculate called with expression: {expression}")
try:
if not expression or not expression.strip():
raise ValueError("Expression cannot be empty")
expression = expression.strip()
# Extract mathematical expression from natural language
math_expression = self._extract_math_expression(expression)
# Validate the extracted expression
if not math_expression:
raise ValueError("No mathematical expression found in the query")
# Security validation
self._validate_expression_security(math_expression)
# Evaluate the expression safely
result = self._safe_evaluate(math_expression)
# Format the result
formatted_result = self._format_result(result, math_expression)
logger.info(f"Calculation completed successfully: {math_expression} = {result}")
return formatted_result
except Exception as e:
logger.error(f"Error in calculation: {e}")
raise RuntimeError(f"Calculation failed: {str(e)}")
def _extract_math_expression(self, query: str) -> str:
"""Extract mathematical expression from natural language query with enhanced parsing."""
import re
query_lower = query.lower()
# Enhanced mathematical patterns
math_patterns = [
# Direct mathematical expressions
r'(\d+[\+\-\*\/\s]+\d+)', # Basic arithmetic: 15 + 27
r'(\d+\s*[\+\-\*\/]\s*\d+)', # With spaces: 15 + 27
r'(\d+\s*[\+\-\*\/]\s*\d+\s*[\+\-\*\/]\s*\d+)', # Multiple operations: 15 + 27 * 3
# Mathematical functions
r'(abs\([^)]+\))', # Absolute value: abs(-42)
r'(round\([^)]+\))', # Round function: round(3.7)
r'(min\([^)]+\))', # Min function: min(5, 10, 3)
r'(max\([^)]+\))', # Max function: max(20, 15, 30)
r'(sqrt\([^)]+\))', # Square root: sqrt(16)
r'(sin\([^)]+\))', # Sine: sin(45)
r'(cos\([^)]+\))', # Cosine: cos(30)
r'(tan\([^)]+\))', # Tangent: tan(60)
# Complex expressions with parentheses
r'(\([^)]*[\+\-\*\/][^)]*\))', # Parenthesized expressions
r'(\d+\s*[\+\-\*\/]\s*\([^)]+\))', # Operations with parentheses
r'(\([^)]+\)\s*[\+\-\*\/]\s*\d+)', # Parentheses with operations
]
# Try to match mathematical patterns
for pattern in math_patterns:
match = re.search(pattern, query)
if match:
return match.group(1)
# Handle natural language math with enhanced parsing
return self._parse_natural_language_math(query)
def _parse_natural_language_math(self, query: str) -> str:
"""Parse natural language mathematical expressions."""
import re
query_lower = query.lower()
# Extract all numbers from the query
numbers = re.findall(r'-?\d+(?:\.\d+)?', query)
if len(numbers) < 2:
raise ValueError(f"Need at least 2 numbers for calculation in: '{query}'")
# Handle different mathematical operations
if any(word in query_lower for word in ['add', 'plus', 'sum', '+']):
return f"{numbers[0]} + {numbers[1]}"
elif any(word in query_lower for word in ['subtract', 'minus', 'difference', '-']):
return f"{numbers[0]} - {numbers[1]}"
elif any(word in query_lower for word in ['multiply', 'times', 'product', '*']):
return f"{numbers[0]} * {numbers[1]}"
elif any(word in query_lower for word in ['divide', 'division', 'quotient', '/']):
return f"{numbers[0]} / {numbers[1]}"
elif any(word in query_lower for word in ['power', 'exponent', 'raised', '^']):
return f"{numbers[0]} ** {numbers[1]}"
elif any(word in query_lower for word in ['absolute', 'abs']):
return f"abs({numbers[0]})"
elif any(word in query_lower for word in ['round']):
return f"round({numbers[0]})"
elif any(word in query_lower for word in ['minimum', 'min']):
return f"min({', '.join(numbers[:3])})" # Use up to 3 numbers
elif any(word in query_lower for word in ['maximum', 'max']):
return f"max({', '.join(numbers[:3])})" # Use up to 3 numbers
elif any(word in query_lower for word in ['square root', 'sqrt']):
return f"sqrt({numbers[0]})"
# If no specific operation is mentioned, try to extract mathematical operators
operators = re.findall(r'[\+\-\*\/\^]', query)
if operators and len(numbers) >= 2:
return f"{numbers[0]} {operators[0]} {numbers[1]}"
# Last resort: try to extract just numbers and operators
math_chars = re.findall(r'[\d\+\-\*\/\(\)\.]', query)
if len(math_chars) >= 3: # At least 2 numbers and 1 operator
return ''.join(math_chars)
raise ValueError(f"Could not extract mathematical expression from: '{query}'. Please provide a clear mathematical expression or operation.")
def _validate_expression_security(self, expression: str):
"""Validate expression for security concerns."""
dangerous_keywords = [
'import', 'exec', 'eval', 'open', 'file', '__', 'globals', 'locals',
'compile', 'input', 'raw_input', 'system', 'subprocess', 'os.', 'sys.'
]
expression_lower = expression.lower()
for keyword in dangerous_keywords:
if keyword in expression_lower:
raise ValueError(f"'{keyword}' is not allowed for security reasons")
# Check for potentially dangerous function calls
dangerous_functions = ['eval', 'exec', 'compile', 'input']
for func in dangerous_functions:
if f"{func}(" in expression_lower:
raise ValueError(f"Function '{func}' is not allowed for security reasons")
def _safe_evaluate(self, expression: str):
"""Safely evaluate mathematical expression."""
try:
# Replace common mathematical symbols
expression = expression.replace('^', '**') # Handle power operator
# Evaluate with restricted environment
result = eval(expression, {"__builtins__": {}}, self.safe_dict)
# Validate result type
if not isinstance(result, (int, float, complex)):
raise ValueError("Result must be a number")
return result
except ZeroDivisionError:
raise ValueError("Division by zero is not allowed")
except ValueError as e:
raise ValueError(f"Invalid mathematical expression: {str(e)}")
except SyntaxError as e:
raise ValueError(f"Invalid syntax in mathematical expression: {str(e)}")
except Exception as e:
raise ValueError(f"Error evaluating expression: {str(e)}")
def _format_result(self, result, original_expression: str) -> str:
"""Format the calculation result."""
if isinstance(result, complex):
return f"Result: {result} (complex number)"
elif isinstance(result, float):
# Format float with appropriate precision
if result.is_integer():
return f"Result: {int(result)}"
else:
return f"Result: {result:.6g}" # Use significant figures
else:
return f"Result: {result}"
# Global instance
calculator_tool = CalculatorTool()
def register_tool() -> ToolDefinition:
"""Register the enhanced calculator tool."""
return ToolDefinition(
name="calculate",
description="Calculate mathematical expressions safely with natural language support. Supports basic arithmetic, complex expressions, mathematical functions (abs, round, min, max, sqrt, sin, cos, etc.), and natural language queries.",
handler=calculator_tool.calculate,
input_schema={
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Mathematical expression to evaluate (e.g., '2 + 3 * 4', 'add 15 and 27', 'abs(-5)', 'round(3.7)')"
}
},
"required": ["expression"]
},
examples=[
"What is 15 + 27?",
"Calculate 100 - 45",
"What is 12 * 8?",
"Add 15 and 27",
"What is the absolute value of -42?",
"Round 3.7 to the nearest integer",
"What is the minimum of 5, 10, and 3?",
"Calculate the square root of 16"
]
)