griptape-nodes 0.57.1__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.1.dist-info → griptape_nodes-0.58.0.dist-info}/METADATA +4 -3
- {griptape_nodes-0.57.1.dist-info → griptape_nodes-0.58.0.dist-info}/RECORD +49 -47
- {griptape_nodes-0.57.1.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.1.dist-info → griptape_nodes-0.58.0.dist-info}/entry_points.txt +0 -0
|
@@ -58,7 +58,7 @@ class LocalStorageDriver(BaseStorageDriver):
|
|
|
58
58
|
|
|
59
59
|
def create_signed_download_url(self, path: Path) -> str:
|
|
60
60
|
# The base_url already includes the /static path, so just append the path
|
|
61
|
-
url = f"{self.base_url}/{path}"
|
|
61
|
+
url = f"{self.base_url}/{path.as_posix()}"
|
|
62
62
|
# Add a cache-busting query parameter to the URL so that the browser always reloads the file
|
|
63
63
|
cache_busted_url = f"{url}?t={int(time.time())}"
|
|
64
64
|
return cache_busted_url
|
|
@@ -70,7 +70,7 @@ class LocalStorageDriver(BaseStorageDriver):
|
|
|
70
70
|
path: The path of the file to delete.
|
|
71
71
|
"""
|
|
72
72
|
# Use the static server's delete endpoint
|
|
73
|
-
delete_url = urljoin(self.base_url, f"/static-files/{path}")
|
|
73
|
+
delete_url = urljoin(self.base_url, f"/static-files/{path.as_posix()}")
|
|
74
74
|
|
|
75
75
|
try:
|
|
76
76
|
response = httpx.delete(delete_url)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from dataclasses import dataclass
|
|
3
|
+
from enum import StrEnum
|
|
3
4
|
|
|
4
5
|
from griptape_nodes.exe_types.core_types import Parameter, ParameterMode, ParameterTypeBuiltin
|
|
5
6
|
from griptape_nodes.exe_types.node_types import BaseNode, Connection, EndLoopNode, NodeResolutionState, StartLoopNode
|
|
@@ -7,6 +8,11 @@ from griptape_nodes.exe_types.node_types import BaseNode, Connection, EndLoopNod
|
|
|
7
8
|
logger = logging.getLogger("griptape_nodes")
|
|
8
9
|
|
|
9
10
|
|
|
11
|
+
class Direction(StrEnum):
|
|
12
|
+
UPSTREAM = "upstream"
|
|
13
|
+
DOWNSTREAM = "downstream"
|
|
14
|
+
|
|
15
|
+
|
|
10
16
|
@dataclass
|
|
11
17
|
class Connections:
|
|
12
18
|
# store connections as IDs
|
|
@@ -127,28 +133,50 @@ class Connections:
|
|
|
127
133
|
return connection.source_node, connection.source_parameter
|
|
128
134
|
return None # No connection found for this control parameter
|
|
129
135
|
|
|
130
|
-
def get_connected_node(
|
|
136
|
+
def get_connected_node(
|
|
137
|
+
self, node: BaseNode, parameter: Parameter, direction: Direction | None = None
|
|
138
|
+
) -> tuple[BaseNode, Parameter] | None:
|
|
131
139
|
# Check to see if we should be getting the next connection or the previous connection based on the parameter.
|
|
132
140
|
# Override this method for EndLoopNodes - these might have to go backwards or forwards.
|
|
133
|
-
if
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
141
|
+
if direction is not None:
|
|
142
|
+
# We've added direction as an override, since we sometimes need to get connections in a certain direction regardless of parameter types.
|
|
143
|
+
if direction == Direction.UPSTREAM:
|
|
144
|
+
connections = self.incoming_index
|
|
145
|
+
elif direction == Direction.DOWNSTREAM:
|
|
146
|
+
connections = self.outgoing_index
|
|
137
147
|
else:
|
|
138
|
-
|
|
148
|
+
if isinstance(node, EndLoopNode) and ParameterTypeBuiltin.CONTROL_TYPE.value == parameter.output_type:
|
|
149
|
+
return self._get_connected_node_for_end_loop_control(node, parameter)
|
|
150
|
+
if ParameterTypeBuiltin.CONTROL_TYPE.value == parameter.output_type:
|
|
151
|
+
connections = self.outgoing_index
|
|
152
|
+
# We still default to downstream (forwards) connections for control parameters
|
|
153
|
+
direction = Direction.DOWNSTREAM
|
|
154
|
+
else:
|
|
155
|
+
connections = self.incoming_index
|
|
156
|
+
# And upstream (backwards) connections for data parameters.
|
|
157
|
+
direction = Direction.UPSTREAM
|
|
139
158
|
connections_from_node = connections.get(node.name, {})
|
|
140
159
|
|
|
141
160
|
connection_id = connections_from_node.get(parameter.name, [])
|
|
142
161
|
# TODO: https://github.com/griptape-ai/griptape-nodes/issues/859
|
|
143
162
|
if not len(connection_id):
|
|
144
163
|
return None
|
|
145
|
-
if
|
|
146
|
-
|
|
164
|
+
# Right now, our special case is that it is ok to have multiple inputs to a CONTROL_TYPE parameter, so if we're going upstream, it's ok. And it's ok to have multiple downstream outputs from a data type parameter.
|
|
165
|
+
if (
|
|
166
|
+
len(connection_id) > 1
|
|
167
|
+
and not (
|
|
168
|
+
direction == Direction.UPSTREAM and parameter.output_type == ParameterTypeBuiltin.CONTROL_TYPE.value
|
|
169
|
+
)
|
|
170
|
+
and not (
|
|
171
|
+
direction == Direction.DOWNSTREAM and parameter.output_type != ParameterTypeBuiltin.CONTROL_TYPE.value
|
|
172
|
+
)
|
|
173
|
+
):
|
|
174
|
+
msg = f"There should not be more than one {direction} connection here to/from {node.name}.{parameter.name}"
|
|
147
175
|
raise ValueError(msg)
|
|
148
176
|
connection_id = connection_id[0]
|
|
149
177
|
if connection_id in self.connections:
|
|
150
178
|
connection = self.connections[connection_id]
|
|
151
|
-
if
|
|
179
|
+
if direction == Direction.DOWNSTREAM:
|
|
152
180
|
# Return the target (next place to go)
|
|
153
181
|
return connection.target_node, connection.target_parameter
|
|
154
182
|
# Return the source (next place to chain back to)
|
|
@@ -1313,7 +1313,7 @@ class ControlParameter(Parameter, ABC):
|
|
|
1313
1313
|
super().__init__(
|
|
1314
1314
|
type=ParameterTypeBuiltin.CONTROL_TYPE.value,
|
|
1315
1315
|
default_value=None,
|
|
1316
|
-
settable=
|
|
1316
|
+
settable=True,
|
|
1317
1317
|
name=name,
|
|
1318
1318
|
tooltip=tooltip,
|
|
1319
1319
|
input_types=input_types,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import asyncio
|
|
4
3
|
import logging
|
|
4
|
+
import threading
|
|
5
|
+
import warnings
|
|
5
6
|
from abc import ABC, abstractmethod
|
|
6
7
|
from collections.abc import Callable, Generator, Iterable
|
|
7
|
-
from concurrent.futures import ThreadPoolExecutor
|
|
8
8
|
from dataclasses import dataclass, field
|
|
9
9
|
from enum import StrEnum, auto
|
|
10
10
|
from typing import TYPE_CHECKING, Any, NamedTuple, TypeVar
|
|
@@ -31,16 +31,13 @@ from griptape_nodes.retained_mode.events.base_events import (
|
|
|
31
31
|
ProgressEvent,
|
|
32
32
|
RequestPayload,
|
|
33
33
|
)
|
|
34
|
-
from griptape_nodes.retained_mode.events.execution_events import (
|
|
35
|
-
NodeUnresolvedEvent,
|
|
36
|
-
ParameterValueUpdateEvent,
|
|
37
|
-
)
|
|
38
34
|
from griptape_nodes.retained_mode.events.parameter_events import (
|
|
39
35
|
AddParameterToNodeRequest,
|
|
40
36
|
RemoveElementEvent,
|
|
41
37
|
RemoveParameterFromNodeRequest,
|
|
42
38
|
)
|
|
43
39
|
from griptape_nodes.traits.options import Options
|
|
40
|
+
from griptape_nodes.utils import async_utils
|
|
44
41
|
|
|
45
42
|
if TYPE_CHECKING:
|
|
46
43
|
from griptape_nodes.exe_types.core_types import NodeMessagePayload
|
|
@@ -53,6 +50,8 @@ T = TypeVar("T")
|
|
|
53
50
|
AsyncResult = Generator[Callable[[], T], T]
|
|
54
51
|
|
|
55
52
|
LOCAL_EXECUTION = "Local Execution"
|
|
53
|
+
PRIVATE_EXECUTION = "Private Execution"
|
|
54
|
+
CONTROL_INPUT_PARAMETER = "Control Input Selection"
|
|
56
55
|
|
|
57
56
|
|
|
58
57
|
class ImportDependency(NamedTuple):
|
|
@@ -116,8 +115,8 @@ def get_library_names_with_publish_handlers() -> list[str]:
|
|
|
116
115
|
library_manager = GriptapeNodes.LibraryManager()
|
|
117
116
|
event_handlers = library_manager.get_registered_event_handlers(PublishWorkflowRequest)
|
|
118
117
|
|
|
119
|
-
# Always include "local" as the first
|
|
120
|
-
library_names = [LOCAL_EXECUTION]
|
|
118
|
+
# Always include "local" and "private" as the first options
|
|
119
|
+
library_names = [LOCAL_EXECUTION, PRIVATE_EXECUTION]
|
|
121
120
|
|
|
122
121
|
# Add all registered library names that can handle PublishWorkflowRequest
|
|
123
122
|
library_names.extend(sorted(event_handlers.keys()))
|
|
@@ -131,17 +130,18 @@ class BaseNode(ABC):
|
|
|
131
130
|
metadata: dict[Any, Any]
|
|
132
131
|
|
|
133
132
|
# Node Context Fields
|
|
134
|
-
state: NodeResolutionState
|
|
135
133
|
current_spotlight_parameter: Parameter | None = None
|
|
136
134
|
parameter_values: dict[str, Any]
|
|
137
135
|
parameter_output_values: TrackedParameterOutputValues
|
|
138
136
|
stop_flow: bool = False
|
|
139
137
|
root_ui_element: BaseNodeElement
|
|
138
|
+
_state: NodeResolutionState
|
|
140
139
|
_tracked_parameters: list[BaseNodeElement]
|
|
141
140
|
_entry_control_parameter: Parameter | None = (
|
|
142
141
|
None # The control input parameter used to enter this node during execution
|
|
143
142
|
)
|
|
144
143
|
lock: bool = False # When lock is true, the node is locked and can't be modified. When lock is false, the node is unlocked and can be modified.
|
|
144
|
+
_cancellation_requested: threading.Event # Event indicating if cancellation has been requested for this node
|
|
145
145
|
|
|
146
146
|
@property
|
|
147
147
|
def parameters(self) -> list[Parameter]:
|
|
@@ -157,7 +157,7 @@ class BaseNode(ABC):
|
|
|
157
157
|
state: NodeResolutionState = NodeResolutionState.UNRESOLVED,
|
|
158
158
|
) -> None:
|
|
159
159
|
self.name = name
|
|
160
|
-
self.
|
|
160
|
+
self._state = state
|
|
161
161
|
if metadata is None:
|
|
162
162
|
self.metadata = {}
|
|
163
163
|
else:
|
|
@@ -169,6 +169,7 @@ class BaseNode(ABC):
|
|
|
169
169
|
self.root_ui_element._node_context = self
|
|
170
170
|
self.process_generator = None
|
|
171
171
|
self._tracked_parameters = []
|
|
172
|
+
self._cancellation_requested = threading.Event()
|
|
172
173
|
self.set_entry_control_parameter(None)
|
|
173
174
|
self.execution_environment = Parameter(
|
|
174
175
|
name="execution_environment",
|
|
@@ -181,6 +182,18 @@ class BaseNode(ABC):
|
|
|
181
182
|
)
|
|
182
183
|
self.add_parameter(self.execution_environment)
|
|
183
184
|
|
|
185
|
+
@property
|
|
186
|
+
def state(self) -> NodeResolutionState:
|
|
187
|
+
"""Get the current resolution state of the node.
|
|
188
|
+
|
|
189
|
+
Existence as @property facilitates subclasses overriding the getter for dynamic/computed state.
|
|
190
|
+
"""
|
|
191
|
+
return self._state
|
|
192
|
+
|
|
193
|
+
@state.setter
|
|
194
|
+
def state(self, new_state: NodeResolutionState) -> None:
|
|
195
|
+
self._state = new_state
|
|
196
|
+
|
|
184
197
|
# This is gross and we need to have a universal pass on resolution state changes and emission of events. That's what this ticket does!
|
|
185
198
|
# https://github.com/griptape-ai/griptape-nodes/issues/994
|
|
186
199
|
def make_node_unresolved(self, current_states_to_trigger_change_event: set[NodeResolutionState] | None) -> None:
|
|
@@ -190,6 +203,7 @@ class BaseNode(ABC):
|
|
|
190
203
|
if current_states_to_trigger_change_event is not None and self.state in current_states_to_trigger_change_event:
|
|
191
204
|
# Trigger the change event.
|
|
192
205
|
# Send an event to the GUI so it knows this node has changed resolution state.
|
|
206
|
+
from griptape_nodes.retained_mode.events.execution_events import NodeUnresolvedEvent
|
|
193
207
|
|
|
194
208
|
GriptapeNodes.EventManager().put_event(
|
|
195
209
|
ExecutionGriptapeNodeEvent(
|
|
@@ -210,6 +224,27 @@ class BaseNode(ABC):
|
|
|
210
224
|
"""
|
|
211
225
|
self._entry_control_parameter = parameter
|
|
212
226
|
|
|
227
|
+
@property
|
|
228
|
+
def is_cancellation_requested(self) -> bool:
|
|
229
|
+
"""Check if cancellation has been requested for this node.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
True if cancellation has been requested, False otherwise
|
|
233
|
+
"""
|
|
234
|
+
return self._cancellation_requested.is_set()
|
|
235
|
+
|
|
236
|
+
def request_cancellation(self) -> None:
|
|
237
|
+
"""Request cancellation of this node's execution.
|
|
238
|
+
|
|
239
|
+
Sets a flag that the node can check during long-running operations
|
|
240
|
+
to cooperatively cancel execution.
|
|
241
|
+
"""
|
|
242
|
+
self._cancellation_requested.set()
|
|
243
|
+
|
|
244
|
+
def clear_cancellation(self) -> None:
|
|
245
|
+
"""Clear the cancellation request flag."""
|
|
246
|
+
self._cancellation_requested.clear()
|
|
247
|
+
|
|
213
248
|
def emit_parameter_changes(self) -> None:
|
|
214
249
|
if self._tracked_parameters:
|
|
215
250
|
for parameter in self._tracked_parameters:
|
|
@@ -816,20 +851,14 @@ class BaseNode(ABC):
|
|
|
816
851
|
return
|
|
817
852
|
|
|
818
853
|
if isinstance(result, Generator):
|
|
819
|
-
# Handle generator pattern asynchronously using the same logic as before
|
|
820
|
-
loop = asyncio.get_running_loop()
|
|
821
|
-
|
|
822
854
|
try:
|
|
823
855
|
# Start the generator
|
|
824
856
|
func = next(result)
|
|
825
857
|
|
|
826
858
|
while True:
|
|
827
|
-
# Run callable in thread pool (preserving existing behavior)
|
|
828
|
-
with ThreadPoolExecutor() as executor:
|
|
829
|
-
future_result = await loop.run_in_executor(executor, func)
|
|
830
|
-
|
|
831
859
|
# Send result back and get next callable
|
|
832
|
-
|
|
860
|
+
func_result = await async_utils.to_thread(func)
|
|
861
|
+
func = result.send(func_result)
|
|
833
862
|
|
|
834
863
|
except StopIteration:
|
|
835
864
|
# Generator is done
|
|
@@ -871,12 +900,26 @@ class BaseNode(ABC):
|
|
|
871
900
|
def get_config_value(self, service: str, value: str) -> str:
|
|
872
901
|
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
873
902
|
|
|
903
|
+
warnings.warn(
|
|
904
|
+
"get_config_value() is deprecated. Use GriptapeNodes.SecretsManager().get_secret() for secrets/API keys "
|
|
905
|
+
"or GriptapeNodes.ConfigManager().get_config_value() for other config values.",
|
|
906
|
+
UserWarning,
|
|
907
|
+
stacklevel=2,
|
|
908
|
+
)
|
|
909
|
+
|
|
874
910
|
config_value = GriptapeNodes.ConfigManager().get_config_value(f"nodes.{service}.{value}")
|
|
875
911
|
return config_value
|
|
876
912
|
|
|
877
913
|
def set_config_value(self, service: str, value: str, new_value: str) -> None:
|
|
878
914
|
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
879
915
|
|
|
916
|
+
warnings.warn(
|
|
917
|
+
"set_config_value() is deprecated. Use GriptapeNodes.SecretsManager().set_secret() for secrets/API keys "
|
|
918
|
+
"or GriptapeNodes.ConfigManager().set_config_value() for other config values.",
|
|
919
|
+
UserWarning,
|
|
920
|
+
stacklevel=2,
|
|
921
|
+
)
|
|
922
|
+
|
|
880
923
|
GriptapeNodes.ConfigManager().set_config_value(f"nodes.{service}.{value}", new_value)
|
|
881
924
|
|
|
882
925
|
def clear_node(self) -> None:
|
|
@@ -884,6 +927,8 @@ class BaseNode(ABC):
|
|
|
884
927
|
self.state = NodeResolutionState.UNRESOLVED
|
|
885
928
|
# delete all output values potentially generated
|
|
886
929
|
self.parameter_output_values.clear()
|
|
930
|
+
# Clear cancellation flag
|
|
931
|
+
self.clear_cancellation()
|
|
887
932
|
# Clear the spotlight linked list
|
|
888
933
|
# First, clear all next/prev pointers to break the linked list
|
|
889
934
|
current = self.current_spotlight_parameter
|
|
@@ -946,6 +991,7 @@ class BaseNode(ABC):
|
|
|
946
991
|
)
|
|
947
992
|
|
|
948
993
|
def publish_update_to_parameter(self, parameter_name: str, value: Any) -> None:
|
|
994
|
+
from griptape_nodes.retained_mode.events.execution_events import ParameterValueUpdateEvent
|
|
949
995
|
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
950
996
|
|
|
951
997
|
parameter = self.get_parameter_by_name(parameter_name)
|
|
@@ -1008,7 +1054,10 @@ class BaseNode(ABC):
|
|
|
1008
1054
|
|
|
1009
1055
|
# Verify we have all elements
|
|
1010
1056
|
if len(ordered_elements) != len(current_elements):
|
|
1011
|
-
|
|
1057
|
+
ordered_names = {e.name for e in ordered_elements}
|
|
1058
|
+
current_names = {e.name for e in current_elements}
|
|
1059
|
+
diff = current_names - ordered_names
|
|
1060
|
+
msg = f"Element order must include all elements exactly once. Missing from new order: {diff}"
|
|
1012
1061
|
raise ValueError(msg)
|
|
1013
1062
|
|
|
1014
1063
|
# Remove all elements from root_ui_element
|
|
@@ -1182,6 +1231,47 @@ class BaseNode(ABC):
|
|
|
1182
1231
|
# Use reorder_elements to apply the move
|
|
1183
1232
|
self.reorder_elements(list(new_order))
|
|
1184
1233
|
|
|
1234
|
+
def get_element_index(self, element: str | BaseNodeElement, root: BaseNodeElement | None = None) -> int:
|
|
1235
|
+
"""Get the current index of an element in the element list.
|
|
1236
|
+
|
|
1237
|
+
Args:
|
|
1238
|
+
element: The element to get the index for, specified by name or element object
|
|
1239
|
+
root: The root element to search within. If None, uses root_ui_element
|
|
1240
|
+
|
|
1241
|
+
Returns:
|
|
1242
|
+
The current index of the element (0-based)
|
|
1243
|
+
|
|
1244
|
+
Raises:
|
|
1245
|
+
ValueError: If element is not found
|
|
1246
|
+
|
|
1247
|
+
Example:
|
|
1248
|
+
# Get index by name in root container
|
|
1249
|
+
index = node.get_element_index("element1")
|
|
1250
|
+
|
|
1251
|
+
# Get index within a specific parameter group
|
|
1252
|
+
group = node.get_element_by_name_and_type("my_group", ParameterGroup)
|
|
1253
|
+
index = node.get_element_index("parameter1", root=group)
|
|
1254
|
+
|
|
1255
|
+
# Get index of a parameter to position another element relative to it
|
|
1256
|
+
reference_index = node.get_element_index("some_parameter")
|
|
1257
|
+
node.move_element_to_position("new_parameter", reference_index + 1)
|
|
1258
|
+
"""
|
|
1259
|
+
# Use root_ui_element if no root specified
|
|
1260
|
+
if root is None:
|
|
1261
|
+
root = self.root_ui_element
|
|
1262
|
+
|
|
1263
|
+
# Get list of all element names in the root
|
|
1264
|
+
element_names = [child.name for child in root._children]
|
|
1265
|
+
|
|
1266
|
+
# Get element name
|
|
1267
|
+
if isinstance(element, str):
|
|
1268
|
+
element_name = element
|
|
1269
|
+
else:
|
|
1270
|
+
element_name = element.name
|
|
1271
|
+
|
|
1272
|
+
# Find the index of the element
|
|
1273
|
+
return element_names.index(element_name)
|
|
1274
|
+
|
|
1185
1275
|
|
|
1186
1276
|
class TrackedParameterOutputValues(dict[str, Any]):
|
|
1187
1277
|
"""A dictionary that tracks modifications and emits AlterElementEvent when parameter output values change."""
|
|
@@ -1476,13 +1566,16 @@ class EndNode(BaseNode):
|
|
|
1476
1566
|
case self.succeeded_control:
|
|
1477
1567
|
was_successful = True
|
|
1478
1568
|
status_prefix = "[SUCCEEDED]"
|
|
1569
|
+
logger.debug("End Node '%s': Matched succeeded_control path", self.name)
|
|
1479
1570
|
case self.failed_control:
|
|
1480
1571
|
was_successful = False
|
|
1481
1572
|
status_prefix = "[FAILED]"
|
|
1573
|
+
logger.debug("End Node '%s': Matched failed_control path", self.name)
|
|
1482
1574
|
case _:
|
|
1483
1575
|
# No specific success/failure connection provided, assume success
|
|
1484
1576
|
was_successful = True
|
|
1485
1577
|
status_prefix = "[SUCCEEDED] No connection provided for success or failure, assuming successful"
|
|
1578
|
+
logger.debug("End Node '%s': No specific control connection, assuming success", self.name)
|
|
1486
1579
|
|
|
1487
1580
|
# Get result details and format the final message
|
|
1488
1581
|
result_details_value = self.get_parameter_value("result_details")
|
|
@@ -1500,10 +1593,10 @@ class EndNode(BaseNode):
|
|
|
1500
1593
|
if param.type != ParameterTypeBuiltin.CONTROL_TYPE:
|
|
1501
1594
|
value = self.get_parameter_value(param.name)
|
|
1502
1595
|
self.parameter_output_values[param.name] = value
|
|
1503
|
-
|
|
1596
|
+
entry_parameter = self._entry_control_parameter
|
|
1504
1597
|
# Update which control parameter to flag as the output value.
|
|
1505
|
-
if
|
|
1506
|
-
self.parameter_output_values[
|
|
1598
|
+
if entry_parameter is not None:
|
|
1599
|
+
self.parameter_output_values[entry_parameter.name] = CONTROL_INPUT_PARAMETER
|
|
1507
1600
|
|
|
1508
1601
|
|
|
1509
1602
|
class StartLoopNode(BaseNode):
|
|
@@ -5,9 +5,8 @@ import logging
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
|
-
from griptape_nodes.exe_types.core_types import Parameter
|
|
9
|
-
from griptape_nodes.exe_types.node_types import BaseNode, NodeResolutionState
|
|
10
|
-
from griptape_nodes.exe_types.type_validator import TypeValidator
|
|
8
|
+
from griptape_nodes.exe_types.core_types import Parameter, ParameterTypeBuiltin
|
|
9
|
+
from griptape_nodes.exe_types.node_types import CONTROL_INPUT_PARAMETER, LOCAL_EXECUTION, BaseNode, NodeResolutionState
|
|
11
10
|
from griptape_nodes.machines.fsm import FSM, State
|
|
12
11
|
from griptape_nodes.machines.parallel_resolution import ParallelResolutionMachine
|
|
13
12
|
from griptape_nodes.machines.sequential_resolution import SequentialResolutionMachine
|
|
@@ -45,6 +44,7 @@ class ControlFlowContext:
|
|
|
45
44
|
selected_output: Parameter | None
|
|
46
45
|
paused: bool = False
|
|
47
46
|
flow_name: str
|
|
47
|
+
pickle_control_flow_result: bool
|
|
48
48
|
|
|
49
49
|
def __init__(
|
|
50
50
|
self,
|
|
@@ -52,6 +52,7 @@ class ControlFlowContext:
|
|
|
52
52
|
max_nodes_in_parallel: int,
|
|
53
53
|
*,
|
|
54
54
|
execution_type: WorkflowExecutionMode = WorkflowExecutionMode.SEQUENTIAL,
|
|
55
|
+
pickle_control_flow_result: bool = False,
|
|
55
56
|
) -> None:
|
|
56
57
|
self.flow_name = flow_name
|
|
57
58
|
if execution_type == WorkflowExecutionMode.PARALLEL:
|
|
@@ -65,6 +66,7 @@ class ControlFlowContext:
|
|
|
65
66
|
else:
|
|
66
67
|
self.resolution_machine = SequentialResolutionMachine()
|
|
67
68
|
self.current_nodes = []
|
|
69
|
+
self.pickle_control_flow_result = pickle_control_flow_result
|
|
68
70
|
|
|
69
71
|
def get_next_nodes(self, output_parameter: Parameter | None = None) -> list[NextNodeInfo]:
|
|
70
72
|
"""Get all next nodes from the current nodes.
|
|
@@ -84,7 +86,10 @@ class ControlFlowContext:
|
|
|
84
86
|
next_nodes.append(NextNodeInfo(node=node, entry_parameter=entry_parameter))
|
|
85
87
|
else:
|
|
86
88
|
# Get next control output for this node
|
|
87
|
-
|
|
89
|
+
if current_node.get_parameter_value(current_node.execution_environment.name) != LOCAL_EXECUTION:
|
|
90
|
+
next_output = self.get_next_control_output_for_non_local_execution(current_node)
|
|
91
|
+
else:
|
|
92
|
+
next_output = current_node.get_next_control_output()
|
|
88
93
|
if next_output is not None:
|
|
89
94
|
node_connection = (
|
|
90
95
|
GriptapeNodes.FlowManager().get_connections().get_connected_node(current_node, next_output)
|
|
@@ -92,6 +97,8 @@ class ControlFlowContext:
|
|
|
92
97
|
if node_connection is not None:
|
|
93
98
|
node, entry_parameter = node_connection
|
|
94
99
|
next_nodes.append(NextNodeInfo(node=node, entry_parameter=entry_parameter))
|
|
100
|
+
else:
|
|
101
|
+
logger.debug("Control Flow: Node '%s' has no control output", current_node.name)
|
|
95
102
|
|
|
96
103
|
# If no connections found, check execution queue
|
|
97
104
|
if not next_nodes:
|
|
@@ -101,6 +108,20 @@ class ControlFlowContext:
|
|
|
101
108
|
|
|
102
109
|
return next_nodes
|
|
103
110
|
|
|
111
|
+
# Mirrored in @parallel_resolution.py. if you update one, update the other.
|
|
112
|
+
def get_next_control_output_for_non_local_execution(self, node: BaseNode) -> Parameter | None:
|
|
113
|
+
for param_name, value in node.parameter_output_values.items():
|
|
114
|
+
parameter = node.get_parameter_by_name(param_name)
|
|
115
|
+
if (
|
|
116
|
+
parameter is not None
|
|
117
|
+
and parameter.type == ParameterTypeBuiltin.CONTROL_TYPE
|
|
118
|
+
and value == CONTROL_INPUT_PARAMETER
|
|
119
|
+
):
|
|
120
|
+
# This is the parameter
|
|
121
|
+
logger.debug("Control Flow: Found control output parameter '%s' for non-local execution", param_name)
|
|
122
|
+
return parameter
|
|
123
|
+
return None
|
|
124
|
+
|
|
104
125
|
def reset(self, *, cancel: bool = False) -> None:
|
|
105
126
|
if self.current_nodes is not None:
|
|
106
127
|
for node in self.current_nodes:
|
|
@@ -152,7 +173,11 @@ class ResolveNodeState(State):
|
|
|
152
173
|
await context.resolution_machine.resolve_node(current_node)
|
|
153
174
|
|
|
154
175
|
if context.resolution_machine.is_complete():
|
|
176
|
+
# Get the last resolved node from the DAG and set it as current
|
|
155
177
|
if isinstance(context.resolution_machine, ParallelResolutionMachine):
|
|
178
|
+
last_resolved_node = context.resolution_machine.get_last_resolved_node()
|
|
179
|
+
if last_resolved_node:
|
|
180
|
+
context.current_nodes = [last_resolved_node]
|
|
156
181
|
return CompleteState
|
|
157
182
|
return NextNodeState
|
|
158
183
|
return None
|
|
@@ -219,12 +244,19 @@ class CompleteState(State):
|
|
|
219
244
|
async def on_enter(context: ControlFlowContext) -> type[State] | None:
|
|
220
245
|
# Broadcast completion events for any remaining current nodes
|
|
221
246
|
for current_node in context.current_nodes:
|
|
247
|
+
# Use pickle-based serialization for complex parameter output values
|
|
248
|
+
from griptape_nodes.retained_mode.managers.node_manager import NodeManager
|
|
249
|
+
|
|
250
|
+
parameter_output_values, unique_uuid_to_values = NodeManager.serialize_parameter_output_values(
|
|
251
|
+
current_node, use_pickling=context.pickle_control_flow_result
|
|
252
|
+
)
|
|
222
253
|
GriptapeNodes.EventManager().put_event(
|
|
223
254
|
ExecutionGriptapeNodeEvent(
|
|
224
255
|
wrapped_event=ExecutionEvent(
|
|
225
256
|
payload=ControlFlowResolvedEvent(
|
|
226
257
|
end_node_name=current_node.name,
|
|
227
|
-
parameter_output_values=
|
|
258
|
+
parameter_output_values=parameter_output_values,
|
|
259
|
+
unique_parameter_uuid_to_values=unique_uuid_to_values if unique_uuid_to_values else None,
|
|
228
260
|
)
|
|
229
261
|
)
|
|
230
262
|
)
|
|
@@ -239,12 +271,17 @@ class CompleteState(State):
|
|
|
239
271
|
|
|
240
272
|
# MACHINE TIME!!!
|
|
241
273
|
class ControlFlowMachine(FSM[ControlFlowContext]):
|
|
242
|
-
def __init__(self, flow_name: str) -> None:
|
|
274
|
+
def __init__(self, flow_name: str, *, pickle_control_flow_result: bool = False) -> None:
|
|
243
275
|
execution_type = GriptapeNodes.ConfigManager().get_config_value(
|
|
244
276
|
"workflow_execution_mode", default=WorkflowExecutionMode.SEQUENTIAL
|
|
245
277
|
)
|
|
246
278
|
max_nodes_in_parallel = GriptapeNodes.ConfigManager().get_config_value("max_nodes_in_parallel", default=5)
|
|
247
|
-
context = ControlFlowContext(
|
|
279
|
+
context = ControlFlowContext(
|
|
280
|
+
flow_name,
|
|
281
|
+
max_nodes_in_parallel,
|
|
282
|
+
execution_type=execution_type,
|
|
283
|
+
pickle_control_flow_result=pickle_control_flow_result,
|
|
284
|
+
)
|
|
248
285
|
super().__init__(context)
|
|
249
286
|
|
|
250
287
|
async def start_flow(self, start_node: BaseNode, debug_mode: bool = False) -> None: # noqa: FBT001, FBT002
|
|
@@ -346,6 +383,10 @@ class ControlFlowMachine(FSM[ControlFlowContext]):
|
|
|
346
383
|
flow_manager.global_flow_queue.queue.remove(item)
|
|
347
384
|
return start_nodes
|
|
348
385
|
|
|
386
|
+
async def cancel_flow(self) -> None:
|
|
387
|
+
"""Cancel all nodes in the flow by delegating to the resolution machine."""
|
|
388
|
+
await self.resolution_machine.cancel_all_nodes()
|
|
389
|
+
|
|
349
390
|
def reset_machine(self, *, cancel: bool = False) -> None:
|
|
350
391
|
self._context.reset(cancel=cancel)
|
|
351
392
|
self._current_state = None
|