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." 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 typing import TYPE_CHECKING
from loguru import logger 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 import Component
from langflow.custom.custom_component.component import _get_component_toolkit from langflow.custom.custom_component.component import _get_component_toolkit
from langflow.field_typing import Tool from langflow.field_typing import Tool
@ -36,7 +36,6 @@ class RunFlowBaseComponent(Component):
info="The name of the flow to run.", info="The name of the flow to run.",
options=[], options=[],
real_time_refresh=True, real_time_refresh=True,
refresh_button=True,
value=None, value=None,
), ),
MessageInput( MessageInput(
@ -201,12 +200,13 @@ class RunFlowBaseComponent(Component):
field.input_types = [] field.input_types = []
return fields return fields
async def to_toolkit(self) -> list[Tool]: @override
async def _get_tools(self) -> list[Tool]:
component_toolkit: type[ComponentToolkit] = _get_component_toolkit() component_toolkit: type[ComponentToolkit] = _get_component_toolkit()
flow_description, tool_mode_inputs = await self.get_required_data(self.flow_name_selected) flow_description, tool_mode_inputs = await self.get_required_data(self.flow_name_selected)
# # convert list of dicts to list of dotdicts # # convert list of dicts to list of dotdicts
tool_mode_inputs = [dotdict(field) for field in tool_mode_inputs] 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_name=f"{self.flow_name_selected}_tool",
tool_description=( tool_description=(
f"Tool designed to execute the flow '{self.flow_name_selected}'. Flow details: {flow_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(), callbacks=self.get_langchain_callbacks(),
flow_mode_inputs=tool_mode_inputs, 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.edge.schema import EdgeData
from langflow.graph.vertex.base import Vertex from langflow.graph.vertex.base import Vertex
from langflow.inputs.inputs import InputTypes from langflow.inputs.inputs import InputTypes
from langflow.schema.dataframe import DataFrame
from langflow.schema.log import LoggableType from langflow.schema.log import LoggableType
@ -1050,12 +1051,94 @@ class Component(CustomComponent):
return Input(**kwargs) return Input(**kwargs)
async def to_toolkit(self) -> list[Tool]: async def to_toolkit(self) -> list[Tool]:
component_toolkit: type[ComponentToolkit] = _get_component_toolkit() """Convert component to a list of tools.
tools = component_toolkit(component=self).get_tools(callbacks=self.get_langchain_callbacks())
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): 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 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): def get_project_name(self):
if hasattr(self, "_tracing_service") and self._tracing_service: if hasattr(self, "_tracing_service") and self._tracing_service:
return self._tracing_service.project_name return self._tracing_service.project_name
@ -1267,45 +1350,6 @@ class Component(CustomComponent):
def _build_tool_output(self) -> Output: def _build_tool_output(self) -> Output:
return Output(name=TOOL_OUTPUT_NAME, display_name=TOOL_OUTPUT_DISPLAY_NAME, method="to_toolkit", types=["Tool"]) 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: def get_input_display_name(self, input_name: str) -> str:
"""Get the display name of an input. """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 { DataTypeDefinition, SelectionChangedEvent } from "ag-grid-community";
import { AgGridReact } from "ag-grid-react"; import { AgGridReact } from "ag-grid-react";
import { cloneDeep } from "lodash"; import { cloneDeep } from "lodash";
import { useMemo, useRef, useState } from "react"; import { useEffect, useMemo, useRef, useState } from "react";
import { ForwardedIconComponent } from "../../../../common/genericIconComponent"; import { ForwardedIconComponent } from "../../../../common/genericIconComponent";
import { Button } from "../../../../ui/button"; import { Button } from "../../../../ui/button";
import { InputProps, TableComponentType } from "../../types"; import { InputProps, TableComponentType } from "../../types";
@ -68,6 +68,11 @@ export default function TableNodeComponent({
const [tempValue, setTempValue] = useState<any[]>(cloneDeep(value)); const [tempValue, setTempValue] = useState<any[]>(cloneDeep(value));
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const agGrid = useRef<AgGridReact>(null); const agGrid = useRef<AgGridReact>(null);
// Add useEffect to sync with incoming value changes
useEffect(() => {
setTempValue(cloneDeep(value));
}, [value]);
const componentColumns = columns const componentColumns = columns
? columns ? columns
: generateBackendColumnsFromValue(tempValue ?? [], table_options); : generateBackendColumnsFromValue(tempValue ?? [], table_options);