diff --git a/src/backend/base/langflow/base/tools/component_tool.py b/src/backend/base/langflow/base/tools/component_tool.py index 24b5a6157..21560a4eb 100644 --- a/src/backend/base/langflow/base/tools/component_tool.py +++ b/src/backend/base/langflow/base/tools/component_tool.py @@ -306,6 +306,7 @@ class ComponentToolkit: # update the tool_name and description according to the name and secriotion mentioned in the list if isinstance(self.metadata, pd.DataFrame): metadata_dict = self.get_tools_metadata_dictionary() + filtered_tools = [] for tool in tools: if isinstance(tool, StructuredTool | BaseTool) and tool.tags: try: @@ -315,13 +316,17 @@ class ComponentToolkit: raise ValueError(msg) from None if tag in metadata_dict: tool_metadata = metadata_dict[tag] - tool.name = tool_metadata.get("name", tool.name) - tool.description = tool_metadata.get("description", tool.description) - if tool_metadata.get("commands"): - tool.description = _add_commands_to_tool_description( - tool.description, tool_metadata.get("commands") - ) + # Only include tools with status=True + if tool_metadata.get("status", False): + tool.name = tool_metadata.get("name", tool.name) + tool.description = tool_metadata.get("description", tool.description) + if tool_metadata.get("commands"): + tool.description = _add_commands_to_tool_description( + tool.description, tool_metadata.get("commands") + ) + filtered_tools.append(tool) else: msg = f"Expected a StructuredTool or BaseTool, got {type(tool)}" raise TypeError(msg) + return filtered_tools return tools diff --git a/src/backend/base/langflow/base/tools/constants.py b/src/backend/base/langflow/base/tools/constants.py index 539ca94be..10b025a22 100644 --- a/src/backend/base/langflow/base/tools/constants.py +++ b/src/backend/base/langflow/base/tools/constants.py @@ -35,6 +35,13 @@ TOOL_TABLE_SCHEMA = [ "edit_mode": EditMode.INLINE, "hidden": True, }, + { + "name": "status", + "display_name": "Enable", + "type": "boolean", + "description": "Indicates whether the tool is currently active. Set to True to activate this tool.", + "default": True, + }, ] TOOLS_METADATA_INFO = "Modify tool names and descriptions to help agents understand when to use each tool." diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index 7b9286e88..c2fd5f369 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -10,6 +10,7 @@ from typing import TYPE_CHECKING, Any, ClassVar, NamedTuple, get_type_hints from uuid import UUID import nanoid +import pandas as pd import yaml from langchain_core.tools import StructuredTool from pydantic import BaseModel, ValidationError @@ -162,6 +163,20 @@ class Component(CustomComponent): self.set_class_code() self._set_output_required_inputs() + @property + def enabled_tools(self) -> list[str] | None: + """Dynamically determine which tools should be enabled. + + This property can be overridden by subclasses to provide custom tool filtering. + By default, it returns None, which means all tools are enabled. + + Returns: + list[str] | None: List of tool names or tags to enable, or None to enable all tools. + """ + # Default implementation returns None (all tools enabled) + # Subclasses can override this to provide custom filtering + return None + def _there_is_overlap_in_inputs_and_outputs(self) -> set[str]: """Check the `.name` of inputs and outputs to see if there is overlap. @@ -1067,8 +1082,11 @@ class Component(CustomComponent): tools = await self._get_tools() if hasattr(self, TOOLS_METADATA_INPUT_NAME): - return self._update_tools_with_metadata(tools, self.tools_metadata) - return tools + tools = self._filter_tools_by_status(tools=tools, metadata=self.tools_metadata) + return self._update_tools_with_metadata(tools=tools, metadata=self.tools_metadata) + + # If no metadata exists yet, filter based on enabled_tools + return self._filter_tools_by_status(tools=tools, metadata=None) async def _get_tools(self) -> list[Tool]: """Get the list of tools for this component. @@ -1094,19 +1112,77 @@ class Component(CustomComponent): def check_for_tool_tag_change(self, old_tags: list[str], new_tags: list[str]) -> bool: return old_tags != new_tags + def _filter_tools_by_status(self, tools: list[Tool], metadata: pd.DataFrame | None) -> list[Tool]: + """Filter tools based on their status in metadata. + + Args: + tools (list[Tool]): List of tools to filter. + metadata (list[dict] | None): Tools metadata containing status information. + + Returns: + list[Tool]: Filtered list of tools. + """ + # Convert metadata to a list of dicts if it's a DataFrame + metadata_dict = None + if isinstance(metadata, pd.DataFrame): + metadata_dict = metadata.to_dict(orient="records") + + # If metadata is None or empty, use enabled_tools + if not metadata_dict: + enabled = self.enabled_tools + return ( + tools + if enabled is None + else [ + tool for tool in tools if any(enabled_name in [tool.name, *tool.tags] for enabled_name in enabled) + ] + ) + + # Ensure metadata is a list of dicts + if not isinstance(metadata_dict, list): + return tools + + # Create a mapping of tool names to their status + tool_status = {item["name"]: item.get("status", True) for item in metadata_dict} + return [tool for tool in tools if tool_status.get(tool.name, True)] + async def _build_tools_metadata_input(self): - tools = await self.to_toolkit() + tools = await self._get_tools() # Always use the latest tool data - tool_data = [{"name": tool.name, "description": tool.description, "tags": tool.tags} for tool in tools] + tool_data = [ + { + "name": tool.name, + "description": tool.description, + "tags": tool.tags, + "status": True, # Initialize all tools with status True + } + for tool in tools + ] + if hasattr(self, TOOLS_METADATA_INPUT_NAME): old_tags = self._extract_tools_tags(self.tools_metadata) new_tags = self._extract_tools_tags(tool_data) if self.check_for_tool_tag_change(old_tags, new_tags): + # If enabled tools are set, update status based on them + enabled = self.enabled_tools + if enabled is not None: + for item in tool_data: + item["status"] = any(enabled_name in [item["name"], *item["tags"]] for enabled_name in enabled) self.tools_metadata = tool_data else: + # Preserve existing status values + existing_status = {item["name"]: item.get("status", True) for item in self.tools_metadata} + for item in tool_data: + item["status"] = existing_status.get(item["name"], True) tool_data = self.tools_metadata else: + # If enabled tools are set, update status based on them + enabled = self.enabled_tools + if enabled is not None: + for item in tool_data: + item["status"] = any(enabled_name in [item["name"], *item["tags"]] for enabled_name in enabled) self.tools_metadata = tool_data + try: from langflow.io import TableInput except ImportError as e: @@ -1125,7 +1201,7 @@ class Component(CustomComponent): table_options=TableOptions( block_add=True, block_delete=True, - block_edit=True, + block_edit=True, # Allow editing for status toggle block_sort=True, block_filter=True, block_hide=True, diff --git a/src/frontend/src/components/core/parameterRenderComponent/components/TableNodeComponent/index.tsx b/src/frontend/src/components/core/parameterRenderComponent/components/TableNodeComponent/index.tsx index cf584cd31..2c3fb1325 100644 --- a/src/frontend/src/components/core/parameterRenderComponent/components/TableNodeComponent/index.tsx +++ b/src/frontend/src/components/core/parameterRenderComponent/components/TableNodeComponent/index.tsx @@ -146,8 +146,7 @@ export default function TableNodeComponent({ const isCustomEdit = column.formatter && ((column.formatter === "text" && column.edit_mode === "modal") || - column.formatter === "json" || - column.formatter === "boolean"); + column.formatter === "json"); return { field: column.name, onUpdate: updateComponent,