featur: Add CalculatorToolComponent (#3772)

* feat: Add CalculatorToolComponent to langflow/components/tools

This commit adds the CalculatorToolComponent to the langflow/components/tools directory. The CalculatorToolComponent allows users to perform basic arithmetic operations on a given expression. It includes an input for the expression and returns the result or an error message.

Ref: #3757

* lint

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
anovazzi1 2024-09-11 18:48:24 -03:00 committed by GitHub
commit 92f59ab9e0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 92 additions and 17 deletions

5
poetry.lock generated
View file

@ -10605,11 +10605,6 @@ files = [
{file = "triton-3.0.0-1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:34e509deb77f1c067d8640725ef00c5cbfcb2052a1a3cb6a6d343841f92624eb"},
{file = "triton-3.0.0-1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bcbf3b1c48af6a28011a5c40a5b3b9b5330530c3827716b5fbf6d7adcc1e53e9"},
{file = "triton-3.0.0-1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6e5727202f7078c56f91ff13ad0c1abab14a0e7f2c87e91b12b6f64f3e8ae609"},
{file = "triton-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39b052da883351fdf6be3d93cedae6db3b8e3988d3b09ed221bccecfa9612230"},
{file = "triton-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd34f19a8582af96e6291d4afce25dac08cb2a5d218c599163761e8e0827208e"},
{file = "triton-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d5e10de8c011adeb7c878c6ce0dd6073b14367749e34467f1cff2bde1b78253"},
{file = "triton-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8903767951bf86ec960b4fe4e21bc970055afc65e9d57e916d79ae3c93665e3"},
{file = "triton-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41004fb1ae9a53fcb3e970745feb87f0e3c94c6ce1ba86e95fa3b8537894bef7"},
]
[package.dependencies]

View file

@ -0,0 +1,82 @@
import ast
import operator
from typing import List
from pydantic import BaseModel, Field
from langflow.base.langchain_utilities.model import LCToolComponent
from langflow.inputs import MessageTextInput
from langflow.schema import Data
from langflow.field_typing import Tool
from langchain.tools import StructuredTool
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._evaluate_expression,
args_schema=self.CalculatorToolSchema,
)
def _evaluate_expression(self, expression: str) -> List[Data]:
try:
# 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,
}
def eval_expr(node):
if isinstance(node, ast.Num):
return node.n
elif isinstance(node, ast.BinOp):
return operators[type(node.op)](eval_expr(node.left), eval_expr(node.right))
elif isinstance(node, ast.UnaryOp):
return operators[type(node.op)](eval_expr(node.operand))
else:
raise TypeError(node)
# Parse the expression and evaluate it
tree = ast.parse(expression, mode="eval")
result = 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: {str(e)}"
self.status = error_message
return [Data(data={"error": error_message})]
except ZeroDivisionError:
error_message = "Error: Division by zero"
self.status = error_message
return [Data(data={"error": error_message})]
except Exception as e:
error_message = f"Error: {str(e)}"
self.status = error_message
return [Data(data={"error": error_message})]

View file

@ -10,6 +10,7 @@ from .SearXNGTool import SearXNGToolComponent
from .SerpAPI import SerpAPIComponent
from .WikipediaAPI import WikipediaAPIComponent
from .WolframAlphaAPI import WolframAlphaAPIComponent
from .Calculator import CalculatorToolComponent
__all__ = [
@ -25,4 +26,5 @@ __all__ = [
"SerpAPIComponent",
"WikipediaAPIComponent",
"WolframAlphaAPIComponent",
"CalculatorToolComponent",
]

View file

@ -1159,7 +1159,7 @@
"description": "Perform basic arithmetic operations on a given expression.",
"display_name": "Calculator",
"documentation": "",
"edited": true,
"edited": false,
"field_order": [
"expression"
],
@ -1176,8 +1176,7 @@
"name": "api_run_model",
"selected": "Data",
"types": [
"Data",
"list"
"Data"
],
"value": "__UNDEFINED__"
},
@ -1188,8 +1187,7 @@
"name": "api_build_tool",
"selected": "Tool",
"types": [
"Tool",
"Sequence"
"Tool"
],
"value": "__UNDEFINED__"
}
@ -1213,7 +1211,7 @@
"show": true,
"title_case": false,
"type": "code",
"value": "import ast\r\nimport operator\r\nfrom typing import List\r\nfrom pydantic import BaseModel, Field\r\nfrom langflow.base.langchain_utilities.model import LCToolComponent\r\nfrom langflow.inputs import MessageTextInput\r\nfrom langflow.schema import Data\r\nfrom langflow.field_typing import Tool\r\nfrom langchain.tools import StructuredTool\r\n\r\nclass CalculatorToolComponent(LCToolComponent):\r\n display_name = \"Calculator\"\r\n description = \"Perform basic arithmetic operations on a given expression.\"\r\n icon = \"calculator\"\r\n name = \"CalculatorTool\"\r\n\r\n inputs = [\r\n MessageTextInput(\r\n name=\"expression\",\r\n display_name=\"Expression\",\r\n info=\"The arithmetic expression to evaluate (e.g., '4*4*(33/22)+12-20').\",\r\n ),\r\n ]\r\n\r\n class CalculatorToolSchema(BaseModel):\r\n expression: str = Field(..., description=\"The arithmetic expression to evaluate.\")\r\n\r\n def run_model(self) -> List[Data]:\r\n return self._evaluate_expression(self.expression)\r\n\r\n def build_tool(self) -> Tool:\r\n return StructuredTool.from_function(\r\n name=\"calculator\",\r\n description=\"Evaluate basic arithmetic expressions. Input should be a string containing the expression.\",\r\n func=self._evaluate_expression,\r\n args_schema=self.CalculatorToolSchema,\r\n )\r\n\r\n def _evaluate_expression(self, expression: str) -> List[Data]:\r\n try:\r\n # Define the allowed operators\r\n operators = {\r\n ast.Add: operator.add,\r\n ast.Sub: operator.sub,\r\n ast.Mult: operator.mul,\r\n ast.Div: operator.truediv,\r\n ast.Pow: operator.pow,\r\n }\r\n\r\n def eval_expr(node):\r\n if isinstance(node, ast.Num):\r\n return node.n\r\n elif isinstance(node, ast.BinOp):\r\n return operators[type(node.op)](eval_expr(node.left), eval_expr(node.right))\r\n elif isinstance(node, ast.UnaryOp):\r\n return operators[type(node.op)](eval_expr(node.operand))\r\n else:\r\n raise TypeError(node)\r\n\r\n # Parse the expression and evaluate it\r\n tree = ast.parse(expression, mode='eval')\r\n result = eval_expr(tree.body)\r\n\r\n # Format the result to a reasonable number of decimal places\r\n formatted_result = f\"{result:.6f}\".rstrip('0').rstrip('.')\r\n\r\n self.status = formatted_result\r\n return [Data(data={\"result\": formatted_result})]\r\n\r\n except (SyntaxError, TypeError, KeyError) as e:\r\n error_message = f\"Invalid expression: {str(e)}\"\r\n self.status = error_message\r\n return [Data(data={\"error\": error_message})]\r\n except ZeroDivisionError:\r\n error_message = \"Error: Division by zero\"\r\n self.status = error_message\r\n return [Data(data={\"error\": error_message})]\r\n except Exception as e:\r\n error_message = f\"Error: {str(e)}\"\r\n self.status = error_message\r\n return [Data(data={\"error\": error_message})]"
"value": "import ast\nimport operator\nfrom typing import List\nfrom pydantic import BaseModel, Field\nfrom langflow.base.langchain_utilities.model import LCToolComponent\nfrom langflow.inputs import MessageTextInput\nfrom langflow.schema import Data\nfrom langflow.field_typing import Tool\nfrom langchain.tools import StructuredTool\n\n\nclass CalculatorToolComponent(LCToolComponent):\n display_name = \"Calculator\"\n description = \"Perform basic arithmetic operations on a given expression.\"\n icon = \"calculator\"\n name = \"CalculatorTool\"\n\n inputs = [\n MessageTextInput(\n name=\"expression\",\n display_name=\"Expression\",\n info=\"The arithmetic expression to evaluate (e.g., '4*4*(33/22)+12-20').\",\n ),\n ]\n\n class CalculatorToolSchema(BaseModel):\n expression: str = Field(..., description=\"The arithmetic expression to evaluate.\")\n\n def run_model(self) -> List[Data]:\n return self._evaluate_expression(self.expression)\n\n def build_tool(self) -> Tool:\n return StructuredTool.from_function(\n name=\"calculator\",\n description=\"Evaluate basic arithmetic expressions. Input should be a string containing the expression.\",\n func=self._evaluate_expression,\n args_schema=self.CalculatorToolSchema,\n )\n\n def _evaluate_expression(self, expression: str) -> List[Data]:\n try:\n # Define the allowed operators\n operators = {\n ast.Add: operator.add,\n ast.Sub: operator.sub,\n ast.Mult: operator.mul,\n ast.Div: operator.truediv,\n ast.Pow: operator.pow,\n }\n\n def eval_expr(node):\n if isinstance(node, ast.Num):\n return node.n\n elif isinstance(node, ast.BinOp):\n return operators[type(node.op)](eval_expr(node.left), eval_expr(node.right))\n elif isinstance(node, ast.UnaryOp):\n return operators[type(node.op)](eval_expr(node.operand))\n else:\n raise TypeError(node)\n\n # Parse the expression and evaluate it\n tree = ast.parse(expression, mode=\"eval\")\n result = eval_expr(tree.body)\n\n # Format the result to a reasonable number of decimal places\n formatted_result = f\"{result:.6f}\".rstrip(\"0\").rstrip(\".\")\n\n self.status = formatted_result\n return [Data(data={\"result\": formatted_result})]\n\n except (SyntaxError, TypeError, KeyError) as e:\n error_message = f\"Invalid expression: {str(e)}\"\n self.status = error_message\n return [Data(data={\"error\": error_message})]\n except ZeroDivisionError:\n error_message = \"Error: Division by zero\"\n self.status = error_message\n return [Data(data={\"error\": error_message})]\n except Exception as e:\n error_message = f\"Error: {str(e)}\"\n self.status = error_message\n return [Data(data={\"error\": error_message})]\n"
},
"expression": {
"_input_type": "MessageTextInput",

View file

@ -2258,7 +2258,7 @@
"description": "Perform basic arithmetic operations on a given expression.",
"display_name": "Calculator",
"documentation": "",
"edited": true,
"edited": false,
"field_order": [
"expression"
],
@ -2275,8 +2275,7 @@
"name": "api_run_model",
"selected": "Data",
"types": [
"Data",
"list"
"Data"
],
"value": "__UNDEFINED__"
},
@ -2287,8 +2286,7 @@
"name": "api_build_tool",
"selected": "Tool",
"types": [
"Tool",
"Sequence"
"Tool"
],
"value": "__UNDEFINED__"
}
@ -2312,7 +2310,7 @@
"show": true,
"title_case": false,
"type": "code",
"value": "import ast\r\nimport operator\r\nfrom typing import List\r\nfrom pydantic import BaseModel, Field\r\nfrom langflow.base.langchain_utilities.model import LCToolComponent\r\nfrom langflow.inputs import MessageTextInput\r\nfrom langflow.schema import Data\r\nfrom langflow.field_typing import Tool\r\nfrom langchain.tools import StructuredTool\r\n\r\nclass CalculatorToolComponent(LCToolComponent):\r\n display_name = \"Calculator\"\r\n description = \"Perform basic arithmetic operations on a given expression.\"\r\n icon = \"calculator\"\r\n name = \"CalculatorTool\"\r\n\r\n inputs = [\r\n MessageTextInput(\r\n name=\"expression\",\r\n display_name=\"Expression\",\r\n info=\"The arithmetic expression to evaluate (e.g., '4*4*(33/22)+12-20').\",\r\n ),\r\n ]\r\n\r\n class CalculatorToolSchema(BaseModel):\r\n expression: str = Field(..., description=\"The arithmetic expression to evaluate.\")\r\n\r\n def run_model(self) -> List[Data]:\r\n return self._evaluate_expression(self.expression)\r\n\r\n def build_tool(self) -> Tool:\r\n return StructuredTool.from_function(\r\n name=\"calculator\",\r\n description=\"Evaluate basic arithmetic expressions. Input should be a string containing the expression.\",\r\n func=self._evaluate_expression,\r\n args_schema=self.CalculatorToolSchema,\r\n )\r\n\r\n def _evaluate_expression(self, expression: str) -> List[Data]:\r\n try:\r\n # Define the allowed operators\r\n operators = {\r\n ast.Add: operator.add,\r\n ast.Sub: operator.sub,\r\n ast.Mult: operator.mul,\r\n ast.Div: operator.truediv,\r\n ast.Pow: operator.pow,\r\n }\r\n\r\n def eval_expr(node):\r\n if isinstance(node, ast.Num):\r\n return node.n\r\n elif isinstance(node, ast.BinOp):\r\n return operators[type(node.op)](eval_expr(node.left), eval_expr(node.right))\r\n elif isinstance(node, ast.UnaryOp):\r\n return operators[type(node.op)](eval_expr(node.operand))\r\n else:\r\n raise TypeError(node)\r\n\r\n # Parse the expression and evaluate it\r\n tree = ast.parse(expression, mode='eval')\r\n result = eval_expr(tree.body)\r\n\r\n # Format the result to a reasonable number of decimal places\r\n formatted_result = f\"{result:.6f}\".rstrip('0').rstrip('.')\r\n\r\n self.status = formatted_result\r\n return [Data(data={\"result\": formatted_result})]\r\n\r\n except (SyntaxError, TypeError, KeyError) as e:\r\n error_message = f\"Invalid expression: {str(e)}\"\r\n self.status = error_message\r\n return [Data(data={\"error\": error_message})]\r\n except ZeroDivisionError:\r\n error_message = \"Error: Division by zero\"\r\n self.status = error_message\r\n return [Data(data={\"error\": error_message})]\r\n except Exception as e:\r\n error_message = f\"Error: {str(e)}\"\r\n self.status = error_message\r\n return [Data(data={\"error\": error_message})]"
"value": "import ast\nimport operator\nfrom typing import List\nfrom pydantic import BaseModel, Field\nfrom langflow.base.langchain_utilities.model import LCToolComponent\nfrom langflow.inputs import MessageTextInput\nfrom langflow.schema import Data\nfrom langflow.field_typing import Tool\nfrom langchain.tools import StructuredTool\n\n\nclass CalculatorToolComponent(LCToolComponent):\n display_name = \"Calculator\"\n description = \"Perform basic arithmetic operations on a given expression.\"\n icon = \"calculator\"\n name = \"CalculatorTool\"\n\n inputs = [\n MessageTextInput(\n name=\"expression\",\n display_name=\"Expression\",\n info=\"The arithmetic expression to evaluate (e.g., '4*4*(33/22)+12-20').\",\n ),\n ]\n\n class CalculatorToolSchema(BaseModel):\n expression: str = Field(..., description=\"The arithmetic expression to evaluate.\")\n\n def run_model(self) -> List[Data]:\n return self._evaluate_expression(self.expression)\n\n def build_tool(self) -> Tool:\n return StructuredTool.from_function(\n name=\"calculator\",\n description=\"Evaluate basic arithmetic expressions. Input should be a string containing the expression.\",\n func=self._evaluate_expression,\n args_schema=self.CalculatorToolSchema,\n )\n\n def _evaluate_expression(self, expression: str) -> List[Data]:\n try:\n # Define the allowed operators\n operators = {\n ast.Add: operator.add,\n ast.Sub: operator.sub,\n ast.Mult: operator.mul,\n ast.Div: operator.truediv,\n ast.Pow: operator.pow,\n }\n\n def eval_expr(node):\n if isinstance(node, ast.Num):\n return node.n\n elif isinstance(node, ast.BinOp):\n return operators[type(node.op)](eval_expr(node.left), eval_expr(node.right))\n elif isinstance(node, ast.UnaryOp):\n return operators[type(node.op)](eval_expr(node.operand))\n else:\n raise TypeError(node)\n\n # Parse the expression and evaluate it\n tree = ast.parse(expression, mode=\"eval\")\n result = eval_expr(tree.body)\n\n # Format the result to a reasonable number of decimal places\n formatted_result = f\"{result:.6f}\".rstrip(\"0\").rstrip(\".\")\n\n self.status = formatted_result\n return [Data(data={\"result\": formatted_result})]\n\n except (SyntaxError, TypeError, KeyError) as e:\n error_message = f\"Invalid expression: {str(e)}\"\n self.status = error_message\n return [Data(data={\"error\": error_message})]\n except ZeroDivisionError:\n error_message = \"Error: Division by zero\"\n self.status = error_message\n return [Data(data={\"error\": error_message})]\n except Exception as e:\n error_message = f\"Error: {str(e)}\"\n self.status = error_message\n return [Data(data={\"error\": error_message})]\n"
},
"expression": {
"_input_type": "MessageTextInput",