griptape-nodes 0.64.11__py3-none-any.whl → 0.65.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- griptape_nodes/app/app.py +25 -5
- griptape_nodes/cli/commands/init.py +65 -54
- griptape_nodes/cli/commands/libraries.py +92 -85
- griptape_nodes/cli/commands/self.py +121 -0
- griptape_nodes/common/node_executor.py +2142 -101
- griptape_nodes/exe_types/base_iterative_nodes.py +1004 -0
- griptape_nodes/exe_types/connections.py +114 -19
- griptape_nodes/exe_types/core_types.py +225 -7
- griptape_nodes/exe_types/flow.py +3 -3
- griptape_nodes/exe_types/node_types.py +681 -225
- griptape_nodes/exe_types/param_components/README.md +414 -0
- griptape_nodes/exe_types/param_components/api_key_provider_parameter.py +200 -0
- griptape_nodes/exe_types/param_components/huggingface/huggingface_model_parameter.py +2 -0
- griptape_nodes/exe_types/param_components/huggingface/huggingface_repo_file_parameter.py +79 -5
- griptape_nodes/exe_types/param_types/parameter_button.py +443 -0
- griptape_nodes/machines/control_flow.py +84 -38
- griptape_nodes/machines/dag_builder.py +148 -70
- griptape_nodes/machines/parallel_resolution.py +61 -35
- griptape_nodes/machines/sequential_resolution.py +11 -113
- griptape_nodes/retained_mode/events/app_events.py +1 -0
- griptape_nodes/retained_mode/events/base_events.py +16 -13
- griptape_nodes/retained_mode/events/connection_events.py +3 -0
- griptape_nodes/retained_mode/events/execution_events.py +35 -0
- griptape_nodes/retained_mode/events/flow_events.py +15 -2
- griptape_nodes/retained_mode/events/library_events.py +347 -0
- griptape_nodes/retained_mode/events/node_events.py +48 -0
- griptape_nodes/retained_mode/events/os_events.py +86 -3
- griptape_nodes/retained_mode/events/project_events.py +15 -1
- griptape_nodes/retained_mode/events/workflow_events.py +48 -1
- griptape_nodes/retained_mode/griptape_nodes.py +6 -2
- griptape_nodes/retained_mode/managers/config_manager.py +10 -8
- griptape_nodes/retained_mode/managers/event_manager.py +168 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/__init__.py +2 -0
- griptape_nodes/retained_mode/managers/fitness_problems/libraries/old_xdg_location_warning_problem.py +43 -0
- griptape_nodes/retained_mode/managers/flow_manager.py +664 -123
- griptape_nodes/retained_mode/managers/library_manager.py +1142 -138
- griptape_nodes/retained_mode/managers/model_manager.py +2 -3
- griptape_nodes/retained_mode/managers/node_manager.py +148 -25
- griptape_nodes/retained_mode/managers/object_manager.py +3 -1
- griptape_nodes/retained_mode/managers/operation_manager.py +3 -1
- griptape_nodes/retained_mode/managers/os_manager.py +1158 -122
- griptape_nodes/retained_mode/managers/secrets_manager.py +2 -3
- griptape_nodes/retained_mode/managers/settings.py +21 -1
- griptape_nodes/retained_mode/managers/sync_manager.py +2 -3
- griptape_nodes/retained_mode/managers/workflow_manager.py +358 -104
- griptape_nodes/retained_mode/retained_mode.py +3 -3
- griptape_nodes/traits/button.py +44 -2
- griptape_nodes/traits/file_system_picker.py +2 -2
- griptape_nodes/utils/file_utils.py +101 -0
- griptape_nodes/utils/git_utils.py +1236 -0
- griptape_nodes/utils/library_utils.py +122 -0
- {griptape_nodes-0.64.11.dist-info → griptape_nodes-0.65.1.dist-info}/METADATA +2 -1
- {griptape_nodes-0.64.11.dist-info → griptape_nodes-0.65.1.dist-info}/RECORD +55 -47
- {griptape_nodes-0.64.11.dist-info → griptape_nodes-0.65.1.dist-info}/WHEEL +1 -1
- {griptape_nodes-0.64.11.dist-info → griptape_nodes-0.65.1.dist-info}/entry_points.txt +0 -0
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import logging
|
|
4
4
|
import threading
|
|
5
5
|
import warnings
|
|
6
|
-
from abc import ABC
|
|
6
|
+
from abc import ABC
|
|
7
7
|
from collections.abc import Callable, Generator, Iterable
|
|
8
8
|
from dataclasses import dataclass, field
|
|
9
9
|
from enum import StrEnum, auto
|
|
@@ -11,6 +11,7 @@ from typing import TYPE_CHECKING, Any, NamedTuple, TypeVar
|
|
|
11
11
|
|
|
12
12
|
from griptape_nodes.exe_types.core_types import (
|
|
13
13
|
BaseNodeElement,
|
|
14
|
+
ControlParameter,
|
|
14
15
|
ControlParameterInput,
|
|
15
16
|
ControlParameterOutput,
|
|
16
17
|
NodeMessageResult,
|
|
@@ -22,6 +23,7 @@ from griptape_nodes.exe_types.core_types import (
|
|
|
22
23
|
ParameterMessage,
|
|
23
24
|
ParameterMode,
|
|
24
25
|
ParameterTypeBuiltin,
|
|
26
|
+
Trait,
|
|
25
27
|
)
|
|
26
28
|
from griptape_nodes.exe_types.param_components.execution_status_component import ExecutionStatusComponent
|
|
27
29
|
from griptape_nodes.exe_types.type_validator import TypeValidator
|
|
@@ -31,8 +33,14 @@ from griptape_nodes.retained_mode.events.base_events import (
|
|
|
31
33
|
ProgressEvent,
|
|
32
34
|
RequestPayload,
|
|
33
35
|
)
|
|
36
|
+
from griptape_nodes.retained_mode.events.connection_events import (
|
|
37
|
+
CreateConnectionRequest,
|
|
38
|
+
DeleteConnectionRequest,
|
|
39
|
+
DeleteConnectionResultSuccess,
|
|
40
|
+
)
|
|
34
41
|
from griptape_nodes.retained_mode.events.parameter_events import (
|
|
35
42
|
AddParameterToNodeRequest,
|
|
43
|
+
AddParameterToNodeResultSuccess,
|
|
36
44
|
RemoveElementEvent,
|
|
37
45
|
RemoveParameterFromNodeRequest,
|
|
38
46
|
)
|
|
@@ -40,6 +48,7 @@ from griptape_nodes.traits.options import Options
|
|
|
40
48
|
from griptape_nodes.utils import async_utils
|
|
41
49
|
|
|
42
50
|
if TYPE_CHECKING:
|
|
51
|
+
from griptape_nodes.exe_types.connections import Connections
|
|
43
52
|
from griptape_nodes.exe_types.core_types import NodeMessagePayload
|
|
44
53
|
from griptape_nodes.node_library.library_registry import LibraryNameAndVersion
|
|
45
54
|
|
|
@@ -47,6 +56,41 @@ logger = logging.getLogger("griptape_nodes")
|
|
|
47
56
|
|
|
48
57
|
T = TypeVar("T")
|
|
49
58
|
|
|
59
|
+
NODE_GROUP_FLOW = "NodeGroupFlow"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class TransformedParameterValue(NamedTuple):
|
|
63
|
+
"""Return type for BaseNode.before_value_set() to transform both value and type.
|
|
64
|
+
|
|
65
|
+
When before_value_set() needs to transform a parameter value to a different type
|
|
66
|
+
(e.g., converting a string path to an artifact object), it can return this NamedTuple
|
|
67
|
+
to inform the node manager of both the new value AND its type. This ensures proper
|
|
68
|
+
type validation during parameter setting.
|
|
69
|
+
|
|
70
|
+
If before_value_set() only transforms the value without changing its type, it can
|
|
71
|
+
return the value directly without using this NamedTuple.
|
|
72
|
+
|
|
73
|
+
Example:
|
|
74
|
+
def before_value_set(self, parameter: Parameter, value: Any) -> Any:
|
|
75
|
+
if parameter == self.artifact_param and isinstance(value, str):
|
|
76
|
+
# Transform string to artifact
|
|
77
|
+
artifact = self._create_artifact(value)
|
|
78
|
+
# Return both transformed value and its type
|
|
79
|
+
return TransformedParameterValue(
|
|
80
|
+
value=artifact,
|
|
81
|
+
parameter_type=self.artifact_param.output_type
|
|
82
|
+
)
|
|
83
|
+
return value
|
|
84
|
+
|
|
85
|
+
Attributes:
|
|
86
|
+
value: The transformed parameter value
|
|
87
|
+
parameter_type: The type string of the transformed value (e.g., "ImageArtifact")
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
value: Any
|
|
91
|
+
parameter_type: str
|
|
92
|
+
|
|
93
|
+
|
|
50
94
|
AsyncResult = Generator[Callable[[], T], T]
|
|
51
95
|
|
|
52
96
|
LOCAL_EXECUTION = "Local Execution"
|
|
@@ -344,7 +388,7 @@ class BaseNode(ABC):
|
|
|
344
388
|
self,
|
|
345
389
|
parameter: Parameter, # noqa: ARG002
|
|
346
390
|
value: Any,
|
|
347
|
-
) -> Any:
|
|
391
|
+
) -> Any | TransformedParameterValue:
|
|
348
392
|
"""Callback when a Parameter's value is ABOUT to be set.
|
|
349
393
|
|
|
350
394
|
Custom nodes may elect to override the default behavior by implementing this function in their node code.
|
|
@@ -362,7 +406,10 @@ class BaseNode(ABC):
|
|
|
362
406
|
|
|
363
407
|
Returns:
|
|
364
408
|
The final value to set for the Parameter. This gives the Node logic one last opportunity to mutate the value
|
|
365
|
-
before it is assigned.
|
|
409
|
+
before it is assigned. Can return either:
|
|
410
|
+
* The transformed value directly (if type doesn't change)
|
|
411
|
+
* TransformedParameterValue(value=..., parameter_type=...) to specify both value and type
|
|
412
|
+
when transforming to a different type (e.g., string to artifact)
|
|
366
413
|
"""
|
|
367
414
|
# Default behavior is to do nothing to the supplied value, and indicate no other modified Parameters.
|
|
368
415
|
return value
|
|
@@ -1628,22 +1675,10 @@ class EndNode(BaseNode):
|
|
|
1628
1675
|
self.parameter_output_values[entry_parameter.name] = CONTROL_INPUT_PARAMETER
|
|
1629
1676
|
|
|
1630
1677
|
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
@abstractmethod
|
|
1636
|
-
def is_loop_finished(self) -> bool:
|
|
1637
|
-
"""Return True if the loop has finished executing.
|
|
1638
|
-
|
|
1639
|
-
This method must be implemented by subclasses to define when
|
|
1640
|
-
the loop should terminate.
|
|
1641
|
-
"""
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
class EndLoopNode(BaseNode):
|
|
1645
|
-
start_node: StartLoopNode | None = None
|
|
1646
|
-
"""Creating class for Start Loop Node in order to implement loop functionality in execution."""
|
|
1678
|
+
# StartLoopNode and EndLoopNode have been moved to base_iterative_nodes.py
|
|
1679
|
+
# They are now BaseIterativeStartNode and BaseIterativeEndNode
|
|
1680
|
+
# Import them here if needed for backwards compatibility in this file
|
|
1681
|
+
# (they are imported elsewhere directly from base_iterative_nodes)
|
|
1647
1682
|
|
|
1648
1683
|
|
|
1649
1684
|
class ErrorProxyNode(BaseNode):
|
|
@@ -1894,8 +1929,7 @@ class NodeGroupNode(BaseNode):
|
|
|
1894
1929
|
"""
|
|
1895
1930
|
|
|
1896
1931
|
nodes: dict[str, BaseNode]
|
|
1897
|
-
|
|
1898
|
-
_proxy_param_to_node_param: dict[str, tuple[BaseNode, str]]
|
|
1932
|
+
_proxy_param_to_connections: dict[str, int]
|
|
1899
1933
|
|
|
1900
1934
|
def __init__(
|
|
1901
1935
|
self,
|
|
@@ -1914,172 +1948,381 @@ class NodeGroupNode(BaseNode):
|
|
|
1914
1948
|
self.add_parameter(self.execution_environment)
|
|
1915
1949
|
self.nodes = {}
|
|
1916
1950
|
# Track mapping from proxy parameter name to (original_node, original_param_name)
|
|
1917
|
-
self.
|
|
1918
|
-
self.
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
if isinstance(node, NodeGroupNode):
|
|
1925
|
-
all_nodes.update(node.nodes)
|
|
1926
|
-
return all_nodes
|
|
1951
|
+
self._proxy_param_to_connections = {}
|
|
1952
|
+
if "execution_environment" not in self.metadata:
|
|
1953
|
+
self.metadata["execution_environment"] = {}
|
|
1954
|
+
self.metadata["execution_environment"]["Griptape Nodes Library"] = {
|
|
1955
|
+
"start_flow_node": "StartFlow",
|
|
1956
|
+
"parameter_names": {},
|
|
1957
|
+
}
|
|
1927
1958
|
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
) -> set[BaseNode]:
|
|
1931
|
-
"""Find all nodes on paths between start_node and end_node (excluding endpoints).
|
|
1959
|
+
# Don't create subflow in __init__ - it will be created on-demand when nodes are added
|
|
1960
|
+
# or restored during deserialization
|
|
1932
1961
|
|
|
1933
|
-
|
|
1934
|
-
|
|
1962
|
+
# Add parameters from registered StartFlow nodes for each publishing library
|
|
1963
|
+
self._add_start_flow_parameters()
|
|
1935
1964
|
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
end_node: Target node for path search
|
|
1965
|
+
def _create_subflow(self) -> None:
|
|
1966
|
+
"""Create a dedicated subflow for this NodeGroup's nodes.
|
|
1939
1967
|
|
|
1940
|
-
|
|
1941
|
-
|
|
1968
|
+
Note: This is called during __init__, so the node may not yet be added to a flow.
|
|
1969
|
+
The subflow will be created without a parent initially, and can be reparented later.
|
|
1942
1970
|
"""
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1971
|
+
from griptape_nodes.retained_mode.events.flow_events import (
|
|
1972
|
+
CreateFlowRequest,
|
|
1973
|
+
CreateFlowResultSuccess,
|
|
1974
|
+
)
|
|
1975
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
1946
1976
|
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
if key not in outgoing_lookup:
|
|
1950
|
-
outgoing_lookup[key] = []
|
|
1951
|
-
outgoing_lookup[key].append(conn)
|
|
1977
|
+
subflow_name = f"{self.name}_subflow"
|
|
1978
|
+
self.metadata["subflow_name"] = subflow_name
|
|
1952
1979
|
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1980
|
+
# Get current flow to set as parent so subflow will be serialized with parent
|
|
1981
|
+
current_flow = GriptapeNodes.ContextManager().get_current_flow()
|
|
1982
|
+
parent_flow_name = current_flow.name if current_flow else None
|
|
1956
1983
|
|
|
1957
|
-
|
|
1958
|
-
|
|
1984
|
+
# Create metadata with flow_type
|
|
1985
|
+
subflow_metadata = {"flow_type": NODE_GROUP_FLOW}
|
|
1959
1986
|
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
key = (current_node.name, param_name)
|
|
1968
|
-
if key in outgoing_lookup:
|
|
1969
|
-
current_outgoing.extend(outgoing_lookup[key])
|
|
1970
|
-
|
|
1971
|
-
for conn in current_outgoing:
|
|
1972
|
-
next_node = conn.target_node
|
|
1973
|
-
|
|
1974
|
-
# If we reached the end node, record intermediate nodes
|
|
1975
|
-
if next_node == end_node:
|
|
1976
|
-
for node in path[1:]:
|
|
1977
|
-
intermediate.add(node)
|
|
1978
|
-
continue
|
|
1987
|
+
request = CreateFlowRequest(
|
|
1988
|
+
flow_name=subflow_name,
|
|
1989
|
+
parent_flow_name=parent_flow_name,
|
|
1990
|
+
set_as_new_context=False,
|
|
1991
|
+
metadata=subflow_metadata,
|
|
1992
|
+
)
|
|
1993
|
+
result = GriptapeNodes.handle_request(request)
|
|
1979
1994
|
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
queue.append((next_node, [*path, next_node]))
|
|
1995
|
+
if not isinstance(result, CreateFlowResultSuccess):
|
|
1996
|
+
logger.warning("%s failed to create subflow '%s': %s", self.name, subflow_name, result.result_details)
|
|
1983
1997
|
|
|
1984
|
-
|
|
1998
|
+
def _add_start_flow_parameters(self) -> None:
|
|
1999
|
+
"""Add parameters from all registered StartFlow nodes to this NodeGroupNode.
|
|
1985
2000
|
|
|
1986
|
-
|
|
1987
|
-
|
|
2001
|
+
For each library that has registered a PublishWorkflowRequest handler with
|
|
2002
|
+
a StartFlow node, this method:
|
|
2003
|
+
1. Creates a temporary instance of that StartFlow node
|
|
2004
|
+
2. Extracts all its parameters
|
|
2005
|
+
3. Adds them to this NodeGroupNode with a prefix based on the class name
|
|
2006
|
+
4. Stores metadata mapping execution environments to their parameters
|
|
2007
|
+
"""
|
|
2008
|
+
from griptape_nodes.retained_mode.events.workflow_events import PublishWorkflowRequest
|
|
2009
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
2010
|
+
|
|
2011
|
+
# Initialize metadata structure for execution environment mappings
|
|
2012
|
+
if self.metadata is None:
|
|
2013
|
+
self.metadata = {}
|
|
2014
|
+
if "execution_environment" not in self.metadata:
|
|
2015
|
+
self.metadata["execution_environment"] = {}
|
|
1988
2016
|
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
the group definition.
|
|
2017
|
+
# Get all libraries that have registered PublishWorkflowRequest handlers
|
|
2018
|
+
library_manager = GriptapeNodes.LibraryManager()
|
|
2019
|
+
event_handlers = library_manager.get_registered_event_handlers(PublishWorkflowRequest)
|
|
1993
2020
|
|
|
1994
|
-
|
|
1995
|
-
|
|
2021
|
+
# Process each registered library
|
|
2022
|
+
for library_name, handler in event_handlers.items():
|
|
2023
|
+
self._process_library_start_flow_parameters(library_name, handler)
|
|
2024
|
+
|
|
2025
|
+
def _process_library_start_flow_parameters(self, library_name: str, handler: Any) -> None:
|
|
2026
|
+
"""Process and add StartFlow parameters from a single library.
|
|
2027
|
+
|
|
2028
|
+
Args:
|
|
2029
|
+
library_name: Name of the library
|
|
2030
|
+
handler: The registered event handler containing event data
|
|
1996
2031
|
"""
|
|
1997
|
-
|
|
1998
|
-
for node_a in self.nodes.values():
|
|
1999
|
-
for node_b in self.nodes.values():
|
|
2000
|
-
if node_a == node_b:
|
|
2001
|
-
continue
|
|
2032
|
+
import logging
|
|
2002
2033
|
|
|
2003
|
-
|
|
2004
|
-
|
|
2034
|
+
from griptape_nodes.node_library.library_registry import LibraryRegistry
|
|
2035
|
+
from griptape_nodes.retained_mode.events.workflow_events import PublishWorkflowRegisteredEventData
|
|
2005
2036
|
|
|
2006
|
-
|
|
2007
|
-
ungrouped_intermediates = [n for n in intermediate_nodes if n.name not in self.nodes]
|
|
2037
|
+
logger = logging.getLogger(__name__)
|
|
2008
2038
|
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2039
|
+
registered_event_data = handler.event_data
|
|
2040
|
+
|
|
2041
|
+
if registered_event_data is None:
|
|
2042
|
+
return
|
|
2043
|
+
if not isinstance(registered_event_data, PublishWorkflowRegisteredEventData):
|
|
2044
|
+
return
|
|
2045
|
+
|
|
2046
|
+
# Get the StartFlow node information
|
|
2047
|
+
start_flow_node_type = registered_event_data.start_flow_node_type
|
|
2048
|
+
start_flow_library_name = registered_event_data.start_flow_node_library_name
|
|
2049
|
+
|
|
2050
|
+
try:
|
|
2051
|
+
# Get the library that contains the StartFlow node
|
|
2052
|
+
library = LibraryRegistry.get_library(name=start_flow_library_name)
|
|
2053
|
+
except KeyError:
|
|
2054
|
+
logger.debug(
|
|
2055
|
+
"Library '%s' not found when adding StartFlow parameters for '%s'",
|
|
2056
|
+
start_flow_library_name,
|
|
2057
|
+
library_name,
|
|
2058
|
+
)
|
|
2059
|
+
return
|
|
2060
|
+
|
|
2061
|
+
try:
|
|
2062
|
+
# Create a temporary instance of the StartFlow node to inspect its parameters
|
|
2063
|
+
temp_start_flow_node = library.create_node(
|
|
2064
|
+
node_type=start_flow_node_type,
|
|
2065
|
+
name=f"temp_{start_flow_node_type}",
|
|
2066
|
+
)
|
|
2067
|
+
except Exception as e:
|
|
2068
|
+
logger.debug(
|
|
2069
|
+
"Failed to create temporary StartFlow node '%s' from library '%s': %s",
|
|
2070
|
+
start_flow_node_type,
|
|
2071
|
+
start_flow_library_name,
|
|
2072
|
+
e,
|
|
2073
|
+
)
|
|
2074
|
+
return
|
|
2075
|
+
|
|
2076
|
+
# Get the class name for prefixing (convert to lowercase for parameter naming)
|
|
2077
|
+
class_name_prefix = start_flow_node_type.lower()
|
|
2078
|
+
|
|
2079
|
+
# Store metadata for this execution environment
|
|
2080
|
+
parameter_names = []
|
|
2081
|
+
|
|
2082
|
+
# Add each parameter from the StartFlow node to this NodeGroupNode
|
|
2083
|
+
for param in temp_start_flow_node.parameters:
|
|
2084
|
+
if isinstance(param, ControlParameter):
|
|
2085
|
+
continue
|
|
2017
2086
|
|
|
2018
|
-
|
|
2019
|
-
|
|
2087
|
+
# Create prefixed parameter name
|
|
2088
|
+
prefixed_param_name = f"{class_name_prefix}_{param.name}"
|
|
2089
|
+
parameter_names.append(prefixed_param_name)
|
|
2090
|
+
|
|
2091
|
+
# Clone and add the parameter
|
|
2092
|
+
self._clone_and_add_parameter(param, prefixed_param_name)
|
|
2093
|
+
|
|
2094
|
+
# Store the mapping in metadata
|
|
2095
|
+
self.metadata["execution_environment"][library_name] = {
|
|
2096
|
+
"start_flow_node": start_flow_node_type,
|
|
2097
|
+
"parameter_names": parameter_names,
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
def _clone_and_add_parameter(self, param: Parameter, new_name: str) -> None:
|
|
2101
|
+
"""Clone a parameter with a new name and add it to this node.
|
|
2020
2102
|
|
|
2021
2103
|
Args:
|
|
2022
|
-
|
|
2104
|
+
param: The parameter to clone
|
|
2105
|
+
new_name: The new name for the cloned parameter
|
|
2023
2106
|
"""
|
|
2024
|
-
|
|
2025
|
-
|
|
2107
|
+
# Extract traits from parameter children (traits are stored as children of type Trait)
|
|
2108
|
+
traits_set: set[type[Trait] | Trait] | None = {child for child in param.children if isinstance(child, Trait)}
|
|
2109
|
+
if not traits_set:
|
|
2110
|
+
traits_set = None
|
|
2111
|
+
|
|
2112
|
+
# Clone the parameter with the new name
|
|
2113
|
+
cloned_param = Parameter(
|
|
2114
|
+
name=new_name,
|
|
2115
|
+
tooltip=param.tooltip,
|
|
2116
|
+
type=param.type,
|
|
2117
|
+
allowed_modes=param.allowed_modes,
|
|
2118
|
+
default_value=param.default_value,
|
|
2119
|
+
traits=traits_set,
|
|
2120
|
+
parent_container_name=param.parent_container_name,
|
|
2121
|
+
parent_element_name=param.parent_element_name,
|
|
2122
|
+
)
|
|
2026
2123
|
|
|
2027
|
-
|
|
2028
|
-
self
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
grouped_node: BaseNode,
|
|
2033
|
-
) -> None:
|
|
2034
|
-
"""Track a connection to/from a node in the group.
|
|
2124
|
+
# Add the parameter to this node
|
|
2125
|
+
self.add_parameter(cloned_param)
|
|
2126
|
+
|
|
2127
|
+
def _create_proxy_parameter_for_connection(self, original_param: Parameter, *, is_incoming: bool) -> Parameter:
|
|
2128
|
+
"""Create a proxy parameter on this NodeGroupNode for an external connection.
|
|
2035
2129
|
|
|
2036
2130
|
Args:
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2131
|
+
original_param: The parameter from the grouped node
|
|
2132
|
+
grouped_node: The node within the group that has the original parameter
|
|
2133
|
+
conn_id: The connection ID for uniqueness
|
|
2134
|
+
is_incoming: True if this is an incoming connection to the group
|
|
2135
|
+
|
|
2136
|
+
Returns:
|
|
2137
|
+
The newly created proxy parameter
|
|
2041
2138
|
"""
|
|
2139
|
+
# Clone the parameter with the new name
|
|
2140
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
2141
|
+
|
|
2142
|
+
input_types = None
|
|
2143
|
+
output_type = None
|
|
2144
|
+
if is_incoming:
|
|
2145
|
+
input_types = original_param.input_types
|
|
2146
|
+
else:
|
|
2147
|
+
output_type = original_param.output_type
|
|
2148
|
+
|
|
2149
|
+
request = AddParameterToNodeRequest(
|
|
2150
|
+
node_name=self.name,
|
|
2151
|
+
parameter_name=original_param.name,
|
|
2152
|
+
input_types=input_types,
|
|
2153
|
+
output_type=output_type,
|
|
2154
|
+
tooltip="",
|
|
2155
|
+
mode_allowed_input=True,
|
|
2156
|
+
mode_allowed_output=True,
|
|
2157
|
+
)
|
|
2158
|
+
# Add with a request, because this will handle naming for us.
|
|
2159
|
+
result = GriptapeNodes.handle_request(request)
|
|
2160
|
+
if not isinstance(result, AddParameterToNodeResultSuccess):
|
|
2161
|
+
msg = "Failed to add parameter to node."
|
|
2162
|
+
raise TypeError(msg)
|
|
2163
|
+
# Retrieve and return the newly created parameter
|
|
2164
|
+
proxy_param = self.get_parameter_by_name(result.parameter_name)
|
|
2165
|
+
if proxy_param is None:
|
|
2166
|
+
msg = f"{self.name} failed to create proxy parameter '{result.parameter_name}'"
|
|
2167
|
+
raise RuntimeError(msg)
|
|
2042
2168
|
if is_incoming:
|
|
2043
|
-
if
|
|
2044
|
-
self.
|
|
2045
|
-
|
|
2169
|
+
if "left_parameters" in self.metadata:
|
|
2170
|
+
self.metadata["left_parameters"].append(proxy_param.name)
|
|
2171
|
+
else:
|
|
2172
|
+
self.metadata["left_parameters"] = [proxy_param.name]
|
|
2173
|
+
elif "right_parameters" in self.metadata:
|
|
2174
|
+
self.metadata["right_parameters"].append(proxy_param.name)
|
|
2046
2175
|
else:
|
|
2047
|
-
|
|
2048
|
-
self.stored_connections.external_connections.outgoing_connections.append(conn)
|
|
2049
|
-
self.stored_connections.original_targets.outgoing_targets[conn_id] = grouped_node
|
|
2176
|
+
self.metadata["right_parameters"] = [proxy_param.name]
|
|
2050
2177
|
|
|
2051
|
-
|
|
2052
|
-
"""Remove tracking of an internal connection.
|
|
2178
|
+
return proxy_param
|
|
2053
2179
|
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2180
|
+
def get_all_nodes(self) -> dict[str, BaseNode]:
|
|
2181
|
+
all_nodes = {}
|
|
2182
|
+
for node_name, node in self.nodes.items():
|
|
2183
|
+
all_nodes[node_name] = node
|
|
2184
|
+
if isinstance(node, NodeGroupNode):
|
|
2185
|
+
all_nodes.update(node.nodes)
|
|
2186
|
+
return all_nodes
|
|
2059
2187
|
|
|
2060
|
-
def
|
|
2061
|
-
|
|
2062
|
-
conn: Connection,
|
|
2063
|
-
conn_id: int,
|
|
2064
|
-
is_incoming: bool, # noqa: FBT001
|
|
2065
|
-
) -> None:
|
|
2066
|
-
"""Remove tracking of an external connection.
|
|
2188
|
+
def map_external_connection(self, conn: Connection, *, is_incoming: bool) -> bool:
|
|
2189
|
+
"""Track a connection to/from a node in the group and rewire it through a proxy parameter.
|
|
2067
2190
|
|
|
2068
2191
|
Args:
|
|
2069
|
-
conn: The external connection to
|
|
2192
|
+
conn: The external connection to track
|
|
2070
2193
|
conn_id: ID of the connection
|
|
2071
|
-
is_incoming: True if connection
|
|
2194
|
+
is_incoming: True if connection is coming INTO the group
|
|
2072
2195
|
"""
|
|
2073
2196
|
if is_incoming:
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2197
|
+
grouped_parameter = conn.target_parameter
|
|
2198
|
+
# Store the existing connection so it can be recreated if needed.
|
|
2199
|
+
else:
|
|
2200
|
+
grouped_parameter = conn.source_parameter
|
|
2201
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
2202
|
+
|
|
2203
|
+
request = DeleteConnectionRequest(
|
|
2204
|
+
conn.source_parameter.name,
|
|
2205
|
+
conn.target_parameter.name,
|
|
2206
|
+
conn.source_node.name,
|
|
2207
|
+
conn.target_node.name,
|
|
2208
|
+
)
|
|
2209
|
+
result = GriptapeNodes.handle_request(request)
|
|
2210
|
+
if not isinstance(result, DeleteConnectionResultSuccess):
|
|
2211
|
+
return False
|
|
2212
|
+
proxy_parameter = self._create_proxy_parameter_for_connection(grouped_parameter, is_incoming=is_incoming)
|
|
2213
|
+
# Create connections for proxy parameter
|
|
2214
|
+
self.create_connections_for_proxy(proxy_parameter, conn, is_incoming=is_incoming)
|
|
2215
|
+
return True
|
|
2216
|
+
|
|
2217
|
+
def create_connections_for_proxy(
|
|
2218
|
+
self, proxy_parameter: Parameter, old_connection: Connection, *, is_incoming: bool
|
|
2219
|
+
) -> None:
|
|
2220
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
2221
|
+
|
|
2222
|
+
create_first_connection = CreateConnectionRequest(
|
|
2223
|
+
source_parameter_name=old_connection.source_parameter.name,
|
|
2224
|
+
target_parameter_name=proxy_parameter.name,
|
|
2225
|
+
source_node_name=old_connection.source_node.name,
|
|
2226
|
+
target_node_name=self.name,
|
|
2227
|
+
is_node_group_internal=not is_incoming,
|
|
2228
|
+
)
|
|
2229
|
+
create_second_connection = CreateConnectionRequest(
|
|
2230
|
+
source_parameter_name=proxy_parameter.name,
|
|
2231
|
+
target_parameter_name=old_connection.target_parameter.name,
|
|
2232
|
+
source_node_name=self.name,
|
|
2233
|
+
target_node_name=old_connection.target_node.name,
|
|
2234
|
+
is_node_group_internal=is_incoming,
|
|
2235
|
+
)
|
|
2236
|
+
# Store the mapping from proxy parameter to original node/parameter
|
|
2237
|
+
# only increment by 1, even though we're making two connections.
|
|
2238
|
+
if proxy_parameter.name not in self._proxy_param_to_connections:
|
|
2239
|
+
self._proxy_param_to_connections[proxy_parameter.name] = 2
|
|
2078
2240
|
else:
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2241
|
+
self._proxy_param_to_connections[proxy_parameter.name] += 2
|
|
2242
|
+
GriptapeNodes.handle_request(create_first_connection)
|
|
2243
|
+
GriptapeNodes.handle_request(create_second_connection)
|
|
2244
|
+
|
|
2245
|
+
def unmap_node_connections(self, node: BaseNode, connections: Connections) -> None: # noqa: C901
|
|
2246
|
+
"""Remove tracking of an external connection, restore original connection, and clean up proxy parameter.
|
|
2247
|
+
|
|
2248
|
+
Args:
|
|
2249
|
+
node: The node to unmap
|
|
2250
|
+
connections: The connections object
|
|
2251
|
+
"""
|
|
2252
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
2253
|
+
|
|
2254
|
+
# For the node being removed - We need to figure out all of it's connections TO the node group. These connections need to be remapped.
|
|
2255
|
+
# If we delete connections from a proxy parameter, and it has no more connections, then the proxy parameter should be deleted unless it's user defined.
|
|
2256
|
+
# It will 1. not be in the proxy map. and 2. it will have a value of > 0
|
|
2257
|
+
# Get all outgoing connections
|
|
2258
|
+
outgoing_connections = connections.get_outgoing_connections_to_node(node, to_node=self)
|
|
2259
|
+
# Delete outgoing connections
|
|
2260
|
+
for parameter_name, outgoing_connection_list in outgoing_connections.items():
|
|
2261
|
+
for outgoing_connection in outgoing_connection_list:
|
|
2262
|
+
proxy_parameter = outgoing_connection.target_parameter
|
|
2263
|
+
# get old connections first, since this will delete the proxy
|
|
2264
|
+
remap_connections = connections.get_outgoing_connections_from_parameter(self, proxy_parameter)
|
|
2265
|
+
# Delete the internal connection
|
|
2266
|
+
delete_result = GriptapeNodes.FlowManager().on_delete_connection_request(
|
|
2267
|
+
DeleteConnectionRequest(
|
|
2268
|
+
source_parameter_name=parameter_name,
|
|
2269
|
+
target_parameter_name=proxy_parameter.name,
|
|
2270
|
+
source_node_name=node.name,
|
|
2271
|
+
target_node_name=self.name,
|
|
2272
|
+
)
|
|
2273
|
+
)
|
|
2274
|
+
if delete_result.failed():
|
|
2275
|
+
msg = f"{self.name}: Failed to delete internal outgoing connection from {node.name}.{parameter_name} to proxy {proxy_parameter.name}: {delete_result.result_details}"
|
|
2276
|
+
raise RuntimeError(msg)
|
|
2277
|
+
|
|
2278
|
+
# Now create the new connection! We need to get the connections from the proxy parameter
|
|
2279
|
+
for connection in remap_connections:
|
|
2280
|
+
create_result = GriptapeNodes.FlowManager().on_create_connection_request(
|
|
2281
|
+
CreateConnectionRequest(
|
|
2282
|
+
source_parameter_name=parameter_name,
|
|
2283
|
+
target_parameter_name=connection.target_parameter.name,
|
|
2284
|
+
source_node_name=node.name,
|
|
2285
|
+
target_node_name=connection.target_node.name,
|
|
2286
|
+
)
|
|
2287
|
+
)
|
|
2288
|
+
if create_result.failed():
|
|
2289
|
+
msg = f"{self.name}: Failed to create direct outgoing connection from {node.name}.{parameter_name} to {connection.target_node.name}.{connection.target_parameter.name}: {create_result.result_details}"
|
|
2290
|
+
raise RuntimeError(msg)
|
|
2291
|
+
|
|
2292
|
+
# Get all incoming connections
|
|
2293
|
+
incoming_connections = connections.get_incoming_connections_from_node(node, from_node=self)
|
|
2294
|
+
# Delete incoming connections
|
|
2295
|
+
for parameter_name, incoming_connection_list in incoming_connections.items():
|
|
2296
|
+
for incoming_connection in incoming_connection_list:
|
|
2297
|
+
proxy_parameter = incoming_connection.source_parameter
|
|
2298
|
+
# Get the incoming connections to the proxy parameter
|
|
2299
|
+
remap_connections = connections.get_incoming_connections_to_parameter(self, proxy_parameter)
|
|
2300
|
+
# Delete the internal connection
|
|
2301
|
+
delete_result = GriptapeNodes.FlowManager().on_delete_connection_request(
|
|
2302
|
+
DeleteConnectionRequest(
|
|
2303
|
+
source_parameter_name=proxy_parameter.name,
|
|
2304
|
+
target_parameter_name=parameter_name,
|
|
2305
|
+
source_node_name=self.name,
|
|
2306
|
+
target_node_name=node.name,
|
|
2307
|
+
)
|
|
2308
|
+
)
|
|
2309
|
+
if delete_result.failed():
|
|
2310
|
+
msg = f"{self.name}: Failed to delete internal incoming connection from proxy {proxy_parameter.name} to {node.name}.{parameter_name}: {delete_result.result_details}"
|
|
2311
|
+
raise RuntimeError(msg)
|
|
2312
|
+
|
|
2313
|
+
# Now create the new connection! We need to get the connections to the proxy parameter
|
|
2314
|
+
for connection in remap_connections:
|
|
2315
|
+
create_result = GriptapeNodes.FlowManager().on_create_connection_request(
|
|
2316
|
+
CreateConnectionRequest(
|
|
2317
|
+
source_parameter_name=connection.source_parameter.name,
|
|
2318
|
+
target_parameter_name=parameter_name,
|
|
2319
|
+
source_node_name=connection.source_node.name,
|
|
2320
|
+
target_node_name=node.name,
|
|
2321
|
+
)
|
|
2322
|
+
)
|
|
2323
|
+
if create_result.failed():
|
|
2324
|
+
msg = f"{self.name}: Failed to create direct incoming connection from {connection.source_node.name}.{connection.source_parameter.name} to {node.name}.{parameter_name}: {create_result.result_details}"
|
|
2325
|
+
raise RuntimeError(msg)
|
|
2083
2326
|
|
|
2084
2327
|
def _remove_nodes_from_existing_parents(self, nodes: list[BaseNode]) -> None:
|
|
2085
2328
|
"""Remove nodes from their existing parent groups."""
|
|
@@ -2098,35 +2341,169 @@ class NodeGroupNode(BaseNode):
|
|
|
2098
2341
|
node.parent_group = self
|
|
2099
2342
|
self.nodes[node.name] = node
|
|
2100
2343
|
|
|
2101
|
-
def
|
|
2102
|
-
"""
|
|
2103
|
-
|
|
2344
|
+
def _cleanup_proxy_parameter(self, proxy_parameter: Parameter, metadata_key: str) -> None:
|
|
2345
|
+
"""Clean up proxy parameter if it has no more connections.
|
|
2346
|
+
|
|
2347
|
+
Args:
|
|
2348
|
+
proxy_parameter: The proxy parameter to potentially clean up
|
|
2349
|
+
metadata_key: The metadata key ('left_parameters' or 'right_parameters')
|
|
2350
|
+
"""
|
|
2351
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
2352
|
+
|
|
2353
|
+
if proxy_parameter.name not in self._proxy_param_to_connections:
|
|
2104
2354
|
return
|
|
2105
2355
|
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2356
|
+
self._proxy_param_to_connections[proxy_parameter.name] -= 1
|
|
2357
|
+
if self._proxy_param_to_connections[proxy_parameter.name] == 0:
|
|
2358
|
+
GriptapeNodes.NodeManager().on_remove_parameter_from_node_request(
|
|
2359
|
+
request=RemoveParameterFromNodeRequest(node_name=self.name, parameter_name=proxy_parameter.name)
|
|
2360
|
+
)
|
|
2361
|
+
del self._proxy_param_to_connections[proxy_parameter.name]
|
|
2362
|
+
if metadata_key in self.metadata and proxy_parameter.name in self.metadata[metadata_key]:
|
|
2363
|
+
self.metadata[metadata_key].remove(proxy_parameter.name)
|
|
2111
2364
|
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
elif conn not in self.stored_connections.internal_connections:
|
|
2115
|
-
self.track_internal_connection(conn)
|
|
2365
|
+
def _remap_outgoing_connections(self, node: BaseNode, connections: Connections) -> None:
|
|
2366
|
+
"""Remap outgoing connections that go through proxy parameters.
|
|
2116
2367
|
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2368
|
+
Args:
|
|
2369
|
+
node: The node being added to the group
|
|
2370
|
+
connections: Connections object from FlowManager
|
|
2371
|
+
"""
|
|
2372
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
2121
2373
|
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2374
|
+
outgoing_connections = connections.get_outgoing_connections_to_node(node, to_node=self)
|
|
2375
|
+
for parameter_name, outgoing_connection_list in outgoing_connections.items():
|
|
2376
|
+
for outgoing_connection in outgoing_connection_list:
|
|
2377
|
+
proxy_parameter = outgoing_connection.target_parameter
|
|
2378
|
+
remap_connections = connections.get_outgoing_connections_from_parameter(self, proxy_parameter)
|
|
2379
|
+
delete_result = GriptapeNodes.FlowManager().on_delete_connection_request(
|
|
2380
|
+
DeleteConnectionRequest(
|
|
2381
|
+
source_parameter_name=parameter_name,
|
|
2382
|
+
target_parameter_name=proxy_parameter.name,
|
|
2383
|
+
source_node_name=node.name,
|
|
2384
|
+
target_node_name=self.name,
|
|
2385
|
+
)
|
|
2386
|
+
)
|
|
2387
|
+
if delete_result.failed():
|
|
2388
|
+
msg = f"{self.name}: Failed to delete internal outgoing connection from {node.name}.{parameter_name} to proxy {proxy_parameter.name}: {delete_result.result_details}"
|
|
2389
|
+
raise RuntimeError(msg)
|
|
2390
|
+
|
|
2391
|
+
for connection in remap_connections:
|
|
2392
|
+
delete_result = GriptapeNodes.FlowManager().on_delete_connection_request(
|
|
2393
|
+
DeleteConnectionRequest(
|
|
2394
|
+
source_parameter_name=connection.source_parameter.name,
|
|
2395
|
+
target_parameter_name=connection.target_parameter.name,
|
|
2396
|
+
source_node_name=connection.source_node.name,
|
|
2397
|
+
target_node_name=connection.target_node.name,
|
|
2398
|
+
)
|
|
2399
|
+
)
|
|
2400
|
+
if delete_result.failed():
|
|
2401
|
+
msg = f"{self.name}: Failed to delete external connection from proxy {proxy_parameter.name} to {connection.target_node.name}.{connection.target_parameter.name}: {delete_result.result_details}"
|
|
2402
|
+
raise RuntimeError(msg)
|
|
2403
|
+
|
|
2404
|
+
create_result = GriptapeNodes.FlowManager().on_create_connection_request(
|
|
2405
|
+
CreateConnectionRequest(
|
|
2406
|
+
source_parameter_name=parameter_name,
|
|
2407
|
+
target_parameter_name=connection.target_parameter.name,
|
|
2408
|
+
source_node_name=node.name,
|
|
2409
|
+
target_node_name=connection.target_node.name,
|
|
2410
|
+
)
|
|
2411
|
+
)
|
|
2412
|
+
if create_result.failed():
|
|
2413
|
+
msg = f"{self.name}: Failed to create direct outgoing connection from {node.name}.{parameter_name} to {connection.target_node.name}.{connection.target_parameter.name}: {create_result.result_details}"
|
|
2414
|
+
raise RuntimeError(msg)
|
|
2127
2415
|
|
|
2128
|
-
|
|
2129
|
-
|
|
2416
|
+
self._cleanup_proxy_parameter(proxy_parameter, "right_parameters")
|
|
2417
|
+
|
|
2418
|
+
def _remap_incoming_connections(self, node: BaseNode, connections: Connections) -> None:
|
|
2419
|
+
"""Remap incoming connections that go through proxy parameters.
|
|
2420
|
+
|
|
2421
|
+
Args:
|
|
2422
|
+
node: The node being added to the group
|
|
2423
|
+
connections: Connections object from FlowManager
|
|
2424
|
+
"""
|
|
2425
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
2426
|
+
|
|
2427
|
+
incoming_connections = connections.get_incoming_connections_from_node(node, from_node=self)
|
|
2428
|
+
for parameter_name, incoming_connection_list in incoming_connections.items():
|
|
2429
|
+
for incoming_connection in incoming_connection_list:
|
|
2430
|
+
proxy_parameter = incoming_connection.source_parameter
|
|
2431
|
+
remap_connections = connections.get_incoming_connections_to_parameter(self, proxy_parameter)
|
|
2432
|
+
delete_result = GriptapeNodes.FlowManager().on_delete_connection_request(
|
|
2433
|
+
DeleteConnectionRequest(
|
|
2434
|
+
source_parameter_name=proxy_parameter.name,
|
|
2435
|
+
target_parameter_name=parameter_name,
|
|
2436
|
+
source_node_name=self.name,
|
|
2437
|
+
target_node_name=node.name,
|
|
2438
|
+
)
|
|
2439
|
+
)
|
|
2440
|
+
if delete_result.failed():
|
|
2441
|
+
msg = f"{self.name}: Failed to delete internal incoming connection from proxy {proxy_parameter.name} to {node.name}.{parameter_name}: {delete_result.result_details}"
|
|
2442
|
+
raise RuntimeError(msg)
|
|
2443
|
+
|
|
2444
|
+
for connection in remap_connections:
|
|
2445
|
+
delete_result = GriptapeNodes.FlowManager().on_delete_connection_request(
|
|
2446
|
+
DeleteConnectionRequest(
|
|
2447
|
+
source_parameter_name=connection.source_parameter.name,
|
|
2448
|
+
target_parameter_name=proxy_parameter.name,
|
|
2449
|
+
source_node_name=connection.source_node.name,
|
|
2450
|
+
target_node_name=self.name,
|
|
2451
|
+
)
|
|
2452
|
+
)
|
|
2453
|
+
if delete_result.failed():
|
|
2454
|
+
msg = f"{self.name}: Failed to delete external connection from {connection.source_node.name}.{connection.source_parameter.name} to proxy {proxy_parameter.name}: {delete_result.result_details}"
|
|
2455
|
+
raise RuntimeError(msg)
|
|
2456
|
+
|
|
2457
|
+
create_result = GriptapeNodes.FlowManager().on_create_connection_request(
|
|
2458
|
+
CreateConnectionRequest(
|
|
2459
|
+
source_parameter_name=connection.source_parameter.name,
|
|
2460
|
+
target_parameter_name=parameter_name,
|
|
2461
|
+
source_node_name=connection.source_node.name,
|
|
2462
|
+
target_node_name=node.name,
|
|
2463
|
+
)
|
|
2464
|
+
)
|
|
2465
|
+
if create_result.failed():
|
|
2466
|
+
msg = f"{self.name}: Failed to create direct incoming connection from {connection.source_node.name}.{connection.source_parameter.name} to {node.name}.{parameter_name}: {create_result.result_details}"
|
|
2467
|
+
raise RuntimeError(msg)
|
|
2468
|
+
|
|
2469
|
+
self._cleanup_proxy_parameter(proxy_parameter, "left_parameters")
|
|
2470
|
+
|
|
2471
|
+
def remap_to_internal(self, nodes: list[BaseNode], connections: Connections) -> None:
|
|
2472
|
+
"""Remap connections that are now internal after adding nodes to the group.
|
|
2473
|
+
|
|
2474
|
+
When nodes are added to a group, some connections that previously went through
|
|
2475
|
+
proxy parameters may now be internal. This method identifies such connections
|
|
2476
|
+
and restores direct connections between the nodes.
|
|
2477
|
+
|
|
2478
|
+
Args:
|
|
2479
|
+
nodes: List of nodes being added to the group
|
|
2480
|
+
connections: Connections object from FlowManager
|
|
2481
|
+
"""
|
|
2482
|
+
for node in nodes:
|
|
2483
|
+
self._remap_outgoing_connections(node, connections)
|
|
2484
|
+
self._remap_incoming_connections(node, connections)
|
|
2485
|
+
|
|
2486
|
+
def after_outgoing_connection_removed(
|
|
2487
|
+
self, source_parameter: Parameter, target_node: BaseNode, target_parameter: Parameter
|
|
2488
|
+
) -> None:
|
|
2489
|
+
# Instead of right_parameters, we should check the internal connections
|
|
2490
|
+
if target_node.parent_group == self:
|
|
2491
|
+
metadata_key = "left_parameters"
|
|
2492
|
+
else:
|
|
2493
|
+
metadata_key = "right_parameters"
|
|
2494
|
+
self._cleanup_proxy_parameter(source_parameter, metadata_key)
|
|
2495
|
+
return super().after_outgoing_connection_removed(source_parameter, target_node, target_parameter)
|
|
2496
|
+
|
|
2497
|
+
def after_incoming_connection_removed(
|
|
2498
|
+
self, source_node: BaseNode, source_parameter: Parameter, target_parameter: Parameter
|
|
2499
|
+
) -> None:
|
|
2500
|
+
# Instead of left_parameters, we should check the internal connections.
|
|
2501
|
+
if source_node.parent_group == self:
|
|
2502
|
+
metadata_key = "right_parameters"
|
|
2503
|
+
else:
|
|
2504
|
+
metadata_key = "left_parameters"
|
|
2505
|
+
self._cleanup_proxy_parameter(target_parameter, metadata_key)
|
|
2506
|
+
return super().after_incoming_connection_removed(source_node, source_parameter, target_parameter)
|
|
2130
2507
|
|
|
2131
2508
|
def add_nodes_to_group(self, nodes: list[BaseNode]) -> None:
|
|
2132
2509
|
"""Add nodes to the group and track their connections.
|
|
@@ -2134,23 +2511,62 @@ class NodeGroupNode(BaseNode):
|
|
|
2134
2511
|
Args:
|
|
2135
2512
|
nodes: List of nodes to add to the group
|
|
2136
2513
|
"""
|
|
2514
|
+
from griptape_nodes.retained_mode.events.node_events import (
|
|
2515
|
+
MoveNodeToNewFlowRequest,
|
|
2516
|
+
MoveNodeToNewFlowResultSuccess,
|
|
2517
|
+
)
|
|
2137
2518
|
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
2138
2519
|
|
|
2139
2520
|
self._remove_nodes_from_existing_parents(nodes)
|
|
2140
2521
|
self._add_nodes_to_group_dict(nodes)
|
|
2141
2522
|
|
|
2523
|
+
# Create subflow on-demand if it doesn't exist
|
|
2524
|
+
subflow_name = self.metadata.get("subflow_name")
|
|
2525
|
+
if subflow_name is None:
|
|
2526
|
+
self._create_subflow()
|
|
2527
|
+
subflow_name = self.metadata.get("subflow_name")
|
|
2528
|
+
|
|
2529
|
+
if subflow_name is not None:
|
|
2530
|
+
for node in nodes:
|
|
2531
|
+
move_request = MoveNodeToNewFlowRequest(node_name=node.name, target_flow_name=subflow_name)
|
|
2532
|
+
move_result = GriptapeNodes.handle_request(move_request)
|
|
2533
|
+
if not isinstance(move_result, MoveNodeToNewFlowResultSuccess):
|
|
2534
|
+
msg = "%s failed to move node '%s' to subflow: %s", self.name, node.name, move_result.result_details
|
|
2535
|
+
logger.error(msg)
|
|
2536
|
+
raise RuntimeError(msg) # noqa: TRY004
|
|
2537
|
+
|
|
2142
2538
|
connections = GriptapeNodes.FlowManager().get_connections()
|
|
2143
2539
|
node_names_in_group = set(self.nodes.keys())
|
|
2144
2540
|
self.metadata["node_names_in_group"] = list(node_names_in_group)
|
|
2541
|
+
self.remap_to_internal(nodes, connections)
|
|
2542
|
+
self._map_external_connections_for_nodes(nodes, connections, node_names_in_group)
|
|
2145
2543
|
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2544
|
+
def _map_external_connections_for_nodes(
|
|
2545
|
+
self, nodes: list[BaseNode], connections: Connections, node_names_in_group: set[str]
|
|
2546
|
+
) -> None:
|
|
2547
|
+
"""Map external connections for nodes being added to the group.
|
|
2150
2548
|
|
|
2549
|
+
Args:
|
|
2550
|
+
nodes: List of nodes being added
|
|
2551
|
+
connections: Connections object from FlowManager
|
|
2552
|
+
node_names_in_group: Set of all node names currently in the group
|
|
2553
|
+
"""
|
|
2151
2554
|
for node in nodes:
|
|
2152
|
-
|
|
2153
|
-
|
|
2555
|
+
outgoing_connections = connections.get_all_outgoing_connections(node)
|
|
2556
|
+
for conn in outgoing_connections:
|
|
2557
|
+
if conn.target_node.name not in node_names_in_group:
|
|
2558
|
+
self.map_external_connection(
|
|
2559
|
+
conn=conn,
|
|
2560
|
+
is_incoming=False,
|
|
2561
|
+
)
|
|
2562
|
+
|
|
2563
|
+
incoming_connections = connections.get_all_incoming_connections(node)
|
|
2564
|
+
for conn in incoming_connections:
|
|
2565
|
+
if conn.source_node.name not in node_names_in_group:
|
|
2566
|
+
self.map_external_connection(
|
|
2567
|
+
conn=conn,
|
|
2568
|
+
is_incoming=True,
|
|
2569
|
+
)
|
|
2154
2570
|
|
|
2155
2571
|
def _validate_nodes_in_group(self, nodes: list[BaseNode]) -> None:
|
|
2156
2572
|
"""Validate that all nodes are in the group."""
|
|
@@ -2159,47 +2575,15 @@ class NodeGroupNode(BaseNode):
|
|
|
2159
2575
|
msg = f"Node {node.name} is not in node group {self.name}"
|
|
2160
2576
|
raise ValueError(msg)
|
|
2161
2577
|
|
|
2162
|
-
def
|
|
2163
|
-
"""
|
|
2164
|
-
for conn in list(self.stored_connections.external_connections.incoming_connections):
|
|
2165
|
-
conn_id = id(conn)
|
|
2166
|
-
original_target = self.stored_connections.original_targets.incoming_sources.get(conn_id)
|
|
2167
|
-
if original_target and original_target.name == node.name:
|
|
2168
|
-
self.untrack_external_connection(conn, conn_id, is_incoming=True)
|
|
2169
|
-
|
|
2170
|
-
def _untrack_external_outgoing_for_node(self, node: BaseNode) -> None:
|
|
2171
|
-
"""Untrack external outgoing connections for a node."""
|
|
2172
|
-
for conn in list(self.stored_connections.external_connections.outgoing_connections):
|
|
2173
|
-
conn_id = id(conn)
|
|
2174
|
-
original_source = self.stored_connections.original_targets.outgoing_targets.get(conn_id)
|
|
2175
|
-
if original_source and original_source.name == node.name:
|
|
2176
|
-
self.untrack_external_connection(conn, conn_id, is_incoming=False)
|
|
2177
|
-
|
|
2178
|
-
def _untrack_internal_for_node(self, node: BaseNode, nodes_being_removed: set[str]) -> None:
|
|
2179
|
-
"""Untrack internal connections for a node."""
|
|
2180
|
-
for conn in list(self.stored_connections.internal_connections):
|
|
2181
|
-
if node.name not in (conn.source_node.name, conn.target_node.name):
|
|
2182
|
-
continue
|
|
2183
|
-
|
|
2184
|
-
other_node_name = conn.target_node.name if conn.source_node.name == node.name else conn.source_node.name
|
|
2185
|
-
if other_node_name in nodes_being_removed or other_node_name not in self.nodes:
|
|
2186
|
-
self.untrack_internal_connection(conn)
|
|
2578
|
+
def delete_nodes_from_group(self, nodes: list[BaseNode]) -> None:
|
|
2579
|
+
"""Delete nodes from the group and untrack their connections.
|
|
2187
2580
|
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
Returns:
|
|
2192
|
-
True if any external incoming connection is a control input, False otherwise
|
|
2581
|
+
Args:
|
|
2582
|
+
nodes: List of nodes to delete from the group
|
|
2193
2583
|
"""
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
if conn.target_parameter.type == ParameterTypeBuiltin.CONTROL_TYPE:
|
|
2198
|
-
return True
|
|
2199
|
-
if ParameterTypeBuiltin.CONTROL_TYPE.value in conn.target_parameter.input_types:
|
|
2200
|
-
return True
|
|
2201
|
-
|
|
2202
|
-
return False
|
|
2584
|
+
for node in nodes:
|
|
2585
|
+
self.nodes.pop(node.name)
|
|
2586
|
+
self.metadata["node_names_in_group"] = list(self.nodes.keys())
|
|
2203
2587
|
|
|
2204
2588
|
def remove_nodes_from_group(self, nodes: list[BaseNode]) -> None:
|
|
2205
2589
|
"""Remove nodes from the group and untrack their connections.
|
|
@@ -2207,24 +2591,48 @@ class NodeGroupNode(BaseNode):
|
|
|
2207
2591
|
Args:
|
|
2208
2592
|
nodes: List of nodes to remove from the group
|
|
2209
2593
|
"""
|
|
2594
|
+
from griptape_nodes.retained_mode.events.node_events import (
|
|
2595
|
+
MoveNodeToNewFlowRequest,
|
|
2596
|
+
MoveNodeToNewFlowResultSuccess,
|
|
2597
|
+
)
|
|
2210
2598
|
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
2211
2599
|
|
|
2212
2600
|
self._validate_nodes_in_group(nodes)
|
|
2213
2601
|
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
self.
|
|
2219
|
-
self._untrack_external_outgoing_for_node(node)
|
|
2220
|
-
self._untrack_internal_for_node(node, nodes_being_removed)
|
|
2602
|
+
parent_flow_name = None
|
|
2603
|
+
try:
|
|
2604
|
+
parent_flow_name = GriptapeNodes.NodeManager().get_node_parent_flow_by_name(self.name)
|
|
2605
|
+
except KeyError:
|
|
2606
|
+
logger.warning("%s has no parent flow, cannot move nodes back", self.name)
|
|
2221
2607
|
|
|
2608
|
+
connections = GriptapeNodes.FlowManager().get_connections()
|
|
2222
2609
|
for node in nodes:
|
|
2223
2610
|
node.parent_group = None
|
|
2224
2611
|
self.nodes.pop(node.name)
|
|
2225
2612
|
|
|
2613
|
+
if parent_flow_name is not None:
|
|
2614
|
+
move_request = MoveNodeToNewFlowRequest(node_name=node.name, target_flow_name=parent_flow_name)
|
|
2615
|
+
move_result = GriptapeNodes.handle_request(move_request)
|
|
2616
|
+
if not isinstance(move_result, MoveNodeToNewFlowResultSuccess):
|
|
2617
|
+
msg = (
|
|
2618
|
+
"%s failed to move node '%s' back to parent flow: %s",
|
|
2619
|
+
self.name,
|
|
2620
|
+
node.name,
|
|
2621
|
+
move_result.result_details,
|
|
2622
|
+
)
|
|
2623
|
+
logger.error(msg)
|
|
2624
|
+
raise RuntimeError(msg)
|
|
2625
|
+
|
|
2626
|
+
for node in nodes:
|
|
2627
|
+
self.unmap_node_connections(node, connections)
|
|
2628
|
+
|
|
2226
2629
|
self.metadata["node_names_in_group"] = list(self.nodes.keys())
|
|
2227
2630
|
|
|
2631
|
+
remaining_nodes = list(self.nodes.values())
|
|
2632
|
+
if remaining_nodes:
|
|
2633
|
+
node_names_in_group = set(self.nodes.keys())
|
|
2634
|
+
self._map_external_connections_for_nodes(remaining_nodes, connections, node_names_in_group)
|
|
2635
|
+
|
|
2228
2636
|
async def aprocess(self) -> None:
|
|
2229
2637
|
"""Execute all nodes in the group in parallel.
|
|
2230
2638
|
|
|
@@ -2232,6 +2640,50 @@ class NodeGroupNode(BaseNode):
|
|
|
2232
2640
|
group concurrently using asyncio.gather and handles propagating input
|
|
2233
2641
|
values from the proxy to the grouped nodes.
|
|
2234
2642
|
"""
|
|
2643
|
+
from griptape_nodes.retained_mode.events.execution_events import StartLocalSubflowRequest
|
|
2644
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
2645
|
+
|
|
2646
|
+
subflow = self.metadata.get("subflow_name")
|
|
2647
|
+
if subflow is not None and isinstance(subflow, str):
|
|
2648
|
+
await GriptapeNodes.FlowManager().on_start_local_subflow_request(
|
|
2649
|
+
StartLocalSubflowRequest(flow_name=subflow)
|
|
2650
|
+
)
|
|
2651
|
+
|
|
2652
|
+
# After subflow execution, collect output values from internal nodes
|
|
2653
|
+
# and set them on the NodeGroup's output (right) proxy parameters
|
|
2654
|
+
connections = GriptapeNodes.FlowManager().get_connections()
|
|
2655
|
+
|
|
2656
|
+
# Get all right parameters (output parameters)
|
|
2657
|
+
right_params = self.metadata.get("right_parameters", [])
|
|
2658
|
+
for proxy_param_name in right_params:
|
|
2659
|
+
proxy_param = self.get_parameter_by_name(proxy_param_name)
|
|
2660
|
+
if proxy_param is None:
|
|
2661
|
+
continue
|
|
2662
|
+
|
|
2663
|
+
# Find the internal node connected to this proxy parameter
|
|
2664
|
+
# The internal connection goes: InternalNode -> ProxyParameter
|
|
2665
|
+
incoming_connections = connections.get_incoming_connections_to_parameter(self, proxy_param)
|
|
2666
|
+
if not incoming_connections:
|
|
2667
|
+
continue
|
|
2668
|
+
|
|
2669
|
+
# Get the first connection (there should only be one internal connection)
|
|
2670
|
+
for connection in incoming_connections:
|
|
2671
|
+
if not connection.is_node_group_internal:
|
|
2672
|
+
continue
|
|
2673
|
+
|
|
2674
|
+
# Get the value from the internal node's output parameter
|
|
2675
|
+
internal_node = connection.source_node
|
|
2676
|
+
internal_param = connection.source_parameter
|
|
2677
|
+
|
|
2678
|
+
if internal_param.name in internal_node.parameter_output_values:
|
|
2679
|
+
value = internal_node.parameter_output_values[internal_param.name]
|
|
2680
|
+
else:
|
|
2681
|
+
value = internal_node.get_parameter_value(internal_param.name)
|
|
2682
|
+
|
|
2683
|
+
# Set the value on the NodeGroup's proxy parameter output
|
|
2684
|
+
if value is not None:
|
|
2685
|
+
self.parameter_output_values[proxy_param_name] = value
|
|
2686
|
+
break
|
|
2235
2687
|
|
|
2236
2688
|
def process(self) -> Any:
|
|
2237
2689
|
"""Synchronous process method - not used for proxy nodes."""
|
|
@@ -2242,6 +2694,7 @@ class Connection:
|
|
|
2242
2694
|
target_node: BaseNode
|
|
2243
2695
|
source_parameter: Parameter
|
|
2244
2696
|
target_parameter: Parameter
|
|
2697
|
+
is_node_group_internal: bool
|
|
2245
2698
|
|
|
2246
2699
|
def __init__(
|
|
2247
2700
|
self,
|
|
@@ -2249,11 +2702,14 @@ class Connection:
|
|
|
2249
2702
|
source_parameter: Parameter,
|
|
2250
2703
|
target_node: BaseNode,
|
|
2251
2704
|
target_parameter: Parameter,
|
|
2705
|
+
*,
|
|
2706
|
+
is_node_group_internal: bool = False,
|
|
2252
2707
|
) -> None:
|
|
2253
2708
|
self.source_node = source_node
|
|
2254
2709
|
self.target_node = target_node
|
|
2255
2710
|
self.source_parameter = source_parameter
|
|
2256
2711
|
self.target_parameter = target_parameter
|
|
2712
|
+
self.is_node_group_internal = is_node_group_internal
|
|
2257
2713
|
|
|
2258
2714
|
def get_target_node(self) -> BaseNode:
|
|
2259
2715
|
return self.target_node
|