vellum-ai 0.14.9__py3-none-any.whl → 0.14.11__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 (27) hide show
  1. vellum/client/core/client_wrapper.py +1 -1
  2. vellum/utils/templating/render.py +4 -1
  3. vellum/workflows/events/types.py +6 -0
  4. vellum/workflows/events/workflow.py +3 -2
  5. vellum/workflows/nodes/core/templating_node/node.py +0 -1
  6. vellum/workflows/nodes/core/templating_node/tests/test_templating_node.py +49 -0
  7. vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py +44 -10
  8. vellum/workflows/nodes/displayable/code_execution_node/utils.py +10 -49
  9. vellum/workflows/nodes/displayable/final_output_node/node.py +13 -1
  10. vellum/workflows/nodes/displayable/final_output_node/tests/__init__.py +0 -0
  11. vellum/workflows/nodes/displayable/final_output_node/tests/test_node.py +20 -0
  12. vellum/workflows/nodes/utils.py +70 -5
  13. vellum/workflows/outputs/base.py +2 -4
  14. vellum/workflows/runner/runner.py +1 -5
  15. vellum/workflows/workflows/base.py +1 -2
  16. {vellum_ai-0.14.9.dist-info → vellum_ai-0.14.11.dist-info}/METADATA +1 -1
  17. {vellum_ai-0.14.9.dist-info → vellum_ai-0.14.11.dist-info}/RECORD +27 -24
  18. vellum_cli/image_push.py +28 -5
  19. vellum_ee/workflows/display/nodes/vellum/retry_node.py +92 -2
  20. vellum_ee/workflows/display/nodes/vellum/tests/test_retry_node.py +47 -0
  21. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +2 -6
  22. vellum_ee/workflows/display/workflows/base_workflow_display.py +37 -13
  23. vellum_ee/workflows/display/workflows/tests/test_workflow_display.py +27 -0
  24. vellum_ee/workflows/tests/test_display_meta.py +2 -0
  25. {vellum_ai-0.14.9.dist-info → vellum_ai-0.14.11.dist-info}/LICENSE +0 -0
  26. {vellum_ai-0.14.9.dist-info → vellum_ai-0.14.11.dist-info}/WHEEL +0 -0
  27. {vellum_ai-0.14.9.dist-info → vellum_ai-0.14.11.dist-info}/entry_points.txt +0 -0
@@ -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.9",
21
+ "X-Fern-SDK-Version": "0.14.11",
22
22
  }
23
23
  headers["X_API_KEY"] = self.api_key
24
24
  return headers
@@ -2,6 +2,7 @@ import json
2
2
  from typing import Any, Dict, Optional
3
3
 
4
4
  from jinja2.sandbox import SandboxedEnvironment
5
+ from pydantic import BaseModel
5
6
 
6
7
  from vellum.utils.templating.constants import FilterFunc
7
8
  from vellum.utils.templating.exceptions import JinjaTemplateError
@@ -12,6 +13,9 @@ def finalize(obj: Any) -> str:
12
13
  if isinstance(obj, (dict, list)):
13
14
  return json.dumps(obj, cls=DefaultStateEncoder)
14
15
 
16
+ if isinstance(obj, BaseModel):
17
+ return json.dumps(obj.model_dump(), cls=DefaultStateEncoder)
18
+
15
19
  return str(obj)
16
20
 
17
21
 
@@ -23,7 +27,6 @@ def render_sandboxed_jinja_template(
23
27
  jinja_globals: Optional[Dict[str, Any]] = None,
24
28
  ) -> str:
25
29
  """Render a Jinja template within a sandboxed environment."""
26
-
27
30
  try:
28
31
  environment = SandboxedEnvironment(
29
32
  keep_trailing_newline=True,
@@ -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
  ]
@@ -39,14 +39,15 @@ class _BaseWorkflowEvent(BaseEvent):
39
39
  return self.body.workflow_definition
40
40
 
41
41
 
42
- class NodeDisplay(UniversalBaseModel):
42
+ class NodeEventDisplayContext(UniversalBaseModel):
43
43
  input_display: Dict[str, UUID]
44
44
  output_display: Dict[str, UUID]
45
45
  port_display: Dict[str, UUID]
46
+ subworkflow_display: Optional["WorkflowEventDisplayContext"] = None
46
47
 
47
48
 
48
49
  class WorkflowEventDisplayContext(UniversalBaseModel):
49
- node_displays: Dict[str, NodeDisplay]
50
+ node_displays: Dict[str, NodeEventDisplayContext]
50
51
  workflow_inputs: Dict[str, UUID]
51
52
  workflow_outputs: Dict[str, UUID]
52
53
 
@@ -82,7 +82,6 @@ class TemplatingNode(BaseNode[StateType], Generic[StateType, _OutputType], metac
82
82
  def run(self) -> Outputs:
83
83
  rendered_template = self._render_template()
84
84
  result = self._cast_rendered_template(rendered_template)
85
-
86
85
  return self.Outputs(result=result)
87
86
 
88
87
  def _render_template(self) -> str:
@@ -234,3 +234,52 @@ def test_templating_node__last_chat_message():
234
234
 
235
235
  # THEN the output is the expected JSON
236
236
  assert outputs.result == [ChatMessage(role="USER", text="Hello")]
237
+
238
+
239
+ def test_templating_node__function_call_value_input():
240
+ # GIVEN a templating node that receives a FunctionCallVellumValue
241
+ class FunctionCallTemplateNode(TemplatingNode[BaseState, FunctionCall]):
242
+ template = """{{ function_call }}"""
243
+ inputs = {
244
+ "function_call": FunctionCallVellumValue(
245
+ type="FUNCTION_CALL",
246
+ value=FunctionCall(name="test_function", arguments={"key": "value"}, id="test_id", state="FULFILLED"),
247
+ )
248
+ }
249
+
250
+ # WHEN the node is run
251
+ node = FunctionCallTemplateNode()
252
+ outputs = node.run()
253
+
254
+ # THEN the output is the expected function call
255
+ assert outputs.result == FunctionCall(
256
+ name="test_function", arguments={"key": "value"}, id="test_id", state="FULFILLED"
257
+ )
258
+
259
+
260
+ def test_templating_node__function_call_as_json():
261
+ # GIVEN a node that receives a FunctionCallVellumValue but outputs as Json
262
+ class JsonOutputNode(TemplatingNode[BaseState, Json]):
263
+ template = """{{ function_call }}"""
264
+ inputs = {
265
+ "function_call": FunctionCallVellumValue(
266
+ type="FUNCTION_CALL",
267
+ value=FunctionCall(name="test_function", arguments={"key": "value"}, id="test_id", state="FULFILLED"),
268
+ )
269
+ }
270
+
271
+ # WHEN the node is run
272
+ node = JsonOutputNode()
273
+ outputs = node.run()
274
+
275
+ # THEN we get just the FunctionCall data as JSON
276
+ assert outputs.result == {
277
+ "name": "test_function",
278
+ "arguments": {"key": "value"},
279
+ "id": "test_id",
280
+ "state": "FULFILLED",
281
+ }
282
+
283
+ # AND we can access fields directly
284
+ assert outputs.result["arguments"] == {"key": "value"}
285
+ assert outputs.result["name"] == "test_function"
@@ -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'"
@@ -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
@@ -109,7 +111,11 @@ def parse_type_from_str(result_as_str: str, output_type: Any) -> Any:
109
111
 
110
112
  if output_type is Json:
111
113
  try:
112
- return json.loads(result_as_str)
114
+ data = json.loads(result_as_str)
115
+ # If we got a FunctionCallVellumValue, return just the value
116
+ if isinstance(data, dict) and data.get("type") == "FUNCTION_CALL" and "value" in data:
117
+ return data["value"]
118
+ return data
113
119
  except json.JSONDecodeError:
114
120
  raise ValueError("Invalid JSON format for result_as_str")
115
121
 
@@ -124,9 +130,68 @@ def parse_type_from_str(result_as_str: str, output_type: Any) -> Any:
124
130
  if issubclass(output_type, BaseModel):
125
131
  try:
126
132
  data = json.loads(result_as_str)
133
+ # If we got a FunctionCallVellumValue extract FunctionCall,
134
+ if (
135
+ hasattr(output_type, "__name__")
136
+ and output_type.__name__ == "FunctionCall"
137
+ and isinstance(data, dict)
138
+ and "value" in data
139
+ ):
140
+ data = data["value"]
141
+ return output_type.model_validate(data)
127
142
  except json.JSONDecodeError:
128
143
  raise ValueError("Invalid JSON format for result_as_str")
129
144
 
130
- return output_type.model_validate(data)
131
-
132
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]]:
@@ -328,11 +328,7 @@ class WorkflowRunner(Generic[StateType]):
328
328
  node_definition=node.__class__,
329
329
  error=e.error,
330
330
  ),
331
- parent=WorkflowParentContext(
332
- span_id=span_id,
333
- workflow_definition=self.workflow.__class__,
334
- parent=self._parent_context,
335
- ),
331
+ parent=parent_context,
336
332
  )
337
333
  )
338
334
  except Exception as e:
@@ -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.9
3
+ Version: 0.14.11
4
4
  Summary:
5
5
  License: MIT
6
6
  Requires-Python: >=3.9,<4.0
@@ -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
@@ -89,9 +90,9 @@ vellum_ee/workflows/display/utils/expressions.py,sha256=9FpOslDI-RCR5m4TgAu9KCHh
89
90
  vellum_ee/workflows/display/utils/vellum.py,sha256=UjK_RxnSEmlIu9klGCPWU5RAQBmgZ7cRbRdgxaTbubE,8081
90
91
  vellum_ee/workflows/display/vellum.py,sha256=7mqQaKZPPrLMcXSAQkPIxCy5x8HkKs5PbCu3GRaC2o8,8507
91
92
  vellum_ee/workflows/display/workflows/__init__.py,sha256=kapXsC67VJcgSuiBMa86FdePG5A9kMB5Pi4Uy1O2ob4,207
92
- vellum_ee/workflows/display/workflows/base_workflow_display.py,sha256=p9Lt1AypyC0Y6WHOUCsk4lYmlgODwg5oz-IDo0UA-1o,18678
93
+ vellum_ee/workflows/display/workflows/base_workflow_display.py,sha256=Z9s_rtqAH5IfBtXOw40gbiT0GZeN6JRdFwGgPT-DIYM,20143
93
94
  vellum_ee/workflows/display/workflows/get_vellum_workflow_display_class.py,sha256=kp0u8LN_2IwshLrhMImhpZx1hRyAcD5gXY-kDuuaGMQ,1269
94
- vellum_ee/workflows/display/workflows/tests/test_workflow_display.py,sha256=_yB3-u7_bWdD4lUBWpRdWztJmJL-DXkkZaw9Vy9HH6g,3245
95
+ vellum_ee/workflows/display/workflows/tests/test_workflow_display.py,sha256=STVSG0eL97mdnwBA5nOOgW8AuK8k-b8kWDyHKatNXIA,4259
95
96
  vellum_ee/workflows/display/workflows/vellum_workflow_display.py,sha256=mbAzCpswOek34ITeTkesbVreCXpulj4NFjIg3RcdVZ8,18243
96
97
  vellum_ee/workflows/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
97
98
  vellum_ee/workflows/server/virtual_file_loader.py,sha256=X_DdNK7MfyOjKWekk6YQpOSCT6klKcdjT6nVJcBH1sM,1481
@@ -114,7 +115,7 @@ vellum_ee/workflows/tests/local_workflow/nodes/__init__.py,sha256=1F6jxUpSKfPXPj
114
115
  vellum_ee/workflows/tests/local_workflow/nodes/final_output.py,sha256=ZX7zBv87zirg0w9VKUW3QVDSdBLDqcqAMZjCL_oWbpU,297
115
116
  vellum_ee/workflows/tests/local_workflow/nodes/templating_node.py,sha256=NQwFN61QkHfI3Vssz-B0NKGfupK8PU0FDSAIAhYBLi0,325
116
117
  vellum_ee/workflows/tests/local_workflow/workflow.py,sha256=A4qOzOPNwePYxWbcAgIPLsmrVS_aVEZEc-wULSv787Q,393
117
- vellum_ee/workflows/tests/test_display_meta.py,sha256=pzdqND4KLWs7EUIbpXuqgso7BIRpoUsO3T_bgeENs0Q,2205
118
+ vellum_ee/workflows/tests/test_display_meta.py,sha256=xLQ7QtIXIiIm61pm2lyl588Ohzc3pjyq1nDp-qVu2Fs,2295
118
119
  vellum_ee/workflows/tests/test_server.py,sha256=M6vvQ2hjIpDWtQdDM9EPbMvUrZ93niAuYnxMNJWOjPA,511
119
120
  vellum_ee/workflows/tests/test_virtual_files.py,sha256=TJEcMR0v2S8CkloXNmCHA0QW0K6pYNGaIjraJz7sFvY,2762
120
121
  vellum/__init__.py,sha256=a_aM1_A04XGma4MAIDNeBF9BKzWbiQaVVMRzImHuxjA,36438
@@ -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=RhAfDORGTXyVqWFHTHUIONrGJRy7OCD049LPC0pwN_k,1868
126
+ vellum/client/core/client_wrapper.py,sha256=l2ugjFyqT9acEpOr6jNbb_P6uA6RL_Y5IP-FTsOd_uQ,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
@@ -1287,7 +1288,7 @@ vellum/utils/templating/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
1287
1288
  vellum/utils/templating/constants.py,sha256=8OHMO6WFAEimbIaiHc5gy6s91D7_KvW-vTlEMWwvl_M,711
1288
1289
  vellum/utils/templating/custom_filters.py,sha256=XVHriwazejRZmxB_eg4xHgCxl7AiQQ2sx-hRLMmylfU,885
1289
1290
  vellum/utils/templating/exceptions.py,sha256=cDp140PP4OnInW4qAvg3KqiSiF70C71UyEAKRBR1Abo,46
1290
- vellum/utils/templating/render.py,sha256=5OsD1e9fks1aysYTyPKjYGaloYUbIKWpajcxtjtiFuU,2037
1291
+ vellum/utils/templating/render.py,sha256=P2t9qU4w_WdXAVLM5Nj3bc1-XlIKOkwK-czQ80pHBag,2172
1291
1292
  vellum/utils/templating/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1292
1293
  vellum/utils/templating/tests/test_custom_filters.py,sha256=mkJwc7t1gE13SKgPxhF-lN_m2XGCkphCB9Te81dGekI,532
1293
1294
  vellum/utils/typing.py,sha256=wx_daFqD69cYkuJTVnvNrpjhqC3uuhbnyJ9_bIwC9OU,327
@@ -1314,8 +1315,8 @@ 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/workflow.py,sha256=XtmGG7NRp0TQ4memOJPcaNOs7qMUBbd4WKSZVlxWrCk,5937
1318
+ vellum/workflows/events/types.py,sha256=AeTJaQt_fNHDLI4nyBzo7XrW9QQybRC09AKzu3kEYEE,3575
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
1321
1322
  vellum/workflows/expressions/accessor.py,sha256=ItZF7fMLzVTqsdAiaXb5SiDupXmX0X9xbIus1W6hRds,1870
@@ -1381,8 +1382,8 @@ vellum/workflows/nodes/core/retry_node/node.py,sha256=Vt3fx4G-DRIb9a-IHIUfaAclgf
1381
1382
  vellum/workflows/nodes/core/retry_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1382
1383
  vellum/workflows/nodes/core/retry_node/tests/test_node.py,sha256=RM_OHwxrHwyxvlQQBJPqVBxpedFuWQ9h2-Xa3kP75sc,4399
1383
1384
  vellum/workflows/nodes/core/templating_node/__init__.py,sha256=GmyuYo81_A1_Bz6id69ozVFS6FKiuDsZTiA3I6MaL2U,70
1384
- vellum/workflows/nodes/core/templating_node/node.py,sha256=Vqlg4L-5XNuIdbZKQe-GEYqTIV7iXNjLO7QIRgz4ujc,3722
1385
- vellum/workflows/nodes/core/templating_node/tests/test_templating_node.py,sha256=nY2P6r7cW85k7NEKXUFNeDTMWlz8ZEZyMY2Sg-0qO_E,7327
1385
+ vellum/workflows/nodes/core/templating_node/node.py,sha256=-JIqLUv6Xpx_LTVZt7whQ2X2VatgHDdTxjMrz64luEs,3721
1386
+ vellum/workflows/nodes/core/templating_node/tests/test_templating_node.py,sha256=ldnmSASx0TfAnT3ZvU0AXtN0diZGrfySiXipuJIIzWU,9055
1386
1387
  vellum/workflows/nodes/core/try_node/__init__.py,sha256=JVD4DrldTIqFQQFrubs9KtWCCc0YCAc7Fzol5ZWIWeM,56
1387
1388
  vellum/workflows/nodes/core/try_node/node.py,sha256=_0df2_6kim8pW4hB7IXUJOYS4fLduaeGDH4rRhYYvcg,4212
1388
1389
  vellum/workflows/nodes/core/try_node/tests/__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
@@ -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=YRj3qIz6--N6CuDiKalsWpBmUR6z7WcRhqtLxoenDZE,4354
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
@@ -1470,7 +1473,7 @@ vellum/workflows/references/workflow_input.py,sha256=86IuhlBz-9cGxeUzizyjdp482aj
1470
1473
  vellum/workflows/resolvers/__init__.py,sha256=eH6hTvZO4IciDaf_cf7aM2vs-DkBDyJPycOQevJxQnI,82
1471
1474
  vellum/workflows/resolvers/base.py,sha256=WHra9LRtlTuB1jmuNqkfVE2JUgB61Cyntn8f0b0WZg4,411
1472
1475
  vellum/workflows/runner/__init__.py,sha256=i1iG5sAhtpdsrlvwgH6B-m49JsINkiWyPWs8vyT-bqM,72
1473
- vellum/workflows/runner/runner.py,sha256=VUGw-QlUNyfwRWjXgBZ1VqKRuYdFC7YdtAmQcsgR6I0,31206
1476
+ vellum/workflows/runner/runner.py,sha256=_p19T1woplSxZGabZuSUFBKSYBrXADrM7b_1YnWVN3g,31013
1474
1477
  vellum/workflows/sandbox.py,sha256=GVJzVjMuYzOBnSrboB0_6MMRZWBluAyQ2o7syeaeBd0,2235
1475
1478
  vellum/workflows/state/__init__.py,sha256=yUUdR-_Vl7UiixNDYQZ-GEM_kJI9dnOia75TtuNEsnE,60
1476
1479
  vellum/workflows/state/base.py,sha256=Vkhneko3VlQrPsMLU1PYSzXU_W1u7_AraJsghiv5O-4,15512
@@ -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.9.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1510
- vellum_ai-0.14.9.dist-info/METADATA,sha256=JO_Tqa4ayXrL4ytBbrfiXfly8yXeiPyQc-4NOoL9kDk,5407
1511
- vellum_ai-0.14.9.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1512
- vellum_ai-0.14.9.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1513
- vellum_ai-0.14.9.dist-info/RECORD,,
1512
+ vellum_ai-0.14.11.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1513
+ vellum_ai-0.14.11.dist-info/METADATA,sha256=584bKGEOfx24lR-N0_BdsuLUo5G-nDYN2-hXxKWGCNg,5408
1514
+ vellum_ai-0.14.11.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1515
+ vellum_ai-0.14.11.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1516
+ vellum_ai-0.14.11.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
 
@@ -9,7 +9,7 @@ from typing import Any, Dict, Generic, Iterator, List, Optional, Tuple, Type, Un
9
9
  from vellum.workflows import BaseWorkflow
10
10
  from vellum.workflows.descriptors.base import BaseDescriptor
11
11
  from vellum.workflows.edges import Edge
12
- from vellum.workflows.events.workflow import NodeDisplay, WorkflowEventDisplayContext
12
+ from vellum.workflows.events.workflow import NodeEventDisplayContext, WorkflowEventDisplayContext
13
13
  from vellum.workflows.expressions.coalesce_expression import CoalesceExpression
14
14
  from vellum.workflows.nodes.bases import BaseNode
15
15
  from vellum.workflows.nodes.utils import get_wrapped_node
@@ -34,7 +34,9 @@ from vellum_ee.workflows.display.base import (
34
34
  from vellum_ee.workflows.display.nodes.base_node_vellum_display import BaseNodeVellumDisplay
35
35
  from vellum_ee.workflows.display.nodes.get_node_display_class import get_node_display_class
36
36
  from vellum_ee.workflows.display.nodes.types import NodeOutputDisplay, PortDisplay, PortDisplayOverrides
37
+ from vellum_ee.workflows.display.nodes.utils import raise_if_descriptor
37
38
  from vellum_ee.workflows.display.types import NodeDisplayType, WorkflowDisplayContext
39
+ from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
38
40
 
39
41
  logger = logging.getLogger(__name__)
40
42
 
@@ -383,10 +385,15 @@ class BaseWorkflowDisplay(
383
385
  except ModuleNotFoundError:
384
386
  return None
385
387
 
386
- display_context = display_class.WorkflowDisplay(workflow_class).display_context
387
- if not isinstance(display_context, WorkflowDisplayContext):
388
+ workflow_display = display_class.WorkflowDisplay(workflow_class)
389
+ if not isinstance(workflow_display, BaseWorkflowDisplay):
388
390
  return None
389
391
 
392
+ return workflow_display.get_event_display_context()
393
+
394
+ def get_event_display_context(self):
395
+ display_context = self.display_context
396
+
390
397
  workflow_outputs = {
391
398
  output.name: display_context.workflow_output_displays[output].id
392
399
  for output in display_context.workflow_output_displays
@@ -398,27 +405,44 @@ class BaseWorkflowDisplay(
398
405
  node_displays = {
399
406
  str(node.__id__): display_context.node_displays[node] for node in display_context.node_displays
400
407
  }
401
- temp_node_displays = {}
402
- for node in node_displays:
403
- current_node = node_displays[node]
408
+ node_event_displays = {}
409
+ for node_id in node_displays:
410
+ current_node_display = node_displays[node_id]
404
411
  input_display = {}
405
- if isinstance(current_node, BaseNodeVellumDisplay):
406
- input_display = current_node.node_input_ids_by_name
412
+ if isinstance(current_node_display, BaseNodeVellumDisplay):
413
+ input_display = current_node_display.node_input_ids_by_name
407
414
  node_display_meta = {
408
- output.name: current_node.output_display[output].id for output in current_node.output_display
415
+ output.name: current_node_display.output_display[output].id
416
+ for output in current_node_display.output_display
409
417
  }
410
- port_display_meta = {port.name: current_node.port_displays[port].id for port in current_node.port_displays}
411
-
412
- temp_node_displays[node] = NodeDisplay(
418
+ port_display_meta = {
419
+ port.name: current_node_display.port_displays[port].id for port in current_node_display.port_displays
420
+ }
421
+ node = current_node_display._node
422
+ subworkflow_display_context: Optional[WorkflowEventDisplayContext] = None
423
+ if hasattr(node, "subworkflow"):
424
+ # All nodes that have a subworkflow attribute are currently expected to call them `subworkflow`
425
+ # This will change if in the future we decide to support multiple subworkflows on a single node
426
+ subworkflow_attribute = raise_if_descriptor(getattr(node, "subworkflow"))
427
+ if issubclass(subworkflow_attribute, BaseWorkflow):
428
+ subworkflow_display = get_workflow_display(
429
+ base_display_class=display_context.workflow_display_class,
430
+ workflow_class=subworkflow_attribute,
431
+ parent_display_context=display_context,
432
+ )
433
+ subworkflow_display_context = subworkflow_display.get_event_display_context()
434
+
435
+ node_event_displays[node_id] = NodeEventDisplayContext(
413
436
  input_display=input_display,
414
437
  output_display=node_display_meta,
415
438
  port_display=port_display_meta,
439
+ subworkflow_display=subworkflow_display_context,
416
440
  )
417
441
 
418
442
  display_meta = WorkflowEventDisplayContext(
419
443
  workflow_outputs=workflow_outputs,
420
444
  workflow_inputs=workflow_inputs,
421
- node_displays=temp_node_displays,
445
+ node_displays=node_event_displays,
422
446
  )
423
447
  return display_meta
424
448
 
@@ -1,6 +1,7 @@
1
1
  import pytest
2
2
 
3
3
  from vellum.workflows.nodes.bases.base import BaseNode
4
+ from vellum.workflows.nodes.core.inline_subworkflow_node.node import InlineSubworkflowNode
4
5
  from vellum.workflows.workflows.base import BaseWorkflow
5
6
  from vellum_ee.workflows.display.nodes import BaseNodeDisplay
6
7
  from vellum_ee.workflows.display.vellum import NodeDisplayData, NodeDisplayPosition
@@ -92,3 +93,29 @@ def test_serialize_workflow__node_display_class_not_registered():
92
93
 
93
94
  # THEN it should should succeed
94
95
  assert data is not None
96
+
97
+
98
+ def test_get_event_display_context__node_display_to_include_subworkflow_display():
99
+ # GIVEN a simple workflow
100
+ class InnerNode(BaseNode):
101
+ pass
102
+
103
+ class Subworkflow(BaseWorkflow):
104
+ graph = InnerNode
105
+
106
+ # AND a workflow that includes the subworkflow
107
+ class SubworkflowNode(InlineSubworkflowNode):
108
+ subworkflow = Subworkflow
109
+
110
+ class MyWorkflow(BaseWorkflow):
111
+ graph = SubworkflowNode
112
+
113
+ # WHEN we gather the event display context
114
+ display_context = VellumWorkflowDisplay(MyWorkflow).get_event_display_context()
115
+
116
+ # THEN the subworkflow display should be included
117
+ assert str(SubworkflowNode.__id__) in display_context.node_displays
118
+ node_event_display = display_context.node_displays[str(SubworkflowNode.__id__)]
119
+
120
+ assert node_event_display.subworkflow_display is not None
121
+ assert str(InnerNode.__id__) in node_event_display.subworkflow_display.node_displays
@@ -40,11 +40,13 @@ def test_base_class_dynamic_import(files):
40
40
  },
41
41
  "output_display": {"result": UUID("423bc529-1a1a-4f72-af4d-cbdb5f0a5929")},
42
42
  "port_display": {"default": UUID("afda9a19-0618-42e1-9b63-5d0db2a88f62")},
43
+ "subworkflow_display": None,
43
44
  },
44
45
  "f3ef4b2b-fec9-4026-9cc6-e5eac295307f": {
45
46
  "input_display": {"node_input": UUID("fe6cba85-2423-4b5e-8f85-06311a8be5fb")},
46
47
  "output_display": {"value": UUID("5469b810-6ea6-4362-9e79-e360d44a1405")},
47
48
  "port_display": {},
49
+ "subworkflow_display": None,
48
50
  },
49
51
  },
50
52
  "workflow_inputs": {"input_value": UUID("2268a996-bd17-4832-b3ff-f5662d54b306")},