vellum-ai 0.14.46__py3-none-any.whl → 0.14.48__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.
Files changed (152) hide show
  1. vellum/client/README.md +2 -2
  2. vellum/client/__init__.py +72 -6
  3. vellum/client/core/client_wrapper.py +1 -1
  4. vellum/client/core/file.py +13 -8
  5. vellum/client/core/http_client.py +26 -14
  6. vellum/client/core/pydantic_utilities.py +2 -2
  7. vellum/client/core/request_options.py +3 -0
  8. vellum/client/resources/ad_hoc/client.py +14 -2
  9. vellum/client/resources/container_images/client.py +6 -0
  10. vellum/client/resources/deployments/client.py +12 -0
  11. vellum/client/resources/document_indexes/client.py +18 -0
  12. vellum/client/resources/documents/client.py +6 -0
  13. vellum/client/resources/folder_entities/client.py +6 -0
  14. vellum/client/resources/metric_definitions/client.py +6 -0
  15. vellum/client/resources/prompts/client.py +6 -0
  16. vellum/client/resources/sandboxes/client.py +12 -0
  17. vellum/client/resources/test_suite_runs/client.py +6 -0
  18. vellum/client/resources/test_suites/client.py +2 -2
  19. vellum/client/resources/workflow_deployments/client.py +6 -0
  20. vellum/client/resources/workflow_sandboxes/client.py +6 -0
  21. vellum/client/resources/workflows/client.py +6 -4
  22. vellum/client/resources/workspace_secrets/client.py +6 -0
  23. vellum/client/types/api_request_parent_context.py +0 -6
  24. vellum/client/types/array_input.py +0 -5
  25. vellum/client/types/code_execution_node_array_result.py +0 -5
  26. vellum/client/types/code_execution_node_result.py +0 -5
  27. vellum/client/types/code_execution_node_result_data.py +0 -5
  28. vellum/client/types/code_executor_response.py +0 -5
  29. vellum/client/types/create_test_suite_test_case_request.py +0 -5
  30. vellum/client/types/deployment_history_item.py +0 -5
  31. vellum/client/types/deployment_read.py +0 -5
  32. vellum/client/types/execute_workflow_response.py +0 -5
  33. vellum/client/types/execution_array_vellum_value.py +0 -5
  34. vellum/client/types/external_test_case_execution.py +0 -5
  35. vellum/client/types/external_test_case_execution_request.py +0 -5
  36. vellum/client/types/fulfilled_execute_workflow_workflow_result_event.py +0 -7
  37. vellum/client/types/fulfilled_workflow_node_result_event.py +0 -5
  38. vellum/client/types/initiated_workflow_node_result_event.py +0 -5
  39. vellum/client/types/metadata_filter_config_request.py +0 -5
  40. vellum/client/types/metric_definition_execution.py +0 -5
  41. vellum/client/types/metric_definition_history_item.py +0 -5
  42. vellum/client/types/named_test_case_array_variable_value.py +0 -5
  43. vellum/client/types/named_test_case_array_variable_value_request.py +0 -7
  44. vellum/client/types/node_execution_fulfilled_event.py +0 -11
  45. vellum/client/types/node_execution_initiated_event.py +0 -11
  46. vellum/client/types/node_execution_paused_event.py +0 -11
  47. vellum/client/types/node_execution_rejected_event.py +0 -11
  48. vellum/client/types/node_execution_resumed_event.py +0 -11
  49. vellum/client/types/node_execution_span.py +0 -11
  50. vellum/client/types/node_execution_streaming_event.py +0 -11
  51. vellum/client/types/node_input_compiled_array_value.py +0 -5
  52. vellum/client/types/node_output_compiled_array_value.py +0 -5
  53. vellum/client/types/node_parent_context.py +0 -6
  54. vellum/client/types/paginated_slim_deployment_read_list.py +0 -5
  55. vellum/client/types/paginated_slim_workflow_deployment_list.py +0 -5
  56. vellum/client/types/paginated_test_suite_run_execution_list.py +0 -5
  57. vellum/client/types/paginated_test_suite_test_case_list.py +0 -5
  58. vellum/client/types/prompt_deployment_parent_context.py +0 -6
  59. vellum/client/types/prompt_exec_config.py +0 -6
  60. vellum/client/types/rejected_workflow_node_result_event.py +0 -5
  61. vellum/client/types/replace_test_suite_test_case_request.py +0 -5
  62. vellum/client/types/search_filters_request.py +0 -7
  63. vellum/client/types/search_request_options_request.py +0 -7
  64. vellum/client/types/slim_deployment_read.py +0 -5
  65. vellum/client/types/slim_workflow_deployment.py +0 -5
  66. vellum/client/types/slim_workflow_execution_read.py +0 -12
  67. vellum/client/types/span_link.py +0 -6
  68. vellum/client/types/streaming_workflow_node_result_event.py +0 -5
  69. vellum/client/types/templating_node_array_result.py +0 -5
  70. vellum/client/types/templating_node_result.py +0 -5
  71. vellum/client/types/templating_node_result_data.py +0 -5
  72. vellum/client/types/terminal_node_array_result.py +0 -5
  73. vellum/client/types/terminal_node_result.py +0 -5
  74. vellum/client/types/terminal_node_result_data.py +0 -5
  75. vellum/client/types/test_case_array_variable_value.py +0 -5
  76. vellum/client/types/test_suite_run_execution.py +0 -5
  77. vellum/client/types/test_suite_run_execution_array_output.py +0 -5
  78. vellum/client/types/test_suite_run_execution_metric_result.py +0 -5
  79. vellum/client/types/test_suite_run_external_exec_config.py +0 -5
  80. vellum/client/types/test_suite_run_external_exec_config_data.py +0 -5
  81. vellum/client/types/test_suite_run_external_exec_config_data_request.py +0 -7
  82. vellum/client/types/test_suite_run_external_exec_config_request.py +0 -7
  83. vellum/client/types/test_suite_run_metric_array_output.py +0 -5
  84. vellum/client/types/test_suite_run_read.py +0 -5
  85. vellum/client/types/test_suite_test_case.py +0 -5
  86. vellum/client/types/test_suite_test_case_create_bulk_operation_request.py +0 -7
  87. vellum/client/types/test_suite_test_case_replace_bulk_operation_request.py +0 -7
  88. vellum/client/types/test_suite_test_case_upsert_bulk_operation_request.py +0 -7
  89. vellum/client/types/upsert_test_suite_test_case_request.py +0 -5
  90. vellum/client/types/vellum_value_logical_condition_group_request.py +0 -3
  91. vellum/client/types/vellum_value_logical_condition_request.py +0 -5
  92. vellum/client/types/vellum_variable.py +0 -5
  93. vellum/client/types/workflow_deployment_event_executions_response.py +0 -26
  94. vellum/client/types/workflow_deployment_history_item.py +0 -5
  95. vellum/client/types/workflow_deployment_parent_context.py +0 -6
  96. vellum/client/types/workflow_deployment_read.py +0 -5
  97. vellum/client/types/workflow_deployment_release.py +0 -5
  98. vellum/client/types/workflow_deployment_release_workflow_version.py +0 -5
  99. vellum/client/types/workflow_event_execution_read.py +0 -12
  100. vellum/client/types/workflow_execution_actual.py +0 -5
  101. vellum/client/types/workflow_execution_fulfilled_event.py +0 -11
  102. vellum/client/types/workflow_execution_initiated_event.py +0 -11
  103. vellum/client/types/workflow_execution_node_result_event.py +0 -5
  104. vellum/client/types/workflow_execution_paused_event.py +0 -11
  105. vellum/client/types/workflow_execution_rejected_event.py +0 -11
  106. vellum/client/types/workflow_execution_resumed_event.py +0 -11
  107. vellum/client/types/workflow_execution_snapshotted_event.py +0 -13
  108. vellum/client/types/workflow_execution_span.py +0 -11
  109. vellum/client/types/workflow_execution_streaming_event.py +0 -11
  110. vellum/client/types/workflow_execution_view_online_eval_metric_result.py +0 -7
  111. vellum/client/types/workflow_execution_workflow_result_event.py +0 -5
  112. vellum/client/types/workflow_output_array.py +0 -5
  113. vellum/client/types/workflow_parent_context.py +0 -6
  114. vellum/client/types/workflow_result_event.py +0 -5
  115. vellum/client/types/workflow_result_event_output_data_array.py +0 -5
  116. vellum/client/types/workflow_sandbox_parent_context.py +0 -6
  117. vellum/workflows/nodes/bases/base.py +26 -6
  118. vellum/workflows/nodes/bases/tests/test_base_node.py +30 -0
  119. vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py +50 -0
  120. vellum/workflows/nodes/utils.py +5 -1
  121. vellum/workflows/types/code_execution_node_wrappers.py +5 -0
  122. {vellum_ai-0.14.46.dist-info → vellum_ai-0.14.48.dist-info}/METADATA +1 -1
  123. {vellum_ai-0.14.46.dist-info → vellum_ai-0.14.48.dist-info}/RECORD +152 -152
  124. vellum_cli/__init__.py +3 -2
  125. vellum_cli/image_push.py +15 -3
  126. vellum_cli/tests/test_image_push.py +109 -0
  127. vellum_ee/workflows/display/nodes/base_node_display.py +25 -9
  128. vellum_ee/workflows/display/nodes/get_node_display_class.py +4 -5
  129. vellum_ee/workflows/display/nodes/vellum/api_node.py +11 -0
  130. vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +5 -0
  131. vellum_ee/workflows/display/nodes/vellum/error_node.py +22 -16
  132. vellum_ee/workflows/display/nodes/vellum/guardrail_node.py +2 -0
  133. vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +47 -13
  134. vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +2 -0
  135. vellum_ee/workflows/display/nodes/vellum/map_node.py +2 -0
  136. vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py +2 -0
  137. vellum_ee/workflows/display/nodes/vellum/search_node.py +8 -0
  138. vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py +1 -0
  139. vellum_ee/workflows/display/nodes/vellum/templating_node.py +2 -0
  140. vellum_ee/workflows/display/nodes/vellum/tests/test_code_execution_node.py +1 -1
  141. vellum_ee/workflows/display/nodes/vellum/tests/test_error_node.py +4 -0
  142. vellum_ee/workflows/display/nodes/vellum/tests/test_prompt_deployment_node.py +4 -3
  143. vellum_ee/workflows/display/nodes/vellum/tests/test_prompt_node.py +67 -2
  144. vellum_ee/workflows/display/nodes/vellum/tests/test_subworkflow_deployment_node.py +5 -4
  145. vellum_ee/workflows/display/nodes/vellum/tests/test_templating_node.py +1 -1
  146. vellum_ee/workflows/display/tests/test_base_workflow_display.py +44 -0
  147. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py +2 -4
  148. vellum_ee/workflows/display/utils/expressions.py +31 -4
  149. vellum_ee/workflows/display/workflows/tests/test_workflow_display.py +162 -0
  150. {vellum_ai-0.14.46.dist-info → vellum_ai-0.14.48.dist-info}/LICENSE +0 -0
  151. {vellum_ai-0.14.46.dist-info → vellum_ai-0.14.48.dist-info}/WHEEL +0 -0
  152. {vellum_ai-0.14.46.dist-info → vellum_ai-0.14.48.dist-info}/entry_points.txt +0 -0
@@ -2,9 +2,11 @@ import pytest
2
2
  from uuid import UUID
3
3
  from typing import Type
4
4
 
5
+ from vellum.client.types.variable_prompt_block import VariablePromptBlock
5
6
  from vellum.workflows import BaseWorkflow
6
7
  from vellum.workflows.nodes import BaseNode
7
8
  from vellum.workflows.nodes.displayable.inline_prompt_node.node import InlinePromptNode
9
+ from vellum.workflows.ports.port import Port
8
10
  from vellum.workflows.references.lazy import LazyReference
9
11
  from vellum.workflows.state.base import BaseState
10
12
  from vellum_ee.workflows.display.nodes.vellum.inline_prompt_node import BaseInlinePromptNodeDisplay
@@ -78,7 +80,7 @@ def _display_class_with_node_input_ids_by_name_with_inputs_prefix(Node: Type[Inl
78
80
  @pytest.mark.parametrize(
79
81
  ["GetDisplayClass", "expected_input_id"],
80
82
  [
81
- (_no_display_class, "8aa4ce7f-5eb8-41b7-abd0-ea2b40c8fb88"),
83
+ (_no_display_class, "9b036991-67ff-4cd0-a4d7-b4ed581e8b6d"),
82
84
  (_display_class_with_node_input_ids_by_name, "fba6a4d5-835a-4e99-afb7-f6a4aed15110"),
83
85
  (_display_class_with_node_input_ids_by_name_with_inputs_prefix, "fba6a4d5-835a-4e99-afb7-f6a4aed15110"),
84
86
  ],
@@ -165,7 +167,7 @@ def test_serialize_node__prompt_inputs__state_reference():
165
167
  },
166
168
  },
167
169
  {
168
- "id": "3750feb9-5d5c-4150-b62d-a9924f466888",
170
+ "id": "b83c40f7-0159-442f-af03-e80870363c52",
169
171
  "key": "bar",
170
172
  "value": {
171
173
  "rules": [
@@ -181,3 +183,66 @@ def test_serialize_node__prompt_inputs__state_reference():
181
183
  },
182
184
  },
183
185
  ]
186
+
187
+
188
+ def test_serialize_node__unreferenced_variable_block__still_serializes():
189
+ # GIVEN a prompt node with an unreferenced variable block
190
+ class MyPromptNode(InlinePromptNode):
191
+ blocks = [VariablePromptBlock(input_variable="foo")]
192
+
193
+ # AND a workflow with the prompt node
194
+ class MyWorkflow(BaseWorkflow):
195
+ graph = MyPromptNode
196
+
197
+ # WHEN the prompt node is serialized
198
+ workflow_display = get_workflow_display(workflow_class=MyWorkflow)
199
+ serialized_workflow: dict = workflow_display.serialize()
200
+
201
+ # THEN the node should skip the state reference input rule
202
+ assert serialized_workflow["workflow_raw_data"]["nodes"][1]["data"]["exec_config"]["prompt_template_block_data"][
203
+ "blocks"
204
+ ] == [
205
+ {
206
+ "id": "fecbb5f3-e0a3-42ed-9774-6c68fd5db50c",
207
+ "block_type": "VARIABLE",
208
+ "input_variable_id": "ea3f6348-8553-4375-bd27-527df4e4f3c2",
209
+ "state": "ENABLED",
210
+ "cache_config": None,
211
+ }
212
+ ]
213
+
214
+ # AND we should have a warning of the invalid reference
215
+ # TODO: Come up with a proposal for how nodes should propagate warnings
216
+ # warnings = list(workflow_display.errors)
217
+ # assert len(warnings) == 1
218
+ # assert "Missing input variable 'foo' for prompt block 0" in str(warnings[0])
219
+
220
+
221
+ def test_serialize_node__port_groups():
222
+ # GIVEN a prompt node with ports
223
+ class MyPromptNode(InlinePromptNode):
224
+ class Ports(InlinePromptNode.Ports):
225
+ apple = Port.on_if(LazyReference(lambda: MyPromptNode.Outputs.text).equals("apple"))
226
+ banana = Port.on_if(LazyReference(lambda: MyPromptNode.Outputs.text).equals("banana"))
227
+
228
+ # AND a workflow with the prompt node
229
+ class MyWorkflow(BaseWorkflow):
230
+ graph = MyPromptNode
231
+
232
+ # WHEN the prompt node is serialized
233
+ workflow_display = get_workflow_display(workflow_class=MyWorkflow)
234
+ serialized_workflow: dict = workflow_display.serialize()
235
+
236
+ # THEN the node should have the ports serialized
237
+ my_prompt_node = next(
238
+ node for node in serialized_workflow["workflow_raw_data"]["nodes"] if node["id"] == str(MyPromptNode.__id__)
239
+ )
240
+ ports = my_prompt_node["ports"]
241
+ assert len(ports) == 2
242
+ assert ports[0]["id"] == "149d97a4-3da3-44a9-95f7-ea7b8d38b877"
243
+ assert ports[1]["id"] == "71f2d2b3-194f-4492-bc1c-a5ca1f60fb0a"
244
+ assert ports[0]["name"] == "apple"
245
+ assert ports[1]["name"] == "banana"
246
+
247
+ # AND the legacy source_handle_id should be the default port
248
+ assert my_prompt_node["data"]["source_handle_id"] == "149d97a4-3da3-44a9-95f7-ea7b8d38b877"
@@ -4,7 +4,8 @@ from uuid import UUID, uuid4
4
4
  from typing import Type
5
5
 
6
6
  from vellum.workflows import BaseWorkflow
7
- from vellum.workflows.nodes import SubworkflowDeploymentNode
7
+ from vellum.workflows.nodes.displayable.subworkflow_deployment_node.node import SubworkflowDeploymentNode
8
+ from vellum_ee.workflows.display.nodes.vellum.subworkflow_deployment_node import BaseSubworkflowDeploymentNodeDisplay
8
9
  from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
9
10
 
10
11
 
@@ -13,14 +14,14 @@ def _no_display_class(Node: Type[SubworkflowDeploymentNode]): # type: ignore
13
14
 
14
15
 
15
16
  def _display_class_with_node_input_ids_by_name(Node: Type[SubworkflowDeploymentNode]):
16
- class SubworkflowNodeDisplay(SubworkflowDeploymentNode[Node]): # type: ignore[valid-type]
17
+ class SubworkflowNodeDisplay(BaseSubworkflowDeploymentNodeDisplay[Node]): # type: ignore[valid-type]
17
18
  node_input_ids_by_name = {"foo": UUID("aff4f838-577e-44b9-ac5c-6d8213abbb9c")}
18
19
 
19
20
  return SubworkflowNodeDisplay
20
21
 
21
22
 
22
23
  def _display_class_with_node_input_ids_by_name_with_inputs_prefix(Node: Type[SubworkflowDeploymentNode]):
23
- class SubworkflowNodeDisplay(SubworkflowDeploymentNode[Node]): # type: ignore[valid-type]
24
+ class SubworkflowNodeDisplay(BaseSubworkflowDeploymentNodeDisplay[Node]): # type: ignore[valid-type]
24
25
  node_input_ids_by_name = {"subworkflow_inputs.foo": UUID("aff4f838-577e-44b9-ac5c-6d8213abbb9c")}
25
26
 
26
27
  return SubworkflowNodeDisplay
@@ -55,7 +56,7 @@ def mock_fetch_deployment(mocker):
55
56
  @pytest.mark.parametrize(
56
57
  ["GetDisplayClass", "expected_input_id"],
57
58
  [
58
- (_no_display_class, "aff4f838-577e-44b9-ac5c-6d8213abbb9c"),
59
+ (_no_display_class, "394132c2-1817-455e-9f3f-b7073eb63a2b"),
59
60
  (_display_class_with_node_input_ids_by_name, "aff4f838-577e-44b9-ac5c-6d8213abbb9c"),
60
61
  (_display_class_with_node_input_ids_by_name_with_inputs_prefix, "aff4f838-577e-44b9-ac5c-6d8213abbb9c"),
61
62
  ],
@@ -29,7 +29,7 @@ def _display_class_with_node_input_ids_by_name_with_inputs_prefix(Node: Type[Tem
29
29
  @pytest.mark.parametrize(
30
30
  ["GetDisplayClass", "expected_input_id"],
31
31
  [
32
- (_no_display_class, "d3519cec-590c-416d-8eb1-96051aed5ddd"),
32
+ (_no_display_class, "91d982a9-6c41-42ac-aff9-7b623c450a55"),
33
33
  (_display_class_with_node_input_ids_by_name, "fba6a4d5-835a-4e99-afb7-f6a4aed15110"),
34
34
  (_display_class_with_node_input_ids_by_name_with_inputs_prefix, "fba6a4d5-835a-4e99-afb7-f6a4aed15110"),
35
35
  ],
@@ -3,6 +3,8 @@ from typing import Dict
3
3
 
4
4
  from vellum.workflows.inputs import BaseInputs
5
5
  from vellum.workflows.nodes import BaseNode
6
+ from vellum.workflows.ports.port import Port
7
+ from vellum.workflows.references.lazy import LazyReference
6
8
  from vellum.workflows.state import BaseState
7
9
  from vellum.workflows.workflows.base import BaseWorkflow
8
10
  from vellum_ee.workflows.display.vellum import WorkflowInputsVellumDisplayOverrides
@@ -285,3 +287,45 @@ def test_vellum_workflow_display__serialize_with_parse_json_expression():
285
287
  "operator": "parseJson",
286
288
  },
287
289
  }
290
+
291
+
292
+ def test_serialize__port_with_lazy_reference():
293
+ # GIVEN a node with a lazy reference in a Port
294
+ class MyNode(BaseNode):
295
+ class Ports(BaseNode.Ports):
296
+ foo = Port.on_if(LazyReference(lambda: MyNode.Outputs.bar))
297
+
298
+ class Outputs(BaseNode.Outputs):
299
+ bar: bool
300
+
301
+ # AND a workflow that uses the node
302
+ class Workflow(BaseWorkflow):
303
+ graph = MyNode
304
+
305
+ # WHEN we serialize the workflow
306
+ workflow_display = get_workflow_display(workflow_class=Workflow)
307
+ exec_config = workflow_display.serialize()
308
+
309
+ # THEN the lazy reference should be serialized correctly
310
+ raw_data = exec_config["workflow_raw_data"]
311
+ assert isinstance(raw_data, dict)
312
+
313
+ nodes = raw_data["nodes"]
314
+ assert isinstance(nodes, list)
315
+
316
+ my_node = nodes[1]
317
+ assert isinstance(my_node, dict)
318
+ ports = my_node.get("ports")
319
+ assert isinstance(ports, list)
320
+ assert ports == [
321
+ {
322
+ "id": "6c26bc2b-6469-47c1-b858-d63f0d311ea6",
323
+ "name": "foo",
324
+ "type": "IF",
325
+ "expression": {
326
+ "type": "NODE_OUTPUT",
327
+ "node_id": str(MyNode.__id__),
328
+ "node_output_id": str(MyNode.__output_ids__["bar"]),
329
+ },
330
+ }
331
+ ]
@@ -87,7 +87,7 @@ def test_serialize_workflow():
87
87
  "type": "ERROR",
88
88
  "inputs": [
89
89
  {
90
- "id": "690d825f-6ffd-493e-8141-c86d384e6150",
90
+ "id": "8e4c8d76-2e02-4d7e-a7bf-d71af392dc49",
91
91
  "key": "error_source_input_id",
92
92
  "value": {
93
93
  "rules": [
@@ -101,11 +101,9 @@ def test_serialize_workflow():
101
101
  }
102
102
  ],
103
103
  "data": {
104
- "name": "error-node",
105
104
  "label": "Fail Node",
106
105
  "target_handle_id": "70c19f1c-309c-4a5d-ba65-664c0bb2fedf",
107
- "error_source_input_id": "None",
108
- "error_output_id": "None",
106
+ "error_source_input_id": "8e4c8d76-2e02-4d7e-a7bf-d71af392dc49",
109
107
  },
110
108
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
111
109
  "base": {
@@ -1,4 +1,4 @@
1
- from typing import TYPE_CHECKING, Any
1
+ from typing import TYPE_CHECKING, Any, Dict, cast
2
2
 
3
3
  from vellum.client.types.logical_operator import LogicalOperator
4
4
  from vellum.workflows.descriptors.base import BaseDescriptor
@@ -37,7 +37,7 @@ from vellum.workflows.references.output import OutputReference
37
37
  from vellum.workflows.references.state_value import StateValueReference
38
38
  from vellum.workflows.references.vellum_secret import VellumSecretReference
39
39
  from vellum.workflows.references.workflow_input import WorkflowInputReference
40
- from vellum.workflows.types.core import JsonObject
40
+ from vellum.workflows.types.core import JsonArray, JsonObject
41
41
  from vellum_ee.workflows.display.utils.exceptions import UnsupportedSerializationException
42
42
 
43
43
  if TYPE_CHECKING:
@@ -116,7 +116,7 @@ def get_child_descriptor(value: LazyReference, display_context: "WorkflowDisplay
116
116
  return value._get()
117
117
 
118
118
 
119
- def serialize_condition(display_context: "WorkflowDisplayContext", condition: BaseDescriptor) -> JsonObject:
119
+ def _serialize_condition(display_context: "WorkflowDisplayContext", condition: BaseDescriptor) -> JsonObject:
120
120
  if isinstance(
121
121
  condition,
122
122
  (
@@ -235,6 +235,33 @@ def serialize_value(display_context: "WorkflowDisplayContext", value: Any) -> Js
235
235
  "node_id": str(node_class_display.node_id),
236
236
  }
237
237
 
238
+ if isinstance(value, list):
239
+ serialized_items = [serialize_value(display_context, item) for item in value]
240
+ if all(isinstance(item, dict) and item["type"] == "CONSTANT_VALUE" for item in serialized_items):
241
+ constant_values = []
242
+ for item in serialized_items:
243
+ item_dict = cast(Dict[str, Any], item)
244
+ value_inner = item_dict["value"]
245
+
246
+ if value_inner["type"] == "JSON" and "items" in value_inner:
247
+ # Nested JSON list
248
+ constant_values.append(value_inner["items"])
249
+ else:
250
+ constant_values.append(value_inner["value"])
251
+
252
+ return {
253
+ "type": "CONSTANT_VALUE",
254
+ "value": {
255
+ "type": "JSON",
256
+ "items": constant_values,
257
+ },
258
+ }
259
+ else:
260
+ return {
261
+ "type": "ARRAY_REFERENCE",
262
+ "items": cast(JsonArray, serialized_items), # list[JsonObject] -> JsonArray
263
+ }
264
+
238
265
  if isinstance(value, dict) and any(isinstance(v, BaseDescriptor) for v in value.values()):
239
266
  raise ValueError("Nested references are not supported.")
240
267
 
@@ -247,4 +274,4 @@ def serialize_value(display_context: "WorkflowDisplayContext", value: Any) -> Js
247
274
 
248
275
  # If it's not any of the references we know about,
249
276
  # then try to serialize it as a nested value
250
- return serialize_condition(display_context, value)
277
+ return _serialize_condition(display_context, value)
@@ -425,3 +425,165 @@ def test_serialize_workflow__nested_lazy_reference():
425
425
  "node_id": str(OuterNode.__id__),
426
426
  "node_output_id": str(OuterNode.__output_ids__["bar"]),
427
427
  }
428
+
429
+
430
+ def test_serialize_workflow__array_values():
431
+ # GIVEN a node with array and nested array values
432
+ class MyNode(BaseNode):
433
+ class Outputs(BaseNode.Outputs):
434
+ array_value = ["item1", "item2", "item3"]
435
+ nested_array_value = [["item1", "item2", "item3"], ["item4", "item5", "item6"]]
436
+ mixed_array_value = [["item1"], "item2", "item3"]
437
+
438
+ # AND a workflow that uses these outputs
439
+ class MyWorkflow(BaseWorkflow):
440
+ graph = MyNode
441
+
442
+ class Outputs(BaseWorkflow.Outputs):
443
+ array_output = MyNode.Outputs.array_value
444
+ nested_array_output = MyNode.Outputs.nested_array_value
445
+
446
+ # WHEN we serialize it
447
+ workflow_display = get_workflow_display(workflow_class=MyWorkflow)
448
+ data = workflow_display.serialize()
449
+
450
+ # THEN it should properly serialize the array and dictionary values
451
+ assert isinstance(data["workflow_raw_data"], dict)
452
+ assert isinstance(data["workflow_raw_data"]["nodes"], list)
453
+ raw_nodes = data["workflow_raw_data"]["nodes"]
454
+ generic_nodes = [node for node in raw_nodes if isinstance(node, dict) and node["type"] == "GENERIC"]
455
+ assert len(generic_nodes) > 0
456
+ my_node = generic_nodes[0]
457
+
458
+ outputs = my_node["outputs"]
459
+ assert isinstance(outputs, list)
460
+
461
+ array_outputs = [val for val in outputs if isinstance(val, dict) and val["name"] == "array_value"]
462
+ assert len(array_outputs) > 0
463
+ array_output = array_outputs[0]
464
+
465
+ assert isinstance(array_output, dict)
466
+ assert "value" in array_output
467
+ assert array_output["value"] == {
468
+ "type": "CONSTANT_VALUE",
469
+ "value": {"type": "JSON", "items": ["item1", "item2", "item3"]},
470
+ }
471
+
472
+ nested_array_outputs = [val for val in outputs if isinstance(val, dict) and val["name"] == "nested_array_value"]
473
+ assert len(nested_array_outputs) > 0
474
+ nested_array_output = nested_array_outputs[0]
475
+
476
+ assert isinstance(nested_array_output, dict)
477
+ assert "value" in nested_array_output
478
+ assert nested_array_output["value"] == {
479
+ "type": "CONSTANT_VALUE",
480
+ "value": {"type": "JSON", "items": [["item1", "item2", "item3"], ["item4", "item5", "item6"]]},
481
+ }
482
+
483
+ mixed_array_outputs = [val for val in outputs if isinstance(val, dict) and val["name"] == "mixed_array_value"]
484
+ assert len(mixed_array_outputs) > 0
485
+ mixed_array_output = mixed_array_outputs[0]
486
+
487
+ assert isinstance(mixed_array_output, dict)
488
+ assert "value" in mixed_array_output
489
+ assert mixed_array_output["value"] == {
490
+ "type": "CONSTANT_VALUE",
491
+ "value": {"type": "JSON", "items": [["item1"], "item2", "item3"]},
492
+ }
493
+
494
+
495
+ def test_serialize_workflow__array_reference():
496
+ # GIVEN a node with array containing non-constant values (node references)
497
+ class FirstNode(BaseNode):
498
+ class Outputs(BaseNode.Outputs):
499
+ value1: str
500
+ value2: str
501
+
502
+ class SecondNode(BaseNode):
503
+ class Outputs(BaseNode.Outputs):
504
+ # Array containing a mix of constants and node references
505
+ mixed_array = ["constant1", FirstNode.Outputs.value1, "constant2", FirstNode.Outputs.value2]
506
+ mixed_nested_array = [["constant1", FirstNode.Outputs.value1], ["constant2", FirstNode.Outputs.value2]]
507
+
508
+ # AND a workflow that uses these outputs
509
+ class MyWorkflow(BaseWorkflow):
510
+ graph = FirstNode >> SecondNode
511
+
512
+ class Outputs(BaseWorkflow.Outputs):
513
+ mixed_array_output = SecondNode.Outputs.mixed_array
514
+ mixed_nested_array_output = SecondNode.Outputs.mixed_nested_array
515
+
516
+ # WHEN we serialize it
517
+ workflow_display = get_workflow_display(workflow_class=MyWorkflow)
518
+ data = workflow_display.serialize()
519
+
520
+ # THEN it should serialize as an ARRAY_REFERENCE
521
+ assert isinstance(data["workflow_raw_data"], dict)
522
+ assert isinstance(data["workflow_raw_data"]["nodes"], list)
523
+ assert len(data["workflow_raw_data"]["nodes"]) == 5
524
+ second_node = data["workflow_raw_data"]["nodes"][2]
525
+ assert isinstance(second_node, dict)
526
+
527
+ assert "outputs" in second_node
528
+ assert isinstance(second_node["outputs"], list)
529
+ outputs = second_node["outputs"]
530
+
531
+ mixed_array_outputs = [val for val in outputs if isinstance(val, dict) and val["name"] == "mixed_array"]
532
+ assert len(mixed_array_outputs) > 0
533
+ mixed_array_output = mixed_array_outputs[0]
534
+
535
+ assert isinstance(mixed_array_output, dict)
536
+ assert "value" in mixed_array_output
537
+ assert mixed_array_output["value"] == {
538
+ "type": "ARRAY_REFERENCE",
539
+ "items": [
540
+ {"type": "CONSTANT_VALUE", "value": {"type": "STRING", "value": "constant1"}},
541
+ {
542
+ "type": "NODE_OUTPUT",
543
+ "node_id": "702a08b5-61e8-4a7a-a83d-77f49e39c5be",
544
+ "node_output_id": "419b6afa-fab5-493a-ba1e-4606f4641616",
545
+ },
546
+ {"type": "CONSTANT_VALUE", "value": {"type": "STRING", "value": "constant2"}},
547
+ {
548
+ "type": "NODE_OUTPUT",
549
+ "node_id": "702a08b5-61e8-4a7a-a83d-77f49e39c5be",
550
+ "node_output_id": "d1cacc41-478d-49a3-a6b3-1ba2d51291e2",
551
+ },
552
+ ],
553
+ }
554
+
555
+ mixed_nested_array_outputs = [
556
+ val for val in outputs if isinstance(val, dict) and val["name"] == "mixed_nested_array"
557
+ ]
558
+ assert len(mixed_nested_array_outputs) > 0
559
+ mixed_nested_array_output = mixed_nested_array_outputs[0]
560
+
561
+ assert isinstance(mixed_nested_array_output, dict)
562
+ assert "value" in mixed_nested_array_output
563
+ assert mixed_nested_array_output["value"] == {
564
+ "type": "ARRAY_REFERENCE",
565
+ "items": [
566
+ {
567
+ "type": "ARRAY_REFERENCE",
568
+ "items": [
569
+ {"type": "CONSTANT_VALUE", "value": {"type": "STRING", "value": "constant1"}},
570
+ {
571
+ "type": "NODE_OUTPUT",
572
+ "node_id": "702a08b5-61e8-4a7a-a83d-77f49e39c5be",
573
+ "node_output_id": "419b6afa-fab5-493a-ba1e-4606f4641616",
574
+ },
575
+ ],
576
+ },
577
+ {
578
+ "type": "ARRAY_REFERENCE",
579
+ "items": [
580
+ {"type": "CONSTANT_VALUE", "value": {"type": "STRING", "value": "constant2"}},
581
+ {
582
+ "type": "NODE_OUTPUT",
583
+ "node_id": "702a08b5-61e8-4a7a-a83d-77f49e39c5be",
584
+ "node_output_id": "d1cacc41-478d-49a3-a6b3-1ba2d51291e2",
585
+ },
586
+ ],
587
+ },
588
+ ],
589
+ }