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.
- vellum/__init__.py +8 -0
- vellum/client/core/client_wrapper.py +1 -1
- vellum/client/core/serialization.py +1 -0
- vellum/client/types/__init__.py +8 -0
- vellum/client/types/build_status_enum.py +5 -0
- vellum/client/types/container_image_build_config.py +20 -0
- vellum/client/types/container_image_read.py +4 -0
- vellum/client/types/execute_api_response.py +2 -2
- vellum/client/types/folder_entity.py +2 -0
- vellum/client/types/folder_entity_dataset.py +26 -0
- vellum/client/types/folder_entity_dataset_data.py +25 -0
- vellum/types/build_status_enum.py +3 -0
- vellum/types/container_image_build_config.py +3 -0
- vellum/types/folder_entity_dataset.py +3 -0
- vellum/types/folder_entity_dataset_data.py +3 -0
- vellum/workflows/nodes/core/retry_node/tests/test_node.py +1 -1
- vellum/workflows/nodes/displayable/api_node/node.py +2 -0
- vellum/workflows/nodes/displayable/api_node/tests/test_api_node.py +43 -0
- vellum/workflows/nodes/displayable/bases/api_node/node.py +6 -0
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +30 -4
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/tests/test_inline_prompt_node.py +43 -3
- vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +68 -58
- vellum/workflows/nodes/experimental/tool_calling_node/node.py +10 -10
- vellum/workflows/nodes/experimental/tool_calling_node/tests/__init__.py +0 -0
- vellum/workflows/nodes/experimental/tool_calling_node/tests/test_utils.py +49 -0
- vellum/workflows/nodes/experimental/tool_calling_node/utils.py +67 -6
- vellum/workflows/ports/utils.py +26 -6
- vellum/workflows/runner/runner.py +35 -3
- vellum/workflows/types/core.py +12 -0
- vellum/workflows/types/definition.py +6 -0
- vellum/workflows/utils/functions.py +9 -9
- vellum/workflows/utils/pydantic_schema.py +38 -0
- vellum/workflows/utils/tests/test_functions.py +11 -11
- {vellum_ai-0.14.72.dist-info → vellum_ai-0.14.73.dist-info}/METADATA +1 -1
- {vellum_ai-0.14.72.dist-info → vellum_ai-0.14.73.dist-info}/RECORD +66 -54
- vellum_cli/push.py +6 -8
- vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py +8 -1
- vellum_ee/workflows/display/tests/test_base_workflow_display.py +1 -1
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +1 -1
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attributes_serialization.py +1 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_api_node_serialization.py +5 -5
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +12 -12
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +10 -10
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_default_state_serialization.py +3 -3
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py +3 -3
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_generic_node_serialization.py +20 -9
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_guardrail_node_serialization.py +3 -3
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_prompt_node_serialization.py +3 -3
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +8 -8
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +6 -6
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +3 -3
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_prompt_deployment_serialization.py +8 -8
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_search_node_serialization.py +3 -3
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_subworkflow_deployment_serialization.py +4 -4
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_node_serialization.py +3 -3
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py +2 -2
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_serialization.py +5 -5
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py +1 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py +1 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +2 -2
- vellum_ee/workflows/display/utils/auto_layout.py +1 -1
- vellum_ee/workflows/display/workflows/base_workflow_display.py +179 -4
- vellum_ee/workflows/tests/test_serialize_module.py +47 -0
- {vellum_ai-0.14.72.dist-info → vellum_ai-0.14.73.dist-info}/LICENSE +0 -0
- {vellum_ai-0.14.72.dist-info → vellum_ai-0.14.73.dist-info}/WHEEL +0 -0
- {vellum_ai-0.14.72.dist-info → vellum_ai-0.14.73.dist-info}/entry_points.txt +0 -0
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_node_serialization.py
CHANGED
@@ -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":
|
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":
|
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":
|
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"],
|
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py
CHANGED
@@ -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":
|
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":
|
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":
|
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":
|
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":
|
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":
|
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":
|
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":
|
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": {
|
vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py
CHANGED
@@ -61,7 +61,7 @@ def test_serialize_workflow__missing_final_output_node():
|
|
61
61
|
},
|
62
62
|
}
|
63
63
|
],
|
64
|
-
"display_data": {"position": {"x":
|
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":
|
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
|
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":
|
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"]
|
File without changes
|
File without changes
|
File without changes
|