feat: improving tool mode metadata updates and tables refresh (#6935)

* updates to tool mode

* 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

* fix the update real time table refresh issues.

* [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

---------

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:
Edwin Jose 2025-03-10 10:45:35 -04:00 committed by GitHub
commit a8f4090888
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 98 additions and 50 deletions

View file

@ -38,3 +38,5 @@ TOOL_TABLE_SCHEMA = [
]
TOOLS_METADATA_INFO = "Modify tool names and descriptions to help agents understand when to use each tool."
TOOL_UPDATE_CONSTANTS = ["tool_mode", "tool_actions", TOOLS_METADATA_INPUT_NAME, "flow_name_selected"]

View file

@ -2,8 +2,8 @@ from abc import abstractmethod
from typing import TYPE_CHECKING
from loguru import logger
from typing_extensions import override
from langflow.base.tools.constants import TOOLS_METADATA_INPUT_NAME
from langflow.custom import Component
from langflow.custom.custom_component.component import _get_component_toolkit
from langflow.field_typing import Tool
@ -36,7 +36,6 @@ class RunFlowBaseComponent(Component):
info="The name of the flow to run.",
options=[],
real_time_refresh=True,
refresh_button=True,
value=None,
),
MessageInput(
@ -201,12 +200,13 @@ class RunFlowBaseComponent(Component):
field.input_types = []
return fields
async def to_toolkit(self) -> list[Tool]:
@override
async def _get_tools(self) -> list[Tool]:
component_toolkit: type[ComponentToolkit] = _get_component_toolkit()
flow_description, tool_mode_inputs = await self.get_required_data(self.flow_name_selected)
# # convert list of dicts to list of dotdicts
tool_mode_inputs = [dotdict(field) for field in tool_mode_inputs]
tools = component_toolkit(component=self).get_tools(
return component_toolkit(component=self).get_tools(
tool_name=f"{self.flow_name_selected}_tool",
tool_description=(
f"Tool designed to execute the flow '{self.flow_name_selected}'. Flow details: {flow_description}."
@ -214,6 +214,3 @@ class RunFlowBaseComponent(Component):
callbacks=self.get_langchain_callbacks(),
flow_mode_inputs=tool_mode_inputs,
)
if hasattr(self, TOOLS_METADATA_INPUT_NAME):
tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)
return tools

View file

@ -49,6 +49,7 @@ if TYPE_CHECKING:
from langflow.graph.edge.schema import EdgeData
from langflow.graph.vertex.base import Vertex
from langflow.inputs.inputs import InputTypes
from langflow.schema.dataframe import DataFrame
from langflow.schema.log import LoggableType
@ -1050,12 +1051,94 @@ class Component(CustomComponent):
return Input(**kwargs)
async def to_toolkit(self) -> list[Tool]:
component_toolkit: type[ComponentToolkit] = _get_component_toolkit()
tools = component_toolkit(component=self).get_tools(callbacks=self.get_langchain_callbacks())
"""Convert component to a list of tools.
This is a template method that defines the skeleton of the toolkit creation
algorithm. Subclasses can override _get_tools() to provide custom tool
implementations while maintaining the metadata update functionality.
Returns:
list[Tool]: A list of tools with updated metadata. Each tool contains:
- name: The name of the tool
- description: A description of what the tool does
- tags: List of tags associated with the tool
"""
# Get tools from subclass implementation
tools = await self._get_tools()
if hasattr(self, TOOLS_METADATA_INPUT_NAME):
tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools)
return self._update_tools_with_metadata(tools, self.tools_metadata)
return tools
async def _get_tools(self) -> list[Tool]:
"""Get the list of tools for this component.
This method can be overridden by subclasses to provide custom tool implementations.
The default implementation uses ComponentToolkit.
Returns:
list[Tool]: List of tools provided by this component
"""
component_toolkit: type[ComponentToolkit] = _get_component_toolkit()
return component_toolkit(component=self).get_tools(callbacks=self.get_langchain_callbacks())
def _extract_tools_tags(self, tools_metadata: list[dict]) -> list[str]:
"""Extract the first tag from each tool's metadata."""
return [tool["tags"][0] for tool in tools_metadata if tool["tags"]]
def _update_tools_with_metadata(self, tools: list[Tool], metadata: DataFrame | None) -> list[Tool]:
"""Update tools with provided metadata."""
component_toolkit: type[ComponentToolkit] = _get_component_toolkit()
return component_toolkit(component=self, metadata=metadata).update_tools_metadata(tools=tools)
def check_for_tool_tag_change(self, old_tags: list[str], new_tags: list[str]) -> bool:
return old_tags != new_tags
async def _build_tools_metadata_input(self):
tools = await self.to_toolkit()
# Always use the latest tool data
tool_data = [{"name": tool.name, "description": tool.description, "tags": tool.tags} 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):
self.tools_metadata = tool_data
else:
tool_data = self.tools_metadata
else:
self.tools_metadata = tool_data
try:
from langflow.io import TableInput
except ImportError as e:
msg = "Failed to import TableInput from langflow.io"
raise ImportError(msg) from e
return TableInput(
name=TOOLS_METADATA_INPUT_NAME,
display_name="Edit tools",
real_time_refresh=True,
table_schema=TOOL_TABLE_SCHEMA,
value=tool_data,
table_icon="Hammer",
trigger_icon="Hammer",
trigger_text="",
table_options=TableOptions(
block_add=True,
block_delete=True,
block_edit=True,
block_sort=True,
block_filter=True,
block_hide=True,
block_select=True,
hide_options=True,
field_parsers={
"name": [FieldParserType.SNAKE_CASE, FieldParserType.NO_BLANK],
"commands": FieldParserType.COMMANDS,
},
description=TOOLS_METADATA_INFO,
),
)
def get_project_name(self):
if hasattr(self, "_tracing_service") and self._tracing_service:
return self._tracing_service.project_name
@ -1267,45 +1350,6 @@ class Component(CustomComponent):
def _build_tool_output(self) -> Output:
return Output(name=TOOL_OUTPUT_NAME, display_name=TOOL_OUTPUT_DISPLAY_NAME, method="to_toolkit", types=["Tool"])
async def _build_tools_metadata_input(self):
tools = await self.to_toolkit()
tool_data = (
self.tools_metadata
if hasattr(self, TOOLS_METADATA_INPUT_NAME)
else [{"name": tool.name, "description": tool.description, "tags": tool.tags} for tool in tools]
)
try:
from langflow.io import TableInput
except ImportError as e:
msg = "Failed to import TableInput from langflow.io"
raise ImportError(msg) from e
return TableInput(
name=TOOLS_METADATA_INPUT_NAME,
display_name="Edit tools",
real_time_refresh=True,
table_schema=TOOL_TABLE_SCHEMA,
value=tool_data,
table_icon="Hammer",
trigger_icon="Hammer",
trigger_text="",
table_options=TableOptions(
block_add=True,
block_delete=True,
block_edit=True,
block_sort=True,
block_filter=True,
block_hide=True,
block_select=True,
hide_options=True,
field_parsers={
"name": [FieldParserType.SNAKE_CASE, FieldParserType.NO_BLANK],
"commands": FieldParserType.COMMANDS,
},
description=TOOLS_METADATA_INFO,
),
)
def get_input_display_name(self, input_name: str) -> str:
"""Get the display name of an input.

View file

@ -4,7 +4,7 @@ import { FormatColumns, generateBackendColumnsFromValue } from "@/utils/utils";
import { DataTypeDefinition, SelectionChangedEvent } from "ag-grid-community";
import { AgGridReact } from "ag-grid-react";
import { cloneDeep } from "lodash";
import { useMemo, useRef, useState } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import { ForwardedIconComponent } from "../../../../common/genericIconComponent";
import { Button } from "../../../../ui/button";
import { InputProps, TableComponentType } from "../../types";
@ -68,6 +68,11 @@ export default function TableNodeComponent({
const [tempValue, setTempValue] = useState<any[]>(cloneDeep(value));
const [isModalOpen, setIsModalOpen] = useState(false);
const agGrid = useRef<AgGridReact>(null);
// Add useEffect to sync with incoming value changes
useEffect(() => {
setTempValue(cloneDeep(value));
}, [value]);
const componentColumns = columns
? columns
: generateBackendColumnsFromValue(tempValue ?? [], table_options);