feat: run flow component with tool mode option to run a flow as a tool (#5518)

* Update calculator.py

* Update json_cleaner.py

* [autofix.ci] apply automated fixes

* updated init

* updated format

format

* update component

sample component update

* Update flow_orchestrator.py

* test files

tests

* update in flow Orchestrator

* Update flow_orchestrator.py

solves issues with agents and session ids

* Update flow_orchestrator.py

* update to FlowOrchetstor

update to FlowOrchetstor

* draft Commit

* updated to run_flow

* updates

* refactor code

* [autofix.ci] apply automated fixes

* default_name of tool to be the name of the flow

* add tool_mode default activation

* [autofix.ci] apply automated fixes

* updates to build schema

* [autofix.ci] apply automated fixes

* update to schema and run flow

* cleanup

* Update run_flow.py

* [autofix.ci] apply automated fixes

* fix(run_flow.py): update condition to check if field_order exists in field_template for improved input validation

* Update run_flow.py

* [autofix.ci] apply automated fixes

* debug updates

* updates in flow tool outputs and tool mode

* code cleanup

* Update component_tool.py

* Update run_flow.py

* updated description

added beta tag as per Simons Suggestion

* [autofix.ci] apply automated fixes

* Update component_tool.py

* Update schema.py

* [autofix.ci] apply automated fixes

* feat: Add ToolModeMixin to CodeInput class

* Update component.py

* updated tests

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
This commit is contained in:
Edwin Jose 2025-01-17 18:12:56 -05:00 • committed by GitHub
commit 040a84ed52
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 437 additions and 114 deletions

View file

@ -0,0 +1,33 @@
from langflow.io.schema import create_input_schema_from_dict
from langflow.schema.dotdict import dotdict
def test_create_schema():
sample_input = [
{
"_input_type": "MultilineInput",
"advanced": False,
"display_name": "Chat Input - Text",
"dynamic": False,
"info": "Message to be passed as input.",
"input_types": ["Message"],
"list": False,
"load_from_db": False,
"multiline": True,
"name": "ChatInput-xNZ0a|input_value",
"placeholder": "",
"required": False,
"show": True,
"title_case": False,
"tool_mode": True,
"trace_as_input": True,
"trace_as_metadata": True,
"type": "str",
"value": "add 1+1",
}
]
# convert to dotdict
# change the key type
sample_input = [dotdict(field) for field in sample_input]
schema = create_input_schema_from_dict(sample_input)
assert schema is not None

View file

@ -5,11 +5,11 @@ from langflow.components.agents.agent import AgentComponent
from langflow.components.tools.calculator import CalculatorToolComponent
def test_component_to_toolkit():
async def test_component_to_toolkit():
calculator_component = CalculatorToolComponent()
agent_component = AgentComponent().set(tools=[calculator_component])
tools = agent_component.to_toolkit()
tools = await agent_component.to_toolkit()
assert len(tools) == 1
tool = tools[0]

View file

@ -4,7 +4,7 @@ from langflow.custom.custom_component.component import Component
class TestComponentOutputs:
def test_run_and_validate_update_outputs_tool_mode(self):
async def test_run_and_validate_update_outputs_tool_mode(self):
"""Test run_and_validate_update_outputs with tool_mode field."""
class TestComponent(Component):
@ -33,7 +33,7 @@ class TestComponentOutputs:
}
# Test enabling tool mode
updated_node = component.run_and_validate_update_outputs(
updated_node = await component.run_and_validate_update_outputs(
frontend_node=frontend_node.copy(), # Use a copy to avoid modifying original
field_name="tool_mode",
field_value=True,
@ -45,7 +45,7 @@ class TestComponentOutputs:
assert updated_node["outputs"][0]["display_name"] == TOOL_OUTPUT_DISPLAY_NAME
# Test disabling tool mode - use the original frontend node
updated_node = component.run_and_validate_update_outputs(
updated_node = await component.run_and_validate_update_outputs(
frontend_node={"outputs": original_outputs.copy()}, # Use original outputs
field_name="tool_mode",
field_value=False,
@ -60,7 +60,7 @@ class TestComponentOutputs:
assert "types" in updated_node["outputs"][0]
assert "selected" in updated_node["outputs"][0]
def test_run_and_validate_update_outputs_invalid_output(self):
async def test_run_and_validate_update_outputs_invalid_output(self):
"""Test run_and_validate_update_outputs with invalid output structure."""
class TestComponent(Component):
@ -74,11 +74,11 @@ class TestComponentOutputs:
# Test validation fails for invalid output
with pytest.raises(ValueError, match="Invalid output: 1 validation error for Output"):
component.run_and_validate_update_outputs(
await component.run_and_validate_update_outputs(
frontend_node=frontend_node, field_name="some_field", field_value="some_value"
)
def test_run_and_validate_update_outputs_custom_update(self):
async def test_run_and_validate_update_outputs_custom_update(self):
"""Test run_and_validate_update_outputs with custom update logic."""
class CustomComponent(Component):
@ -111,7 +111,7 @@ class TestComponentOutputs:
frontend_node = {"outputs": []}
# Test custom update logic
updated_node = component.run_and_validate_update_outputs(
updated_node = await component.run_and_validate_update_outputs(
frontend_node=frontend_node, field_name="custom_field", field_value="custom_value"
)
@ -122,14 +122,14 @@ class TestComponentOutputs:
assert "types" in updated_node["outputs"][0]
assert "selected" in updated_node["outputs"][0]
def test_run_and_validate_update_outputs_with_existing_tool_output(self):
async def test_run_and_validate_update_outputs_with_existing_tool_output(self):
"""Test run_and_validate_update_outputs when tool output already exists."""
class TestComponent(Component):
def build(self) -> None:
pass
def to_toolkit(self) -> list:
async def to_toolkit(self) -> list:
"""Method that returns a list of tools."""
return []
@ -154,7 +154,7 @@ class TestComponentOutputs:
}
# Test enabling tool mode doesn't duplicate tool output
updated_node = component.run_and_validate_update_outputs(
updated_node = await component.run_and_validate_update_outputs(
frontend_node=frontend_node, field_name="tool_mode", field_value=True
)
@ -164,7 +164,7 @@ class TestComponentOutputs:
assert "types" in updated_node["outputs"][0]
assert "selected" in updated_node["outputs"][0]
def test_run_and_validate_update_outputs_with_multiple_outputs(self):
async def test_run_and_validate_update_outputs_with_multiple_outputs(self):
"""Test run_and_validate_update_outputs with multiple outputs."""
class TestComponent(Component):
@ -203,7 +203,7 @@ class TestComponentOutputs:
frontend_node = {"outputs": []}
# Test adding multiple outputs
updated_node = component.run_and_validate_update_outputs(
updated_node = await component.run_and_validate_update_outputs(
frontend_node=frontend_node, field_name="add_output", field_value=True
)
@ -217,7 +217,7 @@ class TestComponentOutputs:
assert set(output["types"]) == {"Text"}
assert output["selected"] == "Text"
def test_run_and_validate_update_outputs_output_validation(self):
async def test_run_and_validate_update_outputs_output_validation(self):
"""Test output validation in run_and_validate_update_outputs."""
class TestComponent(Component):
@ -236,10 +236,14 @@ class TestComponentOutputs:
}
with pytest.raises(AttributeError, match="nonexistent_method not found in TestComponent"):
component.run_and_validate_update_outputs(frontend_node=invalid_node, field_name="test", field_value=True)
await component.run_and_validate_update_outputs(
frontend_node=invalid_node, field_name="test", field_value=True
)
# Test missing method case
invalid_node = {"outputs": [{"name": "test", "type": "str", "display_name": "Test"}]}
with pytest.raises(ValueError, match="Output test does not have a method"):
component.run_and_validate_update_outputs(frontend_node=invalid_node, field_name="test", field_value=True)
await component.run_and_validate_update_outputs(
frontend_node=invalid_node, field_name="test", field_value=True
)