griptape-nodes 0.63.9__py3-none-any.whl → 0.64.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/common/node_executor.py +95 -171
- griptape_nodes/exe_types/connections.py +51 -2
- griptape_nodes/exe_types/flow.py +3 -3
- griptape_nodes/exe_types/node_types.py +330 -202
- griptape_nodes/exe_types/param_components/artifact_url/__init__.py +1 -0
- griptape_nodes/exe_types/param_components/artifact_url/public_artifact_url_parameter.py +155 -0
- griptape_nodes/exe_types/param_components/progress_bar_component.py +1 -1
- griptape_nodes/exe_types/param_types/parameter_string.py +27 -0
- griptape_nodes/machines/control_flow.py +64 -203
- griptape_nodes/machines/dag_builder.py +85 -238
- griptape_nodes/machines/parallel_resolution.py +9 -236
- griptape_nodes/machines/sequential_resolution.py +133 -11
- griptape_nodes/retained_mode/events/agent_events.py +2 -0
- griptape_nodes/retained_mode/events/flow_events.py +5 -6
- griptape_nodes/retained_mode/events/node_events.py +151 -1
- griptape_nodes/retained_mode/events/workflow_events.py +10 -0
- griptape_nodes/retained_mode/managers/agent_manager.py +33 -1
- griptape_nodes/retained_mode/managers/flow_manager.py +213 -290
- griptape_nodes/retained_mode/managers/library_manager.py +24 -7
- griptape_nodes/retained_mode/managers/node_manager.py +391 -77
- griptape_nodes/retained_mode/managers/workflow_manager.py +45 -10
- griptape_nodes/servers/mcp.py +32 -0
- griptape_nodes/updater/__init__.py +1 -1
- {griptape_nodes-0.63.9.dist-info → griptape_nodes-0.64.0.dist-info}/METADATA +3 -1
- {griptape_nodes-0.63.9.dist-info → griptape_nodes-0.64.0.dist-info}/RECORD +27 -25
- {griptape_nodes-0.63.9.dist-info → griptape_nodes-0.64.0.dist-info}/WHEEL +0 -0
- {griptape_nodes-0.63.9.dist-info → griptape_nodes-0.64.0.dist-info}/entry_points.txt +0 -0
|
@@ -13,7 +13,7 @@ from collections import defaultdict
|
|
|
13
13
|
from dataclasses import dataclass, field
|
|
14
14
|
from importlib.resources import files
|
|
15
15
|
from pathlib import Path
|
|
16
|
-
from typing import TYPE_CHECKING, cast
|
|
16
|
+
from typing import TYPE_CHECKING, Any, Generic, TypeVar, cast
|
|
17
17
|
|
|
18
18
|
from packaging.requirements import InvalidRequirement, Requirement
|
|
19
19
|
from pydantic import ValidationError
|
|
@@ -145,6 +145,8 @@ if TYPE_CHECKING:
|
|
|
145
145
|
logger = logging.getLogger("griptape_nodes")
|
|
146
146
|
console = Console()
|
|
147
147
|
|
|
148
|
+
TRegisteredEventData = TypeVar("TRegisteredEventData")
|
|
149
|
+
|
|
148
150
|
|
|
149
151
|
class LibraryManager:
|
|
150
152
|
SANDBOX_LIBRARY_NAME = "Sandbox Library"
|
|
@@ -167,11 +169,16 @@ class LibraryManager:
|
|
|
167
169
|
_library_file_path_to_info: dict[str, LibraryInfo]
|
|
168
170
|
|
|
169
171
|
@dataclass
|
|
170
|
-
class RegisteredEventHandler:
|
|
171
|
-
"""Information regarding an event handler from a registered library.
|
|
172
|
+
class RegisteredEventHandler(Generic[TRegisteredEventData]):
|
|
173
|
+
"""Information regarding an event handler from a registered library.
|
|
174
|
+
|
|
175
|
+
The generic type parameter TRegisteredEventData allows each event type
|
|
176
|
+
to specify its own structured additional data.
|
|
177
|
+
"""
|
|
172
178
|
|
|
173
179
|
handler: Callable[[RequestPayload], ResultPayload]
|
|
174
180
|
library_data: LibrarySchema
|
|
181
|
+
event_data: TRegisteredEventData | None = None
|
|
175
182
|
|
|
176
183
|
# Stable module namespace mappings for workflow serialization
|
|
177
184
|
# These mappings ensure that dynamically loaded modules can be reliably imported
|
|
@@ -197,7 +204,9 @@ class LibraryManager:
|
|
|
197
204
|
self._dynamic_to_stable_module_mapping = {}
|
|
198
205
|
self._stable_to_dynamic_module_mapping = {}
|
|
199
206
|
self._library_to_stable_modules = {}
|
|
200
|
-
self._library_event_handler_mappings: dict[
|
|
207
|
+
self._library_event_handler_mappings: dict[
|
|
208
|
+
type[Payload], dict[str, LibraryManager.RegisteredEventHandler[Any]]
|
|
209
|
+
] = {}
|
|
201
210
|
# LibraryDirectory owns the FSMs and manages library lifecycle
|
|
202
211
|
self._library_directory = LibraryDirectory()
|
|
203
212
|
self._libraries_loading_complete = asyncio.Event()
|
|
@@ -357,15 +366,23 @@ class LibraryManager:
|
|
|
357
366
|
request_type: type[RequestPayload],
|
|
358
367
|
handler: Callable[[RequestPayload], ResultPayload],
|
|
359
368
|
library_data: LibrarySchema,
|
|
369
|
+
event_data: object | None = None,
|
|
360
370
|
) -> None:
|
|
361
|
-
"""Register an event handler for a specific request type from a library.
|
|
371
|
+
"""Register an event handler for a specific request type from a library.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
request_type: The type of request payload this handler processes
|
|
375
|
+
handler: The callable handler function
|
|
376
|
+
library_data: Schema data for the library registering this handler
|
|
377
|
+
event_data: Optional structured data specific to this event type
|
|
378
|
+
"""
|
|
362
379
|
if self._library_event_handler_mappings.get(request_type) is None:
|
|
363
380
|
self._library_event_handler_mappings[request_type] = {}
|
|
364
381
|
self._library_event_handler_mappings[request_type][library_data.name] = LibraryManager.RegisteredEventHandler(
|
|
365
|
-
handler=handler, library_data=library_data
|
|
382
|
+
handler=handler, library_data=library_data, event_data=event_data
|
|
366
383
|
)
|
|
367
384
|
|
|
368
|
-
def get_registered_event_handlers(self, request_type: type[Payload]) -> dict[str, RegisteredEventHandler]:
|
|
385
|
+
def get_registered_event_handlers(self, request_type: type[Payload]) -> dict[str, RegisteredEventHandler[Any]]:
|
|
369
386
|
"""Get all registered event handlers for a specific request type."""
|
|
370
387
|
return self._library_event_handler_mappings.get(request_type, {})
|
|
371
388
|
|
|
@@ -20,7 +20,7 @@ from griptape_nodes.exe_types.node_types import (
|
|
|
20
20
|
EndLoopNode,
|
|
21
21
|
ErrorProxyNode,
|
|
22
22
|
NodeDependencies,
|
|
23
|
-
|
|
23
|
+
NodeGroupNode,
|
|
24
24
|
NodeResolutionState,
|
|
25
25
|
StartLoopNode,
|
|
26
26
|
)
|
|
@@ -60,15 +60,24 @@ from griptape_nodes.retained_mode.events.library_events import (
|
|
|
60
60
|
GetLibraryMetadataResultSuccess,
|
|
61
61
|
)
|
|
62
62
|
from griptape_nodes.retained_mode.events.node_events import (
|
|
63
|
+
AddNodesToNodeGroupRequest,
|
|
64
|
+
AddNodesToNodeGroupResultFailure,
|
|
65
|
+
AddNodesToNodeGroupResultSuccess,
|
|
63
66
|
BatchSetNodeMetadataRequest,
|
|
64
67
|
BatchSetNodeMetadataResultFailure,
|
|
65
68
|
BatchSetNodeMetadataResultSuccess,
|
|
66
69
|
CanResetNodeToDefaultsRequest,
|
|
67
70
|
CanResetNodeToDefaultsResultFailure,
|
|
68
71
|
CanResetNodeToDefaultsResultSuccess,
|
|
72
|
+
CreateNodeGroupRequest,
|
|
73
|
+
CreateNodeGroupResultFailure,
|
|
74
|
+
CreateNodeGroupResultSuccess,
|
|
69
75
|
CreateNodeRequest,
|
|
70
76
|
CreateNodeResultFailure,
|
|
71
77
|
CreateNodeResultSuccess,
|
|
78
|
+
DeleteNodeGroupRequest,
|
|
79
|
+
DeleteNodeGroupResultFailure,
|
|
80
|
+
DeleteNodeGroupResultSuccess,
|
|
72
81
|
DeleteNodeRequest,
|
|
73
82
|
DeleteNodeResultFailure,
|
|
74
83
|
DeleteNodeResultSuccess,
|
|
@@ -96,6 +105,9 @@ from griptape_nodes.retained_mode.events.node_events import (
|
|
|
96
105
|
ListParametersOnNodeRequest,
|
|
97
106
|
ListParametersOnNodeResultFailure,
|
|
98
107
|
ListParametersOnNodeResultSuccess,
|
|
108
|
+
RemoveNodeFromNodeGroupRequest,
|
|
109
|
+
RemoveNodeFromNodeGroupResultFailure,
|
|
110
|
+
RemoveNodeFromNodeGroupResultSuccess,
|
|
99
111
|
ResetNodeToDefaultsRequest,
|
|
100
112
|
ResetNodeToDefaultsResultFailure,
|
|
101
113
|
ResetNodeToDefaultsResultSuccess,
|
|
@@ -200,6 +212,14 @@ class NodeManager:
|
|
|
200
212
|
self._name_to_parent_flow_name = {}
|
|
201
213
|
|
|
202
214
|
event_manager.assign_manager_to_request_type(CreateNodeRequest, self.on_create_node_request)
|
|
215
|
+
event_manager.assign_manager_to_request_type(CreateNodeGroupRequest, self.on_create_node_group_request)
|
|
216
|
+
event_manager.assign_manager_to_request_type(
|
|
217
|
+
AddNodesToNodeGroupRequest, self.on_add_nodes_to_node_group_request
|
|
218
|
+
)
|
|
219
|
+
event_manager.assign_manager_to_request_type(
|
|
220
|
+
RemoveNodeFromNodeGroupRequest, self.on_remove_node_from_node_group_request
|
|
221
|
+
)
|
|
222
|
+
event_manager.assign_manager_to_request_type(DeleteNodeGroupRequest, self.on_delete_node_group_request)
|
|
203
223
|
event_manager.assign_manager_to_request_type(DeleteNodeRequest, self.on_delete_node_request)
|
|
204
224
|
event_manager.assign_manager_to_request_type(
|
|
205
225
|
GetNodeResolutionStateRequest, self.on_get_node_resolution_state_request
|
|
@@ -474,6 +494,288 @@ class NodeManager:
|
|
|
474
494
|
result_details=ResultDetails(message=details, level=log_level),
|
|
475
495
|
)
|
|
476
496
|
|
|
497
|
+
def on_create_node_group_request(self, request: CreateNodeGroupRequest) -> ResultPayload: # noqa: C901
|
|
498
|
+
"""Handle CreateNodeGroupRequest to create a new NodeGroupNode."""
|
|
499
|
+
flow_name = request.flow_name
|
|
500
|
+
flow = None
|
|
501
|
+
|
|
502
|
+
if flow_name is None:
|
|
503
|
+
if not GriptapeNodes.ContextManager().has_current_flow():
|
|
504
|
+
details = "Attempted to create NodeGroup in the Current Context. Failed because the Current Context was empty."
|
|
505
|
+
return CreateNodeGroupResultFailure(result_details=details)
|
|
506
|
+
flow = GriptapeNodes.ContextManager().get_current_flow()
|
|
507
|
+
flow_name = flow.name
|
|
508
|
+
|
|
509
|
+
if flow is None:
|
|
510
|
+
flow_mgr = GriptapeNodes.FlowManager()
|
|
511
|
+
try:
|
|
512
|
+
flow = flow_mgr.get_flow_by_name(flow_name)
|
|
513
|
+
except KeyError as err:
|
|
514
|
+
details = f"Attempted to create NodeGroup. Failed when attempting to find the parent Flow. Error: {err}"
|
|
515
|
+
return CreateNodeGroupResultFailure(result_details=details)
|
|
516
|
+
|
|
517
|
+
requested_node_group_name = request.node_group_name
|
|
518
|
+
if requested_node_group_name is None:
|
|
519
|
+
requested_node_group_name = "NodeGroup"
|
|
520
|
+
|
|
521
|
+
obj_mgr = GriptapeNodes.ObjectManager()
|
|
522
|
+
final_node_group_name = obj_mgr.generate_name_for_object(
|
|
523
|
+
type_name="NodeGroupNode", requested_name=requested_node_group_name
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
try:
|
|
527
|
+
# Create metadata with required keys for serialization
|
|
528
|
+
metadata = request.metadata if request.metadata else {}
|
|
529
|
+
metadata["node_type"] = "NodeGroupNode"
|
|
530
|
+
|
|
531
|
+
node_group = NodeGroupNode(name=final_node_group_name, metadata=metadata)
|
|
532
|
+
except Exception as err:
|
|
533
|
+
details = f"Could not create NodeGroup '{final_node_group_name}': {err}"
|
|
534
|
+
return CreateNodeGroupResultFailure(result_details=details)
|
|
535
|
+
|
|
536
|
+
if request.node_names_to_add:
|
|
537
|
+
nodes_to_add = []
|
|
538
|
+
for node_name in request.node_names_to_add:
|
|
539
|
+
try:
|
|
540
|
+
node = self.get_node_by_name(node_name)
|
|
541
|
+
except KeyError:
|
|
542
|
+
details = f"Attempted to add node '{node_name}' to NodeGroup '{final_node_group_name}'. Failed because node was not found."
|
|
543
|
+
return CreateNodeGroupResultFailure(result_details=details)
|
|
544
|
+
nodes_to_add.append(node)
|
|
545
|
+
# Add Nodes manually here, so we don't have to add the NodeGroup and remove it if it fails.
|
|
546
|
+
try:
|
|
547
|
+
node_group.add_nodes_to_group(nodes_to_add)
|
|
548
|
+
except Exception:
|
|
549
|
+
details = f"Failed to add nodes to NodeGroup '{final_node_group_name}'."
|
|
550
|
+
return CreateNodeGroupResultFailure(result_details=details)
|
|
551
|
+
flow.add_node(node_group)
|
|
552
|
+
obj_mgr.add_object_by_name(node_group.name, node_group)
|
|
553
|
+
self._name_to_parent_flow_name[node_group.name] = flow_name
|
|
554
|
+
if request.flow_name is None:
|
|
555
|
+
details = (
|
|
556
|
+
f"Successfully created NodeGroup '{final_node_group_name}' in the Current Context (Flow '{flow_name}')"
|
|
557
|
+
)
|
|
558
|
+
else:
|
|
559
|
+
details = f"Successfully created NodeGroup '{final_node_group_name}' in Flow '{flow_name}'"
|
|
560
|
+
|
|
561
|
+
return CreateNodeGroupResultSuccess(
|
|
562
|
+
node_group_name=node_group.name, result_details=ResultDetails(message=details, level=logging.DEBUG)
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
def _get_flow_for_node_group_operation(self, flow_name: str | None) -> AddNodesToNodeGroupResultFailure | None:
|
|
566
|
+
"""Get the flow for a node group operation."""
|
|
567
|
+
if flow_name is None:
|
|
568
|
+
if not GriptapeNodes.ContextManager().has_current_flow():
|
|
569
|
+
details = "Attempted to add node to NodeGroup in the Current Context. Failed because the Current Context was empty."
|
|
570
|
+
return AddNodesToNodeGroupResultFailure(result_details=details)
|
|
571
|
+
else:
|
|
572
|
+
try:
|
|
573
|
+
GriptapeNodes.FlowManager().get_flow_by_name(flow_name)
|
|
574
|
+
except KeyError as err:
|
|
575
|
+
details = (
|
|
576
|
+
f"Attempted to add node to NodeGroup. Failed when attempting to find the parent Flow. Error: {err}"
|
|
577
|
+
)
|
|
578
|
+
return AddNodesToNodeGroupResultFailure(result_details=details)
|
|
579
|
+
return None
|
|
580
|
+
|
|
581
|
+
def _get_nodes_for_group_operation(
|
|
582
|
+
self, node_names: list[str], node_group_name: str
|
|
583
|
+
) -> list[BaseNode] | AddNodesToNodeGroupResultFailure:
|
|
584
|
+
"""Get the list of nodes to add to a group.
|
|
585
|
+
|
|
586
|
+
Collects all errors and returns them together if multiple nodes fail.
|
|
587
|
+
"""
|
|
588
|
+
obj_mgr = GriptapeNodes.ObjectManager()
|
|
589
|
+
nodes = []
|
|
590
|
+
errors = []
|
|
591
|
+
|
|
592
|
+
for node_name in node_names:
|
|
593
|
+
try:
|
|
594
|
+
node = obj_mgr.get_object_by_name(node_name)
|
|
595
|
+
except KeyError:
|
|
596
|
+
errors.append(f"Node '{node_name}' was not found")
|
|
597
|
+
continue
|
|
598
|
+
|
|
599
|
+
if not isinstance(node, BaseNode):
|
|
600
|
+
errors.append(f"'{node_name}' is not a node")
|
|
601
|
+
continue
|
|
602
|
+
|
|
603
|
+
nodes.append(node)
|
|
604
|
+
|
|
605
|
+
if errors:
|
|
606
|
+
details = f"Attempted to add nodes to NodeGroup '{node_group_name}'. Failed for the following nodes: {'; '.join(errors)}"
|
|
607
|
+
return AddNodesToNodeGroupResultFailure(result_details=details)
|
|
608
|
+
|
|
609
|
+
return nodes
|
|
610
|
+
|
|
611
|
+
def _get_node_group(
|
|
612
|
+
self, node_group_name: str, node_names: list[str]
|
|
613
|
+
) -> NodeGroupNode | AddNodesToNodeGroupResultFailure:
|
|
614
|
+
"""Get the NodeGroup node."""
|
|
615
|
+
try:
|
|
616
|
+
node_group = GriptapeNodes.ObjectManager().get_object_by_name(node_group_name)
|
|
617
|
+
except KeyError:
|
|
618
|
+
details = f"Attempted to add nodes '{node_names}' to NodeGroup '{node_group_name}'. Failed because NodeGroup was not found."
|
|
619
|
+
return AddNodesToNodeGroupResultFailure(result_details=details)
|
|
620
|
+
|
|
621
|
+
if not isinstance(node_group, NodeGroupNode):
|
|
622
|
+
details = f"Attempted to add nodes '{node_names}' to '{node_group_name}'. Failed because '{node_group_name}' is not a NodeGroup."
|
|
623
|
+
return AddNodesToNodeGroupResultFailure(result_details=details)
|
|
624
|
+
|
|
625
|
+
return node_group
|
|
626
|
+
|
|
627
|
+
def on_add_nodes_to_node_group_request(self, request: AddNodesToNodeGroupRequest) -> ResultPayload:
|
|
628
|
+
"""Handle AddNodeToNodeGroupRequest to add a node to an existing NodeGroup."""
|
|
629
|
+
flow_result = self._get_flow_for_node_group_operation(request.flow_name)
|
|
630
|
+
if isinstance(flow_result, AddNodesToNodeGroupResultFailure):
|
|
631
|
+
return flow_result
|
|
632
|
+
|
|
633
|
+
nodes_result = self._get_nodes_for_group_operation(request.node_names, request.node_group_name)
|
|
634
|
+
if isinstance(nodes_result, AddNodesToNodeGroupResultFailure):
|
|
635
|
+
return nodes_result
|
|
636
|
+
nodes = nodes_result
|
|
637
|
+
|
|
638
|
+
node_group_result = self._get_node_group(request.node_group_name, request.node_names)
|
|
639
|
+
if isinstance(node_group_result, AddNodesToNodeGroupResultFailure):
|
|
640
|
+
return node_group_result
|
|
641
|
+
node_group = node_group_result
|
|
642
|
+
|
|
643
|
+
try:
|
|
644
|
+
node_group.add_nodes_to_group(nodes)
|
|
645
|
+
except Exception as err:
|
|
646
|
+
details = f"Attempted to add node '{request.node_names}' to NodeGroup '{request.node_group_name}'. Failed with error: {err}"
|
|
647
|
+
return AddNodesToNodeGroupResultFailure(result_details=details)
|
|
648
|
+
|
|
649
|
+
details = f"Successfully added node '{request.node_names}' to NodeGroup '{request.node_group_name}'"
|
|
650
|
+
return AddNodesToNodeGroupResultSuccess(
|
|
651
|
+
result_details=ResultDetails(message=details, level=logging.DEBUG),
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
def _get_flow_for_remove_operation(self, flow_name: str | None) -> RemoveNodeFromNodeGroupResultFailure | None:
|
|
655
|
+
"""Get the flow for a remove node from group operation."""
|
|
656
|
+
if flow_name is None:
|
|
657
|
+
if not GriptapeNodes.ContextManager().has_current_flow():
|
|
658
|
+
details = "Attempted to remove nodes from NodeGroup in the Current Context. Failed because the Current Context was empty."
|
|
659
|
+
return RemoveNodeFromNodeGroupResultFailure(result_details=details)
|
|
660
|
+
else:
|
|
661
|
+
try:
|
|
662
|
+
GriptapeNodes.FlowManager().get_flow_by_name(flow_name)
|
|
663
|
+
except KeyError as err:
|
|
664
|
+
details = f"Attempted to remove nodes from NodeGroup. Failed when attempting to find the parent Flow. Error: {err}"
|
|
665
|
+
return RemoveNodeFromNodeGroupResultFailure(result_details=details)
|
|
666
|
+
return None
|
|
667
|
+
|
|
668
|
+
def _get_nodes_for_remove_operation(
|
|
669
|
+
self, node_names: list[str], node_group_name: str
|
|
670
|
+
) -> list[BaseNode] | RemoveNodeFromNodeGroupResultFailure:
|
|
671
|
+
"""Get the list of nodes to remove from a group.
|
|
672
|
+
|
|
673
|
+
Collects all errors and returns them together if multiple nodes fail.
|
|
674
|
+
"""
|
|
675
|
+
obj_mgr = GriptapeNodes.ObjectManager()
|
|
676
|
+
nodes = []
|
|
677
|
+
errors = []
|
|
678
|
+
|
|
679
|
+
for node_name in node_names:
|
|
680
|
+
try:
|
|
681
|
+
node = obj_mgr.get_object_by_name(node_name)
|
|
682
|
+
except KeyError:
|
|
683
|
+
errors.append(f"Node '{node_name}' was not found")
|
|
684
|
+
continue
|
|
685
|
+
|
|
686
|
+
if not isinstance(node, BaseNode):
|
|
687
|
+
errors.append(f"'{node_name}' is not a node")
|
|
688
|
+
continue
|
|
689
|
+
|
|
690
|
+
nodes.append(node)
|
|
691
|
+
|
|
692
|
+
if errors:
|
|
693
|
+
details = f"Attempted to remove nodes from NodeGroup '{node_group_name}'. Failed for the following nodes: {'; '.join(errors)}"
|
|
694
|
+
return RemoveNodeFromNodeGroupResultFailure(result_details=details)
|
|
695
|
+
|
|
696
|
+
return nodes
|
|
697
|
+
|
|
698
|
+
def _get_node_group_for_remove(
|
|
699
|
+
self, node_group_name: str, node_names: list[str]
|
|
700
|
+
) -> NodeGroupNode | RemoveNodeFromNodeGroupResultFailure:
|
|
701
|
+
"""Get the NodeGroup node for remove operation."""
|
|
702
|
+
try:
|
|
703
|
+
node_group = GriptapeNodes.ObjectManager().get_object_by_name(node_group_name)
|
|
704
|
+
except KeyError:
|
|
705
|
+
details = f"Attempted to remove nodes '{node_names}' from NodeGroup '{node_group_name}'. Failed because NodeGroup was not found."
|
|
706
|
+
return RemoveNodeFromNodeGroupResultFailure(result_details=details)
|
|
707
|
+
|
|
708
|
+
if not isinstance(node_group, NodeGroupNode):
|
|
709
|
+
details = f"Attempted to remove nodes '{node_names}' from '{node_group_name}'. Failed because '{node_group_name}' is not a NodeGroup."
|
|
710
|
+
return RemoveNodeFromNodeGroupResultFailure(result_details=details)
|
|
711
|
+
|
|
712
|
+
return node_group
|
|
713
|
+
|
|
714
|
+
def on_remove_node_from_node_group_request(self, request: RemoveNodeFromNodeGroupRequest) -> ResultPayload:
|
|
715
|
+
"""Handle RemoveNodeFromNodeGroupRequest to remove nodes from an existing NodeGroup."""
|
|
716
|
+
flow_result = self._get_flow_for_remove_operation(request.flow_name)
|
|
717
|
+
if isinstance(flow_result, RemoveNodeFromNodeGroupResultFailure):
|
|
718
|
+
return flow_result
|
|
719
|
+
|
|
720
|
+
nodes_result = self._get_nodes_for_remove_operation(request.node_names, request.node_group_name)
|
|
721
|
+
if isinstance(nodes_result, RemoveNodeFromNodeGroupResultFailure):
|
|
722
|
+
return nodes_result
|
|
723
|
+
nodes = nodes_result
|
|
724
|
+
|
|
725
|
+
node_group_result = self._get_node_group_for_remove(request.node_group_name, request.node_names)
|
|
726
|
+
if isinstance(node_group_result, RemoveNodeFromNodeGroupResultFailure):
|
|
727
|
+
return node_group_result
|
|
728
|
+
node_group = node_group_result
|
|
729
|
+
|
|
730
|
+
try:
|
|
731
|
+
node_group.remove_nodes_from_group(nodes)
|
|
732
|
+
except ValueError as err:
|
|
733
|
+
details = f"Attempted to remove nodes '{request.node_names}' from NodeGroup '{request.node_group_name}'. Failed with error: {err}"
|
|
734
|
+
return RemoveNodeFromNodeGroupResultFailure(result_details=details)
|
|
735
|
+
|
|
736
|
+
details = f"Successfully removed nodes '{request.node_names}' from NodeGroup '{request.node_group_name}'"
|
|
737
|
+
return RemoveNodeFromNodeGroupResultSuccess(
|
|
738
|
+
result_details=ResultDetails(message=details, level=logging.DEBUG),
|
|
739
|
+
)
|
|
740
|
+
|
|
741
|
+
def on_delete_node_group_request(self, request: DeleteNodeGroupRequest) -> ResultPayload:
|
|
742
|
+
"""Handle DeleteNodeGroupRequest to delete a NodeGroup and remove all its nodes."""
|
|
743
|
+
# Get the NodeGroup
|
|
744
|
+
obj_mgr = GriptapeNodes.ObjectManager()
|
|
745
|
+
try:
|
|
746
|
+
node_group = obj_mgr.get_object_by_name(request.node_group_name)
|
|
747
|
+
except KeyError:
|
|
748
|
+
details = (
|
|
749
|
+
f"Attempted to delete NodeGroup '{request.node_group_name}'. Failed because NodeGroup was not found."
|
|
750
|
+
)
|
|
751
|
+
return DeleteNodeGroupResultFailure(result_details=details)
|
|
752
|
+
|
|
753
|
+
if not isinstance(node_group, NodeGroupNode):
|
|
754
|
+
details = (
|
|
755
|
+
f"Attempted to delete '{request.node_group_name}' as NodeGroup. Failed because it is not a NodeGroup."
|
|
756
|
+
)
|
|
757
|
+
return DeleteNodeGroupResultFailure(result_details=details)
|
|
758
|
+
|
|
759
|
+
# Remove all nodes from the group first
|
|
760
|
+
if node_group.nodes:
|
|
761
|
+
nodes_to_remove = list(node_group.nodes.values())
|
|
762
|
+
try:
|
|
763
|
+
node_group.remove_nodes_from_group(nodes_to_remove)
|
|
764
|
+
except ValueError as err:
|
|
765
|
+
details = f"Attempted to delete NodeGroup '{request.node_group_name}'. Failed to remove nodes from group: {err}"
|
|
766
|
+
return DeleteNodeGroupResultFailure(result_details=details)
|
|
767
|
+
|
|
768
|
+
# Now delete the NodeGroup node itself
|
|
769
|
+
delete_node_request = DeleteNodeRequest(node_name=request.node_group_name)
|
|
770
|
+
delete_result = self.on_delete_node_request(delete_node_request)
|
|
771
|
+
|
|
772
|
+
if delete_result.failed():
|
|
773
|
+
details = f"Attempted to delete NodeGroup '{request.node_group_name}'. Failed to delete the NodeGroup node: {delete_result.result_details}"
|
|
774
|
+
return DeleteNodeGroupResultFailure(result_details=details)
|
|
775
|
+
|
|
776
|
+
details = f"Successfully deleted NodeGroup '{request.node_group_name}'"
|
|
777
|
+
return DeleteNodeGroupResultSuccess(result_details=ResultDetails(message=details, level=logging.DEBUG))
|
|
778
|
+
|
|
477
779
|
def cancel_conditionally(
|
|
478
780
|
self, parent_flow: ControlFlow, parent_flow_name: str, node: BaseNode
|
|
479
781
|
) -> ResultPayload | None:
|
|
@@ -1624,33 +1926,6 @@ class NodeManager:
|
|
|
1624
1926
|
# Reject runtime parameter value changes on ErrorProxy
|
|
1625
1927
|
details = f"Cannot set parameter '{param_name}' on placeholder node '{node_name}'. This placeholder preserves your workflow structure but doesn't allow parameter changes, as they could cause issues when the original node is restored."
|
|
1626
1928
|
return SetParameterValueResultFailure(result_details=details)
|
|
1627
|
-
elif isinstance(node, NodeGroupProxyNode):
|
|
1628
|
-
# For NodeGroupProxyNode, set the value on both the proxy AND the original node
|
|
1629
|
-
node.set_parameter_value(param_name, request.value)
|
|
1630
|
-
|
|
1631
|
-
# Forward the value to the original node if this proxy parameter maps to one
|
|
1632
|
-
result = None
|
|
1633
|
-
if param_name in node._proxy_param_to_node_param:
|
|
1634
|
-
original_node, original_param_name = node._proxy_param_to_node_param[param_name]
|
|
1635
|
-
result = GriptapeNodes.handle_request(
|
|
1636
|
-
SetParameterValueRequest(
|
|
1637
|
-
parameter_name=original_param_name,
|
|
1638
|
-
node_name=original_node.name,
|
|
1639
|
-
value=request.value,
|
|
1640
|
-
data_type=request.data_type,
|
|
1641
|
-
incoming_connection_source_node_name=request.incoming_connection_source_node_name,
|
|
1642
|
-
incoming_connection_source_parameter_name=request.incoming_connection_source_parameter_name,
|
|
1643
|
-
)
|
|
1644
|
-
)
|
|
1645
|
-
logger.debug(
|
|
1646
|
-
"Forwarded parameter value from proxy '%s.%s' to original '%s.%s'",
|
|
1647
|
-
node.name,
|
|
1648
|
-
param_name,
|
|
1649
|
-
original_node.name,
|
|
1650
|
-
original_param_name,
|
|
1651
|
-
)
|
|
1652
|
-
details = f"Attempted to set parameter value for '{node_name}.{param_name}'. Successfully set value on the NodeGroupProxyNode '{node_name}', but failed to set value on the original node'."
|
|
1653
|
-
return result if result else SetParameterValueResultFailure(result_details=details)
|
|
1654
1929
|
|
|
1655
1930
|
# Does the Parameter actually exist on the Node?
|
|
1656
1931
|
parameter = node.get_parameter_by_name(param_name)
|
|
@@ -2144,55 +2419,81 @@ class NodeManager:
|
|
|
2144
2419
|
|
|
2145
2420
|
# This is our current dude.
|
|
2146
2421
|
with GriptapeNodes.ContextManager().node(node=node):
|
|
2147
|
-
#
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
# Call LibraryManager directly to avoid error toasts when library is unavailable (expected for ErrorProxyNode)
|
|
2152
|
-
# Per https://github.com/griptape-ai/griptape-nodes/issues/1940
|
|
2153
|
-
library_metadata_result = GriptapeNodes.LibraryManager().get_library_metadata_request(
|
|
2154
|
-
library_metadata_request
|
|
2155
|
-
)
|
|
2156
|
-
|
|
2157
|
-
if not isinstance(library_metadata_result, GetLibraryMetadataResultSuccess):
|
|
2158
|
-
if isinstance(node, ErrorProxyNode):
|
|
2159
|
-
# For ErrorProxyNode, use descriptive message when original library unavailable
|
|
2160
|
-
library_version = "<version unavailable; workflow was saved when library was unable to be loaded>"
|
|
2161
|
-
library_details = LibraryNameAndVersion(library_name=library_used, library_version=library_version)
|
|
2162
|
-
details = f"Serializing Node '{node_name}' (original type: {node.original_node_type}) with unavailable library '{library_used}'. Saving as ErrorProxy with placeholder version. Fix the missing library and reload the workflow to restore the original node."
|
|
2163
|
-
logger.warning(details)
|
|
2164
|
-
else:
|
|
2165
|
-
# For regular nodes, this is still an error
|
|
2166
|
-
details = f"Attempted to serialize Node '{node_name}' to commands. Failed to get metadata for library '{library_used}'."
|
|
2167
|
-
return SerializeNodeToCommandsResultFailure(result_details=details)
|
|
2422
|
+
# Handle NodeGroupNode specially - skip library lookup entirely
|
|
2423
|
+
if isinstance(node, NodeGroupNode):
|
|
2424
|
+
# NodeGroupNode doesn't have a library dependency
|
|
2425
|
+
library_details = None
|
|
2168
2426
|
else:
|
|
2169
|
-
|
|
2170
|
-
|
|
2427
|
+
# Get the library and version details for regular nodes
|
|
2428
|
+
library_used = node.metadata["library"]
|
|
2429
|
+
# Get the library metadata so we can get the version.
|
|
2430
|
+
library_metadata_request = GetLibraryMetadataRequest(library=library_used)
|
|
2431
|
+
# Call LibraryManager directly to avoid error toasts when library is unavailable (expected for ErrorProxyNode)
|
|
2432
|
+
# Per https://github.com/griptape-ai/griptape-nodes/issues/1940
|
|
2433
|
+
library_metadata_result = GriptapeNodes.LibraryManager().get_library_metadata_request(
|
|
2434
|
+
library_metadata_request
|
|
2435
|
+
)
|
|
2171
2436
|
|
|
2172
|
-
|
|
2437
|
+
if not isinstance(library_metadata_result, GetLibraryMetadataResultSuccess):
|
|
2438
|
+
if isinstance(node, ErrorProxyNode):
|
|
2439
|
+
# For ErrorProxyNode, use descriptive message when original library unavailable
|
|
2440
|
+
library_version = (
|
|
2441
|
+
"<version unavailable; workflow was saved when library was unable to be loaded>"
|
|
2442
|
+
)
|
|
2443
|
+
library_details = LibraryNameAndVersion(
|
|
2444
|
+
library_name=library_used, library_version=library_version
|
|
2445
|
+
)
|
|
2446
|
+
details = f"Serializing Node '{node_name}' (original type: {node.original_node_type}) with unavailable library '{library_used}'. Saving as ErrorProxy with placeholder version. Fix the missing library and reload the workflow to restore the original node."
|
|
2447
|
+
logger.warning(details)
|
|
2448
|
+
else:
|
|
2449
|
+
# For regular nodes, this is still an error
|
|
2450
|
+
details = f"Attempted to serialize Node '{node_name}' to commands. Failed to get metadata for library '{library_used}'."
|
|
2451
|
+
return SerializeNodeToCommandsResultFailure(result_details=details)
|
|
2452
|
+
else:
|
|
2453
|
+
library_version = library_metadata_result.metadata.library_version
|
|
2454
|
+
library_details = LibraryNameAndVersion(library_name=library_used, library_version=library_version)
|
|
2173
2455
|
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2456
|
+
# Handle NodeGroupNode specially - emit CreateNodeGroupRequest instead
|
|
2457
|
+
if isinstance(node, NodeGroupNode):
|
|
2458
|
+
create_node_request = CreateNodeGroupRequest(
|
|
2459
|
+
node_group_name=node_name,
|
|
2460
|
+
node_names_to_add=list(node.nodes),
|
|
2461
|
+
metadata=copy.deepcopy(node.metadata),
|
|
2462
|
+
)
|
|
2177
2463
|
else:
|
|
2178
|
-
|
|
2179
|
-
|
|
2464
|
+
# For non-NodeGroupNode, library_details should always be set
|
|
2465
|
+
if library_details is None:
|
|
2466
|
+
details = f"Attempted to serialize Node '{node_name}' to commands. Library details missing."
|
|
2467
|
+
return SerializeNodeToCommandsResultFailure(result_details=details)
|
|
2180
2468
|
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2469
|
+
# Handle ErrorProxyNode serialization - serialize as original node type
|
|
2470
|
+
if isinstance(node, ErrorProxyNode):
|
|
2471
|
+
serialized_node_type = node.original_node_type
|
|
2472
|
+
serialized_library_name = node.original_library_name
|
|
2473
|
+
else:
|
|
2474
|
+
serialized_node_type = node.__class__.__name__
|
|
2475
|
+
serialized_library_name = library_details.library_name
|
|
2476
|
+
|
|
2477
|
+
# Get the creation details for regular nodes
|
|
2478
|
+
create_node_request = CreateNodeRequest(
|
|
2479
|
+
node_type=serialized_node_type,
|
|
2480
|
+
node_name=node_name,
|
|
2481
|
+
specific_library_name=serialized_library_name,
|
|
2482
|
+
metadata=copy.deepcopy(node.metadata),
|
|
2483
|
+
# If it is actively resolving, mark as unresolved.
|
|
2484
|
+
resolution=node.state.value,
|
|
2485
|
+
initial_setup=True,
|
|
2486
|
+
)
|
|
2191
2487
|
|
|
2192
2488
|
# We're going to compare this node instance vs. a canonical one. Rez that one up.
|
|
2193
2489
|
# For ErrorProxyNode, we can't create a reference node, so skip comparison
|
|
2194
2490
|
if isinstance(node, ErrorProxyNode):
|
|
2195
2491
|
reference_node = None
|
|
2492
|
+
elif isinstance(node, NodeGroupNode):
|
|
2493
|
+
# For NodeGroupNode, create a fresh reference instance the same way we create NodeGroupNodes
|
|
2494
|
+
reference_node = NodeGroupNode(
|
|
2495
|
+
name="REFERENCE NODE", metadata={"library": "griptape_nodes", "node_type": "NodeGroupNode"}
|
|
2496
|
+
)
|
|
2196
2497
|
else:
|
|
2197
2498
|
reference_node = type(node)(name="REFERENCE NODE")
|
|
2198
2499
|
|
|
@@ -2255,6 +2556,7 @@ class NodeManager:
|
|
|
2255
2556
|
)
|
|
2256
2557
|
if set_param_value_requests is not None:
|
|
2257
2558
|
set_value_commands.extend(set_param_value_requests)
|
|
2559
|
+
|
|
2258
2560
|
# now check if locked
|
|
2259
2561
|
if node.lock:
|
|
2260
2562
|
lock_command = SetLockNodeStateRequest(node_name=None, lock=True)
|
|
@@ -2267,8 +2569,9 @@ class NodeManager:
|
|
|
2267
2569
|
# Ensure we always have a NodeDependencies object, even if empty
|
|
2268
2570
|
node_dependencies = NodeDependencies()
|
|
2269
2571
|
|
|
2270
|
-
# Add the library dependency to the node dependencies
|
|
2271
|
-
|
|
2572
|
+
# Add the library dependency to the node dependencies (if applicable)
|
|
2573
|
+
if library_details is not None:
|
|
2574
|
+
node_dependencies.libraries.add(library_details)
|
|
2272
2575
|
|
|
2273
2576
|
# Hooray
|
|
2274
2577
|
serialized_node_commands = SerializedNodeCommands(
|
|
@@ -2397,12 +2700,21 @@ class NodeManager:
|
|
|
2397
2700
|
# Issue the creation command first.
|
|
2398
2701
|
create_node_request = request.serialized_node_commands.create_node_command
|
|
2399
2702
|
create_node_result = GriptapeNodes().handle_request(create_node_request)
|
|
2400
|
-
if not isinstance(create_node_result, CreateNodeResultSuccess):
|
|
2401
|
-
|
|
2703
|
+
if not isinstance(create_node_result, (CreateNodeResultSuccess, CreateNodeGroupResultSuccess)):
|
|
2704
|
+
req_node_name = (
|
|
2705
|
+
create_node_request.node_group_name
|
|
2706
|
+
if isinstance(create_node_request, CreateNodeGroupRequest)
|
|
2707
|
+
else create_node_request.node_name
|
|
2708
|
+
)
|
|
2709
|
+
details = f"Attempted to deserialize a serialized set of Node Creation commands. Failed to create node '{req_node_name}'."
|
|
2402
2710
|
return DeserializeNodeFromCommandsResultFailure(result_details=details)
|
|
2403
2711
|
|
|
2404
2712
|
# Adopt the newly-created node as our current context.
|
|
2405
|
-
node_name =
|
|
2713
|
+
node_name = (
|
|
2714
|
+
create_node_result.node_group_name
|
|
2715
|
+
if isinstance(create_node_result, CreateNodeGroupResultSuccess)
|
|
2716
|
+
else create_node_result.node_name
|
|
2717
|
+
)
|
|
2406
2718
|
node = GriptapeNodes.ObjectManager().attempt_get_object_by_name_as_type(node_name, BaseNode)
|
|
2407
2719
|
if node is None:
|
|
2408
2720
|
details = f"Attempted to deserialize a serialized set of Node Creation commands. Failed to get node '{node_name}'."
|
|
@@ -2683,7 +2995,7 @@ class NodeManager:
|
|
|
2683
2995
|
node: BaseNode,
|
|
2684
2996
|
unique_parameter_uuid_to_values: dict[SerializedNodeCommands.UniqueParameterValueUUID, Any],
|
|
2685
2997
|
serialized_parameter_value_tracker: SerializedParameterValueTracker,
|
|
2686
|
-
create_node_request: CreateNodeRequest,
|
|
2998
|
+
create_node_request: CreateNodeRequest | CreateNodeGroupRequest,
|
|
2687
2999
|
) -> list[SerializedNodeCommands.IndirectSetParameterValueCommand] | None:
|
|
2688
3000
|
"""Generates code to save a parameter value for a node in a Griptape workflow.
|
|
2689
3001
|
|
|
@@ -2735,8 +3047,9 @@ class NodeManager:
|
|
|
2735
3047
|
if internal_command is None:
|
|
2736
3048
|
details = f"Attempted to serialize set value for parameter '{parameter.name}' on node '{node.name}'. The set value will not be restored in anything that attempts to deserialize or save this node. The value for this parameter was not serialized because it did not match Griptape Nodes' criteria for serializability. To remedy, either update the value's type to support serializability or mark the parameter as not serializable by setting serializable=False when creating the parameter."
|
|
2737
3049
|
logger.warning(details)
|
|
2738
|
-
# Set node to unresolved when serialization fails
|
|
2739
|
-
create_node_request
|
|
3050
|
+
# Set node to unresolved when serialization fails (only for CreateNodeRequest)
|
|
3051
|
+
if isinstance(create_node_request, CreateNodeRequest):
|
|
3052
|
+
create_node_request.resolution = NodeResolutionState.UNRESOLVED.value
|
|
2740
3053
|
else:
|
|
2741
3054
|
commands.append(internal_command)
|
|
2742
3055
|
if output_value is not None:
|
|
@@ -2752,8 +3065,9 @@ class NodeManager:
|
|
|
2752
3065
|
if output_command is None:
|
|
2753
3066
|
details = f"Attempted to serialize output value for parameter '{parameter.name}' on node '{node.name}'. The output value will not be restored in anything that attempts to deserialize or save this node. The value for this parameter was not serialized because it did not match Griptape Nodes' criteria for serializability. To remedy, either update the value's type to support serializability or mark the parameter as not serializable by setting serializable=False when creating the parameter."
|
|
2754
3067
|
logger.warning(details)
|
|
2755
|
-
# Set node to unresolved when serialization fails
|
|
2756
|
-
create_node_request
|
|
3068
|
+
# Set node to unresolved when serialization fails (only for CreateNodeRequest)
|
|
3069
|
+
if isinstance(create_node_request, CreateNodeRequest):
|
|
3070
|
+
create_node_request.resolution = NodeResolutionState.UNRESOLVED.value
|
|
2757
3071
|
else:
|
|
2758
3072
|
commands.append(output_command)
|
|
2759
3073
|
return commands if commands else None
|