vellum-ai 0.12.14__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 (59) hide show
  1. vellum/__init__.py +6 -0
  2. vellum/client/__init__.py +2 -6
  3. vellum/client/core/client_wrapper.py +1 -1
  4. vellum/client/environment.py +3 -3
  5. vellum/client/resources/ad_hoc/client.py +2 -6
  6. vellum/client/resources/container_images/client.py +0 -8
  7. vellum/client/resources/metric_definitions/client.py +2 -6
  8. vellum/client/resources/workflows/client.py +8 -8
  9. vellum/client/types/__init__.py +6 -0
  10. vellum/client/types/audio_prompt_block.py +29 -0
  11. vellum/client/types/function_call_prompt_block.py +30 -0
  12. vellum/client/types/image_prompt_block.py +29 -0
  13. vellum/client/types/prompt_block.py +12 -1
  14. vellum/client/types/workflow_push_response.py +1 -0
  15. vellum/prompts/blocks/compilation.py +43 -0
  16. vellum/types/audio_prompt_block.py +3 -0
  17. vellum/types/function_call_prompt_block.py +3 -0
  18. vellum/types/image_prompt_block.py +3 -0
  19. vellum/workflows/nodes/core/inline_subworkflow_node/node.py +10 -2
  20. vellum/workflows/nodes/core/inline_subworkflow_node/tests/test_node.py +16 -0
  21. {vellum_ai-0.12.14.dist-info → vellum_ai-0.12.16.dist-info}/METADATA +11 -9
  22. {vellum_ai-0.12.14.dist-info → vellum_ai-0.12.16.dist-info}/RECORD +59 -48
  23. vellum_cli/__init__.py +14 -0
  24. vellum_cli/config.py +4 -0
  25. vellum_cli/pull.py +20 -5
  26. vellum_cli/push.py +33 -4
  27. vellum_cli/tests/test_pull.py +19 -1
  28. vellum_cli/tests/test_push.py +63 -0
  29. vellum_ee/workflows/display/nodes/vellum/__init__.py +2 -0
  30. vellum_ee/workflows/display/nodes/vellum/api_node.py +3 -3
  31. vellum_ee/workflows/display/nodes/vellum/base_node.py +35 -0
  32. vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +2 -2
  33. vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +2 -2
  34. vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +20 -6
  35. vellum_ee/workflows/display/nodes/vellum/map_node.py +1 -0
  36. vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py +2 -2
  37. vellum_ee/workflows/display/nodes/vellum/search_node.py +4 -2
  38. vellum_ee/workflows/display/nodes/vellum/templating_node.py +1 -1
  39. vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py +3 -3
  40. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/__init__.py +0 -0
  41. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/conftest.py +28 -0
  42. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_trigger_serialization.py +123 -0
  43. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +6 -46
  44. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py +3 -25
  45. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_generic_node_serialization.py +168 -0
  46. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +18 -10
  47. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +18 -10
  48. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +3 -25
  49. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py +2 -8
  50. vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +13 -27
  51. vellum_ee/workflows/display/types.py +5 -1
  52. vellum_ee/workflows/display/utils/vellum.py +3 -3
  53. vellum_ee/workflows/display/vellum.py +4 -0
  54. vellum_ee/workflows/display/workflows/base_workflow_display.py +44 -16
  55. vellum_ee/workflows/display/workflows/get_vellum_workflow_display_class.py +3 -0
  56. vellum_ee/workflows/display/workflows/vellum_workflow_display.py +7 -8
  57. {vellum_ai-0.12.14.dist-info → vellum_ai-0.12.16.dist-info}/LICENSE +0 -0
  58. {vellum_ai-0.12.14.dist-info → vellum_ai-0.12.16.dist-info}/WHEEL +0 -0
  59. {vellum_ai-0.12.14.dist-info → vellum_ai-0.12.16.dist-info}/entry_points.txt +0 -0
@@ -1,8 +1,5 @@
1
- from unittest import mock
2
-
3
1
  from deepdiff import DeepDiff
4
2
 
5
- from vellum_ee.workflows.display.nodes.base_node_vellum_display import BaseNodeVellumDisplay
6
3
  from vellum_ee.workflows.display.workflows import VellumWorkflowDisplay
7
4
  from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
8
5
 
@@ -15,12 +12,7 @@ def test_serialize_workflow():
15
12
  workflow_display = get_workflow_display(
16
13
  base_display_class=VellumWorkflowDisplay, workflow_class=BasicInlineSubworkflowWorkflow
17
14
  )
18
-
19
- # TODO: Support serialization of BaseNode
20
- # https://app.shortcut.com/vellum/story/4871/support-serialization-of-base-node
21
- with mock.patch.object(BaseNodeVellumDisplay, "serialize") as mocked_serialize:
22
- mocked_serialize.return_value = {"type": "MOCKED"}
23
- serialized_workflow: dict = workflow_display.serialize()
15
+ serialized_workflow: dict = workflow_display.serialize()
24
16
 
25
17
  # THEN we should get a serialized representation of the Workflow
26
18
  assert serialized_workflow.keys() == {
@@ -156,7 +148,23 @@ def test_serialize_workflow():
156
148
  "bases": [],
157
149
  },
158
150
  },
159
- {"type": "MOCKED"},
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
+ },
160
168
  {
161
169
  "id": "a773c3a5-78cb-4250-8d29-7282e8a579d3",
162
170
  "type": "TERMINAL",
@@ -1,8 +1,5 @@
1
- from unittest import mock
2
-
3
1
  from deepdiff import DeepDiff
4
2
 
5
- from vellum_ee.workflows.display.nodes.base_node_vellum_display import BaseNodeVellumDisplay
6
3
  from vellum_ee.workflows.display.workflows import VellumWorkflowDisplay
7
4
  from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
8
5
 
@@ -13,12 +10,7 @@ def test_serialize_workflow():
13
10
  # GIVEN a Workflow that uses a MapNode
14
11
  # WHEN we serialize it
15
12
  workflow_display = get_workflow_display(base_display_class=VellumWorkflowDisplay, workflow_class=SimpleMapExample)
16
-
17
- # TODO: Support serialization of BaseNode
18
- # https://app.shortcut.com/vellum/story/4871/support-serialization-of-base-node
19
- with mock.patch.object(BaseNodeVellumDisplay, "serialize") as mocked_serialize:
20
- mocked_serialize.return_value = {"type": "MOCKED"}
21
- serialized_workflow: dict = workflow_display.serialize()
13
+ serialized_workflow: dict = workflow_display.serialize()
22
14
 
23
15
  # THEN we should get a serialized representation of the Workflow
24
16
  assert serialized_workflow.keys() == {
@@ -141,7 +133,23 @@ def test_serialize_workflow():
141
133
  "bases": [],
142
134
  },
143
135
  },
144
- {"type": "MOCKED"},
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
+ },
145
153
  {
146
154
  "id": "6f4883b2-70b1-4e1c-ae15-7d0f5aec810b",
147
155
  "type": "TERMINAL",
@@ -1,8 +1,5 @@
1
- from unittest import mock
2
-
3
1
  from deepdiff import DeepDiff
4
2
 
5
- from vellum_ee.workflows.display.nodes.base_node_vellum_display import BaseNodeVellumDisplay
6
3
  from vellum_ee.workflows.display.workflows import VellumWorkflowDisplay
7
4
  from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
8
5
 
@@ -15,12 +12,7 @@ def test_serialize_workflow__await_all():
15
12
  workflow_display = get_workflow_display(
16
13
  base_display_class=VellumWorkflowDisplay, workflow_class=AwaitAllPassingWorkflow
17
14
  )
18
-
19
- # TODO: Support serialization of BaseNode
20
- # https://app.shortcut.com/vellum/story/4871/support-serialization-of-base-node
21
- with mock.patch.object(BaseNodeVellumDisplay, "serialize") as mocked_serialize:
22
- mocked_serialize.return_value = {"type": "MOCKED"}
23
- serialized_workflow: dict = workflow_display.serialize()
15
+ serialized_workflow: dict = workflow_display.serialize()
24
16
 
25
17
  # THEN we should get a serialized representation of the Workflow
26
18
  assert serialized_workflow.keys() == {
@@ -76,22 +68,8 @@ def test_serialize_workflow__await_all():
76
68
  },
77
69
  }
78
70
 
79
- passthrough_nodes = [node for node in workflow_raw_data["nodes"] if node["type"] == "MOCKED"]
80
- assert not DeepDiff(
81
- [
82
- {
83
- "type": "MOCKED",
84
- },
85
- {
86
- "type": "MOCKED",
87
- },
88
- {
89
- "type": "MOCKED",
90
- },
91
- ],
92
- passthrough_nodes,
93
- ignore_order=True,
94
- )
71
+ passthrough_nodes = [node for node in workflow_raw_data["nodes"] if node["type"] == "GENERIC"]
72
+ assert len(passthrough_nodes) == 3
95
73
 
96
74
  merge_node = next(node for node in workflow_raw_data["nodes"] if node["type"] == "MERGE")
97
75
  assert not DeepDiff(
@@ -1,8 +1,5 @@
1
- from unittest import mock
2
-
3
1
  from deepdiff import DeepDiff
4
2
 
5
- from vellum_ee.workflows.display.nodes.base_node_vellum_display import BaseNodeVellumDisplay
6
3
  from vellum_ee.workflows.display.workflows import VellumWorkflowDisplay
7
4
  from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
8
5
 
@@ -13,10 +10,7 @@ def test_serialize_workflow():
13
10
  # GIVEN a Workflow with a TryNode
14
11
  # WHEN we serialize it
15
12
  workflow_display = get_workflow_display(base_display_class=VellumWorkflowDisplay, workflow_class=SimpleTryExample)
16
-
17
- with mock.patch.object(BaseNodeVellumDisplay, "serialize") as mocked_serialize:
18
- mocked_serialize.return_value = {"type": "MOCKED"}
19
- serialized_workflow: dict = workflow_display.serialize()
13
+ serialized_workflow: dict = workflow_display.serialize()
20
14
 
21
15
  # THEN we should get a serialized representation of the Workflow
22
16
  assert serialized_workflow.keys() == {
@@ -82,4 +76,4 @@ def test_serialize_workflow():
82
76
  }
83
77
 
84
78
  try_node = workflow_raw_data["nodes"][1]
85
- assert try_node == {"type": "MOCKED"}
79
+ assert try_node["id"] == "1381c078-efa2-4255-89a1-7b4cb742c7fc"
@@ -1,9 +1,7 @@
1
1
  import pytest
2
- from unittest import mock
3
2
 
4
3
  from deepdiff import DeepDiff
5
4
 
6
- from vellum_ee.workflows.display.nodes.base_node_vellum_display import BaseNodeVellumDisplay
7
5
  from vellum_ee.workflows.display.workflows import VellumWorkflowDisplay
8
6
  from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
9
7
 
@@ -13,17 +11,12 @@ from tests.workflows.complex_final_output_node.missing_workflow_output import Mi
13
11
 
14
12
  def test_serialize_workflow__missing_final_output_node():
15
13
  # GIVEN a Workflow that is missing a Terminal Node
14
+ workflow_display = get_workflow_display(
15
+ base_display_class=VellumWorkflowDisplay, workflow_class=MissingFinalOutputNodeWorkflow
16
+ )
16
17
 
17
- # TODO: Support serialization of BaseNode
18
- # https://app.shortcut.com/vellum/story/4871/support-serialization-of-base-node
19
- with mock.patch.object(BaseNodeVellumDisplay, "serialize") as mocked_serialize:
20
- mocked_serialize.return_value = {"type": "MOCKED"}
21
- workflow_display = get_workflow_display(
22
- base_display_class=VellumWorkflowDisplay, workflow_class=MissingFinalOutputNodeWorkflow
23
- )
24
-
25
- # WHEN we serialize it
26
- serialized_workflow: dict = workflow_display.serialize()
18
+ # WHEN we serialize it
19
+ serialized_workflow: dict = workflow_display.serialize()
27
20
 
28
21
  # THEN we should get a serialized representation of the Workflow
29
22
  assert serialized_workflow.keys() == {
@@ -97,10 +90,8 @@ def test_serialize_workflow__missing_final_output_node():
97
90
  },
98
91
  }
99
92
 
100
- passthrough_node = next(node for node in workflow_raw_data["nodes"] if node["type"] == "MOCKED")
101
- assert passthrough_node == {
102
- "type": "MOCKED",
103
- }
93
+ passthrough_node = next(node for node in workflow_raw_data["nodes"] if node["type"] == "GENERIC")
94
+ assert passthrough_node["id"] == "32d88cab-e9fa-4a56-9bc2-fb6e1fd0897f"
104
95
 
105
96
  final_output_nodes = [node for node in workflow_raw_data["nodes"] if node["type"] == "TERMINAL"]
106
97
  assert not DeepDiff(
@@ -218,17 +209,12 @@ def test_serialize_workflow__missing_final_output_node():
218
209
 
219
210
  def test_serialize_workflow__missing_workflow_output():
220
211
  # GIVEN a Workflow that contains a terminal node that is unreferenced by the Workflow's Outputs
212
+ workflow_display = get_workflow_display(
213
+ base_display_class=VellumWorkflowDisplay, workflow_class=MissingWorkflowOutputWorkflow
214
+ )
221
215
 
222
- # TODO: Support serialization of BaseNode
223
- # https://app.shortcut.com/vellum/story/4871/support-serialization-of-base-node
224
- with mock.patch.object(BaseNodeVellumDisplay, "serialize") as mocked_serialize:
225
- mocked_serialize.return_value = {"type": "MOCKED"}
226
- workflow_display = get_workflow_display(
227
- base_display_class=VellumWorkflowDisplay, workflow_class=MissingWorkflowOutputWorkflow
228
- )
229
-
230
- # WHEN we serialize it, it should throw an error
231
- with pytest.raises(ValueError) as exc_info:
232
- workflow_display.serialize()
216
+ # WHEN we serialize it, it should throw an error
217
+ with pytest.raises(ValueError) as exc_info:
218
+ workflow_display.serialize()
233
219
 
234
220
  assert exc_info.value.args[0] == "Unable to serialize terminal nodes that are not referenced by workflow outputs."
@@ -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] = [