vellum-ai 0.14.10__py3-none-any.whl → 0.14.12__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.
@@ -18,7 +18,7 @@ class BaseClientWrapper:
18
18
  headers: typing.Dict[str, str] = {
19
19
  "X-Fern-Language": "Python",
20
20
  "X-Fern-SDK-Name": "vellum-ai",
21
- "X-Fern-SDK-Version": "0.14.10",
21
+ "X-Fern-SDK-Version": "0.14.12",
22
22
  }
23
23
  headers["X_API_KEY"] = self.api_key
24
24
  return headers
@@ -75,6 +75,7 @@ class BaseDeploymentParentContext(BaseParentContext):
75
75
  release_tag_id: UUID
76
76
  release_tag_name: str
77
77
  external_id: Optional[str]
78
+ metadata: Optional[dict]
78
79
 
79
80
 
80
81
  class WorkflowDeploymentParentContext(BaseDeploymentParentContext):
@@ -104,6 +105,10 @@ class WorkflowSandboxParentContext(BaseParentContext):
104
105
  scenario_id: UUID
105
106
 
106
107
 
108
+ class APIRequestParentContext(BaseParentContext):
109
+ type: Literal["API_REQUEST"] = "API_REQUEST"
110
+
111
+
107
112
  # Define the discriminated union
108
113
  ParentContext = Annotated[
109
114
  Union[
@@ -112,6 +117,7 @@ ParentContext = Annotated[
112
117
  WorkflowDeploymentParentContext,
113
118
  PromptDeploymentParentContext,
114
119
  WorkflowSandboxParentContext,
120
+ APIRequestParentContext,
115
121
  ],
116
122
  Field(discriminator="type"),
117
123
  ]
@@ -458,16 +458,7 @@ def main(word: str) -> int:
458
458
  node.run()
459
459
 
460
460
  # THEN the node should have produced the exception we expected
461
- assert (
462
- exc_info.value.message
463
- == """\
464
- 2 validation errors for FunctionCall
465
- arguments
466
- Field required [type=missing, input_value={'n': 'hello', 'a': {}}, input_type=dict]
467
- name
468
- Field required [type=missing, input_value={'n': 'hello', 'a': {}}, input_type=dict]\
469
- """
470
- )
461
+ assert exc_info.value.message == "Expected an output of type 'FunctionCall', but received 'dict'"
471
462
 
472
463
 
473
464
  def test_run_node__run_inline__valid_dict_to_pydantic_any_type():
@@ -743,3 +734,46 @@ Node.js v21.7.3
743
734
 
744
735
  # AND the error should contain the execution error details
745
736
  assert exc_info.value.message == message
737
+
738
+
739
+ def test_run_node__execute_code__list_extends():
740
+ # GIVEN a node that will return a list with output type Json
741
+ class ExampleCodeExecutionNode(CodeExecutionNode[BaseState, Json]):
742
+ code = """\
743
+ def main(left, right):
744
+ all = []
745
+ all.extend(left)
746
+ all.extend(right)
747
+ return all
748
+ """
749
+ code_inputs = {
750
+ "left": [1, 2, 3],
751
+ "right": [4, 5, 6],
752
+ }
753
+ runtime = "PYTHON_3_11_6"
754
+
755
+ # WHEN we run the node
756
+ node = ExampleCodeExecutionNode()
757
+ outputs = node.run()
758
+
759
+ # AND the result should be the correct output
760
+ assert outputs == {"result": [1, 2, 3, 4, 5, 6], "log": ""}
761
+
762
+
763
+ def test_run_node__execute_code__non_str_print():
764
+ # GIVEN a node that will print a non-string value
765
+ class ExampleCodeExecutionNode(CodeExecutionNode[BaseState, str]):
766
+ code = """\
767
+ def main():
768
+ print(type(1))
769
+ return "hello"
770
+ """
771
+ code_inputs = {}
772
+ runtime = "PYTHON_3_11_6"
773
+
774
+ # WHEN we run the node
775
+ node = ExampleCodeExecutionNode()
776
+ outputs = node.run()
777
+
778
+ # AND the result should be the correct output
779
+ assert outputs == {"result": "hello", "log": "<class 'int'>\n"}
@@ -1,12 +1,12 @@
1
1
  import io
2
2
  import os
3
- import re
4
- from typing import Any, Tuple, Union, get_args, get_origin
3
+ from typing import Any, Tuple, Union
5
4
 
6
- from pydantic import BaseModel, ValidationError
5
+ from pydantic import BaseModel
7
6
 
8
7
  from vellum.workflows.errors.types import WorkflowErrorCode
9
8
  from vellum.workflows.exceptions import NodeException
9
+ from vellum.workflows.nodes.utils import cast_to_output_type
10
10
  from vellum.workflows.types.core import EntityInputsInterface
11
11
 
12
12
 
@@ -67,49 +67,6 @@ def _clean_for_dict_wrapper(obj):
67
67
  return obj
68
68
 
69
69
 
70
- def _get_type_name(obj: Any) -> str:
71
- if isinstance(obj, type):
72
- return obj.__name__
73
-
74
- if get_origin(obj) is Union:
75
- children = [_get_type_name(child) for child in get_args(obj)]
76
- return " | ".join(children)
77
-
78
- return str(obj)
79
-
80
-
81
- def _cast_to_output_type(result: Any, output_type: Any) -> Any:
82
- is_valid_output_type = isinstance(output_type, type)
83
- if get_origin(output_type) is Union:
84
- allowed_types = get_args(output_type)
85
- for allowed_type in allowed_types:
86
- try:
87
- return _cast_to_output_type(result, allowed_type)
88
- except NodeException:
89
- continue
90
- elif get_origin(output_type) is list:
91
- allowed_item_type = get_args(output_type)[0]
92
- if isinstance(result, list):
93
- return [_cast_to_output_type(item, allowed_item_type) for item in result]
94
- elif is_valid_output_type and issubclass(output_type, BaseModel) and not isinstance(result, output_type):
95
- try:
96
- return output_type.model_validate(result)
97
- except ValidationError as e:
98
- raise NodeException(
99
- code=WorkflowErrorCode.INVALID_OUTPUTS,
100
- message=re.sub(r"\s+For further information visit [^\s]+", "", str(e)),
101
- ) from e
102
- elif is_valid_output_type and isinstance(result, output_type):
103
- return result
104
-
105
- output_type_name = _get_type_name(output_type)
106
- result_type_name = _get_type_name(type(result))
107
- raise NodeException(
108
- code=WorkflowErrorCode.INVALID_OUTPUTS,
109
- message=f"Expected an output of type '{output_type_name}', but received '{result_type_name}'",
110
- )
111
-
112
-
113
70
  def run_code_inline(
114
71
  code: str,
115
72
  inputs: EntityInputsInterface,
@@ -117,6 +74,11 @@ def run_code_inline(
117
74
  ) -> Tuple[str, Any]:
118
75
  log_buffer = io.StringIO()
119
76
 
77
+ def _inline_print(*args: Any, **kwargs: Any) -> None:
78
+ str_args = [str(arg) for arg in args]
79
+ print_line = f"{' '.join(str_args)}\n"
80
+ log_buffer.write(print_line)
81
+
120
82
  def wrap_value(value):
121
83
  if isinstance(value, list):
122
84
  return ListWrapper(
@@ -135,7 +97,7 @@ def run_code_inline(
135
97
  exec_globals = {
136
98
  "__arg__inputs": {name: wrap_value(value) for name, value in inputs.items()},
137
99
  "__arg__out": None,
138
- "print": lambda *args, **kwargs: log_buffer.write(f"{' '.join(args)}\n"),
100
+ "print": _inline_print,
139
101
  }
140
102
  run_args = [f"{name}=__arg__inputs['{name}']" for name in inputs.keys()]
141
103
  execution_code = f"""\
@@ -154,7 +116,6 @@ __arg__out = main({", ".join(run_args)})
154
116
  logs = log_buffer.getvalue()
155
117
  result = exec_globals["__arg__out"]
156
118
 
157
- if output_type != Any:
158
- result = _cast_to_output_type(result, output_type)
119
+ result = cast_to_output_type(result, output_type)
159
120
 
160
121
  return logs, result
@@ -1,7 +1,9 @@
1
1
  from typing import Any, Dict, Generic, Tuple, Type, TypeVar, get_args
2
2
 
3
+ from vellum.workflows.constants import undefined
3
4
  from vellum.workflows.nodes.bases import BaseNode
4
5
  from vellum.workflows.nodes.bases.base import BaseNodeMeta
6
+ from vellum.workflows.nodes.utils import cast_to_output_type
5
7
  from vellum.workflows.types import MergeBehavior
6
8
  from vellum.workflows.types.generics import StateType
7
9
  from vellum.workflows.types.utils import get_original_base
@@ -48,4 +50,14 @@ class FinalOutputNode(BaseNode[StateType], Generic[StateType, _OutputType], meta
48
50
  class Outputs(BaseNode.Outputs):
49
51
  # We use our mypy plugin to override the _OutputType with the actual output type
50
52
  # for downstream references to this output.
51
- value: _OutputType # type: ignore[valid-type]
53
+ value: _OutputType = undefined # type: ignore[valid-type]
54
+
55
+ def run(self) -> Outputs:
56
+ original_outputs = self.Outputs()
57
+
58
+ return self.Outputs(
59
+ value=cast_to_output_type(
60
+ original_outputs.value,
61
+ self.__class__.get_output_type(),
62
+ )
63
+ )
@@ -0,0 +1,20 @@
1
+ import pytest
2
+
3
+ from vellum.workflows.exceptions import NodeException
4
+ from vellum.workflows.nodes.displayable.final_output_node import FinalOutputNode
5
+ from vellum.workflows.state.base import BaseState
6
+
7
+
8
+ def test_final_output_node__mismatched_output_type():
9
+ # GIVEN a FinalOutputNode with a mismatched output type
10
+ class StringOutputNode(FinalOutputNode[BaseState, str]):
11
+ class Outputs(FinalOutputNode.Outputs):
12
+ value = {"foo": "bar"}
13
+
14
+ # WHEN the node is run
15
+ node = StringOutputNode()
16
+ with pytest.raises(NodeException) as exc_info:
17
+ node.run()
18
+
19
+ # THEN an error is raised
20
+ assert str(exc_info.value) == "Expected an output of type 'str', but received 'dict'"
@@ -70,8 +70,10 @@ class SubworkflowDeploymentNode(BaseNode[StateType], Generic[StateType]):
70
70
  value=input_value,
71
71
  )
72
72
  )
73
- elif isinstance(input_value, list) and all(
74
- isinstance(message, (ChatMessage, ChatMessageRequest)) for message in input_value
73
+ elif (
74
+ isinstance(input_value, list)
75
+ and len(input_value) > 0
76
+ and all(isinstance(message, (ChatMessage, ChatMessageRequest)) for message in input_value)
75
77
  ):
76
78
  chat_history = [
77
79
  (
@@ -95,7 +97,7 @@ class SubworkflowDeploymentNode(BaseNode[StateType], Generic[StateType]):
95
97
  value=cast(Dict[str, Any], input_value),
96
98
  )
97
99
  )
98
- elif isinstance(input_value, float):
100
+ elif isinstance(input_value, (int, float)):
99
101
  compiled_inputs.append(
100
102
  WorkflowRequestNumberInputRequest(
101
103
  name=input_name,
@@ -110,7 +112,6 @@ class SubworkflowDeploymentNode(BaseNode[StateType], Generic[StateType]):
110
112
  message=f"Failed to serialize input '{input_name}' of type '{input_value.__class__}': {e}",
111
113
  code=WorkflowErrorCode.INVALID_INPUTS,
112
114
  )
113
-
114
115
  compiled_inputs.append(
115
116
  WorkflowRequestJsonInputRequest(
116
117
  name=input_name,
@@ -10,6 +10,7 @@ from vellum.client.types.workflow_execution_workflow_result_event import Workflo
10
10
  from vellum.client.types.workflow_output_string import WorkflowOutputString
11
11
  from vellum.client.types.workflow_request_chat_history_input_request import WorkflowRequestChatHistoryInputRequest
12
12
  from vellum.client.types.workflow_request_json_input_request import WorkflowRequestJsonInputRequest
13
+ from vellum.client.types.workflow_request_number_input_request import WorkflowRequestNumberInputRequest
13
14
  from vellum.client.types.workflow_result_event import WorkflowResultEvent
14
15
  from vellum.client.types.workflow_stream_event import WorkflowStreamEvent
15
16
  from vellum.workflows.errors import WorkflowErrorCode
@@ -134,6 +135,116 @@ def test_run_workflow__any_array(vellum_client):
134
135
  ]
135
136
 
136
137
 
138
+ def test_run_workflow__empty_array(vellum_client):
139
+ # GIVEN a Subworkflow Deployment Node
140
+ class ExampleSubworkflowDeploymentNode(SubworkflowDeploymentNode):
141
+ deployment = "example_subworkflow_deployment"
142
+ subworkflow_inputs = {
143
+ "fruits": [],
144
+ }
145
+
146
+ # AND we know what the Subworkflow Deployment will respond with
147
+ def generate_subworkflow_events(*args: Any, **kwargs: Any) -> Iterator[WorkflowStreamEvent]:
148
+ execution_id = str(uuid4())
149
+ expected_events: List[WorkflowStreamEvent] = [
150
+ WorkflowExecutionWorkflowResultEvent(
151
+ execution_id=execution_id,
152
+ data=WorkflowResultEvent(
153
+ id=str(uuid4()),
154
+ state="INITIATED",
155
+ ts=datetime.now(),
156
+ ),
157
+ ),
158
+ WorkflowExecutionWorkflowResultEvent(
159
+ execution_id=execution_id,
160
+ data=WorkflowResultEvent(
161
+ id=str(uuid4()),
162
+ state="FULFILLED",
163
+ ts=datetime.now(),
164
+ outputs=[
165
+ WorkflowOutputString(
166
+ id=str(uuid4()),
167
+ name="greeting",
168
+ value="Great!",
169
+ )
170
+ ],
171
+ ),
172
+ ),
173
+ ]
174
+ yield from expected_events
175
+
176
+ vellum_client.execute_workflow_stream.side_effect = generate_subworkflow_events
177
+
178
+ # WHEN we run the node
179
+ node = ExampleSubworkflowDeploymentNode()
180
+ events = list(node.run())
181
+
182
+ # THEN the node should have completed successfully
183
+ assert events[-1].name == "greeting"
184
+ assert events[-1].value == "Great!"
185
+
186
+ # AND we should have invoked the Subworkflow Deployment with the expected inputs
187
+ call_kwargs = vellum_client.execute_workflow_stream.call_args.kwargs
188
+ assert call_kwargs["inputs"] == [
189
+ WorkflowRequestJsonInputRequest(name="fruits", value=[]),
190
+ ]
191
+
192
+
193
+ def test_run_workflow__int_input(vellum_client):
194
+ # GIVEN a Subworkflow Deployment Node
195
+ class ExampleSubworkflowDeploymentNode(SubworkflowDeploymentNode):
196
+ deployment = "example_subworkflow_deployment"
197
+ subworkflow_inputs = {
198
+ "number": 42,
199
+ }
200
+
201
+ # AND we know what the Subworkflow Deployment will respond with
202
+ def generate_subworkflow_events(*args: Any, **kwargs: Any) -> Iterator[WorkflowStreamEvent]:
203
+ execution_id = str(uuid4())
204
+ expected_events: List[WorkflowStreamEvent] = [
205
+ WorkflowExecutionWorkflowResultEvent(
206
+ execution_id=execution_id,
207
+ data=WorkflowResultEvent(
208
+ id=str(uuid4()),
209
+ state="INITIATED",
210
+ ts=datetime.now(),
211
+ ),
212
+ ),
213
+ WorkflowExecutionWorkflowResultEvent(
214
+ execution_id=execution_id,
215
+ data=WorkflowResultEvent(
216
+ id=str(uuid4()),
217
+ state="FULFILLED",
218
+ ts=datetime.now(),
219
+ outputs=[
220
+ WorkflowOutputString(
221
+ id=str(uuid4()),
222
+ name="greeting",
223
+ value="Great!",
224
+ )
225
+ ],
226
+ ),
227
+ ),
228
+ ]
229
+ yield from expected_events
230
+
231
+ vellum_client.execute_workflow_stream.side_effect = generate_subworkflow_events
232
+
233
+ # WHEN we run the node
234
+ node = ExampleSubworkflowDeploymentNode()
235
+ events = list(node.run())
236
+
237
+ # THEN the node should have completed successfully
238
+ assert events[-1].name == "greeting"
239
+ assert events[-1].value == "Great!"
240
+
241
+ # AND we should have invoked the Subworkflow Deployment with the expected inputs
242
+ call_kwargs = vellum_client.execute_workflow_stream.call_args.kwargs
243
+ assert call_kwargs["inputs"] == [
244
+ WorkflowRequestNumberInputRequest(name="number", value=42),
245
+ ]
246
+
247
+
137
248
  def test_run_workflow__no_deployment():
138
249
  """Confirm that we raise error when running a subworkflow deployment node with no deployment attribute set"""
139
250
 
@@ -2,10 +2,12 @@ from functools import cache
2
2
  import json
3
3
  import sys
4
4
  from types import ModuleType
5
- from typing import Any, Callable, Optional, Type, TypeVar, Union, get_args, get_origin
5
+ from typing import Any, Callable, Dict, ForwardRef, List, Optional, Type, TypeVar, Union, get_args, get_origin
6
6
 
7
- from pydantic import BaseModel
7
+ from pydantic import BaseModel, create_model
8
8
 
9
+ from vellum.workflows.errors.types import WorkflowErrorCode
10
+ from vellum.workflows.exceptions import NodeException
9
11
  from vellum.workflows.nodes import BaseNode
10
12
  from vellum.workflows.nodes.bases.base_adornment_node import BaseAdornmentNode
11
13
  from vellum.workflows.ports.port import Port
@@ -141,3 +143,55 @@ def parse_type_from_str(result_as_str: str, output_type: Any) -> Any:
141
143
  raise ValueError("Invalid JSON format for result_as_str")
142
144
 
143
145
  raise ValueError(f"Unsupported output type: {output_type}")
146
+
147
+
148
+ def _get_type_name(obj: Any) -> str:
149
+ if isinstance(obj, type):
150
+ return obj.__name__
151
+
152
+ if get_origin(obj) is Union:
153
+ children = [_get_type_name(child) for child in get_args(obj)]
154
+ return " | ".join(children)
155
+
156
+ return str(obj)
157
+
158
+
159
+ def _clean_output_type(output_type: Any) -> Any:
160
+ """
161
+ pydantic currently has a bug where it doesn't support forward references in the create_model function. It will
162
+ fail due to a max recursion depth error. This negatively impacts our `Json` type, but could also apply to other
163
+ user defined ForwardRef types.
164
+ """
165
+ if get_origin(output_type) is Union:
166
+ clean_args = [_clean_output_type(child) for child in get_args(output_type)]
167
+ return Union[tuple(clean_args)]
168
+
169
+ if isinstance(output_type, ForwardRef):
170
+ # Here is where we prevent the max recursion depth error
171
+ return Any
172
+
173
+ if get_origin(output_type) is list:
174
+ clean_args = [_clean_output_type(child) for child in get_args(output_type)]
175
+ return List[clean_args[0]] # type: ignore[valid-type]
176
+
177
+ if get_origin(output_type) is dict:
178
+ clean_args = [_clean_output_type(child) for child in get_args(output_type)]
179
+ return Dict[clean_args[0], clean_args[1]] # type: ignore[valid-type]
180
+
181
+ return output_type
182
+
183
+
184
+ def cast_to_output_type(result: Any, output_type: Any) -> Any:
185
+ clean_output_type = _clean_output_type(output_type)
186
+ DynamicModel = create_model("Output", output_type=(clean_output_type, ...))
187
+
188
+ try:
189
+ # mypy doesn't realize that this dynamic model has the output_type field defined above
190
+ return DynamicModel.model_validate({"output_type": result}).output_type # type: ignore[attr-defined]
191
+ except Exception:
192
+ output_type_name = _get_type_name(output_type)
193
+ result_type_name = _get_type_name(type(result))
194
+ raise NodeException(
195
+ code=WorkflowErrorCode.INVALID_OUTPUTS,
196
+ message=f"Expected an output of type '{output_type_name}', but received '{result_type_name}'",
197
+ )
@@ -204,13 +204,11 @@ class BaseOutputs(metaclass=_BaseOutputsMeta):
204
204
  if not isinstance(other, dict):
205
205
  return super().__eq__(other)
206
206
 
207
- outputs = {
208
- name: value for name, value in vars(self).items() if not name.startswith("_") and value is not undefined
209
- }
207
+ outputs = {ref.name: value for ref, value in self if value is not undefined}
210
208
  return outputs == other
211
209
 
212
210
  def __repr__(self) -> str:
213
- values = f"{', '.join(f'{k}={v}' for k, v in vars(self).items() if not k.startswith('_'))}"
211
+ values = f"{', '.join(f'{ref.name}={value}' for ref, value in self if value is not undefined)}"
214
212
  return f"{self.__class__.__name__}({values})"
215
213
 
216
214
  def __iter__(self) -> Iterator[Tuple[OutputReference, Any]]:
@@ -24,7 +24,6 @@ from typing import (
24
24
  get_args,
25
25
  )
26
26
 
27
- from vellum.workflows.context import get_execution_context
28
27
  from vellum.workflows.edges import Edge
29
28
  from vellum.workflows.emitters.base import BaseWorkflowEmitter
30
29
  from vellum.workflows.errors import WorkflowError, WorkflowErrorCode
@@ -481,7 +480,7 @@ class BaseWorkflow(Generic[InputsType, StateType], metaclass=_BaseWorkflowMeta):
481
480
  return self.get_inputs_class()()
482
481
 
483
482
  def get_default_state(self, workflow_inputs: Optional[InputsType] = None) -> StateType:
484
- execution_context = get_execution_context()
483
+ execution_context = self._execution_context
485
484
  return self.get_state_class()(
486
485
  meta=(
487
486
  StateMeta(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-ai
3
- Version: 0.14.10
3
+ Version: 0.14.12
4
4
  Summary:
5
5
  License: MIT
6
6
  Requires-Python: >=3.9,<4.0
@@ -32,7 +32,7 @@ Requires-Dist: pydantic (>=1.9.2)
32
32
  Requires-Dist: pydantic-core (>=2.18.2,<3.0.0)
33
33
  Requires-Dist: pydash (==7.0.6)
34
34
  Requires-Dist: python-dotenv (==1.0.1)
35
- Requires-Dist: pytz (==2022.6)
35
+ Requires-Dist: pytz (==2025.1)
36
36
  Requires-Dist: pyyaml (==6.0.1)
37
37
  Requires-Dist: requests (==2.32.3)
38
38
  Requires-Dist: tomli (==2.0.2)
@@ -3,7 +3,7 @@ vellum_cli/README.md,sha256=2NudRoLzWxNKqnuVy1JuQ7DerIaxWGYkrH8kMd-asIE,90
3
3
  vellum_cli/__init__.py,sha256=7aO9XFnaEVRiVshn86cFudebFUccT-gV8xIARJWqKYo,12257
4
4
  vellum_cli/aliased_group.py,sha256=ugW498j0yv4ALJ8vS9MsO7ctDW7Jlir9j6nE_uHAP8c,3363
5
5
  vellum_cli/config.py,sha256=aKnhvM5B8QdPA4cQC5Sqg7ImP-vNcVdSkZmk_OBpQTw,9309
6
- vellum_cli/image_push.py,sha256=SJwhwWJsLjwGNezNVd_oCVpFMfPsAB3dfLWmriZZUtw,4419
6
+ vellum_cli/image_push.py,sha256=4auU15Pb6c8DTGvT-AQ5HHXXrvIvEDs6L02d4OvJYI8,5199
7
7
  vellum_cli/init.py,sha256=WpnMXPItPmh0f0bBGIer3p-e5gu8DUGwSArT_FuoMEw,5093
8
8
  vellum_cli/logger.py,sha256=PuRFa0WCh4sAGFS5aqWB0QIYpS6nBWwPJrIXpWxugV4,1022
9
9
  vellum_cli/ping.py,sha256=lWyJw6sziXjyTopTYRdFF5hV-sYPVDdX0yVbG5fzcY4,585
@@ -44,13 +44,14 @@ vellum_ee/workflows/display/nodes/vellum/map_node.py,sha256=8CPnn06HIBxBOiECevUf
44
44
  vellum_ee/workflows/display/nodes/vellum/merge_node.py,sha256=HkNMgdQELiON42jdO-xDLmqrEKdGx1RVqrz2DXNTLS8,3239
45
45
  vellum_ee/workflows/display/nodes/vellum/note_node.py,sha256=TMb8txILu2uWjzoxaghjgjlzeBAgzn4vkP_8zSh2qoE,1151
46
46
  vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py,sha256=LFjLUrH6sJ4czPnExdRqFr0PB_yKBMLXLvK5GAzIAgc,3273
47
- vellum_ee/workflows/display/nodes/vellum/retry_node.py,sha256=hVqTtuHjlw_cYJ3ydNAvUHfGEoQi5YocVONZUo4i_Gs,1717
47
+ vellum_ee/workflows/display/nodes/vellum/retry_node.py,sha256=-eH41LUcpUeZ8Jt5f40-e7bn2nymeKnsYsmuprVSo7g,5805
48
48
  vellum_ee/workflows/display/nodes/vellum/search_node.py,sha256=TxcAGZDl_hvJ7Y1hUi9YVEVrj9Ie0hKkASdpfRL4_cs,9227
49
49
  vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py,sha256=62baAElKoRKIoba0lLhnrXGWWx96B73VxKGxh7BaIxc,2612
50
50
  vellum_ee/workflows/display/nodes/vellum/templating_node.py,sha256=JVIMPR3WpveOCWZubHKZkE04mavnTdb_9QY_r3XliRg,3424
51
51
  vellum_ee/workflows/display/nodes/vellum/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
52
  vellum_ee/workflows/display/nodes/vellum/tests/test_error_node.py,sha256=ulrpoYUW-5kIxfG4Lf5F2p0k_EoYKhmahEbF3P_eruM,1648
53
53
  vellum_ee/workflows/display/nodes/vellum/tests/test_prompt_node.py,sha256=bg9INsXiWfyK047u8TD1oEOFYrqDq8GC7Hvgz69n7BE,1988
54
+ vellum_ee/workflows/display/nodes/vellum/tests/test_retry_node.py,sha256=NuIw8Yb42KUdoGi3Ur8_7VPg50IC4hNrwAkCociwqNk,2091
54
55
  vellum_ee/workflows/display/nodes/vellum/tests/test_try_node.py,sha256=mtzB8LJlFCHVFM4H5AanLp29gQfaVmnN4A4iaRGJHoI,2427
55
56
  vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py,sha256=4YUaTeD_OWF-UaPMyOTBTu9skGC1jgSHlAYrzbH7Z04,5039
56
57
  vellum_ee/workflows/display/nodes/vellum/try_node.py,sha256=HBfGz4yt9GlmMW9JxzaCacPnHBDNIeXE8Jhqr9DqLLw,6191
@@ -60,7 +61,7 @@ vellum_ee/workflows/display/tests/test_vellum_workflow_display.py,sha256=cdpUoDN
60
61
  vellum_ee/workflows/display/tests/workflow_serialization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
61
62
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
62
63
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/conftest.py,sha256=A1-tIpC5KIKG9JA_rkd1nLS8zUG3Kb4QiVdvb3boFxE,2509
63
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py,sha256=9bIAEXXZQDqsUrDJqmHEeWYiZsYkVTQ4jBY-dPFVXEc,15054
64
+ vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py,sha256=jw8keqLNwqtaRSAqStsMqSH_OuuaTQm2MvIx8NTWAa4,14905
64
65
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attributes_serialization.py,sha256=1cszL6N6FNGVm61MOa7AEiHnF0QjZWqDQuPOp4yiG94,18277
65
66
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_outputs_serialization.py,sha256=-12ZkZb3f5gyoNASV2yeQtMo5HmNsVEo8nXwL6IC-I8,6261
66
67
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_ports_serialization.py,sha256=6th6kCwzql6lddjkTQx4Jbvvs4ChqtJwctW-B4QuBhI,37352
@@ -122,7 +123,7 @@ vellum/client/README.md,sha256=JkCJjmMZl4jrPj46pkmL9dpK4gSzQQmP5I7z4aME4LY,4749
122
123
  vellum/client/__init__.py,sha256=tKtdM1_GqmGq1gpi9ydWD_T-MM7fPn8QdHh8ww19cNI,117564
123
124
  vellum/client/core/__init__.py,sha256=SQ85PF84B9MuKnBwHNHWemSGuy-g_515gFYNFhvEE0I,1438
124
125
  vellum/client/core/api_error.py,sha256=RE8LELok2QCjABadECTvtDp7qejA1VmINCh6TbqPwSE,426
125
- vellum/client/core/client_wrapper.py,sha256=6EXwOd5Ka1mFWVlZmNuffO99dtW8DTAiRFes4vDlQFY,1869
126
+ vellum/client/core/client_wrapper.py,sha256=MP_Nnw3gVGA7BJKaD9TBDDwVxta-T1ASzBcOv7dF4nU,1869
126
127
  vellum/client/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
127
128
  vellum/client/core/file.py,sha256=X9IbmkZmB2bB_DpmZAO3crWdXagOakAyn6UCOCImCPg,2322
128
129
  vellum/client/core/http_client.py,sha256=R0pQpCppnEtxccGvXl4uJ76s7ro_65Fo_erlNNLp_AI,19228
@@ -1314,7 +1315,7 @@ vellum/workflows/events/__init__.py,sha256=6pxxceJo2dcaRkWtkDAYlUQZ-PHBQSZytIoyu
1314
1315
  vellum/workflows/events/node.py,sha256=uHT6If0esgZ3nLjrjmUPTKf3qbjGhoV_x5YKpjDBDcU,5280
1315
1316
  vellum/workflows/events/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1316
1317
  vellum/workflows/events/tests/test_event.py,sha256=uRfMwSOqU-ROeZKCEngGvvJYlOZuxBhnC3qH5AGi3fM,15399
1317
- vellum/workflows/events/types.py,sha256=cjRE8WL8tYCFradd9NOGl_H0mN3LiWWnA1uHmyT2Q0Q,3412
1318
+ vellum/workflows/events/types.py,sha256=AeTJaQt_fNHDLI4nyBzo7XrW9QQybRC09AKzu3kEYEE,3575
1318
1319
  vellum/workflows/events/workflow.py,sha256=sLO29djAeHGVd4hLhaNtOQ48uwUjfl-DotZQt06PxQA,6033
1319
1320
  vellum/workflows/exceptions.py,sha256=NiBiR3ggfmPxBVqD-H1SqmjI-7mIn0EStSN1BqApvCM,1213
1320
1321
  vellum/workflows/expressions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -1411,12 +1412,14 @@ vellum/workflows/nodes/displayable/code_execution_node/node.py,sha256=pdDrjI8wdq
1411
1412
  vellum/workflows/nodes/displayable/code_execution_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1412
1413
  vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1413
1414
  vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/main.py,sha256=5QsbmkzSlSbcbWTG_JmIqcP-JNJzOPTKxGzdHos19W4,79
1414
- vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py,sha256=OTnw5jgX90xWVB3vKryL5QSIr2YnGBz-W0q9C9LpNoc,22471
1415
- vellum/workflows/nodes/displayable/code_execution_node/utils.py,sha256=BQraIN4I3DCzXLEuBlRYCyp7ote7hQmnnKHu4jFHCCA,5174
1415
+ vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py,sha256=6_RMWedAWU8Zrl0HpQorQgDfjV9khnhSouZeKadClyI,23441
1416
+ vellum/workflows/nodes/displayable/code_execution_node/utils.py,sha256=-7YdoF-NO6sYRFLrdkd0n1FhQFiZbJBK5jFcLNghgVo,3635
1416
1417
  vellum/workflows/nodes/displayable/conditional_node/__init__.py,sha256=AS_EIqFdU1F9t8aLmbZU-rLh9ry6LCJ0uj0D8F0L5Uw,72
1417
1418
  vellum/workflows/nodes/displayable/conditional_node/node.py,sha256=Qjfl33gZ3JEgxBA1EgzSUebboGvsARthIxxcQyvx5Gg,1152
1418
1419
  vellum/workflows/nodes/displayable/final_output_node/__init__.py,sha256=G7VXM4OWpubvSJtVkGmMNeqgb9GkM7qZT838eL18XU4,72
1419
- vellum/workflows/nodes/displayable/final_output_node/node.py,sha256=TFbDZLGEtmg2cOQeJ56pUQdAkuHRa_qjBRIOGZU7Fy4,1990
1420
+ vellum/workflows/nodes/displayable/final_output_node/node.py,sha256=PuQ0RvtAmoSIZ5En_92tym7gpSMEoiHgwu20-UDbC7o,2368
1421
+ vellum/workflows/nodes/displayable/final_output_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1422
+ vellum/workflows/nodes/displayable/final_output_node/tests/test_node.py,sha256=E6LQ74qZjY4Xi4avx2qdOCgGhF8pEcNLBh8cqYRkzMI,709
1420
1423
  vellum/workflows/nodes/displayable/guardrail_node/__init__.py,sha256=Ab5eXmOoBhyV4dMWdzh32HLUmnPIBEK_zFCT38C4Fng,68
1421
1424
  vellum/workflows/nodes/displayable/guardrail_node/node.py,sha256=h5nIBzQxbXTrdTq1wjDcekk1RV4-rKUNCshqdBAiJJY,4025
1422
1425
  vellum/workflows/nodes/displayable/inline_prompt_node/__init__.py,sha256=gSUOoEZLlrx35-tQhSAd3An8WDwBqyiQh-sIebLU9wU,74
@@ -1436,9 +1439,9 @@ vellum/workflows/nodes/displayable/search_node/node.py,sha256=_VHHuTNN4icZBgc7O5
1436
1439
  vellum/workflows/nodes/displayable/search_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1437
1440
  vellum/workflows/nodes/displayable/search_node/tests/test_node.py,sha256=2-QCV7Vk_-YMls33p0GOUtCv3f2uPNZCjkB2CRjek7o,6562
1438
1441
  vellum/workflows/nodes/displayable/subworkflow_deployment_node/__init__.py,sha256=9yYM6001YZeqI1VOk1QuEM_yrffk_EdsO7qaPzINKds,92
1439
- vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py,sha256=epzcTald7En5vcIunCBXPT2t21FfR6V154rvGutkbLA,9596
1442
+ vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py,sha256=DGjvfusXCH5F98oPT3S6Xn0yedLuCq6EXb2DTzu2oDM,9661
1440
1443
  vellum/workflows/nodes/displayable/subworkflow_deployment_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1441
- vellum/workflows/nodes/displayable/subworkflow_deployment_node/tests/test_node.py,sha256=Eo66jE2eHGD94U0XEfhpvGNG9kFDOBADQ1_5-1EFmIc,11735
1444
+ vellum/workflows/nodes/displayable/subworkflow_deployment_node/tests/test_node.py,sha256=7Yt8rmN7xoZ91BrcA1pDGwG_t0NYtym4ORXrAIIX-P0,15861
1442
1445
  vellum/workflows/nodes/displayable/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1443
1446
  vellum/workflows/nodes/displayable/tests/test_inline_text_prompt_node.py,sha256=LaxohBcKfSW2PSiBBlx67FdW_q4YC2BM2ouH-vuGPAA,4700
1444
1447
  vellum/workflows/nodes/displayable/tests/test_search_node_wth_text_output.py,sha256=VepO5z1277c1y5N6LLIC31nnWD1aak2m5oPFplfJHHs,6935
@@ -1448,9 +1451,9 @@ vellum/workflows/nodes/experimental/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQ
1448
1451
  vellum/workflows/nodes/experimental/openai_chat_completion_node/__init__.py,sha256=lsyD9laR9p7kx5-BXGH2gUTM242UhKy8SMV0SR6S2iE,90
1449
1452
  vellum/workflows/nodes/experimental/openai_chat_completion_node/node.py,sha256=1EGeiaT-Zoo6pttQFKKBcdf3dmhAbjKGaErYD5FFwlc,10185
1450
1453
  vellum/workflows/nodes/mocks.py,sha256=gvM2tyoe-V84jFbFdhQsyGAPyQBzmjn_CkhT_yxccgY,499
1451
- vellum/workflows/nodes/utils.py,sha256=fq49n624n_nBMIFcc0JiNkHy3qna-bsA1HZRPa5AOJE,4918
1454
+ vellum/workflows/nodes/utils.py,sha256=uaTPGYp4utenz_QDghqQ23Q1iCsGHQ40nNZh1g9H9WI,7117
1452
1455
  vellum/workflows/outputs/__init__.py,sha256=AyZ4pRh_ACQIGvkf0byJO46EDnSix1ZCAXfvh-ms1QE,94
1453
- vellum/workflows/outputs/base.py,sha256=2MtTlyzePopMZDpBWQIV8HRV-km1-z0vI8Gm012q9OQ,8665
1456
+ vellum/workflows/outputs/base.py,sha256=W5KL9FaWfSbZuF7lOQ677giHO839Do-MXmuzkDuzPqk,8607
1454
1457
  vellum/workflows/ports/__init__.py,sha256=bZuMt-R7z5bKwpu4uPW7LlJeePOQWmCcDSXe5frUY5g,101
1455
1458
  vellum/workflows/ports/node_ports.py,sha256=g4A-8iUAvEJSkaWppbvzAR8XU02R9U-qLN4rP2Kq4Aw,2743
1456
1459
  vellum/workflows/ports/port.py,sha256=eI2SOZPZ5rsC3jMsxW6Rbn0NpaYQsrR7AapiIbbiy8Q,3635
@@ -1501,13 +1504,13 @@ vellum/workflows/utils/uuids.py,sha256=DFzPv9RCvsKhvdTEIQyfSek2A31D6S_QcmeLPbgrg
1501
1504
  vellum/workflows/utils/vellum_variables.py,sha256=fC2aSLvlS31D15dOWu43LBRR0QsgUKNXBiCUvvaLXSs,3231
1502
1505
  vellum/workflows/vellum_client.py,sha256=ODrq_TSl-drX2aezXegf7pizpWDVJuTXH-j6528t75s,683
1503
1506
  vellum/workflows/workflows/__init__.py,sha256=KY45TqvavCCvXIkyCFMEc0dc6jTMOUci93U2DUrlZYc,66
1504
- vellum/workflows/workflows/base.py,sha256=ggnrtDWSScVRuH3PEs2A81qXX0LngYuvS62SkDS2SOE,22724
1507
+ vellum/workflows/workflows/base.py,sha256=TSS2BHC8LAi-N5GdEa75BeChwzwTzL7yldFnTlLINro,22665
1505
1508
  vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnadGsrSZGa7t7LpJA,2008
1506
1509
  vellum/workflows/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1507
1510
  vellum/workflows/workflows/tests/test_base_workflow.py,sha256=NRteiICyJvDM5zrtUfq2fZoXcGQVaWC9xmNlLLVW0cU,7979
1508
1511
  vellum/workflows/workflows/tests/test_context.py,sha256=VJBUcyWVtMa_lE5KxdhgMu0WYNYnUQUDvTF7qm89hJ0,2333
1509
- vellum_ai-0.14.10.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1510
- vellum_ai-0.14.10.dist-info/METADATA,sha256=XNLaqqk85P8uRcr65-_IDfNPVi1dpNP9YvZgm43wAGs,5408
1511
- vellum_ai-0.14.10.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1512
- vellum_ai-0.14.10.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1513
- vellum_ai-0.14.10.dist-info/RECORD,,
1512
+ vellum_ai-0.14.12.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1513
+ vellum_ai-0.14.12.dist-info/METADATA,sha256=1EKavjMZSN9SKB-BwKDVMb4xfc-TnvoUWiYD7JMCqLk,5408
1514
+ vellum_ai-0.14.12.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1515
+ vellum_ai-0.14.12.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1516
+ vellum_ai-0.14.12.dist-info/RECORD,,
vellum_cli/image_push.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import json
2
2
  import logging
3
+ import re
3
4
  import subprocess
4
5
  from typing import List, Optional
5
6
 
@@ -24,9 +25,11 @@ def image_push_command(image: str, tags: Optional[List[str]] = None) -> None:
24
25
  # listing all of the architectures of the image instead of just the one that matches the machine. We can fall back
25
26
  # to using normal inspect which returns the machine image for this case though. And in the future we could figure
26
27
  # out how to call the docker host directly to do this.
28
+ logger.info("Pre-validating image...")
27
29
  docker_client = docker.from_env()
28
30
  check_architecture(docker_client, image, logger)
29
31
 
32
+ logger.info("Authenticating...")
30
33
  auth = vellum_client.container_images.docker_service_token()
31
34
 
32
35
  docker_client.login(
@@ -70,9 +73,29 @@ def image_push_command(image: str, tags: Optional[List[str]] = None) -> None:
70
73
  except Exception:
71
74
  continue
72
75
 
73
- logger.info("Updating Vellum metadata and enforcing the first law of robotics...")
74
- image_details = docker_client.api.inspect_image(image)
75
- sha = image_details["Id"]
76
+ result = subprocess.run(
77
+ ["docker", "inspect", "--format='{{index .RepoDigests 0}}'", image],
78
+ stdout=subprocess.PIPE,
79
+ stderr=subprocess.PIPE,
80
+ )
81
+
82
+ sha = ""
83
+ if result.returncode == 0:
84
+ match = re.search(r"sha256[^']*", result.stdout.decode("utf-8"))
85
+ if match and match.group(0):
86
+ sha = match.group(0)
87
+
88
+ if not sha:
89
+ # Fallback to using docker client if command line fails, at least on some systems
90
+ # this appears to give a bad sha.
91
+ logger.warning(
92
+ "Could not determine image hash with command line docker falling back to python docker client..."
93
+ )
94
+
95
+ image_details = docker_client.api.inspect_image(image)
96
+ sha = image_details["Id"]
97
+
98
+ logger.info(f"Updating Vellum metadata and validating image works in our system with image digest: {sha}...")
76
99
 
77
100
  vellum_client.container_images.push_container_image(
78
101
  name=image_name,
@@ -95,12 +118,12 @@ def check_architecture(docker_client: DockerClient, image: str, logger: logging.
95
118
  manifest = json.loads(result.stdout)
96
119
  architectures = [manifest_item["platform"]["architecture"] for manifest_item in manifest["manifests"]]
97
120
  except Exception:
98
- logger.warning("Error parsing manifest response")
121
+ logger.debug("Error parsing manifest response")
99
122
  manifest_parse_failed = True
100
123
 
101
124
  # Fall back to inspect image if we errored out using docker command line
102
125
  if result.returncode != 0 or manifest_parse_failed:
103
- logger.warning(f"Error inspecting manifest: {result.stderr.decode('utf-8').strip()}")
126
+ logger.debug(f"Error inspecting manifest: {result.stderr.decode('utf-8').strip()}")
104
127
  image_details = docker_client.api.inspect_image(image)
105
128
 
106
129
  if image_details["Architecture"] != _SUPPORTED_ARCHITECTURE:
@@ -1,11 +1,19 @@
1
1
  import inspect
2
- from typing import Any, Generic, TypeVar, cast
2
+ from typing import Any, Callable, Generic, Optional, Tuple, Type, TypeVar, cast
3
3
 
4
4
  from vellum.workflows.descriptors.base import BaseDescriptor
5
+ from vellum.workflows.errors.types import WorkflowErrorCode
6
+ from vellum.workflows.nodes.bases.base import BaseNode
5
7
  from vellum.workflows.nodes.core.retry_node.node import RetryNode
8
+ from vellum.workflows.nodes.utils import ADORNMENT_MODULE_NAME
9
+ from vellum.workflows.references.output import OutputReference
6
10
  from vellum.workflows.types.core import JsonArray, JsonObject
11
+ from vellum.workflows.types.utils import get_original_base
7
12
  from vellum.workflows.utils.uuids import uuid4_from_hash
8
13
  from vellum.workflows.workflows.base import BaseWorkflow
14
+ from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
15
+ from vellum_ee.workflows.display.nodes.get_node_display_class import get_node_display_class
16
+ from vellum_ee.workflows.display.nodes.types import NodeOutputDisplay
9
17
  from vellum_ee.workflows.display.nodes.vellum.base_adornment_node import BaseAdornmentNodeDisplay
10
18
  from vellum_ee.workflows.display.types import WorkflowDisplayContext
11
19
 
@@ -39,4 +47,86 @@ class BaseRetryNodeDisplay(BaseAdornmentNodeDisplay[_RetryNodeType], Generic[_Re
39
47
  "attributes": attributes,
40
48
  }
41
49
 
42
- return super().serialize(display_context, adornment=adornment)
50
+ serialized_node = super().serialize(
51
+ display_context,
52
+ adornment=adornment,
53
+ )
54
+
55
+ if serialized_node["type"] == "GENERIC":
56
+ return serialized_node
57
+
58
+ serialized_node_definition = serialized_node.get("definition")
59
+ if isinstance(serialized_node_definition, dict):
60
+ serialized_node_definition_module = serialized_node_definition.get("module")
61
+ if isinstance(serialized_node_definition_module, list):
62
+ serialized_node_definition_module.extend(
63
+ [
64
+ serialized_node_definition["name"],
65
+ ADORNMENT_MODULE_NAME,
66
+ ]
67
+ )
68
+ serialized_node_definition["name"] = node.__name__
69
+
70
+ return serialized_node
71
+
72
+ def get_node_output_display(self, output: OutputReference) -> Tuple[Type[BaseNode], NodeOutputDisplay]:
73
+ inner_node = self._node.__wrapped_node__
74
+ if not inner_node:
75
+ return super().get_node_output_display(output)
76
+
77
+ node_display_class = get_node_display_class(BaseNodeDisplay, inner_node)
78
+ node_display = node_display_class()
79
+
80
+ inner_output = getattr(inner_node.Outputs, output.name)
81
+ return node_display.get_node_output_display(inner_output)
82
+
83
+ @classmethod
84
+ def wrap(
85
+ cls,
86
+ max_attempts: int,
87
+ delay: Optional[float] = None,
88
+ retry_on_error_code: Optional[WorkflowErrorCode] = None,
89
+ retry_on_condition: Optional[BaseDescriptor] = None,
90
+ ) -> Callable[..., Type["BaseRetryNodeDisplay"]]:
91
+ _max_attempts = max_attempts
92
+ _delay = delay
93
+ _retry_on_error_code = retry_on_error_code
94
+ _retry_on_condition = retry_on_condition
95
+
96
+ NodeDisplayType = TypeVar("NodeDisplayType", bound=BaseNodeDisplay)
97
+
98
+ def decorator(inner_cls: Type[NodeDisplayType]) -> Type[NodeDisplayType]:
99
+ node_class = inner_cls.infer_node_class()
100
+ wrapped_node_class = cast(Type[BaseNode], node_class.__wrapped_node__)
101
+
102
+ class RetryNodeDisplay(BaseRetryNodeDisplay[node_class]): # type: ignore[valid-type]
103
+ max_attempts = _max_attempts
104
+ delay = _delay
105
+ retry_on_error_code = _retry_on_error_code
106
+ retry_on_condition = _retry_on_condition
107
+
108
+ setattr(inner_cls, "__adorned_by__", RetryNodeDisplay)
109
+
110
+ # We must edit the node display class to use __wrapped_node__ everywhere it
111
+ # references the adorned node class, which is three places:
112
+
113
+ # 1. The node display class' parameterized type
114
+ original_base_node_display = get_original_base(inner_cls)
115
+ original_base_node_display.__args__ = (wrapped_node_class,)
116
+ inner_cls._node_display_registry[wrapped_node_class] = inner_cls
117
+
118
+ # 2. The node display class' output displays
119
+ old_outputs = list(inner_cls.output_display.keys())
120
+ for old_output in old_outputs:
121
+ new_output = getattr(wrapped_node_class.Outputs, old_output.name)
122
+ inner_cls.output_display[new_output] = inner_cls.output_display.pop(old_output)
123
+
124
+ # 3. The node display class' port displays
125
+ old_ports = list(inner_cls.port_displays.keys())
126
+ for old_port in old_ports:
127
+ new_port = getattr(wrapped_node_class.Ports, old_port.name)
128
+ inner_cls.port_displays[new_port] = inner_cls.port_displays.pop(old_port)
129
+
130
+ return inner_cls
131
+
132
+ return decorator
@@ -0,0 +1,47 @@
1
+ from typing import Any, Dict, cast
2
+
3
+ from vellum.workflows import BaseWorkflow
4
+ from vellum.workflows.errors.types import WorkflowErrorCode
5
+ from vellum.workflows.nodes.bases.base import BaseNode
6
+ from vellum.workflows.nodes.core.retry_node.node import RetryNode
7
+ from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
8
+ from vellum_ee.workflows.display.workflows.vellum_workflow_display import VellumWorkflowDisplay
9
+
10
+
11
+ def test_retry_node_parameters():
12
+ """Test that RetryNode parameters are correctly serialized."""
13
+
14
+ # GIVEN a RetryNode with specific parameters
15
+ @RetryNode.wrap(max_attempts=5, delay=2.5, retry_on_error_code=WorkflowErrorCode.INVALID_INPUTS)
16
+ class MyRetryNode(BaseNode):
17
+ pass
18
+
19
+ # AND a workflow using the node
20
+ class MyWorkflow(BaseWorkflow):
21
+ graph = MyRetryNode
22
+
23
+ # WHEN we serialize the workflow
24
+ workflow_display = get_workflow_display(base_display_class=VellumWorkflowDisplay, workflow_class=MyWorkflow)
25
+ serialized_workflow = cast(Dict[str, Any], workflow_display.serialize())
26
+
27
+ # THEN the correct inputs should be serialized on the node
28
+ serialized_node = next(
29
+ node for node in serialized_workflow["workflow_raw_data"]["nodes"] if node["type"] != "ENTRYPOINT"
30
+ )
31
+
32
+ retry_adornment = next(
33
+ adornment for adornment in serialized_node["adornments"] if adornment["label"] == "RetryNode"
34
+ )
35
+
36
+ max_attempts_attribute = next(attr for attr in retry_adornment["attributes"] if attr["name"] == "max_attempts")
37
+ assert max_attempts_attribute["value"]["value"]["value"] == 5
38
+
39
+ delay_attribute = next(attr for attr in retry_adornment["attributes"] if attr["name"] == "delay")
40
+ assert delay_attribute["value"]["value"]["value"] == 2.5
41
+
42
+ retry_on_error_code_attribute = next(
43
+ attr for attr in retry_adornment["attributes"] if attr["name"] == "retry_on_error_code"
44
+ )
45
+
46
+ assert retry_on_error_code_attribute["value"]["value"]["type"] == "STRING"
47
+ assert retry_on_error_code_attribute["value"]["value"]["value"] == "INVALID_INPUTS"
@@ -29,11 +29,8 @@ class InnerRetryGenericNode(BaseNode):
29
29
  output: str
30
30
 
31
31
 
32
- class InnerRetryGenericNodeDisplay(BaseNodeDisplay[InnerRetryGenericNode.__wrapped_node__]): # type: ignore
33
- pass
34
-
35
-
36
- class OuterRetryNodeDisplay(BaseRetryNodeDisplay[InnerRetryGenericNode]): # type: ignore
32
+ @BaseRetryNodeDisplay.wrap(max_attempts=3)
33
+ class InnerRetryGenericNodeDisplay(BaseNodeDisplay[InnerRetryGenericNode]):
37
34
  pass
38
35
 
39
36
 
@@ -44,7 +41,6 @@ def test_serialize_node__retry(serialize_node):
44
41
  global_workflow_input_displays={Inputs.input: WorkflowInputsDisplay(id=input_id)},
45
42
  global_node_displays={
46
43
  InnerRetryGenericNode.__wrapped_node__: InnerRetryGenericNodeDisplay,
47
- InnerRetryGenericNode: OuterRetryNodeDisplay,
48
44
  },
49
45
  )
50
46