vellum-ai 0.14.72__py3-none-any.whl → 0.14.73__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 (66) hide show
  1. vellum/__init__.py +8 -0
  2. vellum/client/core/client_wrapper.py +1 -1
  3. vellum/client/core/serialization.py +1 -0
  4. vellum/client/types/__init__.py +8 -0
  5. vellum/client/types/build_status_enum.py +5 -0
  6. vellum/client/types/container_image_build_config.py +20 -0
  7. vellum/client/types/container_image_read.py +4 -0
  8. vellum/client/types/execute_api_response.py +2 -2
  9. vellum/client/types/folder_entity.py +2 -0
  10. vellum/client/types/folder_entity_dataset.py +26 -0
  11. vellum/client/types/folder_entity_dataset_data.py +25 -0
  12. vellum/types/build_status_enum.py +3 -0
  13. vellum/types/container_image_build_config.py +3 -0
  14. vellum/types/folder_entity_dataset.py +3 -0
  15. vellum/types/folder_entity_dataset_data.py +3 -0
  16. vellum/workflows/nodes/core/retry_node/tests/test_node.py +1 -1
  17. vellum/workflows/nodes/displayable/api_node/node.py +2 -0
  18. vellum/workflows/nodes/displayable/api_node/tests/test_api_node.py +43 -0
  19. vellum/workflows/nodes/displayable/bases/api_node/node.py +6 -0
  20. vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +30 -4
  21. vellum/workflows/nodes/displayable/bases/inline_prompt_node/tests/test_inline_prompt_node.py +43 -3
  22. vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +68 -58
  23. vellum/workflows/nodes/experimental/tool_calling_node/node.py +10 -10
  24. vellum/workflows/nodes/experimental/tool_calling_node/tests/__init__.py +0 -0
  25. vellum/workflows/nodes/experimental/tool_calling_node/tests/test_utils.py +49 -0
  26. vellum/workflows/nodes/experimental/tool_calling_node/utils.py +67 -6
  27. vellum/workflows/ports/utils.py +26 -6
  28. vellum/workflows/runner/runner.py +35 -3
  29. vellum/workflows/types/core.py +12 -0
  30. vellum/workflows/types/definition.py +6 -0
  31. vellum/workflows/utils/functions.py +9 -9
  32. vellum/workflows/utils/pydantic_schema.py +38 -0
  33. vellum/workflows/utils/tests/test_functions.py +11 -11
  34. {vellum_ai-0.14.72.dist-info → vellum_ai-0.14.73.dist-info}/METADATA +1 -1
  35. {vellum_ai-0.14.72.dist-info → vellum_ai-0.14.73.dist-info}/RECORD +66 -54
  36. vellum_cli/push.py +6 -8
  37. vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py +8 -1
  38. vellum_ee/workflows/display/tests/test_base_workflow_display.py +1 -1
  39. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +1 -1
  40. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attributes_serialization.py +1 -1
  41. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_api_node_serialization.py +5 -5
  42. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +12 -12
  43. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +10 -10
  44. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_default_state_serialization.py +3 -3
  45. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py +3 -3
  46. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_generic_node_serialization.py +20 -9
  47. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_guardrail_node_serialization.py +3 -3
  48. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_prompt_node_serialization.py +3 -3
  49. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +8 -8
  50. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +6 -6
  51. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +3 -3
  52. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_prompt_deployment_serialization.py +8 -8
  53. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_search_node_serialization.py +3 -3
  54. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_subworkflow_deployment_serialization.py +4 -4
  55. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_node_serialization.py +3 -3
  56. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py +2 -2
  57. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_serialization.py +5 -5
  58. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py +1 -1
  59. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py +1 -1
  60. vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +2 -2
  61. vellum_ee/workflows/display/utils/auto_layout.py +1 -1
  62. vellum_ee/workflows/display/workflows/base_workflow_display.py +179 -4
  63. vellum_ee/workflows/tests/test_serialize_module.py +47 -0
  64. {vellum_ai-0.14.72.dist-info → vellum_ai-0.14.73.dist-info}/LICENSE +0 -0
  65. {vellum_ai-0.14.72.dist-info → vellum_ai-0.14.73.dist-info}/WHEEL +0 -0
  66. {vellum_ai-0.14.72.dist-info → vellum_ai-0.14.73.dist-info}/entry_points.txt +0 -0
@@ -56,7 +56,7 @@ def test_serialize_workflow():
56
56
  "type": "ENTRYPOINT",
57
57
  "inputs": [],
58
58
  "data": {"label": "Entrypoint Node", "source_handle_id": "34069190-0942-4e0c-8700-b33b9dea4ea0"},
59
- "display_data": {"position": {"x": 0.0, "y": 0.0}},
59
+ "display_data": {"position": {"x": 0.0, "y": -50.0}},
60
60
  "base": None,
61
61
  "definition": None,
62
62
  }
@@ -103,7 +103,7 @@ def test_serialize_workflow():
103
103
  "template_node_input_id": "7c775379-d589-4d79-b876-dcd224d72966",
104
104
  "output_type": "JSON",
105
105
  },
106
- "display_data": {"position": {"x": 0.0, "y": 0.0}},
106
+ "display_data": {"position": {"x": 200.0, "y": -50.0}},
107
107
  "base": {
108
108
  "name": "TemplatingNode",
109
109
  "module": ["vellum", "workflows", "nodes", "core", "templating_node", "node"],
@@ -148,7 +148,7 @@ def test_serialize_workflow():
148
148
  },
149
149
  }
150
150
  ],
151
- "display_data": {"position": {"x": 0.0, "y": 0.0}},
151
+ "display_data": {"position": {"x": 400.0, "y": -50.0}},
152
152
  "base": {
153
153
  "name": "FinalOutputNode",
154
154
  "module": ["vellum", "workflows", "nodes", "displayable", "final_output_node", "node"],
@@ -54,7 +54,7 @@ def test_serialize_workflow():
54
54
  "type": "ENTRYPOINT",
55
55
  "inputs": [],
56
56
  "data": {"label": "Entrypoint Node", "source_handle_id": "8b8d52a2-844f-44fe-a6c4-142fa70d391b"},
57
- "display_data": {"position": {"x": 0.0, "y": 0.0}},
57
+ "display_data": {"position": {"x": 0.0, "y": -50.0}},
58
58
  "base": None,
59
59
  "definition": None,
60
60
  }
@@ -86,7 +86,7 @@ def test_serialize_workflow():
86
86
  },
87
87
  }
88
88
  ],
89
- "display_data": {"position": {"x": 0.0, "y": 0.0}},
89
+ "display_data": {"position": {"x": 200.0, "y": -50.0}},
90
90
  "base": {
91
91
  "name": "FinalOutputNode",
92
92
  "module": ["vellum", "workflows", "nodes", "displayable", "final_output_node", "node"],
@@ -43,7 +43,7 @@ def test_serialize_workflow():
43
43
  "label": "GetCurrentWeatherNode",
44
44
  "type": "GENERIC",
45
45
  "display_data": {
46
- "position": {"x": 0.0, "y": 0.0},
46
+ "position": {"x": 200.0, "y": -50.0},
47
47
  "comment": {"value": "\n A tool calling node that calls the get_current_weather function.\n "},
48
48
  },
49
49
  "base": {
@@ -142,7 +142,7 @@ def test_serialize_workflow():
142
142
  "label": "Entrypoint Node",
143
143
  "source_handle_id": "c344fdee-282b-40c9-8c97-6dd08830948c",
144
144
  },
145
- "display_data": {"position": {"x": 0.0, "y": 0.0}},
145
+ "display_data": {"position": {"x": 0.0, "y": -50.0}},
146
146
  "base": None,
147
147
  "definition": None,
148
148
  },
@@ -150,7 +150,7 @@ def test_serialize_workflow():
150
150
  "id": "1381c078-efa2-4255-89a1-7b4cb742c7fc",
151
151
  "label": "StartNode",
152
152
  "type": "GENERIC",
153
- "display_data": {"position": {"x": 0.0, "y": 0.0}},
153
+ "display_data": {"position": {"x": 200.0, "y": -50.0}},
154
154
  "base": {
155
155
  "name": "BaseNode",
156
156
  "module": ["vellum", "workflows", "nodes", "bases", "base"],
@@ -238,7 +238,7 @@ def test_serialize_workflow():
238
238
  },
239
239
  }
240
240
  ],
241
- "display_data": {"position": {"x": 0.0, "y": 0.0}},
241
+ "display_data": {"position": {"x": 400.0, "y": -175.0}},
242
242
  "base": {
243
243
  "name": "FinalOutputNode",
244
244
  "module": [
@@ -281,7 +281,7 @@ def test_serialize_workflow():
281
281
  },
282
282
  }
283
283
  ],
284
- "display_data": {"position": {"x": 0.0, "y": 0.0}},
284
+ "display_data": {"position": {"x": 400.0, "y": 75.0}},
285
285
  "base": {
286
286
  "name": "FinalOutputNode",
287
287
  "module": [
@@ -43,7 +43,7 @@ def test_serialize_workflow():
43
43
  "label": "GetCurrentWeatherNode",
44
44
  "type": "GENERIC",
45
45
  "display_data": {
46
- "position": {"x": 0.0, "y": 0.0},
46
+ "position": {"x": 200.0, "y": -50.0},
47
47
  "comment": {"value": "\n A tool calling node that calls the get_current_weather function.\n "},
48
48
  },
49
49
  "base": {
@@ -63,7 +63,7 @@ def test_serialize_workflow():
63
63
  "source_handle_id": "04da0bb6-5b42-4dd1-a4e4-08f3ab03e1a3",
64
64
  },
65
65
  "display_data": {
66
- "position": {"x": 0.0, "y": 0.0},
66
+ "position": {"x": 0.0, "y": -50.0},
67
67
  },
68
68
  }
69
69
 
@@ -61,7 +61,7 @@ def test_serialize_workflow__missing_final_output_node():
61
61
  },
62
62
  }
63
63
  ],
64
- "display_data": {"position": {"x": 0.0, "y": 0.0}},
64
+ "display_data": {"position": {"x": 200.0, "y": 75.0}},
65
65
  "base": {
66
66
  "name": "FinalOutputNode",
67
67
  "module": ["vellum", "workflows", "nodes", "displayable", "final_output_node", "node"],
@@ -111,7 +111,7 @@ def test_serialize_workflow__missing_final_output_node():
111
111
  },
112
112
  }
113
113
  ],
114
- "display_data": {"position": {"x": 0.0, "y": 0.0}},
114
+ "display_data": {"position": {"x": 400.0, "y": -50.0}},
115
115
  "base": {
116
116
  "name": "FinalOutputNode",
117
117
  "module": ["vellum", "workflows", "nodes", "displayable", "final_output_node", "node"],
@@ -102,7 +102,7 @@ def _topological_sort_layers(
102
102
  if neighbor in remaining_nodes:
103
103
  in_degree[neighbor] -= 1
104
104
 
105
- return layers
105
+ return [sorted(layer) for layer in layers]
106
106
 
107
107
 
108
108
  def _calculate_layer_height(layer_nodes: List[Tuple[str, NodeDisplayData]], node_spacing: float) -> float:
@@ -1,12 +1,15 @@
1
1
  from copy import copy
2
+ import fnmatch
2
3
  from functools import cached_property
3
4
  import importlib
4
5
  import inspect
5
6
  import logging
7
+ import os
6
8
  from uuid import UUID
7
9
  from typing import Any, Dict, ForwardRef, Generic, Iterator, List, Optional, Tuple, Type, TypeVar, Union, cast, get_args
8
10
 
9
11
  from vellum.client import Vellum as VellumClient
12
+ from vellum.core.pydantic_utilities import UniversalBaseModel
10
13
  from vellum.workflows import BaseWorkflow
11
14
  from vellum.workflows.constants import undefined
12
15
  from vellum.workflows.descriptors.base import BaseDescriptor
@@ -18,7 +21,7 @@ from vellum.workflows.nodes.displayable.final_output_node.node import FinalOutpu
18
21
  from vellum.workflows.nodes.utils import get_unadorned_node, get_unadorned_port, get_wrapped_node
19
22
  from vellum.workflows.ports import Port
20
23
  from vellum.workflows.references import OutputReference, WorkflowInputReference
21
- from vellum.workflows.types.core import JsonArray, JsonObject
24
+ from vellum.workflows.types.core import Json, JsonArray, JsonObject
22
25
  from vellum.workflows.types.generics import WorkflowType
23
26
  from vellum.workflows.types.utils import get_original_base
24
27
  from vellum.workflows.utils.uuids import uuid4_from_hash
@@ -31,7 +34,7 @@ from vellum_ee.workflows.display.base import (
31
34
  WorkflowMetaDisplay,
32
35
  WorkflowOutputDisplay,
33
36
  )
34
- from vellum_ee.workflows.display.editor.types import NodeDisplayData
37
+ from vellum_ee.workflows.display.editor.types import NodeDisplayData, NodeDisplayPosition
35
38
  from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
36
39
  from vellum_ee.workflows.display.nodes.get_node_display_class import get_node_display_class
37
40
  from vellum_ee.workflows.display.nodes.types import NodeOutputDisplay, PortDisplay
@@ -48,6 +51,7 @@ from vellum_ee.workflows.display.types import (
48
51
  WorkflowInputsDisplays,
49
52
  WorkflowOutputDisplays,
50
53
  )
54
+ from vellum_ee.workflows.display.utils.auto_layout import auto_layout_nodes
51
55
  from vellum_ee.workflows.display.utils.expressions import serialize_value
52
56
  from vellum_ee.workflows.display.utils.registry import register_workflow_display_class
53
57
  from vellum_ee.workflows.display.utils.vellum import infer_vellum_variable_type
@@ -55,6 +59,19 @@ from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class imp
55
59
 
56
60
  logger = logging.getLogger(__name__)
57
61
 
62
+ IGNORE_PATTERNS = [
63
+ "*.pyc",
64
+ "__pycache__",
65
+ ".*",
66
+ "node_modules/*",
67
+ "*.log",
68
+ ]
69
+
70
+
71
+ class WorkflowSerializationResult(UniversalBaseModel):
72
+ exec_config: Dict[str, Any]
73
+ errors: List[str]
74
+
58
75
 
59
76
  class BaseWorkflowDisplay(Generic[WorkflowType]):
60
77
  # Used to specify the display data for a workflow.
@@ -80,6 +97,8 @@ class BaseWorkflowDisplay(Generic[WorkflowType]):
80
97
 
81
98
  _errors: List[Exception]
82
99
 
100
+ _serialized_files: List[str]
101
+
83
102
  _dry_run: bool
84
103
 
85
104
  def __init__(
@@ -96,10 +115,20 @@ class BaseWorkflowDisplay(Generic[WorkflowType]):
96
115
  if self._parent_display_context
97
116
  else create_vellum_client()
98
117
  )
99
- self._errors: List[Exception] = []
118
+ self._errors = []
119
+ self._serialized_files = []
100
120
  self._dry_run = dry_run
101
121
 
102
122
  def serialize(self) -> JsonObject:
123
+ self._serialized_files = [
124
+ "__init__.py",
125
+ "display/*",
126
+ "inputs.py",
127
+ "nodes/*",
128
+ "state.py",
129
+ "workflow.py",
130
+ ]
131
+
103
132
  input_variables: JsonArray = []
104
133
  for workflow_input_reference, workflow_input_display in self.display_context.workflow_input_displays.items():
105
134
  default = (
@@ -320,9 +349,30 @@ class BaseWorkflowDisplay(Generic[WorkflowType]):
320
349
 
321
350
  edges.extend(synthetic_output_edges)
322
351
 
352
+ nodes_list = list(serialized_nodes.values())
353
+ nodes_dict_list = [cast(Dict[str, Any], node) for node in nodes_list if isinstance(node, dict)]
354
+
355
+ all_nodes_at_zero = all(
356
+ (
357
+ isinstance(node.get("display_data"), dict)
358
+ and isinstance(node["display_data"].get("position"), dict)
359
+ and node["display_data"]["position"].get("x", 0) == 0.0
360
+ and node["display_data"]["position"].get("y", 0) == 0.0
361
+ )
362
+ for node in nodes_dict_list
363
+ )
364
+
365
+ should_apply_auto_layout = all_nodes_at_zero and len(nodes_dict_list) > 0
366
+
367
+ if should_apply_auto_layout:
368
+ try:
369
+ self._apply_auto_layout(nodes_dict_list, edges)
370
+ except Exception as e:
371
+ self.add_error(e)
372
+
323
373
  return {
324
374
  "workflow_raw_data": {
325
- "nodes": list(serialized_nodes.values()),
375
+ "nodes": cast(JsonArray, nodes_dict_list),
326
376
  "edges": edges,
327
377
  "display_data": self.display_context.workflow_display.display_data.dict(),
328
378
  "definition": {
@@ -336,6 +386,54 @@ class BaseWorkflowDisplay(Generic[WorkflowType]):
336
386
  "output_variables": output_variables,
337
387
  }
338
388
 
389
+ def _apply_auto_layout(self, nodes_dict_list: List[Dict[str, Any]], edges: List[Json]) -> None:
390
+ """Apply auto-layout to nodes that are all positioned at (0,0)."""
391
+ nodes_for_layout: List[Tuple[str, NodeDisplayData]] = []
392
+ for node_dict in nodes_dict_list:
393
+ if isinstance(node_dict.get("id"), str) and isinstance(node_dict.get("display_data"), dict):
394
+ display_data = node_dict["display_data"]
395
+ position = display_data.get("position", {})
396
+ if isinstance(position, dict):
397
+ nodes_for_layout.append(
398
+ (
399
+ str(node_dict["id"]),
400
+ NodeDisplayData(
401
+ position=NodeDisplayPosition(
402
+ x=float(position.get("x", 0.0)), y=float(position.get("y", 0.0))
403
+ ),
404
+ width=display_data.get("width"),
405
+ height=display_data.get("height"),
406
+ comment=display_data.get("comment"),
407
+ ),
408
+ )
409
+ )
410
+
411
+ edges_for_layout: List[Tuple[str, str, EdgeDisplay]] = []
412
+ for edge in edges:
413
+ if isinstance(edge, dict):
414
+ edge_dict = cast(Dict[str, Any], edge)
415
+ edge_source_node_id: Optional[Any] = edge_dict.get("source_node_id")
416
+ edge_target_node_id: Optional[Any] = edge_dict.get("target_node_id")
417
+ edge_id_raw: Optional[Any] = edge_dict.get("id")
418
+ if (
419
+ isinstance(edge_source_node_id, str)
420
+ and isinstance(edge_target_node_id, str)
421
+ and isinstance(edge_id_raw, str)
422
+ ):
423
+ edges_for_layout.append(
424
+ (edge_source_node_id, edge_target_node_id, EdgeDisplay(id=UUID(edge_id_raw)))
425
+ )
426
+
427
+ positioned_nodes = auto_layout_nodes(nodes_for_layout, edges_for_layout)
428
+
429
+ for node_id, positioned_data in positioned_nodes:
430
+ for node_dict in nodes_dict_list:
431
+ node_id_val = node_dict.get("id")
432
+ display_data = node_dict.get("display_data")
433
+ if isinstance(node_id_val, str) and node_id_val == node_id and isinstance(display_data, dict):
434
+ display_data_dict = cast(Dict[str, Any], display_data)
435
+ display_data_dict["position"] = positioned_data.position.dict()
436
+
339
437
  @cached_property
340
438
  def workflow_id(self) -> UUID:
341
439
  """Can be overridden as a class attribute to specify a custom workflow id."""
@@ -744,5 +842,82 @@ class BaseWorkflowDisplay(Generic[WorkflowType]):
744
842
  def _workflow(self) -> Type[WorkflowType]:
745
843
  return cast(Type[WorkflowType], self.__class__.infer_workflow_class())
746
844
 
845
+ @staticmethod
846
+ def serialize_module(
847
+ module: str,
848
+ *,
849
+ client: Optional[VellumClient] = None,
850
+ dry_run: bool = False,
851
+ ) -> WorkflowSerializationResult:
852
+ """
853
+ Load a workflow from a module and serialize it to JSON.
854
+
855
+ Args:
856
+ module: The module path to load the workflow from
857
+ client: Optional Vellum client to use for serialization
858
+ dry_run: Whether to run in dry-run mode
859
+
860
+ Returns:
861
+ WorkflowSerializationResult containing exec_config and errors
862
+ """
863
+ workflow = BaseWorkflow.load_from_module(module)
864
+ workflow_display = get_workflow_display(
865
+ workflow_class=workflow,
866
+ client=client,
867
+ dry_run=dry_run,
868
+ )
869
+
870
+ exec_config = workflow_display.serialize()
871
+ additional_files = workflow_display._gather_additional_module_files(module)
872
+
873
+ if additional_files:
874
+ exec_config["module_data"] = {"additional_files": cast(JsonObject, additional_files)}
875
+
876
+ return WorkflowSerializationResult(
877
+ exec_config=exec_config,
878
+ errors=[str(error) for error in workflow_display.errors],
879
+ )
880
+
881
+ def _gather_additional_module_files(self, module_path: str) -> Dict[str, str]:
882
+ workflow_module_path = f"{module_path}.workflow"
883
+ workflow_module = importlib.import_module(workflow_module_path)
884
+
885
+ workflow_file_path = workflow_module.__file__
886
+ if not workflow_file_path:
887
+ return {}
888
+
889
+ module_dir = os.path.dirname(workflow_file_path)
890
+ additional_files = {}
891
+
892
+ for root, _, filenames in os.walk(module_dir):
893
+ for filename in filenames:
894
+ file_path = os.path.join(root, filename)
895
+ relative_path = os.path.relpath(file_path, start=module_dir)
896
+
897
+ should_ignore = False
898
+ for ignore_pattern in IGNORE_PATTERNS:
899
+ if fnmatch.fnmatch(filename, ignore_pattern) or fnmatch.fnmatch(relative_path, ignore_pattern):
900
+ should_ignore = True
901
+ break
902
+
903
+ if not should_ignore:
904
+ for serialized_pattern in self._serialized_files:
905
+ if fnmatch.fnmatch(relative_path, serialized_pattern) or fnmatch.fnmatch(
906
+ filename, serialized_pattern
907
+ ):
908
+ should_ignore = True
909
+ break
910
+
911
+ if should_ignore:
912
+ continue
913
+
914
+ try:
915
+ with open(file_path, encoding="utf-8") as f:
916
+ additional_files[relative_path] = f.read()
917
+ except (UnicodeDecodeError, PermissionError):
918
+ continue
919
+
920
+ return additional_files
921
+
747
922
 
748
923
  register_workflow_display_class(workflow_class=BaseWorkflow, workflow_display_class=BaseWorkflowDisplay)
@@ -0,0 +1,47 @@
1
+ from vellum_ee.workflows.display.workflows.base_workflow_display import BaseWorkflowDisplay
2
+
3
+
4
+ def test_serialize_module_happy_path():
5
+ """Test that serialize_module works with a valid module path."""
6
+ module_path = "tests.workflows.trivial"
7
+
8
+ result = BaseWorkflowDisplay.serialize_module(module_path)
9
+
10
+ assert hasattr(result, "exec_config")
11
+ assert hasattr(result, "errors")
12
+ assert isinstance(result.exec_config, dict)
13
+ assert isinstance(result.errors, list)
14
+ assert "workflow_raw_data" in result.exec_config
15
+ assert "input_variables" in result.exec_config
16
+ assert "output_variables" in result.exec_config
17
+
18
+
19
+ def test_serialize_module_includes_additional_files():
20
+ """Test that serialize_module includes additional files from the module directory."""
21
+ module_path = "tests.workflows.module_with_additional_files"
22
+
23
+ result = BaseWorkflowDisplay.serialize_module(module_path)
24
+
25
+ assert hasattr(result, "exec_config")
26
+ assert hasattr(result, "errors")
27
+ assert isinstance(result.exec_config, dict)
28
+ assert isinstance(result.errors, list)
29
+
30
+ assert "module_data" in result.exec_config
31
+ assert "additional_files" in result.exec_config["module_data"]
32
+
33
+ additional_files = result.exec_config["module_data"]["additional_files"]
34
+ assert isinstance(additional_files, dict)
35
+
36
+ assert "helper.py" in additional_files
37
+ assert "data.txt" in additional_files
38
+ assert "utils/constants.py" in additional_files
39
+
40
+ assert "workflow.py" not in additional_files
41
+ assert "__init__.py" not in additional_files
42
+ assert "utils/__init__.py" not in additional_files
43
+ assert "nodes/test_node.py" not in additional_files
44
+
45
+ assert "def helper_function():" in additional_files["helper.py"]
46
+ assert "sample data file" in additional_files["data.txt"]
47
+ assert "CONSTANT_VALUE" in additional_files["utils/constants.py"]