vellum-ai 0.14.21__py3-none-any.whl → 0.14.23__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 (147) hide show
  1. vellum/__init__.py +122 -0
  2. vellum/client/core/client_wrapper.py +1 -1
  3. vellum/client/resources/workflow_deployments/client.py +250 -0
  4. vellum/client/types/__init__.py +122 -0
  5. vellum/client/types/api_request_parent_context.py +41 -0
  6. vellum/client/types/api_version_enum.py +5 -0
  7. vellum/client/types/base_output.py +21 -0
  8. vellum/client/types/code_resource_definition.py +31 -0
  9. vellum/client/types/external_input_descriptor.py +23 -0
  10. vellum/client/types/invoked_port.py +19 -0
  11. vellum/client/types/ml_model_usage_wrapper.py +21 -0
  12. vellum/client/types/node_event_display_context.py +30 -0
  13. vellum/client/types/node_execution_fulfilled_body.py +24 -0
  14. vellum/client/types/node_execution_fulfilled_event.py +49 -0
  15. vellum/client/types/node_execution_initiated_body.py +21 -0
  16. vellum/client/types/node_execution_initiated_event.py +49 -0
  17. vellum/client/types/node_execution_paused_body.py +20 -0
  18. vellum/client/types/node_execution_paused_event.py +49 -0
  19. vellum/client/types/node_execution_rejected_body.py +22 -0
  20. vellum/client/types/node_execution_rejected_event.py +49 -0
  21. vellum/client/types/node_execution_resumed_body.py +20 -0
  22. vellum/client/types/node_execution_resumed_event.py +49 -0
  23. vellum/client/types/node_execution_span.py +46 -0
  24. vellum/client/types/node_execution_span_attributes.py +19 -0
  25. vellum/client/types/node_execution_streaming_body.py +22 -0
  26. vellum/client/types/node_execution_streaming_event.py +49 -0
  27. vellum/client/types/node_parent_context.py +43 -0
  28. vellum/client/types/parent_context.py +21 -0
  29. vellum/client/types/prompt_deployment_parent_context.py +49 -0
  30. vellum/client/types/slim_workflow_execution_read.py +54 -0
  31. vellum/client/types/span_link.py +41 -0
  32. vellum/client/types/span_link_type_enum.py +5 -0
  33. vellum/client/types/vellum_code_resource_definition.py +25 -0
  34. vellum/client/types/vellum_node_execution_event.py +18 -0
  35. vellum/client/types/vellum_sdk_error.py +21 -0
  36. vellum/client/types/vellum_sdk_error_code_enum.py +20 -0
  37. vellum/client/types/vellum_span.py +7 -0
  38. vellum/client/types/vellum_workflow_execution_event.py +20 -0
  39. vellum/client/types/workflow_deployment_event_executions_response.py +55 -0
  40. vellum/client/types/workflow_deployment_parent_context.py +49 -0
  41. vellum/client/types/workflow_error.py +7 -0
  42. vellum/client/types/workflow_event_display_context.py +28 -0
  43. vellum/client/types/workflow_event_execution_read.py +60 -0
  44. vellum/client/types/workflow_execution_actual.py +30 -0
  45. vellum/client/types/workflow_execution_fulfilled_body.py +21 -0
  46. vellum/client/types/workflow_execution_fulfilled_event.py +49 -0
  47. vellum/client/types/workflow_execution_initiated_body.py +30 -0
  48. vellum/client/types/workflow_execution_initiated_event.py +53 -0
  49. vellum/client/types/workflow_execution_paused_body.py +22 -0
  50. vellum/client/types/workflow_execution_paused_event.py +49 -0
  51. vellum/client/types/workflow_execution_rejected_body.py +22 -0
  52. vellum/client/types/workflow_execution_rejected_event.py +49 -0
  53. vellum/client/types/workflow_execution_resumed_body.py +20 -0
  54. vellum/client/types/workflow_execution_resumed_event.py +49 -0
  55. vellum/client/types/workflow_execution_snapshotted_body.py +21 -0
  56. vellum/client/types/workflow_execution_snapshotted_event.py +51 -0
  57. vellum/client/types/workflow_execution_span.py +50 -0
  58. vellum/client/types/workflow_execution_span_attributes.py +19 -0
  59. vellum/client/types/workflow_execution_streaming_body.py +22 -0
  60. vellum/client/types/workflow_execution_streaming_event.py +49 -0
  61. vellum/client/types/workflow_execution_usage_calculation_fulfilled_body.py +22 -0
  62. vellum/client/types/workflow_execution_view_online_eval_metric_result.py +30 -0
  63. vellum/client/types/workflow_initialization_error.py +24 -0
  64. vellum/client/types/workflow_parent_context.py +43 -0
  65. vellum/client/types/workflow_sandbox_parent_context.py +44 -0
  66. vellum/types/api_request_parent_context.py +3 -0
  67. vellum/types/api_version_enum.py +3 -0
  68. vellum/types/base_output.py +3 -0
  69. vellum/types/code_resource_definition.py +3 -0
  70. vellum/types/external_input_descriptor.py +3 -0
  71. vellum/types/invoked_port.py +3 -0
  72. vellum/types/ml_model_usage_wrapper.py +3 -0
  73. vellum/types/node_event_display_context.py +3 -0
  74. vellum/types/node_execution_fulfilled_body.py +3 -0
  75. vellum/types/node_execution_fulfilled_event.py +3 -0
  76. vellum/types/node_execution_initiated_body.py +3 -0
  77. vellum/types/node_execution_initiated_event.py +3 -0
  78. vellum/types/node_execution_paused_body.py +3 -0
  79. vellum/types/node_execution_paused_event.py +3 -0
  80. vellum/types/node_execution_rejected_body.py +3 -0
  81. vellum/types/node_execution_rejected_event.py +3 -0
  82. vellum/types/node_execution_resumed_body.py +3 -0
  83. vellum/types/node_execution_resumed_event.py +3 -0
  84. vellum/types/node_execution_span.py +3 -0
  85. vellum/types/node_execution_span_attributes.py +3 -0
  86. vellum/types/node_execution_streaming_body.py +3 -0
  87. vellum/types/node_execution_streaming_event.py +3 -0
  88. vellum/types/node_parent_context.py +3 -0
  89. vellum/types/parent_context.py +3 -0
  90. vellum/types/prompt_deployment_parent_context.py +3 -0
  91. vellum/types/slim_workflow_execution_read.py +3 -0
  92. vellum/types/span_link.py +3 -0
  93. vellum/types/span_link_type_enum.py +3 -0
  94. vellum/types/vellum_code_resource_definition.py +3 -0
  95. vellum/types/vellum_node_execution_event.py +3 -0
  96. vellum/types/vellum_sdk_error.py +3 -0
  97. vellum/types/vellum_sdk_error_code_enum.py +3 -0
  98. vellum/types/vellum_span.py +3 -0
  99. vellum/types/vellum_workflow_execution_event.py +3 -0
  100. vellum/types/workflow_deployment_event_executions_response.py +3 -0
  101. vellum/types/workflow_deployment_parent_context.py +3 -0
  102. vellum/types/workflow_error.py +3 -0
  103. vellum/types/workflow_event_display_context.py +3 -0
  104. vellum/types/workflow_event_execution_read.py +3 -0
  105. vellum/types/workflow_execution_actual.py +3 -0
  106. vellum/types/workflow_execution_fulfilled_body.py +3 -0
  107. vellum/types/workflow_execution_fulfilled_event.py +3 -0
  108. vellum/types/workflow_execution_initiated_body.py +3 -0
  109. vellum/types/workflow_execution_initiated_event.py +3 -0
  110. vellum/types/workflow_execution_paused_body.py +3 -0
  111. vellum/types/workflow_execution_paused_event.py +3 -0
  112. vellum/types/workflow_execution_rejected_body.py +3 -0
  113. vellum/types/workflow_execution_rejected_event.py +3 -0
  114. vellum/types/workflow_execution_resumed_body.py +3 -0
  115. vellum/types/workflow_execution_resumed_event.py +3 -0
  116. vellum/types/workflow_execution_snapshotted_body.py +3 -0
  117. vellum/types/workflow_execution_snapshotted_event.py +3 -0
  118. vellum/types/workflow_execution_span.py +3 -0
  119. vellum/types/workflow_execution_span_attributes.py +3 -0
  120. vellum/types/workflow_execution_streaming_body.py +3 -0
  121. vellum/types/workflow_execution_streaming_event.py +3 -0
  122. vellum/types/workflow_execution_usage_calculation_fulfilled_body.py +3 -0
  123. vellum/types/workflow_execution_view_online_eval_metric_result.py +3 -0
  124. vellum/types/workflow_initialization_error.py +3 -0
  125. vellum/types/workflow_parent_context.py +3 -0
  126. vellum/types/workflow_sandbox_parent_context.py +3 -0
  127. vellum/utils/templating/constants.py +7 -2
  128. vellum/utils/templating/custom_filters.py +9 -0
  129. vellum/workflows/README.md +3 -2
  130. vellum/workflows/nodes/core/retry_node/tests/test_node.py +0 -23
  131. vellum/workflows/nodes/core/templating_node/tests/test_templating_node.py +18 -0
  132. vellum/workflows/nodes/displayable/api_node/tests/test_api_node.py +4 -2
  133. vellum/workflows/nodes/displayable/bases/api_node/node.py +1 -1
  134. vellum/workflows/workflows/base.py +2 -2
  135. vellum/workflows/workflows/tests/test_base_workflow.py +10 -0
  136. {vellum_ai-0.14.21.dist-info → vellum_ai-0.14.23.dist-info}/METADATA +2 -3
  137. {vellum_ai-0.14.21.dist-info → vellum_ai-0.14.23.dist-info}/RECORD +147 -25
  138. vellum_ee/workflows/display/nodes/vellum/base_adornment_node.py +63 -42
  139. vellum_ee/workflows/display/nodes/vellum/final_output_node.py +8 -0
  140. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py +8 -0
  141. vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +12 -8
  142. vellum_ee/workflows/display/workflows/base_workflow_display.py +9 -27
  143. vellum_ee/workflows/display/workflows/tests/test_workflow_display.py +54 -0
  144. vellum_ee/workflows/display/workflows/vellum_workflow_display.py +2 -2
  145. {vellum_ai-0.14.21.dist-info → vellum_ai-0.14.23.dist-info}/LICENSE +0 -0
  146. {vellum_ai-0.14.21.dist-info → vellum_ai-0.14.23.dist-info}/WHEEL +0 -0
  147. {vellum_ai-0.14.21.dist-info → vellum_ai-0.14.23.dist-info}/entry_points.txt +0 -0
@@ -3,6 +3,7 @@ import types
3
3
  from uuid import UUID
4
4
  from typing import Any, Callable, Dict, Generic, Optional, Type, TypeVar, cast
5
5
 
6
+ from vellum.workflows.nodes.bases.base import BaseNode
6
7
  from vellum.workflows.nodes.bases.base_adornment_node import BaseAdornmentNode
7
8
  from vellum.workflows.nodes.utils import get_wrapped_node
8
9
  from vellum.workflows.types.core import JsonArray, JsonObject
@@ -17,7 +18,62 @@ from vellum_ee.workflows.display.types import WorkflowDisplayContext
17
18
  _BaseAdornmentNodeType = TypeVar("_BaseAdornmentNodeType", bound=BaseAdornmentNode)
18
19
 
19
20
 
21
+ def _recursively_replace_wrapped_node(node_class: Type[BaseNode], wrapped_node_display_class: Type["BaseNodeDisplay"]):
22
+ if not issubclass(node_class, BaseAdornmentNode):
23
+ return
24
+
25
+ # We must edit the node display class to use __wrapped_node__ everywhere it
26
+ # references the adorned node class, which is three places:
27
+ wrapped_node_class = node_class.__wrapped_node__
28
+ if not wrapped_node_class:
29
+ raise ValueError(f"Node {node_class.__name__} must be used as an adornment with the `wrap` method.")
30
+
31
+ # 1. The node display class' parameterized type
32
+ original_base_node_display = get_original_base(wrapped_node_display_class)
33
+ original_base_node_display.__args__ = (wrapped_node_class,)
34
+ wrapped_node_display_class._node_display_registry[wrapped_node_class] = wrapped_node_display_class
35
+ wrapped_node_display_class.__annotate_node__()
36
+
37
+ # 2. The node display class' output displays
38
+ for old_output in node_class.Outputs:
39
+ new_output = getattr(wrapped_node_class.Outputs, old_output.name, None)
40
+ if new_output is None:
41
+ # If the adornment is adding a new output, such as TryNode adding an "error" output,
42
+ # we skip it, since it should not be included in the adorned node's output displays
43
+ wrapped_node_display_class.output_display.pop(old_output, None)
44
+ continue
45
+
46
+ if old_output not in wrapped_node_display_class.output_display:
47
+ # If the adorned node doesn't have an output display defined for this output, we define one
48
+ wrapped_node_display_class.output_display[new_output] = NodeOutputDisplay(
49
+ id=wrapped_node_class.__output_ids__[old_output.name],
50
+ name=old_output.name,
51
+ )
52
+ else:
53
+ wrapped_node_display_class.output_display[new_output] = wrapped_node_display_class.output_display.pop(
54
+ old_output
55
+ )
56
+
57
+ # 3. The node display class' port displays
58
+ old_ports = list(wrapped_node_display_class.port_displays.keys())
59
+ for old_port in old_ports:
60
+ new_port = getattr(wrapped_node_class.Ports, old_port.name)
61
+ wrapped_node_display_class.port_displays[new_port] = wrapped_node_display_class.port_displays.pop(old_port)
62
+
63
+ # Now we keep drilling down recursively
64
+ if (
65
+ issubclass(wrapped_node_display_class, BaseAdornmentNodeDisplay)
66
+ and wrapped_node_display_class.__wrapped_node_display__
67
+ ):
68
+ _recursively_replace_wrapped_node(
69
+ wrapped_node_class,
70
+ wrapped_node_display_class.__wrapped_node_display__,
71
+ )
72
+
73
+
20
74
  class BaseAdornmentNodeDisplay(BaseNodeVellumDisplay[_BaseAdornmentNodeType], Generic[_BaseAdornmentNodeType]):
75
+ __wrapped_node_display__: Optional[Type[BaseNodeDisplay]] = None
76
+
21
77
  def serialize(
22
78
  self,
23
79
  display_context: "WorkflowDisplayContext",
@@ -52,10 +108,6 @@ class BaseAdornmentNodeDisplay(BaseNodeVellumDisplay[_BaseAdornmentNodeType], Ge
52
108
  if not issubclass(node_class, BaseAdornmentNode):
53
109
  raise ValueError(f"Node {node_class.__name__} must be wrapped with a {BaseAdornmentNode.__name__}")
54
110
 
55
- wrapped_node_class = node_class.__wrapped_node__
56
- if not wrapped_node_class:
57
- raise ValueError(f"Node {node_class.__name__} must be used as an adornment with the `wrap` method.")
58
-
59
111
  # `mypy` is wrong here, `cls` is indexable bc it's Generic
60
112
  BaseAdornmentDisplay = cls[node_class] # type: ignore[index]
61
113
 
@@ -64,6 +116,7 @@ class BaseAdornmentNodeDisplay(BaseNodeVellumDisplay[_BaseAdornmentNodeType], Ge
64
116
  ns[key] = kwarg
65
117
 
66
118
  ns["node_id"] = node_id or uuid4_from_hash(node_class.__qualname__)
119
+ ns["__wrapped_node_display__"] = wrapped_node_display_class
67
120
 
68
121
  AdornmentDisplay = types.new_class(
69
122
  re.sub(r"^Base", "", cls.__name__), bases=(BaseAdornmentDisplay,), exec_body=exec_body
@@ -71,43 +124,11 @@ class BaseAdornmentNodeDisplay(BaseNodeVellumDisplay[_BaseAdornmentNodeType], Ge
71
124
 
72
125
  setattr(wrapped_node_display_class, "__adorned_by__", AdornmentDisplay)
73
126
 
74
- # We must edit the node display class to use __wrapped_node__ everywhere it
75
- # references the adorned node class, which is three places:
76
-
77
- # 1. The node display class' parameterized type
78
- original_base_node_display = get_original_base(wrapped_node_display_class)
79
- original_base_node_display.__args__ = (wrapped_node_class,)
80
- wrapped_node_display_class._node_display_registry[wrapped_node_class] = wrapped_node_display_class
81
- wrapped_node_display_class.__annotate_node__()
82
-
83
- # 2. The node display class' output displays
84
- for old_output in node_class.Outputs:
85
- new_output = getattr(wrapped_node_class.Outputs, old_output.name, None)
86
- if new_output is None:
87
- # If the adornment is adding a new output, such as TryNode adding an "error" output,
88
- # we skip it, since it should not be included in the adorned node's output displays
89
- wrapped_node_display_class.output_display.pop(old_output, None)
90
- continue
91
-
92
- if old_output not in wrapped_node_display_class.output_display:
93
- # If the adorned node doesn't have an output display defined for this output, we define one
94
- wrapped_node_display_class.output_display[new_output] = NodeOutputDisplay(
95
- id=wrapped_node_class.__output_ids__[old_output.name],
96
- name=old_output.name,
97
- )
98
- else:
99
- wrapped_node_display_class.output_display[new_output] = (
100
- wrapped_node_display_class.output_display.pop(old_output)
101
- )
102
-
103
- # 3. The node display class' port displays
104
- old_ports = list(wrapped_node_display_class.port_displays.keys())
105
- for old_port in old_ports:
106
- new_port = getattr(wrapped_node_class.Ports, old_port.name)
107
- wrapped_node_display_class.port_displays[new_port] = wrapped_node_display_class.port_displays.pop(
108
- old_port
109
- )
110
-
111
- return wrapped_node_display_class
127
+ _recursively_replace_wrapped_node(
128
+ node_class,
129
+ wrapped_node_display_class,
130
+ )
131
+
132
+ return AdornmentDisplay
112
133
 
113
134
  return decorator
@@ -47,6 +47,14 @@ class BaseFinalOutputNodeDisplay(BaseNodeVellumDisplay[_FinalOutputNodeType], Ge
47
47
  "display_data": self.get_display_data().dict(),
48
48
  "base": self.get_base().dict(),
49
49
  "definition": self.get_definition().dict(),
50
+ "outputs": [
51
+ {
52
+ "id": str(self._get_output_id()),
53
+ "name": node.Outputs.value.name,
54
+ "type": inferred_type,
55
+ "value": self.serialize_value(display_context, node.Outputs.value.instance),
56
+ }
57
+ ],
50
58
  }
51
59
 
52
60
  def _get_output_id(self) -> UUID:
@@ -99,4 +99,12 @@ def test_serialize_workflow():
99
99
  "name": "BasicFinalOutputNode",
100
100
  "module": ["tests", "workflows", "basic_final_output_node", "workflow"],
101
101
  },
102
+ "outputs": [
103
+ {
104
+ "id": "97349956-d228-4b51-a64b-1331f788373f",
105
+ "name": "value",
106
+ "type": "STRING",
107
+ "value": {"type": "WORKFLOW_INPUT", "input_variable_id": "e39a7b63-de15-490a-ae9b-8112c767aea0"},
108
+ }
109
+ ],
102
110
  }
@@ -123,6 +123,17 @@ def test_serialize_workflow__missing_final_output_node():
123
123
  "name": "FirstFinalOutputNode",
124
124
  "module": ["tests", "workflows", "complex_final_output_node", "missing_final_output_node"],
125
125
  },
126
+ "outputs": [
127
+ {
128
+ "id": "5517e50d-7f40-4f7c-acb2-e329d79a25bf",
129
+ "name": "value",
130
+ "type": "STRING",
131
+ "value": {
132
+ "type": "WORKFLOW_INPUT",
133
+ "input_variable_id": "da086239-d743-4246-b666-5c91e22fb88c",
134
+ },
135
+ }
136
+ ],
126
137
  },
127
138
  {
128
139
  "id": "bb88768d-472e-4997-b7ea-de09163d1b4c",
@@ -156,14 +167,7 @@ def test_serialize_workflow__missing_final_output_node():
156
167
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
157
168
  "base": {
158
169
  "name": "FinalOutputNode",
159
- "module": [
160
- "vellum",
161
- "workflows",
162
- "nodes",
163
- "displayable",
164
- "final_output_node",
165
- "node",
166
- ],
170
+ "module": ["vellum", "workflows", "nodes", "displayable", "final_output_node", "node"],
167
171
  },
168
172
  "definition": None,
169
173
  },
@@ -10,7 +10,6 @@ from vellum.workflows import BaseWorkflow
10
10
  from vellum.workflows.descriptors.base import BaseDescriptor
11
11
  from vellum.workflows.edges import Edge
12
12
  from vellum.workflows.events.workflow import NodeEventDisplayContext, WorkflowEventDisplayContext
13
- from vellum.workflows.expressions.coalesce_expression import CoalesceExpression
14
13
  from vellum.workflows.nodes.bases import BaseNode
15
14
  from vellum.workflows.nodes.utils import get_unadorned_node, get_wrapped_node
16
15
  from vellum.workflows.ports import Port
@@ -150,8 +149,6 @@ class BaseWorkflowDisplay(
150
149
  if node_output in node_output_displays:
151
150
  continue
152
151
 
153
- # TODO: Make sure this output ID matches the workflow output ID of the subworkflow node's workflow
154
- # https://app.shortcut.com/vellum/story/5660/fix-output-id-in-subworkflow-nodes
155
152
  node_output_displays[node_output] = node_display.get_node_output_display(node_output)
156
153
 
157
154
  def _enrich_node_port_displays(
@@ -272,8 +269,8 @@ class BaseWorkflowDisplay(
272
269
  continue
273
270
 
274
271
  edge_display_overrides = self.edge_displays.get((edge.from_port, edge.to_node))
275
- edge_displays[(edge.from_port, edge.to_node)] = self._generate_edge_display(
276
- edge, node_displays, overrides=edge_display_overrides
272
+ edge_displays[(edge.from_port, edge.to_node)] = edge_display_overrides or self._generate_edge_display(
273
+ edge, node_displays
277
274
  )
278
275
 
279
276
  for edge in self._workflow.get_unused_edges():
@@ -281,8 +278,8 @@ class BaseWorkflowDisplay(
281
278
  continue
282
279
 
283
280
  edge_display_overrides = self.edge_displays.get((edge.from_port, edge.to_node))
284
- edge_displays[(edge.from_port, edge.to_node)] = self._generate_edge_display(
285
- edge, node_displays, overrides=edge_display_overrides
281
+ edge_displays[(edge.from_port, edge.to_node)] = edge_display_overrides or self._generate_edge_display(
282
+ edge, node_displays
286
283
  )
287
284
 
288
285
  workflow_output_displays: Dict[BaseDescriptor, WorkflowOutputDisplay] = {}
@@ -293,11 +290,6 @@ class BaseWorkflowDisplay(
293
290
  if not isinstance(workflow_output, OutputReference):
294
291
  raise ValueError(f"{workflow_output} must be an {OutputReference.__name__}")
295
292
 
296
- if not workflow_output.instance or not isinstance(
297
- workflow_output.instance, (OutputReference, CoalesceExpression)
298
- ):
299
- raise ValueError("Expected to find a descriptor instance on the workflow output")
300
-
301
293
  workflow_output_display = self.output_displays.get(workflow_output)
302
294
  workflow_output_displays[workflow_output] = (
303
295
  workflow_output_display or self._generate_workflow_output_display(workflow_output)
@@ -443,30 +435,20 @@ class BaseWorkflowDisplay(
443
435
 
444
436
  return additional_node_displays
445
437
 
446
- def _generate_edge_display(
447
- self,
448
- edge: Edge,
449
- node_displays: Dict[Type[BaseNode], BaseNodeDisplay],
450
- overrides: Optional[EdgeDisplay] = None,
451
- ) -> EdgeDisplay:
438
+ def _generate_edge_display(self, edge: Edge, node_displays: Dict[Type[BaseNode], BaseNodeDisplay]) -> EdgeDisplay:
452
439
  source_node = get_unadorned_node(edge.from_port.node_class)
453
440
  target_node = get_unadorned_node(edge.to_node)
454
441
 
455
442
  source_node_id = node_displays[source_node].node_id
456
443
  target_node_id = node_displays[target_node].node_id
457
444
 
458
- return self._generate_edge_display_from_source(source_node_id, target_node_id, overrides)
445
+ return self._generate_edge_display_from_source(source_node_id, target_node_id)
459
446
 
460
447
  def _generate_edge_display_from_source(
461
448
  self,
462
449
  source_node_id: UUID,
463
450
  target_node_id: UUID,
464
- overrides: Optional[EdgeDisplay] = None,
465
451
  ) -> EdgeDisplay:
466
- edge_id: UUID
467
- if overrides:
468
- edge_id = overrides.id
469
- else:
470
- edge_id = uuid4_from_hash(f"{self.workflow_id}|id|{source_node_id}|{target_node_id}")
471
-
472
- return EdgeDisplay(id=edge_id)
452
+ return EdgeDisplay(
453
+ id=uuid4_from_hash(f"{self.workflow_id}|id|{source_node_id}|{target_node_id}"),
454
+ )
@@ -235,3 +235,57 @@ def test_get_event_display_context__templating_node_input_display():
235
235
  node_event_display = display_context.node_displays[MyNode.__id__]
236
236
 
237
237
  assert node_event_display.input_display.keys() == {"inputs.foo"}
238
+
239
+
240
+ def test_get_event_display_context__node_display_for_mutiple_adornments():
241
+ # GIVEN a simple workflow with multiple adornments
242
+ @TryNode.wrap()
243
+ @RetryNode.wrap()
244
+ class MyNode(BaseNode):
245
+ class Outputs(BaseNode.Outputs):
246
+ foo: str
247
+
248
+ class MyWorkflow(BaseWorkflow):
249
+ graph = MyNode
250
+
251
+ # AND a display class for the node
252
+ node_id = uuid4()
253
+ inner_node_id = uuid4()
254
+ innermost_node_id = uuid4()
255
+
256
+ @BaseTryNodeDisplay.wrap(node_id=node_id)
257
+ @BaseRetryNodeDisplay.wrap(node_id=inner_node_id)
258
+ class MyNodeDisplay(BaseNodeDisplay[MyNode]):
259
+ node_id = innermost_node_id
260
+
261
+ # WHEN we gather the event display context
262
+ display_context = VellumWorkflowDisplay(MyWorkflow).get_event_display_context()
263
+
264
+ # THEN the subworkflow display should be included
265
+ assert node_id in display_context.node_displays
266
+ node_event_display = display_context.node_displays[node_id]
267
+ assert node_event_display.subworkflow_display
268
+
269
+ # AND the inner node should be included
270
+ assert inner_node_id in node_event_display.subworkflow_display.node_displays
271
+ inner_node_event_display = node_event_display.subworkflow_display.node_displays[inner_node_id]
272
+ assert inner_node_event_display.subworkflow_display
273
+
274
+ # AND the innermost node should be included
275
+ assert innermost_node_id in inner_node_event_display.subworkflow_display.node_displays
276
+ innermost_node_event_display = inner_node_event_display.subworkflow_display.node_displays[innermost_node_id]
277
+ assert not innermost_node_event_display.subworkflow_display
278
+
279
+
280
+ def test_get_event_display_context__workflow_output_display_with_none():
281
+ # GIVEN a workflow with a workflow output that is None
282
+ class MyWorkflow(BaseWorkflow):
283
+ class Outputs(BaseWorkflow.Outputs):
284
+ foo = None
285
+ bar = "baz"
286
+
287
+ # WHEN we gather the event display context
288
+ display_context = VellumWorkflowDisplay(MyWorkflow).get_event_display_context()
289
+
290
+ # THEN the workflow output display should be included
291
+ assert display_context.workflow_outputs.keys() == {"foo", "bar"}
@@ -363,8 +363,8 @@ class VellumWorkflowDisplay(
363
363
  target_node_display = node_displays[entrypoint_target]
364
364
  target_node_id = target_node_display.node_id
365
365
 
366
- edge_display = self._generate_edge_display_from_source(
367
- entrypoint_node_id, target_node_id, overrides=edge_display_overrides
366
+ edge_display = edge_display_overrides or self._generate_edge_display_from_source(
367
+ entrypoint_node_id, target_node_id
368
368
  )
369
369
 
370
370
  return EntrypointVellumDisplay(id=entrypoint_id, edge_display=edge_display)