griptape-nodes 0.55.1__py3-none-any.whl → 0.56.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/cli/commands/init.py +88 -0
- griptape_nodes/cli/commands/models.py +2 -0
- griptape_nodes/cli/shared.py +1 -0
- griptape_nodes/exe_types/core_types.py +104 -0
- griptape_nodes/exe_types/node_types.py +9 -12
- griptape_nodes/machines/control_flow.py +10 -0
- griptape_nodes/machines/dag_builder.py +21 -2
- griptape_nodes/machines/parallel_resolution.py +25 -10
- griptape_nodes/node_library/workflow_registry.py +73 -3
- griptape_nodes/retained_mode/events/execution_events.py +12 -2
- griptape_nodes/retained_mode/events/flow_events.py +58 -0
- griptape_nodes/retained_mode/events/resource_events.py +290 -0
- griptape_nodes/retained_mode/events/workflow_events.py +57 -2
- griptape_nodes/retained_mode/griptape_nodes.py +9 -1
- griptape_nodes/retained_mode/managers/flow_manager.py +678 -12
- griptape_nodes/retained_mode/managers/library_manager.py +13 -19
- griptape_nodes/retained_mode/managers/model_manager.py +184 -83
- griptape_nodes/retained_mode/managers/node_manager.py +3 -3
- griptape_nodes/retained_mode/managers/os_manager.py +118 -1
- griptape_nodes/retained_mode/managers/resource_components/__init__.py +1 -0
- griptape_nodes/retained_mode/managers/resource_components/capability_field.py +41 -0
- griptape_nodes/retained_mode/managers/resource_components/comparator.py +18 -0
- griptape_nodes/retained_mode/managers/resource_components/resource_instance.py +236 -0
- griptape_nodes/retained_mode/managers/resource_components/resource_type.py +79 -0
- griptape_nodes/retained_mode/managers/resource_manager.py +306 -0
- griptape_nodes/retained_mode/managers/resource_types/__init__.py +1 -0
- griptape_nodes/retained_mode/managers/resource_types/cpu_resource.py +108 -0
- griptape_nodes/retained_mode/managers/resource_types/os_resource.py +87 -0
- griptape_nodes/retained_mode/managers/settings.py +5 -0
- griptape_nodes/retained_mode/managers/sync_manager.py +10 -3
- griptape_nodes/retained_mode/managers/workflow_manager.py +359 -261
- {griptape_nodes-0.55.1.dist-info → griptape_nodes-0.56.0.dist-info}/METADATA +1 -1
- {griptape_nodes-0.55.1.dist-info → griptape_nodes-0.56.0.dist-info}/RECORD +35 -25
- {griptape_nodes-0.55.1.dist-info → griptape_nodes-0.56.0.dist-info}/WHEEL +1 -1
- {griptape_nodes-0.55.1.dist-info → griptape_nodes-0.56.0.dist-info}/entry_points.txt +0 -0
|
@@ -4,7 +4,6 @@ import ast
|
|
|
4
4
|
import asyncio
|
|
5
5
|
import logging
|
|
6
6
|
import pickle
|
|
7
|
-
import pkgutil
|
|
8
7
|
import re
|
|
9
8
|
from dataclasses import dataclass, field, fields, is_dataclass
|
|
10
9
|
from datetime import UTC, datetime
|
|
@@ -24,7 +23,7 @@ from griptape_nodes.drivers.storage import StorageBackend
|
|
|
24
23
|
from griptape_nodes.exe_types.core_types import ParameterTypeBuiltin
|
|
25
24
|
from griptape_nodes.exe_types.flow import ControlFlow
|
|
26
25
|
from griptape_nodes.exe_types.node_types import BaseNode, EndNode, StartNode
|
|
27
|
-
from griptape_nodes.node_library.workflow_registry import Workflow, WorkflowMetadata, WorkflowRegistry
|
|
26
|
+
from griptape_nodes.node_library.workflow_registry import Workflow, WorkflowMetadata, WorkflowRegistry, WorkflowShape
|
|
28
27
|
from griptape_nodes.retained_mode.events.app_events import (
|
|
29
28
|
GetEngineVersionRequest,
|
|
30
29
|
GetEngineVersionResultSuccess,
|
|
@@ -100,6 +99,9 @@ from griptape_nodes.retained_mode.events.workflow_events import (
|
|
|
100
99
|
RunWorkflowWithCurrentStateRequest,
|
|
101
100
|
RunWorkflowWithCurrentStateResultFailure,
|
|
102
101
|
RunWorkflowWithCurrentStateResultSuccess,
|
|
102
|
+
SaveWorkflowFileFromSerializedFlowRequest,
|
|
103
|
+
SaveWorkflowFileFromSerializedFlowResultFailure,
|
|
104
|
+
SaveWorkflowFileFromSerializedFlowResultSuccess,
|
|
103
105
|
SaveWorkflowRequest,
|
|
104
106
|
SaveWorkflowResultFailure,
|
|
105
107
|
SaveWorkflowResultSuccess,
|
|
@@ -115,7 +117,6 @@ if TYPE_CHECKING:
|
|
|
115
117
|
from types import TracebackType
|
|
116
118
|
|
|
117
119
|
from griptape_nodes.exe_types.core_types import Parameter
|
|
118
|
-
from griptape_nodes.node_library.library_registry import LibraryNameAndVersion
|
|
119
120
|
from griptape_nodes.retained_mode.events.base_events import ResultPayload
|
|
120
121
|
from griptape_nodes.retained_mode.events.node_events import SetLockNodeStateRequest
|
|
121
122
|
from griptape_nodes.retained_mode.managers.event_manager import EventManager
|
|
@@ -123,6 +124,10 @@ if TYPE_CHECKING:
|
|
|
123
124
|
|
|
124
125
|
T = TypeVar("T")
|
|
125
126
|
|
|
127
|
+
# Type aliases for workflow shape building
|
|
128
|
+
ParameterShapeInfo = dict[str, Any] # Parameter metadata dict from _convert_parameter_to_minimal_dict
|
|
129
|
+
NodeParameterMap = dict[str, ParameterShapeInfo] # {param_name: param_info}
|
|
130
|
+
WorkflowShapeNodes = dict[str, NodeParameterMap] # {node_name: {param_name: param_info}}
|
|
126
131
|
|
|
127
132
|
logger = logging.getLogger("griptape_nodes")
|
|
128
133
|
|
|
@@ -269,6 +274,10 @@ class WorkflowManager:
|
|
|
269
274
|
SaveWorkflowRequest,
|
|
270
275
|
self.on_save_workflow_request,
|
|
271
276
|
)
|
|
277
|
+
event_manager.assign_manager_to_request_type(
|
|
278
|
+
SaveWorkflowFileFromSerializedFlowRequest,
|
|
279
|
+
self.on_save_workflow_file_from_serialized_flow_request,
|
|
280
|
+
)
|
|
272
281
|
event_manager.assign_manager_to_request_type(LoadWorkflowMetadata, self.on_load_workflow_metadata_request)
|
|
273
282
|
event_manager.assign_manager_to_request_type(
|
|
274
283
|
PublishWorkflowRequest,
|
|
@@ -1115,111 +1124,287 @@ class WorkflowManager:
|
|
|
1115
1124
|
|
|
1116
1125
|
return True
|
|
1117
1126
|
|
|
1118
|
-
|
|
1119
|
-
"""
|
|
1120
|
-
import_template = "from {} import *"
|
|
1121
|
-
import_statements = []
|
|
1122
|
-
|
|
1123
|
-
from griptape_nodes.retained_mode import events as events_pkg
|
|
1127
|
+
class WriteWorkflowFileResult(NamedTuple):
|
|
1128
|
+
"""Result of writing a workflow file."""
|
|
1124
1129
|
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
if module_name.endswith("generate_request_payload_schemas"):
|
|
1128
|
-
continue
|
|
1129
|
-
import_statements.append(import_template.format(module_name))
|
|
1130
|
+
success: bool
|
|
1131
|
+
error_details: str
|
|
1130
1132
|
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
def _generate_workflow_file_contents_and_metadata( # noqa: C901, PLR0912, PLR0915
|
|
1134
|
-
self,
|
|
1135
|
-
file_name: str,
|
|
1136
|
-
creation_date: datetime,
|
|
1137
|
-
image_path: str | None = None,
|
|
1138
|
-
prior_workflow: Workflow | None = None,
|
|
1139
|
-
custom_metadata: WorkflowMetadata | None = None,
|
|
1140
|
-
) -> tuple[str, WorkflowMetadata]:
|
|
1141
|
-
"""Generate the contents of a workflow file.
|
|
1133
|
+
def _write_workflow_file(self, file_path: Path, content: str, file_name: str) -> WriteWorkflowFileResult:
|
|
1134
|
+
"""Write workflow content to file with proper validation and error handling.
|
|
1142
1135
|
|
|
1143
1136
|
Args:
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
prior_workflow: Optional; existing workflow to preserve branch info from
|
|
1148
|
-
custom_metadata: Optional; pre-constructed metadata to use instead of generating it
|
|
1149
|
-
from the current workflow state. When provided, this metadata will be
|
|
1150
|
-
used directly, allowing branch/merge operations to pass specific metadata.
|
|
1137
|
+
file_path: Path where to write the file
|
|
1138
|
+
content: Content to write
|
|
1139
|
+
file_name: Name for error messages
|
|
1151
1140
|
|
|
1152
1141
|
Returns:
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
Raises:
|
|
1156
|
-
ValueError, TypeError: If workflow generation fails
|
|
1142
|
+
WriteWorkflowFileResult with success status and error details if failed
|
|
1157
1143
|
"""
|
|
1158
|
-
#
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
if not
|
|
1162
|
-
|
|
1163
|
-
|
|
1144
|
+
# Check disk space before any file system operations
|
|
1145
|
+
config_manager = GriptapeNodes.ConfigManager()
|
|
1146
|
+
min_space_gb = config_manager.get_config_value("minimum_disk_space_gb_workflows")
|
|
1147
|
+
if not OSManager.check_available_disk_space(file_path.parent, min_space_gb):
|
|
1148
|
+
error_msg = OSManager.format_disk_space_error(file_path.parent)
|
|
1149
|
+
details = f"Attempted to save workflow '{file_name}' (requires {min_space_gb:.1f} GB). Failed due to insufficient disk space: {error_msg}"
|
|
1150
|
+
return self.WriteWorkflowFileResult(success=False, error_details=details)
|
|
1151
|
+
|
|
1152
|
+
# Create directory structure
|
|
1164
1153
|
try:
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
)
|
|
1169
|
-
except Exception as err:
|
|
1170
|
-
details = f"Failed getting the engine version for workflow '{file_name}': {err}"
|
|
1171
|
-
raise ValueError(details) from err
|
|
1154
|
+
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1155
|
+
except OSError as e:
|
|
1156
|
+
details = f"Attempted to save workflow '{file_name}'. Failed when creating directory structure: {e}"
|
|
1157
|
+
return self.WriteWorkflowFileResult(success=False, error_details=details)
|
|
1172
1158
|
|
|
1173
|
-
#
|
|
1174
|
-
|
|
1159
|
+
# Write the file content
|
|
1160
|
+
try:
|
|
1161
|
+
with file_path.open("w", encoding="utf-8") as file:
|
|
1162
|
+
file.write(content)
|
|
1163
|
+
except OSError as e:
|
|
1164
|
+
details = f"Attempted to save workflow '{file_name}'. Failed when writing file content: {e}"
|
|
1165
|
+
return self.WriteWorkflowFileResult(success=False, error_details=details)
|
|
1175
1166
|
|
|
1176
|
-
|
|
1177
|
-
flow_creation_index = 0
|
|
1167
|
+
return self.WriteWorkflowFileResult(success=True, error_details="")
|
|
1178
1168
|
|
|
1179
|
-
|
|
1169
|
+
def on_save_workflow_request(self, request: SaveWorkflowRequest) -> ResultPayload: # noqa: C901, PLR0912, PLR0915
|
|
1170
|
+
# Start with the file name provided; we may change it.
|
|
1171
|
+
file_name = request.file_name
|
|
1172
|
+
|
|
1173
|
+
# See if we had an existing workflow for this.
|
|
1174
|
+
prior_workflow = None
|
|
1175
|
+
creation_date = None
|
|
1176
|
+
if file_name and WorkflowRegistry.has_workflow_with_name(file_name):
|
|
1177
|
+
# Get the metadata.
|
|
1178
|
+
prior_workflow = WorkflowRegistry.get_workflow_by_name(file_name)
|
|
1179
|
+
# We'll use its creation date.
|
|
1180
|
+
creation_date = prior_workflow.metadata.creation_date
|
|
1181
|
+
elif file_name:
|
|
1182
|
+
# If no prior workflow exists for the new name, check if there's a current workflow
|
|
1183
|
+
# context (e.g., during rename operations) to preserve metadata from
|
|
1184
|
+
context_manager = GriptapeNodes.ContextManager()
|
|
1185
|
+
if context_manager.has_current_workflow():
|
|
1186
|
+
current_workflow_name = context_manager.get_current_workflow_name()
|
|
1187
|
+
if current_workflow_name and WorkflowRegistry.has_workflow_with_name(current_workflow_name):
|
|
1188
|
+
prior_workflow = WorkflowRegistry.get_workflow_by_name(current_workflow_name)
|
|
1189
|
+
creation_date = prior_workflow.metadata.creation_date
|
|
1190
|
+
|
|
1191
|
+
if (creation_date is None) or (creation_date == WorkflowManager.EPOCH_START):
|
|
1192
|
+
# Either a new workflow, or a backcompat situation.
|
|
1193
|
+
creation_date = datetime.now(tz=UTC)
|
|
1194
|
+
|
|
1195
|
+
# Let's see if this is a template file; if so, re-route it as a copy in the customer's workflow directory.
|
|
1196
|
+
if prior_workflow and prior_workflow.metadata.is_template:
|
|
1197
|
+
# Aha! User is attempting to save a template. Create a differently-named file in their workspace.
|
|
1198
|
+
# Find the first available file name that doesn't conflict.
|
|
1199
|
+
curr_idx = 1
|
|
1200
|
+
free_file_found = False
|
|
1201
|
+
while not free_file_found:
|
|
1202
|
+
# Composite a new candidate file name to test.
|
|
1203
|
+
new_file_name = f"{file_name}_{curr_idx}"
|
|
1204
|
+
new_file_name_with_extension = f"{new_file_name}.py"
|
|
1205
|
+
new_file_full_path = GriptapeNodes.ConfigManager().workspace_path.joinpath(new_file_name_with_extension)
|
|
1206
|
+
if new_file_full_path.exists():
|
|
1207
|
+
# Keep going.
|
|
1208
|
+
curr_idx += 1
|
|
1209
|
+
else:
|
|
1210
|
+
free_file_found = True
|
|
1211
|
+
file_name = new_file_name
|
|
1212
|
+
|
|
1213
|
+
# Get file name stuff prepped.
|
|
1214
|
+
# Use the existing registered file path if this is an existing workflow (not a template)
|
|
1215
|
+
if prior_workflow and not prior_workflow.metadata.is_template:
|
|
1216
|
+
# Use the existing registered file path
|
|
1217
|
+
relative_file_path = prior_workflow.file_path
|
|
1218
|
+
file_path = Path(WorkflowRegistry.get_complete_file_path(relative_file_path))
|
|
1219
|
+
# Extract file name from the path for metadata generation
|
|
1220
|
+
if not file_name:
|
|
1221
|
+
file_name = prior_workflow.metadata.name
|
|
1222
|
+
else:
|
|
1223
|
+
# Create new path in workspace for new workflows or templates
|
|
1224
|
+
if not file_name:
|
|
1225
|
+
file_name = datetime.now(tz=UTC).strftime("%d.%m_%H.%M")
|
|
1226
|
+
relative_file_path = f"{file_name}.py"
|
|
1227
|
+
file_path = GriptapeNodes.ConfigManager().workspace_path.joinpath(relative_file_path)
|
|
1228
|
+
|
|
1229
|
+
# First, serialize the current workflow state
|
|
1180
1230
|
top_level_flow_request = GetTopLevelFlowRequest()
|
|
1181
1231
|
top_level_flow_result = GriptapeNodes.handle_request(top_level_flow_request)
|
|
1182
1232
|
if not isinstance(top_level_flow_result, GetTopLevelFlowResultSuccess):
|
|
1183
|
-
details = f"Failed when requesting
|
|
1184
|
-
|
|
1233
|
+
details = f"Attempted to save workflow '{relative_file_path}'. Failed when requesting top level flow."
|
|
1234
|
+
return SaveWorkflowResultFailure(result_details=details)
|
|
1235
|
+
|
|
1185
1236
|
top_level_flow_name = top_level_flow_result.flow_name
|
|
1186
1237
|
serialized_flow_request = SerializeFlowToCommandsRequest(
|
|
1187
1238
|
flow_name=top_level_flow_name, include_create_flow_command=True
|
|
1188
1239
|
)
|
|
1189
1240
|
serialized_flow_result = GriptapeNodes.handle_request(serialized_flow_request)
|
|
1190
1241
|
if not isinstance(serialized_flow_result, SerializeFlowToCommandsResultSuccess):
|
|
1191
|
-
details = f"
|
|
1192
|
-
|
|
1242
|
+
details = f"Attempted to save workflow '{relative_file_path}'. Failed when serializing flow."
|
|
1243
|
+
return SaveWorkflowResultFailure(result_details=details)
|
|
1244
|
+
|
|
1193
1245
|
serialized_flow_commands = serialized_flow_result.serialized_flow_commands
|
|
1194
1246
|
|
|
1195
|
-
#
|
|
1196
|
-
|
|
1197
|
-
|
|
1247
|
+
# Extract branched_from information if it exists
|
|
1248
|
+
branched_from = None
|
|
1249
|
+
if prior_workflow and prior_workflow.metadata.branched_from:
|
|
1250
|
+
branched_from = prior_workflow.metadata.branched_from
|
|
1251
|
+
|
|
1252
|
+
# Extract workflow shape if possible
|
|
1253
|
+
workflow_shape = None
|
|
1254
|
+
try:
|
|
1255
|
+
workflow_shape_dict = self.extract_workflow_shape(workflow_name=file_name)
|
|
1256
|
+
workflow_shape = WorkflowShape(inputs=workflow_shape_dict["input"], outputs=workflow_shape_dict["output"])
|
|
1257
|
+
except ValueError:
|
|
1258
|
+
# If we can't extract workflow shape, continue without it
|
|
1259
|
+
pass
|
|
1260
|
+
|
|
1261
|
+
# Use the standalone request to save the workflow file
|
|
1262
|
+
save_file_request = SaveWorkflowFileFromSerializedFlowRequest(
|
|
1263
|
+
serialized_flow_commands=serialized_flow_commands,
|
|
1264
|
+
file_name=file_name,
|
|
1265
|
+
creation_date=creation_date,
|
|
1266
|
+
image_path=request.image_path,
|
|
1267
|
+
execution_flow_name=top_level_flow_name,
|
|
1268
|
+
branched_from=branched_from,
|
|
1269
|
+
workflow_shape=workflow_shape,
|
|
1270
|
+
file_path=str(file_path),
|
|
1271
|
+
)
|
|
1272
|
+
save_file_result = self.on_save_workflow_file_from_serialized_flow_request(save_file_request)
|
|
1273
|
+
|
|
1274
|
+
if not isinstance(save_file_result, SaveWorkflowFileFromSerializedFlowResultSuccess):
|
|
1275
|
+
details = f"Attempted to save workflow '{relative_file_path}'. Failed during file generation: {save_file_result.result_details}"
|
|
1276
|
+
return SaveWorkflowResultFailure(result_details=details)
|
|
1277
|
+
|
|
1278
|
+
# Use the metadata returned by the save operation
|
|
1279
|
+
workflow_metadata = save_file_result.workflow_metadata
|
|
1280
|
+
|
|
1281
|
+
# save the created workflow as an entry in the JSON config file.
|
|
1282
|
+
registered_workflows = WorkflowRegistry.list_workflows()
|
|
1283
|
+
if file_name not in registered_workflows:
|
|
1284
|
+
# Only add to config if it's in the workspace directory (not an external file)
|
|
1285
|
+
if not Path(relative_file_path).is_absolute():
|
|
1286
|
+
try:
|
|
1287
|
+
GriptapeNodes.ConfigManager().save_user_workflow_json(str(file_path))
|
|
1288
|
+
except OSError as e:
|
|
1289
|
+
details = f"Attempted to save workflow '{file_name}'. Failed when saving configuration: {e}"
|
|
1290
|
+
return SaveWorkflowResultFailure(result_details=details)
|
|
1291
|
+
WorkflowRegistry.generate_new_workflow(metadata=workflow_metadata, file_path=relative_file_path)
|
|
1292
|
+
# Update existing workflow's metadata in the registry
|
|
1293
|
+
existing_workflow = WorkflowRegistry.get_workflow_by_name(file_name)
|
|
1294
|
+
existing_workflow.metadata = workflow_metadata
|
|
1295
|
+
details = f"Successfully saved workflow to: {save_file_result.file_path}"
|
|
1296
|
+
return SaveWorkflowResultSuccess(
|
|
1297
|
+
file_path=save_file_result.file_path, result_details=ResultDetails(message=details, level=logging.INFO)
|
|
1298
|
+
)
|
|
1299
|
+
|
|
1300
|
+
def on_save_workflow_file_from_serialized_flow_request(
|
|
1301
|
+
self, request: SaveWorkflowFileFromSerializedFlowRequest
|
|
1302
|
+
) -> ResultPayload:
|
|
1303
|
+
"""Save a workflow file from serialized flow commands without registry overhead."""
|
|
1304
|
+
# Determine file path
|
|
1305
|
+
if request.file_path:
|
|
1306
|
+
# Use provided file path
|
|
1307
|
+
file_path = Path(request.file_path)
|
|
1198
1308
|
else:
|
|
1199
|
-
#
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1309
|
+
# Default to workspace path
|
|
1310
|
+
relative_file_path = f"{request.file_name}.py"
|
|
1311
|
+
file_path = GriptapeNodes.ConfigManager().workspace_path.joinpath(relative_file_path)
|
|
1312
|
+
|
|
1313
|
+
# Use provided creation date or default to current time
|
|
1314
|
+
creation_date = request.creation_date
|
|
1315
|
+
if creation_date is None:
|
|
1316
|
+
creation_date = datetime.now(tz=UTC)
|
|
1317
|
+
|
|
1318
|
+
# Generate metadata from the serialized commands
|
|
1319
|
+
try:
|
|
1320
|
+
workflow_metadata = self._generate_workflow_metadata_from_commands(
|
|
1321
|
+
serialized_flow_commands=request.serialized_flow_commands,
|
|
1322
|
+
file_name=request.file_name,
|
|
1207
1323
|
creation_date=creation_date,
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1324
|
+
image_path=request.image_path,
|
|
1325
|
+
branched_from=request.branched_from,
|
|
1326
|
+
workflow_shape=request.workflow_shape,
|
|
1211
1327
|
)
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1328
|
+
except Exception as err:
|
|
1329
|
+
details = f"Attempted to save workflow file '{request.file_name}' from serialized flow commands. Failed during metadata generation: {err}"
|
|
1330
|
+
return SaveWorkflowFileFromSerializedFlowResultFailure(result_details=details)
|
|
1215
1331
|
|
|
1216
|
-
#
|
|
1217
|
-
|
|
1218
|
-
|
|
1332
|
+
# Use provided execution flow name or default to file name
|
|
1333
|
+
execution_flow_name = request.execution_flow_name
|
|
1334
|
+
if execution_flow_name is None:
|
|
1335
|
+
execution_flow_name = request.file_name
|
|
1219
1336
|
|
|
1337
|
+
# Generate the workflow file content
|
|
1338
|
+
try:
|
|
1339
|
+
final_code_output = self._generate_workflow_file_content(
|
|
1340
|
+
serialized_flow_commands=request.serialized_flow_commands,
|
|
1341
|
+
workflow_metadata=workflow_metadata,
|
|
1342
|
+
execution_flow_name=execution_flow_name,
|
|
1343
|
+
)
|
|
1344
|
+
except Exception as err:
|
|
1345
|
+
details = f"Attempted to save workflow file '{request.file_name}' from serialized flow commands. Failed during content generation: {err}"
|
|
1346
|
+
return SaveWorkflowFileFromSerializedFlowResultFailure(result_details=details)
|
|
1347
|
+
|
|
1348
|
+
# Write the workflow file
|
|
1349
|
+
write_result = self._write_workflow_file(file_path, final_code_output, request.file_name)
|
|
1350
|
+
if not write_result.success:
|
|
1351
|
+
return SaveWorkflowFileFromSerializedFlowResultFailure(result_details=write_result.error_details)
|
|
1352
|
+
|
|
1353
|
+
details = f"Successfully saved workflow file at: {file_path}"
|
|
1354
|
+
return SaveWorkflowFileFromSerializedFlowResultSuccess(
|
|
1355
|
+
file_path=str(file_path),
|
|
1356
|
+
workflow_metadata=workflow_metadata,
|
|
1357
|
+
result_details=ResultDetails(message=details, level=logging.INFO),
|
|
1358
|
+
)
|
|
1359
|
+
|
|
1360
|
+
def _generate_workflow_metadata_from_commands( # noqa: PLR0913
|
|
1361
|
+
self,
|
|
1362
|
+
serialized_flow_commands: SerializedFlowCommands,
|
|
1363
|
+
file_name: str,
|
|
1364
|
+
creation_date: datetime,
|
|
1365
|
+
image_path: str | None = None,
|
|
1366
|
+
branched_from: str | None = None,
|
|
1367
|
+
workflow_shape: WorkflowShape | None = None,
|
|
1368
|
+
) -> WorkflowMetadata:
|
|
1369
|
+
"""Generate workflow metadata from serialized commands."""
|
|
1370
|
+
# Get the engine version
|
|
1371
|
+
engine_version_request = GetEngineVersionRequest()
|
|
1372
|
+
engine_version_result = GriptapeNodes.handle_request(request=engine_version_request)
|
|
1373
|
+
if not isinstance(engine_version_result, GetEngineVersionResultSuccess):
|
|
1374
|
+
details = f"Failed getting the engine version for workflow '{file_name}'."
|
|
1375
|
+
raise TypeError(details)
|
|
1376
|
+
|
|
1377
|
+
engine_version_success = cast("GetEngineVersionResultSuccess", engine_version_result)
|
|
1378
|
+
engine_version = f"{engine_version_success.major}.{engine_version_success.minor}.{engine_version_success.patch}"
|
|
1379
|
+
|
|
1380
|
+
# Create the Workflow Metadata header
|
|
1381
|
+
workflows_referenced = None
|
|
1382
|
+
if serialized_flow_commands.referenced_workflows:
|
|
1383
|
+
workflows_referenced = list(serialized_flow_commands.referenced_workflows)
|
|
1384
|
+
|
|
1385
|
+
return WorkflowMetadata(
|
|
1386
|
+
name=str(file_name),
|
|
1387
|
+
schema_version=WorkflowMetadata.LATEST_SCHEMA_VERSION,
|
|
1388
|
+
engine_version_created_with=engine_version,
|
|
1389
|
+
node_libraries_referenced=list(serialized_flow_commands.node_libraries_used),
|
|
1390
|
+
workflows_referenced=workflows_referenced,
|
|
1391
|
+
creation_date=creation_date,
|
|
1392
|
+
last_modified_date=datetime.now(tz=UTC),
|
|
1393
|
+
branched_from=branched_from,
|
|
1394
|
+
workflow_shape=workflow_shape,
|
|
1395
|
+
image=image_path,
|
|
1396
|
+
)
|
|
1397
|
+
|
|
1398
|
+
def _generate_workflow_file_content( # noqa: PLR0912, C901
|
|
1399
|
+
self,
|
|
1400
|
+
serialized_flow_commands: SerializedFlowCommands,
|
|
1401
|
+
workflow_metadata: WorkflowMetadata,
|
|
1402
|
+
execution_flow_name: str,
|
|
1403
|
+
) -> str:
|
|
1404
|
+
"""Generate workflow file content from serialized commands and metadata."""
|
|
1220
1405
|
metadata_block = self._generate_workflow_metadata_header(workflow_metadata=workflow_metadata)
|
|
1221
1406
|
if metadata_block is None:
|
|
1222
|
-
details = f"Failed to generate metadata block for workflow '{
|
|
1407
|
+
details = f"Failed to generate metadata block for workflow '{workflow_metadata.name}'."
|
|
1223
1408
|
raise ValueError(details)
|
|
1224
1409
|
|
|
1225
1410
|
import_recorder = ImportRecorder()
|
|
@@ -1233,7 +1418,7 @@ class WorkflowManager:
|
|
|
1233
1418
|
for node in prereq_code:
|
|
1234
1419
|
ast_container.add_node(node)
|
|
1235
1420
|
|
|
1236
|
-
# Generate unique values code AST node
|
|
1421
|
+
# Generate unique values code AST node
|
|
1237
1422
|
unique_values_node = self._generate_unique_values_code(
|
|
1238
1423
|
unique_parameter_uuid_to_values=serialized_flow_commands.unique_parameter_uuid_to_values,
|
|
1239
1424
|
prefix="top_level",
|
|
@@ -1241,7 +1426,10 @@ class WorkflowManager:
|
|
|
1241
1426
|
)
|
|
1242
1427
|
ast_container.add_node(unique_values_node)
|
|
1243
1428
|
|
|
1244
|
-
#
|
|
1429
|
+
# Keep track of each flow and node index we've created
|
|
1430
|
+
flow_creation_index = 0
|
|
1431
|
+
|
|
1432
|
+
# See if this serialized flow has a flow initialization command; if it does, we'll need to insert that
|
|
1245
1433
|
flow_initialization_command = serialized_flow_commands.flow_initialization_command
|
|
1246
1434
|
|
|
1247
1435
|
match flow_initialization_command:
|
|
@@ -1263,7 +1451,7 @@ class WorkflowManager:
|
|
|
1263
1451
|
# No initialization command, deserialize into current context
|
|
1264
1452
|
pass
|
|
1265
1453
|
|
|
1266
|
-
# Generate assign flow context AST node, if we have any children commands
|
|
1454
|
+
# Generate assign flow context AST node, if we have any children commands
|
|
1267
1455
|
# Skip content generation for referenced workflows - they should only have the import command
|
|
1268
1456
|
is_referenced_workflow = isinstance(flow_initialization_command, ImportWorkflowAsReferencedSubFlowRequest)
|
|
1269
1457
|
has_content_to_serialize = (
|
|
@@ -1275,12 +1463,15 @@ class WorkflowManager:
|
|
|
1275
1463
|
)
|
|
1276
1464
|
|
|
1277
1465
|
if not is_referenced_workflow and has_content_to_serialize:
|
|
1466
|
+
# Keep track of all of the nodes we create and the generated variable names for them
|
|
1467
|
+
node_uuid_to_node_variable_name: dict[SerializedNodeCommands.NodeUUID, str] = {}
|
|
1468
|
+
|
|
1278
1469
|
# Create the "with..." statement
|
|
1279
1470
|
assign_flow_context_node = self._generate_assign_flow_context(
|
|
1280
1471
|
flow_initialization_command=flow_initialization_command, flow_creation_index=flow_creation_index
|
|
1281
1472
|
)
|
|
1282
1473
|
|
|
1283
|
-
# Generate nodes in flow AST node. This will create the node and apply all element modifiers
|
|
1474
|
+
# Generate nodes in flow AST node. This will create the node and apply all element modifiers
|
|
1284
1475
|
nodes_in_flow = self._generate_nodes_in_flow(
|
|
1285
1476
|
serialized_flow_commands, import_recorder, node_uuid_to_node_variable_name
|
|
1286
1477
|
)
|
|
@@ -1307,7 +1498,7 @@ class WorkflowManager:
|
|
|
1307
1498
|
)
|
|
1308
1499
|
assign_flow_context_node.body.append(cast("ast.stmt", sub_flow_import_node))
|
|
1309
1500
|
|
|
1310
|
-
# Now generate the connection code and add it to the flow context
|
|
1501
|
+
# Now generate the connection code and add it to the flow context
|
|
1311
1502
|
connection_asts = self._generate_connections_code(
|
|
1312
1503
|
serialized_connections=serialized_flow_commands.serialized_connections,
|
|
1313
1504
|
node_uuid_to_node_variable_name=node_uuid_to_node_variable_name,
|
|
@@ -1315,7 +1506,7 @@ class WorkflowManager:
|
|
|
1315
1506
|
)
|
|
1316
1507
|
assign_flow_context_node.body.extend(connection_asts)
|
|
1317
1508
|
|
|
1318
|
-
#
|
|
1509
|
+
# Generate parameter values
|
|
1319
1510
|
set_parameter_value_asts = self._generate_set_parameter_value_code(
|
|
1320
1511
|
set_parameter_value_commands=serialized_flow_commands.set_parameter_value_commands,
|
|
1321
1512
|
lock_commands=serialized_flow_commands.set_lock_commands_per_node,
|
|
@@ -1327,166 +1518,20 @@ class WorkflowManager:
|
|
|
1327
1518
|
|
|
1328
1519
|
ast_container.add_node(assign_flow_context_node)
|
|
1329
1520
|
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
if top_level_flow_name
|
|
1336
|
-
else None
|
|
1521
|
+
# Generate workflow execution code
|
|
1522
|
+
workflow_execution_code = self._generate_workflow_execution(
|
|
1523
|
+
flow_name=execution_flow_name,
|
|
1524
|
+
import_recorder=import_recorder,
|
|
1525
|
+
workflow_metadata=workflow_metadata,
|
|
1337
1526
|
)
|
|
1338
1527
|
if workflow_execution_code is not None:
|
|
1339
1528
|
for node in workflow_execution_code:
|
|
1340
1529
|
ast_container.add_node(node)
|
|
1341
1530
|
|
|
1342
|
-
# TODO: https://github.com/griptape-ai/griptape-nodes/issues/1190 do child workflows
|
|
1343
|
-
|
|
1344
1531
|
# Generate final code from ASTContainer
|
|
1345
1532
|
ast_output = "\n\n".join([ast.unparse(node) for node in ast_container.get_ast()])
|
|
1346
1533
|
import_output = import_recorder.generate_imports()
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
return final_code_output, workflow_metadata
|
|
1350
|
-
|
|
1351
|
-
def on_save_workflow_request(self, request: SaveWorkflowRequest) -> ResultPayload: # noqa: C901, PLR0912, PLR0915
|
|
1352
|
-
# Start with the file name provided; we may change it.
|
|
1353
|
-
file_name = request.file_name
|
|
1354
|
-
|
|
1355
|
-
# See if we had an existing workflow for this.
|
|
1356
|
-
prior_workflow = None
|
|
1357
|
-
creation_date = None
|
|
1358
|
-
if file_name and WorkflowRegistry.has_workflow_with_name(file_name):
|
|
1359
|
-
# Get the metadata.
|
|
1360
|
-
prior_workflow = WorkflowRegistry.get_workflow_by_name(file_name)
|
|
1361
|
-
# We'll use its creation date.
|
|
1362
|
-
creation_date = prior_workflow.metadata.creation_date
|
|
1363
|
-
elif file_name:
|
|
1364
|
-
# If no prior workflow exists for the new name, check if there's a current workflow
|
|
1365
|
-
# context (e.g., during rename operations) to preserve metadata from
|
|
1366
|
-
context_manager = GriptapeNodes.ContextManager()
|
|
1367
|
-
if context_manager.has_current_workflow():
|
|
1368
|
-
current_workflow_name = context_manager.get_current_workflow_name()
|
|
1369
|
-
if current_workflow_name and WorkflowRegistry.has_workflow_with_name(current_workflow_name):
|
|
1370
|
-
prior_workflow = WorkflowRegistry.get_workflow_by_name(current_workflow_name)
|
|
1371
|
-
creation_date = prior_workflow.metadata.creation_date
|
|
1372
|
-
|
|
1373
|
-
if (creation_date is None) or (creation_date == WorkflowManager.EPOCH_START):
|
|
1374
|
-
# Either a new workflow, or a backcompat situation.
|
|
1375
|
-
creation_date = datetime.now(tz=UTC)
|
|
1376
|
-
|
|
1377
|
-
# Let's see if this is a template file; if so, re-route it as a copy in the customer's workflow directory.
|
|
1378
|
-
if prior_workflow and prior_workflow.metadata.is_template:
|
|
1379
|
-
# Aha! User is attempting to save a template. Create a differently-named file in their workspace.
|
|
1380
|
-
# Find the first available file name that doesn't conflict.
|
|
1381
|
-
curr_idx = 1
|
|
1382
|
-
free_file_found = False
|
|
1383
|
-
while not free_file_found:
|
|
1384
|
-
# Composite a new candidate file name to test.
|
|
1385
|
-
new_file_name = f"{file_name}_{curr_idx}"
|
|
1386
|
-
new_file_name_with_extension = f"{new_file_name}.py"
|
|
1387
|
-
new_file_full_path = GriptapeNodes.ConfigManager().workspace_path.joinpath(new_file_name_with_extension)
|
|
1388
|
-
if new_file_full_path.exists():
|
|
1389
|
-
# Keep going.
|
|
1390
|
-
curr_idx += 1
|
|
1391
|
-
else:
|
|
1392
|
-
free_file_found = True
|
|
1393
|
-
file_name = new_file_name
|
|
1394
|
-
|
|
1395
|
-
# Get file name stuff prepped.
|
|
1396
|
-
# Use the existing registered file path if this is an existing workflow (not a template)
|
|
1397
|
-
if prior_workflow and not prior_workflow.metadata.is_template:
|
|
1398
|
-
# Use the existing registered file path
|
|
1399
|
-
relative_file_path = prior_workflow.file_path
|
|
1400
|
-
file_path = Path(WorkflowRegistry.get_complete_file_path(relative_file_path))
|
|
1401
|
-
# Extract file name from the path for metadata generation
|
|
1402
|
-
if not file_name:
|
|
1403
|
-
file_name = prior_workflow.metadata.name
|
|
1404
|
-
else:
|
|
1405
|
-
# Create new path in workspace for new workflows or templates
|
|
1406
|
-
if not file_name:
|
|
1407
|
-
file_name = datetime.now(tz=UTC).strftime("%d.%m_%H.%M")
|
|
1408
|
-
relative_file_path = f"{file_name}.py"
|
|
1409
|
-
file_path = GriptapeNodes.ConfigManager().workspace_path.joinpath(relative_file_path)
|
|
1410
|
-
|
|
1411
|
-
# Generate the workflow file contents
|
|
1412
|
-
try:
|
|
1413
|
-
final_code_output, workflow_metadata = self._generate_workflow_file_contents_and_metadata(
|
|
1414
|
-
file_name=file_name,
|
|
1415
|
-
creation_date=creation_date,
|
|
1416
|
-
image_path=request.image_path,
|
|
1417
|
-
prior_workflow=prior_workflow,
|
|
1418
|
-
)
|
|
1419
|
-
except Exception as err:
|
|
1420
|
-
details = f"Attempted to save workflow '{relative_file_path}', but {err}"
|
|
1421
|
-
return SaveWorkflowResultFailure(result_details=details)
|
|
1422
|
-
|
|
1423
|
-
# Create the pathing and write the file
|
|
1424
|
-
try:
|
|
1425
|
-
file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1426
|
-
except OSError as e:
|
|
1427
|
-
details = f"Attempted to save workflow '{file_name}'. Failed when creating directory: {e}"
|
|
1428
|
-
return SaveWorkflowResultFailure(result_details=details)
|
|
1429
|
-
|
|
1430
|
-
# Check disk space before writing
|
|
1431
|
-
config_manager = GriptapeNodes.ConfigManager()
|
|
1432
|
-
min_space_gb = config_manager.get_config_value("minimum_disk_space_gb_workflows")
|
|
1433
|
-
if not OSManager.check_available_disk_space(file_path.parent, min_space_gb):
|
|
1434
|
-
error_msg = OSManager.format_disk_space_error(file_path.parent)
|
|
1435
|
-
details = f"Attempted to save workflow '{file_name}' (requires {min_space_gb:.1f} GB). Failed: {error_msg}"
|
|
1436
|
-
return SaveWorkflowResultFailure(result_details=details)
|
|
1437
|
-
|
|
1438
|
-
try:
|
|
1439
|
-
with file_path.open("w", encoding="utf-8") as file:
|
|
1440
|
-
file.write(final_code_output)
|
|
1441
|
-
except OSError as e:
|
|
1442
|
-
details = f"Attempted to save workflow '{file_name}'. Failed when writing file: {e}"
|
|
1443
|
-
return SaveWorkflowResultFailure(result_details=details)
|
|
1444
|
-
|
|
1445
|
-
# save the created workflow as an entry in the JSON config file.
|
|
1446
|
-
registered_workflows = WorkflowRegistry.list_workflows()
|
|
1447
|
-
if file_name not in registered_workflows:
|
|
1448
|
-
# Only add to config if it's in the workspace directory (not an external file)
|
|
1449
|
-
if not Path(relative_file_path).is_absolute():
|
|
1450
|
-
try:
|
|
1451
|
-
GriptapeNodes.ConfigManager().save_user_workflow_json(str(file_path))
|
|
1452
|
-
except OSError as e:
|
|
1453
|
-
details = f"Attempted to save workflow '{file_name}'. Failed when saving configuration: {e}"
|
|
1454
|
-
return SaveWorkflowResultFailure(result_details=details)
|
|
1455
|
-
WorkflowRegistry.generate_new_workflow(metadata=workflow_metadata, file_path=relative_file_path)
|
|
1456
|
-
# Update existing workflow's metadata in the registry
|
|
1457
|
-
existing_workflow = WorkflowRegistry.get_workflow_by_name(file_name)
|
|
1458
|
-
existing_workflow.metadata = workflow_metadata
|
|
1459
|
-
details = f"Successfully saved workflow to: {file_path}"
|
|
1460
|
-
return SaveWorkflowResultSuccess(
|
|
1461
|
-
file_path=str(file_path), result_details=ResultDetails(message=details, level=logging.INFO)
|
|
1462
|
-
)
|
|
1463
|
-
|
|
1464
|
-
def _generate_workflow_metadata( # noqa: PLR0913
|
|
1465
|
-
self,
|
|
1466
|
-
file_name: str,
|
|
1467
|
-
engine_version: str,
|
|
1468
|
-
creation_date: datetime,
|
|
1469
|
-
node_libraries_referenced: list[LibraryNameAndVersion],
|
|
1470
|
-
workflows_referenced: list[str] | None = None,
|
|
1471
|
-
prior_workflow: Workflow | None = None,
|
|
1472
|
-
) -> WorkflowMetadata | None:
|
|
1473
|
-
# Preserve branched workflow information if it exists
|
|
1474
|
-
branched_from = None
|
|
1475
|
-
if prior_workflow and prior_workflow.metadata.branched_from:
|
|
1476
|
-
branched_from = prior_workflow.metadata.branched_from
|
|
1477
|
-
|
|
1478
|
-
workflow_metadata = WorkflowMetadata(
|
|
1479
|
-
name=str(file_name),
|
|
1480
|
-
schema_version=WorkflowMetadata.LATEST_SCHEMA_VERSION,
|
|
1481
|
-
engine_version_created_with=engine_version,
|
|
1482
|
-
node_libraries_referenced=node_libraries_referenced,
|
|
1483
|
-
workflows_referenced=workflows_referenced,
|
|
1484
|
-
creation_date=creation_date,
|
|
1485
|
-
last_modified_date=datetime.now(tz=UTC),
|
|
1486
|
-
branched_from=branched_from,
|
|
1487
|
-
)
|
|
1488
|
-
|
|
1489
|
-
return workflow_metadata
|
|
1534
|
+
return f"{metadata_block}\n\n{import_output}\n\n{ast_output}\n"
|
|
1490
1535
|
|
|
1491
1536
|
def _replace_workflow_metadata_header(self, workflow_content: str, new_metadata: WorkflowMetadata) -> str | None:
|
|
1492
1537
|
"""Replace the metadata header in a workflow file with new metadata.
|
|
@@ -1516,11 +1561,13 @@ class WorkflowManager:
|
|
|
1516
1561
|
toml_doc = tomlkit.document()
|
|
1517
1562
|
toml_doc.add("dependencies", tomlkit.item([]))
|
|
1518
1563
|
griptape_tool_table = tomlkit.table()
|
|
1519
|
-
|
|
1564
|
+
# Strip out the Nones since TOML doesn't like those
|
|
1565
|
+
# WorkflowShape is now serialized as JSON string by Pydantic field_serializer;
|
|
1566
|
+
# this preserves the nil/null/None values that we WANT, but for all of the
|
|
1567
|
+
# Python-related Nones, TOML will flip out if they are not stripped.
|
|
1568
|
+
metadata_dict = workflow_metadata.model_dump(exclude_none=True)
|
|
1520
1569
|
for key, value in metadata_dict.items():
|
|
1521
|
-
|
|
1522
|
-
if value is not None:
|
|
1523
|
-
griptape_tool_table.add(key=key, value=value)
|
|
1570
|
+
griptape_tool_table.add(key=key, value=value)
|
|
1524
1571
|
toml_doc["tool"] = tomlkit.table()
|
|
1525
1572
|
toml_doc["tool"]["griptape-nodes"] = griptape_tool_table # type: ignore (this is the only way I could find to get tomlkit to do the dotted notation correctly)
|
|
1526
1573
|
except Exception as err:
|
|
@@ -1545,14 +1592,20 @@ class WorkflowManager:
|
|
|
1545
1592
|
self,
|
|
1546
1593
|
flow_name: str,
|
|
1547
1594
|
import_recorder: ImportRecorder,
|
|
1595
|
+
workflow_metadata: WorkflowMetadata,
|
|
1548
1596
|
) -> list[ast.AST] | None:
|
|
1549
1597
|
"""Generates execute_workflow(...) and the __main__ guard."""
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
except ValueError:
|
|
1598
|
+
# Use workflow shape from metadata if available, otherwise skip execution block
|
|
1599
|
+
if workflow_metadata.workflow_shape is None:
|
|
1553
1600
|
logger.info("Workflow shape does not have required Start or End Nodes. Skipping local execution block.")
|
|
1554
1601
|
return None
|
|
1555
1602
|
|
|
1603
|
+
# Convert WorkflowShape to dict format expected by the rest of the method
|
|
1604
|
+
workflow_shape = {
|
|
1605
|
+
"input": workflow_metadata.workflow_shape.inputs,
|
|
1606
|
+
"output": workflow_metadata.workflow_shape.outputs,
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1556
1609
|
# === imports ===
|
|
1557
1610
|
import_recorder.add_import("argparse")
|
|
1558
1611
|
import_recorder.add_import("asyncio")
|
|
@@ -2608,7 +2661,7 @@ class WorkflowManager:
|
|
|
2608
2661
|
)
|
|
2609
2662
|
|
|
2610
2663
|
if flow_initialization_command is None:
|
|
2611
|
-
# Construct AST for "GriptapeNodes.ContextManager().flow(GriptapeNodes.ContextManager().
|
|
2664
|
+
# Construct AST for "GriptapeNodes.ContextManager().flow(GriptapeNodes.ContextManager().get_current_flow().flow_name)"
|
|
2612
2665
|
flow_call = ast.Call(
|
|
2613
2666
|
func=ast.Attribute(
|
|
2614
2667
|
value=ast.Call(func=context_manager, args=[], keywords=[], lineno=1, col_offset=0),
|
|
@@ -2618,16 +2671,22 @@ class WorkflowManager:
|
|
|
2618
2671
|
col_offset=0,
|
|
2619
2672
|
),
|
|
2620
2673
|
args=[
|
|
2621
|
-
ast.
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2674
|
+
ast.Attribute(
|
|
2675
|
+
value=ast.Call(
|
|
2676
|
+
func=ast.Attribute(
|
|
2677
|
+
value=ast.Call(func=context_manager, args=[], keywords=[], lineno=1, col_offset=0),
|
|
2678
|
+
attr="get_current_flow",
|
|
2679
|
+
ctx=ast.Load(),
|
|
2680
|
+
lineno=1,
|
|
2681
|
+
col_offset=0,
|
|
2682
|
+
),
|
|
2683
|
+
args=[],
|
|
2684
|
+
keywords=[],
|
|
2626
2685
|
lineno=1,
|
|
2627
2686
|
col_offset=0,
|
|
2628
2687
|
),
|
|
2629
|
-
|
|
2630
|
-
|
|
2688
|
+
attr="flow_name",
|
|
2689
|
+
ctx=ast.Load(),
|
|
2631
2690
|
lineno=1,
|
|
2632
2691
|
col_offset=0,
|
|
2633
2692
|
)
|
|
@@ -3101,17 +3160,12 @@ class WorkflowManager:
|
|
|
3101
3160
|
for node in nodes:
|
|
3102
3161
|
for param in node.parameters:
|
|
3103
3162
|
# Expose only the parameters that are relevant for workflow input and output.
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
if param.type != ParameterTypeBuiltin.CONTROL_TYPE.value and not param.type.startswith("list"):
|
|
3163
|
+
param_info = self.extract_parameter_shape_info(param, include_control_params=False)
|
|
3164
|
+
if param_info is not None:
|
|
3107
3165
|
if node.name in workflow_shape[workflow_shape_type]:
|
|
3108
|
-
cast("dict", workflow_shape[workflow_shape_type][node.name])[param.name] =
|
|
3109
|
-
self._convert_parameter_to_minimal_dict(param)
|
|
3110
|
-
)
|
|
3166
|
+
cast("dict", workflow_shape[workflow_shape_type][node.name])[param.name] = param_info
|
|
3111
3167
|
else:
|
|
3112
|
-
workflow_shape[workflow_shape_type][node.name] = {
|
|
3113
|
-
param.name: self._convert_parameter_to_minimal_dict(param)
|
|
3114
|
-
}
|
|
3168
|
+
workflow_shape[workflow_shape_type][node.name] = {param.name: param_info}
|
|
3115
3169
|
return workflow_shape
|
|
3116
3170
|
|
|
3117
3171
|
def extract_workflow_shape(self, workflow_name: str) -> dict[str, Any]:
|
|
@@ -3162,6 +3216,50 @@ class WorkflowManager:
|
|
|
3162
3216
|
|
|
3163
3217
|
return workflow_shape
|
|
3164
3218
|
|
|
3219
|
+
def extract_parameter_shape_info(
|
|
3220
|
+
self, parameter: Parameter, *, include_control_params: bool = False
|
|
3221
|
+
) -> ParameterShapeInfo | None:
|
|
3222
|
+
"""Extract shape information from a parameter for workflow shape building.
|
|
3223
|
+
|
|
3224
|
+
Expose only the parameters that are relevant for workflow input and output.
|
|
3225
|
+
|
|
3226
|
+
Args:
|
|
3227
|
+
parameter: The parameter to extract shape info from
|
|
3228
|
+
include_control_params: Whether to include control type parameters (default: False)
|
|
3229
|
+
|
|
3230
|
+
Returns:
|
|
3231
|
+
Parameter info dict if relevant for workflow shape, None if should be excluded
|
|
3232
|
+
"""
|
|
3233
|
+
# TODO (https://github.com/griptape-ai/griptape-nodes/issues/1090): This is a temporary solution until we know how to handle container types.
|
|
3234
|
+
# Always exclude list types until container type handling is implemented
|
|
3235
|
+
if parameter.type.startswith("list"):
|
|
3236
|
+
logger.warning(
|
|
3237
|
+
"Skipping list parameter '%s' of type '%s' in workflow shape - container types not yet supported",
|
|
3238
|
+
parameter.name,
|
|
3239
|
+
parameter.type,
|
|
3240
|
+
)
|
|
3241
|
+
return None
|
|
3242
|
+
|
|
3243
|
+
# Conditionally exclude control types
|
|
3244
|
+
if not include_control_params and parameter.type == ParameterTypeBuiltin.CONTROL_TYPE.value:
|
|
3245
|
+
return None
|
|
3246
|
+
|
|
3247
|
+
return self._convert_parameter_to_minimal_dict(parameter)
|
|
3248
|
+
|
|
3249
|
+
def build_workflow_shape_from_parameter_info(
|
|
3250
|
+
self, input_node_params: WorkflowShapeNodes, output_node_params: WorkflowShapeNodes
|
|
3251
|
+
) -> WorkflowShape:
|
|
3252
|
+
"""Build a WorkflowShape from collected parameter information.
|
|
3253
|
+
|
|
3254
|
+
Args:
|
|
3255
|
+
input_node_params: Mapping of input node names to their parameter info
|
|
3256
|
+
output_node_params: Mapping of output node names to their parameter info
|
|
3257
|
+
|
|
3258
|
+
Returns:
|
|
3259
|
+
WorkflowShape object with inputs and outputs
|
|
3260
|
+
"""
|
|
3261
|
+
return WorkflowShape(inputs=input_node_params, outputs=output_node_params)
|
|
3262
|
+
|
|
3165
3263
|
async def on_publish_workflow_request(self, request: PublishWorkflowRequest) -> ResultPayload:
|
|
3166
3264
|
try:
|
|
3167
3265
|
publisher_name = request.publisher_name
|