feat: new tool mode dialog and UI (#7647)
* Add tools input to the backend * Add tools type * Add tools component to parameter render * Added tools to langflow supported types * Add tools modal * Instantiate tools component in parameter render * Removed div when there is not any visible actions * Added margin * Add ComboBoxItem and ListItem components for enhanced selection UI * Update ComboBoxItem to display item description and enhance ToolsModal layout * Refactor ToolsModal header styling for improved layout and icon padding * Enhance ComboBoxItem layout and styling; update ToolsModal size and class for better responsiveness * Changed display name * Adds truncate for badges * Adds custom styling for table used for Tools modal * Added Tools modal with AgGrid * Changed button * made name and description editable * Parse values for saving * Add focused row without triggering checkbox click * [autofix.ci] apply automated fixes * Added types for tools modal * added toolsTable with sidebar on toolsModal * Added changes to work with MCP * update component.py to add display name and display description in tool mode * removed editing directly * Fixed editing * removed to upper case * Make editing apply filters * Adds design changes for MCP * Adds new design and null check * 📝 (frontend): add data-testid attribute to elements for testing purposes 🔧 (frontend): update data-testid attribute values for consistency and clarity in testing ✅ (frontend): update tests to use correct selectors and improve test coverage for editing tools functionality * ✅ (edit-tools.spec.ts): update test to use a more reliable method for checking visibility of an element * ✅ (edit-tools.spec.ts): add "@components" tag to the test to categorize it under components for better organization and filtering in test suites. * Updated design with new design * update padding * send args to tools data * Implemented showing arguments passed to LLM in tool mode * add componentg name to description * update package lock * fixed tests * fixed backend test * fixed backend test * fixed formatting * Fixed frontend tests * updated font sizes for badges on actions and styling on sortable list * Update tool mode design * added tooltips for info * tool name update * Update component_tool.py * styling utils * default values change * fixed tools test * fix format issues --------- Co-authored-by: deon-sanchez <deon.sanchez@datastax.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Edwin Jose <edwin.jose@datastax.com> Co-authored-by: Cristhian Zanforlin Lousa <cristhian.lousa@gmail.com>
This commit is contained in:
parent
6019b3ee06
commit
b1217f96fe
30 changed files with 1045 additions and 188 deletions
|
|
@ -273,12 +273,14 @@ class ComposioBaseComponent(Component):
|
|||
|
||||
def configure_tools(self, toolset: ComposioToolSet) -> list[Tool]:
|
||||
tools = toolset.get_tools(actions=self._actions_data.keys())
|
||||
logger.info(f"Tools: {tools}")
|
||||
configured_tools = []
|
||||
for tool in tools:
|
||||
# Set the sanitized name
|
||||
tool.name = self._sanitized_names.get(tool.name, self._name_sanitizer.sub("-", tool.name))
|
||||
display_name = self._sanitized_names.get(tool.name, self._name_sanitizer.sub("-", tool.name))
|
||||
# Set the tags
|
||||
tool.tags = [tool.name]
|
||||
tool.metadata = {"display_name": display_name, "display_description": tool.description}
|
||||
configured_tools.append(tool)
|
||||
return configured_tools
|
||||
|
||||
|
|
|
|||
|
|
@ -42,19 +42,19 @@ def _get_input_type(input_: InputTypes):
|
|||
def build_description(component: Component, output: Output) -> str:
|
||||
if not output.required_inputs:
|
||||
logger.warning(f"Output {output.name} does not have required inputs defined")
|
||||
|
||||
name = component.name or component.__class__.__name__
|
||||
if output.required_inputs:
|
||||
args = ", ".join(
|
||||
sorted(
|
||||
[
|
||||
f"{input_name}: {_get_input_type(component._inputs[input_name])}"
|
||||
f"{name}. {input_name}: {_get_input_type(component._inputs[input_name])}"
|
||||
for input_name in output.required_inputs
|
||||
]
|
||||
)
|
||||
)
|
||||
else:
|
||||
args = ""
|
||||
return f"{output.method}({args}) - {component.description}"
|
||||
return f"{name}. {output.method}({args}) - {component.description}"
|
||||
|
||||
|
||||
async def send_message_noop(
|
||||
|
|
@ -239,7 +239,7 @@ class ComponentToolkit:
|
|||
else:
|
||||
args_schema = create_input_schema(self.component.inputs)
|
||||
|
||||
name = f"{self.component.name or self.component.__class__.__name__ or ''}.{output.method}".strip(".")
|
||||
name = f"{output.method}".strip(".")
|
||||
formatted_name = _format_tool_name(name)
|
||||
event_manager = self.component._event_manager
|
||||
if asyncio.iscoroutinefunction(output_method):
|
||||
|
|
@ -252,6 +252,10 @@ class ComponentToolkit:
|
|||
handle_tool_error=True,
|
||||
callbacks=callbacks,
|
||||
tags=[formatted_name],
|
||||
metadata={
|
||||
"display_name": formatted_name,
|
||||
"display_description": build_description(self.component, output),
|
||||
},
|
||||
)
|
||||
)
|
||||
else:
|
||||
|
|
@ -264,6 +268,10 @@ class ComponentToolkit:
|
|||
handle_tool_error=True,
|
||||
callbacks=callbacks,
|
||||
tags=[formatted_name],
|
||||
metadata={
|
||||
"display_name": formatted_name,
|
||||
"display_description": build_description(self.component, output),
|
||||
},
|
||||
)
|
||||
)
|
||||
if len(tools) == 1 and (tool_name or tool_description):
|
||||
|
|
|
|||
|
|
@ -394,6 +394,6 @@ class ComposioGmailAPIComponent(ComposioBaseComponent):
|
|||
|
||||
def set_default_tools(self):
|
||||
self._default_tools = {
|
||||
self.sanitize_action_name("GMAIL_SEND_EMAIL").replace(" ", "-"),
|
||||
self.sanitize_action_name("GMAIL_FETCH_EMAILS").replace(" ", "-"),
|
||||
"GMAIL_SEND_EMAIL",
|
||||
"GMAIL_FETCH_EMAILS",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -581,6 +581,6 @@ class ComposioSlackAPIComponent(ComposioBaseComponent):
|
|||
|
||||
def set_default_tools(self):
|
||||
self._default_tools = {
|
||||
self.sanitize_action_name("SLACK_SENDS_A_MESSAGE_TO_A_SLACK_CHANNEL").replace(" ", "-"),
|
||||
self.sanitize_action_name("SLACK_SEARCH_FOR_MESSAGES_WITH_QUERY").replace(" ", "-"),
|
||||
"SLACK_SENDS_A_MESSAGE_TO_A_SLACK_CHANNEL",
|
||||
"SLACK_SEARCH_FOR_MESSAGES_WITH_QUERY",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ from pydantic import BaseModel, ValidationError
|
|||
from langflow.base.tools.constants import (
|
||||
TOOL_OUTPUT_DISPLAY_NAME,
|
||||
TOOL_OUTPUT_NAME,
|
||||
TOOL_TABLE_SCHEMA,
|
||||
TOOLS_METADATA_INFO,
|
||||
TOOLS_METADATA_INPUT_NAME,
|
||||
)
|
||||
|
|
@ -33,7 +32,6 @@ from langflow.schema.artifact import get_artifact_type, post_process_raw
|
|||
from langflow.schema.data import Data
|
||||
from langflow.schema.message import ErrorMessage, Message
|
||||
from langflow.schema.properties import Source
|
||||
from langflow.schema.table import FieldParserType, TableOptions
|
||||
from langflow.services.tracing.schema import Log
|
||||
from langflow.template.field.base import UNDEFINED, Input, Output
|
||||
from langflow.template.frontend_node.custom_components import ComponentFrontendNode
|
||||
|
|
@ -1174,10 +1172,14 @@ class Component(CustomComponent):
|
|||
"description": tool.description,
|
||||
"tags": tool.tags if hasattr(tool, "tags") and tool.tags else [tool.name],
|
||||
"status": True, # Initialize all tools with status True
|
||||
"display_name": tool.metadata.get("display_name", tool.name),
|
||||
"display_description": tool.metadata.get("display_description", tool.description),
|
||||
"args": tool.args,
|
||||
# "args_schema": tool.args_schema,
|
||||
}
|
||||
for tool in tools
|
||||
]
|
||||
|
||||
# print(tool_data)
|
||||
if hasattr(self, TOOLS_METADATA_INPUT_NAME):
|
||||
old_tags = self._extract_tools_tags(self.tools_metadata)
|
||||
new_tags = self._extract_tools_tags(tool_data)
|
||||
|
|
@ -1203,35 +1205,16 @@ class Component(CustomComponent):
|
|||
self.tools_metadata = tool_data
|
||||
|
||||
try:
|
||||
from langflow.io import TableInput
|
||||
from langflow.io import ToolsInput
|
||||
except ImportError as e:
|
||||
msg = "Failed to import TableInput from langflow.io"
|
||||
msg = "Failed to import ToolsInput from langflow.io"
|
||||
raise ImportError(msg) from e
|
||||
|
||||
return TableInput(
|
||||
return ToolsInput(
|
||||
name=TOOLS_METADATA_INPUT_NAME,
|
||||
display_name="Edit tools",
|
||||
real_time_refresh=True,
|
||||
table_schema=TOOL_TABLE_SCHEMA,
|
||||
display_name="Actions",
|
||||
info=TOOLS_METADATA_INFO,
|
||||
value=tool_data,
|
||||
table_icon="Hammer",
|
||||
trigger_icon="Hammer",
|
||||
trigger_text="",
|
||||
table_options=TableOptions(
|
||||
block_add=True,
|
||||
block_delete=True,
|
||||
block_edit=True, # Allow editing for status toggle
|
||||
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):
|
||||
|
|
|
|||
|
|
@ -242,7 +242,7 @@ class ParameterHandler:
|
|||
params[field_name] = val
|
||||
case str():
|
||||
params[field_name] = bool(val)
|
||||
case "table":
|
||||
case "table" | "tools":
|
||||
if isinstance(val, list) and all(isinstance(item, dict) for item in val):
|
||||
params[field_name] = pd.DataFrame(val)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ from .inputs import (
|
|||
StrInput,
|
||||
TabInput,
|
||||
TableInput,
|
||||
ToolsInput,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
|
|
@ -62,4 +63,5 @@ __all__ = [
|
|||
"StrInput",
|
||||
"TabInput",
|
||||
"TableInput",
|
||||
"ToolsInput",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ class FieldTypes(str, Enum):
|
|||
SLIDER = "slider"
|
||||
TAB = "tab"
|
||||
QUERY = "query"
|
||||
TOOLS = "tools"
|
||||
|
||||
|
||||
SerializableFieldTypes = Annotated[FieldTypes, PlainSerializer(lambda v: v.value, return_type=str)]
|
||||
|
|
|
|||
|
|
@ -87,6 +87,21 @@ class HandleInput(BaseInputMixin, ListableInputMixin, MetadataTraceMixin):
|
|||
field_type: SerializableFieldTypes = FieldTypes.OTHER
|
||||
|
||||
|
||||
class ToolsInput(BaseInputMixin, ListableInputMixin, MetadataTraceMixin, ToolModeMixin):
|
||||
"""Represents an Input that contains a list of tools to activate, deactivate, or edit.
|
||||
|
||||
Attributes:
|
||||
field_type (SerializableFieldTypes): The field type of the input.
|
||||
value (list[dict]): The value of the input.
|
||||
|
||||
"""
|
||||
|
||||
field_type: SerializableFieldTypes = FieldTypes.TOOLS
|
||||
value: list[dict] = Field(default_factory=list)
|
||||
is_list: bool = True
|
||||
real_time_refresh: bool = True
|
||||
|
||||
|
||||
class DataInput(HandleInput, InputTraceMixin, ListableInputMixin, ToolModeMixin):
|
||||
"""Represents an Input that has a Handle that receives a Data object.
|
||||
|
||||
|
|
@ -644,6 +659,7 @@ InputTypes: TypeAlias = (
|
|||
| MultilineInput
|
||||
| MultilineSecretInput
|
||||
| NestedDictInput
|
||||
| ToolsInput
|
||||
| PromptInput
|
||||
| CodeInput
|
||||
| SecretStrInput
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ from langflow.inputs import (
|
|||
StrInput,
|
||||
TabInput,
|
||||
TableInput,
|
||||
ToolsInput,
|
||||
)
|
||||
from langflow.template import Output
|
||||
|
||||
|
|
@ -57,4 +58,5 @@ __all__ = [
|
|||
"StrInput",
|
||||
"TabInput",
|
||||
"TableInput",
|
||||
"ToolsInput",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ DIRECT_TYPES = [
|
|||
"auth",
|
||||
"connect",
|
||||
"query",
|
||||
"tools",
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ def test_component_tool():
|
|||
calculator_component = CalculatorToolComponent()
|
||||
component_toolkit = ComponentToolkit(component=calculator_component)
|
||||
component_tool = component_toolkit.get_tools()[0]
|
||||
assert component_tool.name == "CalculatorTool-run_model"
|
||||
assert component_tool.name == "run_model"
|
||||
assert issubclass(component_tool.args_schema, BaseModel)
|
||||
# TODO: fix this
|
||||
# assert component_tool.args_schema.model_json_schema()["properties"] == {
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ def test_component_inputs_toolkit():
|
|||
component = AllInputsComponent()
|
||||
component_toolkit = ComponentToolkit(component=component)
|
||||
component_tool = component_toolkit.get_tools()[0]
|
||||
assert component_tool.name == "AllInputsComponent-build_output"
|
||||
assert component_tool.name == "build_output"
|
||||
assert issubclass(component_tool.args_schema, BaseModel)
|
||||
properties = component_tool.args_schema.model_json_schema()["properties"]
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue