griptape-nodes 0.57.0__py3-none-any.whl → 0.58.0__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/api_client/__init__.py +9 -0
- griptape_nodes/api_client/client.py +279 -0
- griptape_nodes/api_client/request_client.py +273 -0
- griptape_nodes/app/app.py +57 -150
- griptape_nodes/bootstrap/utils/python_subprocess_executor.py +1 -1
- griptape_nodes/bootstrap/workflow_executors/local_session_workflow_executor.py +22 -50
- griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +6 -1
- griptape_nodes/bootstrap/workflow_executors/subprocess_workflow_executor.py +27 -46
- griptape_nodes/bootstrap/workflow_executors/utils/subprocess_script.py +7 -0
- griptape_nodes/bootstrap/workflow_publishers/local_workflow_publisher.py +3 -1
- griptape_nodes/bootstrap/workflow_publishers/subprocess_workflow_publisher.py +3 -1
- griptape_nodes/bootstrap/workflow_publishers/utils/subprocess_script.py +16 -1
- griptape_nodes/common/node_executor.py +466 -0
- griptape_nodes/drivers/storage/base_storage_driver.py +0 -11
- griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +7 -25
- griptape_nodes/drivers/storage/local_storage_driver.py +2 -2
- griptape_nodes/exe_types/connections.py +37 -9
- griptape_nodes/exe_types/core_types.py +1 -1
- griptape_nodes/exe_types/node_types.py +115 -22
- griptape_nodes/machines/control_flow.py +48 -7
- griptape_nodes/machines/parallel_resolution.py +98 -29
- griptape_nodes/machines/sequential_resolution.py +61 -22
- griptape_nodes/node_library/library_registry.py +24 -1
- griptape_nodes/node_library/workflow_registry.py +38 -2
- griptape_nodes/retained_mode/events/execution_events.py +8 -1
- griptape_nodes/retained_mode/events/flow_events.py +90 -3
- griptape_nodes/retained_mode/events/node_events.py +17 -10
- griptape_nodes/retained_mode/events/workflow_events.py +5 -0
- griptape_nodes/retained_mode/griptape_nodes.py +16 -219
- griptape_nodes/retained_mode/managers/config_manager.py +0 -46
- griptape_nodes/retained_mode/managers/engine_identity_manager.py +225 -74
- griptape_nodes/retained_mode/managers/flow_manager.py +1276 -230
- griptape_nodes/retained_mode/managers/library_manager.py +7 -8
- griptape_nodes/retained_mode/managers/node_manager.py +197 -9
- griptape_nodes/retained_mode/managers/secrets_manager.py +26 -0
- griptape_nodes/retained_mode/managers/session_manager.py +264 -227
- griptape_nodes/retained_mode/managers/settings.py +4 -38
- griptape_nodes/retained_mode/managers/static_files_manager.py +3 -3
- griptape_nodes/retained_mode/managers/version_compatibility_manager.py +135 -6
- griptape_nodes/retained_mode/managers/workflow_manager.py +206 -78
- griptape_nodes/servers/mcp.py +23 -15
- griptape_nodes/utils/async_utils.py +36 -0
- griptape_nodes/utils/dict_utils.py +8 -2
- griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +11 -6
- griptape_nodes/version_compatibility/workflow_versions/v0_7_0/local_executor_argument_addition.py +12 -5
- {griptape_nodes-0.57.0.dist-info → griptape_nodes-0.58.0.dist-info}/METADATA +4 -3
- {griptape_nodes-0.57.0.dist-info → griptape_nodes-0.58.0.dist-info}/RECORD +49 -47
- {griptape_nodes-0.57.0.dist-info → griptape_nodes-0.58.0.dist-info}/WHEEL +1 -1
- griptape_nodes/retained_mode/utils/engine_identity.py +0 -245
- griptape_nodes/servers/ws_request_manager.py +0 -268
- {griptape_nodes-0.57.0.dist-info → griptape_nodes-0.58.0.dist-info}/entry_points.txt +0 -0
|
@@ -6,6 +6,7 @@ from queue import Queue
|
|
|
6
6
|
from typing import TYPE_CHECKING, Any, NamedTuple, cast
|
|
7
7
|
from uuid import uuid4
|
|
8
8
|
|
|
9
|
+
from griptape_nodes.common.node_executor import NodeExecutor
|
|
9
10
|
from griptape_nodes.exe_types.connections import Connections
|
|
10
11
|
from griptape_nodes.exe_types.core_types import (
|
|
11
12
|
Parameter,
|
|
@@ -29,6 +30,7 @@ from griptape_nodes.machines.dag_builder import DagBuilder
|
|
|
29
30
|
from griptape_nodes.machines.parallel_resolution import ParallelResolutionMachine
|
|
30
31
|
from griptape_nodes.machines.sequential_resolution import SequentialResolutionMachine
|
|
31
32
|
from griptape_nodes.node_library.library_registry import LibraryNameAndVersion, LibraryRegistry
|
|
33
|
+
from griptape_nodes.node_library.workflow_registry import LibraryNameAndNodeType
|
|
32
34
|
from griptape_nodes.retained_mode.events.base_events import (
|
|
33
35
|
ExecutionEvent,
|
|
34
36
|
ExecutionGriptapeNodeEvent,
|
|
@@ -103,9 +105,14 @@ from griptape_nodes.retained_mode.events.flow_events import (
|
|
|
103
105
|
ListNodesInFlowRequest,
|
|
104
106
|
ListNodesInFlowResultFailure,
|
|
105
107
|
ListNodesInFlowResultSuccess,
|
|
108
|
+
OriginalNodeParameter,
|
|
106
109
|
PackageNodeAsSerializedFlowRequest,
|
|
107
110
|
PackageNodeAsSerializedFlowResultFailure,
|
|
108
111
|
PackageNodeAsSerializedFlowResultSuccess,
|
|
112
|
+
PackageNodesAsSerializedFlowRequest,
|
|
113
|
+
PackageNodesAsSerializedFlowResultFailure,
|
|
114
|
+
PackageNodesAsSerializedFlowResultSuccess,
|
|
115
|
+
SanitizedParameterName,
|
|
109
116
|
SerializedFlowCommands,
|
|
110
117
|
SerializeFlowToCommandsRequest,
|
|
111
118
|
SerializeFlowToCommandsResultFailure,
|
|
@@ -177,11 +184,20 @@ class PackageNodeInfo(NamedTuple):
|
|
|
177
184
|
package_flow_name: str
|
|
178
185
|
|
|
179
186
|
|
|
187
|
+
class StartNodeIncomingDataResult(NamedTuple):
|
|
188
|
+
"""Result of processing incoming data connections for a start node."""
|
|
189
|
+
|
|
190
|
+
parameter_commands: list[AddParameterToNodeRequest]
|
|
191
|
+
data_connections: list[SerializedFlowCommands.IndirectConnectionSerialization]
|
|
192
|
+
input_shape_data: WorkflowShapeNodes
|
|
193
|
+
parameter_value_commands: list[SerializedNodeCommands.IndirectSetParameterValueCommand]
|
|
194
|
+
|
|
195
|
+
|
|
180
196
|
class PackagingStartNodeResult(NamedTuple):
|
|
181
|
-
"""Result of creating start node commands and
|
|
197
|
+
"""Result of creating start node commands and connections for flow packaging."""
|
|
182
198
|
|
|
183
199
|
start_node_commands: SerializedNodeCommands
|
|
184
|
-
|
|
200
|
+
start_to_package_connections: list[SerializedFlowCommands.IndirectConnectionSerialization]
|
|
185
201
|
input_shape_data: WorkflowShapeNodes
|
|
186
202
|
start_node_parameter_value_commands: list[SerializedNodeCommands.IndirectSetParameterValueCommand]
|
|
187
203
|
|
|
@@ -190,10 +206,18 @@ class PackagingEndNodeResult(NamedTuple):
|
|
|
190
206
|
"""Result of creating end node commands and data connections for flow packaging."""
|
|
191
207
|
|
|
192
208
|
end_node_commands: SerializedNodeCommands
|
|
193
|
-
|
|
209
|
+
package_to_end_connections: list[SerializedFlowCommands.IndirectConnectionSerialization]
|
|
194
210
|
output_shape_data: WorkflowShapeNodes
|
|
195
211
|
|
|
196
212
|
|
|
213
|
+
class MultiNodeEndNodeResult(NamedTuple):
|
|
214
|
+
"""Result of creating end node commands and parameter mappings for multi-node packaging."""
|
|
215
|
+
|
|
216
|
+
packaging_result: PackagingEndNodeResult
|
|
217
|
+
parameter_name_mappings: dict[SanitizedParameterName, OriginalNodeParameter]
|
|
218
|
+
alter_parameter_commands: list[AlterParameterDetailsRequest]
|
|
219
|
+
|
|
220
|
+
|
|
197
221
|
class FlowManager:
|
|
198
222
|
_name_to_parent_name: dict[str, str | None]
|
|
199
223
|
_flow_to_referenced_workflow_name: dict[ControlFlow, str]
|
|
@@ -204,6 +228,7 @@ class FlowManager:
|
|
|
204
228
|
_global_control_flow_machine: ControlFlowMachine | None
|
|
205
229
|
_global_single_node_resolution: bool
|
|
206
230
|
_global_dag_builder: DagBuilder
|
|
231
|
+
_node_executor: NodeExecutor
|
|
207
232
|
|
|
208
233
|
def __init__(self, event_manager: EventManager) -> None:
|
|
209
234
|
event_manager.assign_manager_to_request_type(CreateFlowRequest, self.on_create_flow_request)
|
|
@@ -240,6 +265,9 @@ class FlowManager:
|
|
|
240
265
|
event_manager.assign_manager_to_request_type(
|
|
241
266
|
PackageNodeAsSerializedFlowRequest, self.on_package_node_as_serialized_flow_request
|
|
242
267
|
)
|
|
268
|
+
event_manager.assign_manager_to_request_type(
|
|
269
|
+
PackageNodesAsSerializedFlowRequest, self.on_package_nodes_as_serialized_flow_request
|
|
270
|
+
)
|
|
243
271
|
event_manager.assign_manager_to_request_type(FlushParameterChangesRequest, self.on_flush_request)
|
|
244
272
|
|
|
245
273
|
self._name_to_parent_name = {}
|
|
@@ -251,6 +279,7 @@ class FlowManager:
|
|
|
251
279
|
self._global_control_flow_machine = None # Track the current control flow machine
|
|
252
280
|
self._global_single_node_resolution = False
|
|
253
281
|
self._global_dag_builder = DagBuilder()
|
|
282
|
+
self._node_executor = NodeExecutor()
|
|
254
283
|
|
|
255
284
|
@property
|
|
256
285
|
def global_single_node_resolution(self) -> bool:
|
|
@@ -264,6 +293,10 @@ class FlowManager:
|
|
|
264
293
|
def global_dag_builder(self) -> DagBuilder:
|
|
265
294
|
return self._global_dag_builder
|
|
266
295
|
|
|
296
|
+
@property
|
|
297
|
+
def node_executor(self) -> NodeExecutor:
|
|
298
|
+
return self._node_executor
|
|
299
|
+
|
|
267
300
|
def get_connections(self) -> Connections:
|
|
268
301
|
"""Get the connections instance."""
|
|
269
302
|
return self._connections
|
|
@@ -1249,48 +1282,6 @@ class FlowManager:
|
|
|
1249
1282
|
start_end_library_metadata = start_end_library.get_metadata()
|
|
1250
1283
|
return start_end_library_metadata.library_version
|
|
1251
1284
|
|
|
1252
|
-
def _analyze_package_node_connections(
|
|
1253
|
-
self, package_node: BaseNode, node_name: str
|
|
1254
|
-
) -> ConnectionAnalysis | PackageNodeAsSerializedFlowResultFailure:
|
|
1255
|
-
"""Analyze package node connections and separate control from data connections."""
|
|
1256
|
-
# Get connection details using the efficient approach
|
|
1257
|
-
list_connections_request = ListConnectionsForNodeRequest(node_name=node_name)
|
|
1258
|
-
list_connections_result = GriptapeNodes.NodeManager().on_list_connections_for_node_request(
|
|
1259
|
-
list_connections_request
|
|
1260
|
-
)
|
|
1261
|
-
|
|
1262
|
-
if not isinstance(list_connections_result, ListConnectionsForNodeResultSuccess):
|
|
1263
|
-
details = f"Attempted to analyze connections for package node '{node_name}'. Failed because connection listing failed."
|
|
1264
|
-
return PackageNodeAsSerializedFlowResultFailure(result_details=details)
|
|
1265
|
-
|
|
1266
|
-
# Separate control connections from data connections based on package node's parameter types
|
|
1267
|
-
incoming_data_connections = []
|
|
1268
|
-
incoming_control_connections = []
|
|
1269
|
-
for incoming_conn in list_connections_result.incoming_connections:
|
|
1270
|
-
# Get the package node's parameter to check if it's a control type
|
|
1271
|
-
package_param = package_node.get_parameter_by_name(incoming_conn.target_parameter_name)
|
|
1272
|
-
if package_param and ParameterTypeBuiltin.CONTROL_TYPE.value in package_param.input_types:
|
|
1273
|
-
incoming_control_connections.append(incoming_conn)
|
|
1274
|
-
else:
|
|
1275
|
-
incoming_data_connections.append(incoming_conn)
|
|
1276
|
-
|
|
1277
|
-
outgoing_data_connections = []
|
|
1278
|
-
outgoing_control_connections = []
|
|
1279
|
-
for outgoing_conn in list_connections_result.outgoing_connections:
|
|
1280
|
-
# Get the package node's parameter to check if it's a control type
|
|
1281
|
-
package_param = package_node.get_parameter_by_name(outgoing_conn.source_parameter_name)
|
|
1282
|
-
if package_param and ParameterTypeBuiltin.CONTROL_TYPE.value == package_param.output_type:
|
|
1283
|
-
outgoing_control_connections.append(outgoing_conn)
|
|
1284
|
-
else:
|
|
1285
|
-
outgoing_data_connections.append(outgoing_conn)
|
|
1286
|
-
|
|
1287
|
-
return ConnectionAnalysis(
|
|
1288
|
-
incoming_data_connections=incoming_data_connections,
|
|
1289
|
-
incoming_control_connections=incoming_control_connections,
|
|
1290
|
-
outgoing_data_connections=outgoing_data_connections,
|
|
1291
|
-
outgoing_control_connections=outgoing_control_connections,
|
|
1292
|
-
)
|
|
1293
|
-
|
|
1294
1285
|
def _serialize_package_node(
|
|
1295
1286
|
self,
|
|
1296
1287
|
node_name: str,
|
|
@@ -1372,7 +1363,7 @@ class FlowManager:
|
|
|
1372
1363
|
|
|
1373
1364
|
# Create parameter modification commands and connection mappings for the start node based on incoming DATA connections
|
|
1374
1365
|
start_node_parameter_commands = []
|
|
1375
|
-
|
|
1366
|
+
start_to_package_connections = []
|
|
1376
1367
|
start_node_parameter_value_commands = []
|
|
1377
1368
|
input_shape_data: WorkflowShapeNodes = {}
|
|
1378
1369
|
|
|
@@ -1437,7 +1428,7 @@ class FlowManager:
|
|
|
1437
1428
|
target_node_uuid=package_node_uuid,
|
|
1438
1429
|
target_parameter_name=param_name,
|
|
1439
1430
|
)
|
|
1440
|
-
|
|
1431
|
+
start_to_package_connections.append(start_to_package_connection)
|
|
1441
1432
|
|
|
1442
1433
|
# Build complete SerializedNodeCommands for start node
|
|
1443
1434
|
start_node_dependencies = NodeDependencies()
|
|
@@ -1452,7 +1443,7 @@ class FlowManager:
|
|
|
1452
1443
|
|
|
1453
1444
|
return PackagingStartNodeResult(
|
|
1454
1445
|
start_node_commands=start_node_commands,
|
|
1455
|
-
|
|
1446
|
+
start_to_package_connections=start_to_package_connections,
|
|
1456
1447
|
input_shape_data=input_shape_data,
|
|
1457
1448
|
start_node_parameter_value_commands=start_node_parameter_value_commands,
|
|
1458
1449
|
)
|
|
@@ -1488,7 +1479,7 @@ class FlowManager:
|
|
|
1488
1479
|
# Process ALL package node parameters to create end node parameters and connections
|
|
1489
1480
|
# Note: PROPERTY-only parameters are guaranteed to have OUTPUT mode after serialization
|
|
1490
1481
|
end_node_parameter_commands = []
|
|
1491
|
-
|
|
1482
|
+
package_to_end_connections = []
|
|
1492
1483
|
output_shape_data: WorkflowShapeNodes = {}
|
|
1493
1484
|
|
|
1494
1485
|
for package_param in package_node.parameters:
|
|
@@ -1531,7 +1522,7 @@ class FlowManager:
|
|
|
1531
1522
|
target_node_uuid=end_node_uuid,
|
|
1532
1523
|
target_parameter_name=end_param_name,
|
|
1533
1524
|
)
|
|
1534
|
-
|
|
1525
|
+
package_to_end_connections.append(package_to_end_connection)
|
|
1535
1526
|
|
|
1536
1527
|
# Build complete SerializedNodeCommands for end node
|
|
1537
1528
|
end_node_dependencies = NodeDependencies()
|
|
@@ -1546,54 +1537,10 @@ class FlowManager:
|
|
|
1546
1537
|
|
|
1547
1538
|
return PackagingEndNodeResult(
|
|
1548
1539
|
end_node_commands=end_node_commands,
|
|
1549
|
-
|
|
1540
|
+
package_to_end_connections=package_to_end_connections,
|
|
1550
1541
|
output_shape_data=output_shape_data,
|
|
1551
1542
|
)
|
|
1552
1543
|
|
|
1553
|
-
def _create_start_node_control_connection(
|
|
1554
|
-
self,
|
|
1555
|
-
entry_control_parameter_name: str | None,
|
|
1556
|
-
start_node_uuid: SerializedNodeCommands.NodeUUID,
|
|
1557
|
-
package_node_uuid: SerializedNodeCommands.NodeUUID,
|
|
1558
|
-
package_node: BaseNode,
|
|
1559
|
-
) -> SerializedFlowCommands.IndirectConnectionSerialization | PackageNodeAsSerializedFlowResultFailure:
|
|
1560
|
-
"""Create control flow connection from start node to package node.
|
|
1561
|
-
|
|
1562
|
-
Connects the start node's first control output to the specified or first available package node control input.
|
|
1563
|
-
"""
|
|
1564
|
-
if entry_control_parameter_name is not None:
|
|
1565
|
-
# Case 1: Specific entry parameter name provided
|
|
1566
|
-
package_control_input_name = entry_control_parameter_name
|
|
1567
|
-
else:
|
|
1568
|
-
# Case 2: Find the first available control input parameter
|
|
1569
|
-
package_control_input_name = None
|
|
1570
|
-
for param in package_node.parameters:
|
|
1571
|
-
if ParameterTypeBuiltin.CONTROL_TYPE.value in param.input_types:
|
|
1572
|
-
package_control_input_name = param.name
|
|
1573
|
-
logger.warning(
|
|
1574
|
-
"No entry_control_parameter_name specified for packaging node '%s'. "
|
|
1575
|
-
"Using first available control input parameter: '%s'",
|
|
1576
|
-
package_node.name,
|
|
1577
|
-
package_control_input_name,
|
|
1578
|
-
)
|
|
1579
|
-
break
|
|
1580
|
-
|
|
1581
|
-
if package_control_input_name is None:
|
|
1582
|
-
details = f"Attempted to package node '{package_node.name}'. Failed because no control input parameters found on the node, so cannot create control flow connection."
|
|
1583
|
-
return PackageNodeAsSerializedFlowResultFailure(result_details=details)
|
|
1584
|
-
|
|
1585
|
-
# StartNode always has a control output parameter with name "exec_out"
|
|
1586
|
-
source_control_parameter_name = "exec_out"
|
|
1587
|
-
|
|
1588
|
-
# Create the connection
|
|
1589
|
-
control_connection = SerializedFlowCommands.IndirectConnectionSerialization(
|
|
1590
|
-
source_node_uuid=start_node_uuid,
|
|
1591
|
-
source_parameter_name=source_control_parameter_name,
|
|
1592
|
-
target_node_uuid=package_node_uuid,
|
|
1593
|
-
target_parameter_name=package_control_input_name,
|
|
1594
|
-
)
|
|
1595
|
-
return control_connection
|
|
1596
|
-
|
|
1597
1544
|
def _assemble_serialized_flow( # noqa: PLR0913
|
|
1598
1545
|
self,
|
|
1599
1546
|
serialized_package_result: SerializeNodeToCommandsResultSuccess,
|
|
@@ -1607,8 +1554,8 @@ class FlowManager:
|
|
|
1607
1554
|
"""Assemble the complete SerializedFlowCommands from all components."""
|
|
1608
1555
|
# Combine all connections: Start->Package + Package->End + Control Flow
|
|
1609
1556
|
all_connections = (
|
|
1610
|
-
start_node_result.
|
|
1611
|
-
+ end_node_result.
|
|
1557
|
+
start_node_result.start_to_package_connections
|
|
1558
|
+
+ end_node_result.package_to_end_connections
|
|
1612
1559
|
+ control_flow_connections
|
|
1613
1560
|
)
|
|
1614
1561
|
|
|
@@ -1650,6 +1597,11 @@ class FlowManager:
|
|
|
1650
1597
|
)
|
|
1651
1598
|
packaged_dependencies.libraries.add(start_end_library_dependency)
|
|
1652
1599
|
|
|
1600
|
+
# Aggregate node types used
|
|
1601
|
+
packaged_node_types_used = self._aggregate_node_types_used(
|
|
1602
|
+
serialized_node_commands=all_serialized_nodes, sub_flows_commands=[]
|
|
1603
|
+
)
|
|
1604
|
+
|
|
1653
1605
|
# Build the complete SerializedFlowCommands
|
|
1654
1606
|
return SerializedFlowCommands(
|
|
1655
1607
|
flow_initialization_command=create_packaged_flow_request,
|
|
@@ -1663,153 +1615,1198 @@ class FlowManager:
|
|
|
1663
1615
|
set_lock_commands_per_node=set_lock_commands_per_node,
|
|
1664
1616
|
sub_flows_commands=[],
|
|
1665
1617
|
node_dependencies=packaged_dependencies,
|
|
1618
|
+
node_types_used=packaged_node_types_used,
|
|
1666
1619
|
)
|
|
1667
1620
|
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
details = "Must provide flow name to start a flow."
|
|
1621
|
+
def on_package_nodes_as_serialized_flow_request( # noqa: C901, PLR0911
|
|
1622
|
+
self, request: PackageNodesAsSerializedFlowRequest
|
|
1623
|
+
) -> ResultPayload:
|
|
1624
|
+
"""Handle request to package multiple nodes as a serialized flow.
|
|
1673
1625
|
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
if self.check_for_existing_running_flow():
|
|
1683
|
-
details = "Cannot start flow. Flow is already running."
|
|
1684
|
-
return StartFlowResultFailure(validation_exceptions=[], result_details=details)
|
|
1685
|
-
# A node has been provided to either start or to run up to.
|
|
1686
|
-
if request.flow_node_name:
|
|
1687
|
-
flow_node_name = request.flow_node_name
|
|
1688
|
-
flow_node = GriptapeNodes.ObjectManager().attempt_get_object_by_name_as_type(flow_node_name, BaseNode)
|
|
1689
|
-
if not flow_node:
|
|
1690
|
-
details = f"Provided node with name {flow_node_name} does not exist"
|
|
1691
|
-
return StartFlowResultFailure(validation_exceptions=[], result_details=details)
|
|
1692
|
-
# lets get the first control node in the flow!
|
|
1693
|
-
start_node = self.get_start_node_from_node(flow, flow_node)
|
|
1694
|
-
# if the start is not the node provided, set a breakpoint at the stop (we're running up until there)
|
|
1695
|
-
if not start_node:
|
|
1696
|
-
details = f"Start node for node with name {flow_node_name} does not exist"
|
|
1697
|
-
return StartFlowResultFailure(validation_exceptions=[], result_details=details)
|
|
1698
|
-
if start_node != flow_node:
|
|
1699
|
-
flow_node.stop_flow = True
|
|
1700
|
-
else:
|
|
1701
|
-
# we wont hit this if we dont have a request id, our requests always have nodes
|
|
1702
|
-
# If there is a request, reinitialize the queue
|
|
1703
|
-
self.get_start_node_queue() # initialize the start flow queue!
|
|
1704
|
-
start_node = None
|
|
1705
|
-
# Run Validation before starting a flow
|
|
1706
|
-
result = await self.on_validate_flow_dependencies_request(
|
|
1707
|
-
ValidateFlowDependenciesRequest(flow_name=flow_name, flow_node_name=start_node.name if start_node else None)
|
|
1708
|
-
)
|
|
1709
|
-
try:
|
|
1710
|
-
if not result.succeeded():
|
|
1711
|
-
details = f"Couldn't start flow with name {flow_name}. Flow Validation Failed"
|
|
1712
|
-
return StartFlowResultFailure(validation_exceptions=[], result_details=details)
|
|
1713
|
-
result = cast("ValidateFlowDependenciesResultSuccess", result)
|
|
1626
|
+
Creates a self-contained flow with Start → [Selected Nodes] → End structure,
|
|
1627
|
+
where artificial start/end nodes interface with external connections only.
|
|
1628
|
+
"""
|
|
1629
|
+
# Step 1: Reject empty node list
|
|
1630
|
+
if not request.node_names:
|
|
1631
|
+
return PackageNodesAsSerializedFlowResultFailure(
|
|
1632
|
+
result_details="Attempted to package nodes as serialized flow. Failed because no nodes were specified in the node_names list."
|
|
1633
|
+
)
|
|
1714
1634
|
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
details = f"{details}\n\t{exception}"
|
|
1720
|
-
return StartFlowResultFailure(validation_exceptions=result.exceptions, result_details=details)
|
|
1721
|
-
except Exception as e:
|
|
1722
|
-
details = f"Couldn't start flow with name {flow_name}. Flow Validation Failed: {e}"
|
|
1723
|
-
return StartFlowResultFailure(validation_exceptions=[e], result_details=details)
|
|
1724
|
-
# By now, it has been validated with no exceptions.
|
|
1725
|
-
try:
|
|
1726
|
-
await self.start_flow(flow, start_node, debug_mode=request.debug_mode)
|
|
1727
|
-
except Exception as e:
|
|
1728
|
-
details = f"Failed to kick off flow with name {flow_name}. Exception occurred: {e} "
|
|
1729
|
-
return StartFlowResultFailure(validation_exceptions=[e], result_details=details)
|
|
1635
|
+
# Step 2: Validate library and get version
|
|
1636
|
+
library_version = self._validate_and_get_multi_node_library_info(request=request)
|
|
1637
|
+
if isinstance(library_version, PackageNodesAsSerializedFlowResultFailure):
|
|
1638
|
+
return library_version
|
|
1730
1639
|
|
|
1731
|
-
|
|
1640
|
+
# Step 3: Validate all nodes exist
|
|
1641
|
+
validation_result = self._validate_multi_node_request(request)
|
|
1642
|
+
if validation_result is not None:
|
|
1643
|
+
return validation_result
|
|
1732
1644
|
|
|
1733
|
-
|
|
1645
|
+
# Get the actual node objects for processing
|
|
1646
|
+
nodes_to_package = []
|
|
1647
|
+
for node_name in request.node_names:
|
|
1648
|
+
node = GriptapeNodes.NodeManager().get_node_by_name(node_name)
|
|
1649
|
+
nodes_to_package.append(node)
|
|
1734
1650
|
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1651
|
+
# Step 4: Initialize tracking variables and mappings (moved up so package node serialization can use them)
|
|
1652
|
+
unique_parameter_uuid_to_values = {}
|
|
1653
|
+
serialized_parameter_value_tracker = SerializedParameterValueTracker()
|
|
1654
|
+
node_name_to_uuid: dict[str, SerializedNodeCommands.NodeUUID] = {}
|
|
1655
|
+
packaged_nodes_set_parameter_value_commands: dict[
|
|
1656
|
+
SerializedNodeCommands.NodeUUID, list[SerializedNodeCommands.IndirectSetParameterValueCommand]
|
|
1657
|
+
] = {}
|
|
1658
|
+
packaged_nodes_internal_connections: list[SerializedFlowCommands.IndirectConnectionSerialization] = []
|
|
1659
|
+
|
|
1660
|
+
# Step 5: Serialize nodes with local execution environment to prevent recursive loops
|
|
1661
|
+
serialized_package_nodes = self._serialize_package_nodes_for_local_execution(
|
|
1662
|
+
nodes_to_package=nodes_to_package,
|
|
1663
|
+
unique_parameter_uuid_to_values=unique_parameter_uuid_to_values,
|
|
1664
|
+
serialized_parameter_value_tracker=serialized_parameter_value_tracker,
|
|
1665
|
+
node_name_to_uuid=node_name_to_uuid,
|
|
1666
|
+
set_parameter_value_commands=packaged_nodes_set_parameter_value_commands,
|
|
1667
|
+
internal_connections=packaged_nodes_internal_connections,
|
|
1668
|
+
)
|
|
1669
|
+
if isinstance(serialized_package_nodes, PackageNodesAsSerializedFlowResultFailure):
|
|
1670
|
+
return serialized_package_nodes
|
|
1671
|
+
|
|
1672
|
+
# Step 6: Inject OUTPUT mode changes for PROPERTY-only parameters to enable value reconciliation after the
|
|
1673
|
+
# packaged workflow is run.
|
|
1674
|
+
# Example: Nodes A -> B -> C. If B has property-only parameters, those values may change during execution,
|
|
1675
|
+
# so we need to send the value back after the packaged flow has run. We do this by making connections from
|
|
1676
|
+
# B's property-only parameters to the End Node, ensuring they're reflected when the packaged flow returns.
|
|
1677
|
+
# Since connections require an OUTPUT parameter mode, we inject that here.
|
|
1678
|
+
self._inject_output_mode_for_property_parameters(nodes_to_package, serialized_package_nodes)
|
|
1679
|
+
|
|
1680
|
+
# Step 7: Analyze external connections (connections from/to nodes outside our selection)
|
|
1681
|
+
node_connections_dict = self._analyze_multi_node_external_connections(package_nodes=nodes_to_package)
|
|
1682
|
+
if isinstance(node_connections_dict, PackageNodesAsSerializedFlowResultFailure):
|
|
1683
|
+
return node_connections_dict
|
|
1684
|
+
|
|
1685
|
+
# Step 8: Create start node with parameters for external incoming connections
|
|
1686
|
+
start_node_result = self._create_multi_node_start_node_with_connections(
|
|
1687
|
+
request=request,
|
|
1688
|
+
library_version=library_version,
|
|
1689
|
+
unique_parameter_uuid_to_values=unique_parameter_uuid_to_values,
|
|
1690
|
+
serialized_parameter_value_tracker=serialized_parameter_value_tracker,
|
|
1691
|
+
node_name_to_uuid=node_name_to_uuid,
|
|
1692
|
+
external_connections_dict=node_connections_dict,
|
|
1756
1693
|
)
|
|
1694
|
+
if isinstance(start_node_result, PackageNodesAsSerializedFlowResultFailure):
|
|
1695
|
+
return start_node_result
|
|
1757
1696
|
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1697
|
+
# Step 9: Create end node with parameters for external outgoing connections and parameter mappings
|
|
1698
|
+
end_node_result = self._create_multi_node_end_node_with_connections(
|
|
1699
|
+
request=request,
|
|
1700
|
+
package_nodes=nodes_to_package,
|
|
1701
|
+
node_name_to_uuid=node_name_to_uuid,
|
|
1702
|
+
library_version=library_version,
|
|
1703
|
+
node_connections_dict=node_connections_dict,
|
|
1704
|
+
)
|
|
1705
|
+
if isinstance(end_node_result, PackageNodesAsSerializedFlowResultFailure):
|
|
1706
|
+
return end_node_result
|
|
1762
1707
|
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
self.get_flow_by_name(flow_name)
|
|
1766
|
-
except KeyError as err:
|
|
1767
|
-
details = f"Could not cancel flow execution. Error: {err}"
|
|
1708
|
+
end_node_packaging_result = end_node_result.packaging_result
|
|
1709
|
+
parameter_name_mappings = end_node_result.parameter_name_mappings
|
|
1768
1710
|
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1711
|
+
# Step 10: Assemble final SerializedFlowCommands
|
|
1712
|
+
# Collect all connections from start/end nodes and internal package connections
|
|
1713
|
+
all_connections = self._collect_all_connections_for_multi_node_package(
|
|
1714
|
+
start_node_result=start_node_result,
|
|
1715
|
+
end_node_packaging_result=end_node_packaging_result,
|
|
1716
|
+
packaged_nodes_internal_connections=packaged_nodes_internal_connections,
|
|
1717
|
+
)
|
|
1774
1718
|
|
|
1775
|
-
|
|
1776
|
-
|
|
1719
|
+
# Build WorkflowShape from collected parameter shape data
|
|
1720
|
+
workflow_shape = GriptapeNodes.WorkflowManager().build_workflow_shape_from_parameter_info(
|
|
1721
|
+
input_node_params=start_node_result.input_shape_data,
|
|
1722
|
+
output_node_params=end_node_packaging_result.output_shape_data,
|
|
1723
|
+
)
|
|
1777
1724
|
|
|
1778
|
-
|
|
1725
|
+
# Create set parameter value commands dict
|
|
1726
|
+
set_parameter_value_commands = {
|
|
1727
|
+
start_node_result.start_node_commands.node_uuid: start_node_result.start_node_parameter_value_commands,
|
|
1728
|
+
**packaged_nodes_set_parameter_value_commands,
|
|
1729
|
+
}
|
|
1779
1730
|
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1731
|
+
# Collect all serialized nodes
|
|
1732
|
+
all_serialized_nodes = [
|
|
1733
|
+
start_node_result.start_node_commands,
|
|
1734
|
+
*serialized_package_nodes,
|
|
1735
|
+
end_node_packaging_result.end_node_commands,
|
|
1736
|
+
]
|
|
1784
1737
|
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
details = f"Could not advance to the next step of a running workflow. No flow with name {flow_name} exists. Error: {err}"
|
|
1738
|
+
# Create comprehensive dependencies from all nodes
|
|
1739
|
+
combined_dependencies = NodeDependencies()
|
|
1740
|
+
for serialized_node in all_serialized_nodes:
|
|
1741
|
+
combined_dependencies.aggregate_from(serialized_node.node_dependencies)
|
|
1790
1742
|
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
details = f"Could not advance to the next step of a running workflow. Exception: {e}"
|
|
1797
|
-
return SingleNodeStepResultFailure(validation_exceptions=[], result_details=details)
|
|
1743
|
+
# Extract lock commands from serialized nodes (they're embedded in SerializedNodeCommands)
|
|
1744
|
+
set_lock_commands_per_node = {}
|
|
1745
|
+
for serialized_node in all_serialized_nodes:
|
|
1746
|
+
if serialized_node.lock_node_command:
|
|
1747
|
+
set_lock_commands_per_node[serialized_node.node_uuid] = serialized_node.lock_node_command
|
|
1798
1748
|
|
|
1799
|
-
#
|
|
1800
|
-
|
|
1749
|
+
# Create a CreateFlowRequest for the packaged flow so that it can
|
|
1750
|
+
# run as a standalone workflow
|
|
1751
|
+
packaged_flow_metadata = {} # Keep it simple until we have reason to populate it
|
|
1801
1752
|
|
|
1802
|
-
|
|
1753
|
+
create_packaged_flow_request = CreateFlowRequest(
|
|
1754
|
+
parent_flow_name=None, # Standalone flow
|
|
1755
|
+
set_as_new_context=False, # Let deserializer decide
|
|
1756
|
+
metadata=packaged_flow_metadata,
|
|
1757
|
+
)
|
|
1803
1758
|
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1759
|
+
# Aggregate node types used
|
|
1760
|
+
combined_node_types_used = self._aggregate_node_types_used(
|
|
1761
|
+
serialized_node_commands=all_serialized_nodes, sub_flows_commands=[]
|
|
1762
|
+
)
|
|
1808
1763
|
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1764
|
+
# Build the complete serialized flow
|
|
1765
|
+
final_serialized_flow = SerializedFlowCommands(
|
|
1766
|
+
flow_initialization_command=create_packaged_flow_request,
|
|
1767
|
+
serialized_node_commands=all_serialized_nodes,
|
|
1768
|
+
serialized_connections=all_connections,
|
|
1769
|
+
unique_parameter_uuid_to_values=unique_parameter_uuid_to_values,
|
|
1770
|
+
set_parameter_value_commands=set_parameter_value_commands,
|
|
1771
|
+
set_lock_commands_per_node=set_lock_commands_per_node,
|
|
1772
|
+
sub_flows_commands=[],
|
|
1773
|
+
node_dependencies=combined_dependencies,
|
|
1774
|
+
node_types_used=combined_node_types_used,
|
|
1775
|
+
)
|
|
1776
|
+
|
|
1777
|
+
return PackageNodesAsSerializedFlowResultSuccess(
|
|
1778
|
+
serialized_flow_commands=final_serialized_flow,
|
|
1779
|
+
workflow_shape=workflow_shape,
|
|
1780
|
+
packaged_node_names=request.node_names,
|
|
1781
|
+
parameter_name_mappings=parameter_name_mappings,
|
|
1782
|
+
result_details=f"Successfully packaged {len(request.node_names)} nodes as serialized flow.",
|
|
1783
|
+
)
|
|
1784
|
+
|
|
1785
|
+
def _validate_and_get_multi_node_library_info(
|
|
1786
|
+
self, request: PackageNodesAsSerializedFlowRequest
|
|
1787
|
+
) -> str | PackageNodesAsSerializedFlowResultFailure:
|
|
1788
|
+
"""Validate start/end node types exist in library and return library version."""
|
|
1789
|
+
# Early validation - ensure both start and end node types exist in the specified library
|
|
1790
|
+
try:
|
|
1791
|
+
start_end_library = LibraryRegistry.get_library_for_node_type(
|
|
1792
|
+
node_type=request.start_node_type, specific_library_name=request.start_end_specific_library_name
|
|
1793
|
+
)
|
|
1794
|
+
except KeyError as err:
|
|
1795
|
+
details = f"Attempted to package nodes with start node type '{request.start_node_type}' from library '{request.start_end_specific_library_name}'. Failed because start node type was not found in library. Error: {err}."
|
|
1796
|
+
return PackageNodesAsSerializedFlowResultFailure(result_details=details)
|
|
1797
|
+
|
|
1798
|
+
try:
|
|
1799
|
+
LibraryRegistry.get_library_for_node_type(
|
|
1800
|
+
node_type=request.end_node_type, specific_library_name=request.start_end_specific_library_name
|
|
1801
|
+
)
|
|
1802
|
+
except KeyError as err:
|
|
1803
|
+
details = f"Attempted to package nodes with end node type '{request.end_node_type}' from library '{request.start_end_specific_library_name}'. Failed because end node type was not found in library. Error: {err}."
|
|
1804
|
+
return PackageNodesAsSerializedFlowResultFailure(result_details=details)
|
|
1805
|
+
|
|
1806
|
+
# Get the actual library version
|
|
1807
|
+
start_end_library_metadata = start_end_library.get_metadata()
|
|
1808
|
+
return start_end_library_metadata.library_version
|
|
1809
|
+
|
|
1810
|
+
def _validate_multi_node_request(
|
|
1811
|
+
self, request: PackageNodesAsSerializedFlowRequest
|
|
1812
|
+
) -> None | PackageNodesAsSerializedFlowResultFailure:
|
|
1813
|
+
"""Validate that all requested nodes exist and control flow configuration is valid."""
|
|
1814
|
+
# Validate all nodes exist
|
|
1815
|
+
missing_nodes = []
|
|
1816
|
+
for node_name in request.node_names:
|
|
1817
|
+
try:
|
|
1818
|
+
GriptapeNodes.NodeManager().get_node_by_name(node_name)
|
|
1819
|
+
except Exception:
|
|
1820
|
+
missing_nodes.append(node_name)
|
|
1821
|
+
|
|
1822
|
+
if missing_nodes:
|
|
1823
|
+
return PackageNodesAsSerializedFlowResultFailure(
|
|
1824
|
+
result_details=f"Attempted to package nodes as serialized flow. Failed because the following nodes were not found: {', '.join(missing_nodes)}."
|
|
1825
|
+
)
|
|
1826
|
+
|
|
1827
|
+
# Validate control flow configuration for non-empty node lists
|
|
1828
|
+
if request.node_names and request.entry_control_parameter_name and not request.entry_control_node_name:
|
|
1829
|
+
return PackageNodesAsSerializedFlowResultFailure(
|
|
1830
|
+
result_details="Attempted to package nodes as serialized flow. Failed because entry_control_parameter_name was specified but entry_control_node_name was not. For multi-node packaging with a non-empty node list, both must be specified to avoid ambiguity about which node should receive the control connection."
|
|
1831
|
+
)
|
|
1832
|
+
|
|
1833
|
+
# Validate entry_control_node_name exists and is in our package list
|
|
1834
|
+
if request.entry_control_node_name and request.entry_control_node_name not in request.node_names:
|
|
1835
|
+
return PackageNodesAsSerializedFlowResultFailure(
|
|
1836
|
+
result_details=f"Attempted to package nodes as serialized flow. Failed because entry_control_node_name '{request.entry_control_node_name}' is not in the list of nodes to package: {request.node_names}."
|
|
1837
|
+
)
|
|
1838
|
+
|
|
1839
|
+
return None
|
|
1840
|
+
|
|
1841
|
+
def _serialize_package_nodes_for_local_execution( # noqa: C901, PLR0913
|
|
1842
|
+
self,
|
|
1843
|
+
nodes_to_package: list[BaseNode],
|
|
1844
|
+
unique_parameter_uuid_to_values: dict[SerializedNodeCommands.UniqueParameterValueUUID, Any],
|
|
1845
|
+
serialized_parameter_value_tracker: SerializedParameterValueTracker,
|
|
1846
|
+
node_name_to_uuid: dict[str, SerializedNodeCommands.NodeUUID], # OUTPUT: will be populated
|
|
1847
|
+
set_parameter_value_commands: dict[ # OUTPUT: will be populated
|
|
1848
|
+
SerializedNodeCommands.NodeUUID, list[SerializedNodeCommands.IndirectSetParameterValueCommand]
|
|
1849
|
+
],
|
|
1850
|
+
internal_connections: list[SerializedFlowCommands.IndirectConnectionSerialization], # OUTPUT: will be populated
|
|
1851
|
+
) -> list[SerializedNodeCommands] | PackageNodesAsSerializedFlowResultFailure:
|
|
1852
|
+
"""Serialize package nodes while temporarily setting execution environment to local to prevent recursive loops.
|
|
1853
|
+
|
|
1854
|
+
This method temporarily overrides each node's execution_environment parameter to LOCAL_EXECUTION
|
|
1855
|
+
during serialization, then restores the original values. This prevents recursive packaging loops
|
|
1856
|
+
that could occur if nodes were configured for remote execution environments.
|
|
1857
|
+
|
|
1858
|
+
Args:
|
|
1859
|
+
nodes_to_package: List of nodes to serialize
|
|
1860
|
+
unique_parameter_uuid_to_values: Shared dictionary for deduplicating parameter values across all nodes
|
|
1861
|
+
serialized_parameter_value_tracker: Tracker for serialized parameter values
|
|
1862
|
+
node_name_to_uuid: OUTPUT - Dictionary mapping node names to UUIDs (populated by this method)
|
|
1863
|
+
set_parameter_value_commands: OUTPUT - Dict mapping node UUIDs to parameter value commands (populated by this method)
|
|
1864
|
+
internal_connections: OUTPUT - List of connections between package nodes (populated by this method)
|
|
1865
|
+
|
|
1866
|
+
Returns:
|
|
1867
|
+
List of SerializedNodeCommands on success, or PackageNodesAsSerializedFlowResultFailure on failure
|
|
1868
|
+
"""
|
|
1869
|
+
# Intercept execution_environment for all nodes before serialization
|
|
1870
|
+
original_execution_environments = {}
|
|
1871
|
+
for node in nodes_to_package:
|
|
1872
|
+
original_value = node.get_parameter_value("execution_environment")
|
|
1873
|
+
original_execution_environments[node.name] = original_value
|
|
1874
|
+
node.set_parameter_value("execution_environment", LOCAL_EXECUTION)
|
|
1875
|
+
|
|
1876
|
+
try:
|
|
1877
|
+
# Serialize each node using shared unique_parameter_uuid_to_values dictionary for deduplication
|
|
1878
|
+
serialized_node_commands = []
|
|
1879
|
+
|
|
1880
|
+
for node in nodes_to_package:
|
|
1881
|
+
# Serialize this node using shared dictionaries for value deduplication
|
|
1882
|
+
serialize_request = SerializeNodeToCommandsRequest(
|
|
1883
|
+
node_name=node.name,
|
|
1884
|
+
unique_parameter_uuid_to_values=unique_parameter_uuid_to_values,
|
|
1885
|
+
serialized_parameter_value_tracker=serialized_parameter_value_tracker,
|
|
1886
|
+
)
|
|
1887
|
+
serialize_result = GriptapeNodes.NodeManager().on_serialize_node_to_commands(serialize_request)
|
|
1888
|
+
|
|
1889
|
+
if not isinstance(serialize_result, SerializeNodeToCommandsResultSuccess):
|
|
1890
|
+
return PackageNodesAsSerializedFlowResultFailure(
|
|
1891
|
+
result_details=f"Attempted to package nodes as serialized flow. Failed to serialize node '{node.name}': {serialize_result.result_details}"
|
|
1892
|
+
)
|
|
1893
|
+
|
|
1894
|
+
# Collect serialized node
|
|
1895
|
+
serialized_node_commands.append(serialize_result.serialized_node_commands)
|
|
1896
|
+
|
|
1897
|
+
# Populate the shared node_name_to_uuid mapping
|
|
1898
|
+
if serialize_result.serialized_node_commands.create_node_command.node_name is not None:
|
|
1899
|
+
node_name_to_uuid[serialize_result.serialized_node_commands.create_node_command.node_name] = (
|
|
1900
|
+
serialize_result.serialized_node_commands.node_uuid
|
|
1901
|
+
)
|
|
1902
|
+
|
|
1903
|
+
# Collect set parameter value commands (references to unique_parameter_uuid_to_values)
|
|
1904
|
+
if serialize_result.set_parameter_value_commands:
|
|
1905
|
+
set_parameter_value_commands[serialize_result.serialized_node_commands.node_uuid] = (
|
|
1906
|
+
serialize_result.set_parameter_value_commands
|
|
1907
|
+
)
|
|
1908
|
+
|
|
1909
|
+
# Build internal connections between package nodes
|
|
1910
|
+
package_node_names_set = {n.name for n in nodes_to_package}
|
|
1911
|
+
for node in nodes_to_package:
|
|
1912
|
+
# Get connections FROM this node TO other nodes in the package
|
|
1913
|
+
list_connections_request = ListConnectionsForNodeRequest(node_name=node.name)
|
|
1914
|
+
list_connections_result = GriptapeNodes.NodeManager().on_list_connections_for_node_request(
|
|
1915
|
+
list_connections_request
|
|
1916
|
+
)
|
|
1917
|
+
|
|
1918
|
+
if not isinstance(list_connections_result, ListConnectionsForNodeResultSuccess):
|
|
1919
|
+
return PackageNodesAsSerializedFlowResultFailure(
|
|
1920
|
+
result_details=f"Attempted to package nodes as serialized flow. Failed to list connections for node '{node.name}': {list_connections_result.result_details}"
|
|
1921
|
+
)
|
|
1922
|
+
|
|
1923
|
+
# Only include connections where BOTH source and target are in the package
|
|
1924
|
+
for outgoing_conn in list_connections_result.outgoing_connections:
|
|
1925
|
+
if outgoing_conn.target_node_name in package_node_names_set:
|
|
1926
|
+
source_uuid = node_name_to_uuid[node.name]
|
|
1927
|
+
target_uuid = node_name_to_uuid[outgoing_conn.target_node_name]
|
|
1928
|
+
internal_connections.append(
|
|
1929
|
+
SerializedFlowCommands.IndirectConnectionSerialization(
|
|
1930
|
+
source_node_uuid=source_uuid,
|
|
1931
|
+
source_parameter_name=outgoing_conn.source_parameter_name,
|
|
1932
|
+
target_node_uuid=target_uuid,
|
|
1933
|
+
target_parameter_name=outgoing_conn.target_parameter_name,
|
|
1934
|
+
)
|
|
1935
|
+
)
|
|
1936
|
+
finally:
|
|
1937
|
+
# Always restore original execution_environment values, even on failure
|
|
1938
|
+
for node_name, original_value in original_execution_environments.items():
|
|
1939
|
+
restore_node = GriptapeNodes.NodeManager().get_node_by_name(node_name)
|
|
1940
|
+
restore_node.set_parameter_value("execution_environment", original_value)
|
|
1941
|
+
|
|
1942
|
+
return serialized_node_commands
|
|
1943
|
+
|
|
1944
|
+
def _inject_output_mode_for_property_parameters(
|
|
1945
|
+
self, nodes_to_package: list[BaseNode], serialized_package_nodes: list[SerializedNodeCommands]
|
|
1946
|
+
) -> None:
|
|
1947
|
+
"""Inject OUTPUT mode for PROPERTY-only parameters to enable value reconciliation.
|
|
1948
|
+
|
|
1949
|
+
This method adds ALTER parameter commands to serialized nodes for parameters that have
|
|
1950
|
+
PROPERTY mode but not OUTPUT mode. This allows the orchestrator/caller to reconcile
|
|
1951
|
+
the packaged node's values after execution.
|
|
1952
|
+
|
|
1953
|
+
Args:
|
|
1954
|
+
nodes_to_package: List of nodes being packaged
|
|
1955
|
+
serialized_package_nodes: The serialized node commands to modify
|
|
1956
|
+
"""
|
|
1957
|
+
# Apply ALTER parameter commands for PROPERTY-only parameters to enable OUTPUT mode
|
|
1958
|
+
# We need these to emit their values back so that the orchestrator/caller
|
|
1959
|
+
# can reconcile the packaged node's values after it is executed.
|
|
1960
|
+
for package_node in nodes_to_package:
|
|
1961
|
+
# Find the corresponding serialized node
|
|
1962
|
+
serialized_node = None
|
|
1963
|
+
for serialized_node_command in serialized_package_nodes:
|
|
1964
|
+
if serialized_node_command.create_node_command.node_name == package_node.name:
|
|
1965
|
+
serialized_node = serialized_node_command
|
|
1966
|
+
break
|
|
1967
|
+
|
|
1968
|
+
if serialized_node is None:
|
|
1969
|
+
error_msg = f"Data integrity error: Could not find serialized node for package node '{package_node.name}'. This indicates a logic error in the serialization process."
|
|
1970
|
+
logger.error(error_msg)
|
|
1971
|
+
raise RuntimeError(error_msg)
|
|
1972
|
+
|
|
1973
|
+
package_alter_parameter_commands = []
|
|
1974
|
+
for package_param in package_node.parameters:
|
|
1975
|
+
has_output_mode = ParameterMode.OUTPUT in package_param.allowed_modes
|
|
1976
|
+
has_property_mode = ParameterMode.PROPERTY in package_param.allowed_modes
|
|
1977
|
+
# If has PROPERTY but not OUTPUT, add ALTER command to enable OUTPUT
|
|
1978
|
+
if has_property_mode and not has_output_mode:
|
|
1979
|
+
alter_param_request = AlterParameterDetailsRequest(
|
|
1980
|
+
parameter_name=package_param.name,
|
|
1981
|
+
node_name=package_node.name,
|
|
1982
|
+
mode_allowed_output=True,
|
|
1983
|
+
)
|
|
1984
|
+
package_alter_parameter_commands.append(alter_param_request)
|
|
1985
|
+
|
|
1986
|
+
# If we have alter parameter commands, append them to the existing element_modification_commands
|
|
1987
|
+
if package_alter_parameter_commands:
|
|
1988
|
+
serialized_node.element_modification_commands.extend(package_alter_parameter_commands)
|
|
1989
|
+
|
|
1990
|
+
def _analyze_multi_node_external_connections(
|
|
1991
|
+
self, package_nodes: list[BaseNode]
|
|
1992
|
+
) -> dict[str, ConnectionAnalysis] | PackageNodesAsSerializedFlowResultFailure:
|
|
1993
|
+
"""Analyze external connections for each package node using filtered single-node analysis.
|
|
1994
|
+
|
|
1995
|
+
This method reuses the existing single-node connection analysis method but applies filtering
|
|
1996
|
+
to only capture "external" connections - those that cross the package boundary.
|
|
1997
|
+
|
|
1998
|
+
External connections are:
|
|
1999
|
+
- Incoming connections where the source node is NOT in the package
|
|
2000
|
+
- Outgoing connections where the target node is NOT in the package
|
|
2001
|
+
|
|
2002
|
+
Internal connections (between nodes within the package) are filtered out by passing
|
|
2003
|
+
the set of package node names to the single-node analysis method.
|
|
2004
|
+
|
|
2005
|
+
Args:
|
|
2006
|
+
package_nodes: List of nodes being packaged together
|
|
2007
|
+
|
|
2008
|
+
Returns:
|
|
2009
|
+
Dictionary mapping node_name -> ConnectionAnalysis, where each ConnectionAnalysis
|
|
2010
|
+
contains only the external connections for that node, or failure result on error
|
|
2011
|
+
"""
|
|
2012
|
+
package_node_names_set = {node.name for node in package_nodes}
|
|
2013
|
+
node_connections = {}
|
|
2014
|
+
|
|
2015
|
+
for package_node in package_nodes:
|
|
2016
|
+
# Perform a single node analysis, filtering out internal connections
|
|
2017
|
+
connection_analysis = self._analyze_package_node_connections(
|
|
2018
|
+
package_node=package_node,
|
|
2019
|
+
node_name=package_node.name,
|
|
2020
|
+
package_node_names=package_node_names_set,
|
|
2021
|
+
)
|
|
2022
|
+
if isinstance(connection_analysis, PackageNodeAsSerializedFlowResultFailure):
|
|
2023
|
+
return PackageNodesAsSerializedFlowResultFailure(result_details=connection_analysis.result_details)
|
|
2024
|
+
|
|
2025
|
+
node_connections[package_node.name] = connection_analysis
|
|
2026
|
+
|
|
2027
|
+
return node_connections
|
|
2028
|
+
|
|
2029
|
+
def _analyze_package_node_connections(
|
|
2030
|
+
self, package_node: BaseNode, node_name: str, package_node_names: set[str] | None = None
|
|
2031
|
+
) -> ConnectionAnalysis | PackageNodeAsSerializedFlowResultFailure:
|
|
2032
|
+
"""Analyze package node connections and separate control from data connections."""
|
|
2033
|
+
# Get connection details using the efficient approach
|
|
2034
|
+
list_connections_request = ListConnectionsForNodeRequest(node_name=node_name)
|
|
2035
|
+
list_connections_result = GriptapeNodes.NodeManager().on_list_connections_for_node_request(
|
|
2036
|
+
list_connections_request
|
|
2037
|
+
)
|
|
2038
|
+
|
|
2039
|
+
if not isinstance(list_connections_result, ListConnectionsForNodeResultSuccess):
|
|
2040
|
+
details = f"Attempted to analyze connections for package node '{node_name}'. Failed because connection listing failed."
|
|
2041
|
+
return PackageNodeAsSerializedFlowResultFailure(result_details=details)
|
|
2042
|
+
|
|
2043
|
+
# Separate control connections from data connections based on package node's parameter types
|
|
2044
|
+
incoming_data_connections = []
|
|
2045
|
+
incoming_control_connections = []
|
|
2046
|
+
for incoming_conn in list_connections_result.incoming_connections:
|
|
2047
|
+
# Filter out internal connections if package_node_names is provided
|
|
2048
|
+
if package_node_names is not None and incoming_conn.source_node_name in package_node_names:
|
|
2049
|
+
continue
|
|
2050
|
+
|
|
2051
|
+
# Get the package node's parameter to check if it's a control type
|
|
2052
|
+
package_param = package_node.get_parameter_by_name(incoming_conn.target_parameter_name)
|
|
2053
|
+
if package_param and ParameterTypeBuiltin.CONTROL_TYPE.value in package_param.input_types:
|
|
2054
|
+
incoming_control_connections.append(incoming_conn)
|
|
2055
|
+
else:
|
|
2056
|
+
incoming_data_connections.append(incoming_conn)
|
|
2057
|
+
|
|
2058
|
+
outgoing_data_connections = []
|
|
2059
|
+
outgoing_control_connections = []
|
|
2060
|
+
for outgoing_conn in list_connections_result.outgoing_connections:
|
|
2061
|
+
# Filter out internal connections if package_node_names is provided
|
|
2062
|
+
if package_node_names is not None and outgoing_conn.target_node_name in package_node_names:
|
|
2063
|
+
continue
|
|
2064
|
+
|
|
2065
|
+
# Get the package node's parameter to check if it's a control type
|
|
2066
|
+
package_param = package_node.get_parameter_by_name(outgoing_conn.source_parameter_name)
|
|
2067
|
+
if package_param and ParameterTypeBuiltin.CONTROL_TYPE.value == package_param.output_type:
|
|
2068
|
+
outgoing_control_connections.append(outgoing_conn)
|
|
2069
|
+
else:
|
|
2070
|
+
outgoing_data_connections.append(outgoing_conn)
|
|
2071
|
+
|
|
2072
|
+
return ConnectionAnalysis(
|
|
2073
|
+
incoming_data_connections=incoming_data_connections,
|
|
2074
|
+
incoming_control_connections=incoming_control_connections,
|
|
2075
|
+
outgoing_data_connections=outgoing_data_connections,
|
|
2076
|
+
outgoing_control_connections=outgoing_control_connections,
|
|
2077
|
+
)
|
|
2078
|
+
|
|
2079
|
+
def _create_multi_node_end_node_with_connections(
|
|
2080
|
+
self,
|
|
2081
|
+
request: PackageNodesAsSerializedFlowRequest,
|
|
2082
|
+
package_nodes: list[BaseNode],
|
|
2083
|
+
node_name_to_uuid: dict[str, SerializedNodeCommands.NodeUUID],
|
|
2084
|
+
library_version: str,
|
|
2085
|
+
node_connections_dict: dict[str, ConnectionAnalysis],
|
|
2086
|
+
) -> MultiNodeEndNodeResult | PackageNodesAsSerializedFlowResultFailure:
|
|
2087
|
+
"""Create end node commands and connections for ALL package parameters that meet criteria (copied from single-node)."""
|
|
2088
|
+
# Generate UUID and name for end node
|
|
2089
|
+
end_node_uuid = SerializedNodeCommands.NodeUUID(str(uuid4()))
|
|
2090
|
+
end_node_name = "End_Multi_Package"
|
|
2091
|
+
|
|
2092
|
+
# Build end node CreateNodeRequest
|
|
2093
|
+
end_create_node_command = CreateNodeRequest(
|
|
2094
|
+
node_type=request.end_node_type,
|
|
2095
|
+
specific_library_name=request.start_end_specific_library_name,
|
|
2096
|
+
node_name=end_node_name,
|
|
2097
|
+
metadata={},
|
|
2098
|
+
initial_setup=True,
|
|
2099
|
+
create_error_proxy_on_failure=False,
|
|
2100
|
+
)
|
|
2101
|
+
|
|
2102
|
+
# Create library details
|
|
2103
|
+
end_node_library_details = LibraryNameAndVersion(
|
|
2104
|
+
library_name=request.start_end_specific_library_name,
|
|
2105
|
+
library_version=library_version,
|
|
2106
|
+
)
|
|
2107
|
+
|
|
2108
|
+
# Initialize collections for building the end node
|
|
2109
|
+
end_node_parameter_commands = []
|
|
2110
|
+
package_to_end_connections = []
|
|
2111
|
+
output_shape_data: WorkflowShapeNodes = {}
|
|
2112
|
+
# Parameter name mappings (rosetta stone): maps mangled end node parameter names back to original (node_name, parameter_name)
|
|
2113
|
+
# This is essential for callers to understand which end node outputs correspond to which original node parameters
|
|
2114
|
+
parameter_name_mappings: dict[SanitizedParameterName, OriginalNodeParameter] = {}
|
|
2115
|
+
|
|
2116
|
+
# Handle external control connections first
|
|
2117
|
+
self._create_end_node_control_connections(
|
|
2118
|
+
request=request,
|
|
2119
|
+
package_nodes=package_nodes,
|
|
2120
|
+
node_connections_dict=node_connections_dict,
|
|
2121
|
+
node_name_to_uuid=node_name_to_uuid,
|
|
2122
|
+
end_node_uuid=end_node_uuid,
|
|
2123
|
+
end_node_name=end_node_name,
|
|
2124
|
+
end_node_parameter_commands=end_node_parameter_commands,
|
|
2125
|
+
package_to_end_connections=package_to_end_connections,
|
|
2126
|
+
parameter_name_mappings=parameter_name_mappings,
|
|
2127
|
+
output_shape_data=output_shape_data,
|
|
2128
|
+
)
|
|
2129
|
+
|
|
2130
|
+
# Process ALL parameters with OUTPUT or PROPERTY modes for comprehensive coverage
|
|
2131
|
+
self._create_end_node_data_parameters_and_connections(
|
|
2132
|
+
request=request,
|
|
2133
|
+
package_nodes=package_nodes,
|
|
2134
|
+
node_name_to_uuid=node_name_to_uuid,
|
|
2135
|
+
end_node_uuid=end_node_uuid,
|
|
2136
|
+
end_node_name=end_node_name,
|
|
2137
|
+
end_node_parameter_commands=end_node_parameter_commands,
|
|
2138
|
+
package_to_end_connections=package_to_end_connections,
|
|
2139
|
+
parameter_name_mappings=parameter_name_mappings,
|
|
2140
|
+
output_shape_data=output_shape_data,
|
|
2141
|
+
)
|
|
2142
|
+
|
|
2143
|
+
# Build complete SerializedNodeCommands for end node
|
|
2144
|
+
end_node_dependencies = NodeDependencies()
|
|
2145
|
+
end_node_dependencies.libraries.add(end_node_library_details)
|
|
2146
|
+
|
|
2147
|
+
end_node_commands = SerializedNodeCommands(
|
|
2148
|
+
create_node_command=end_create_node_command,
|
|
2149
|
+
element_modification_commands=end_node_parameter_commands,
|
|
2150
|
+
node_dependencies=end_node_dependencies,
|
|
2151
|
+
node_uuid=end_node_uuid,
|
|
2152
|
+
)
|
|
2153
|
+
|
|
2154
|
+
end_node_result = PackagingEndNodeResult(
|
|
2155
|
+
end_node_commands=end_node_commands,
|
|
2156
|
+
package_to_end_connections=package_to_end_connections,
|
|
2157
|
+
output_shape_data=output_shape_data,
|
|
2158
|
+
)
|
|
2159
|
+
|
|
2160
|
+
return MultiNodeEndNodeResult(
|
|
2161
|
+
packaging_result=end_node_result,
|
|
2162
|
+
parameter_name_mappings=parameter_name_mappings,
|
|
2163
|
+
alter_parameter_commands=[],
|
|
2164
|
+
)
|
|
2165
|
+
|
|
2166
|
+
def _create_end_node_control_connections( # noqa: PLR0913
|
|
2167
|
+
self,
|
|
2168
|
+
request: PackageNodesAsSerializedFlowRequest,
|
|
2169
|
+
package_nodes: list[BaseNode],
|
|
2170
|
+
node_connections_dict: dict[str, ConnectionAnalysis], # Contains only EXTERNAL connections
|
|
2171
|
+
node_name_to_uuid: dict[
|
|
2172
|
+
str, SerializedNodeCommands.NodeUUID
|
|
2173
|
+
], # Map node names to UUIDs for connection creation
|
|
2174
|
+
end_node_uuid: SerializedNodeCommands.NodeUUID,
|
|
2175
|
+
end_node_name: str,
|
|
2176
|
+
end_node_parameter_commands: list[
|
|
2177
|
+
AddParameterToNodeRequest
|
|
2178
|
+
], # OUTPUT: Will populate with parameters to add to end node
|
|
2179
|
+
package_to_end_connections: list[
|
|
2180
|
+
SerializedFlowCommands.IndirectConnectionSerialization
|
|
2181
|
+
], # OUTPUT: Will populate with connections to add
|
|
2182
|
+
parameter_name_mappings: dict[
|
|
2183
|
+
SanitizedParameterName, OriginalNodeParameter
|
|
2184
|
+
], # OUTPUT: Will populate rosetta stone for parameter names so customer knows how to map mangled names back to original nodes.
|
|
2185
|
+
output_shape_data: WorkflowShapeNodes, # OUTPUT: Will populate with workflow shape data
|
|
2186
|
+
) -> None:
|
|
2187
|
+
"""Create control connections and parameters on end node for EXTERNAL control flow connections."""
|
|
2188
|
+
for package_node in package_nodes:
|
|
2189
|
+
node_connection_analysis = node_connections_dict.get(package_node.name)
|
|
2190
|
+
if node_connection_analysis is None:
|
|
2191
|
+
# This node has no external connections (neither incoming nor outgoing), skip it
|
|
2192
|
+
continue
|
|
2193
|
+
|
|
2194
|
+
# Handle external outgoing control connections
|
|
2195
|
+
for control_conn in node_connection_analysis.outgoing_control_connections:
|
|
2196
|
+
# Get the source parameter for validation and processing
|
|
2197
|
+
source_param = package_node.get_parameter_by_name(control_conn.source_parameter_name)
|
|
2198
|
+
if source_param is None:
|
|
2199
|
+
msg = f"External control connection references parameter '{control_conn.source_parameter_name}' on node '{package_node.name}' which does not exist. This indicates a data consistency issue."
|
|
2200
|
+
raise ValueError(msg)
|
|
2201
|
+
|
|
2202
|
+
# Use comprehensive helper to process this control parameter
|
|
2203
|
+
self._process_parameter_for_end_node(
|
|
2204
|
+
request=request,
|
|
2205
|
+
parameter=source_param,
|
|
2206
|
+
node_name=package_node.name,
|
|
2207
|
+
node_uuid=node_name_to_uuid[package_node.name],
|
|
2208
|
+
end_node_name=end_node_name,
|
|
2209
|
+
end_node_uuid=end_node_uuid,
|
|
2210
|
+
tooltip=f"Control output {control_conn.source_parameter_name} from packaged node {package_node.name}",
|
|
2211
|
+
end_node_parameter_commands=end_node_parameter_commands,
|
|
2212
|
+
package_to_end_connections=package_to_end_connections,
|
|
2213
|
+
parameter_name_mappings=parameter_name_mappings,
|
|
2214
|
+
output_shape_data=output_shape_data,
|
|
2215
|
+
)
|
|
2216
|
+
|
|
2217
|
+
def _create_end_node_data_parameters_and_connections( # noqa: PLR0913
|
|
2218
|
+
self,
|
|
2219
|
+
request: PackageNodesAsSerializedFlowRequest,
|
|
2220
|
+
package_nodes: list[BaseNode],
|
|
2221
|
+
node_name_to_uuid: dict[str, SerializedNodeCommands.NodeUUID],
|
|
2222
|
+
end_node_uuid: SerializedNodeCommands.NodeUUID,
|
|
2223
|
+
end_node_name: str,
|
|
2224
|
+
end_node_parameter_commands: list[
|
|
2225
|
+
AddParameterToNodeRequest
|
|
2226
|
+
], # OUTPUT: Will populate with parameters to add to end node
|
|
2227
|
+
package_to_end_connections: list[
|
|
2228
|
+
SerializedFlowCommands.IndirectConnectionSerialization
|
|
2229
|
+
], # OUTPUT: Will populate with connections to add
|
|
2230
|
+
parameter_name_mappings: dict[
|
|
2231
|
+
SanitizedParameterName, OriginalNodeParameter
|
|
2232
|
+
], # OUTPUT: Will populate rosetta stone for parameter names
|
|
2233
|
+
output_shape_data: WorkflowShapeNodes, # OUTPUT: Will populate with workflow shape data
|
|
2234
|
+
) -> None:
|
|
2235
|
+
"""Create data parameters and connections on end node for all OUTPUT/PROPERTY parameters from packaged nodes."""
|
|
2236
|
+
for package_node in package_nodes:
|
|
2237
|
+
package_node_uuid = node_name_to_uuid[package_node.name]
|
|
2238
|
+
|
|
2239
|
+
for package_param in package_node.parameters:
|
|
2240
|
+
# Only process parameters with OUTPUT or PROPERTY mode
|
|
2241
|
+
has_output_mode = ParameterMode.OUTPUT in package_param.allowed_modes
|
|
2242
|
+
has_property_mode = ParameterMode.PROPERTY in package_param.allowed_modes
|
|
2243
|
+
|
|
2244
|
+
if not has_output_mode and not has_property_mode:
|
|
2245
|
+
continue
|
|
2246
|
+
|
|
2247
|
+
# Skip control parameters - those are handled by the control connections helper
|
|
2248
|
+
if package_param.output_type == ParameterTypeBuiltin.CONTROL_TYPE.value:
|
|
2249
|
+
continue
|
|
2250
|
+
|
|
2251
|
+
# Use comprehensive helper to process this data parameter
|
|
2252
|
+
self._process_parameter_for_end_node(
|
|
2253
|
+
request=request,
|
|
2254
|
+
parameter=package_param,
|
|
2255
|
+
node_name=package_node.name,
|
|
2256
|
+
node_uuid=package_node_uuid,
|
|
2257
|
+
end_node_name=end_node_name,
|
|
2258
|
+
end_node_uuid=end_node_uuid,
|
|
2259
|
+
tooltip=f"Output parameter {package_param.name} from packaged node {package_node.name}",
|
|
2260
|
+
end_node_parameter_commands=end_node_parameter_commands,
|
|
2261
|
+
package_to_end_connections=package_to_end_connections,
|
|
2262
|
+
parameter_name_mappings=parameter_name_mappings,
|
|
2263
|
+
output_shape_data=output_shape_data,
|
|
2264
|
+
)
|
|
2265
|
+
|
|
2266
|
+
def _process_parameter_for_end_node( # noqa: PLR0913
|
|
2267
|
+
self,
|
|
2268
|
+
request: PackageNodesAsSerializedFlowRequest,
|
|
2269
|
+
parameter: Parameter,
|
|
2270
|
+
node_name: str,
|
|
2271
|
+
node_uuid: SerializedNodeCommands.NodeUUID,
|
|
2272
|
+
end_node_name: str,
|
|
2273
|
+
end_node_uuid: SerializedNodeCommands.NodeUUID,
|
|
2274
|
+
tooltip: str,
|
|
2275
|
+
end_node_parameter_commands: list[
|
|
2276
|
+
AddParameterToNodeRequest
|
|
2277
|
+
], # OUTPUT: Will populate with parameters to add to end node
|
|
2278
|
+
package_to_end_connections: list[
|
|
2279
|
+
SerializedFlowCommands.IndirectConnectionSerialization
|
|
2280
|
+
], # OUTPUT: Will populate with connections to add
|
|
2281
|
+
parameter_name_mappings: dict[
|
|
2282
|
+
SanitizedParameterName, OriginalNodeParameter
|
|
2283
|
+
], # OUTPUT: Will populate rosetta stone for parameter names
|
|
2284
|
+
output_shape_data: WorkflowShapeNodes, # OUTPUT: Will populate with workflow shape data
|
|
2285
|
+
) -> None:
|
|
2286
|
+
"""Process a single parameter for inclusion in the end node, handling all aspects of parameter creation and connection."""
|
|
2287
|
+
# Create sanitized parameter name with collision avoidance
|
|
2288
|
+
sanitized_param_name = self._generate_sanitized_parameter_name(
|
|
2289
|
+
prefix=request.output_parameter_prefix,
|
|
2290
|
+
node_name=node_name,
|
|
2291
|
+
parameter_name=parameter.name,
|
|
2292
|
+
)
|
|
2293
|
+
|
|
2294
|
+
# Build parameter name mapping for rosetta stone
|
|
2295
|
+
parameter_name_mappings[sanitized_param_name] = OriginalNodeParameter(
|
|
2296
|
+
node_name=node_name,
|
|
2297
|
+
parameter_name=parameter.name,
|
|
2298
|
+
)
|
|
2299
|
+
|
|
2300
|
+
# Extract parameter shape info for workflow shape (outputs to external consumers)
|
|
2301
|
+
param_shape_info = GriptapeNodes.WorkflowManager().extract_parameter_shape_info(
|
|
2302
|
+
parameter, include_control_params=True
|
|
2303
|
+
)
|
|
2304
|
+
if param_shape_info is not None:
|
|
2305
|
+
if end_node_name not in output_shape_data:
|
|
2306
|
+
output_shape_data[end_node_name] = {}
|
|
2307
|
+
output_shape_data[end_node_name][sanitized_param_name] = param_shape_info
|
|
2308
|
+
|
|
2309
|
+
# Create parameter command for end node
|
|
2310
|
+
add_param_request = AddParameterToNodeRequest(
|
|
2311
|
+
node_name=end_node_name,
|
|
2312
|
+
parameter_name=sanitized_param_name,
|
|
2313
|
+
type=parameter.output_type,
|
|
2314
|
+
default_value=None,
|
|
2315
|
+
tooltip=tooltip,
|
|
2316
|
+
initial_setup=True,
|
|
2317
|
+
)
|
|
2318
|
+
end_node_parameter_commands.append(add_param_request)
|
|
2319
|
+
|
|
2320
|
+
# Create connection from package node to end node
|
|
2321
|
+
package_to_end_connection = SerializedFlowCommands.IndirectConnectionSerialization(
|
|
2322
|
+
source_node_uuid=node_uuid,
|
|
2323
|
+
source_parameter_name=parameter.name,
|
|
2324
|
+
target_node_uuid=end_node_uuid,
|
|
2325
|
+
target_parameter_name=sanitized_param_name,
|
|
2326
|
+
)
|
|
2327
|
+
package_to_end_connections.append(package_to_end_connection)
|
|
2328
|
+
|
|
2329
|
+
def _create_multi_node_start_node_with_connections( # noqa: PLR0913
|
|
2330
|
+
self,
|
|
2331
|
+
request: PackageNodesAsSerializedFlowRequest,
|
|
2332
|
+
library_version: str,
|
|
2333
|
+
unique_parameter_uuid_to_values: dict[SerializedNodeCommands.UniqueParameterValueUUID, Any],
|
|
2334
|
+
serialized_parameter_value_tracker: SerializedParameterValueTracker,
|
|
2335
|
+
node_name_to_uuid: dict[str, SerializedNodeCommands.NodeUUID],
|
|
2336
|
+
external_connections_dict: dict[
|
|
2337
|
+
str, ConnectionAnalysis
|
|
2338
|
+
], # Contains EXTERNAL connections only - used to determine which parameters need start node inputs
|
|
2339
|
+
) -> PackagingStartNodeResult | PackageNodesAsSerializedFlowResultFailure:
|
|
2340
|
+
"""Create start node commands and connections for external incoming connections."""
|
|
2341
|
+
# Generate UUID and name for start node
|
|
2342
|
+
start_node_uuid = SerializedNodeCommands.NodeUUID(str(uuid4()))
|
|
2343
|
+
start_node_name = "Start_Package_MultiNode"
|
|
2344
|
+
|
|
2345
|
+
# Build start node CreateNodeRequest
|
|
2346
|
+
start_create_node_command = CreateNodeRequest(
|
|
2347
|
+
node_type=request.start_node_type,
|
|
2348
|
+
specific_library_name=request.start_end_specific_library_name,
|
|
2349
|
+
node_name=start_node_name,
|
|
2350
|
+
metadata={},
|
|
2351
|
+
initial_setup=True,
|
|
2352
|
+
create_error_proxy_on_failure=False,
|
|
2353
|
+
)
|
|
2354
|
+
|
|
2355
|
+
# Create library details
|
|
2356
|
+
start_node_library_details = LibraryNameAndVersion(
|
|
2357
|
+
library_name=request.start_end_specific_library_name,
|
|
2358
|
+
library_version=library_version,
|
|
2359
|
+
)
|
|
2360
|
+
|
|
2361
|
+
# Create parameter modification commands and connection mappings for the start node
|
|
2362
|
+
start_node_parameter_commands = []
|
|
2363
|
+
start_to_package_connections = []
|
|
2364
|
+
start_node_parameter_value_commands = []
|
|
2365
|
+
input_shape_data: WorkflowShapeNodes = {}
|
|
2366
|
+
|
|
2367
|
+
# Iterate through all EXTERNAL, INCOMING, DATA connections.
|
|
2368
|
+
# We will then, for each connection:
|
|
2369
|
+
# 1. Generate a mangled name
|
|
2370
|
+
# 2. Add parameter with the mangled name for each connection source on the Start Node object
|
|
2371
|
+
# 3. Extract the value from the source node and have it assigned to the Start Node so that it can be propagated
|
|
2372
|
+
# 4. Create a connection from the Start Node to the package node
|
|
2373
|
+
for target_node_name, connection_analysis in external_connections_dict.items():
|
|
2374
|
+
result = self._create_start_node_parameters_and_connections_for_incoming_data(
|
|
2375
|
+
target_node_name=target_node_name,
|
|
2376
|
+
incoming_data_connections=connection_analysis.incoming_data_connections,
|
|
2377
|
+
output_parameter_prefix=request.output_parameter_prefix,
|
|
2378
|
+
start_node_name=start_node_name,
|
|
2379
|
+
start_node_uuid=start_node_uuid,
|
|
2380
|
+
start_create_node_command=start_create_node_command,
|
|
2381
|
+
node_name_to_uuid=node_name_to_uuid,
|
|
2382
|
+
unique_parameter_uuid_to_values=unique_parameter_uuid_to_values,
|
|
2383
|
+
serialized_parameter_value_tracker=serialized_parameter_value_tracker,
|
|
2384
|
+
)
|
|
2385
|
+
if isinstance(result, PackageNodesAsSerializedFlowResultFailure):
|
|
2386
|
+
return result
|
|
2387
|
+
|
|
2388
|
+
# Accumulate results from helper
|
|
2389
|
+
start_node_parameter_commands.extend(result.parameter_commands)
|
|
2390
|
+
start_to_package_connections.extend(result.data_connections)
|
|
2391
|
+
start_node_parameter_value_commands.extend(result.parameter_value_commands)
|
|
2392
|
+
# Merge input shape data
|
|
2393
|
+
for node_name, params in result.input_shape_data.items():
|
|
2394
|
+
if node_name not in input_shape_data:
|
|
2395
|
+
input_shape_data[node_name] = {}
|
|
2396
|
+
input_shape_data[node_name].update(params)
|
|
2397
|
+
|
|
2398
|
+
# Create all control connections
|
|
2399
|
+
control_connections = self._create_start_node_control_connections(
|
|
2400
|
+
request=request,
|
|
2401
|
+
start_node_uuid=start_node_uuid,
|
|
2402
|
+
node_name_to_uuid=node_name_to_uuid,
|
|
2403
|
+
)
|
|
2404
|
+
if isinstance(control_connections, PackageNodesAsSerializedFlowResultFailure):
|
|
2405
|
+
return control_connections
|
|
2406
|
+
|
|
2407
|
+
# Add control connections to the same list as data connections
|
|
2408
|
+
start_to_package_connections.extend(control_connections)
|
|
2409
|
+
|
|
2410
|
+
# Build complete SerializedNodeCommands for start node
|
|
2411
|
+
start_node_dependencies = NodeDependencies()
|
|
2412
|
+
start_node_dependencies.libraries.add(start_node_library_details)
|
|
2413
|
+
|
|
2414
|
+
start_node_commands = SerializedNodeCommands(
|
|
2415
|
+
create_node_command=start_create_node_command,
|
|
2416
|
+
element_modification_commands=start_node_parameter_commands,
|
|
2417
|
+
node_dependencies=start_node_dependencies,
|
|
2418
|
+
node_uuid=start_node_uuid,
|
|
2419
|
+
)
|
|
2420
|
+
|
|
2421
|
+
return PackagingStartNodeResult(
|
|
2422
|
+
start_node_commands=start_node_commands,
|
|
2423
|
+
start_to_package_connections=start_to_package_connections,
|
|
2424
|
+
input_shape_data=input_shape_data,
|
|
2425
|
+
start_node_parameter_value_commands=start_node_parameter_value_commands,
|
|
2426
|
+
)
|
|
2427
|
+
|
|
2428
|
+
def _create_start_node_parameters_and_connections_for_incoming_data( # noqa: PLR0913
|
|
2429
|
+
self,
|
|
2430
|
+
target_node_name: str,
|
|
2431
|
+
incoming_data_connections: list[IncomingConnection],
|
|
2432
|
+
output_parameter_prefix: str,
|
|
2433
|
+
start_node_name: str,
|
|
2434
|
+
start_node_uuid: SerializedNodeCommands.NodeUUID,
|
|
2435
|
+
start_create_node_command: CreateNodeRequest,
|
|
2436
|
+
node_name_to_uuid: dict[str, SerializedNodeCommands.NodeUUID],
|
|
2437
|
+
unique_parameter_uuid_to_values: dict[SerializedNodeCommands.UniqueParameterValueUUID, Any],
|
|
2438
|
+
serialized_parameter_value_tracker: SerializedParameterValueTracker,
|
|
2439
|
+
) -> StartNodeIncomingDataResult | PackageNodesAsSerializedFlowResultFailure:
|
|
2440
|
+
"""Create parameters and connections for incoming data connections to a specific target node."""
|
|
2441
|
+
start_node_parameter_commands = []
|
|
2442
|
+
start_to_package_connections = []
|
|
2443
|
+
start_node_parameter_value_commands = []
|
|
2444
|
+
input_shape_data: WorkflowShapeNodes = {}
|
|
2445
|
+
|
|
2446
|
+
for connection in incoming_data_connections:
|
|
2447
|
+
target_parameter_name = connection.target_parameter_name
|
|
2448
|
+
|
|
2449
|
+
# Create sanitized parameter name with prefix + node + parameter
|
|
2450
|
+
param_name = self._generate_sanitized_parameter_name(
|
|
2451
|
+
prefix=output_parameter_prefix,
|
|
2452
|
+
node_name=target_node_name,
|
|
2453
|
+
parameter_name=target_parameter_name,
|
|
2454
|
+
)
|
|
2455
|
+
|
|
2456
|
+
# Get the source node to determine parameter type (from the external connection)
|
|
2457
|
+
try:
|
|
2458
|
+
source_node = GriptapeNodes.NodeManager().get_node_by_name(connection.source_node_name)
|
|
2459
|
+
except ValueError as err:
|
|
2460
|
+
details = f"Attempted to package nodes as serialized flow. Failed because source node '{connection.source_node_name}' from incoming connection could not be found. Error: {err}."
|
|
2461
|
+
return PackageNodesAsSerializedFlowResultFailure(result_details=details)
|
|
2462
|
+
|
|
2463
|
+
# Get the source parameter
|
|
2464
|
+
source_param = source_node.get_parameter_by_name(connection.source_parameter_name)
|
|
2465
|
+
if not source_param:
|
|
2466
|
+
details = f"Attempted to package nodes as serialized flow. Failed because source parameter '{connection.source_parameter_name}' on node '{connection.source_node_name}' from incoming connection could not be found."
|
|
2467
|
+
return PackageNodesAsSerializedFlowResultFailure(result_details=details)
|
|
2468
|
+
|
|
2469
|
+
# Extract parameter shape info for workflow shape (inputs from external sources)
|
|
2470
|
+
param_shape_info = GriptapeNodes.WorkflowManager().extract_parameter_shape_info(
|
|
2471
|
+
source_param, include_control_params=True
|
|
2472
|
+
)
|
|
2473
|
+
if param_shape_info is not None:
|
|
2474
|
+
if start_node_name not in input_shape_data:
|
|
2475
|
+
input_shape_data[start_node_name] = {}
|
|
2476
|
+
input_shape_data[start_node_name][param_name] = param_shape_info
|
|
2477
|
+
|
|
2478
|
+
# Extract parameter value from source node to set on start node
|
|
2479
|
+
param_value_commands = GriptapeNodes.NodeManager().handle_parameter_value_saving(
|
|
2480
|
+
parameter=source_param,
|
|
2481
|
+
node=source_node,
|
|
2482
|
+
unique_parameter_uuid_to_values=unique_parameter_uuid_to_values,
|
|
2483
|
+
serialized_parameter_value_tracker=serialized_parameter_value_tracker,
|
|
2484
|
+
create_node_request=start_create_node_command,
|
|
2485
|
+
)
|
|
2486
|
+
if param_value_commands is not None:
|
|
2487
|
+
# Modify each command to target the start node parameter instead
|
|
2488
|
+
for param_value_command in param_value_commands:
|
|
2489
|
+
param_value_command.set_parameter_value_command.node_name = start_node_name
|
|
2490
|
+
param_value_command.set_parameter_value_command.parameter_name = param_name
|
|
2491
|
+
start_node_parameter_value_commands.append(param_value_command)
|
|
2492
|
+
|
|
2493
|
+
# Create parameter command for start node (following single-node pattern exactly)
|
|
2494
|
+
add_param_request = AddParameterToNodeRequest(
|
|
2495
|
+
node_name=start_node_name,
|
|
2496
|
+
parameter_name=param_name,
|
|
2497
|
+
type=source_param.output_type,
|
|
2498
|
+
default_value=None,
|
|
2499
|
+
tooltip=f"Parameter {target_parameter_name} from node {target_node_name} in packaged flow",
|
|
2500
|
+
initial_setup=True,
|
|
2501
|
+
)
|
|
2502
|
+
start_node_parameter_commands.append(add_param_request)
|
|
2503
|
+
|
|
2504
|
+
# Get target node UUID from mapping
|
|
2505
|
+
target_node_uuid = node_name_to_uuid.get(target_node_name)
|
|
2506
|
+
if target_node_uuid is None:
|
|
2507
|
+
details = f"Attempted to package nodes as serialized flow. Failed because target node '{target_node_name}' UUID not found in mapping."
|
|
2508
|
+
return PackageNodesAsSerializedFlowResultFailure(result_details=details)
|
|
2509
|
+
|
|
2510
|
+
# Create connection from start node to target node
|
|
2511
|
+
start_to_package_connections.append(
|
|
2512
|
+
SerializedFlowCommands.IndirectConnectionSerialization(
|
|
2513
|
+
source_node_uuid=start_node_uuid,
|
|
2514
|
+
source_parameter_name=param_name,
|
|
2515
|
+
target_node_uuid=target_node_uuid,
|
|
2516
|
+
target_parameter_name=target_parameter_name,
|
|
2517
|
+
)
|
|
2518
|
+
)
|
|
2519
|
+
|
|
2520
|
+
return StartNodeIncomingDataResult(
|
|
2521
|
+
parameter_commands=start_node_parameter_commands,
|
|
2522
|
+
data_connections=start_to_package_connections,
|
|
2523
|
+
input_shape_data=input_shape_data,
|
|
2524
|
+
parameter_value_commands=start_node_parameter_value_commands,
|
|
2525
|
+
)
|
|
2526
|
+
|
|
2527
|
+
def _create_start_node_control_connections(
|
|
2528
|
+
self,
|
|
2529
|
+
request: PackageNodesAsSerializedFlowRequest,
|
|
2530
|
+
start_node_uuid: SerializedNodeCommands.NodeUUID,
|
|
2531
|
+
node_name_to_uuid: dict[str, SerializedNodeCommands.NodeUUID],
|
|
2532
|
+
) -> list[SerializedFlowCommands.IndirectConnectionSerialization] | PackageNodesAsSerializedFlowResultFailure:
|
|
2533
|
+
"""Create control connection from start node to entry control node.
|
|
2534
|
+
|
|
2535
|
+
Args:
|
|
2536
|
+
request: The packaging request with control flow configuration
|
|
2537
|
+
start_node_uuid: UUID of the start node
|
|
2538
|
+
node_name_to_uuid: Mapping of node names to UUIDs for lookup
|
|
2539
|
+
|
|
2540
|
+
Returns:
|
|
2541
|
+
List of control connections or failure result
|
|
2542
|
+
"""
|
|
2543
|
+
control_connections = []
|
|
2544
|
+
|
|
2545
|
+
# Connect start node to specified entry control node (if specified)
|
|
2546
|
+
if request.entry_control_node_name:
|
|
2547
|
+
# Get the entry node (already validated to exist)
|
|
2548
|
+
entry_node = GriptapeNodes.NodeManager().get_node_by_name(request.entry_control_node_name)
|
|
2549
|
+
entry_node_uuid = node_name_to_uuid[request.entry_control_node_name]
|
|
2550
|
+
|
|
2551
|
+
# Connect start node to specified entry control node
|
|
2552
|
+
control_connection_result = self._create_start_node_control_connection(
|
|
2553
|
+
entry_control_parameter_name=request.entry_control_parameter_name,
|
|
2554
|
+
start_node_uuid=start_node_uuid,
|
|
2555
|
+
package_node_uuid=entry_node_uuid,
|
|
2556
|
+
package_node=entry_node,
|
|
2557
|
+
)
|
|
2558
|
+
if isinstance(control_connection_result, PackageNodeAsSerializedFlowResultFailure):
|
|
2559
|
+
return PackageNodesAsSerializedFlowResultFailure(
|
|
2560
|
+
result_details=control_connection_result.result_details
|
|
2561
|
+
)
|
|
2562
|
+
|
|
2563
|
+
control_connections.append(control_connection_result)
|
|
2564
|
+
|
|
2565
|
+
return control_connections
|
|
2566
|
+
|
|
2567
|
+
def _create_start_node_control_connection(
|
|
2568
|
+
self,
|
|
2569
|
+
entry_control_parameter_name: str | None,
|
|
2570
|
+
start_node_uuid: SerializedNodeCommands.NodeUUID,
|
|
2571
|
+
package_node_uuid: SerializedNodeCommands.NodeUUID,
|
|
2572
|
+
package_node: BaseNode,
|
|
2573
|
+
) -> SerializedFlowCommands.IndirectConnectionSerialization | PackageNodeAsSerializedFlowResultFailure:
|
|
2574
|
+
"""Create control flow connection from start node to package node.
|
|
2575
|
+
|
|
2576
|
+
Connects the start node's first control output to the specified or first available package node control input.
|
|
2577
|
+
"""
|
|
2578
|
+
if entry_control_parameter_name is not None:
|
|
2579
|
+
# Case 1: Specific entry parameter name provided
|
|
2580
|
+
package_control_input_name = entry_control_parameter_name
|
|
2581
|
+
else:
|
|
2582
|
+
# Case 2: Find the first available control input parameter
|
|
2583
|
+
package_control_input_name = None
|
|
2584
|
+
for param in package_node.parameters:
|
|
2585
|
+
if ParameterTypeBuiltin.CONTROL_TYPE.value in param.input_types:
|
|
2586
|
+
package_control_input_name = param.name
|
|
2587
|
+
logger.warning(
|
|
2588
|
+
"No entry_control_parameter_name specified for packaging node '%s'. "
|
|
2589
|
+
"Using first available control input parameter: '%s'",
|
|
2590
|
+
package_node.name,
|
|
2591
|
+
package_control_input_name,
|
|
2592
|
+
)
|
|
2593
|
+
break
|
|
2594
|
+
|
|
2595
|
+
if package_control_input_name is None:
|
|
2596
|
+
details = f"Attempted to package node '{package_node.name}'. Failed because no control input parameters found on the node, so cannot create control flow connection."
|
|
2597
|
+
return PackageNodeAsSerializedFlowResultFailure(result_details=details)
|
|
2598
|
+
|
|
2599
|
+
# StartNode always has a control output parameter with name "exec_out"
|
|
2600
|
+
source_control_parameter_name = "exec_out"
|
|
2601
|
+
|
|
2602
|
+
# Create the connection
|
|
2603
|
+
control_connection = SerializedFlowCommands.IndirectConnectionSerialization(
|
|
2604
|
+
source_node_uuid=start_node_uuid,
|
|
2605
|
+
source_parameter_name=source_control_parameter_name,
|
|
2606
|
+
target_node_uuid=package_node_uuid,
|
|
2607
|
+
target_parameter_name=package_control_input_name,
|
|
2608
|
+
)
|
|
2609
|
+
return control_connection
|
|
2610
|
+
|
|
2611
|
+
def _collect_all_connections_for_multi_node_package(
|
|
2612
|
+
self,
|
|
2613
|
+
start_node_result: PackagingStartNodeResult,
|
|
2614
|
+
end_node_packaging_result: PackagingEndNodeResult,
|
|
2615
|
+
packaged_nodes_internal_connections: list[SerializedFlowCommands.IndirectConnectionSerialization],
|
|
2616
|
+
) -> list[SerializedFlowCommands.IndirectConnectionSerialization]:
|
|
2617
|
+
"""Collect all connections for the multi-node packaged flow.
|
|
2618
|
+
|
|
2619
|
+
Returns a list containing:
|
|
2620
|
+
1. Start node connections (data + control)
|
|
2621
|
+
2. End node connections (data)
|
|
2622
|
+
3. Internal package node connections
|
|
2623
|
+
|
|
2624
|
+
Args:
|
|
2625
|
+
start_node_result: Result containing start node and its connections
|
|
2626
|
+
end_node_packaging_result: Result containing end node and its connections
|
|
2627
|
+
packaged_nodes_internal_connections: Internal connections between package nodes
|
|
2628
|
+
|
|
2629
|
+
Returns:
|
|
2630
|
+
List of all connections for the packaged flow
|
|
2631
|
+
"""
|
|
2632
|
+
all_connections = []
|
|
2633
|
+
|
|
2634
|
+
# Add start and end node connections
|
|
2635
|
+
all_connections.extend(start_node_result.start_to_package_connections)
|
|
2636
|
+
all_connections.extend(end_node_packaging_result.package_to_end_connections)
|
|
2637
|
+
|
|
2638
|
+
# Add internal package node connections
|
|
2639
|
+
all_connections.extend(packaged_nodes_internal_connections)
|
|
2640
|
+
|
|
2641
|
+
return all_connections
|
|
2642
|
+
|
|
2643
|
+
def _generate_sanitized_parameter_name(self, prefix: str, node_name: str, parameter_name: str) -> str:
|
|
2644
|
+
"""Generate a sanitized parameter name for multi-node packaging.
|
|
2645
|
+
|
|
2646
|
+
Creates a parameter name in the format: prefix + sanitized_node_name + _ + parameter_name
|
|
2647
|
+
Node names are sanitized by replacing spaces and dots with underscores.
|
|
2648
|
+
|
|
2649
|
+
Args:
|
|
2650
|
+
prefix: Prefix for the parameter name (e.g., "packaged_node_")
|
|
2651
|
+
node_name: Original node name (may contain spaces, dots, etc.)
|
|
2652
|
+
parameter_name: Original parameter name
|
|
2653
|
+
|
|
2654
|
+
Returns:
|
|
2655
|
+
Sanitized parameter name safe for use (e.g., "packaged_node_Merge_Texts_merge_string")
|
|
2656
|
+
"""
|
|
2657
|
+
sanitized_node_name = node_name.replace(" ", "_").replace(".", "_")
|
|
2658
|
+
return f"{prefix}{sanitized_node_name}_{parameter_name}"
|
|
2659
|
+
|
|
2660
|
+
async def on_start_flow_request(self, request: StartFlowRequest) -> ResultPayload: # noqa: C901, PLR0911, PLR0912
|
|
2661
|
+
# which flow
|
|
2662
|
+
flow_name = request.flow_name
|
|
2663
|
+
if not flow_name:
|
|
2664
|
+
details = "Must provide flow name to start a flow."
|
|
2665
|
+
|
|
2666
|
+
return StartFlowResultFailure(validation_exceptions=[], result_details=details)
|
|
2667
|
+
# get the flow by ID
|
|
2668
|
+
try:
|
|
2669
|
+
flow = self.get_flow_by_name(flow_name)
|
|
2670
|
+
except KeyError as err:
|
|
2671
|
+
details = f"Cannot start flow. Error: {err}"
|
|
2672
|
+
return StartFlowResultFailure(validation_exceptions=[err], result_details=details)
|
|
2673
|
+
# Check to see if the flow is already running.
|
|
2674
|
+
if self.check_for_existing_running_flow():
|
|
2675
|
+
details = "Cannot start flow. Flow is already running."
|
|
2676
|
+
return StartFlowResultFailure(validation_exceptions=[], result_details=details)
|
|
2677
|
+
# A node has been provided to either start or to run up to.
|
|
2678
|
+
if request.flow_node_name:
|
|
2679
|
+
flow_node_name = request.flow_node_name
|
|
2680
|
+
flow_node = GriptapeNodes.ObjectManager().attempt_get_object_by_name_as_type(flow_node_name, BaseNode)
|
|
2681
|
+
if not flow_node:
|
|
2682
|
+
details = f"Provided node with name {flow_node_name} does not exist"
|
|
2683
|
+
return StartFlowResultFailure(validation_exceptions=[], result_details=details)
|
|
2684
|
+
# lets get the first control node in the flow!
|
|
2685
|
+
start_node = self.get_start_node_from_node(flow, flow_node)
|
|
2686
|
+
# if the start is not the node provided, set a breakpoint at the stop (we're running up until there)
|
|
2687
|
+
if not start_node:
|
|
2688
|
+
details = f"Start node for node with name {flow_node_name} does not exist"
|
|
2689
|
+
return StartFlowResultFailure(validation_exceptions=[], result_details=details)
|
|
2690
|
+
if start_node != flow_node:
|
|
2691
|
+
flow_node.stop_flow = True
|
|
2692
|
+
else:
|
|
2693
|
+
# we wont hit this if we dont have a request id, our requests always have nodes
|
|
2694
|
+
# If there is a request, reinitialize the queue
|
|
2695
|
+
self.get_start_node_queue() # initialize the start flow queue!
|
|
2696
|
+
start_node = None
|
|
2697
|
+
# Run Validation before starting a flow
|
|
2698
|
+
result = await self.on_validate_flow_dependencies_request(
|
|
2699
|
+
ValidateFlowDependenciesRequest(flow_name=flow_name, flow_node_name=start_node.name if start_node else None)
|
|
2700
|
+
)
|
|
2701
|
+
try:
|
|
2702
|
+
if not result.succeeded():
|
|
2703
|
+
details = f"Couldn't start flow with name {flow_name}. Flow Validation Failed"
|
|
2704
|
+
return StartFlowResultFailure(validation_exceptions=[], result_details=details)
|
|
2705
|
+
result = cast("ValidateFlowDependenciesResultSuccess", result)
|
|
2706
|
+
|
|
2707
|
+
if not result.validation_succeeded:
|
|
2708
|
+
details = f"Couldn't start flow with name {flow_name}. Flow Validation Failed."
|
|
2709
|
+
if len(result.exceptions) > 0:
|
|
2710
|
+
for exception in result.exceptions:
|
|
2711
|
+
details = f"{details}\n\t{exception}"
|
|
2712
|
+
return StartFlowResultFailure(validation_exceptions=result.exceptions, result_details=details)
|
|
2713
|
+
except Exception as e:
|
|
2714
|
+
details = f"Couldn't start flow with name {flow_name}. Flow Validation Failed: {e}"
|
|
2715
|
+
return StartFlowResultFailure(validation_exceptions=[e], result_details=details)
|
|
2716
|
+
# By now, it has been validated with no exceptions.
|
|
2717
|
+
try:
|
|
2718
|
+
await self.start_flow(
|
|
2719
|
+
flow,
|
|
2720
|
+
start_node,
|
|
2721
|
+
debug_mode=request.debug_mode,
|
|
2722
|
+
pickle_control_flow_result=request.pickle_control_flow_result,
|
|
2723
|
+
)
|
|
2724
|
+
except Exception as e:
|
|
2725
|
+
details = f"Failed to kick off flow with name {flow_name}. Exception occurred: {e} "
|
|
2726
|
+
return StartFlowResultFailure(validation_exceptions=[e], result_details=details)
|
|
2727
|
+
|
|
2728
|
+
details = f"Successfully kicked off flow with name {flow_name}"
|
|
2729
|
+
|
|
2730
|
+
return StartFlowResultSuccess(result_details=details)
|
|
2731
|
+
|
|
2732
|
+
def on_get_flow_state_request(self, event: GetFlowStateRequest) -> ResultPayload:
|
|
2733
|
+
flow_name = event.flow_name
|
|
2734
|
+
if not flow_name:
|
|
2735
|
+
details = "Could not get flow state. No flow name was provided."
|
|
2736
|
+
return GetFlowStateResultFailure(result_details=details)
|
|
2737
|
+
try:
|
|
2738
|
+
flow = self.get_flow_by_name(flow_name)
|
|
2739
|
+
except KeyError as err:
|
|
2740
|
+
details = f"Could not get flow state. Error: {err}"
|
|
2741
|
+
return GetFlowStateResultFailure(result_details=details)
|
|
2742
|
+
try:
|
|
2743
|
+
control_nodes, resolving_nodes = self.flow_state(flow)
|
|
2744
|
+
except Exception as e:
|
|
2745
|
+
details = f"Failed to get flow state of flow with name {flow_name}. Exception occurred: {e} "
|
|
2746
|
+
logger.exception(details)
|
|
2747
|
+
return GetFlowStateResultFailure(result_details=details)
|
|
2748
|
+
details = f"Successfully got flow state for flow with name {flow_name}."
|
|
2749
|
+
return GetFlowStateResultSuccess(
|
|
2750
|
+
control_nodes=control_nodes,
|
|
2751
|
+
resolving_node=resolving_nodes,
|
|
2752
|
+
result_details=details,
|
|
2753
|
+
)
|
|
2754
|
+
|
|
2755
|
+
async def on_cancel_flow_request(self, request: CancelFlowRequest) -> ResultPayload:
|
|
2756
|
+
flow_name = request.flow_name
|
|
2757
|
+
if not flow_name:
|
|
2758
|
+
details = "Could not cancel flow execution. No flow name was provided."
|
|
2759
|
+
|
|
2760
|
+
return CancelFlowResultFailure(result_details=details)
|
|
2761
|
+
try:
|
|
2762
|
+
self.get_flow_by_name(flow_name)
|
|
2763
|
+
except KeyError as err:
|
|
2764
|
+
details = f"Could not cancel flow execution. Error: {err}"
|
|
2765
|
+
|
|
2766
|
+
return CancelFlowResultFailure(result_details=details)
|
|
2767
|
+
try:
|
|
2768
|
+
await self.cancel_flow_run()
|
|
2769
|
+
except Exception as e:
|
|
2770
|
+
details = f"Could not cancel flow execution. Exception: {e}"
|
|
2771
|
+
|
|
2772
|
+
return CancelFlowResultFailure(result_details=details)
|
|
2773
|
+
details = f"Successfully cancelled flow execution with name {flow_name}"
|
|
2774
|
+
|
|
2775
|
+
return CancelFlowResultSuccess(result_details=details)
|
|
2776
|
+
|
|
2777
|
+
async def on_single_node_step_request(self, request: SingleNodeStepRequest) -> ResultPayload:
|
|
2778
|
+
flow_name = request.flow_name
|
|
2779
|
+
if not flow_name:
|
|
2780
|
+
details = "Could not advance to the next step of a running workflow. No flow name was provided."
|
|
2781
|
+
|
|
2782
|
+
return SingleNodeStepResultFailure(validation_exceptions=[], result_details=details)
|
|
2783
|
+
try:
|
|
2784
|
+
self.get_flow_by_name(flow_name)
|
|
2785
|
+
except KeyError as err:
|
|
2786
|
+
details = f"Could not advance to the next step of a running workflow. No flow with name {flow_name} exists. Error: {err}"
|
|
2787
|
+
|
|
2788
|
+
return SingleNodeStepResultFailure(validation_exceptions=[err], result_details=details)
|
|
2789
|
+
try:
|
|
2790
|
+
flow = self.get_flow_by_name(flow_name)
|
|
2791
|
+
await self.single_node_step(flow)
|
|
2792
|
+
except Exception as e:
|
|
2793
|
+
details = f"Could not advance to the next step of a running workflow. Exception: {e}"
|
|
2794
|
+
return SingleNodeStepResultFailure(validation_exceptions=[], result_details=details)
|
|
2795
|
+
|
|
2796
|
+
# All completed happily
|
|
2797
|
+
details = f"Successfully advanced to the next step of a running workflow with name {flow_name}"
|
|
2798
|
+
|
|
2799
|
+
return SingleNodeStepResultSuccess(result_details=details)
|
|
2800
|
+
|
|
2801
|
+
async def on_single_execution_step_request(self, request: SingleExecutionStepRequest) -> ResultPayload:
|
|
2802
|
+
flow_name = request.flow_name
|
|
2803
|
+
if not flow_name:
|
|
2804
|
+
details = "Could not advance to the next step of a running workflow. No flow name was provided."
|
|
2805
|
+
|
|
2806
|
+
return SingleExecutionStepResultFailure(result_details=details)
|
|
2807
|
+
try:
|
|
2808
|
+
flow = self.get_flow_by_name(flow_name)
|
|
2809
|
+
except KeyError as err:
|
|
1813
2810
|
details = f"Could not advance to the next step of a running workflow. Error: {err}."
|
|
1814
2811
|
|
|
1815
2812
|
return SingleExecutionStepResultFailure(result_details=details)
|
|
@@ -1820,7 +2817,7 @@ class FlowManager:
|
|
|
1820
2817
|
# We REALLY don't want to fail here, else we'll take the whole engine down
|
|
1821
2818
|
try:
|
|
1822
2819
|
if self.check_for_existing_running_flow():
|
|
1823
|
-
self.cancel_flow_run()
|
|
2820
|
+
await self.cancel_flow_run()
|
|
1824
2821
|
except Exception as e_inner:
|
|
1825
2822
|
details = f"Could not cancel flow execution. Exception: {e_inner}"
|
|
1826
2823
|
|
|
@@ -1941,6 +2938,38 @@ class FlowManager:
|
|
|
1941
2938
|
|
|
1942
2939
|
return aggregated_deps
|
|
1943
2940
|
|
|
2941
|
+
def _aggregate_node_types_used(
|
|
2942
|
+
self, serialized_node_commands: list[SerializedNodeCommands], sub_flows_commands: list[SerializedFlowCommands]
|
|
2943
|
+
) -> set[LibraryNameAndNodeType]:
|
|
2944
|
+
"""Aggregate node types used from nodes and sub-flows.
|
|
2945
|
+
|
|
2946
|
+
Args:
|
|
2947
|
+
serialized_node_commands: List of serialized node commands to aggregate from
|
|
2948
|
+
sub_flows_commands: List of sub-flow commands to aggregate from
|
|
2949
|
+
|
|
2950
|
+
Returns:
|
|
2951
|
+
Set of LibraryNameAndNodeType with all node types used
|
|
2952
|
+
|
|
2953
|
+
Raises:
|
|
2954
|
+
ValueError: If a node command has no library name specified
|
|
2955
|
+
"""
|
|
2956
|
+
node_types_used: set[LibraryNameAndNodeType] = set()
|
|
2957
|
+
|
|
2958
|
+
# Collect node types from all nodes in this flow
|
|
2959
|
+
for node_cmd in serialized_node_commands:
|
|
2960
|
+
node_type = node_cmd.create_node_command.node_type
|
|
2961
|
+
library_name = node_cmd.create_node_command.specific_library_name
|
|
2962
|
+
if library_name is None:
|
|
2963
|
+
msg = f"Node type '{node_type}' has no library name specified during serialization"
|
|
2964
|
+
raise ValueError(msg)
|
|
2965
|
+
node_types_used.add(LibraryNameAndNodeType(library_name=library_name, node_type=node_type))
|
|
2966
|
+
|
|
2967
|
+
# Aggregate node types from all sub-flows
|
|
2968
|
+
for sub_flow_cmd in sub_flows_commands:
|
|
2969
|
+
node_types_used.update(sub_flow_cmd.node_types_used)
|
|
2970
|
+
|
|
2971
|
+
return node_types_used
|
|
2972
|
+
|
|
1944
2973
|
# TODO: https://github.com/griptape-ai/griptape-nodes/issues/861
|
|
1945
2974
|
# similar manager refactors: https://github.com/griptape-ai/griptape-nodes/issues/806
|
|
1946
2975
|
def on_serialize_flow_to_commands(self, request: SerializeFlowToCommandsRequest) -> ResultPayload: # noqa: C901, PLR0911, PLR0912, PLR0915
|
|
@@ -2086,6 +3115,7 @@ class FlowManager:
|
|
|
2086
3115
|
set_lock_commands_per_node={},
|
|
2087
3116
|
sub_flows_commands=[],
|
|
2088
3117
|
node_dependencies=sub_flow_dependencies,
|
|
3118
|
+
node_types_used=set(),
|
|
2089
3119
|
)
|
|
2090
3120
|
sub_flow_commands.append(serialized_flow)
|
|
2091
3121
|
else:
|
|
@@ -2102,6 +3132,13 @@ class FlowManager:
|
|
|
2102
3132
|
# Aggregate all dependencies from nodes and sub-flows
|
|
2103
3133
|
aggregated_dependencies = self._aggregate_flow_dependencies(serialized_node_commands, sub_flow_commands)
|
|
2104
3134
|
|
|
3135
|
+
# Aggregate all node types used from nodes and sub-flows
|
|
3136
|
+
try:
|
|
3137
|
+
aggregated_node_types_used = self._aggregate_node_types_used(serialized_node_commands, sub_flow_commands)
|
|
3138
|
+
except ValueError as e:
|
|
3139
|
+
details = f"Attempted to serialize Flow '{flow_name}' to commands. Failed while aggregating node types: {e}"
|
|
3140
|
+
return SerializeFlowToCommandsResultFailure(result_details=details)
|
|
3141
|
+
|
|
2105
3142
|
serialized_flow = SerializedFlowCommands(
|
|
2106
3143
|
flow_initialization_command=create_flow_request,
|
|
2107
3144
|
serialized_node_commands=serialized_node_commands,
|
|
@@ -2111,6 +3148,7 @@ class FlowManager:
|
|
|
2111
3148
|
set_lock_commands_per_node=set_lock_commands_per_node,
|
|
2112
3149
|
sub_flows_commands=sub_flow_commands,
|
|
2113
3150
|
node_dependencies=aggregated_dependencies,
|
|
3151
|
+
node_types_used=aggregated_node_types_used,
|
|
2114
3152
|
)
|
|
2115
3153
|
details = f"Successfully serialized Flow '{flow_name}' into commands."
|
|
2116
3154
|
result = SerializeFlowToCommandsResultSuccess(serialized_flow_commands=serialized_flow, result_details=details)
|
|
@@ -2258,6 +3296,7 @@ class FlowManager:
|
|
|
2258
3296
|
start_node: BaseNode | None = None,
|
|
2259
3297
|
*,
|
|
2260
3298
|
debug_mode: bool = False,
|
|
3299
|
+
pickle_control_flow_result: bool = False,
|
|
2261
3300
|
) -> None:
|
|
2262
3301
|
if self.check_for_existing_running_flow():
|
|
2263
3302
|
# If flow already exists, throw an error
|
|
@@ -2274,13 +3313,15 @@ class FlowManager:
|
|
|
2274
3313
|
|
|
2275
3314
|
# Initialize global control flow machine and DAG builder
|
|
2276
3315
|
|
|
2277
|
-
self._global_control_flow_machine = ControlFlowMachine(
|
|
3316
|
+
self._global_control_flow_machine = ControlFlowMachine(
|
|
3317
|
+
flow.name, pickle_control_flow_result=pickle_control_flow_result
|
|
3318
|
+
)
|
|
2278
3319
|
# Set off the request here.
|
|
2279
3320
|
try:
|
|
2280
3321
|
await self._global_control_flow_machine.start_flow(start_node, debug_mode)
|
|
2281
3322
|
except Exception:
|
|
2282
3323
|
if self.check_for_existing_running_flow():
|
|
2283
|
-
self.cancel_flow_run()
|
|
3324
|
+
await self.cancel_flow_run()
|
|
2284
3325
|
raise
|
|
2285
3326
|
GriptapeNodes.EventManager().put_event(
|
|
2286
3327
|
ExecutionGriptapeNodeEvent(wrapped_event=ExecutionEvent(payload=InvolvedNodesEvent(involved_nodes=[])))
|
|
@@ -2298,11 +3339,16 @@ class FlowManager:
|
|
|
2298
3339
|
and self._global_control_flow_machine.context.resolution_machine.is_started()
|
|
2299
3340
|
)
|
|
2300
3341
|
|
|
2301
|
-
def cancel_flow_run(self) -> None:
|
|
3342
|
+
async def cancel_flow_run(self) -> None:
|
|
2302
3343
|
if not self.check_for_existing_running_flow():
|
|
2303
3344
|
errormsg = "Flow has not yet been started. Cannot cancel flow that hasn't begun."
|
|
2304
3345
|
raise RuntimeError(errormsg)
|
|
2305
3346
|
self._global_flow_queue.queue.clear()
|
|
3347
|
+
|
|
3348
|
+
# Request cancellation on all nodes and wait for them to complete
|
|
3349
|
+
if self._global_control_flow_machine is not None:
|
|
3350
|
+
await self._global_control_flow_machine.cancel_flow()
|
|
3351
|
+
|
|
2306
3352
|
# Reset control flow machine
|
|
2307
3353
|
if self._global_control_flow_machine is not None:
|
|
2308
3354
|
self._global_control_flow_machine.reset_machine(cancel=True)
|
|
@@ -2429,8 +3475,8 @@ class FlowManager:
|
|
|
2429
3475
|
except Exception as e:
|
|
2430
3476
|
logger.exception("Exception during single node resolution")
|
|
2431
3477
|
if self.check_for_existing_running_flow():
|
|
2432
|
-
self.cancel_flow_run()
|
|
2433
|
-
|
|
3478
|
+
await self.cancel_flow_run()
|
|
3479
|
+
raise RuntimeError(e) from e
|
|
2434
3480
|
if resolution_machine.is_complete():
|
|
2435
3481
|
self._global_single_node_resolution = False
|
|
2436
3482
|
self._global_control_flow_machine.context.current_nodes = []
|