import ast import operator from langchain.tools import StructuredTool from langchain_core.tools import ToolException from loguru import logger from pydantic import BaseModel, Field from langflow.base.langchain_utilities.model import LCToolComponent from langflow.field_typing import Tool from langflow.inputs import MessageTextInput from langflow.schema import Data class CalculatorToolComponent(LCToolComponent): display_name = "Calculator" description = "Perform basic arithmetic operations on a given expression." icon = "calculator" name = "CalculatorTool" inputs = [ MessageTextInput( name="expression", display_name="Expression", info="The arithmetic expression to evaluate (e.g., '4*4*(33/22)+12-20').", ), ] class CalculatorToolSchema(BaseModel): expression: str = Field(..., description="The arithmetic expression to evaluate.") def run_model(self) -> list[Data]: return self._evaluate_expression(self.expression) def build_tool(self) -> Tool: return StructuredTool.from_function( name="calculator", description="Evaluate basic arithmetic expressions. Input should be a string containing the expression.", func=self._eval_expr_with_error, args_schema=self.CalculatorToolSchema, ) def _eval_expr(self, node): # Define the allowed operators operators = { ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv, ast.Pow: operator.pow, } if isinstance(node, ast.Num): return node.n if isinstance(node, ast.BinOp): return operators[type(node.op)](self._eval_expr(node.left), self._eval_expr(node.right)) if isinstance(node, ast.UnaryOp): return operators[type(node.op)](self._eval_expr(node.operand)) if isinstance(node, ast.Call): msg = ( "Function calls like sqrt(), sin(), cos() etc. are not supported. " "Only basic arithmetic operations (+, -, *, /, **) are allowed." ) raise TypeError(msg) msg = f"Unsupported operation or expression type: {type(node).__name__}" raise TypeError(msg) def _eval_expr_with_error(self, expression: str) -> list[Data]: try: return self._evaluate_expression(expression) except Exception as e: raise ToolException(str(e)) from e def _evaluate_expression(self, expression: str) -> list[Data]: try: # Parse the expression and evaluate it tree = ast.parse(expression, mode="eval") result = self._eval_expr(tree.body) # Format the result to a reasonable number of decimal places formatted_result = f"{result:.6f}".rstrip("0").rstrip(".") self.status = formatted_result return [Data(data={"result": formatted_result})] except (SyntaxError, TypeError, KeyError) as e: error_message = f"Invalid expression: {e}" self.status = error_message return [Data(data={"error": error_message, "input": expression})] except ZeroDivisionError: error_message = "Error: Division by zero" self.status = error_message return [Data(data={"error": error_message, "input": expression})] except Exception as e: # noqa: BLE001 logger.opt(exception=True).debug("Error evaluating expression") error_message = f"Error: {e}" self.status = error_message return [Data(data={"error": error_message, "input": expression})]