griptape-nodes 0.51.2__py3-none-any.whl → 0.52.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/__init__.py +5 -4
- griptape_nodes/app/api.py +27 -24
- griptape_nodes/app/app.py +243 -221
- griptape_nodes/app/watch.py +17 -2
- griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +66 -103
- griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +16 -4
- griptape_nodes/exe_types/core_types.py +16 -4
- griptape_nodes/exe_types/node_types.py +74 -16
- griptape_nodes/machines/control_flow.py +21 -26
- griptape_nodes/machines/fsm.py +16 -16
- griptape_nodes/machines/node_resolution.py +30 -119
- griptape_nodes/mcp_server/server.py +14 -10
- griptape_nodes/mcp_server/ws_request_manager.py +2 -2
- griptape_nodes/node_library/workflow_registry.py +5 -0
- griptape_nodes/retained_mode/events/base_events.py +12 -7
- griptape_nodes/retained_mode/events/execution_events.py +0 -6
- griptape_nodes/retained_mode/events/node_events.py +38 -0
- griptape_nodes/retained_mode/events/parameter_events.py +11 -0
- griptape_nodes/retained_mode/events/variable_events.py +361 -0
- griptape_nodes/retained_mode/events/workflow_events.py +35 -0
- griptape_nodes/retained_mode/griptape_nodes.py +61 -26
- griptape_nodes/retained_mode/managers/agent_manager.py +8 -9
- griptape_nodes/retained_mode/managers/event_manager.py +215 -74
- griptape_nodes/retained_mode/managers/flow_manager.py +39 -33
- griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +14 -14
- griptape_nodes/retained_mode/managers/library_lifecycle/library_fsm.py +20 -20
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/base.py +1 -1
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/github.py +1 -1
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +4 -3
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/package.py +1 -1
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/sandbox.py +1 -1
- griptape_nodes/retained_mode/managers/library_manager.py +20 -19
- griptape_nodes/retained_mode/managers/node_manager.py +83 -8
- griptape_nodes/retained_mode/managers/object_manager.py +4 -0
- griptape_nodes/retained_mode/managers/settings.py +1 -0
- griptape_nodes/retained_mode/managers/sync_manager.py +3 -9
- griptape_nodes/retained_mode/managers/variable_manager.py +529 -0
- griptape_nodes/retained_mode/managers/workflow_manager.py +156 -50
- griptape_nodes/retained_mode/variable_types.py +18 -0
- griptape_nodes/utils/__init__.py +4 -0
- griptape_nodes/utils/async_utils.py +89 -0
- {griptape_nodes-0.51.2.dist-info → griptape_nodes-0.52.0.dist-info}/METADATA +2 -3
- {griptape_nodes-0.51.2.dist-info → griptape_nodes-0.52.0.dist-info}/RECORD +45 -42
- {griptape_nodes-0.51.2.dist-info → griptape_nodes-0.52.0.dist-info}/WHEEL +1 -1
- griptape_nodes/bootstrap/workflow_executors/subprocess_workflow_executor.py +0 -90
- {griptape_nodes-0.51.2.dist-info → griptape_nodes-0.52.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from queue import Queue
|
|
3
2
|
from typing import Any
|
|
4
3
|
|
|
5
|
-
from griptape.events import BaseEvent, EventBus, EventListener
|
|
6
|
-
|
|
7
4
|
from griptape_nodes.bootstrap.workflow_executors.workflow_executor import WorkflowExecutor
|
|
8
5
|
from griptape_nodes.drivers.storage import StorageBackend
|
|
9
6
|
from griptape_nodes.exe_types.node_types import EndNode, StartNode
|
|
10
7
|
from griptape_nodes.retained_mode.events.base_events import (
|
|
11
|
-
AppEvent,
|
|
12
8
|
EventRequest,
|
|
13
9
|
ExecutionGriptapeNodeEvent,
|
|
14
|
-
GriptapeNodeEvent,
|
|
15
|
-
ProgressEvent,
|
|
16
10
|
)
|
|
17
|
-
from griptape_nodes.retained_mode.events.execution_events import
|
|
11
|
+
from griptape_nodes.retained_mode.events.execution_events import StartFlowRequest
|
|
18
12
|
from griptape_nodes.retained_mode.events.parameter_events import SetParameterValueRequest
|
|
13
|
+
from griptape_nodes.retained_mode.events.workflow_events import (
|
|
14
|
+
RunWorkflowFromScratchRequest,
|
|
15
|
+
)
|
|
19
16
|
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
20
17
|
|
|
21
18
|
logger = logging.getLogger(__name__)
|
|
@@ -26,10 +23,6 @@ class LocalExecutorError(Exception):
|
|
|
26
23
|
|
|
27
24
|
|
|
28
25
|
class LocalWorkflowExecutor(WorkflowExecutor):
|
|
29
|
-
def __init__(self) -> None:
|
|
30
|
-
self.queue = Queue()
|
|
31
|
-
self.output: dict | None = None
|
|
32
|
-
|
|
33
26
|
def _load_flow_for_workflow(self) -> str:
|
|
34
27
|
try:
|
|
35
28
|
context_manager = GriptapeNodes.ContextManager()
|
|
@@ -53,69 +46,10 @@ class LocalWorkflowExecutor(WorkflowExecutor):
|
|
|
53
46
|
logger.exception(msg)
|
|
54
47
|
raise LocalExecutorError(msg) from e
|
|
55
48
|
|
|
56
|
-
def _handle_event(self, event: BaseEvent) -> None:
|
|
57
|
-
try:
|
|
58
|
-
match event:
|
|
59
|
-
case GriptapeNodeEvent():
|
|
60
|
-
self.__handle_node_event(event)
|
|
61
|
-
case ExecutionGriptapeNodeEvent():
|
|
62
|
-
self.__handle_execution_node_event(event)
|
|
63
|
-
case ProgressEvent():
|
|
64
|
-
self.__handle_progress_event(event)
|
|
65
|
-
case AppEvent():
|
|
66
|
-
self.__handle_app_event(event)
|
|
67
|
-
case _:
|
|
68
|
-
msg = f"Unknown event type: {type(event)}"
|
|
69
|
-
logger.info(msg)
|
|
70
|
-
self.queue.put(event)
|
|
71
|
-
except Exception as e:
|
|
72
|
-
logger.info(e)
|
|
73
|
-
|
|
74
|
-
def __handle_node_event(self, event: GriptapeNodeEvent) -> None:
|
|
75
|
-
result_event = event.wrapped_event
|
|
76
|
-
event_json = result_event.json()
|
|
77
|
-
event_log = f"GriptapeNodeEvent: {event_json}"
|
|
78
|
-
logger.info(event_log)
|
|
79
|
-
|
|
80
|
-
def __handle_execution_node_event(self, event: ExecutionGriptapeNodeEvent) -> None:
|
|
81
|
-
result_event = event.wrapped_event
|
|
82
|
-
if type(result_event.payload).__name__ == "NodeStartProcessEvent":
|
|
83
|
-
event_log = f"NodeStartProcessEvent: {result_event.payload}"
|
|
84
|
-
logger.info(event_log)
|
|
85
|
-
|
|
86
|
-
elif type(result_event.payload).__name__ == "ResumeNodeProcessingEvent":
|
|
87
|
-
event_log = f"ResumeNodeProcessingEvent: {result_event.payload}"
|
|
88
|
-
logger.info(event_log)
|
|
89
|
-
|
|
90
|
-
# Here we need to handle the resume event since this is the callback mechanism
|
|
91
|
-
# for the flow to be resumed for any Node that yields a generator in its process method.
|
|
92
|
-
node_name = result_event.payload.node_name
|
|
93
|
-
flow_name = GriptapeNodes.NodeManager().get_node_parent_flow_by_name(node_name)
|
|
94
|
-
event_request = EventRequest(request=SingleExecutionStepRequest(flow_name=flow_name))
|
|
95
|
-
self.queue.put(event_request)
|
|
96
|
-
|
|
97
|
-
elif type(result_event.payload).__name__ == "NodeFinishProcessEvent":
|
|
98
|
-
event_log = f"NodeFinishProcessEvent: {result_event.payload}"
|
|
99
|
-
logger.info(event_log)
|
|
100
|
-
|
|
101
|
-
else:
|
|
102
|
-
event_log = f"ExecutionGriptapeNodeEvent: {result_event.payload}"
|
|
103
|
-
logger.info(event_log)
|
|
104
|
-
|
|
105
|
-
self.queue.put(event)
|
|
106
|
-
|
|
107
|
-
def __handle_progress_event(self, gt_event: ProgressEvent) -> None:
|
|
108
|
-
event_log = f"ProgressEvent: {gt_event}"
|
|
109
|
-
logger.info(event_log)
|
|
110
|
-
|
|
111
|
-
def __handle_app_event(self, event: AppEvent) -> None:
|
|
112
|
-
event_log = f"AppEvent: {event.payload}"
|
|
113
|
-
logger.info(event_log)
|
|
114
|
-
|
|
115
49
|
def _submit_output(self, output: dict) -> None:
|
|
116
50
|
self.output = output
|
|
117
51
|
|
|
118
|
-
def _set_input_for_flow(self, flow_name: str, flow_input: dict[str, dict]) -> None:
|
|
52
|
+
async def _set_input_for_flow(self, flow_name: str, flow_input: dict[str, dict]) -> None:
|
|
119
53
|
control_flow = GriptapeNodes.FlowManager().get_flow_by_name(flow_name)
|
|
120
54
|
nodes = control_flow.nodes
|
|
121
55
|
for node_name, node in nodes.items():
|
|
@@ -128,7 +62,7 @@ class LocalWorkflowExecutor(WorkflowExecutor):
|
|
|
128
62
|
value=parameter_value,
|
|
129
63
|
node_name=node_name,
|
|
130
64
|
)
|
|
131
|
-
set_parameter_value_result = GriptapeNodes.
|
|
65
|
+
set_parameter_value_result = await GriptapeNodes.ahandle_request(set_parameter_value_request)
|
|
132
66
|
|
|
133
67
|
if set_parameter_value_result.failed():
|
|
134
68
|
msg = f"Failed to set parameter {parameter_name} for node {node_name}."
|
|
@@ -144,12 +78,54 @@ class LocalWorkflowExecutor(WorkflowExecutor):
|
|
|
144
78
|
|
|
145
79
|
return output
|
|
146
80
|
|
|
147
|
-
def
|
|
81
|
+
async def _load_workflow_from_path(self, workflow_path: str) -> None:
|
|
82
|
+
"""Load a workflow from a file path."""
|
|
83
|
+
|
|
84
|
+
def _raise_load_error(msg: str) -> None:
|
|
85
|
+
raise LocalExecutorError(msg)
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
# Use the RunWorkflowFromScratchRequest to load the workflow
|
|
89
|
+
request = RunWorkflowFromScratchRequest(file_path=workflow_path)
|
|
90
|
+
result = await GriptapeNodes.ahandle_request(request)
|
|
91
|
+
|
|
92
|
+
logger.info("Successfully loaded workflow from %s", workflow_path)
|
|
93
|
+
except Exception as e:
|
|
94
|
+
msg = f"Error loading workflow from path {workflow_path}: {e}"
|
|
95
|
+
logger.exception(msg)
|
|
96
|
+
raise LocalExecutorError(msg) from e
|
|
97
|
+
|
|
98
|
+
if result.failed():
|
|
99
|
+
msg = f"Failed to load workflow from path {workflow_path}"
|
|
100
|
+
_raise_load_error(msg)
|
|
101
|
+
|
|
102
|
+
async def _handle_event_request(self, event: EventRequest) -> None:
|
|
103
|
+
"""Handle EventRequest objects by processing them through GriptapeNodes."""
|
|
104
|
+
await GriptapeNodes.ahandle_request(event.request)
|
|
105
|
+
|
|
106
|
+
async def _handle_execution_event(
|
|
107
|
+
self, event: ExecutionGriptapeNodeEvent, flow_name: str
|
|
108
|
+
) -> tuple[bool, Exception | None]:
|
|
109
|
+
"""Handle ExecutionGriptapeNodeEvent and return (is_finished, error)."""
|
|
110
|
+
result_event = event.wrapped_event
|
|
111
|
+
|
|
112
|
+
if type(result_event.payload).__name__ == "ControlFlowResolvedEvent":
|
|
113
|
+
self._submit_output(self._get_output_for_flow(flow_name=flow_name))
|
|
114
|
+
logger.info("Workflow finished!")
|
|
115
|
+
return True, None
|
|
116
|
+
if type(result_event.payload).__name__ == "ControlFlowCancelledEvent":
|
|
117
|
+
msg = "Control flow cancelled"
|
|
118
|
+
logger.error(msg)
|
|
119
|
+
return True, LocalExecutorError(msg)
|
|
120
|
+
|
|
121
|
+
return False, None
|
|
122
|
+
|
|
123
|
+
async def arun(
|
|
148
124
|
self,
|
|
149
125
|
workflow_name: str,
|
|
150
126
|
flow_input: Any,
|
|
151
127
|
storage_backend: StorageBackend = StorageBackend.LOCAL,
|
|
152
|
-
**kwargs: Any,
|
|
128
|
+
**kwargs: Any,
|
|
153
129
|
) -> None:
|
|
154
130
|
"""Executes a local workflow.
|
|
155
131
|
|
|
@@ -165,23 +141,24 @@ class LocalWorkflowExecutor(WorkflowExecutor):
|
|
|
165
141
|
None
|
|
166
142
|
"""
|
|
167
143
|
logger.info("Executing workflow: %s", workflow_name)
|
|
168
|
-
|
|
169
|
-
EventBus.add_event_listener(
|
|
170
|
-
event_listener=EventListener(
|
|
171
|
-
on_event=self._handle_event,
|
|
172
|
-
)
|
|
173
|
-
)
|
|
144
|
+
GriptapeNodes.EventManager().initialize_queue()
|
|
174
145
|
|
|
175
146
|
# Set the storage backend
|
|
176
147
|
self._set_storage_backend(storage_backend=storage_backend)
|
|
148
|
+
|
|
149
|
+
# Load workflow from file if workflow_path is provided
|
|
150
|
+
workflow_path = kwargs.get("workflow_path")
|
|
151
|
+
if workflow_path:
|
|
152
|
+
await self._load_workflow_from_path(workflow_path)
|
|
153
|
+
|
|
177
154
|
# Load the flow
|
|
178
155
|
flow_name = self._load_flow_for_workflow()
|
|
179
156
|
# Now let's set the input to the flow
|
|
180
|
-
self._set_input_for_flow(flow_name=flow_name, flow_input=flow_input)
|
|
157
|
+
await self._set_input_for_flow(flow_name=flow_name, flow_input=flow_input)
|
|
181
158
|
|
|
182
159
|
# Now send the run command to actually execute it
|
|
183
160
|
start_flow_request = StartFlowRequest(flow_name=flow_name)
|
|
184
|
-
start_flow_result = GriptapeNodes.
|
|
161
|
+
start_flow_result = await GriptapeNodes.ahandle_request(start_flow_request)
|
|
185
162
|
|
|
186
163
|
if start_flow_result.failed():
|
|
187
164
|
msg = f"Failed to start flow {flow_name}"
|
|
@@ -192,32 +169,18 @@ class LocalWorkflowExecutor(WorkflowExecutor):
|
|
|
192
169
|
# Wait for the control flow to finish
|
|
193
170
|
is_flow_finished = False
|
|
194
171
|
error: Exception | None = None
|
|
172
|
+
|
|
173
|
+
event_queue = GriptapeNodes.EventManager().event_queue
|
|
195
174
|
while not is_flow_finished:
|
|
196
175
|
try:
|
|
197
|
-
event =
|
|
176
|
+
event = await event_queue.get()
|
|
198
177
|
|
|
199
178
|
if isinstance(event, EventRequest):
|
|
200
|
-
|
|
201
|
-
request_payload = event.request
|
|
202
|
-
GriptapeNodes.handle_request(
|
|
203
|
-
request_payload, response_topic=event.response_topic, request_id=event.request_id
|
|
204
|
-
)
|
|
179
|
+
await self._handle_event_request(event)
|
|
205
180
|
elif isinstance(event, ExecutionGriptapeNodeEvent):
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
self._submit_output(self._get_output_for_flow(flow_name=flow_name))
|
|
210
|
-
is_flow_finished = True
|
|
211
|
-
logger.info("Workflow finished!")
|
|
212
|
-
elif type(result_event.payload).__name__ == "ControlFlowCancelledEvent":
|
|
213
|
-
msg = "Control flow cancelled"
|
|
214
|
-
is_flow_finished = True
|
|
215
|
-
logger.error(msg)
|
|
216
|
-
error = LocalExecutorError(msg)
|
|
217
|
-
else:
|
|
218
|
-
logger.info("Unknown event type encountered: %s", type(event))
|
|
219
|
-
|
|
220
|
-
self.queue.task_done()
|
|
181
|
+
is_flow_finished, error = await self._handle_execution_event(event, flow_name)
|
|
182
|
+
|
|
183
|
+
event_queue.task_done()
|
|
221
184
|
|
|
222
185
|
except Exception as e:
|
|
223
186
|
msg = f"Error handling queue event: {e}"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import logging
|
|
2
|
-
from abc import
|
|
3
|
+
from abc import abstractmethod
|
|
3
4
|
from typing import Any
|
|
4
5
|
|
|
5
6
|
from griptape_nodes.drivers.storage import StorageBackend
|
|
@@ -7,8 +8,10 @@ from griptape_nodes.drivers.storage import StorageBackend
|
|
|
7
8
|
logger = logging.getLogger(__name__)
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
class WorkflowExecutor
|
|
11
|
-
|
|
11
|
+
class WorkflowExecutor:
|
|
12
|
+
def __init__(self) -> None:
|
|
13
|
+
self.output: dict | None = None
|
|
14
|
+
|
|
12
15
|
def run(
|
|
13
16
|
self,
|
|
14
17
|
workflow_name: str,
|
|
@@ -16,4 +19,13 @@ class WorkflowExecutor(ABC):
|
|
|
16
19
|
storage_backend: StorageBackend = StorageBackend.LOCAL,
|
|
17
20
|
**kwargs: Any,
|
|
18
21
|
) -> None:
|
|
19
|
-
|
|
22
|
+
return asyncio.run(self.arun(workflow_name, flow_input, storage_backend, **kwargs))
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
async def arun(
|
|
26
|
+
self,
|
|
27
|
+
workflow_name: str,
|
|
28
|
+
flow_input: Any,
|
|
29
|
+
storage_backend: StorageBackend = StorageBackend.LOCAL,
|
|
30
|
+
**kwargs: Any,
|
|
31
|
+
) -> None: ...
|
|
@@ -12,6 +12,7 @@ if TYPE_CHECKING:
|
|
|
12
12
|
from types import TracebackType
|
|
13
13
|
|
|
14
14
|
from griptape_nodes.exe_types.node_types import BaseNode
|
|
15
|
+
|
|
15
16
|
T = TypeVar("T", bound="Parameter")
|
|
16
17
|
N = TypeVar("N", bound="BaseNodeElement")
|
|
17
18
|
|
|
@@ -253,11 +254,12 @@ class BaseNodeElement:
|
|
|
253
254
|
|
|
254
255
|
def _emit_alter_element_event_if_possible(self) -> None:
|
|
255
256
|
"""Emit an AlterElementEvent if we have node context and the necessary dependencies."""
|
|
257
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
258
|
+
|
|
256
259
|
if self._node_context is None:
|
|
257
260
|
return
|
|
258
261
|
|
|
259
262
|
# Import here to avoid circular dependencies
|
|
260
|
-
from griptape.events import EventBus
|
|
261
263
|
|
|
262
264
|
from griptape_nodes.retained_mode.events.base_events import ExecutionEvent, ExecutionGriptapeNodeEvent
|
|
263
265
|
from griptape_nodes.retained_mode.events.parameter_events import AlterElementEvent
|
|
@@ -281,7 +283,8 @@ class BaseNodeElement:
|
|
|
281
283
|
event = ExecutionGriptapeNodeEvent(
|
|
282
284
|
wrapped_event=ExecutionEvent(payload=AlterElementEvent(element_details=event_data))
|
|
283
285
|
)
|
|
284
|
-
|
|
286
|
+
|
|
287
|
+
GriptapeNodes.EventManager().put_event(event)
|
|
285
288
|
self._changes.clear()
|
|
286
289
|
|
|
287
290
|
def to_dict(self) -> dict[str, Any]:
|
|
@@ -712,7 +715,7 @@ class Parameter(BaseNodeElement, UIOptionsMixin):
|
|
|
712
715
|
|
|
713
716
|
# "settable" here means whether it can be assigned to during regular business operation.
|
|
714
717
|
# During save/load, this value IS still serialized to save its proper state.
|
|
715
|
-
|
|
718
|
+
_settable: bool = True
|
|
716
719
|
|
|
717
720
|
# "serializable" controls whether parameter values should be serialized during save/load operations.
|
|
718
721
|
# Set to False for parameters containing non-serializable types (ImageDrivers, PromptDrivers, file handles, etc.)
|
|
@@ -768,7 +771,7 @@ class Parameter(BaseNodeElement, UIOptionsMixin):
|
|
|
768
771
|
self.tooltip_as_input = tooltip_as_input
|
|
769
772
|
self.tooltip_as_property = tooltip_as_property
|
|
770
773
|
self.tooltip_as_output = tooltip_as_output
|
|
771
|
-
self.
|
|
774
|
+
self._settable = settable
|
|
772
775
|
self.serializable = serializable
|
|
773
776
|
self.user_defined = user_defined
|
|
774
777
|
if allowed_modes is None:
|
|
@@ -909,6 +912,15 @@ class Parameter(BaseNodeElement, UIOptionsMixin):
|
|
|
909
912
|
self._changes["mode_allowed_output"] = ParameterMode.OUTPUT in value
|
|
910
913
|
self._changes["mode_allowed_property"] = ParameterMode.PROPERTY in value
|
|
911
914
|
|
|
915
|
+
@property
|
|
916
|
+
def settable(self) -> bool:
|
|
917
|
+
return self._settable
|
|
918
|
+
|
|
919
|
+
@settable.setter
|
|
920
|
+
@BaseNodeElement.emits_update_on_write
|
|
921
|
+
def settable(self, value: bool) -> None:
|
|
922
|
+
self._settable = value
|
|
923
|
+
|
|
912
924
|
@property
|
|
913
925
|
def ui_options(self) -> dict:
|
|
914
926
|
ui_options = {}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
3
4
|
import logging
|
|
4
5
|
from abc import ABC, abstractmethod
|
|
5
6
|
from collections.abc import Callable, Generator, Iterable
|
|
7
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
6
8
|
from enum import StrEnum, auto
|
|
7
9
|
from typing import Any, NamedTuple, TypeVar
|
|
8
10
|
|
|
9
|
-
from griptape.events import BaseEvent, EventBus
|
|
10
|
-
|
|
11
11
|
from griptape_nodes.exe_types.core_types import (
|
|
12
12
|
BaseNodeElement,
|
|
13
13
|
ControlParameterInput,
|
|
@@ -120,11 +120,14 @@ class BaseNode(ABC):
|
|
|
120
120
|
# 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!
|
|
121
121
|
# https://github.com/griptape-ai/griptape-nodes/issues/994
|
|
122
122
|
def make_node_unresolved(self, current_states_to_trigger_change_event: set[NodeResolutionState] | None) -> None:
|
|
123
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
124
|
+
|
|
123
125
|
# See if the current state is in the set of states to trigger a change event.
|
|
124
126
|
if current_states_to_trigger_change_event is not None and self.state in current_states_to_trigger_change_event:
|
|
125
127
|
# Trigger the change event.
|
|
126
128
|
# Send an event to the GUI so it knows this node has changed resolution state.
|
|
127
|
-
|
|
129
|
+
|
|
130
|
+
GriptapeNodes.EventManager().put_event(
|
|
128
131
|
ExecutionGriptapeNodeEvent(
|
|
129
132
|
wrapped_event=ExecutionEvent(payload=NodeUnresolvedEvent(node_name=self.name))
|
|
130
133
|
)
|
|
@@ -284,10 +287,6 @@ class BaseNode(ABC):
|
|
|
284
287
|
# Waiting for https://github.com/griptape-ai/griptape-nodes/issues/1309
|
|
285
288
|
return
|
|
286
289
|
|
|
287
|
-
def on_griptape_event(self, event: BaseEvent) -> None: # noqa: ARG002
|
|
288
|
-
"""Callback for when a Griptape Event comes destined for this Node."""
|
|
289
|
-
return
|
|
290
|
-
|
|
291
290
|
def on_node_message_received(
|
|
292
291
|
self,
|
|
293
292
|
optional_element_name: str | None, # noqa: ARG002
|
|
@@ -435,6 +434,9 @@ class BaseNode(ABC):
|
|
|
435
434
|
if traits:
|
|
436
435
|
trait = traits[0] # Take the first Options trait
|
|
437
436
|
trait.choices = choices
|
|
437
|
+
# Update the manually set UI options to include the new simple_dropdown
|
|
438
|
+
if hasattr(parameter, "_ui_options") and parameter._ui_options:
|
|
439
|
+
parameter._ui_options["simple_dropdown"] = choices
|
|
438
440
|
|
|
439
441
|
if default in choices:
|
|
440
442
|
parameter.default_value = default
|
|
@@ -443,9 +445,6 @@ class BaseNode(ABC):
|
|
|
443
445
|
msg = f"Default model '{default}' is not in the provided choices."
|
|
444
446
|
raise ValueError(msg)
|
|
445
447
|
|
|
446
|
-
# Update the manually set UI options to include the new simple_dropdown
|
|
447
|
-
if hasattr(parameter, "_ui_options") and parameter._ui_options:
|
|
448
|
-
parameter._ui_options["simple_dropdown"] = choices
|
|
449
448
|
else:
|
|
450
449
|
msg = f"No Options trait found for parameter '{param}'."
|
|
451
450
|
raise ValueError(msg)
|
|
@@ -565,7 +564,13 @@ class BaseNode(ABC):
|
|
|
565
564
|
return None
|
|
566
565
|
|
|
567
566
|
def set_parameter_value(
|
|
568
|
-
self,
|
|
567
|
+
self,
|
|
568
|
+
param_name: str,
|
|
569
|
+
value: Any,
|
|
570
|
+
*,
|
|
571
|
+
initial_setup: bool = False,
|
|
572
|
+
emit_change: bool = True,
|
|
573
|
+
skip_before_value_set: bool = False,
|
|
569
574
|
) -> None:
|
|
570
575
|
"""Attempt to set a Parameter's value.
|
|
571
576
|
|
|
@@ -586,6 +591,7 @@ class BaseNode(ABC):
|
|
|
586
591
|
value: the value intended to be set
|
|
587
592
|
emit_change: whether to emit a parameter lifecycle event, defaults to True
|
|
588
593
|
initial_setup: Whether this value is being set as the initial setup on the node, defaults to False. When True, the value is not given to any before/after hooks.
|
|
594
|
+
skip_before_value_set: Whether to skip the before_value_set hook, defaults to False. Used when before_value_set has already been called earlier in the flow.
|
|
589
595
|
|
|
590
596
|
Returns:
|
|
591
597
|
A set of parameter names within this node that were modified as a result
|
|
@@ -614,7 +620,10 @@ class BaseNode(ABC):
|
|
|
614
620
|
# Allow custom node logic to prepare and possibly mutate the value before it is actually set.
|
|
615
621
|
# Record any parameters modified for cascading.
|
|
616
622
|
if not initial_setup:
|
|
617
|
-
|
|
623
|
+
if not skip_before_value_set:
|
|
624
|
+
final_value = self.before_value_set(parameter=parameter, value=candidate_value)
|
|
625
|
+
else:
|
|
626
|
+
final_value = candidate_value
|
|
618
627
|
# ACTUALLY SET THE NEW VALUE
|
|
619
628
|
self.parameter_values[param_name] = final_value
|
|
620
629
|
|
|
@@ -710,6 +719,41 @@ class BaseNode(ABC):
|
|
|
710
719
|
def process[T](self) -> AsyncResult | None:
|
|
711
720
|
pass
|
|
712
721
|
|
|
722
|
+
async def aprocess(self) -> None:
|
|
723
|
+
"""Async version of process().
|
|
724
|
+
|
|
725
|
+
Default implementation wraps the existing process() method to maintain backwards compatibility.
|
|
726
|
+
Subclasses can override this method to provide direct async implementation.
|
|
727
|
+
"""
|
|
728
|
+
result = self.process()
|
|
729
|
+
|
|
730
|
+
if result is None:
|
|
731
|
+
# Simple synchronous node - nothing to do
|
|
732
|
+
return
|
|
733
|
+
|
|
734
|
+
if isinstance(result, Generator):
|
|
735
|
+
# Handle generator pattern asynchronously using the same logic as before
|
|
736
|
+
loop = asyncio.get_running_loop()
|
|
737
|
+
|
|
738
|
+
try:
|
|
739
|
+
# Start the generator
|
|
740
|
+
func = next(result)
|
|
741
|
+
|
|
742
|
+
while True:
|
|
743
|
+
# Run callable in thread pool (preserving existing behavior)
|
|
744
|
+
with ThreadPoolExecutor() as executor:
|
|
745
|
+
future_result = await loop.run_in_executor(executor, func)
|
|
746
|
+
|
|
747
|
+
# Send result back and get next callable
|
|
748
|
+
func = result.send(future_result)
|
|
749
|
+
|
|
750
|
+
except StopIteration:
|
|
751
|
+
# Generator is done
|
|
752
|
+
return
|
|
753
|
+
else:
|
|
754
|
+
# Some other return type - log warning but continue
|
|
755
|
+
logger.warning("Node %s process() returned unexpected type: %s", self.name, type(result))
|
|
756
|
+
|
|
713
757
|
# if not implemented, it will return no issues.
|
|
714
758
|
def validate_before_workflow_run(self) -> list[Exception] | None:
|
|
715
759
|
"""Runs before the entire workflow is run."""
|
|
@@ -768,6 +812,8 @@ class BaseNode(ABC):
|
|
|
768
812
|
self.current_spotlight_parameter = None
|
|
769
813
|
|
|
770
814
|
def append_value_to_parameter(self, parameter_name: str, value: Any) -> None:
|
|
815
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
816
|
+
|
|
771
817
|
# Add the value to the node
|
|
772
818
|
if parameter_name in self.parameter_output_values:
|
|
773
819
|
try:
|
|
@@ -781,9 +827,14 @@ class BaseNode(ABC):
|
|
|
781
827
|
else:
|
|
782
828
|
self.parameter_output_values[parameter_name] = value
|
|
783
829
|
# Publish the event up!
|
|
784
|
-
|
|
830
|
+
|
|
831
|
+
GriptapeNodes.EventManager().put_event(
|
|
832
|
+
ProgressEvent(value=value, node_name=self.name, parameter_name=parameter_name)
|
|
833
|
+
)
|
|
785
834
|
|
|
786
835
|
def publish_update_to_parameter(self, parameter_name: str, value: Any) -> None:
|
|
836
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
837
|
+
|
|
787
838
|
parameter = self.get_parameter_by_name(parameter_name)
|
|
788
839
|
if parameter:
|
|
789
840
|
data_type = parameter.type
|
|
@@ -794,7 +845,10 @@ class BaseNode(ABC):
|
|
|
794
845
|
data_type=data_type,
|
|
795
846
|
value=TypeValidator.safe_serialize(value),
|
|
796
847
|
)
|
|
797
|
-
|
|
848
|
+
|
|
849
|
+
GriptapeNodes.EventManager().put_event(
|
|
850
|
+
ExecutionGriptapeNodeEvent(wrapped_event=ExecutionEvent(payload=payload))
|
|
851
|
+
)
|
|
798
852
|
else:
|
|
799
853
|
msg = f"Parameter '{parameter_name} doesn't exist on {self.name}'"
|
|
800
854
|
raise RuntimeError(msg)
|
|
@@ -907,6 +961,7 @@ class BaseNode(ABC):
|
|
|
907
961
|
"""Emit an AlterElementEvent for parameter add/remove operations."""
|
|
908
962
|
from griptape_nodes.retained_mode.events.base_events import ExecutionEvent, ExecutionGriptapeNodeEvent
|
|
909
963
|
from griptape_nodes.retained_mode.events.parameter_events import AlterElementEvent
|
|
964
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
910
965
|
|
|
911
966
|
# Create event data using the parameter's to_event method
|
|
912
967
|
if remove:
|
|
@@ -920,7 +975,8 @@ class BaseNode(ABC):
|
|
|
920
975
|
event = ExecutionGriptapeNodeEvent(
|
|
921
976
|
wrapped_event=ExecutionEvent(payload=AlterElementEvent(element_details=event_data))
|
|
922
977
|
)
|
|
923
|
-
|
|
978
|
+
|
|
979
|
+
GriptapeNodes.EventManager().put_event(event)
|
|
924
980
|
|
|
925
981
|
def _get_element_name(self, element: str | int, element_names: list[str]) -> str:
|
|
926
982
|
"""Convert an element identifier (name or index) to its name.
|
|
@@ -1061,6 +1117,7 @@ class TrackedParameterOutputValues(dict[str, Any]):
|
|
|
1061
1117
|
if parameter is not None:
|
|
1062
1118
|
from griptape_nodes.retained_mode.events.base_events import ExecutionEvent, ExecutionGriptapeNodeEvent
|
|
1063
1119
|
from griptape_nodes.retained_mode.events.parameter_events import AlterElementEvent
|
|
1120
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
1064
1121
|
|
|
1065
1122
|
# Create event data using the parameter's to_event method
|
|
1066
1123
|
event_data = parameter.to_event(self._node)
|
|
@@ -1073,7 +1130,8 @@ class TrackedParameterOutputValues(dict[str, Any]):
|
|
|
1073
1130
|
event = ExecutionGriptapeNodeEvent(
|
|
1074
1131
|
wrapped_event=ExecutionEvent(payload=AlterElementEvent(element_details=event_data))
|
|
1075
1132
|
)
|
|
1076
|
-
|
|
1133
|
+
|
|
1134
|
+
GriptapeNodes.EventManager().put_event(event)
|
|
1077
1135
|
|
|
1078
1136
|
|
|
1079
1137
|
class ControlNode(BaseNode):
|