vellum-ai 0.12.15__py3-none-any.whl → 0.12.16__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 (39) hide show
  1. vellum/client/core/client_wrapper.py +1 -1
  2. vellum/prompts/blocks/compilation.py +43 -0
  3. vellum/workflows/nodes/core/inline_subworkflow_node/node.py +10 -2
  4. vellum/workflows/nodes/core/inline_subworkflow_node/tests/test_node.py +16 -0
  5. {vellum_ai-0.12.15.dist-info → vellum_ai-0.12.16.dist-info}/METADATA +10 -8
  6. {vellum_ai-0.12.15.dist-info → vellum_ai-0.12.16.dist-info}/RECORD +39 -36
  7. vellum_cli/__init__.py +14 -0
  8. vellum_cli/push.py +26 -4
  9. vellum_cli/tests/test_push.py +63 -0
  10. vellum_ee/workflows/display/nodes/vellum/api_node.py +3 -3
  11. vellum_ee/workflows/display/nodes/vellum/base_node.py +17 -0
  12. vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +2 -2
  13. vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +2 -2
  14. vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +20 -6
  15. vellum_ee/workflows/display/nodes/vellum/map_node.py +1 -0
  16. vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py +2 -2
  17. vellum_ee/workflows/display/nodes/vellum/search_node.py +4 -2
  18. vellum_ee/workflows/display/nodes/vellum/templating_node.py +1 -1
  19. vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py +3 -3
  20. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/__init__.py +0 -0
  21. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/conftest.py +28 -0
  22. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_trigger_serialization.py +123 -0
  23. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +2 -11
  24. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py +2 -14
  25. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_generic_node_serialization.py +1 -7
  26. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +17 -1
  27. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +17 -1
  28. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +1 -9
  29. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py +1 -1
  30. vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +1 -4
  31. vellum_ee/workflows/display/types.py +5 -1
  32. vellum_ee/workflows/display/utils/vellum.py +3 -3
  33. vellum_ee/workflows/display/vellum.py +4 -0
  34. vellum_ee/workflows/display/workflows/base_workflow_display.py +44 -16
  35. vellum_ee/workflows/display/workflows/get_vellum_workflow_display_class.py +3 -0
  36. vellum_ee/workflows/display/workflows/vellum_workflow_display.py +7 -8
  37. {vellum_ai-0.12.15.dist-info → vellum_ai-0.12.16.dist-info}/LICENSE +0 -0
  38. {vellum_ai-0.12.15.dist-info → vellum_ai-0.12.16.dist-info}/WHEEL +0 -0
  39. {vellum_ai-0.12.15.dist-info → vellum_ai-0.12.16.dist-info}/entry_points.txt +0 -0
@@ -38,8 +38,8 @@ class BasePromptDeploymentNodeDisplay(
38
38
  for variable_name, variable_value in prompt_inputs.items()
39
39
  ]
40
40
 
41
- _, output_display = display_context.node_output_displays[cast(OutputReference, node.Outputs.text)]
42
- _, array_display = display_context.node_output_displays[cast(OutputReference, node.Outputs.results)]
41
+ _, output_display = display_context.global_node_output_displays[cast(OutputReference, node.Outputs.text)]
42
+ _, array_display = display_context.global_node_output_displays[cast(OutputReference, node.Outputs.results)]
43
43
 
44
44
  # TODO: Pass through the name instead of retrieving the ID
45
45
  # https://app.shortcut.com/vellum/story/4702
@@ -39,8 +39,10 @@ class BaseSearchNodeDisplay(BaseNodeVellumDisplay[_SearchNodeType], Generic[_Sea
39
39
  node_id = self.node_id
40
40
  node_inputs = self._generate_search_node_inputs(node_id, node, display_context)
41
41
 
42
- _, results_output_display = display_context.node_output_displays[cast(OutputReference, node.Outputs.results)]
43
- _, text_output_display = display_context.node_output_displays[cast(OutputReference, node.Outputs.text)]
42
+ _, results_output_display = display_context.global_node_output_displays[
43
+ cast(OutputReference, node.Outputs.results)
44
+ ]
45
+ _, text_output_display = display_context.global_node_output_displays[cast(OutputReference, node.Outputs.text)]
44
46
 
45
47
  return {
46
48
  "id": str(node_id),
@@ -56,7 +56,7 @@ class BaseTemplatingNodeDisplay(BaseNodeVellumDisplay[_TemplatingNodeType], Gene
56
56
  # Misc type ignore is due to `node.Outputs` being generic
57
57
  # https://app.shortcut.com/vellum/story/4784
58
58
  output_descriptor = node.Outputs.result # type: ignore [misc]
59
- _, output_display = display_context.node_output_displays[output_descriptor]
59
+ _, output_display = display_context.global_node_output_displays[output_descriptor]
60
60
  inferred_output_type = primitive_type_to_vellum_variable_type(output_descriptor)
61
61
 
62
62
  return {
@@ -91,18 +91,18 @@ def test_create_node_input_value_pointer_rules(
91
91
  entrypoint_node_source_handle_id=uuid4(),
92
92
  entrypoint_node_display=NodeDisplayData(),
93
93
  ),
94
- workflow_input_displays={
94
+ global_workflow_input_displays={
95
95
  cast(WorkflowInputReference, Inputs.example_workflow_input): WorkflowInputsVellumDisplayOverrides(
96
96
  id=UUID("a154c29d-fac0-4cd0-ba88-bc52034f5470"),
97
97
  ),
98
98
  },
99
- node_output_displays={
99
+ global_node_output_displays={
100
100
  cast(OutputReference, MyNodeA.Outputs.output): (
101
101
  MyNodeA,
102
102
  NodeOutputDisplay(id=UUID("4b16a629-11a1-4b3f-a965-a57b872d13b8"), name="output"),
103
103
  ),
104
104
  },
105
- node_displays={
105
+ global_node_displays={
106
106
  MyNodeA: MyNodeADisplay(),
107
107
  },
108
108
  ),
@@ -0,0 +1,28 @@
1
+ import pytest
2
+ from uuid import uuid4
3
+
4
+ from vellum_ee.workflows.display.nodes.get_node_display_class import get_node_display_class
5
+ from vellum_ee.workflows.display.nodes.vellum.base_node import BaseNodeDisplay
6
+ from vellum_ee.workflows.display.types import WorkflowDisplayContext
7
+ from vellum_ee.workflows.display.vellum import NodeDisplayData, WorkflowMetaVellumDisplay
8
+ from vellum_ee.workflows.display.workflows.vellum_workflow_display import VellumWorkflowDisplay
9
+
10
+
11
+ @pytest.fixture()
12
+ def serialize_node():
13
+ def _serialize_node(node_class) -> dict:
14
+ node_display_class = get_node_display_class(BaseNodeDisplay, node_class)
15
+ node_display = node_display_class()
16
+
17
+ context: WorkflowDisplayContext = WorkflowDisplayContext(
18
+ workflow_display_class=VellumWorkflowDisplay,
19
+ workflow_display=WorkflowMetaVellumDisplay(
20
+ entrypoint_node_id=uuid4(),
21
+ entrypoint_node_source_handle_id=uuid4(),
22
+ entrypoint_node_display=NodeDisplayData(),
23
+ ),
24
+ node_displays={node_class: node_display},
25
+ )
26
+ return node_display.serialize(context)
27
+
28
+ return _serialize_node
@@ -0,0 +1,123 @@
1
+ from deepdiff import DeepDiff
2
+
3
+ from vellum.workflows.inputs.base import BaseInputs
4
+ from vellum.workflows.nodes.bases.base import BaseNode
5
+ from vellum.workflows.types.core import MergeBehavior
6
+
7
+
8
+ class Inputs(BaseInputs):
9
+ input: str
10
+
11
+
12
+ class BasicGenericNode(BaseNode):
13
+ class Outputs(BaseNode.Outputs):
14
+ output = Inputs.input
15
+
16
+
17
+ class AwaitAnyGenericNode(BaseNode):
18
+ class Outputs(BaseNode.Outputs):
19
+ output = Inputs.input
20
+
21
+ class Trigger(BaseNode.Trigger):
22
+ merge_behavior = MergeBehavior.AWAIT_ANY
23
+
24
+
25
+ class AwaitAllGenericNode(BaseNode):
26
+ class Outputs(BaseNode.Outputs):
27
+ output = Inputs.input
28
+
29
+ class Trigger(BaseNode.Trigger):
30
+ merge_behavior = MergeBehavior.AWAIT_ALL
31
+
32
+
33
+ def test_serialize_node__basic(serialize_node):
34
+ serialized_node = serialize_node(BasicGenericNode)
35
+ assert not DeepDiff(
36
+ {
37
+ "id": "c2ed23f7-f6cb-4a56-a91c-2e5f9d8fda7f",
38
+ "label": "BasicGenericNode",
39
+ "type": "GENERIC",
40
+ "display_data": {"position": {"x": 0.0, "y": 0.0}},
41
+ "definition": {
42
+ "name": "BasicGenericNode",
43
+ "module": [
44
+ "vellum_ee",
45
+ "workflows",
46
+ "display",
47
+ "tests",
48
+ "workflow_serialization",
49
+ "generic_nodes",
50
+ "test_trigger_serialization",
51
+ ],
52
+ "bases": [{"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]}],
53
+ },
54
+ "trigger": {"id": "9d3a1b3d-4a38-4f2e-bbf1-dd8be152bce8", "merge_behavior": "AWAIT_ANY"},
55
+ "ports": [],
56
+ "adornments": None,
57
+ "attributes": [],
58
+ },
59
+ serialized_node,
60
+ ignore_order=True,
61
+ )
62
+
63
+
64
+ def test_serialize_node__await_any(serialize_node):
65
+ serialized_node = serialize_node(AwaitAnyGenericNode)
66
+ assert not DeepDiff(
67
+ {
68
+ "id": "0ba67f76-aaff-4bd4-a20f-73a32ef5810d",
69
+ "label": "AwaitAnyGenericNode",
70
+ "type": "GENERIC",
71
+ "display_data": {"position": {"x": 0.0, "y": 0.0}},
72
+ "definition": {
73
+ "name": "AwaitAnyGenericNode",
74
+ "module": [
75
+ "vellum_ee",
76
+ "workflows",
77
+ "display",
78
+ "tests",
79
+ "workflow_serialization",
80
+ "generic_nodes",
81
+ "test_trigger_serialization",
82
+ ],
83
+ "bases": [{"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]}],
84
+ },
85
+ "trigger": {"id": "ffa72187-9a18-453f-ae55-b77aad332630", "merge_behavior": "AWAIT_ANY"},
86
+ "ports": [],
87
+ "adornments": None,
88
+ "attributes": [],
89
+ },
90
+ serialized_node,
91
+ ignore_order=True,
92
+ )
93
+
94
+
95
+ def test_serialize_node__await_all(serialize_node):
96
+ serialized_node = serialize_node(AwaitAllGenericNode)
97
+ assert not DeepDiff(
98
+ {
99
+ "id": "09d06cd3-06ea-40cc-afd8-17ad88542271",
100
+ "label": "AwaitAllGenericNode",
101
+ "type": "GENERIC",
102
+ "display_data": {"position": {"x": 0.0, "y": 0.0}},
103
+ "definition": {
104
+ "name": "AwaitAllGenericNode",
105
+ "module": [
106
+ "vellum_ee",
107
+ "workflows",
108
+ "display",
109
+ "tests",
110
+ "workflow_serialization",
111
+ "generic_nodes",
112
+ "test_trigger_serialization",
113
+ ],
114
+ "bases": [{"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]}],
115
+ },
116
+ "trigger": {"id": "62074276-c817-476d-b59d-da523ae3f218", "merge_behavior": "AWAIT_ALL"},
117
+ "ports": [],
118
+ "adornments": None,
119
+ "attributes": [],
120
+ },
121
+ serialized_node,
122
+ ignore_order=True,
123
+ )
@@ -448,17 +448,8 @@ def test_serialize_workflow():
448
448
  ignore_order=True,
449
449
  )
450
450
 
451
- assert not DeepDiff(
452
- [
453
- {"id": "148c61bd-e8b0-4d4b-8734-b043a72b90ed", "type": "GENERIC"},
454
- {"id": "ed7caf01-9ae7-47a3-b15a-16697abaf486", "type": "GENERIC"},
455
- {"id": "0d959311-c836-4641-a867-58f63df9dfea", "type": "GENERIC"},
456
- {"id": "8df781b1-ff28-48a5-98a2-d7d796b932b0", "type": "GENERIC"},
457
- {"id": "68c02b7c-5077-4087-803d-841474a8081f", "type": "GENERIC"},
458
- ],
459
- workflow_raw_data["nodes"][2:7],
460
- ignore_order=True,
461
- )
451
+ passthrough_nodes = [node for node in workflow_raw_data["nodes"] if node["type"] == "GENERIC"]
452
+ assert len(passthrough_nodes) == 5
462
453
 
463
454
  assert not DeepDiff(
464
455
  [
@@ -122,20 +122,8 @@ def test_serialize_workflow():
122
122
  ignore_order=True,
123
123
  )
124
124
 
125
- mocked_base_nodes = [
126
- node
127
- for i, node in enumerate(workflow_raw_data["nodes"])
128
- if i != error_index and i != 0 and i != len(workflow_raw_data["nodes"]) - 1
129
- ]
130
-
131
- assert not DeepDiff(
132
- [
133
- {"id": "1381c078-efa2-4255-89a1-7b4cb742c7fc", "type": "GENERIC"},
134
- {"id": "1eee9b4e-531f-45f2-a4b9-42207fac2c33", "type": "GENERIC"},
135
- ],
136
- mocked_base_nodes,
137
- ignore_order=True,
138
- )
125
+ passthrough_nodes = [node for node in workflow_raw_data["nodes"] if node["type"] == "GENERIC"]
126
+ assert len(passthrough_nodes) == 2
139
127
 
140
128
  terminal_node = workflow_raw_data["nodes"][-1]
141
129
  assert not DeepDiff(
@@ -79,13 +79,7 @@ def test_serialize_workflow(vellum_client):
79
79
  }
80
80
 
81
81
  api_node = workflow_raw_data["nodes"][1]
82
- assert not DeepDiff(
83
- {
84
- "id": "c2ed23f7-f6cb-4a56-a91c-2e5f9d8fda7f",
85
- "type": "GENERIC",
86
- },
87
- api_node,
88
- )
82
+ assert api_node["id"] == "c2ed23f7-f6cb-4a56-a91c-2e5f9d8fda7f"
89
83
 
90
84
  final_output_node = workflow_raw_data["nodes"][2]
91
85
  assert not DeepDiff(
@@ -148,7 +148,23 @@ def test_serialize_workflow():
148
148
  "bases": [],
149
149
  },
150
150
  },
151
- {"id": "1381c078-efa2-4255-89a1-7b4cb742c7fc", "type": "GENERIC"},
151
+ {
152
+ "id": "1381c078-efa2-4255-89a1-7b4cb742c7fc",
153
+ "label": "StartNode",
154
+ "type": "GENERIC",
155
+ "display_data": {"position": {"x": 0.0, "y": 0.0}},
156
+ "definition": {
157
+ "name": "StartNode",
158
+ "module": ["tests", "workflows", "basic_inline_subworkflow", "workflow"],
159
+ "bases": [
160
+ {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]}
161
+ ],
162
+ },
163
+ "trigger": {"id": "a95a34f2-e894-4fb6-a2c9-15d12c1e3135", "merge_behavior": "AWAIT_ANY"},
164
+ "ports": [],
165
+ "adornments": None,
166
+ "attributes": [],
167
+ },
152
168
  {
153
169
  "id": "a773c3a5-78cb-4250-8d29-7282e8a579d3",
154
170
  "type": "TERMINAL",
@@ -133,7 +133,23 @@ def test_serialize_workflow():
133
133
  "bases": [],
134
134
  },
135
135
  },
136
- {"id": "baf6d316-dc75-41e8-96c0-015aede96309", "type": "GENERIC"},
136
+ {
137
+ "id": "baf6d316-dc75-41e8-96c0-015aede96309",
138
+ "label": "Iteration",
139
+ "type": "GENERIC",
140
+ "display_data": {"position": {"x": 0.0, "y": 0.0}},
141
+ "definition": {
142
+ "name": "Iteration",
143
+ "module": ["tests", "workflows", "basic_map_node", "workflow"],
144
+ "bases": [
145
+ {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]}
146
+ ],
147
+ },
148
+ "trigger": {"id": "01324747-9bc0-4ecd-a8ab-40dca5a94e2e", "merge_behavior": "AWAIT_ANY"},
149
+ "ports": [],
150
+ "adornments": None,
151
+ "attributes": [],
152
+ },
137
153
  {
138
154
  "id": "6f4883b2-70b1-4e1c-ae15-7d0f5aec810b",
139
155
  "type": "TERMINAL",
@@ -69,15 +69,7 @@ def test_serialize_workflow__await_all():
69
69
  }
70
70
 
71
71
  passthrough_nodes = [node for node in workflow_raw_data["nodes"] if node["type"] == "GENERIC"]
72
- assert not DeepDiff(
73
- [
74
- {"id": "127ef456-91bc-43c6-bd8b-1772db5e3cb5", "type": "GENERIC"},
75
- {"id": "59243c65-053f-4ea6-9157-3f3edb1477bf", "type": "GENERIC"},
76
- {"id": "634f0202-9ea9-4c62-b152-1a58c595cffb", "type": "GENERIC"},
77
- ],
78
- passthrough_nodes,
79
- ignore_order=True,
80
- )
72
+ assert len(passthrough_nodes) == 3
81
73
 
82
74
  merge_node = next(node for node in workflow_raw_data["nodes"] if node["type"] == "MERGE")
83
75
  assert not DeepDiff(
@@ -76,4 +76,4 @@ def test_serialize_workflow():
76
76
  }
77
77
 
78
78
  try_node = workflow_raw_data["nodes"][1]
79
- assert try_node == {"id": "1381c078-efa2-4255-89a1-7b4cb742c7fc", "type": "GENERIC"}
79
+ assert try_node["id"] == "1381c078-efa2-4255-89a1-7b4cb742c7fc"
@@ -91,10 +91,7 @@ def test_serialize_workflow__missing_final_output_node():
91
91
  }
92
92
 
93
93
  passthrough_node = next(node for node in workflow_raw_data["nodes"] if node["type"] == "GENERIC")
94
- assert passthrough_node == {
95
- "id": "32d88cab-e9fa-4a56-9bc2-fb6e1fd0897f",
96
- "type": "GENERIC",
97
- }
94
+ assert passthrough_node["id"] == "32d88cab-e9fa-4a56-9bc2-fb6e1fd0897f"
98
95
 
99
96
  final_output_nodes = [node for node in workflow_raw_data["nodes"] if node["type"] == "TERMINAL"]
100
97
  assert not DeepDiff(
@@ -36,8 +36,12 @@ class WorkflowDisplayContext(
36
36
  workflow_display_class: Type["BaseWorkflowDisplay"]
37
37
  workflow_display: WorkflowMetaDisplayType
38
38
  workflow_input_displays: Dict[WorkflowInputReference, WorkflowInputsDisplayType] = field(default_factory=dict)
39
+ global_workflow_input_displays: Dict[WorkflowInputReference, WorkflowInputsDisplayType] = field(
40
+ default_factory=dict
41
+ )
39
42
  node_displays: Dict[Type[BaseNode], "NodeDisplayType"] = field(default_factory=dict)
40
- node_output_displays: Dict[OutputReference, Tuple[Type[BaseNode], "NodeOutputDisplay"]] = field(
43
+ global_node_displays: Dict[Type[BaseNode], NodeDisplayType] = field(default_factory=dict)
44
+ global_node_output_displays: Dict[OutputReference, Tuple[Type[BaseNode], "NodeOutputDisplay"]] = field(
41
45
  default_factory=dict
42
46
  )
43
47
  entrypoint_displays: Dict[Type[BaseNode], EntrypointDisplayType] = field(default_factory=dict)
@@ -58,13 +58,13 @@ def create_node_input_value_pointer_rule(
58
58
  value: Any, display_context: WorkflowDisplayContext
59
59
  ) -> NodeInputValuePointerRule:
60
60
  if isinstance(value, OutputReference):
61
- upstream_node, output_display = display_context.node_output_displays[value]
62
- upstream_node_display = display_context.node_displays[upstream_node]
61
+ upstream_node, output_display = display_context.global_node_output_displays[value]
62
+ upstream_node_display = display_context.global_node_displays[upstream_node]
63
63
  return NodeOutputPointer(
64
64
  data=NodeOutputData(node_id=str(upstream_node_display.node_id), output_id=str(output_display.id)),
65
65
  )
66
66
  if isinstance(value, WorkflowInputReference):
67
- workflow_input_display = display_context.workflow_input_displays[value]
67
+ workflow_input_display = display_context.global_workflow_input_displays[value]
68
68
  return InputVariablePointer(data=InputVariableData(input_variable_id=str(workflow_input_display.id)))
69
69
  if isinstance(value, VellumSecretReference):
70
70
  # TODO: Pass through the name instead of retrieving the ID
@@ -38,6 +38,10 @@ class NodeDisplayData(UniversalBaseModel):
38
38
  comment: Optional[NodeDisplayComment] = None
39
39
 
40
40
 
41
+ class GenericNodeDisplayData(UniversalBaseModel):
42
+ position: NodeDisplayPosition = Field(default_factory=NodeDisplayPosition)
43
+
44
+
41
45
  class CodeResourceDefinition(UniversalBaseModel):
42
46
  name: str
43
47
  module: List[str]
@@ -1,9 +1,9 @@
1
1
  from abc import abstractmethod
2
- from copy import deepcopy
2
+ from copy import copy
3
3
  from functools import cached_property
4
4
  import logging
5
5
  from uuid import UUID
6
- from typing import Any, Dict, Generic, Optional, Tuple, Type, get_args
6
+ from typing import Any, Dict, Generic, Iterator, List, Optional, Tuple, Type, get_args
7
7
 
8
8
  from vellum.workflows.descriptors.base import BaseDescriptor
9
9
  from vellum.workflows.edges import Edge
@@ -71,6 +71,10 @@ class BaseWorkflowDisplay(
71
71
  # Used to store the mapping between workflows and their display classes
72
72
  _workflow_display_registry: Dict[Type[WorkflowType], Type["BaseWorkflowDisplay"]] = {}
73
73
 
74
+ _errors: List[Exception]
75
+
76
+ _dry_run: bool
77
+
74
78
  def __init__(
75
79
  self,
76
80
  workflow: Type[WorkflowType],
@@ -85,12 +89,15 @@ class BaseWorkflowDisplay(
85
89
  EdgeDisplayType,
86
90
  ]
87
91
  ] = None,
92
+ dry_run: bool = False,
88
93
  ):
89
94
  self._workflow = workflow
90
95
  self._parent_display_context = parent_display_context
96
+ self._errors: List[Exception] = []
97
+ self._dry_run = dry_run
91
98
 
92
99
  @abstractmethod
93
- def serialize(self, raise_errors: bool = True) -> JsonObject:
100
+ def serialize(self) -> JsonObject:
94
101
  pass
95
102
 
96
103
  @classmethod
@@ -110,7 +117,18 @@ class BaseWorkflowDisplay(
110
117
  def node_display_base_class(self) -> Type[NodeDisplayType]:
111
118
  pass
112
119
 
113
- def _enrich_node_output_displays(
120
+ def add_error(self, error: Exception) -> None:
121
+ if self._dry_run:
122
+ self._errors.append(error)
123
+ return
124
+
125
+ raise error
126
+
127
+ @property
128
+ def errors(self) -> Iterator[Exception]:
129
+ return iter(self._errors)
130
+
131
+ def _enrich_global_node_output_displays(
114
132
  self,
115
133
  node: Type[BaseNode],
116
134
  node_display: NodeDisplayType,
@@ -126,7 +144,7 @@ class BaseWorkflowDisplay(
126
144
  inner_node = get_wrapped_node(node)
127
145
  if inner_node._is_wrapped_node:
128
146
  inner_node_display = self._get_node_display(inner_node)
129
- self._enrich_node_output_displays(inner_node, inner_node_display, node_output_displays)
147
+ self._enrich_global_node_output_displays(inner_node, inner_node_display, node_output_displays)
130
148
 
131
149
  # TODO: Make sure this output ID matches the workflow output ID of the subworkflow node's workflow
132
150
  # https://app.shortcut.com/vellum/story/5660/fix-output-id-in-subworkflow-nodes
@@ -174,19 +192,26 @@ class BaseWorkflowDisplay(
174
192
  ]:
175
193
  workflow_display = self._generate_workflow_meta_display()
176
194
 
177
- # If we're dealing with a nested workflow, then it should have access to the outputs of all nodes
178
- node_output_displays: Dict[OutputReference, Tuple[Type[BaseNode], NodeOutputDisplay]] = (
179
- deepcopy(self._parent_display_context.node_output_displays) if self._parent_display_context else {}
195
+ global_node_output_displays: Dict[OutputReference, Tuple[Type[BaseNode], NodeOutputDisplay]] = (
196
+ copy(self._parent_display_context.global_node_output_displays) if self._parent_display_context else {}
180
197
  )
181
198
 
182
199
  node_displays: Dict[Type[BaseNode], NodeDisplayType] = {}
200
+
201
+ global_node_displays: Dict[Type[BaseNode], NodeDisplayType] = (
202
+ copy(self._parent_display_context.global_node_displays) if self._parent_display_context else {}
203
+ )
204
+
183
205
  port_displays: Dict[Port, PortDisplay] = {}
184
206
 
185
207
  # TODO: We should still serialize nodes that are in the workflow's directory but aren't used in the graph.
186
208
  # https://app.shortcut.com/vellum/story/5394
187
209
  for node in self._workflow.get_nodes():
210
+ if node in global_node_displays:
211
+ continue
188
212
  node_display = self._get_node_display(node)
189
213
  node_displays[node] = node_display
214
+ global_node_displays[node] = node_display
190
215
 
191
216
  # Nodes wrapped in a decorator need to be in our node display dictionary for later retrieval
192
217
  if has_wrapped_node(node):
@@ -195,22 +220,23 @@ class BaseWorkflowDisplay(
195
220
 
196
221
  if inner_node._is_wrapped_node:
197
222
  node_displays[inner_node] = inner_node_display
223
+ global_node_displays[inner_node] = inner_node_display
198
224
 
199
- self._enrich_node_output_displays(node, node_display, node_output_displays)
225
+ self._enrich_global_node_output_displays(node, node_display, global_node_output_displays)
200
226
  self._enrich_node_port_displays(node, node_display, port_displays)
201
227
 
228
+ workflow_input_displays: Dict[WorkflowInputReference, WorkflowInputsDisplayType] = {}
202
229
  # If we're dealing with a nested workflow, then it should have access to the inputs of its parents.
203
- workflow_input_displays: Dict[WorkflowInputReference, WorkflowInputsDisplayType] = (
204
- deepcopy(self._parent_display_context.workflow_input_displays) if self._parent_display_context else {}
230
+ global_workflow_input_displays = (
231
+ copy(self._parent_display_context.workflow_input_displays) if self._parent_display_context else {}
205
232
  )
206
233
  for workflow_input in self._workflow.get_inputs_class():
207
- if workflow_input in workflow_input_displays:
208
- continue
209
-
210
234
  workflow_input_display_overrides = self.inputs_display.get(workflow_input)
211
- workflow_input_displays[workflow_input] = self._generate_workflow_input_display(
235
+ input_display = self._generate_workflow_input_display(
212
236
  workflow_input, overrides=workflow_input_display_overrides
213
237
  )
238
+ workflow_input_displays[workflow_input] = input_display
239
+ global_workflow_input_displays[workflow_input] = input_display
214
240
 
215
241
  entrypoint_displays: Dict[Type[BaseNode], EntrypointDisplayType] = {}
216
242
  for entrypoint in self._workflow.get_entrypoints():
@@ -253,8 +279,10 @@ class BaseWorkflowDisplay(
253
279
  return WorkflowDisplayContext(
254
280
  workflow_display=workflow_display,
255
281
  workflow_input_displays=workflow_input_displays,
282
+ global_workflow_input_displays=global_workflow_input_displays,
256
283
  node_displays=node_displays,
257
- node_output_displays=node_output_displays,
284
+ global_node_output_displays=global_node_output_displays,
285
+ global_node_displays=global_node_displays,
258
286
  entrypoint_displays=entrypoint_displays,
259
287
  workflow_output_displays=workflow_output_displays,
260
288
  edge_displays=edge_displays,
@@ -10,6 +10,7 @@ def get_workflow_display(
10
10
  workflow_class: Type[WorkflowType],
11
11
  root_workflow_class: Optional[Type[WorkflowType]] = None,
12
12
  parent_display_context: Optional[WorkflowDisplayContext] = None,
13
+ dry_run: bool = False,
13
14
  ) -> WorkflowDisplayType:
14
15
  try:
15
16
  workflow_display_class = base_display_class.get_from_workflow_display_registry(workflow_class)
@@ -20,6 +21,7 @@ def get_workflow_display(
20
21
  workflow_class=workflow_class.__bases__[0],
21
22
  root_workflow_class=workflow_class if root_workflow_class is None else root_workflow_class,
22
23
  parent_display_context=parent_display_context,
24
+ dry_run=dry_run,
23
25
  )
24
26
  except IndexError:
25
27
  return base_display_class(workflow_class)
@@ -27,4 +29,5 @@ def get_workflow_display(
27
29
  return workflow_display_class( # type: ignore[return-value]
28
30
  workflow_class,
29
31
  parent_display_context=parent_display_context,
32
+ dry_run=dry_run,
30
33
  )
@@ -53,7 +53,7 @@ class VellumWorkflowDisplay(
53
53
  ):
54
54
  node_display_base_class = BaseNodeVellumDisplay
55
55
 
56
- def serialize(self, raise_errors: bool = True) -> JsonObject:
56
+ def serialize(self) -> JsonObject:
57
57
  input_variables: JsonArray = []
58
58
  for workflow_input, workflow_input_display in self.display_context.workflow_input_displays.items():
59
59
  default = primitive_to_vellum_value(workflow_input.instance) if workflow_input.instance else None
@@ -106,12 +106,9 @@ class VellumWorkflowDisplay(
106
106
 
107
107
  try:
108
108
  serialized_node = node_display.serialize(self.display_context)
109
- except NotImplementedError:
110
- logger.warning("Unable to serialize node", extra={"node": node.__name__})
111
- if raise_errors:
112
- raise
113
- else:
114
- continue
109
+ except NotImplementedError as e:
110
+ self.add_error(e)
111
+ continue
115
112
 
116
113
  nodes.append(serialized_node)
117
114
 
@@ -205,7 +202,9 @@ class VellumWorkflowDisplay(
205
202
  # If there are terminal nodes with no workflow output reference,
206
203
  # raise a serialization error
207
204
  if len(unreferenced_final_output_node_outputs) > 0:
208
- raise ValueError("Unable to serialize terminal nodes that are not referenced by workflow outputs.")
205
+ self.add_error(
206
+ ValueError("Unable to serialize terminal nodes that are not referenced by workflow outputs.")
207
+ )
209
208
 
210
209
  # Add an edge for each edge in the workflow
211
210
  all_edge_displays: List[EdgeVellumDisplay] = [