feat: add tool filter (#6951)
* updates to tool mode * fix the update real time table refresh issues. * Update src/backend/base/langflow/custom/custom_component/component.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes * Update component.py * Update component.py * format errors * [autofix.ci] apply automated fixes * Update index.tsx * [autofix.ci] apply automated fixes * lint error fix * [autofix.ci] apply automated fixes * fix format issue * [autofix.ci] apply automated fixes * fix typing.override does not exist in your Python version * filter added * Update index.tsx * [autofix.ci] apply automated fixes * update variables * modify tool filter * added dynamic property to set the tools in a better effective way * filter with property * lint fix * [autofix.ci] apply automated fixes --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
ae6bbf5079
commit
5a13f32da5
4 changed files with 100 additions and 13 deletions
|
|
@ -306,6 +306,7 @@ class ComponentToolkit:
|
||||||
# update the tool_name and description according to the name and secriotion mentioned in the list
|
# update the tool_name and description according to the name and secriotion mentioned in the list
|
||||||
if isinstance(self.metadata, pd.DataFrame):
|
if isinstance(self.metadata, pd.DataFrame):
|
||||||
metadata_dict = self.get_tools_metadata_dictionary()
|
metadata_dict = self.get_tools_metadata_dictionary()
|
||||||
|
filtered_tools = []
|
||||||
for tool in tools:
|
for tool in tools:
|
||||||
if isinstance(tool, StructuredTool | BaseTool) and tool.tags:
|
if isinstance(tool, StructuredTool | BaseTool) and tool.tags:
|
||||||
try:
|
try:
|
||||||
|
|
@ -315,13 +316,17 @@ class ComponentToolkit:
|
||||||
raise ValueError(msg) from None
|
raise ValueError(msg) from None
|
||||||
if tag in metadata_dict:
|
if tag in metadata_dict:
|
||||||
tool_metadata = metadata_dict[tag]
|
tool_metadata = metadata_dict[tag]
|
||||||
tool.name = tool_metadata.get("name", tool.name)
|
# Only include tools with status=True
|
||||||
tool.description = tool_metadata.get("description", tool.description)
|
if tool_metadata.get("status", False):
|
||||||
if tool_metadata.get("commands"):
|
tool.name = tool_metadata.get("name", tool.name)
|
||||||
tool.description = _add_commands_to_tool_description(
|
tool.description = tool_metadata.get("description", tool.description)
|
||||||
tool.description, tool_metadata.get("commands")
|
if tool_metadata.get("commands"):
|
||||||
)
|
tool.description = _add_commands_to_tool_description(
|
||||||
|
tool.description, tool_metadata.get("commands")
|
||||||
|
)
|
||||||
|
filtered_tools.append(tool)
|
||||||
else:
|
else:
|
||||||
msg = f"Expected a StructuredTool or BaseTool, got {type(tool)}"
|
msg = f"Expected a StructuredTool or BaseTool, got {type(tool)}"
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
return filtered_tools
|
||||||
return tools
|
return tools
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,13 @@ TOOL_TABLE_SCHEMA = [
|
||||||
"edit_mode": EditMode.INLINE,
|
"edit_mode": EditMode.INLINE,
|
||||||
"hidden": True,
|
"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."
|
TOOLS_METADATA_INFO = "Modify tool names and descriptions to help agents understand when to use each tool."
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ from typing import TYPE_CHECKING, Any, ClassVar, NamedTuple, get_type_hints
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
import nanoid
|
import nanoid
|
||||||
|
import pandas as pd
|
||||||
import yaml
|
import yaml
|
||||||
from langchain_core.tools import StructuredTool
|
from langchain_core.tools import StructuredTool
|
||||||
from pydantic import BaseModel, ValidationError
|
from pydantic import BaseModel, ValidationError
|
||||||
|
|
@ -162,6 +163,20 @@ class Component(CustomComponent):
|
||||||
self.set_class_code()
|
self.set_class_code()
|
||||||
self._set_output_required_inputs()
|
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]:
|
def _there_is_overlap_in_inputs_and_outputs(self) -> set[str]:
|
||||||
"""Check the `.name` of inputs and outputs to see if there is overlap.
|
"""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()
|
tools = await self._get_tools()
|
||||||
|
|
||||||
if hasattr(self, TOOLS_METADATA_INPUT_NAME):
|
if hasattr(self, TOOLS_METADATA_INPUT_NAME):
|
||||||
return self._update_tools_with_metadata(tools, self.tools_metadata)
|
tools = self._filter_tools_by_status(tools=tools, metadata=self.tools_metadata)
|
||||||
return tools
|
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]:
|
async def _get_tools(self) -> list[Tool]:
|
||||||
"""Get the list of tools for this component.
|
"""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:
|
def check_for_tool_tag_change(self, old_tags: list[str], new_tags: list[str]) -> bool:
|
||||||
return old_tags != new_tags
|
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):
|
async def _build_tools_metadata_input(self):
|
||||||
tools = await self.to_toolkit()
|
tools = await self._get_tools()
|
||||||
# Always use the latest tool data
|
# 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):
|
if hasattr(self, TOOLS_METADATA_INPUT_NAME):
|
||||||
old_tags = self._extract_tools_tags(self.tools_metadata)
|
old_tags = self._extract_tools_tags(self.tools_metadata)
|
||||||
new_tags = self._extract_tools_tags(tool_data)
|
new_tags = self._extract_tools_tags(tool_data)
|
||||||
if self.check_for_tool_tag_change(old_tags, new_tags):
|
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
|
self.tools_metadata = tool_data
|
||||||
else:
|
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
|
tool_data = self.tools_metadata
|
||||||
else:
|
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
|
self.tools_metadata = tool_data
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from langflow.io import TableInput
|
from langflow.io import TableInput
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
|
|
@ -1125,7 +1201,7 @@ class Component(CustomComponent):
|
||||||
table_options=TableOptions(
|
table_options=TableOptions(
|
||||||
block_add=True,
|
block_add=True,
|
||||||
block_delete=True,
|
block_delete=True,
|
||||||
block_edit=True,
|
block_edit=True, # Allow editing for status toggle
|
||||||
block_sort=True,
|
block_sort=True,
|
||||||
block_filter=True,
|
block_filter=True,
|
||||||
block_hide=True,
|
block_hide=True,
|
||||||
|
|
|
||||||
|
|
@ -146,8 +146,7 @@ export default function TableNodeComponent({
|
||||||
const isCustomEdit =
|
const isCustomEdit =
|
||||||
column.formatter &&
|
column.formatter &&
|
||||||
((column.formatter === "text" && column.edit_mode === "modal") ||
|
((column.formatter === "text" && column.edit_mode === "modal") ||
|
||||||
column.formatter === "json" ||
|
column.formatter === "json");
|
||||||
column.formatter === "boolean");
|
|
||||||
return {
|
return {
|
||||||
field: column.name,
|
field: column.name,
|
||||||
onUpdate: updateComponent,
|
onUpdate: updateComponent,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue