griptape-nodes 0.41.0__py3-none-any.whl → 0.43.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 +0 -0
- griptape_nodes/app/.python-version +0 -0
- griptape_nodes/app/__init__.py +1 -10
- griptape_nodes/app/api.py +199 -0
- griptape_nodes/app/app.py +140 -222
- griptape_nodes/app/watch.py +4 -2
- griptape_nodes/bootstrap/__init__.py +0 -0
- griptape_nodes/bootstrap/bootstrap_script.py +0 -0
- griptape_nodes/bootstrap/register_libraries_script.py +0 -0
- griptape_nodes/bootstrap/structure_config.yaml +0 -0
- griptape_nodes/bootstrap/workflow_executors/__init__.py +0 -0
- griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py +0 -0
- griptape_nodes/bootstrap/workflow_executors/workflow_executor.py +0 -0
- griptape_nodes/bootstrap/workflow_runners/__init__.py +0 -0
- griptape_nodes/bootstrap/workflow_runners/bootstrap_workflow_runner.py +0 -0
- griptape_nodes/bootstrap/workflow_runners/local_workflow_runner.py +0 -0
- griptape_nodes/bootstrap/workflow_runners/subprocess_workflow_runner.py +6 -2
- griptape_nodes/bootstrap/workflow_runners/workflow_runner.py +0 -0
- griptape_nodes/drivers/__init__.py +0 -0
- griptape_nodes/drivers/storage/__init__.py +0 -0
- griptape_nodes/drivers/storage/base_storage_driver.py +0 -0
- griptape_nodes/drivers/storage/griptape_cloud_storage_driver.py +0 -0
- griptape_nodes/drivers/storage/local_storage_driver.py +5 -3
- griptape_nodes/drivers/storage/storage_backend.py +0 -0
- griptape_nodes/exe_types/__init__.py +0 -0
- griptape_nodes/exe_types/connections.py +0 -0
- griptape_nodes/exe_types/core_types.py +0 -0
- griptape_nodes/exe_types/flow.py +68 -368
- griptape_nodes/exe_types/node_types.py +17 -1
- griptape_nodes/exe_types/type_validator.py +0 -0
- griptape_nodes/machines/__init__.py +0 -0
- griptape_nodes/machines/control_flow.py +52 -20
- griptape_nodes/machines/fsm.py +16 -2
- griptape_nodes/machines/node_resolution.py +16 -14
- griptape_nodes/mcp_server/__init__.py +1 -0
- griptape_nodes/mcp_server/server.py +126 -0
- griptape_nodes/mcp_server/ws_request_manager.py +268 -0
- griptape_nodes/node_library/__init__.py +0 -0
- griptape_nodes/node_library/advanced_node_library.py +0 -0
- griptape_nodes/node_library/library_registry.py +0 -0
- griptape_nodes/node_library/workflow_registry.py +2 -2
- griptape_nodes/py.typed +0 -0
- griptape_nodes/retained_mode/__init__.py +0 -0
- griptape_nodes/retained_mode/events/__init__.py +0 -0
- griptape_nodes/retained_mode/events/agent_events.py +70 -8
- griptape_nodes/retained_mode/events/app_events.py +137 -12
- griptape_nodes/retained_mode/events/arbitrary_python_events.py +23 -0
- griptape_nodes/retained_mode/events/base_events.py +13 -31
- griptape_nodes/retained_mode/events/config_events.py +87 -11
- griptape_nodes/retained_mode/events/connection_events.py +56 -5
- griptape_nodes/retained_mode/events/context_events.py +27 -4
- griptape_nodes/retained_mode/events/execution_events.py +99 -14
- griptape_nodes/retained_mode/events/flow_events.py +165 -7
- griptape_nodes/retained_mode/events/generate_request_payload_schemas.py +0 -0
- griptape_nodes/retained_mode/events/library_events.py +195 -17
- griptape_nodes/retained_mode/events/logger_events.py +11 -0
- griptape_nodes/retained_mode/events/node_events.py +242 -22
- griptape_nodes/retained_mode/events/object_events.py +40 -4
- griptape_nodes/retained_mode/events/os_events.py +116 -3
- griptape_nodes/retained_mode/events/parameter_events.py +212 -8
- griptape_nodes/retained_mode/events/payload_registry.py +0 -0
- griptape_nodes/retained_mode/events/secrets_events.py +59 -7
- griptape_nodes/retained_mode/events/static_file_events.py +57 -4
- griptape_nodes/retained_mode/events/validation_events.py +39 -4
- griptape_nodes/retained_mode/events/workflow_events.py +188 -17
- griptape_nodes/retained_mode/griptape_nodes.py +89 -363
- griptape_nodes/retained_mode/managers/__init__.py +0 -0
- griptape_nodes/retained_mode/managers/agent_manager.py +49 -23
- griptape_nodes/retained_mode/managers/arbitrary_code_exec_manager.py +0 -0
- griptape_nodes/retained_mode/managers/config_manager.py +0 -0
- griptape_nodes/retained_mode/managers/context_manager.py +0 -0
- griptape_nodes/retained_mode/managers/engine_identity_manager.py +146 -0
- griptape_nodes/retained_mode/managers/event_manager.py +14 -2
- griptape_nodes/retained_mode/managers/flow_manager.py +751 -64
- griptape_nodes/retained_mode/managers/library_lifecycle/__init__.py +45 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/data_models.py +191 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_directory.py +346 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_fsm.py +439 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/__init__.py +17 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/base.py +82 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/github.py +116 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/local_file.py +352 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/package.py +104 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance/sandbox.py +155 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_provenance.py +18 -0
- griptape_nodes/retained_mode/managers/library_lifecycle/library_status.py +12 -0
- griptape_nodes/retained_mode/managers/library_manager.py +255 -40
- griptape_nodes/retained_mode/managers/node_manager.py +120 -103
- griptape_nodes/retained_mode/managers/object_manager.py +11 -3
- griptape_nodes/retained_mode/managers/operation_manager.py +0 -0
- griptape_nodes/retained_mode/managers/os_manager.py +582 -8
- griptape_nodes/retained_mode/managers/secrets_manager.py +4 -0
- griptape_nodes/retained_mode/managers/session_manager.py +328 -0
- griptape_nodes/retained_mode/managers/settings.py +7 -0
- griptape_nodes/retained_mode/managers/static_files_manager.py +0 -0
- griptape_nodes/retained_mode/managers/version_compatibility_manager.py +2 -2
- griptape_nodes/retained_mode/managers/workflow_manager.py +722 -456
- griptape_nodes/retained_mode/retained_mode.py +44 -0
- griptape_nodes/retained_mode/utils/__init__.py +0 -0
- griptape_nodes/retained_mode/utils/engine_identity.py +141 -27
- griptape_nodes/retained_mode/utils/name_generator.py +0 -0
- griptape_nodes/traits/__init__.py +0 -0
- griptape_nodes/traits/add_param_button.py +0 -0
- griptape_nodes/traits/button.py +0 -0
- griptape_nodes/traits/clamp.py +0 -0
- griptape_nodes/traits/compare.py +0 -0
- griptape_nodes/traits/compare_images.py +0 -0
- griptape_nodes/traits/file_system_picker.py +127 -0
- griptape_nodes/traits/minmax.py +0 -0
- griptape_nodes/traits/options.py +0 -0
- griptape_nodes/traits/slider.py +0 -0
- griptape_nodes/traits/trait_registry.py +0 -0
- griptape_nodes/traits/traits.json +0 -0
- griptape_nodes/updater/__init__.py +2 -2
- griptape_nodes/updater/__main__.py +0 -0
- griptape_nodes/utils/__init__.py +0 -0
- griptape_nodes/utils/dict_utils.py +0 -0
- griptape_nodes/utils/image_preview.py +128 -0
- griptape_nodes/utils/metaclasses.py +0 -0
- griptape_nodes/version_compatibility/__init__.py +0 -0
- griptape_nodes/version_compatibility/versions/__init__.py +0 -0
- griptape_nodes/version_compatibility/versions/v0_39_0/__init__.py +0 -0
- griptape_nodes/version_compatibility/versions/v0_39_0/modified_parameters_set_removal.py +5 -5
- griptape_nodes-0.43.0.dist-info/METADATA +90 -0
- griptape_nodes-0.43.0.dist-info/RECORD +129 -0
- griptape_nodes-0.43.0.dist-info/WHEEL +4 -0
- {griptape_nodes-0.41.0.dist-info → griptape_nodes-0.43.0.dist-info}/entry_points.txt +1 -0
- griptape_nodes/app/app_sessions.py +0 -458
- griptape_nodes/retained_mode/utils/session_persistence.py +0 -105
- griptape_nodes-0.41.0.dist-info/METADATA +0 -78
- griptape_nodes-0.41.0.dist-info/RECORD +0 -112
- griptape_nodes-0.41.0.dist-info/WHEEL +0 -4
- griptape_nodes-0.41.0.dist-info/licenses/LICENSE +0 -201
griptape_nodes/exe_types/flow.py
CHANGED
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from queue import Queue
|
|
5
4
|
from typing import TYPE_CHECKING, NamedTuple
|
|
6
5
|
|
|
7
|
-
from griptape.events import EventBus
|
|
8
|
-
|
|
9
|
-
from griptape_nodes.exe_types.connections import Connections
|
|
10
|
-
from griptape_nodes.exe_types.core_types import ParameterTypeBuiltin
|
|
11
|
-
from griptape_nodes.exe_types.node_types import NodeResolutionState, StartLoopNode, StartNode
|
|
12
|
-
from griptape_nodes.machines.control_flow import CompleteState, ControlFlowMachine
|
|
13
|
-
from griptape_nodes.retained_mode.events.base_events import ExecutionEvent, ExecutionGriptapeNodeEvent
|
|
14
|
-
from griptape_nodes.retained_mode.events.execution_events import ControlFlowCancelledEvent
|
|
15
|
-
|
|
16
6
|
if TYPE_CHECKING:
|
|
7
|
+
from queue import Queue
|
|
8
|
+
|
|
17
9
|
from griptape_nodes.exe_types.core_types import Parameter
|
|
18
10
|
from griptape_nodes.exe_types.node_types import BaseNode
|
|
19
11
|
|
|
@@ -28,22 +20,16 @@ class CurrentNodes(NamedTuple):
|
|
|
28
20
|
current_resolving_node: str | None
|
|
29
21
|
|
|
30
22
|
|
|
31
|
-
# The flow will own all of the nodes
|
|
23
|
+
# The flow will own all of the nodes
|
|
32
24
|
class ControlFlow:
|
|
33
25
|
name: str
|
|
34
|
-
connections: Connections
|
|
35
26
|
nodes: dict[str, BaseNode]
|
|
36
|
-
|
|
37
|
-
single_node_resolution: bool
|
|
38
|
-
flow_queue: Queue[BaseNode]
|
|
27
|
+
metadata: dict
|
|
39
28
|
|
|
40
|
-
def __init__(self, name: str) -> None:
|
|
29
|
+
def __init__(self, name: str, metadata: dict | None = None) -> None:
|
|
41
30
|
self.name = name
|
|
42
|
-
self.connections = Connections()
|
|
43
31
|
self.nodes = {}
|
|
44
|
-
self.
|
|
45
|
-
self.single_node_resolution = False
|
|
46
|
-
self.flow_queue = Queue()
|
|
32
|
+
self.metadata = metadata or {}
|
|
47
33
|
|
|
48
34
|
def add_node(self, node: BaseNode) -> None:
|
|
49
35
|
self.nodes[node.name] = node
|
|
@@ -59,15 +45,25 @@ class ControlFlow:
|
|
|
59
45
|
target_parameter: Parameter,
|
|
60
46
|
) -> bool:
|
|
61
47
|
if source_node.name in self.nodes and target_node.name in self.nodes:
|
|
62
|
-
|
|
48
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
GriptapeNodes.FlowManager()
|
|
52
|
+
.get_connections()
|
|
53
|
+
.add_connection(source_node, source_parameter, target_node, target_parameter)
|
|
54
|
+
)
|
|
63
55
|
return False
|
|
64
56
|
|
|
65
57
|
def remove_connection(
|
|
66
58
|
self, source_node: BaseNode, source_parameter: Parameter, target_node: BaseNode, target_parameter: Parameter
|
|
67
59
|
) -> bool:
|
|
68
60
|
if source_node.name in self.nodes and target_node.name in self.nodes:
|
|
69
|
-
|
|
70
|
-
|
|
61
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
GriptapeNodes.FlowManager()
|
|
65
|
+
.get_connections()
|
|
66
|
+
.remove_connection(source_node.name, source_parameter.name, target_node.name, target_parameter.name)
|
|
71
67
|
)
|
|
72
68
|
return False
|
|
73
69
|
|
|
@@ -78,343 +74,24 @@ class ControlFlow:
|
|
|
78
74
|
target_node: BaseNode,
|
|
79
75
|
target_parameter: Parameter,
|
|
80
76
|
) -> bool:
|
|
81
|
-
|
|
82
|
-
connected_node_tuple = self.get_connected_output_parameters(node=source_node, param=source_parameter)
|
|
83
|
-
if connected_node_tuple is not None:
|
|
84
|
-
for connected_node_values in connected_node_tuple:
|
|
85
|
-
connected_node, connected_param = connected_node_values
|
|
86
|
-
if connected_node is target_node and connected_param is target_parameter:
|
|
87
|
-
return True
|
|
88
|
-
return False
|
|
77
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
89
78
|
|
|
90
|
-
|
|
91
|
-
if self.check_for_existing_running_flow():
|
|
92
|
-
# If flow already exists, throw an error
|
|
93
|
-
errormsg = "This workflow is already in progress. Please wait for the current process to finish before starting again."
|
|
94
|
-
raise RuntimeError(errormsg)
|
|
95
|
-
|
|
96
|
-
if start_node is None:
|
|
97
|
-
if self.flow_queue.empty():
|
|
98
|
-
errormsg = "No Flow exists. You must create at least one control connection."
|
|
99
|
-
raise RuntimeError(errormsg)
|
|
100
|
-
start_node = self.flow_queue.get()
|
|
101
|
-
|
|
102
|
-
try:
|
|
103
|
-
self.control_flow_machine.start_flow(start_node, debug_mode)
|
|
104
|
-
self.flow_queue.task_done()
|
|
105
|
-
except Exception:
|
|
106
|
-
if self.check_for_existing_running_flow():
|
|
107
|
-
self.cancel_flow_run()
|
|
108
|
-
raise
|
|
109
|
-
|
|
110
|
-
def check_for_existing_running_flow(self) -> bool:
|
|
111
|
-
if self.control_flow_machine._current_state is not CompleteState and self.control_flow_machine._current_state:
|
|
112
|
-
# Flow already exists in progress
|
|
113
|
-
return True
|
|
114
|
-
return bool(
|
|
115
|
-
not self.control_flow_machine._context.resolution_machine.is_complete()
|
|
116
|
-
and self.control_flow_machine._context.resolution_machine.is_started()
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
def resolve_singular_node(self, node: BaseNode, debug_mode: bool = False) -> None: # noqa: FBT001, FBT002
|
|
120
|
-
# Set that we are only working on one node right now! no other stepping allowed
|
|
121
|
-
if self.check_for_existing_running_flow():
|
|
122
|
-
# If flow already exists, throw an error
|
|
123
|
-
errormsg = f"This workflow is already in progress. Please wait for the current process to finish before starting {node.name} again."
|
|
124
|
-
raise RuntimeError(errormsg)
|
|
125
|
-
self.single_node_resolution = True
|
|
126
|
-
# Get the node resolution machine for the current flow!
|
|
127
|
-
self.control_flow_machine._context.current_node = node
|
|
128
|
-
resolution_machine = self.control_flow_machine._context.resolution_machine
|
|
129
|
-
# Set debug mode
|
|
130
|
-
resolution_machine.change_debug_mode(debug_mode)
|
|
131
|
-
# Resolve the node.
|
|
132
|
-
node.state = NodeResolutionState.UNRESOLVED
|
|
133
|
-
resolution_machine.resolve_node(node)
|
|
134
|
-
# decide if we can change it back to normal flow mode!
|
|
135
|
-
if resolution_machine.is_complete():
|
|
136
|
-
self.single_node_resolution = False
|
|
137
|
-
self.control_flow_machine._context.current_node = None
|
|
138
|
-
|
|
139
|
-
def single_execution_step(self, change_debug_mode: bool) -> None: # noqa: FBT001
|
|
140
|
-
# do a granular step
|
|
141
|
-
if not self.check_for_existing_running_flow():
|
|
142
|
-
if self.flow_queue.empty():
|
|
143
|
-
errormsg = "Flow has not yet been started. Cannot step while no flow has begun."
|
|
144
|
-
raise RuntimeError(errormsg)
|
|
145
|
-
start_node = self.flow_queue.get()
|
|
146
|
-
self.control_flow_machine.start_flow(start_node, debug_mode=True)
|
|
147
|
-
start_node = self.flow_queue.task_done()
|
|
148
|
-
return
|
|
149
|
-
self.control_flow_machine.granular_step(change_debug_mode)
|
|
150
|
-
resolution_machine = self.control_flow_machine._context.resolution_machine
|
|
151
|
-
if self.single_node_resolution:
|
|
152
|
-
resolution_machine = self.control_flow_machine._context.resolution_machine
|
|
153
|
-
if resolution_machine.is_complete():
|
|
154
|
-
self.single_node_resolution = False
|
|
155
|
-
|
|
156
|
-
def single_node_step(self) -> None:
|
|
157
|
-
# It won't call single_node_step without an existing flow running from US.
|
|
158
|
-
if not self.check_for_existing_running_flow():
|
|
159
|
-
if self.flow_queue.empty():
|
|
160
|
-
errormsg = "Flow has not yet been started. Cannot step while no flow has begun."
|
|
161
|
-
raise RuntimeError(errormsg)
|
|
162
|
-
start_node = self.flow_queue.get()
|
|
163
|
-
self.control_flow_machine.start_flow(start_node, debug_mode=True)
|
|
164
|
-
start_node = self.flow_queue.task_done()
|
|
165
|
-
return
|
|
166
|
-
# Step over a whole node
|
|
167
|
-
if self.single_node_resolution:
|
|
168
|
-
msg = "Cannot step through the Control Flow in Single Node Execution"
|
|
169
|
-
raise RuntimeError(msg)
|
|
170
|
-
self.control_flow_machine.node_step()
|
|
171
|
-
# Start the next resolution step now please.
|
|
172
|
-
if not self.check_for_existing_running_flow() and not self.flow_queue.empty():
|
|
173
|
-
start_node = self.flow_queue.get()
|
|
174
|
-
self.flow_queue.task_done()
|
|
175
|
-
self.control_flow_machine.start_flow(start_node, debug_mode=True)
|
|
176
|
-
|
|
177
|
-
def continue_executing(self) -> None:
|
|
178
|
-
if not self.check_for_existing_running_flow():
|
|
179
|
-
if self.flow_queue.empty():
|
|
180
|
-
errormsg = "Flow has not yet been started. Cannot step while no flow has begun."
|
|
181
|
-
raise RuntimeError(errormsg)
|
|
182
|
-
start_node = self.flow_queue.get()
|
|
183
|
-
self.flow_queue.task_done()
|
|
184
|
-
self.control_flow_machine.start_flow(start_node, debug_mode=False)
|
|
185
|
-
return
|
|
186
|
-
# Turn all debugging to false and continue on
|
|
187
|
-
self.control_flow_machine.change_debug_mode(False)
|
|
188
|
-
if self.single_node_resolution:
|
|
189
|
-
if self.control_flow_machine._context.resolution_machine.is_complete():
|
|
190
|
-
self.single_node_resolution = False
|
|
191
|
-
else:
|
|
192
|
-
self.control_flow_machine._context.resolution_machine.update()
|
|
193
|
-
else:
|
|
194
|
-
self.control_flow_machine.node_step()
|
|
195
|
-
# Now it is done executing. make sure it's actually done?
|
|
196
|
-
if not self.check_for_existing_running_flow() and not self.flow_queue.empty():
|
|
197
|
-
start_node = self.flow_queue.get()
|
|
198
|
-
self.flow_queue.task_done()
|
|
199
|
-
self.control_flow_machine.start_flow(start_node, debug_mode=False)
|
|
200
|
-
|
|
201
|
-
def cancel_flow_run(self) -> None:
|
|
202
|
-
if not self.check_for_existing_running_flow():
|
|
203
|
-
errormsg = "Flow has not yet been started. Cannot cancel flow that hasn't begun."
|
|
204
|
-
raise RuntimeError(errormsg)
|
|
205
|
-
self.clear_flow_queue()
|
|
206
|
-
self.control_flow_machine.reset_machine()
|
|
207
|
-
# Reset control flow machine
|
|
208
|
-
self.single_node_resolution = False
|
|
209
|
-
logger.debug("Cancelling flow run")
|
|
210
|
-
|
|
211
|
-
EventBus.publish_event(
|
|
212
|
-
ExecutionGriptapeNodeEvent(wrapped_event=ExecutionEvent(payload=ControlFlowCancelledEvent()))
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
def unresolve_whole_flow(self) -> None:
|
|
216
|
-
for node in self.nodes.values():
|
|
217
|
-
node.make_node_unresolved(current_states_to_trigger_change_event=None)
|
|
218
|
-
|
|
219
|
-
def flow_state(self) -> CurrentNodes:
|
|
220
|
-
if not self.check_for_existing_running_flow():
|
|
221
|
-
msg = "Flow hasn't started."
|
|
222
|
-
raise RuntimeError(msg)
|
|
223
|
-
current_control_node = (
|
|
224
|
-
self.control_flow_machine._context.current_node.name
|
|
225
|
-
if self.control_flow_machine._context.current_node is not None
|
|
226
|
-
else None
|
|
227
|
-
)
|
|
228
|
-
focus_stack_for_node = self.control_flow_machine._context.resolution_machine._context.focus_stack
|
|
229
|
-
current_resolving_node = focus_stack_for_node[-1].node.name if len(focus_stack_for_node) else None
|
|
230
|
-
return CurrentNodes(current_control_node, current_resolving_node)
|
|
231
|
-
|
|
232
|
-
def clear_flow_queue(self) -> None:
|
|
233
|
-
self.flow_queue.queue.clear()
|
|
79
|
+
return GriptapeNodes.FlowManager().has_connection(source_node, source_parameter, target_node, target_parameter)
|
|
234
80
|
|
|
235
|
-
def
|
|
236
|
-
|
|
237
|
-
if node.name in self.connections.outgoing_index:
|
|
238
|
-
outgoing_params = self.connections.outgoing_index[node.name]
|
|
239
|
-
if param.name in outgoing_params:
|
|
240
|
-
for connection_id in outgoing_params[param.name]:
|
|
241
|
-
connection = self.connections.connections[connection_id]
|
|
242
|
-
connections.append((connection.target_node, connection.target_parameter))
|
|
243
|
-
return connections
|
|
81
|
+
def clear_execution_queue(self) -> None:
|
|
82
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
244
83
|
|
|
245
|
-
|
|
246
|
-
connections = []
|
|
247
|
-
if node.name in self.connections.incoming_index:
|
|
248
|
-
incoming_params = self.connections.incoming_index[node.name]
|
|
249
|
-
if param.name in incoming_params:
|
|
250
|
-
for connection_id in incoming_params[param.name]:
|
|
251
|
-
connection = self.connections.connections[connection_id]
|
|
252
|
-
connections.append((connection.source_node, connection.source_parameter))
|
|
253
|
-
return connections
|
|
254
|
-
|
|
255
|
-
def get_connected_output_from_node(self, node: BaseNode) -> list[tuple[BaseNode, Parameter]]:
|
|
256
|
-
connections = []
|
|
257
|
-
if node.name in self.connections.outgoing_index:
|
|
258
|
-
connection_ids = [
|
|
259
|
-
item for value_list in self.connections.outgoing_index[node.name].values() for item in value_list
|
|
260
|
-
]
|
|
261
|
-
for connection_id in connection_ids:
|
|
262
|
-
connection = self.connections.connections[connection_id]
|
|
263
|
-
connections.append((connection.target_node, connection.target_parameter))
|
|
264
|
-
return connections
|
|
265
|
-
|
|
266
|
-
def get_connected_input_from_node(self, node: BaseNode) -> list[tuple[BaseNode, Parameter]]:
|
|
267
|
-
connections = []
|
|
268
|
-
if node.name in self.connections.incoming_index:
|
|
269
|
-
connection_ids = [
|
|
270
|
-
item for value_list in self.connections.incoming_index[node.name].values() for item in value_list
|
|
271
|
-
]
|
|
272
|
-
for connection_id in connection_ids:
|
|
273
|
-
connection = self.connections.connections[connection_id]
|
|
274
|
-
connections.append((connection.source_node, connection.source_parameter))
|
|
275
|
-
return connections
|
|
276
|
-
|
|
277
|
-
def get_start_node_queue(self) -> Queue | None: # noqa: C901, PLR0912
|
|
278
|
-
# check all nodes in flow
|
|
279
|
-
# add them all to a stack. We're calling this only if no flow was specified, so we're running them all.
|
|
280
|
-
self.flow_queue = Queue()
|
|
281
|
-
# if no nodes, no flow.
|
|
282
|
-
if not len(self.nodes):
|
|
283
|
-
return None
|
|
284
|
-
data_nodes = []
|
|
285
|
-
valid_data_nodes = []
|
|
286
|
-
start_nodes = []
|
|
287
|
-
control_nodes = []
|
|
288
|
-
for node in self.nodes.values():
|
|
289
|
-
# if it's a start node, start here! Return the first one!
|
|
290
|
-
if isinstance(node, StartNode):
|
|
291
|
-
start_nodes.append(node)
|
|
292
|
-
continue
|
|
293
|
-
# no start nodes. let's find the first control node.
|
|
294
|
-
# if it's a control node, there could be a flow.
|
|
295
|
-
control_param = False
|
|
296
|
-
for parameter in node.parameters:
|
|
297
|
-
if ParameterTypeBuiltin.CONTROL_TYPE.value == parameter.output_type:
|
|
298
|
-
control_param = True
|
|
299
|
-
break
|
|
300
|
-
if not control_param:
|
|
301
|
-
# saving this for later
|
|
302
|
-
data_nodes.append(node)
|
|
303
|
-
# If this node doesn't have a control connection..
|
|
304
|
-
continue
|
|
305
|
-
cn_mgr = self.connections
|
|
306
|
-
# check if it has an incoming connection. If it does, it's not a start node
|
|
307
|
-
has_control_connection = False
|
|
308
|
-
if node.name in cn_mgr.incoming_index:
|
|
309
|
-
for param_name in cn_mgr.incoming_index[node.name]:
|
|
310
|
-
param = node.get_parameter_by_name(param_name)
|
|
311
|
-
if param and ParameterTypeBuiltin.CONTROL_TYPE.value == param.output_type:
|
|
312
|
-
# there is a control connection coming in
|
|
313
|
-
has_control_connection = True
|
|
314
|
-
break
|
|
315
|
-
# if there is a connection coming in, isn't a start.
|
|
316
|
-
if has_control_connection and not isinstance(node, StartLoopNode):
|
|
317
|
-
continue
|
|
318
|
-
# Does it have an outgoing connection?
|
|
319
|
-
if node.name in cn_mgr.outgoing_index:
|
|
320
|
-
# If one of the outgoing connections is control, add it. otherwise don't.
|
|
321
|
-
for param_name in cn_mgr.outgoing_index[node.name]:
|
|
322
|
-
param = node.get_parameter_by_name(param_name)
|
|
323
|
-
if param and ParameterTypeBuiltin.CONTROL_TYPE.value == param.output_type:
|
|
324
|
-
control_nodes.append(node)
|
|
325
|
-
break
|
|
326
|
-
else:
|
|
327
|
-
control_nodes.append(node)
|
|
328
|
-
|
|
329
|
-
# If we've gotten to this point, there are no control parameters
|
|
330
|
-
# Let's return a data node that has no OUTGOING data connections!
|
|
331
|
-
for node in data_nodes:
|
|
332
|
-
cn_mgr = self.connections
|
|
333
|
-
# check if it has an outgoing connection. We don't want it to (that means we get the most resolution)
|
|
334
|
-
if node.name not in cn_mgr.outgoing_index:
|
|
335
|
-
valid_data_nodes.append(node)
|
|
336
|
-
# ok now
|
|
337
|
-
for node in start_nodes:
|
|
338
|
-
self.flow_queue.put(node)
|
|
339
|
-
for node in control_nodes:
|
|
340
|
-
self.flow_queue.put(node)
|
|
341
|
-
for node in valid_data_nodes:
|
|
342
|
-
self.flow_queue.put(node)
|
|
343
|
-
|
|
344
|
-
return self.flow_queue
|
|
345
|
-
|
|
346
|
-
def get_start_node_from_node(self, node: BaseNode) -> BaseNode | None:
|
|
347
|
-
# backwards chain in control outputs.
|
|
348
|
-
if node not in self.nodes.values():
|
|
349
|
-
return None
|
|
350
|
-
# Go back through incoming control connections to get the start node
|
|
351
|
-
curr_node = node
|
|
352
|
-
prev_node = self.get_prev_node(curr_node)
|
|
353
|
-
# Fencepost loop - get the first previous node name and then we go
|
|
354
|
-
while prev_node:
|
|
355
|
-
curr_node = prev_node
|
|
356
|
-
prev_node = self.get_prev_node(prev_node)
|
|
357
|
-
return curr_node
|
|
358
|
-
|
|
359
|
-
def get_prev_node(self, node: BaseNode) -> BaseNode | None:
|
|
360
|
-
if node.name in self.connections.incoming_index:
|
|
361
|
-
parameters = self.connections.incoming_index[node.name]
|
|
362
|
-
for parameter_name in parameters:
|
|
363
|
-
parameter = node.get_parameter_by_name(parameter_name)
|
|
364
|
-
if parameter and ParameterTypeBuiltin.CONTROL_TYPE.value == parameter.output_type:
|
|
365
|
-
# this is a control connection
|
|
366
|
-
connection_ids = self.connections.incoming_index[node.name][parameter_name]
|
|
367
|
-
for connection_id in connection_ids:
|
|
368
|
-
connection = self.connections.connections[connection_id]
|
|
369
|
-
return connection.get_source_node()
|
|
370
|
-
return None
|
|
371
|
-
|
|
372
|
-
def stop_flow_breakpoint(self, node: BaseNode) -> None:
|
|
373
|
-
# This will prevent the flow from continuing on.
|
|
374
|
-
node.stop_flow = True
|
|
84
|
+
GriptapeNodes.FlowManager().clear_execution_queue()
|
|
375
85
|
|
|
376
86
|
def get_connections_on_node(self, node: BaseNode) -> list[BaseNode] | None:
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
if node.name in self.connections.outgoing_index:
|
|
381
|
-
outgoing_params = self.connections.outgoing_index[node.name]
|
|
382
|
-
outgoing_connection_ids = []
|
|
383
|
-
for connection_ids in outgoing_params.values():
|
|
384
|
-
outgoing_connection_ids = outgoing_connection_ids + connection_ids
|
|
385
|
-
for connection_id in outgoing_connection_ids:
|
|
386
|
-
connection = self.connections.connections[connection_id]
|
|
387
|
-
if connection.source_node not in connected_nodes:
|
|
388
|
-
connected_nodes.append(connection.target_node)
|
|
389
|
-
# Handle incoming connections
|
|
390
|
-
if node.name in self.connections.incoming_index:
|
|
391
|
-
incoming_params = self.connections.incoming_index[node.name]
|
|
392
|
-
incoming_connection_ids = []
|
|
393
|
-
for connection_ids in incoming_params.values():
|
|
394
|
-
incoming_connection_ids = incoming_connection_ids + connection_ids
|
|
395
|
-
for connection_id in incoming_connection_ids:
|
|
396
|
-
connection = self.connections.connections[connection_id]
|
|
397
|
-
if connection.source_node not in connected_nodes:
|
|
398
|
-
connected_nodes.append(connection.source_node)
|
|
399
|
-
# Return all connected nodes. No duplicates
|
|
400
|
-
return connected_nodes
|
|
87
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
88
|
+
|
|
89
|
+
return GriptapeNodes.FlowManager().get_connections_on_node(self, node)
|
|
401
90
|
|
|
402
91
|
def get_all_connected_nodes(self, node: BaseNode) -> list[BaseNode]:
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
queue.put(node)
|
|
407
|
-
discovered[node] = True
|
|
408
|
-
while not queue.empty():
|
|
409
|
-
curr_node = queue.get()
|
|
410
|
-
processed[curr_node] = True
|
|
411
|
-
next_nodes = self.get_connections_on_node(curr_node)
|
|
412
|
-
if next_nodes:
|
|
413
|
-
for next_node in next_nodes:
|
|
414
|
-
if next_node not in discovered:
|
|
415
|
-
discovered[next_node] = True
|
|
416
|
-
queue.put(next_node)
|
|
417
|
-
return list(processed.keys())
|
|
92
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
93
|
+
|
|
94
|
+
return GriptapeNodes.FlowManager().get_all_connected_nodes(self, node)
|
|
418
95
|
|
|
419
96
|
def get_node_dependencies(self, node: BaseNode) -> list[BaseNode]:
|
|
420
97
|
"""Get all upstream nodes that the given node depends on.
|
|
@@ -428,18 +105,41 @@ class ControlFlow:
|
|
|
428
105
|
Returns:
|
|
429
106
|
list[BaseNode]: A list of all nodes that the given node depends on, including the node itself (as the first element)
|
|
430
107
|
"""
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
108
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
109
|
+
|
|
110
|
+
return GriptapeNodes.FlowManager().get_node_dependencies(self, node)
|
|
111
|
+
|
|
112
|
+
def get_connected_output_parameters(self, node: BaseNode, param: Parameter) -> list[tuple[BaseNode, Parameter]]:
|
|
113
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
114
|
+
|
|
115
|
+
return GriptapeNodes.FlowManager().get_connected_output_parameters(node, param)
|
|
116
|
+
|
|
117
|
+
def get_connected_input_parameters(self, node: BaseNode, param: Parameter) -> list[tuple[BaseNode, Parameter]]:
|
|
118
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
119
|
+
|
|
120
|
+
return GriptapeNodes.FlowManager().get_connected_input_parameters(self, node, param)
|
|
121
|
+
|
|
122
|
+
def get_connected_output_from_node(self, node: BaseNode) -> list[tuple[BaseNode, Parameter]]:
|
|
123
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
124
|
+
|
|
125
|
+
return GriptapeNodes.FlowManager().get_connected_output_from_node(self, node)
|
|
126
|
+
|
|
127
|
+
def get_connected_input_from_node(self, node: BaseNode) -> list[tuple[BaseNode, Parameter]]:
|
|
128
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
129
|
+
|
|
130
|
+
return GriptapeNodes.FlowManager().get_connected_input_from_node(self, node)
|
|
131
|
+
|
|
132
|
+
def get_start_node_queue(self) -> Queue | None:
|
|
133
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
134
|
+
|
|
135
|
+
return GriptapeNodes.FlowManager().get_start_node_queue()
|
|
136
|
+
|
|
137
|
+
def get_start_node_from_node(self, node: BaseNode) -> BaseNode | None:
|
|
138
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
139
|
+
|
|
140
|
+
return GriptapeNodes.FlowManager().get_start_node_from_node(self, node)
|
|
141
|
+
|
|
142
|
+
def get_prev_node(self, node: BaseNode) -> BaseNode | None:
|
|
143
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
144
|
+
|
|
145
|
+
return GriptapeNodes.FlowManager().get_prev_node(self, node)
|
|
@@ -65,6 +65,9 @@ class BaseNode(ABC):
|
|
|
65
65
|
stop_flow: bool = False
|
|
66
66
|
root_ui_element: BaseNodeElement
|
|
67
67
|
_tracked_parameters: list[BaseNodeElement]
|
|
68
|
+
_entry_control_parameter: Parameter | None = (
|
|
69
|
+
None # The control input parameter used to enter this node during execution
|
|
70
|
+
)
|
|
68
71
|
|
|
69
72
|
@property
|
|
70
73
|
def parameters(self) -> list[Parameter]:
|
|
@@ -92,6 +95,7 @@ class BaseNode(ABC):
|
|
|
92
95
|
self.root_ui_element._node_context = self
|
|
93
96
|
self.process_generator = None
|
|
94
97
|
self._tracked_parameters = []
|
|
98
|
+
self.set_entry_control_parameter(None)
|
|
95
99
|
|
|
96
100
|
# 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!
|
|
97
101
|
# https://github.com/griptape-ai/griptape-nodes/issues/994
|
|
@@ -106,6 +110,18 @@ class BaseNode(ABC):
|
|
|
106
110
|
)
|
|
107
111
|
)
|
|
108
112
|
self.state = NodeResolutionState.UNRESOLVED
|
|
113
|
+
# NOTE: _entry_control_parameter is NOT cleared here as it represents execution context
|
|
114
|
+
# that should persist through the resolve/unresolve cycle during a single execution
|
|
115
|
+
|
|
116
|
+
def set_entry_control_parameter(self, parameter: Parameter | None) -> None:
|
|
117
|
+
"""Set the control parameter that was used to enter this node.
|
|
118
|
+
|
|
119
|
+
This should only be called by the ControlFlowContext during execution.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
parameter: The control input parameter that triggered this node's execution, or None to clear
|
|
123
|
+
"""
|
|
124
|
+
self._entry_control_parameter = parameter
|
|
109
125
|
|
|
110
126
|
def emit_parameter_changes(self) -> None:
|
|
111
127
|
if self._tracked_parameters:
|
|
@@ -577,7 +593,7 @@ class BaseNode(ABC):
|
|
|
577
593
|
|
|
578
594
|
def _flatten(items: Iterable[Any]) -> Generator[Any, None, None]:
|
|
579
595
|
for item in items:
|
|
580
|
-
if isinstance(item, Iterable) and not isinstance(item, (str, bytes)):
|
|
596
|
+
if isinstance(item, Iterable) and not isinstance(item, (str, bytes, dict)):
|
|
581
597
|
yield from _flatten(item)
|
|
582
598
|
elif item:
|
|
583
599
|
yield item
|
|
File without changes
|
|
File without changes
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import logging
|
|
5
|
+
from dataclasses import dataclass
|
|
5
6
|
from typing import TYPE_CHECKING
|
|
6
7
|
|
|
7
8
|
from griptape.events import EventBus
|
|
8
9
|
|
|
10
|
+
from griptape_nodes.exe_types.core_types import Parameter
|
|
9
11
|
from griptape_nodes.exe_types.node_types import BaseNode, NodeResolutionState
|
|
10
12
|
from griptape_nodes.exe_types.type_validator import TypeValidator
|
|
11
13
|
from griptape_nodes.machines.fsm import FSM, State
|
|
@@ -17,6 +19,15 @@ from griptape_nodes.retained_mode.events.execution_events import (
|
|
|
17
19
|
SelectedControlOutputEvent,
|
|
18
20
|
)
|
|
19
21
|
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class NextNodeInfo:
|
|
25
|
+
"""Information about the next node to execute and how to reach it."""
|
|
26
|
+
|
|
27
|
+
node: BaseNode
|
|
28
|
+
entry_parameter: Parameter | None
|
|
29
|
+
|
|
30
|
+
|
|
20
31
|
if TYPE_CHECKING:
|
|
21
32
|
from griptape_nodes.exe_types.core_types import Parameter
|
|
22
33
|
from griptape_nodes.exe_types.flow import ControlFlow
|
|
@@ -32,21 +43,30 @@ class ControlFlowContext:
|
|
|
32
43
|
selected_output: Parameter | None
|
|
33
44
|
paused: bool = False
|
|
34
45
|
|
|
35
|
-
def __init__(self
|
|
36
|
-
self.resolution_machine = NodeResolutionMachine(
|
|
37
|
-
self.flow = flow
|
|
46
|
+
def __init__(self) -> None:
|
|
47
|
+
self.resolution_machine = NodeResolutionMachine()
|
|
38
48
|
self.current_node = None
|
|
39
49
|
|
|
40
|
-
def get_next_node(self, output_parameter: Parameter) ->
|
|
50
|
+
def get_next_node(self, output_parameter: Parameter) -> NextNodeInfo | None:
|
|
51
|
+
"""Get the next node and the target parameter that will receive the control flow.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
NextNodeInfo | None: Information about the next node or None if no connection
|
|
55
|
+
"""
|
|
41
56
|
if self.current_node is not None:
|
|
42
|
-
|
|
57
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
58
|
+
|
|
59
|
+
node_connection = (
|
|
60
|
+
GriptapeNodes.FlowManager().get_connections().get_connected_node(self.current_node, output_parameter)
|
|
61
|
+
)
|
|
62
|
+
if node_connection is not None:
|
|
63
|
+
node, entry_parameter = node_connection
|
|
64
|
+
return NextNodeInfo(node=node, entry_parameter=entry_parameter)
|
|
65
|
+
# Continue Execution to the next node that needs to be executed using global execution queue
|
|
66
|
+
# Get the next node in the execution queue, or None if queue is empty
|
|
67
|
+
node = GriptapeNodes.FlowManager().get_next_node_from_execution_queue()
|
|
43
68
|
if node is not None:
|
|
44
|
-
node,
|
|
45
|
-
# Continue Execution to the next node that needs to be executed.
|
|
46
|
-
elif not self.flow.flow_queue.empty():
|
|
47
|
-
node = self.flow.flow_queue.get()
|
|
48
|
-
self.flow.flow_queue.task_done()
|
|
49
|
-
return node
|
|
69
|
+
return NextNodeInfo(node=node, entry_parameter=None)
|
|
50
70
|
return None
|
|
51
71
|
|
|
52
72
|
def reset(self) -> None:
|
|
@@ -110,10 +130,11 @@ class NextNodeState(State):
|
|
|
110
130
|
context.current_node.stop_flow = False
|
|
111
131
|
return CompleteState
|
|
112
132
|
next_output = context.current_node.get_next_control_output()
|
|
113
|
-
|
|
133
|
+
next_node_info = None
|
|
134
|
+
|
|
114
135
|
if next_output is not None:
|
|
115
136
|
context.selected_output = next_output
|
|
116
|
-
|
|
137
|
+
next_node_info = context.get_next_node(context.selected_output)
|
|
117
138
|
EventBus.publish_event(
|
|
118
139
|
ExecutionGriptapeNodeEvent(
|
|
119
140
|
wrapped_event=ExecutionEvent(
|
|
@@ -124,14 +145,23 @@ class NextNodeState(State):
|
|
|
124
145
|
)
|
|
125
146
|
)
|
|
126
147
|
)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
148
|
+
else:
|
|
149
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
150
|
+
|
|
151
|
+
# Get the next node in the execution queue, or None if queue is empty
|
|
152
|
+
next_node = GriptapeNodes.FlowManager().get_next_node_from_execution_queue()
|
|
153
|
+
if next_node is not None:
|
|
154
|
+
next_node_info = NextNodeInfo(node=next_node, entry_parameter=None)
|
|
155
|
+
|
|
130
156
|
# The parameter that will be evaluated next
|
|
131
|
-
if
|
|
157
|
+
if next_node_info is None:
|
|
132
158
|
# If no node attached
|
|
133
159
|
return CompleteState
|
|
134
|
-
|
|
160
|
+
|
|
161
|
+
# Always set the entry control parameter (None for execution queue nodes)
|
|
162
|
+
next_node_info.node.set_entry_control_parameter(next_node_info.entry_parameter)
|
|
163
|
+
|
|
164
|
+
context.current_node = next_node_info.node
|
|
135
165
|
context.selected_output = None
|
|
136
166
|
if not context.paused:
|
|
137
167
|
return ResolveNodeState
|
|
@@ -168,12 +198,14 @@ class CompleteState(State):
|
|
|
168
198
|
|
|
169
199
|
# MACHINE TIME!!!
|
|
170
200
|
class ControlFlowMachine(FSM[ControlFlowContext]):
|
|
171
|
-
def __init__(self
|
|
172
|
-
context = ControlFlowContext(
|
|
201
|
+
def __init__(self) -> None:
|
|
202
|
+
context = ControlFlowContext()
|
|
173
203
|
super().__init__(context)
|
|
174
204
|
|
|
175
205
|
def start_flow(self, start_node: BaseNode, debug_mode: bool = False) -> None: # noqa: FBT001, FBT002
|
|
176
206
|
self._context.current_node = start_node
|
|
207
|
+
# Set entry control parameter for initial node (None for workflow start)
|
|
208
|
+
start_node.set_entry_control_parameter(None)
|
|
177
209
|
# Set up to debug
|
|
178
210
|
self._context.paused = debug_mode
|
|
179
211
|
self.start(ResolveNodeState) # Begins the flow
|