griptape-nodes 0.63.9__py3-none-any.whl → 0.64.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- griptape_nodes/common/node_executor.py +95 -171
- griptape_nodes/exe_types/connections.py +51 -2
- griptape_nodes/exe_types/flow.py +3 -3
- griptape_nodes/exe_types/node_types.py +330 -202
- griptape_nodes/exe_types/param_components/artifact_url/__init__.py +1 -0
- griptape_nodes/exe_types/param_components/artifact_url/public_artifact_url_parameter.py +155 -0
- griptape_nodes/exe_types/param_components/progress_bar_component.py +1 -1
- griptape_nodes/exe_types/param_types/parameter_string.py +27 -0
- griptape_nodes/machines/control_flow.py +64 -203
- griptape_nodes/machines/dag_builder.py +85 -238
- griptape_nodes/machines/parallel_resolution.py +9 -236
- griptape_nodes/machines/sequential_resolution.py +133 -11
- griptape_nodes/retained_mode/events/agent_events.py +2 -0
- griptape_nodes/retained_mode/events/flow_events.py +5 -6
- griptape_nodes/retained_mode/events/node_events.py +151 -1
- griptape_nodes/retained_mode/events/workflow_events.py +10 -0
- griptape_nodes/retained_mode/managers/agent_manager.py +33 -1
- griptape_nodes/retained_mode/managers/flow_manager.py +213 -290
- griptape_nodes/retained_mode/managers/library_manager.py +24 -7
- griptape_nodes/retained_mode/managers/node_manager.py +391 -77
- griptape_nodes/retained_mode/managers/workflow_manager.py +45 -10
- griptape_nodes/servers/mcp.py +32 -0
- griptape_nodes/updater/__init__.py +1 -1
- {griptape_nodes-0.63.9.dist-info → griptape_nodes-0.64.0.dist-info}/METADATA +3 -1
- {griptape_nodes-0.63.9.dist-info → griptape_nodes-0.64.0.dist-info}/RECORD +27 -25
- {griptape_nodes-0.63.9.dist-info → griptape_nodes-0.64.0.dist-info}/WHEEL +0 -0
- {griptape_nodes-0.63.9.dist-info → griptape_nodes-0.64.0.dist-info}/entry_points.txt +0 -0
|
@@ -11,7 +11,6 @@ from griptape_nodes.exe_types.node_types import (
|
|
|
11
11
|
CONTROL_INPUT_PARAMETER,
|
|
12
12
|
LOCAL_EXECUTION,
|
|
13
13
|
BaseNode,
|
|
14
|
-
NodeGroupProxyNode,
|
|
15
14
|
NodeResolutionState,
|
|
16
15
|
)
|
|
17
16
|
from griptape_nodes.exe_types.type_validator import TypeValidator
|
|
@@ -129,12 +128,6 @@ class ExecuteDagState(State):
|
|
|
129
128
|
network_name,
|
|
130
129
|
)
|
|
131
130
|
return
|
|
132
|
-
|
|
133
|
-
# Check if this is a NodeGroupProxyNode - if so, handle grouped nodes
|
|
134
|
-
|
|
135
|
-
if isinstance(current_node, NodeGroupProxyNode):
|
|
136
|
-
await ExecuteDagState._handle_group_proxy_completion(context, current_node, network_name)
|
|
137
|
-
return
|
|
138
131
|
# Publish all parameter updates.
|
|
139
132
|
current_node.state = NodeResolutionState.RESOLVED
|
|
140
133
|
# Track this as the last resolved node
|
|
@@ -192,171 +185,6 @@ class ExecuteDagState(State):
|
|
|
192
185
|
# Now the final thing to do, is to take their directed graph and update it.
|
|
193
186
|
ExecuteDagState.get_next_control_graph(context, current_node, network_name)
|
|
194
187
|
|
|
195
|
-
# Method is mirrored in Control_flow.py. If you update one, update the other.
|
|
196
|
-
@staticmethod
|
|
197
|
-
async def _handle_group_proxy_completion(
|
|
198
|
-
context: ParallelResolutionContext, proxy_node: BaseNode, network_name: str
|
|
199
|
-
) -> None:
|
|
200
|
-
"""Handle completion of a NodeGroupProxyNode by marking all grouped nodes as resolved.
|
|
201
|
-
|
|
202
|
-
When a NodeGroupProxyNode completes, all nodes in the group have been executed
|
|
203
|
-
in parallel by the NodeExecutor. This method marks each grouped node as RESOLVED
|
|
204
|
-
and emits NodeResolvedEvent for each one.
|
|
205
|
-
|
|
206
|
-
Args:
|
|
207
|
-
proxy_node: The NodeGroupProxyNode that completed execution
|
|
208
|
-
context: The ParallelResolutionContext
|
|
209
|
-
network_name: The name of the network
|
|
210
|
-
"""
|
|
211
|
-
from griptape_nodes.exe_types.node_types import NodeGroupProxyNode
|
|
212
|
-
|
|
213
|
-
if not isinstance(proxy_node, NodeGroupProxyNode):
|
|
214
|
-
return
|
|
215
|
-
|
|
216
|
-
node_group = proxy_node.node_group_data
|
|
217
|
-
|
|
218
|
-
# Mark all grouped nodes as resolved and emit events
|
|
219
|
-
proxy_node.state = NodeResolutionState.RESOLVED
|
|
220
|
-
for grouped_node in node_group.nodes.values():
|
|
221
|
-
# Mark node as resolved
|
|
222
|
-
grouped_node.state = NodeResolutionState.RESOLVED
|
|
223
|
-
|
|
224
|
-
# Emit parameter update events for each output parameter
|
|
225
|
-
for parameter_name, value in grouped_node.parameter_output_values.items():
|
|
226
|
-
parameter = grouped_node.get_parameter_by_name(parameter_name)
|
|
227
|
-
if parameter is None:
|
|
228
|
-
logger.warning(
|
|
229
|
-
"Node '%s' in group '%s' has output parameter '%s' but parameter not found",
|
|
230
|
-
grouped_node.name,
|
|
231
|
-
node_group.group_id,
|
|
232
|
-
parameter_name,
|
|
233
|
-
)
|
|
234
|
-
continue
|
|
235
|
-
|
|
236
|
-
data_type = parameter.type
|
|
237
|
-
if data_type is None:
|
|
238
|
-
data_type = ParameterTypeBuiltin.NONE.value
|
|
239
|
-
|
|
240
|
-
await GriptapeNodes.EventManager().aput_event(
|
|
241
|
-
ExecutionGriptapeNodeEvent(
|
|
242
|
-
wrapped_event=ExecutionEvent(
|
|
243
|
-
payload=ParameterValueUpdateEvent(
|
|
244
|
-
node_name=grouped_node.name,
|
|
245
|
-
parameter_name=parameter_name,
|
|
246
|
-
data_type=data_type,
|
|
247
|
-
value=TypeValidator.safe_serialize(value),
|
|
248
|
-
)
|
|
249
|
-
),
|
|
250
|
-
)
|
|
251
|
-
)
|
|
252
|
-
|
|
253
|
-
# Emit NodeResolvedEvent for the grouped node
|
|
254
|
-
library = LibraryRegistry.get_libraries_with_node_type(grouped_node.__class__.__name__)
|
|
255
|
-
library_name = library[0] if len(library) == 1 else None
|
|
256
|
-
|
|
257
|
-
await GriptapeNodes.EventManager().aput_event(
|
|
258
|
-
ExecutionGriptapeNodeEvent(
|
|
259
|
-
wrapped_event=ExecutionEvent(
|
|
260
|
-
payload=NodeResolvedEvent(
|
|
261
|
-
node_name=grouped_node.name,
|
|
262
|
-
parameter_output_values=TypeValidator.safe_serialize(grouped_node.parameter_output_values),
|
|
263
|
-
node_type=grouped_node.__class__.__name__,
|
|
264
|
-
specific_library_name=library_name,
|
|
265
|
-
)
|
|
266
|
-
)
|
|
267
|
-
)
|
|
268
|
-
)
|
|
269
|
-
|
|
270
|
-
# Cleanup: restore connections and deregister proxy
|
|
271
|
-
ExecuteDagState.get_next_control_graph(context, proxy_node, network_name)
|
|
272
|
-
ExecuteDagState._cleanup_proxy_node(proxy_node)
|
|
273
|
-
|
|
274
|
-
@staticmethod
|
|
275
|
-
def _cleanup_proxy_node(proxy_node: BaseNode) -> None:
|
|
276
|
-
"""Clean up a NodeGroupProxyNode after execution completes.
|
|
277
|
-
|
|
278
|
-
Restores original connections from proxy back to grouped nodes and
|
|
279
|
-
deregisters the proxy node from ObjectManager.
|
|
280
|
-
|
|
281
|
-
Args:
|
|
282
|
-
proxy_node: The NodeGroupProxyNode to clean up
|
|
283
|
-
"""
|
|
284
|
-
from griptape_nodes.exe_types.node_types import NodeGroupProxyNode
|
|
285
|
-
|
|
286
|
-
if not isinstance(proxy_node, NodeGroupProxyNode):
|
|
287
|
-
return
|
|
288
|
-
|
|
289
|
-
node_group = proxy_node.node_group_data
|
|
290
|
-
connections = GriptapeNodes.FlowManager().get_connections()
|
|
291
|
-
|
|
292
|
-
# Restore external incoming connections (proxy -> original target node)
|
|
293
|
-
for conn in node_group.external_incoming_connections:
|
|
294
|
-
conn_id = id(conn)
|
|
295
|
-
|
|
296
|
-
# Get original target node
|
|
297
|
-
original_target = node_group.original_incoming_targets.get(conn_id)
|
|
298
|
-
if original_target is None:
|
|
299
|
-
continue
|
|
300
|
-
|
|
301
|
-
# Create proxy parameter name to find it in the index
|
|
302
|
-
sanitized_node_name = original_target.name.replace(" ", "_")
|
|
303
|
-
proxy_param_name = f"{sanitized_node_name}__{conn.target_parameter.name}"
|
|
304
|
-
|
|
305
|
-
# Remove proxy from incoming index (using proxy parameter name)
|
|
306
|
-
if (
|
|
307
|
-
proxy_node.name in connections.incoming_index
|
|
308
|
-
and proxy_param_name in connections.incoming_index[proxy_node.name]
|
|
309
|
-
):
|
|
310
|
-
connections.incoming_index[proxy_node.name][proxy_param_name].remove(conn_id)
|
|
311
|
-
|
|
312
|
-
# Restore connection to original target node
|
|
313
|
-
conn.target_node = original_target
|
|
314
|
-
|
|
315
|
-
# Add back to original target node's incoming index (using original parameter name)
|
|
316
|
-
connections.incoming_index.setdefault(conn.target_node.name, {}).setdefault(
|
|
317
|
-
conn.target_parameter.name, []
|
|
318
|
-
).append(conn_id)
|
|
319
|
-
|
|
320
|
-
# Restore external outgoing connections (original source node -> proxy)
|
|
321
|
-
for conn in node_group.external_outgoing_connections:
|
|
322
|
-
conn_id = id(conn)
|
|
323
|
-
|
|
324
|
-
# Get original source node
|
|
325
|
-
original_source = node_group.original_outgoing_sources.get(conn_id)
|
|
326
|
-
if original_source is None:
|
|
327
|
-
continue
|
|
328
|
-
|
|
329
|
-
# Create proxy parameter name to find it in the index
|
|
330
|
-
sanitized_node_name = original_source.name.replace(" ", "_")
|
|
331
|
-
proxy_param_name = f"{sanitized_node_name}__{conn.source_parameter.name}"
|
|
332
|
-
|
|
333
|
-
# Remove proxy from outgoing index (using proxy parameter name)
|
|
334
|
-
if (
|
|
335
|
-
proxy_node.name in connections.outgoing_index
|
|
336
|
-
and proxy_param_name in connections.outgoing_index[proxy_node.name]
|
|
337
|
-
):
|
|
338
|
-
connections.outgoing_index[proxy_node.name][proxy_param_name].remove(conn_id)
|
|
339
|
-
|
|
340
|
-
# Restore connection to original source node
|
|
341
|
-
conn.source_node = original_source
|
|
342
|
-
|
|
343
|
-
# Add back to original source node's outgoing index (using original parameter name)
|
|
344
|
-
connections.outgoing_index.setdefault(conn.source_node.name, {}).setdefault(
|
|
345
|
-
conn.source_parameter.name, []
|
|
346
|
-
).append(conn_id)
|
|
347
|
-
|
|
348
|
-
# Deregister proxy node from ObjectManager
|
|
349
|
-
obj_manager = GriptapeNodes.ObjectManager()
|
|
350
|
-
if obj_manager.has_object_with_name(proxy_node.name):
|
|
351
|
-
del obj_manager._name_to_objects[proxy_node.name]
|
|
352
|
-
|
|
353
|
-
logger.debug(
|
|
354
|
-
"Cleaned up proxy node '%s' for group '%s' - restored %d connections",
|
|
355
|
-
proxy_node.name,
|
|
356
|
-
node_group.group_id,
|
|
357
|
-
len(node_group.external_incoming_connections) + len(node_group.external_outgoing_connections),
|
|
358
|
-
)
|
|
359
|
-
|
|
360
188
|
@staticmethod
|
|
361
189
|
def get_next_control_output_for_non_local_execution(node: BaseNode) -> Parameter | None:
|
|
362
190
|
for param_name, value in node.parameter_output_values.items():
|
|
@@ -381,11 +209,12 @@ class ExecuteDagState(State):
|
|
|
381
209
|
# Early returns for various conditions
|
|
382
210
|
if ExecuteDagState._should_skip_control_flow(context, node, network_name, flow_manager):
|
|
383
211
|
return
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
212
|
+
from griptape_nodes.exe_types.node_types import NodeGroupNode
|
|
213
|
+
|
|
214
|
+
if (
|
|
215
|
+
isinstance(node, NodeGroupNode)
|
|
216
|
+
and node.get_parameter_value(node.execution_environment.name) != LOCAL_EXECUTION
|
|
217
|
+
):
|
|
389
218
|
next_output = ExecuteDagState.get_next_control_output_for_non_local_execution(node)
|
|
390
219
|
else:
|
|
391
220
|
next_output = node.get_next_control_output()
|
|
@@ -425,14 +254,6 @@ class ExecuteDagState(State):
|
|
|
425
254
|
"""Process the next control node in the flow."""
|
|
426
255
|
node_connection = flow_manager.get_connections().get_connected_node(node, next_output)
|
|
427
256
|
if node_connection is not None:
|
|
428
|
-
next_node, next_parameter = node_connection
|
|
429
|
-
# Set entry control parameter
|
|
430
|
-
logger.debug(
|
|
431
|
-
"Parallel Resolution: Setting entry control parameter for node '%s' to '%s'",
|
|
432
|
-
next_node.name,
|
|
433
|
-
next_parameter.name if next_parameter else None,
|
|
434
|
-
)
|
|
435
|
-
next_node.set_entry_control_parameter(next_parameter)
|
|
436
257
|
next_node, next_parameter = node_connection
|
|
437
258
|
# Set entry control parameter
|
|
438
259
|
logger.debug(
|
|
@@ -519,7 +340,6 @@ class ExecuteDagState(State):
|
|
|
519
340
|
for parameter in current_node.parameters:
|
|
520
341
|
# Get the connected upstream node for this parameter
|
|
521
342
|
upstream_connection = connections.get_connected_node(current_node, parameter, direction=Direction.UPSTREAM)
|
|
522
|
-
upstream_connection = connections.get_connected_node(current_node, parameter, direction=Direction.UPSTREAM)
|
|
523
343
|
if upstream_connection:
|
|
524
344
|
upstream_node, upstream_parameter = upstream_connection
|
|
525
345
|
|
|
@@ -544,20 +364,6 @@ class ExecuteDagState(State):
|
|
|
544
364
|
msg = f"Failed to set value for parameter '{parameter.name}' on node '{current_node.name}': {result.result_details}"
|
|
545
365
|
logger.error(msg)
|
|
546
366
|
raise RuntimeError(msg)
|
|
547
|
-
result = await GriptapeNodes.get_instance().ahandle_request(
|
|
548
|
-
SetParameterValueRequest(
|
|
549
|
-
parameter_name=parameter.name,
|
|
550
|
-
node_name=current_node.name,
|
|
551
|
-
value=output_value,
|
|
552
|
-
data_type=upstream_parameter.output_type,
|
|
553
|
-
incoming_connection_source_node_name=upstream_node.name,
|
|
554
|
-
incoming_connection_source_parameter_name=upstream_parameter.name,
|
|
555
|
-
)
|
|
556
|
-
)
|
|
557
|
-
if isinstance(result, SetParameterValueResultFailure):
|
|
558
|
-
msg = f"Failed to set value for parameter '{parameter.name}' on node '{current_node.name}': {result.result_details}"
|
|
559
|
-
logger.error(msg)
|
|
560
|
-
raise RuntimeError(msg)
|
|
561
367
|
|
|
562
368
|
@staticmethod
|
|
563
369
|
def build_node_states(context: ParallelResolutionContext) -> tuple[set[str], set[str], set[str]]:
|
|
@@ -582,6 +388,7 @@ class ExecuteDagState(State):
|
|
|
582
388
|
canceled_nodes.add(node)
|
|
583
389
|
elif node_state == NodeState.QUEUED:
|
|
584
390
|
queued_nodes.add(node)
|
|
391
|
+
|
|
585
392
|
return canceled_nodes, queued_nodes, leaf_nodes
|
|
586
393
|
|
|
587
394
|
@staticmethod
|
|
@@ -636,7 +443,7 @@ class ExecuteDagState(State):
|
|
|
636
443
|
return None
|
|
637
444
|
|
|
638
445
|
@staticmethod
|
|
639
|
-
async def on_update(context: ParallelResolutionContext) -> type[State] | None: # noqa: C901, PLR0911
|
|
446
|
+
async def on_update(context: ParallelResolutionContext) -> type[State] | None: # noqa: C901, PLR0911
|
|
640
447
|
# Check if execution is paused
|
|
641
448
|
if context.paused:
|
|
642
449
|
return None
|
|
@@ -728,26 +535,12 @@ class ExecuteDagState(State):
|
|
|
728
535
|
exc,
|
|
729
536
|
)
|
|
730
537
|
|
|
731
|
-
dag_node = context.task_to_node.get(task)
|
|
732
|
-
node_name = dag_node.node_reference.name if dag_node else "Unknown"
|
|
733
|
-
node_type = dag_node.node_reference.__class__.__name__ if dag_node else "Unknown"
|
|
734
|
-
|
|
735
|
-
logger.exception(
|
|
736
|
-
"Task execution failed for node '%s' (type: %s) in flow '%s'. Exception: %s",
|
|
737
|
-
node_name,
|
|
738
|
-
node_type,
|
|
739
|
-
context.flow_name,
|
|
740
|
-
exc,
|
|
741
|
-
)
|
|
742
|
-
|
|
743
538
|
context.task_to_node.pop(task)
|
|
744
539
|
context.error_message = f"Task execution failed for node '{node_name}': {exc}"
|
|
745
540
|
context.workflow_state = WorkflowState.ERRORED
|
|
746
541
|
return ErrorState
|
|
747
|
-
context.error_message = f"Task execution failed for node '{node_name}': {exc}"
|
|
748
|
-
context.workflow_state = WorkflowState.ERRORED
|
|
749
|
-
return ErrorState
|
|
750
542
|
context.task_to_node.pop(task)
|
|
543
|
+
|
|
751
544
|
# Once a task has finished, loop back to the top.
|
|
752
545
|
await ExecuteDagState.pop_done_states(context)
|
|
753
546
|
# Remove all nodes that are done
|
|
@@ -762,18 +555,7 @@ class ErrorState(State):
|
|
|
762
555
|
if context.error_message:
|
|
763
556
|
logger.error("DAG execution error: %s", context.error_message)
|
|
764
557
|
|
|
765
|
-
# Clean up any proxy nodes that failed before completion
|
|
766
|
-
from griptape_nodes.exe_types.node_types import NodeGroupProxyNode
|
|
767
|
-
|
|
768
558
|
for node in context.node_to_reference.values():
|
|
769
|
-
# Clean up proxy nodes that were processing or queued
|
|
770
|
-
if isinstance(node.node_reference, NodeGroupProxyNode) and node.node_state in (
|
|
771
|
-
NodeState.PROCESSING,
|
|
772
|
-
NodeState.QUEUED,
|
|
773
|
-
):
|
|
774
|
-
logger.info("Cleaning up proxy node '%s' that failed during execution", node.node_reference.name)
|
|
775
|
-
ExecuteDagState._cleanup_proxy_node(node.node_reference)
|
|
776
|
-
|
|
777
559
|
# Cancel all nodes that haven't yet begun processing.
|
|
778
560
|
if node.node_state == NodeState.QUEUED:
|
|
779
561
|
node.node_state = NodeState.CANCELED
|
|
@@ -796,15 +578,6 @@ class ErrorState(State):
|
|
|
796
578
|
node.node_state = NodeState.CANCELED
|
|
797
579
|
task_to_node.pop(task)
|
|
798
580
|
|
|
799
|
-
# Handle async tasks
|
|
800
|
-
task_to_node = context.task_to_node
|
|
801
|
-
for task, node in task_to_node.copy().items():
|
|
802
|
-
if task.done():
|
|
803
|
-
node.node_state = NodeState.DONE
|
|
804
|
-
elif task.cancelled():
|
|
805
|
-
node.node_state = NodeState.CANCELED
|
|
806
|
-
task_to_node.pop(task)
|
|
807
|
-
|
|
808
581
|
if len(task_to_node) == 0:
|
|
809
582
|
# Finish up. We failed.
|
|
810
583
|
context.workflow_state = WorkflowState.ERRORED
|
|
@@ -3,10 +3,11 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
5
|
from dataclasses import dataclass
|
|
6
|
+
from typing import Any
|
|
6
7
|
|
|
7
8
|
from griptape_nodes.exe_types.connections import Direction
|
|
8
9
|
from griptape_nodes.exe_types.core_types import ParameterTypeBuiltin
|
|
9
|
-
from griptape_nodes.exe_types.node_types import BaseNode, NodeResolutionState
|
|
10
|
+
from griptape_nodes.exe_types.node_types import LOCAL_EXECUTION, BaseNode, NodeGroupNode, NodeResolutionState
|
|
10
11
|
from griptape_nodes.exe_types.type_validator import TypeValidator
|
|
11
12
|
from griptape_nodes.machines.fsm import FSM, State
|
|
12
13
|
from griptape_nodes.node_library.library_registry import LibraryRegistry
|
|
@@ -77,6 +78,43 @@ class InitializeSpotlightState(State):
|
|
|
77
78
|
if not len(context.focus_stack):
|
|
78
79
|
return CompleteState
|
|
79
80
|
current_node = context.current_node
|
|
81
|
+
|
|
82
|
+
# If this node has a non-LOCAL parent group, redirect to parent instead
|
|
83
|
+
# Handle nested groups recursively - keep redirecting until we find the top-level parent
|
|
84
|
+
from griptape_nodes.exe_types.node_types import LOCAL_EXECUTION, NodeGroupNode
|
|
85
|
+
|
|
86
|
+
while current_node.parent_group is not None and isinstance(current_node.parent_group, NodeGroupNode):
|
|
87
|
+
execution_env = current_node.parent_group.get_parameter_value(
|
|
88
|
+
current_node.parent_group.execution_environment.name
|
|
89
|
+
)
|
|
90
|
+
if execution_env != LOCAL_EXECUTION:
|
|
91
|
+
# Replace current node with parent group
|
|
92
|
+
parent_group = current_node.parent_group
|
|
93
|
+
logger.info(
|
|
94
|
+
"Sequential Resolution: Redirecting from child node '%s' to parent node group '%s' at InitializeSpotlight",
|
|
95
|
+
current_node.name,
|
|
96
|
+
parent_group.name,
|
|
97
|
+
)
|
|
98
|
+
# Update the focus stack to use parent instead
|
|
99
|
+
context.focus_stack[-1] = Focus(node=parent_group)
|
|
100
|
+
current_node = parent_group
|
|
101
|
+
# Continue loop to check if this parent also has a parent
|
|
102
|
+
else:
|
|
103
|
+
# Parent is LOCAL_EXECUTION, stop redirecting
|
|
104
|
+
break
|
|
105
|
+
|
|
106
|
+
# For NodeGroups, check external connections for unresolved dependencies
|
|
107
|
+
if isinstance(current_node, NodeGroupNode):
|
|
108
|
+
unresolved_dependency = EvaluateParameterState._check_node_group_external_dependencies(current_node)
|
|
109
|
+
if unresolved_dependency:
|
|
110
|
+
logger.info(
|
|
111
|
+
"Sequential Resolution: NodeGroup '%s' has unresolved external dependency on '%s', queuing dependency first",
|
|
112
|
+
current_node.name,
|
|
113
|
+
unresolved_dependency.name,
|
|
114
|
+
)
|
|
115
|
+
context.focus_stack.append(Focus(node=unresolved_dependency))
|
|
116
|
+
return InitializeSpotlightState
|
|
117
|
+
|
|
80
118
|
if current_node.state == NodeResolutionState.UNRESOLVED:
|
|
81
119
|
# Mark all future nodes unresolved.
|
|
82
120
|
# TODO: https://github.com/griptape-ai/griptape-nodes/issues/862
|
|
@@ -120,27 +158,111 @@ class EvaluateParameterState(State):
|
|
|
120
158
|
return EvaluateParameterState
|
|
121
159
|
return None
|
|
122
160
|
|
|
161
|
+
@staticmethod
|
|
162
|
+
def _get_next_node(current_node: BaseNode, current_parameter: Any, connections: Any) -> BaseNode | None:
|
|
163
|
+
"""Get the next node connected to the current parameter."""
|
|
164
|
+
next_node = connections.get_connected_node(current_node, current_parameter)
|
|
165
|
+
if next_node:
|
|
166
|
+
next_node, _ = next_node
|
|
167
|
+
return next_node
|
|
168
|
+
|
|
169
|
+
@staticmethod
|
|
170
|
+
def _check_for_cycle(next_node: BaseNode, current_node: BaseNode, focus_stack_names: set[str]) -> None:
|
|
171
|
+
"""Check if queuing next_node would create a cycle."""
|
|
172
|
+
if next_node.name in focus_stack_names:
|
|
173
|
+
msg = f"Cycle detected between node '{current_node.name}' and '{next_node.name}'."
|
|
174
|
+
raise RuntimeError(msg)
|
|
175
|
+
|
|
176
|
+
@staticmethod
|
|
177
|
+
def _handle_parent_already_resolved(current_node: BaseNode) -> type[State]:
|
|
178
|
+
"""Handle case where parent node group is already resolved."""
|
|
179
|
+
if current_node.advance_parameter():
|
|
180
|
+
return InitializeSpotlightState
|
|
181
|
+
return ExecuteNodeState
|
|
182
|
+
|
|
183
|
+
@staticmethod
|
|
184
|
+
def _check_node_group_external_dependencies(node_group: BaseNode) -> BaseNode | None:
|
|
185
|
+
"""Check if NodeGroup has unresolved external incoming connections.
|
|
186
|
+
|
|
187
|
+
Returns the first unresolved source node (or its parent if applicable) if found, None otherwise.
|
|
188
|
+
"""
|
|
189
|
+
if not isinstance(node_group, NodeGroupNode):
|
|
190
|
+
return None
|
|
191
|
+
|
|
192
|
+
for conn in node_group.stored_connections.external_connections.incoming_connections:
|
|
193
|
+
source_node = conn.source_node
|
|
194
|
+
if source_node.state == NodeResolutionState.UNRESOLVED:
|
|
195
|
+
# Check if source has a parent group to use instead
|
|
196
|
+
if source_node.parent_group is not None and isinstance(source_node.parent_group, NodeGroupNode):
|
|
197
|
+
execution_env = source_node.parent_group.get_parameter_value(
|
|
198
|
+
source_node.parent_group.execution_environment.name
|
|
199
|
+
)
|
|
200
|
+
if execution_env != LOCAL_EXECUTION:
|
|
201
|
+
return source_node.parent_group
|
|
202
|
+
return source_node
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
@staticmethod
|
|
206
|
+
def _determine_node_to_queue(
|
|
207
|
+
next_node: BaseNode, current_node: BaseNode, focus_stack_names: set[str]
|
|
208
|
+
) -> BaseNode | None:
|
|
209
|
+
"""Determine which node to queue - the next node or its parent group.
|
|
210
|
+
|
|
211
|
+
Returns None if the parent node group is already resolved.
|
|
212
|
+
"""
|
|
213
|
+
if next_node.parent_group is None or not isinstance(next_node.parent_group, NodeGroupNode):
|
|
214
|
+
return next_node
|
|
215
|
+
|
|
216
|
+
parent_group = next_node.parent_group
|
|
217
|
+
execution_env = parent_group.get_parameter_value(parent_group.execution_environment.name)
|
|
218
|
+
if execution_env == LOCAL_EXECUTION:
|
|
219
|
+
return next_node
|
|
220
|
+
|
|
221
|
+
if parent_group.state == NodeResolutionState.RESOLVED:
|
|
222
|
+
logger.info(
|
|
223
|
+
"Sequential Resolution: Parent node group '%s' is already resolved, skipping child node '%s' (execution environment: %s)",
|
|
224
|
+
parent_group.name,
|
|
225
|
+
next_node.name,
|
|
226
|
+
execution_env,
|
|
227
|
+
)
|
|
228
|
+
return None
|
|
229
|
+
|
|
230
|
+
if parent_group.name in focus_stack_names:
|
|
231
|
+
msg = f"Cycle detected: parent node group '{parent_group.name}' is already in focus stack while processing dependency for '{current_node.name}'."
|
|
232
|
+
raise RuntimeError(msg)
|
|
233
|
+
|
|
234
|
+
logger.info(
|
|
235
|
+
"Sequential Resolution: Queuing parent node group '%s' instead of child node '%s' (execution environment: %s) - child is a dependency of '%s'",
|
|
236
|
+
parent_group.name,
|
|
237
|
+
next_node.name,
|
|
238
|
+
execution_env,
|
|
239
|
+
current_node.name,
|
|
240
|
+
)
|
|
241
|
+
return parent_group
|
|
242
|
+
|
|
123
243
|
@staticmethod
|
|
124
244
|
async def on_update(context: ResolutionContext) -> type[State] | None:
|
|
245
|
+
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
246
|
+
|
|
125
247
|
current_node = context.current_node
|
|
126
248
|
current_parameter = current_node.get_current_parameter()
|
|
127
|
-
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
128
249
|
|
|
129
|
-
connections = GriptapeNodes.FlowManager().get_connections()
|
|
130
250
|
if current_parameter is None:
|
|
131
251
|
msg = "No current parameter set."
|
|
132
252
|
raise ValueError(msg)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
253
|
+
|
|
254
|
+
connections = GriptapeNodes.FlowManager().get_connections()
|
|
255
|
+
next_node = EvaluateParameterState._get_next_node(current_node, current_parameter, connections)
|
|
256
|
+
|
|
137
257
|
if next_node and next_node.state == NodeResolutionState.UNRESOLVED:
|
|
138
258
|
focus_stack_names = {focus.node.name for focus in context.focus_stack}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
259
|
+
EvaluateParameterState._check_for_cycle(next_node, current_node, focus_stack_names)
|
|
260
|
+
|
|
261
|
+
node_to_queue = EvaluateParameterState._determine_node_to_queue(next_node, current_node, focus_stack_names)
|
|
262
|
+
if node_to_queue is None:
|
|
263
|
+
return EvaluateParameterState._handle_parent_already_resolved(current_node)
|
|
142
264
|
|
|
143
|
-
context.focus_stack.append(Focus(node=
|
|
265
|
+
context.focus_stack.append(Focus(node=node_to_queue))
|
|
144
266
|
return InitializeSpotlightState
|
|
145
267
|
|
|
146
268
|
if current_node.advance_parameter():
|
|
@@ -120,11 +120,13 @@ class ConfigureAgentRequest(RequestPayload):
|
|
|
120
120
|
|
|
121
121
|
Args:
|
|
122
122
|
prompt_driver: Dictionary of prompt driver configuration options
|
|
123
|
+
image_generation_driver: Dictionary of image generation driver configuration options
|
|
123
124
|
|
|
124
125
|
Results: ConfigureAgentResultSuccess | ConfigureAgentResultFailure (configuration error)
|
|
125
126
|
"""
|
|
126
127
|
|
|
127
128
|
prompt_driver: dict = field(default_factory=dict)
|
|
129
|
+
image_generation_driver: dict = field(default_factory=dict)
|
|
128
130
|
|
|
129
131
|
|
|
130
132
|
@dataclass
|
|
@@ -4,7 +4,7 @@ from dataclasses import dataclass, field
|
|
|
4
4
|
from typing import TYPE_CHECKING, Any, NamedTuple
|
|
5
5
|
|
|
6
6
|
if TYPE_CHECKING:
|
|
7
|
-
from griptape_nodes.exe_types.node_types import NodeDependencies
|
|
7
|
+
from griptape_nodes.exe_types.node_types import NodeDependencies
|
|
8
8
|
from griptape_nodes.node_library.workflow_registry import LibraryNameAndNodeType, WorkflowShape
|
|
9
9
|
from griptape_nodes.retained_mode.events.node_events import SerializedNodeCommands, SetLockNodeStateRequest
|
|
10
10
|
from griptape_nodes.retained_mode.events.workflow_events import ImportWorkflowAsReferencedSubFlowRequest
|
|
@@ -428,7 +428,8 @@ class PackageNodesAsSerializedFlowRequest(RequestPayload):
|
|
|
428
428
|
node_names: List of node names to package as a flow (empty list will create StartFlow→EndFlow only with warning)
|
|
429
429
|
start_node_type: Node type name for the artificial start node (None or omitted defaults to "StartFlow")
|
|
430
430
|
end_node_type: Node type name for the artificial end node (None or omitted defaults to "EndFlow")
|
|
431
|
-
|
|
431
|
+
start_node_library_name: Library name containing the start node (defaults to "Griptape Nodes Library")
|
|
432
|
+
end_node_library_name: Library name containing the end node (defaults to "Griptape Nodes Library")
|
|
432
433
|
entry_control_node_name: Name of the node that should receive the control flow entry (required if entry_control_parameter_name specified)
|
|
433
434
|
entry_control_parameter_name: Name of the control parameter on the entry node (None for auto-detection of first available control parameter)
|
|
434
435
|
output_parameter_prefix: Prefix for parameter names on the generated end node to avoid collisions (defaults to "packaged_node_")
|
|
@@ -440,13 +441,11 @@ class PackageNodesAsSerializedFlowRequest(RequestPayload):
|
|
|
440
441
|
node_names: list[str] = field(default_factory=list)
|
|
441
442
|
start_node_type: str | None = None
|
|
442
443
|
end_node_type: str | None = None
|
|
443
|
-
|
|
444
|
+
start_node_library_name: str = "Griptape Nodes Library"
|
|
445
|
+
end_node_library_name: str = "Griptape Nodes Library"
|
|
444
446
|
entry_control_node_name: str | None = None
|
|
445
447
|
entry_control_parameter_name: str | None = None
|
|
446
448
|
output_parameter_prefix: str = "packaged_node_"
|
|
447
|
-
proxy_node: NodeGroupProxyNode | None = (
|
|
448
|
-
None # NodeGroupProxyNode if packaging nodes from a proxy, used to access original connections
|
|
449
|
-
)
|
|
450
449
|
|
|
451
450
|
|
|
452
451
|
@dataclass
|