feat: Table Input for tools metadata (tool name and description) in component as tools (#4961)

*  (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 <cristhian.lousa@gmail.com>
Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Edwin Jose 2024-12-04 15:22:14 -05:00 committed by GitHub
commit 2c56177ef1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 74 additions and 7 deletions

View file

@ -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,

View file

@ -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

View file

@ -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.",
},
]

View file

@ -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,
)