griptape-nodes 0.65.1__py3-none-any.whl → 0.65.2__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.
@@ -11,7 +11,6 @@ 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,
15
14
  ControlParameterInput,
16
15
  ControlParameterOutput,
17
16
  NodeMessageResult,
@@ -23,7 +22,6 @@ from griptape_nodes.exe_types.core_types import (
23
22
  ParameterMessage,
24
23
  ParameterMode,
25
24
  ParameterTypeBuiltin,
26
- Trait,
27
25
  )
28
26
  from griptape_nodes.exe_types.param_components.execution_status_component import ExecutionStatusComponent
29
27
  from griptape_nodes.exe_types.type_validator import TypeValidator
@@ -33,14 +31,8 @@ from griptape_nodes.retained_mode.events.base_events import (
33
31
  ProgressEvent,
34
32
  RequestPayload,
35
33
  )
36
- from griptape_nodes.retained_mode.events.connection_events import (
37
- CreateConnectionRequest,
38
- DeleteConnectionRequest,
39
- DeleteConnectionResultSuccess,
40
- )
41
34
  from griptape_nodes.retained_mode.events.parameter_events import (
42
35
  AddParameterToNodeRequest,
43
- AddParameterToNodeResultSuccess,
44
36
  RemoveElementEvent,
45
37
  RemoveParameterFromNodeRequest,
46
38
  )
@@ -48,7 +40,6 @@ from griptape_nodes.traits.options import Options
48
40
  from griptape_nodes.utils import async_utils
49
41
 
50
42
  if TYPE_CHECKING:
51
- from griptape_nodes.exe_types.connections import Connections
52
43
  from griptape_nodes.exe_types.core_types import NodeMessagePayload
53
44
  from griptape_nodes.node_library.library_registry import LibraryNameAndVersion
54
45
 
@@ -1893,802 +1884,6 @@ class ErrorProxyNode(BaseNode):
1893
1884
  return None
1894
1885
 
1895
1886
 
1896
- @dataclass
1897
- class NodeGroupStoredConnections:
1898
- """Stores all of the connections for when we create/remove connections on a node group based on the parameters."""
1899
-
1900
- @dataclass
1901
- class ExternalConnections:
1902
- """Represents the External connections to/from the node group."""
1903
-
1904
- incoming_connections: list[Connection] = field(default_factory=list)
1905
- outgoing_connections: list[Connection] = field(default_factory=list)
1906
-
1907
- @dataclass
1908
- class OriginalTargets:
1909
- """Represents the connections before they were remapped."""
1910
-
1911
- incoming_sources: dict[int, BaseNode] = field(default_factory=dict)
1912
- outgoing_targets: dict[int, BaseNode] = field(default_factory=dict)
1913
-
1914
- internal_connections: list[Connection] = field(default_factory=list)
1915
- external_connections: ExternalConnections = field(default_factory=ExternalConnections)
1916
- original_targets: OriginalTargets = field(default_factory=OriginalTargets)
1917
-
1918
-
1919
- class NodeGroupNode(BaseNode):
1920
- """Proxy node that represents a group of nodes during DAG execution.
1921
-
1922
- This node acts as a single execution unit for a group of nodes that should
1923
- be executed in parallel. When the DAG executor encounters this proxy node,
1924
- it passes the entire NodeGroup to the NodeExecutor which handles parallel
1925
- execution of all grouped nodes.
1926
-
1927
- The proxy node has parameters that mirror the external connections to/from
1928
- the group, allowing it to seamlessly integrate into the DAG structure.
1929
- """
1930
-
1931
- nodes: dict[str, BaseNode]
1932
- _proxy_param_to_connections: dict[str, int]
1933
-
1934
- def __init__(
1935
- self,
1936
- name: str,
1937
- metadata: dict[Any, Any] | None = None,
1938
- ) -> None:
1939
- super().__init__(name, metadata)
1940
- self.execution_environment = Parameter(
1941
- name="execution_environment",
1942
- tooltip="Environment that the group should execute in",
1943
- type=ParameterTypeBuiltin.STR,
1944
- allowed_modes={ParameterMode.PROPERTY},
1945
- default_value=LOCAL_EXECUTION,
1946
- traits={Options(choices=get_library_names_with_publish_handlers())},
1947
- )
1948
- self.add_parameter(self.execution_environment)
1949
- self.nodes = {}
1950
- # Track mapping from proxy parameter name to (original_node, original_param_name)
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
- }
1958
-
1959
- # Don't create subflow in __init__ - it will be created on-demand when nodes are added
1960
- # or restored during deserialization
1961
-
1962
- # Add parameters from registered StartFlow nodes for each publishing library
1963
- self._add_start_flow_parameters()
1964
-
1965
- def _create_subflow(self) -> None:
1966
- """Create a dedicated subflow for this NodeGroup's nodes.
1967
-
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.
1970
- """
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
1976
-
1977
- subflow_name = f"{self.name}_subflow"
1978
- self.metadata["subflow_name"] = subflow_name
1979
-
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
1983
-
1984
- # Create metadata with flow_type
1985
- subflow_metadata = {"flow_type": NODE_GROUP_FLOW}
1986
-
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)
1994
-
1995
- if not isinstance(result, CreateFlowResultSuccess):
1996
- logger.warning("%s failed to create subflow '%s': %s", self.name, subflow_name, result.result_details)
1997
-
1998
- def _add_start_flow_parameters(self) -> None:
1999
- """Add parameters from all registered StartFlow nodes to this NodeGroupNode.
2000
-
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"] = {}
2016
-
2017
- # Get all libraries that have registered PublishWorkflowRequest handlers
2018
- library_manager = GriptapeNodes.LibraryManager()
2019
- event_handlers = library_manager.get_registered_event_handlers(PublishWorkflowRequest)
2020
-
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
2031
- """
2032
- import logging
2033
-
2034
- from griptape_nodes.node_library.library_registry import LibraryRegistry
2035
- from griptape_nodes.retained_mode.events.workflow_events import PublishWorkflowRegisteredEventData
2036
-
2037
- logger = logging.getLogger(__name__)
2038
-
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
2086
-
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.
2102
-
2103
- Args:
2104
- param: The parameter to clone
2105
- new_name: The new name for the cloned parameter
2106
- """
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
- )
2123
-
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.
2129
-
2130
- Args:
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
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)
2168
- if is_incoming:
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)
2175
- else:
2176
- self.metadata["right_parameters"] = [proxy_param.name]
2177
-
2178
- return proxy_param
2179
-
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
2187
-
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.
2190
-
2191
- Args:
2192
- conn: The external connection to track
2193
- conn_id: ID of the connection
2194
- is_incoming: True if connection is coming INTO the group
2195
- """
2196
- if is_incoming:
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
2240
- else:
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)
2326
-
2327
- def _remove_nodes_from_existing_parents(self, nodes: list[BaseNode]) -> None:
2328
- """Remove nodes from their existing parent groups."""
2329
- child_nodes = {}
2330
- for node in nodes:
2331
- if node.parent_group is not None:
2332
- existing_parent_group = node.parent_group
2333
- if isinstance(existing_parent_group, NodeGroupNode):
2334
- child_nodes.setdefault(existing_parent_group, []).append(node)
2335
- for parent_group, node_list in child_nodes.items():
2336
- parent_group.remove_nodes_from_group(node_list)
2337
-
2338
- def _add_nodes_to_group_dict(self, nodes: list[BaseNode]) -> None:
2339
- """Add nodes to the group's node dictionary."""
2340
- for node in nodes:
2341
- node.parent_group = self
2342
- self.nodes[node.name] = node
2343
-
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:
2354
- return
2355
-
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)
2364
-
2365
- def _remap_outgoing_connections(self, node: BaseNode, connections: Connections) -> None:
2366
- """Remap outgoing connections that go through proxy parameters.
2367
-
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
2373
-
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)
2415
-
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)
2507
-
2508
- def add_nodes_to_group(self, nodes: list[BaseNode]) -> None:
2509
- """Add nodes to the group and track their connections.
2510
-
2511
- Args:
2512
- nodes: List of nodes to add to the group
2513
- """
2514
- from griptape_nodes.retained_mode.events.node_events import (
2515
- MoveNodeToNewFlowRequest,
2516
- MoveNodeToNewFlowResultSuccess,
2517
- )
2518
- from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
2519
-
2520
- self._remove_nodes_from_existing_parents(nodes)
2521
- self._add_nodes_to_group_dict(nodes)
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
-
2538
- connections = GriptapeNodes.FlowManager().get_connections()
2539
- node_names_in_group = set(self.nodes.keys())
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)
2543
-
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.
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
- """
2554
- for node in nodes:
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
- )
2570
-
2571
- def _validate_nodes_in_group(self, nodes: list[BaseNode]) -> None:
2572
- """Validate that all nodes are in the group."""
2573
- for node in nodes:
2574
- if node.name not in self.nodes:
2575
- msg = f"Node {node.name} is not in node group {self.name}"
2576
- raise ValueError(msg)
2577
-
2578
- def delete_nodes_from_group(self, nodes: list[BaseNode]) -> None:
2579
- """Delete nodes from the group and untrack their connections.
2580
-
2581
- Args:
2582
- nodes: List of nodes to delete from the group
2583
- """
2584
- for node in nodes:
2585
- self.nodes.pop(node.name)
2586
- self.metadata["node_names_in_group"] = list(self.nodes.keys())
2587
-
2588
- def remove_nodes_from_group(self, nodes: list[BaseNode]) -> None:
2589
- """Remove nodes from the group and untrack their connections.
2590
-
2591
- Args:
2592
- nodes: List of nodes to remove from the group
2593
- """
2594
- from griptape_nodes.retained_mode.events.node_events import (
2595
- MoveNodeToNewFlowRequest,
2596
- MoveNodeToNewFlowResultSuccess,
2597
- )
2598
- from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
2599
-
2600
- self._validate_nodes_in_group(nodes)
2601
-
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)
2607
-
2608
- connections = GriptapeNodes.FlowManager().get_connections()
2609
- for node in nodes:
2610
- node.parent_group = None
2611
- self.nodes.pop(node.name)
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
-
2629
- self.metadata["node_names_in_group"] = list(self.nodes.keys())
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
-
2636
- async def aprocess(self) -> None:
2637
- """Execute all nodes in the group in parallel.
2638
-
2639
- This method is called by the DAG executor. It executes all nodes in the
2640
- group concurrently using asyncio.gather and handles propagating input
2641
- values from the proxy to the grouped nodes.
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
2687
-
2688
- def process(self) -> Any:
2689
- """Synchronous process method - not used for proxy nodes."""
2690
-
2691
-
2692
1887
  class Connection:
2693
1888
  source_node: BaseNode
2694
1889
  target_node: BaseNode