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
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue