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.
- vellum/client/core/client_wrapper.py +1 -1
- vellum/workflows/events/types.py +6 -0
- vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py +44 -10
- vellum/workflows/nodes/displayable/code_execution_node/utils.py +10 -49
- vellum/workflows/nodes/displayable/final_output_node/node.py +13 -1
- vellum/workflows/nodes/displayable/final_output_node/tests/__init__.py +0 -0
- vellum/workflows/nodes/displayable/final_output_node/tests/test_node.py +20 -0
- vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +5 -4
- vellum/workflows/nodes/displayable/subworkflow_deployment_node/tests/test_node.py +111 -0
- vellum/workflows/nodes/utils.py +56 -2
- vellum/workflows/outputs/base.py +2 -4
- vellum/workflows/workflows/base.py +1 -2
- {vellum_ai-0.14.10.dist-info → vellum_ai-0.14.12.dist-info}/METADATA +2 -2
- {vellum_ai-0.14.10.dist-info → vellum_ai-0.14.12.dist-info}/RECORD +21 -18
- vellum_cli/image_push.py +28 -5
- vellum_ee/workflows/display/nodes/vellum/retry_node.py +92 -2
- vellum_ee/workflows/display/nodes/vellum/tests/test_retry_node.py +47 -0
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +2 -6
- {vellum_ai-0.14.10.dist-info → vellum_ai-0.14.12.dist-info}/LICENSE +0 -0
- {vellum_ai-0.14.10.dist-info → vellum_ai-0.14.12.dist-info}/WHEEL +0 -0
- {vellum_ai-0.14.10.dist-info → vellum_ai-0.14.12.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.
|
21
|
+
"X-Fern-SDK-Version": "0.14.12",
|
22
22
|
}
|
23
23
|
headers["X_API_KEY"] = self.api_key
|
24
24
|
return headers
|
vellum/workflows/events/types.py
CHANGED
@@ -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
|
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
|
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":
|
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
|
-
|
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
|
+
)
|
File without changes
|
@@ -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
|
74
|
-
isinstance(
|
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
|
|
vellum/workflows/nodes/utils.py
CHANGED
@@ -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
|
+
)
|
vellum/workflows/outputs/base.py
CHANGED
@@ -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'{
|
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 =
|
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.
|
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 (==
|
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=
|
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
|
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=
|
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=
|
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=
|
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=
|
1415
|
-
vellum/workflows/nodes/displayable/code_execution_node/utils.py,sha256
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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.
|
1510
|
-
vellum_ai-0.14.
|
1511
|
-
vellum_ai-0.14.
|
1512
|
-
vellum_ai-0.14.
|
1513
|
-
vellum_ai-0.14.
|
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
|
-
|
74
|
-
|
75
|
-
|
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.
|
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.
|
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
|
-
|
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
|
-
|
33
|
-
|
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
|
|
File without changes
|
File without changes
|
File without changes
|