From 2c56177ef17046aa1e1781875b75bbe19af17591 Mon Sep 17 00:00:00 2001 From: Edwin Jose Date: Wed, 4 Dec 2024 15:22:14 -0500 Subject: [PATCH] feat: Table Input for tools metadata (tool name and description) in component as tools (#4961) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ (endpoints.py): Add support for tool_mode field in custom component update 🔧 (schemas.py): Add tool_mode field to UpdateCustomComponentRequest schema ♻️ (component.py): Refactor run_and_validate_update_outputs method to handle tool_mode field 🔧 (index.tsx): Add tool_mode property to NodeInputField component 🔧 (index.tsx): Refactor hasToolMode logic to use checkHasToolMode utility function 🔧 (mutate-template.ts): Add callback parameter to mutateTemplate function 🔧 (use-handle-new-value.tsx): Add tool_mode property to useHandleOnNewValue hook 🔧 (popover/index.tsx): Add console.log for placeholder, value, and id 🔧 (inputGlobalComponent/index.tsx): Add tool_mode property to InputGlobalComponent 🔧 (refreshParameterComponent/index.tsx): Add tool_mode property to RefreshParameterComponent 🔧 (use-post-template-value.ts): Add tool_mode parameter to usePostTemplateValue function 🔧 (nodeToolbarComponent/index.tsx): Add support for tool_mode functionality in NodeToolbarComponent 🔧 (reactflowUtils.ts): Add checkHasToolMode utility function to check for tool_mode field in template * fix: Set default value for tool_mode in UpdateCustomComponentRequest schema * adding table input in tool mode adding table input * Update component.py update real-time refresh * added dynamic tool description input added dynamic tool description input * [autofix.ci] apply automated fixes * Update component.py simplifying the tool mode logic * Update component_tool.py updated logic to be more readable * Update component.py * adding tool table schema as constant --------- Co-authored-by: cristhianzl Co-authored-by: Gabriel Luiz Freitas Almeida Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- src/backend/base/langflow/api/v1/endpoints.py | 1 - .../langflow/base/tools/component_tool.py | 23 ++++++++-- .../base/langflow/base/tools/constants.py | 15 +++++++ .../custom/custom_component/component.py | 42 +++++++++++++++++-- 4 files changed, 74 insertions(+), 7 deletions(-) diff --git a/src/backend/base/langflow/api/v1/endpoints.py b/src/backend/base/langflow/api/v1/endpoints.py index 654fd546b..369c69f93 100644 --- a/src/backend/base/langflow/api/v1/endpoints.py +++ b/src/backend/base/langflow/api/v1/endpoints.py @@ -609,7 +609,6 @@ async def custom_component_update( """ try: component = Component(_code=code_request.code) - component_node, cc_instance = build_custom_component_template( component, user_id=user.id, diff --git a/src/backend/base/langflow/base/tools/component_tool.py b/src/backend/base/langflow/base/tools/component_tool.py index f97df9ad9..9f098792c 100644 --- a/src/backend/base/langflow/base/tools/component_tool.py +++ b/src/backend/base/langflow/base/tools/component_tool.py @@ -4,7 +4,8 @@ import asyncio import re from typing import TYPE_CHECKING, Literal -from langchain_core.tools import ToolException +import pandas as pd +from langchain_core.tools import BaseTool, ToolException from langchain_core.tools.structured import StructuredTool from loguru import logger from pydantic import BaseModel @@ -18,7 +19,6 @@ if TYPE_CHECKING: from collections.abc import Callable from langchain_core.callbacks import Callbacks - from langchain_core.tools import BaseTool from langflow.custom.custom_component.component import Component from langflow.events.event_manager import EventManager @@ -160,8 +160,9 @@ def _format_tool_name(name: str): class ComponentToolkit: - def __init__(self, component: Component): + def __init__(self, component: Component, metadata: pd.DataFrame | None = None): self.component = component + self.metadata = metadata def get_tools( self, tool_name: str | None = None, tool_description: str | None = None, callbacks: Callbacks | None = None @@ -241,3 +242,19 @@ class ComponentToolkit: ) raise ValueError(msg) return tools + + def update_tools_metadata( + self, + tools: list[BaseTool | StructuredTool], + ) -> list[BaseTool]: + # 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.metadata.to_dict(orient="records") + for tool, metadata in zip(tools, metadata_dict, strict=False): + if isinstance(tool, StructuredTool | BaseTool): + tool.name = metadata.get("name", tool.name) + tool.description = metadata.get("description", tool.description) + else: + msg = f"Expected a StructuredTool or BaseTool, got {type(tool)}" + raise TypeError(msg) + return tools diff --git a/src/backend/base/langflow/base/tools/constants.py b/src/backend/base/langflow/base/tools/constants.py index 4da1036af..ea3189701 100644 --- a/src/backend/base/langflow/base/tools/constants.py +++ b/src/backend/base/langflow/base/tools/constants.py @@ -1,2 +1,17 @@ TOOL_OUTPUT_NAME = "component_as_tool" TOOL_OUTPUT_DISPLAY_NAME = "Toolset" +TOOLS_METADATA_INPUT_NAME = "tools_metadata" +TOOL_TABLE_SCHEMA = [ + { + "name": "name", + "display_name": "Name", + "type": "str", + "description": "Specify the name of the output field.", + }, + { + "name": "description", + "display_name": "Description", + "type": "str", + "description": "Describe the purpose of the output field.", + }, +] diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index d76e47adc..11a1c15bf 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -13,7 +13,12 @@ import yaml from langchain_core.tools import StructuredTool from pydantic import BaseModel, ValidationError -from langflow.base.tools.constants import TOOL_OUTPUT_DISPLAY_NAME, TOOL_OUTPUT_NAME +from langflow.base.tools.constants import ( + TOOL_OUTPUT_DISPLAY_NAME, + TOOL_OUTPUT_NAME, + TOOL_TABLE_SCHEMA, + TOOLS_METADATA_INPUT_NAME, +) from langflow.custom.tree_visitor import RequiredInputsVisitor from langflow.exceptions.component import StreamingError from langflow.field_typing import Tool # noqa: TCH001 Needed by _add_toolkit_output @@ -399,7 +404,12 @@ class Component(CustomComponent): if field_name == "tool_mode" or frontend_node.get("tool_mode"): is_tool_mode = field_value or frontend_node.get("tool_mode") frontend_node["outputs"] = [self._build_tool_output()] if is_tool_mode else frontend_node["outputs"] - + if is_tool_mode: + frontend_node.setdefault("template", {}) + frontend_node["template"][TOOLS_METADATA_INPUT_NAME] = self._build_tools_metadata_input().to_dict() + elif "template" in frontend_node: + frontend_node["template"].pop(TOOLS_METADATA_INPUT_NAME, None) + self.tools_metadata = frontend_node.get("template", {}).get(TOOLS_METADATA_INPUT_NAME, {}).get("value") return self._validate_frontend_node(frontend_node) def _validate_frontend_node(self, frontend_node: dict): @@ -966,7 +976,10 @@ class Component(CustomComponent): def to_toolkit(self) -> list[Tool]: component_toolkit = _get_component_toolkit() - return component_toolkit(component=self).get_tools(callbacks=self.get_langchain_callbacks()) + tools = component_toolkit(component=self).get_tools(callbacks=self.get_langchain_callbacks()) + if hasattr(self, TOOLS_METADATA_INPUT_NAME): + tools = component_toolkit(component=self, metadata=self.tools_metadata).update_tools_metadata(tools=tools) + return tools def get_project_name(self): if hasattr(self, "_tracing_service") and self._tracing_service: @@ -1137,6 +1150,29 @@ class Component(CustomComponent): def _append_tool_to_outputs_map(self): self._outputs_map[TOOL_OUTPUT_NAME] = self._build_tool_output() + # add a new input for the tool schema + # self.inputs.append(self._build_tool_schema()) def _build_tool_output(self) -> Output: return Output(name=TOOL_OUTPUT_NAME, display_name=TOOL_OUTPUT_DISPLAY_NAME, method="to_toolkit", types=["Tool"]) + + def _build_tools_metadata_input(self): + tools = self.to_toolkit() + tool_data = ( + self.tools_metadata + if hasattr(self, TOOLS_METADATA_INPUT_NAME) + else [{"name": tool.name, "description": tool.description} 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="Tools Metadata", + real_time_refresh=True, + table_schema=TOOL_TABLE_SCHEMA, + value=tool_data, + )