vellum-ai 0.12.14__py3-none-any.whl → 0.12.16__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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] = [