griptape-nodes 0.63.10__py3-none-any.whl → 0.64.1__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 +400 -77
- griptape_nodes/retained_mode/managers/version_compatibility_manager.py +113 -69
- griptape_nodes/retained_mode/managers/workflow_manager.py +45 -10
- griptape_nodes/servers/mcp.py +32 -0
- griptape_nodes/version_compatibility/versions/v0_63_8/__init__.py +1 -0
- griptape_nodes/version_compatibility/versions/v0_63_8/deprecated_nodegroup_parameters.py +105 -0
- {griptape_nodes-0.63.10.dist-info → griptape_nodes-0.64.1.dist-info}/METADATA +3 -1
- {griptape_nodes-0.63.10.dist-info → griptape_nodes-0.64.1.dist-info}/RECORD +31 -28
- griptape_nodes/version_compatibility/workflow_versions/__init__.py +0 -1
- /griptape_nodes/version_compatibility/{workflow_versions → versions}/v0_7_0/__init__.py +0 -0
- /griptape_nodes/version_compatibility/{workflow_versions → versions}/v0_7_0/local_executor_argument_addition.py +0 -0
- {griptape_nodes-0.63.10.dist-info → griptape_nodes-0.64.1.dist-info}/WHEEL +0 -0
- {griptape_nodes-0.63.10.dist-info → griptape_nodes-0.64.1.dist-info}/entry_points.txt +0 -0
|
@@ -7,13 +7,13 @@ from typing import TYPE_CHECKING
|
|
|
7
7
|
|
|
8
8
|
from griptape_nodes.common.directed_graph import DirectedGraph
|
|
9
9
|
from griptape_nodes.exe_types.core_types import ParameterTypeBuiltin
|
|
10
|
-
from griptape_nodes.exe_types.node_types import NodeResolutionState
|
|
10
|
+
from griptape_nodes.exe_types.node_types import LOCAL_EXECUTION, NodeGroupNode, NodeResolutionState
|
|
11
11
|
|
|
12
12
|
if TYPE_CHECKING:
|
|
13
13
|
import asyncio
|
|
14
14
|
|
|
15
15
|
from griptape_nodes.exe_types.connections import Connections
|
|
16
|
-
from griptape_nodes.exe_types.node_types import BaseNode
|
|
16
|
+
from griptape_nodes.exe_types.node_types import BaseNode
|
|
17
17
|
|
|
18
18
|
logger = logging.getLogger("griptape_nodes")
|
|
19
19
|
|
|
@@ -50,6 +50,50 @@ class DagBuilder:
|
|
|
50
50
|
self.node_to_reference: dict[str, DagNode] = {}
|
|
51
51
|
self.graph_to_nodes = {}
|
|
52
52
|
|
|
53
|
+
@staticmethod
|
|
54
|
+
def _should_use_parent_group(node: BaseNode) -> bool:
|
|
55
|
+
"""Check if a node's parent group should be used for DAG edges.
|
|
56
|
+
|
|
57
|
+
Returns True if the node has a parent NodeGroupNode that is NOT in LOCAL_EXECUTION mode.
|
|
58
|
+
In LOCAL_EXECUTION mode, groups are transparent and children are treated as separate nodes.
|
|
59
|
+
"""
|
|
60
|
+
if not isinstance(node.parent_group, NodeGroupNode):
|
|
61
|
+
return False
|
|
62
|
+
parent_execution_env = node.parent_group.get_parameter_value(node.parent_group.execution_environment.name)
|
|
63
|
+
return parent_execution_env != LOCAL_EXECUTION
|
|
64
|
+
|
|
65
|
+
def _get_node_for_dag_edge(self, node: BaseNode, graph: DirectedGraph, graph_name: str) -> BaseNode:
|
|
66
|
+
"""Get the node to use for DAG edges - either the node itself or its parent group.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
node: The original node
|
|
70
|
+
graph: The graph being built
|
|
71
|
+
graph_name: Name of the graph for tracking
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
The node or parent group to use in DAG edges
|
|
75
|
+
"""
|
|
76
|
+
if self._should_use_parent_group(node):
|
|
77
|
+
parent_group = node.parent_group
|
|
78
|
+
if isinstance(parent_group, NodeGroupNode):
|
|
79
|
+
self._ensure_group_node_in_dag(parent_group, graph, graph_name)
|
|
80
|
+
return parent_group
|
|
81
|
+
return node
|
|
82
|
+
|
|
83
|
+
def _ensure_group_node_in_dag(self, group_node: NodeGroupNode, graph: DirectedGraph, graph_name: str) -> None:
|
|
84
|
+
"""Ensure a NodeGroupNode is added to the DAG if not already present.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
group_node: The NodeGroupNode to add
|
|
88
|
+
graph: The graph to add it to
|
|
89
|
+
graph_name: Name of the graph for tracking
|
|
90
|
+
"""
|
|
91
|
+
if group_node.name not in self.node_to_reference:
|
|
92
|
+
dag_node = DagNode(node_reference=group_node, node_state=NodeState.WAITING)
|
|
93
|
+
self.node_to_reference[group_node.name] = dag_node
|
|
94
|
+
graph.add_node(node_for_adding=group_node.name)
|
|
95
|
+
self.graph_to_nodes[graph_name].add(group_node.name)
|
|
96
|
+
|
|
53
97
|
# Complex with the inner recursive method, but it needs connections and added_nodes.
|
|
54
98
|
def add_node_with_dependencies(self, node: BaseNode, graph_name: str = "default") -> list[BaseNode]: # noqa: C901
|
|
55
99
|
"""Add node and all its dependencies to DAG. Returns list of added nodes."""
|
|
@@ -64,48 +108,62 @@ class DagBuilder:
|
|
|
64
108
|
self.graph_to_nodes[graph_name] = set()
|
|
65
109
|
|
|
66
110
|
def _add_node_recursive(current_node: BaseNode, visited: set[str], graph: DirectedGraph) -> None:
|
|
111
|
+
# Skip if already visited or already in DAG
|
|
67
112
|
if current_node.name in visited:
|
|
68
113
|
return
|
|
69
114
|
visited.add(current_node.name)
|
|
70
|
-
|
|
115
|
+
|
|
71
116
|
if current_node.name in self.node_to_reference:
|
|
72
117
|
return
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
118
|
+
|
|
119
|
+
# Check if we should ignore dependencies (for special nodes like output_selector)
|
|
120
|
+
ignore_data_dependencies = hasattr(current_node, "ignore_dependencies")
|
|
121
|
+
|
|
122
|
+
# Process all upstream dependencies first (depth-first traversal)
|
|
78
123
|
for param in current_node.parameters:
|
|
124
|
+
# Skip control flow parameters
|
|
79
125
|
if param.type == ParameterTypeBuiltin.CONTROL_TYPE:
|
|
80
126
|
continue
|
|
127
|
+
|
|
128
|
+
# Skip if node ignores dependencies
|
|
81
129
|
if ignore_data_dependencies:
|
|
82
130
|
continue
|
|
131
|
+
|
|
83
132
|
upstream_connection = connections.get_connected_node(current_node, param)
|
|
84
|
-
if upstream_connection:
|
|
85
|
-
|
|
86
|
-
# Don't add nodes that have already been resolved.
|
|
87
|
-
if upstream_node.state == NodeResolutionState.RESOLVED:
|
|
88
|
-
continue
|
|
89
|
-
# If upstream is already in DAG, skip creating edge (it's in another graph)
|
|
90
|
-
if upstream_node.name in self.node_to_reference:
|
|
91
|
-
graph.add_edge(upstream_node.name, current_node.name)
|
|
92
|
-
# Otherwise, add it to DAG first then create edge
|
|
93
|
-
else:
|
|
94
|
-
_add_node_recursive(upstream_node, visited, graph)
|
|
95
|
-
graph.add_edge(upstream_node.name, current_node.name)
|
|
133
|
+
if not upstream_connection:
|
|
134
|
+
continue
|
|
96
135
|
|
|
97
|
-
|
|
136
|
+
upstream_node, _ = upstream_connection
|
|
98
137
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
138
|
+
# Skip already resolved nodes
|
|
139
|
+
if upstream_node.state == NodeResolutionState.RESOLVED:
|
|
140
|
+
continue
|
|
141
|
+
|
|
142
|
+
# Check for internal group connections - traverse but don't add edge
|
|
143
|
+
is_internal_connection = (
|
|
144
|
+
self._should_use_parent_group(current_node)
|
|
145
|
+
and upstream_node.parent_group == current_node.parent_group
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Recursively add upstream node
|
|
149
|
+
_add_node_recursive(upstream_node, visited, graph)
|
|
102
150
|
|
|
103
|
-
|
|
104
|
-
|
|
151
|
+
# Add edge unless it's an internal group connection
|
|
152
|
+
if not is_internal_connection:
|
|
153
|
+
upstream_for_edge = self._get_node_for_dag_edge(upstream_node, graph, graph_name)
|
|
154
|
+
current_for_edge = self._get_node_for_dag_edge(current_node, graph, graph_name)
|
|
155
|
+
graph.add_edge(upstream_for_edge.name, current_for_edge.name)
|
|
105
156
|
|
|
106
|
-
#
|
|
157
|
+
# Always add current node to tracking (even if parent group is used for edges)
|
|
158
|
+
dag_node = DagNode(node_reference=current_node, node_state=NodeState.WAITING)
|
|
159
|
+
self.node_to_reference[current_node.name] = dag_node
|
|
107
160
|
added_nodes.append(current_node)
|
|
108
161
|
|
|
162
|
+
# Add to graph if not using parent group
|
|
163
|
+
if not self._should_use_parent_group(current_node):
|
|
164
|
+
graph.add_node(node_for_adding=current_node.name)
|
|
165
|
+
self.graph_to_nodes[graph_name].add(current_node.name)
|
|
166
|
+
|
|
109
167
|
_add_node_recursive(node, set(), graph)
|
|
110
168
|
|
|
111
169
|
return added_nodes
|
|
@@ -224,214 +282,3 @@ class DagBuilder:
|
|
|
224
282
|
for node_name in self.graph_to_nodes[graph_name]:
|
|
225
283
|
self.node_to_reference.pop(node_name, None)
|
|
226
284
|
self.graph_to_nodes.pop(graph_name, None)
|
|
227
|
-
|
|
228
|
-
def identify_and_process_node_groups(self) -> dict[str, BaseNode]:
|
|
229
|
-
"""Identify node groups, validate them, and replace with proxy nodes.
|
|
230
|
-
|
|
231
|
-
Scans all nodes in the DAG for non-empty node_group parameter values,
|
|
232
|
-
creates NodeGroup instances, validates they have no intermediate ungrouped nodes,
|
|
233
|
-
and replaces them with NodeGroupProxyNode instances in the DAG.
|
|
234
|
-
|
|
235
|
-
Returns:
|
|
236
|
-
Dictionary mapping group IDs to their proxy nodes
|
|
237
|
-
|
|
238
|
-
Raises:
|
|
239
|
-
ValueError: If validation fails (e.g., ungrouped nodes between grouped nodes)
|
|
240
|
-
"""
|
|
241
|
-
from griptape_nodes.retained_mode.griptape_nodes import GriptapeNodes
|
|
242
|
-
|
|
243
|
-
connections = GriptapeNodes.FlowManager().get_connections()
|
|
244
|
-
|
|
245
|
-
# Step 1: Identify groups by scanning all nodes
|
|
246
|
-
groups = self._identify_node_groups(connections)
|
|
247
|
-
|
|
248
|
-
if not groups:
|
|
249
|
-
return {}
|
|
250
|
-
|
|
251
|
-
# Step 2: Validate each group
|
|
252
|
-
for group in groups.values():
|
|
253
|
-
group.validate_no_intermediate_nodes(connections.connections)
|
|
254
|
-
|
|
255
|
-
# Step 3: Create proxy nodes and replace groups in DAG
|
|
256
|
-
proxy_nodes = {}
|
|
257
|
-
for group_id, group in groups.items():
|
|
258
|
-
proxy_node = self._create_and_install_proxy_node(group_id, group, connections)
|
|
259
|
-
proxy_nodes[group_id] = proxy_node
|
|
260
|
-
|
|
261
|
-
return proxy_nodes
|
|
262
|
-
|
|
263
|
-
def _identify_node_groups(self, connections: Connections) -> dict[str, NodeGroup]:
|
|
264
|
-
"""Identify and build NodeGroup instances from nodes in the DAG.
|
|
265
|
-
|
|
266
|
-
Args:
|
|
267
|
-
connections: Connections object for analyzing connection topology
|
|
268
|
-
|
|
269
|
-
Returns:
|
|
270
|
-
Dictionary mapping group IDs to NodeGroup instances
|
|
271
|
-
"""
|
|
272
|
-
from griptape_nodes.exe_types.node_types import NodeGroup
|
|
273
|
-
|
|
274
|
-
groups: dict[str, NodeGroup] = {}
|
|
275
|
-
|
|
276
|
-
# Scan all nodes in DAG for group membership
|
|
277
|
-
for dag_node in self.node_to_reference.values():
|
|
278
|
-
node = dag_node.node_reference
|
|
279
|
-
group_id = node.get_parameter_value("job_group")
|
|
280
|
-
|
|
281
|
-
# Skip nodes without group assignment or empty group ID
|
|
282
|
-
if not group_id or group_id == "":
|
|
283
|
-
continue
|
|
284
|
-
|
|
285
|
-
# Create group if it doesn't exist
|
|
286
|
-
if group_id not in groups:
|
|
287
|
-
groups[group_id] = NodeGroup(group_id=group_id)
|
|
288
|
-
|
|
289
|
-
# Add node to group
|
|
290
|
-
groups[group_id].add_node(node)
|
|
291
|
-
|
|
292
|
-
# Analyze connections for each group
|
|
293
|
-
for group in groups.values():
|
|
294
|
-
self._analyze_group_connections(group, connections)
|
|
295
|
-
|
|
296
|
-
return groups
|
|
297
|
-
|
|
298
|
-
def _analyze_group_connections(self, group: NodeGroup, connections: Connections) -> None:
|
|
299
|
-
"""Analyze and categorize connections for a node group.
|
|
300
|
-
|
|
301
|
-
Categorizes all connections involving group nodes as either:
|
|
302
|
-
- Internal: Both endpoints within the group
|
|
303
|
-
- External incoming: From outside node to group node
|
|
304
|
-
- External outgoing: From group node to outside node
|
|
305
|
-
|
|
306
|
-
Args:
|
|
307
|
-
group: NodeGroup to analyze
|
|
308
|
-
connections: Connections object containing all flow connections
|
|
309
|
-
"""
|
|
310
|
-
node_names_in_group = set(group.nodes.keys())
|
|
311
|
-
|
|
312
|
-
# Analyze all connections in the flow
|
|
313
|
-
for conn in connections.connections.values():
|
|
314
|
-
source_in_group = conn.source_node.name in node_names_in_group
|
|
315
|
-
target_in_group = conn.target_node.name in node_names_in_group
|
|
316
|
-
|
|
317
|
-
if source_in_group and target_in_group:
|
|
318
|
-
# Both endpoints in group - internal connection
|
|
319
|
-
group.internal_connections.append(conn)
|
|
320
|
-
elif source_in_group and not target_in_group:
|
|
321
|
-
# From group to outside - external outgoing
|
|
322
|
-
group.external_outgoing_connections.append(conn)
|
|
323
|
-
elif not source_in_group and target_in_group:
|
|
324
|
-
# From outside to group - external incoming
|
|
325
|
-
group.external_incoming_connections.append(conn)
|
|
326
|
-
|
|
327
|
-
def _create_and_install_proxy_node(self, group_id: str, group: NodeGroup, connections: Connections) -> BaseNode:
|
|
328
|
-
"""Create a proxy node for a group and install it in the DAG.
|
|
329
|
-
|
|
330
|
-
Creates a NodeGroupProxyNode, adds it to the DAG, remaps all external
|
|
331
|
-
connections to point to the proxy, and removes the original grouped
|
|
332
|
-
nodes from the DAG.
|
|
333
|
-
|
|
334
|
-
Args:
|
|
335
|
-
group_id: Unique identifier for the group
|
|
336
|
-
group: NodeGroup instance to replace
|
|
337
|
-
connections: Connections object for remapping connections
|
|
338
|
-
|
|
339
|
-
Returns:
|
|
340
|
-
The created NodeGroupProxyNode
|
|
341
|
-
"""
|
|
342
|
-
from griptape_nodes.exe_types.node_types import NodeGroupProxyNode
|
|
343
|
-
|
|
344
|
-
# Create proxy node with unique name
|
|
345
|
-
proxy_name = f"__group_proxy_{group_id}"
|
|
346
|
-
proxy_node = NodeGroupProxyNode(name=proxy_name, node_group=group)
|
|
347
|
-
|
|
348
|
-
# Determine which graph to add proxy to (use first grouped node's graph)
|
|
349
|
-
target_graph_name = None
|
|
350
|
-
for graph_name, node_set in self.graph_to_nodes.items():
|
|
351
|
-
if any(node_name in node_set for node_name in group.nodes):
|
|
352
|
-
target_graph_name = graph_name
|
|
353
|
-
break
|
|
354
|
-
|
|
355
|
-
if target_graph_name is None:
|
|
356
|
-
target_graph_name = "default"
|
|
357
|
-
|
|
358
|
-
# Add proxy node to DAG
|
|
359
|
-
self.add_node(proxy_node, target_graph_name)
|
|
360
|
-
|
|
361
|
-
# Remap external connections to proxy
|
|
362
|
-
self._remap_connections_to_proxy(group, proxy_node, connections)
|
|
363
|
-
|
|
364
|
-
# Remove grouped nodes from DAG
|
|
365
|
-
self._remove_grouped_nodes_from_dag(group)
|
|
366
|
-
|
|
367
|
-
return proxy_node
|
|
368
|
-
|
|
369
|
-
def _remap_connections_to_proxy(self, group: NodeGroup, proxy_node: BaseNode, connections: Connections) -> None:
|
|
370
|
-
"""Remap external connections from group nodes to the proxy node.
|
|
371
|
-
|
|
372
|
-
Updates the connection indices and Connection objects to redirect
|
|
373
|
-
external connections through the proxy node instead of the original
|
|
374
|
-
grouped nodes.
|
|
375
|
-
|
|
376
|
-
Args:
|
|
377
|
-
group: NodeGroup being replaced
|
|
378
|
-
proxy_node: Proxy node that will handle external connections
|
|
379
|
-
connections: Connections object to update
|
|
380
|
-
"""
|
|
381
|
-
# Remap external incoming connections (from outside -> group becomes outside -> proxy)
|
|
382
|
-
for conn in group.external_incoming_connections:
|
|
383
|
-
conn_id = id(conn)
|
|
384
|
-
|
|
385
|
-
# Remove old incoming index entry
|
|
386
|
-
if (
|
|
387
|
-
conn.target_node.name in connections.incoming_index
|
|
388
|
-
and conn.target_parameter.name in connections.incoming_index[conn.target_node.name]
|
|
389
|
-
):
|
|
390
|
-
connections.incoming_index[conn.target_node.name][conn.target_parameter.name].remove(conn_id)
|
|
391
|
-
|
|
392
|
-
# Update connection target to proxy
|
|
393
|
-
conn.target_node = proxy_node
|
|
394
|
-
|
|
395
|
-
# Add new incoming index entry
|
|
396
|
-
connections.incoming_index.setdefault(proxy_node.name, {}).setdefault(
|
|
397
|
-
conn.target_parameter.name, []
|
|
398
|
-
).append(conn_id)
|
|
399
|
-
|
|
400
|
-
# Remap external outgoing connections (group -> outside becomes proxy -> outside)
|
|
401
|
-
for conn in group.external_outgoing_connections:
|
|
402
|
-
conn_id = id(conn)
|
|
403
|
-
|
|
404
|
-
# Remove old outgoing index entry
|
|
405
|
-
if (
|
|
406
|
-
conn.source_node.name in connections.outgoing_index
|
|
407
|
-
and conn.source_parameter.name in connections.outgoing_index[conn.source_node.name]
|
|
408
|
-
):
|
|
409
|
-
connections.outgoing_index[conn.source_node.name][conn.source_parameter.name].remove(conn_id)
|
|
410
|
-
|
|
411
|
-
# Update connection source to proxy
|
|
412
|
-
conn.source_node = proxy_node
|
|
413
|
-
|
|
414
|
-
# Add new outgoing index entry
|
|
415
|
-
connections.outgoing_index.setdefault(proxy_node.name, {}).setdefault(
|
|
416
|
-
conn.source_parameter.name, []
|
|
417
|
-
).append(conn_id)
|
|
418
|
-
|
|
419
|
-
def _remove_grouped_nodes_from_dag(self, group: NodeGroup) -> None:
|
|
420
|
-
"""Remove all nodes in a group from the DAG graphs and references.
|
|
421
|
-
|
|
422
|
-
Args:
|
|
423
|
-
group: NodeGroup whose nodes should be removed from the DAG
|
|
424
|
-
"""
|
|
425
|
-
for node_name in group.nodes:
|
|
426
|
-
# Remove from node_to_reference
|
|
427
|
-
if node_name in self.node_to_reference:
|
|
428
|
-
del self.node_to_reference[node_name]
|
|
429
|
-
|
|
430
|
-
# Remove from all graphs
|
|
431
|
-
for graph in self.graphs.values():
|
|
432
|
-
if node_name in graph.nodes():
|
|
433
|
-
graph.remove_node(node_name)
|
|
434
|
-
|
|
435
|
-
# Remove from graph_to_nodes tracking
|
|
436
|
-
for node_set in self.graph_to_nodes.values():
|
|
437
|
-
node_set.discard(node_name)
|