griptape-nodes 0.67.0__py3-none-any.whl → 0.68.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/bootstrap/utils/python_subprocess_executor.py +17 -4
- griptape_nodes/common/node_executor.py +218 -18
- griptape_nodes/exe_types/node_groups/__init__.py +2 -2
- griptape_nodes/exe_types/node_groups/base_iterative_node_group.py +54 -2
- griptape_nodes/exe_types/param_components/huggingface/huggingface_repo_variant_parameter.py +152 -0
- griptape_nodes/retained_mode/managers/flow_manager.py +2 -1
- {griptape_nodes-0.67.0.dist-info → griptape_nodes-0.68.0.dist-info}/METADATA +1 -1
- {griptape_nodes-0.67.0.dist-info → griptape_nodes-0.68.0.dist-info}/RECORD +10 -9
- {griptape_nodes-0.67.0.dist-info → griptape_nodes-0.68.0.dist-info}/WHEEL +1 -1
- {griptape_nodes-0.67.0.dist-info → griptape_nodes-0.68.0.dist-info}/entry_points.txt +0 -0
|
@@ -2,16 +2,24 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
|
+
import os
|
|
5
6
|
import sys
|
|
6
7
|
from typing import TYPE_CHECKING, Any
|
|
7
8
|
|
|
8
9
|
if TYPE_CHECKING:
|
|
9
10
|
from pathlib import Path
|
|
10
|
-
from subprocess import _ENV
|
|
11
11
|
|
|
12
12
|
logger = logging.getLogger(__name__)
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
def _create_subprocess_env(extra_env: dict[str, str] | None = None) -> dict[str, str]:
|
|
16
|
+
"""Create environment for subprocess, inheriting parent env with optional overrides."""
|
|
17
|
+
env = os.environ.copy()
|
|
18
|
+
if extra_env:
|
|
19
|
+
env.update(extra_env)
|
|
20
|
+
return env
|
|
21
|
+
|
|
22
|
+
|
|
15
23
|
class PythonSubprocessExecutorError(Exception):
|
|
16
24
|
"""Exception raised during Python subprocess execution."""
|
|
17
25
|
|
|
@@ -22,7 +30,11 @@ class PythonSubprocessExecutor:
|
|
|
22
30
|
self._is_running = False
|
|
23
31
|
|
|
24
32
|
async def execute_python_script(
|
|
25
|
-
self,
|
|
33
|
+
self,
|
|
34
|
+
script_path: Path,
|
|
35
|
+
args: list[str] | None = None,
|
|
36
|
+
cwd: Path | None = None,
|
|
37
|
+
env: dict[str, str] | None = None,
|
|
26
38
|
) -> None:
|
|
27
39
|
"""Execute a Python script in a subprocess and wait for completion.
|
|
28
40
|
|
|
@@ -30,7 +42,7 @@ class PythonSubprocessExecutor:
|
|
|
30
42
|
script_path: Path to the Python script to execute
|
|
31
43
|
args: Additional command line arguments
|
|
32
44
|
cwd: Working directory for the subprocess
|
|
33
|
-
env:
|
|
45
|
+
env: Extra environment variables to add or override in the subprocess
|
|
34
46
|
"""
|
|
35
47
|
if self.is_running():
|
|
36
48
|
logger.warning("Another subprocess is already running. Terminating it first.")
|
|
@@ -38,6 +50,7 @@ class PythonSubprocessExecutor:
|
|
|
38
50
|
|
|
39
51
|
args = args or []
|
|
40
52
|
command = [sys.executable, str(script_path), *args]
|
|
53
|
+
subprocess_env = _create_subprocess_env(env)
|
|
41
54
|
|
|
42
55
|
try:
|
|
43
56
|
logger.info("Starting subprocess: %s", " ".join(command))
|
|
@@ -46,7 +59,7 @@ class PythonSubprocessExecutor:
|
|
|
46
59
|
self._process = await asyncio.create_subprocess_exec(
|
|
47
60
|
*command,
|
|
48
61
|
cwd=cwd,
|
|
49
|
-
env=
|
|
62
|
+
env=subprocess_env,
|
|
50
63
|
stdout=asyncio.subprocess.PIPE,
|
|
51
64
|
stderr=asyncio.subprocess.PIPE,
|
|
52
65
|
)
|
|
@@ -5,6 +5,7 @@ import asyncio
|
|
|
5
5
|
import logging
|
|
6
6
|
import pickle
|
|
7
7
|
from dataclasses import dataclass
|
|
8
|
+
from enum import StrEnum
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
from typing import TYPE_CHECKING, Any, NamedTuple
|
|
10
11
|
|
|
@@ -16,7 +17,7 @@ from griptape_nodes.exe_types.base_iterative_nodes import (
|
|
|
16
17
|
BaseIterativeStartNode,
|
|
17
18
|
)
|
|
18
19
|
from griptape_nodes.exe_types.core_types import ParameterTypeBuiltin
|
|
19
|
-
from griptape_nodes.exe_types.node_groups import BaseIterativeNodeGroup, SubflowNodeGroup
|
|
20
|
+
from griptape_nodes.exe_types.node_groups import BaseIterativeNodeGroup, IterationControlParam, SubflowNodeGroup
|
|
20
21
|
from griptape_nodes.exe_types.node_types import (
|
|
21
22
|
CONTROL_INPUT_PARAMETER,
|
|
22
23
|
LOCAL_EXECUTION,
|
|
@@ -106,6 +107,15 @@ if TYPE_CHECKING:
|
|
|
106
107
|
|
|
107
108
|
logger = logging.getLogger("griptape_nodes")
|
|
108
109
|
|
|
110
|
+
|
|
111
|
+
class IterationControlAction(StrEnum):
|
|
112
|
+
"""Enum for iterative group control actions."""
|
|
113
|
+
|
|
114
|
+
ADD = "add" # Normal path - add result to list
|
|
115
|
+
SKIP = "skip" # Skip this iteration, don't add result
|
|
116
|
+
BREAK = "break" # Break out of loop immediately
|
|
117
|
+
|
|
118
|
+
|
|
109
119
|
LOOP_EVENTS_TO_SUPPRESS = {
|
|
110
120
|
CreateFlowResultSuccess,
|
|
111
121
|
CreateFlowResultFailure,
|
|
@@ -816,13 +826,153 @@ class NodeExecutor:
|
|
|
816
826
|
# Check if it's the break signal
|
|
817
827
|
return next_control_output == deserialized_end_node.break_loop_signal_output
|
|
818
828
|
|
|
819
|
-
|
|
829
|
+
def _get_iteration_control_action_for_group(
|
|
830
|
+
self,
|
|
831
|
+
end_loop_node: BaseIterativeNodeGroup,
|
|
832
|
+
node_name_mappings: dict[str, str],
|
|
833
|
+
) -> IterationControlAction:
|
|
834
|
+
"""Determine which control action was taken during iteration for an iterative group.
|
|
835
|
+
|
|
836
|
+
Checks if any internal nodes have executed their control outputs that connect to the
|
|
837
|
+
group's control parameters (loop_complete, skip_iteration, break_loop). This is done by
|
|
838
|
+
checking the source node's parameter_output_values for CONTROL_INPUT_PARAMETER.
|
|
839
|
+
|
|
840
|
+
Args:
|
|
841
|
+
end_loop_node: The BaseIterativeNodeGroup being executed
|
|
842
|
+
node_name_mappings: Mapping from original to deserialized node names
|
|
843
|
+
|
|
844
|
+
Returns:
|
|
845
|
+
IterationControlAction indicating which control path was taken
|
|
846
|
+
"""
|
|
847
|
+
# Get incoming connections to the end_loop_node (the iterative group)
|
|
848
|
+
list_connections_request = ListConnectionsForNodeRequest(node_name=end_loop_node.name)
|
|
849
|
+
list_connections_result = GriptapeNodes.handle_request(list_connections_request)
|
|
850
|
+
if not isinstance(list_connections_result, ListConnectionsForNodeResultSuccess):
|
|
851
|
+
logger.warning("Failed to list connections for node %s", end_loop_node.name)
|
|
852
|
+
return IterationControlAction.ADD
|
|
853
|
+
|
|
854
|
+
incoming_connections = list_connections_result.incoming_connections
|
|
855
|
+
|
|
856
|
+
# Check each control parameter to see if its source node has fired
|
|
857
|
+
# Priority: BREAK > SKIP > LOOP_COMPLETE > default ADD
|
|
858
|
+
break_source = self._find_source_for_control_param(incoming_connections, IterationControlParam.BREAK_LOOP)
|
|
859
|
+
skip_source = self._find_source_for_control_param(incoming_connections, IterationControlParam.SKIP_ITERATION)
|
|
860
|
+
loop_complete_source = self._find_source_for_control_param(
|
|
861
|
+
incoming_connections, IterationControlParam.LOOP_COMPLETE
|
|
862
|
+
)
|
|
863
|
+
|
|
864
|
+
# Check if break was triggered
|
|
865
|
+
if self._check_control_source_fired(break_source, node_name_mappings):
|
|
866
|
+
return IterationControlAction.BREAK
|
|
867
|
+
|
|
868
|
+
# Check if skip was triggered
|
|
869
|
+
if self._check_control_source_fired(skip_source, node_name_mappings):
|
|
870
|
+
return IterationControlAction.SKIP
|
|
871
|
+
|
|
872
|
+
# Check if loop_complete was triggered
|
|
873
|
+
if self._check_control_source_fired(loop_complete_source, node_name_mappings):
|
|
874
|
+
return IterationControlAction.ADD
|
|
875
|
+
|
|
876
|
+
# If none of the control parameters were triggered, default to ADD
|
|
877
|
+
# This preserves backward compatibility for workflows without explicit loop_complete connections
|
|
878
|
+
return IterationControlAction.ADD
|
|
879
|
+
|
|
880
|
+
def _check_control_source_fired(
|
|
881
|
+
self,
|
|
882
|
+
source: tuple[str, str] | None,
|
|
883
|
+
node_name_mappings: dict[str, str],
|
|
884
|
+
) -> bool:
|
|
885
|
+
"""Check if a control source node has fired its control output.
|
|
886
|
+
|
|
887
|
+
Args:
|
|
888
|
+
source: Tuple of (source_node_name, source_parameter_name) or None
|
|
889
|
+
node_name_mappings: Mapping from original to deserialized node names
|
|
890
|
+
|
|
891
|
+
Returns:
|
|
892
|
+
True if the source node's next control output matches the specified parameter
|
|
893
|
+
"""
|
|
894
|
+
if source is None:
|
|
895
|
+
return False
|
|
896
|
+
|
|
897
|
+
source_node_name, source_param_name = source
|
|
898
|
+
deserialized_source_name = node_name_mappings.get(source_node_name)
|
|
899
|
+
if deserialized_source_name is None:
|
|
900
|
+
return False
|
|
901
|
+
|
|
902
|
+
node_manager = GriptapeNodes.NodeManager()
|
|
903
|
+
try:
|
|
904
|
+
deserialized_source_node = node_manager.get_node_by_name(deserialized_source_name)
|
|
905
|
+
except ValueError:
|
|
906
|
+
return False
|
|
907
|
+
|
|
908
|
+
if deserialized_source_node is None:
|
|
909
|
+
return False
|
|
910
|
+
|
|
911
|
+
# Check if the node's next control output matches the source parameter
|
|
912
|
+
next_control_output = deserialized_source_node.get_next_control_output()
|
|
913
|
+
if next_control_output is None:
|
|
914
|
+
return False
|
|
915
|
+
|
|
916
|
+
# Get the parameter object to compare
|
|
917
|
+
source_param = deserialized_source_node.get_parameter_by_name(source_param_name)
|
|
918
|
+
return next_control_output == source_param
|
|
919
|
+
|
|
920
|
+
def _find_source_for_control_param(
|
|
921
|
+
self,
|
|
922
|
+
incoming_connections: list,
|
|
923
|
+
control_param_name: str,
|
|
924
|
+
) -> tuple[str, str] | None:
|
|
925
|
+
"""Find the source node and parameter that connects to a control parameter on the iterative group.
|
|
926
|
+
|
|
927
|
+
Args:
|
|
928
|
+
incoming_connections: List of incoming connections to the iterative group
|
|
929
|
+
control_param_name: Name of the control parameter to find (e.g., "break_loop")
|
|
930
|
+
|
|
931
|
+
Returns:
|
|
932
|
+
Tuple of (source_node_name, source_parameter_name), or None if not found
|
|
933
|
+
"""
|
|
934
|
+
flow_manager = GriptapeNodes.FlowManager()
|
|
935
|
+
connections = flow_manager.get_connections()
|
|
936
|
+
|
|
937
|
+
for conn in incoming_connections:
|
|
938
|
+
if conn.target_parameter_name != control_param_name:
|
|
939
|
+
continue
|
|
940
|
+
|
|
941
|
+
source_node_name = conn.source_node_name
|
|
942
|
+
source_param_name = conn.source_parameter_name
|
|
943
|
+
|
|
944
|
+
# If source is a SubflowNodeGroup, follow the internal connection to get the actual source
|
|
945
|
+
node_manager = GriptapeNodes.NodeManager()
|
|
946
|
+
try:
|
|
947
|
+
source_node = node_manager.get_node_by_name(source_node_name)
|
|
948
|
+
except ValueError:
|
|
949
|
+
continue
|
|
950
|
+
|
|
951
|
+
if isinstance(source_node, SubflowNodeGroup):
|
|
952
|
+
# Get connections to this proxy parameter to find the actual internal source
|
|
953
|
+
proxy_param = source_node.get_parameter_by_name(source_param_name)
|
|
954
|
+
if proxy_param:
|
|
955
|
+
internal_connections = connections.get_all_incoming_connections(source_node)
|
|
956
|
+
for internal_conn in internal_connections:
|
|
957
|
+
if (
|
|
958
|
+
internal_conn.target_parameter.name == source_param_name
|
|
959
|
+
and internal_conn.is_node_group_internal
|
|
960
|
+
):
|
|
961
|
+
source_node_name = internal_conn.source_node.name
|
|
962
|
+
source_param_name = internal_conn.source_parameter.name
|
|
963
|
+
break
|
|
964
|
+
|
|
965
|
+
return (source_node_name, source_param_name)
|
|
966
|
+
|
|
967
|
+
return None
|
|
968
|
+
|
|
969
|
+
async def _execute_loop_iterations_sequentially( # noqa: PLR0915, C901, PLR0912
|
|
820
970
|
self,
|
|
821
971
|
package_result: PackageNodesAsSerializedFlowResultSuccess,
|
|
822
972
|
total_iterations: int,
|
|
823
973
|
parameter_values_per_iteration: dict[int, dict[str, Any]],
|
|
824
974
|
end_loop_node: BaseIterativeEndNode | BaseIterativeNodeGroup,
|
|
825
|
-
) -> tuple[dict[int, Any], list[int], dict[str, Any]]:
|
|
975
|
+
) -> tuple[dict[int, Any], list[int], dict[str, Any], int, bool]:
|
|
826
976
|
"""Execute loop iterations sequentially by running one flow instance N times.
|
|
827
977
|
|
|
828
978
|
Args:
|
|
@@ -834,8 +984,10 @@ class NodeExecutor:
|
|
|
834
984
|
Returns:
|
|
835
985
|
Tuple of:
|
|
836
986
|
- iteration_results: Dict mapping iteration_index -> result value
|
|
837
|
-
- successful_iterations: List of iteration indices that
|
|
987
|
+
- successful_iterations: List of iteration indices that executed without error
|
|
838
988
|
- last_iteration_values: Dict mapping parameter names -> values from last iteration
|
|
989
|
+
- skipped_count: Number of iterations that were skipped via skip control signal
|
|
990
|
+
- break_occurred: True if the loop exited early due to a break signal
|
|
839
991
|
"""
|
|
840
992
|
# Deserialize flow once
|
|
841
993
|
context_manager = GriptapeNodes.ContextManager()
|
|
@@ -868,6 +1020,8 @@ class NodeExecutor:
|
|
|
868
1020
|
|
|
869
1021
|
iteration_results: dict[int, Any] = {}
|
|
870
1022
|
successful_iterations: list[int] = []
|
|
1023
|
+
skipped_count = 0
|
|
1024
|
+
break_occurred = False
|
|
871
1025
|
|
|
872
1026
|
# Build reverse mapping: packaged_name → original_name for event translation
|
|
873
1027
|
reverse_node_mapping = {
|
|
@@ -927,6 +1081,39 @@ class NodeExecutor:
|
|
|
927
1081
|
|
|
928
1082
|
successful_iterations.append(iteration_index)
|
|
929
1083
|
|
|
1084
|
+
# For BaseIterativeNodeGroup, check control action to handle skip/break
|
|
1085
|
+
if isinstance(end_loop_node, BaseIterativeNodeGroup):
|
|
1086
|
+
control_action = self._get_iteration_control_action_for_group(end_loop_node, node_name_mappings)
|
|
1087
|
+
|
|
1088
|
+
if control_action == IterationControlAction.SKIP:
|
|
1089
|
+
logger.info(
|
|
1090
|
+
"Skip detected at iteration %d/%d - skipping result collection",
|
|
1091
|
+
iteration_index + 1,
|
|
1092
|
+
total_iterations,
|
|
1093
|
+
)
|
|
1094
|
+
skipped_count += 1
|
|
1095
|
+
continue
|
|
1096
|
+
|
|
1097
|
+
if control_action == IterationControlAction.BREAK:
|
|
1098
|
+
logger.info(
|
|
1099
|
+
"Break detected at iteration %d/%d - collecting result then stopping",
|
|
1100
|
+
iteration_index + 1,
|
|
1101
|
+
total_iterations,
|
|
1102
|
+
)
|
|
1103
|
+
# Extract result from this iteration before breaking
|
|
1104
|
+
# (the work was done, so we should collect the result)
|
|
1105
|
+
deserialized_flows = [(iteration_index, flow_name, node_name_mappings)]
|
|
1106
|
+
single_iteration_results = self.get_parameter_values_from_iterations(
|
|
1107
|
+
end_loop_node=end_loop_node,
|
|
1108
|
+
deserialized_flows=deserialized_flows,
|
|
1109
|
+
package_flow_result_success=package_result,
|
|
1110
|
+
)
|
|
1111
|
+
iteration_results.update(single_iteration_results)
|
|
1112
|
+
break_occurred = True
|
|
1113
|
+
break
|
|
1114
|
+
|
|
1115
|
+
# control_action == ADD: fall through to extract result
|
|
1116
|
+
|
|
930
1117
|
# Extract result from this iteration
|
|
931
1118
|
deserialized_flows = [(iteration_index, flow_name, node_name_mappings)]
|
|
932
1119
|
single_iteration_results = self.get_parameter_values_from_iterations(
|
|
@@ -938,13 +1125,14 @@ class NodeExecutor:
|
|
|
938
1125
|
|
|
939
1126
|
logger.info("Completed sequential iteration %d/%d", iteration_index + 1, total_iterations)
|
|
940
1127
|
|
|
941
|
-
# Check if the end node signaled a break
|
|
1128
|
+
# Check if the end node signaled a break (for BaseIterativeEndNode)
|
|
942
1129
|
if self._should_break_loop(node_name_mappings, package_result):
|
|
943
1130
|
logger.info(
|
|
944
1131
|
"Loop break detected at iteration %d/%d - stopping execution early",
|
|
945
1132
|
iteration_index + 1,
|
|
946
1133
|
total_iterations,
|
|
947
1134
|
)
|
|
1135
|
+
break_occurred = True
|
|
948
1136
|
break
|
|
949
1137
|
|
|
950
1138
|
# Extract last iteration values from the last successful iteration
|
|
@@ -956,7 +1144,7 @@ class NodeExecutor:
|
|
|
956
1144
|
total_iterations=len(successful_iterations),
|
|
957
1145
|
)
|
|
958
1146
|
|
|
959
|
-
return iteration_results, successful_iterations, last_iteration_values
|
|
1147
|
+
return iteration_results, successful_iterations, last_iteration_values, skipped_count, break_occurred
|
|
960
1148
|
|
|
961
1149
|
finally:
|
|
962
1150
|
# Cleanup - delete the flow
|
|
@@ -1018,6 +1206,8 @@ class NodeExecutor:
|
|
|
1018
1206
|
iteration_results,
|
|
1019
1207
|
successful_iterations,
|
|
1020
1208
|
last_iteration_values,
|
|
1209
|
+
_skipped_count,
|
|
1210
|
+
_break_occurred,
|
|
1021
1211
|
) = await self._execute_loop_iterations_sequentially(
|
|
1022
1212
|
package_result=package_result,
|
|
1023
1213
|
total_iterations=total_iterations,
|
|
@@ -1383,12 +1573,16 @@ class NodeExecutor:
|
|
|
1383
1573
|
parameter_values_per_iteration = self._get_merged_parameter_values_for_iterative_group(node, package_result)
|
|
1384
1574
|
|
|
1385
1575
|
# Execute iterations sequentially based on execution environment
|
|
1576
|
+
skipped_count = 0
|
|
1577
|
+
break_occurred = False
|
|
1386
1578
|
match execution_type:
|
|
1387
1579
|
case node_types.LOCAL_EXECUTION:
|
|
1388
1580
|
(
|
|
1389
1581
|
iteration_results,
|
|
1390
1582
|
successful_iterations,
|
|
1391
1583
|
last_iteration_values,
|
|
1584
|
+
skipped_count,
|
|
1585
|
+
break_occurred,
|
|
1392
1586
|
) = await self._execute_loop_iterations_sequentially(
|
|
1393
1587
|
package_result=package_result,
|
|
1394
1588
|
total_iterations=total_iterations,
|
|
@@ -1420,19 +1614,25 @@ class NodeExecutor:
|
|
|
1420
1614
|
execution_type=execution_type,
|
|
1421
1615
|
)
|
|
1422
1616
|
|
|
1423
|
-
# Check if execution stopped early
|
|
1617
|
+
# Check if execution stopped early
|
|
1424
1618
|
if len(successful_iterations) < total_iterations:
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1619
|
+
if break_occurred:
|
|
1620
|
+
# Early exit due to break signal - this is expected behavior
|
|
1621
|
+
logger.info(
|
|
1622
|
+
"Iterative group execution stopped early at %d of %d iterations (break signal)",
|
|
1623
|
+
len(successful_iterations),
|
|
1624
|
+
total_iterations,
|
|
1625
|
+
)
|
|
1626
|
+
else:
|
|
1627
|
+
# Early exit not due to break - check for failures
|
|
1628
|
+
# successful_iterations includes skipped iterations, but iteration_results does not
|
|
1629
|
+
# So we need to account for skipped iterations when checking for failures
|
|
1630
|
+
expected_result_count = len(successful_iterations) - skipped_count
|
|
1631
|
+
actual_result_count = len(iteration_results)
|
|
1632
|
+
if expected_result_count != actual_result_count:
|
|
1633
|
+
failed_count = expected_result_count - actual_result_count
|
|
1634
|
+
msg = f"Iterative group execution failed: {failed_count} of {len(successful_iterations)} iterations failed"
|
|
1635
|
+
raise RuntimeError(msg)
|
|
1436
1636
|
|
|
1437
1637
|
# Build results list in iteration order
|
|
1438
1638
|
node._results_list = []
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Node group implementations for managing collections of nodes."""
|
|
2
2
|
|
|
3
|
-
from .base_iterative_node_group import BaseIterativeNodeGroup
|
|
3
|
+
from .base_iterative_node_group import BaseIterativeNodeGroup, IterationControlParam
|
|
4
4
|
from .base_node_group import BaseNodeGroup
|
|
5
5
|
from .subflow_node_group import SubflowNodeGroup
|
|
6
6
|
|
|
7
|
-
__all__ = ["BaseIterativeNodeGroup", "BaseNodeGroup", "SubflowNodeGroup"]
|
|
7
|
+
__all__ = ["BaseIterativeNodeGroup", "BaseNodeGroup", "IterationControlParam", "SubflowNodeGroup"]
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from abc import abstractmethod
|
|
6
|
+
from enum import StrEnum
|
|
6
7
|
from typing import Any
|
|
7
8
|
|
|
8
9
|
from griptape_nodes.exe_types.core_types import (
|
|
10
|
+
ControlParameterInput,
|
|
9
11
|
Parameter,
|
|
10
12
|
ParameterMode,
|
|
11
13
|
ParameterTypeBuiltin,
|
|
@@ -23,6 +25,14 @@ EXECUTION_MODE_VALUE_LOOKUP = {
|
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
|
|
28
|
+
class IterationControlParam(StrEnum):
|
|
29
|
+
"""Parameter names for iteration control on iterative node groups."""
|
|
30
|
+
|
|
31
|
+
LOOP_COMPLETE = "loop_complete"
|
|
32
|
+
SKIP_ITERATION = "skip_iteration"
|
|
33
|
+
BREAK_LOOP = "break_loop"
|
|
34
|
+
|
|
35
|
+
|
|
26
36
|
class BaseIterativeNodeGroup(SubflowNodeGroup):
|
|
27
37
|
"""Base class for iterative node groups (ForEach, ForLoop, etc.).
|
|
28
38
|
|
|
@@ -100,7 +110,15 @@ class BaseIterativeNodeGroup(SubflowNodeGroup):
|
|
|
100
110
|
self.metadata["left_parameters"] = []
|
|
101
111
|
self.metadata["left_parameters"].append("index")
|
|
102
112
|
|
|
103
|
-
#
|
|
113
|
+
# Control input for loop completion (right side - primary loop completion path)
|
|
114
|
+
self.loop_complete = ControlParameterInput(
|
|
115
|
+
tooltip="Signal that this iteration is complete and continue to next iteration",
|
|
116
|
+
name=IterationControlParam.LOOP_COMPLETE.value,
|
|
117
|
+
)
|
|
118
|
+
self.loop_complete.ui_options = {"display_name": "Loop Complete"}
|
|
119
|
+
self.add_parameter(self.loop_complete)
|
|
120
|
+
|
|
121
|
+
# Data parameter for the item to add (right side - collects from group)
|
|
104
122
|
self.new_item_to_add = Parameter(
|
|
105
123
|
name="new_item_to_add",
|
|
106
124
|
tooltip="Item to add to results list for each iteration",
|
|
@@ -109,6 +127,21 @@ class BaseIterativeNodeGroup(SubflowNodeGroup):
|
|
|
109
127
|
)
|
|
110
128
|
self.add_parameter(self.new_item_to_add)
|
|
111
129
|
|
|
130
|
+
# Skip and Break control inputs (right side - for loop control)
|
|
131
|
+
self.skip_iteration = ControlParameterInput(
|
|
132
|
+
tooltip="Skip current item and continue to next iteration",
|
|
133
|
+
name=IterationControlParam.SKIP_ITERATION.value,
|
|
134
|
+
)
|
|
135
|
+
self.skip_iteration.ui_options = {"display_name": "Skip to Next Iteration"}
|
|
136
|
+
self.add_parameter(self.skip_iteration)
|
|
137
|
+
|
|
138
|
+
self.break_loop = ControlParameterInput(
|
|
139
|
+
tooltip="Break out of loop immediately",
|
|
140
|
+
name=IterationControlParam.BREAK_LOOP.value,
|
|
141
|
+
)
|
|
142
|
+
self.break_loop.ui_options = {"display_name": "Break Out of Loop"}
|
|
143
|
+
self.add_parameter(self.break_loop)
|
|
144
|
+
|
|
112
145
|
self.results = Parameter(
|
|
113
146
|
name="results",
|
|
114
147
|
tooltip="Collected results from all iterations",
|
|
@@ -120,7 +153,15 @@ class BaseIterativeNodeGroup(SubflowNodeGroup):
|
|
|
120
153
|
# Track right parameters for UI layout
|
|
121
154
|
if "right_parameters" not in self.metadata:
|
|
122
155
|
self.metadata["right_parameters"] = []
|
|
123
|
-
self.metadata["right_parameters"].extend(
|
|
156
|
+
self.metadata["right_parameters"].extend(
|
|
157
|
+
[
|
|
158
|
+
IterationControlParam.LOOP_COMPLETE.value,
|
|
159
|
+
"new_item_to_add",
|
|
160
|
+
IterationControlParam.SKIP_ITERATION.value,
|
|
161
|
+
IterationControlParam.BREAK_LOOP.value,
|
|
162
|
+
"results",
|
|
163
|
+
]
|
|
164
|
+
)
|
|
124
165
|
|
|
125
166
|
def after_value_set(self, parameter: Parameter, value: Any) -> None:
|
|
126
167
|
"""Handle parameter value changes."""
|
|
@@ -130,6 +171,17 @@ class BaseIterativeNodeGroup(SubflowNodeGroup):
|
|
|
130
171
|
run_in_order = EXECUTION_MODE_VALUE_LOOKUP.get(value, True)
|
|
131
172
|
self.set_parameter_value("run_in_order", run_in_order)
|
|
132
173
|
|
|
174
|
+
# Hide or show skip/break controls based on execution mode
|
|
175
|
+
# Skip and Break are only supported in sequential mode (run_in_order=True)
|
|
176
|
+
if run_in_order:
|
|
177
|
+
# Show controls when running sequentially
|
|
178
|
+
self.show_parameter_by_name(self.skip_iteration.name)
|
|
179
|
+
self.show_parameter_by_name(self.break_loop.name)
|
|
180
|
+
else:
|
|
181
|
+
# Hide controls when running in parallel (not supported)
|
|
182
|
+
self.hide_parameter_by_name(self.skip_iteration.name)
|
|
183
|
+
self.hide_parameter_by_name(self.break_loop.name)
|
|
184
|
+
|
|
133
185
|
@abstractmethod
|
|
134
186
|
def _get_iteration_items(self) -> list[Any]:
|
|
135
187
|
"""Get the list of items to iterate over.
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""HuggingFace parameter class that supports repo + variant/subfolder selection."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from huggingface_hub.constants import HF_HUB_CACHE
|
|
7
|
+
|
|
8
|
+
from griptape_nodes.exe_types.node_types import BaseNode
|
|
9
|
+
from griptape_nodes.exe_types.param_components.huggingface.huggingface_model_parameter import HuggingFaceModelParameter
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger("griptape_nodes")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _get_repo_cache_path(repo_id: str) -> Path | None:
|
|
15
|
+
"""Get the cache path for a repo if it exists."""
|
|
16
|
+
cache_path = Path(HF_HUB_CACHE)
|
|
17
|
+
if not cache_path.exists():
|
|
18
|
+
return None
|
|
19
|
+
|
|
20
|
+
# Convert repo_id to cache folder format: owner/repo -> models--owner--repo
|
|
21
|
+
folder_name = f"models--{repo_id.replace('/', '--')}"
|
|
22
|
+
repo_path = cache_path / folder_name
|
|
23
|
+
|
|
24
|
+
if repo_path.exists():
|
|
25
|
+
return repo_path
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _get_snapshot_path(repo_path: Path) -> Path | None:
|
|
30
|
+
"""Get the latest snapshot path for a repo."""
|
|
31
|
+
snapshots_dir = repo_path / "snapshots"
|
|
32
|
+
if not snapshots_dir.exists():
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
snapshots = [p for p in snapshots_dir.iterdir() if p.is_dir()]
|
|
36
|
+
if not snapshots:
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
# Return the most recent snapshot
|
|
40
|
+
return max(snapshots, key=lambda p: p.stat().st_mtime)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _list_variants_in_cache(repo_id: str, variants: list[str]) -> list[tuple[str, str, str]]:
|
|
44
|
+
"""List (repo_id, variant, revision) tuples for variants that exist in cache.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
repo_id: The HuggingFace repo ID (e.g., "Lightricks/LTX-2")
|
|
48
|
+
variants: List of variant/subfolder names to check for (e.g., ["ltx-2-19b-dev", "ltx-2-19b-dev-fp8"])
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
List of (repo_id, variant, revision) tuples for variants found in cache
|
|
52
|
+
"""
|
|
53
|
+
repo_path = _get_repo_cache_path(repo_id)
|
|
54
|
+
if repo_path is None:
|
|
55
|
+
return []
|
|
56
|
+
|
|
57
|
+
snapshot_path = _get_snapshot_path(repo_path)
|
|
58
|
+
if snapshot_path is None:
|
|
59
|
+
return []
|
|
60
|
+
|
|
61
|
+
revision = snapshot_path.name
|
|
62
|
+
results = []
|
|
63
|
+
|
|
64
|
+
for variant in variants:
|
|
65
|
+
# Check for variant as a directory (subfolder model structure)
|
|
66
|
+
variant_dir_path = snapshot_path / variant
|
|
67
|
+
if variant_dir_path.exists() and variant_dir_path.is_dir():
|
|
68
|
+
results.append((repo_id, variant, revision))
|
|
69
|
+
continue
|
|
70
|
+
|
|
71
|
+
# Check for variant as a .safetensors file (single-file model structure)
|
|
72
|
+
variant_file_path = snapshot_path / f"{variant}.safetensors"
|
|
73
|
+
if variant_file_path.exists() and variant_file_path.is_file():
|
|
74
|
+
results.append((repo_id, variant, revision))
|
|
75
|
+
|
|
76
|
+
return results
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class HuggingFaceRepoVariantParameter(HuggingFaceModelParameter):
|
|
80
|
+
"""Parameter class for selecting a variant/subfolder within a HuggingFace repo.
|
|
81
|
+
|
|
82
|
+
Use this when a single repo contains multiple model variants as subfolders.
|
|
83
|
+
For example, Lightricks/LTX-2 contains:
|
|
84
|
+
- ltx-2-19b-dev
|
|
85
|
+
- ltx-2-19b-dev-fp8
|
|
86
|
+
- ltx-2-19b-dev-fp4
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
def __init__(
|
|
90
|
+
self,
|
|
91
|
+
node: BaseNode,
|
|
92
|
+
repo_id: str,
|
|
93
|
+
variants: list[str],
|
|
94
|
+
parameter_name: str = "model",
|
|
95
|
+
):
|
|
96
|
+
"""Initialize the parameter.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
node: The node this parameter belongs to
|
|
100
|
+
repo_id: The HuggingFace repo ID (e.g., "Lightricks/LTX-2")
|
|
101
|
+
variants: List of variant/subfolder names (e.g., ["ltx-2-19b-dev", "ltx-2-19b-dev-fp8"])
|
|
102
|
+
parameter_name: Name of the parameter (default: "model")
|
|
103
|
+
"""
|
|
104
|
+
super().__init__(node, parameter_name)
|
|
105
|
+
self._repo_id = repo_id
|
|
106
|
+
self._variants = variants
|
|
107
|
+
self.refresh_parameters()
|
|
108
|
+
|
|
109
|
+
@classmethod
|
|
110
|
+
def _repo_variant_to_key(cls, repo_id: str, variant: str) -> str:
|
|
111
|
+
"""Convert repo_id and variant to a display key."""
|
|
112
|
+
return f"{repo_id}/{variant}"
|
|
113
|
+
|
|
114
|
+
@classmethod
|
|
115
|
+
def _key_to_repo_variant(cls, key: str) -> tuple[str, str]:
|
|
116
|
+
"""Parse a display key back to repo_id and variant.
|
|
117
|
+
|
|
118
|
+
Key format: "owner/repo/variant"
|
|
119
|
+
"""
|
|
120
|
+
parts = key.rsplit("/", 1)
|
|
121
|
+
if len(parts) == 2: # noqa: PLR2004
|
|
122
|
+
return parts[0], parts[1]
|
|
123
|
+
# Fallback: treat entire key as repo_id with empty variant
|
|
124
|
+
return key, ""
|
|
125
|
+
|
|
126
|
+
def fetch_repo_revisions(self) -> list[tuple[str, str]]:
|
|
127
|
+
"""Fetch available variants from cache.
|
|
128
|
+
|
|
129
|
+
Returns list of (display_key, revision) tuples where display_key is "repo_id/variant".
|
|
130
|
+
"""
|
|
131
|
+
variant_revisions = _list_variants_in_cache(self._repo_id, self._variants)
|
|
132
|
+
return [
|
|
133
|
+
(self._repo_variant_to_key(repo_id, variant), revision) for repo_id, variant, revision in variant_revisions
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
def get_download_commands(self) -> list[str]:
|
|
137
|
+
"""Return download commands for the repo."""
|
|
138
|
+
return [f'huggingface-cli download "{self._repo_id}"']
|
|
139
|
+
|
|
140
|
+
def get_download_models(self) -> list[str]:
|
|
141
|
+
"""Return list of model names for download."""
|
|
142
|
+
return [self._repo_id]
|
|
143
|
+
|
|
144
|
+
def get_repo_variant_revision(self) -> tuple[str, str, str]:
|
|
145
|
+
"""Get the selected repo_id, variant, and revision.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Tuple of (repo_id, variant, revision)
|
|
149
|
+
"""
|
|
150
|
+
repo_key, revision = self.get_repo_revision()
|
|
151
|
+
repo_id, variant = self._key_to_repo_variant(repo_key)
|
|
152
|
+
return repo_id, variant, revision
|
|
@@ -2319,11 +2319,12 @@ class FlowManager:
|
|
|
2319
2319
|
start_node_parameter_value_commands.append(param_value_command)
|
|
2320
2320
|
|
|
2321
2321
|
# Create parameter command for start node (following single-node pattern exactly)
|
|
2322
|
+
# Use source parameter's default value to ensure type-safe propagation during connection creation
|
|
2322
2323
|
add_param_request = AddParameterToNodeRequest(
|
|
2323
2324
|
node_name=start_node_name,
|
|
2324
2325
|
parameter_name=param_name,
|
|
2325
2326
|
type=source_param.output_type,
|
|
2326
|
-
default_value=
|
|
2327
|
+
default_value=source_param.default_value,
|
|
2327
2328
|
tooltip=f"Parameter {target_parameter_name} from node {target_node_name} in packaged flow",
|
|
2328
2329
|
initial_setup=True,
|
|
2329
2330
|
)
|
|
@@ -9,7 +9,7 @@ griptape_nodes/app/app.py,sha256=48q09c2xDjvbf4Qa1_buQhnPjR2jqkXo428GeBRkw8g,160
|
|
|
9
9
|
griptape_nodes/app/watch.py,sha256=hKVP_SuV9C17bH1h9o4uIVTKH-IL_-0iyHaNYmILTWU,1594
|
|
10
10
|
griptape_nodes/bootstrap/__init__.py,sha256=ENv3SIzQ9TtlRrg1y4e4CnoBpJaFpFSkNpTFBV8X5Ls,25
|
|
11
11
|
griptape_nodes/bootstrap/utils/__init__.py,sha256=tlNEApJLZazcBNhxkTdup4URwznnz4nZxjSaRfFrTBM,31
|
|
12
|
-
griptape_nodes/bootstrap/utils/python_subprocess_executor.py,sha256=
|
|
12
|
+
griptape_nodes/bootstrap/utils/python_subprocess_executor.py,sha256=_XUQxrxt8n3vPv3_UHFlaAFRSODLhzKmAK5iNcf92lg,4847
|
|
13
13
|
griptape_nodes/bootstrap/workflow_executors/__init__.py,sha256=pyjN81-eLtjyECFYLXOtMCixiiI9qBi5yald86iM7Ek,34
|
|
14
14
|
griptape_nodes/bootstrap/workflow_executors/local_session_workflow_executor.py,sha256=Zqb9yQe-3PxcaCEpLVcq6CQPn5iwjl5TJjQehm1fvZ0,16652
|
|
15
15
|
griptape_nodes/bootstrap/workflow_executors/local_workflow_executor.py,sha256=jp81z4QFSuQsXigKN0h2Ywo0Tr_whbxFG-TJ7MCrS2Q,10263
|
|
@@ -42,7 +42,7 @@ griptape_nodes/common/macro_parser/matching.py,sha256=hnlPIihz8W1TGnfq4anbBGciwg
|
|
|
42
42
|
griptape_nodes/common/macro_parser/parsing.py,sha256=_37L3lCQFeetamWIiHT0C7eW1E3Wyx5V8y8OWzIUw_g,7428
|
|
43
43
|
griptape_nodes/common/macro_parser/resolution.py,sha256=30_bAIRMKWw-kM3E1ne-my16xT1Wd734JMmZSOZtNPA,6937
|
|
44
44
|
griptape_nodes/common/macro_parser/segments.py,sha256=r-WbZas1nSc-PlEdAZ7DycNoD6IUf3R_nhGo3T9vIa8,1010
|
|
45
|
-
griptape_nodes/common/node_executor.py,sha256=
|
|
45
|
+
griptape_nodes/common/node_executor.py,sha256=zaVCw4BxurjwhVhCA9ujGXw2xzC4-Tkvjo3SFtY0S7Y,147191
|
|
46
46
|
griptape_nodes/common/project_templates/__init__.py,sha256=5DP6Sa6ClsdDcjD30pkK0iTTysTgGdRKEkeY7ztHZyc,1541
|
|
47
47
|
griptape_nodes/common/project_templates/default_project_template.py,sha256=PJFPfziH0EOGVxU5L0wZnc21bVVweW4S7wcnGPF45Co,3120
|
|
48
48
|
griptape_nodes/common/project_templates/defaults/README.md,sha256=1ou6_OFi7-t86nj-7tulW1vxNMtsvkJmNxr6SseG8hA,1080
|
|
@@ -67,8 +67,8 @@ griptape_nodes/exe_types/base_iterative_nodes.py,sha256=z2u4Pb9iqy2Zl3gz_LB7k6hm
|
|
|
67
67
|
griptape_nodes/exe_types/connections.py,sha256=HcM_fwWivMsXk5q6K7ZBSJpmK9THQwX_xLS_daF0PDQ,21737
|
|
68
68
|
griptape_nodes/exe_types/core_types.py,sha256=XrMT0Rf0bPZBcx06f1GUTi5G_IwLgdyzGjBKkUwRh3M,101505
|
|
69
69
|
griptape_nodes/exe_types/flow.py,sha256=2iAh3vN5wnMVxTc5jcPBg9TSiASq1DGIm5jgpO9Bdq4,5732
|
|
70
|
-
griptape_nodes/exe_types/node_groups/__init__.py,sha256=
|
|
71
|
-
griptape_nodes/exe_types/node_groups/base_iterative_node_group.py,sha256=
|
|
70
|
+
griptape_nodes/exe_types/node_groups/__init__.py,sha256=u91XCSR4OAAr6x5kYq8i14mtHEWGQF_fSDUsWHmiOdY,346
|
|
71
|
+
griptape_nodes/exe_types/node_groups/base_iterative_node_group.py,sha256=sPPy3Psl-Ku_R46upWm_8hIGFLNPeLpUBN_Sm4RzXzg,9655
|
|
72
72
|
griptape_nodes/exe_types/node_groups/base_node_group.py,sha256=dJofcmERzYQSQ2KB7dC0rOKjbZKW8Ko8eD6r0smBu48,3209
|
|
73
73
|
griptape_nodes/exe_types/node_groups/subflow_node_group.py,sha256=UlZS95TKUCWiHgg6z4Wm1t9xXfUjWhk0L8pB5cXMcV0,48361
|
|
74
74
|
griptape_nodes/exe_types/node_types.py,sha256=pPljG84zJ6Wd6yzcRjnK0n1805b-Jyzxx7-NFox66QE,84227
|
|
@@ -82,6 +82,7 @@ griptape_nodes/exe_types/param_components/huggingface/__init__.py,sha256=YeVFck-
|
|
|
82
82
|
griptape_nodes/exe_types/param_components/huggingface/huggingface_model_parameter.py,sha256=rEGN4kvblSAlRh7pD73U__wzAtVzDYWYlDC-k1nKtyM,6395
|
|
83
83
|
griptape_nodes/exe_types/param_components/huggingface/huggingface_repo_file_parameter.py,sha256=c61de3mbkc5FvVkf5vubtD0XfThDInTfibAtYhZKcKA,4265
|
|
84
84
|
griptape_nodes/exe_types/param_components/huggingface/huggingface_repo_parameter.py,sha256=zcBRQP0eZJloSdPQwJFw89B6QDHECnrosRZUy8mE2fM,1401
|
|
85
|
+
griptape_nodes/exe_types/param_components/huggingface/huggingface_repo_variant_parameter.py,sha256=FaaDZ4dvZJCnpqSOKfJ5nR1klgl07JZto2A7gxhUi3o,5290
|
|
85
86
|
griptape_nodes/exe_types/param_components/huggingface/huggingface_utils.py,sha256=cE0Ht81kDcZjlN_2VoRirCA4zMlrG9GFlcptn-gC2tU,4696
|
|
86
87
|
griptape_nodes/exe_types/param_components/log_parameter.py,sha256=K6XRWVIpL9MtQ6IZG3hYYwIv3sNqFxONxbVnKcA_Oes,4160
|
|
87
88
|
griptape_nodes/exe_types/param_components/progress_bar_component.py,sha256=GrAFTOrLNAx6q9zfOqiioPTG_NLHKhTe9NiZOo6zGSc,1949
|
|
@@ -200,7 +201,7 @@ griptape_nodes/retained_mode/managers/fitness_problems/workflows/node_type_not_f
|
|
|
200
201
|
griptape_nodes/retained_mode/managers/fitness_problems/workflows/workflow_not_found_problem.py,sha256=kPhaIeDxp0duxJBJLKFiYp269kEDsCx3uVLnkKbF2gk,895
|
|
201
202
|
griptape_nodes/retained_mode/managers/fitness_problems/workflows/workflow_problem.py,sha256=NLAoF_x7g5tL70qZtPVFI8xeM1jPZNsm7FKt9nkuGBc,542
|
|
202
203
|
griptape_nodes/retained_mode/managers/fitness_problems/workflows/workflow_schema_version_problem.py,sha256=rUnbBBnGwgq5WTVPt2G4pGP3gVPoX5xvCdTkRPBmP04,1328
|
|
203
|
-
griptape_nodes/retained_mode/managers/flow_manager.py,sha256=
|
|
204
|
+
griptape_nodes/retained_mode/managers/flow_manager.py,sha256=fY0xCXHiualDwVQJnNlPpMUnLz5bYWvr5D1_UvOyNVM,220269
|
|
204
205
|
griptape_nodes/retained_mode/managers/library_manager.py,sha256=paogGavZj2MDOYvfUklhjORQg16YdpVGBxBncVSxtR0,192265
|
|
205
206
|
griptape_nodes/retained_mode/managers/mcp_manager.py,sha256=Ezmn5_48r4JWYe-tFGqmw9dXLvvTiO1rw9b4MNCkw0U,15955
|
|
206
207
|
griptape_nodes/retained_mode/managers/model_manager.py,sha256=3lj2X8vIvDSERPtR2VEXNFEWy_D8H6muxRvD-PEx8U8,44845
|
|
@@ -275,7 +276,7 @@ griptape_nodes/version_compatibility/versions/v0_65_5/__init__.py,sha256=4UyspOV
|
|
|
275
276
|
griptape_nodes/version_compatibility/versions/v0_65_5/flux_2_removed_parameters.py,sha256=jOlmY5kKHXh8HPzAUtvwMJqzD4bP7pkE--yHUb7jTRA,3305
|
|
276
277
|
griptape_nodes/version_compatibility/versions/v0_7_0/__init__.py,sha256=IzPPmGK86h2swfGGTOHyVcBIlOng6SjgWQzlbf3ngmo,51
|
|
277
278
|
griptape_nodes/version_compatibility/versions/v0_7_0/local_executor_argument_addition.py,sha256=Thx8acnbw5OychhwEEj9aFxvbPe7Wgn4V9ZmZ7KRZqc,2082
|
|
278
|
-
griptape_nodes-0.
|
|
279
|
-
griptape_nodes-0.
|
|
280
|
-
griptape_nodes-0.
|
|
281
|
-
griptape_nodes-0.
|
|
279
|
+
griptape_nodes-0.68.0.dist-info/WHEEL,sha256=eycQt0QpYmJMLKpE3X9iDk8R04v2ZF0x82ogq-zP6bQ,79
|
|
280
|
+
griptape_nodes-0.68.0.dist-info/entry_points.txt,sha256=qvevqd3BVbAV5TcantnAm0ouqaqYKhsRO3pkFymWLWM,82
|
|
281
|
+
griptape_nodes-0.68.0.dist-info/METADATA,sha256=N19_zdNWANm20AwShxLGrCdlns7RVwaJ9IoewOOXH1Q,5374
|
|
282
|
+
griptape_nodes-0.68.0.dist-info/RECORD,,
|
|
File without changes
|