vellum-ai 0.11.9__py3-none-any.whl → 0.12.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- vellum/client/core/client_wrapper.py +1 -1
- vellum/workflows/descriptors/base.py +2 -2
- vellum/workflows/descriptors/tests/test_utils.py +4 -4
- vellum/workflows/errors/__init__.py +3 -3
- vellum/workflows/errors/types.py +46 -3
- vellum/workflows/events/node.py +3 -3
- vellum/workflows/events/tests/test_event.py +3 -3
- vellum/workflows/events/workflow.py +3 -3
- vellum/workflows/exceptions.py +8 -4
- vellum/workflows/nodes/bases/base.py +9 -2
- vellum/workflows/nodes/bases/tests/test_base_node.py +13 -0
- vellum/workflows/nodes/core/error_node/node.py +9 -5
- vellum/workflows/nodes/core/inline_subworkflow_node/node.py +3 -13
- vellum/workflows/nodes/core/map_node/node.py +2 -2
- vellum/workflows/nodes/core/retry_node/node.py +5 -5
- vellum/workflows/nodes/core/retry_node/tests/test_node.py +6 -6
- vellum/workflows/nodes/core/templating_node/node.py +2 -2
- vellum/workflows/nodes/core/try_node/node.py +7 -7
- vellum/workflows/nodes/core/try_node/tests/test_node.py +9 -7
- vellum/workflows/nodes/displayable/bases/api_node/node.py +3 -3
- vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py +4 -12
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +2 -2
- vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py +2 -2
- vellum/workflows/nodes/displayable/bases/search_node.py +3 -3
- vellum/workflows/nodes/displayable/code_execution_node/node.py +21 -5
- vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py +141 -0
- vellum/workflows/nodes/displayable/guardrail_node/node.py +3 -3
- vellum/workflows/nodes/displayable/inline_prompt_node/node.py +3 -3
- vellum/workflows/nodes/displayable/prompt_deployment_node/node.py +3 -3
- vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +7 -14
- vellum/workflows/nodes/displayable/tests/test_inline_text_prompt_node.py +4 -4
- vellum/workflows/nodes/utils.py +5 -9
- vellum/workflows/references/external_input.py +2 -2
- vellum/workflows/references/node.py +2 -2
- vellum/workflows/references/state_value.py +2 -2
- vellum/workflows/references/workflow_input.py +2 -2
- vellum/workflows/runner/runner.py +15 -15
- {vellum_ee/workflows/display → vellum/workflows}/utils/tests/test_uuids.py +1 -1
- vellum/workflows/workflows/base.py +7 -7
- {vellum_ai-0.11.9.dist-info → vellum_ai-0.12.0.dist-info}/METADATA +1 -1
- {vellum_ai-0.11.9.dist-info → vellum_ai-0.12.0.dist-info}/RECORD +61 -62
- {vellum_ai-0.11.9.dist-info → vellum_ai-0.12.0.dist-info}/WHEEL +1 -1
- vellum_ee/workflows/display/nodes/base_node_display.py +50 -21
- vellum_ee/workflows/display/nodes/base_node_vellum_display.py +10 -1
- vellum_ee/workflows/display/nodes/get_node_display_class.py +10 -1
- vellum_ee/workflows/display/nodes/tests/test_base_node_display.py +5 -4
- vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +13 -3
- vellum_ee/workflows/display/nodes/vellum/conditional_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/final_output_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/merge_node.py +4 -4
- vellum_ee/workflows/display/nodes/vellum/search_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py +5 -1
- vellum_ee/workflows/display/nodes/vellum/try_node.py +12 -6
- vellum_ee/workflows/display/nodes/vellum/utils.py +1 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +269 -85
- vellum_ee/workflows/display/workflows/base_workflow_display.py +2 -2
- vellum_ee/workflows/display/workflows/vellum_workflow_display.py +2 -6
- vellum_ee/workflows/display/utils/tests/__init__.py +0 -0
- {vellum_ee/workflows/display → vellum/workflows}/utils/uuids.py +0 -0
- {vellum_ai-0.11.9.dist-info → vellum_ai-0.12.0.dist-info}/LICENSE +0 -0
- {vellum_ai-0.11.9.dist-info → vellum_ai-0.12.0.dist-info}/entry_points.txt +0 -0
@@ -17,7 +17,7 @@ class BaseClientWrapper:
|
|
17
17
|
headers: typing.Dict[str, str] = {
|
18
18
|
"X-Fern-Language": "Python",
|
19
19
|
"X-Fern-SDK-Name": "vellum-ai",
|
20
|
-
"X-Fern-SDK-Version": "0.
|
20
|
+
"X-Fern-SDK-Version": "0.12.0",
|
21
21
|
}
|
22
22
|
headers["X_API_KEY"] = self.api_key
|
23
23
|
return headers
|
@@ -258,12 +258,12 @@ class BaseDescriptor(Generic[_T]):
|
|
258
258
|
|
259
259
|
return DoesNotEndWithExpression(lhs=self, rhs=other)
|
260
260
|
|
261
|
-
def
|
261
|
+
def is_null(self) -> "IsNullExpression":
|
262
262
|
from vellum.workflows.expressions.is_null import IsNullExpression
|
263
263
|
|
264
264
|
return IsNullExpression(expression=self)
|
265
265
|
|
266
|
-
def
|
266
|
+
def is_not_null(self) -> "IsNotNullExpression":
|
267
267
|
from vellum.workflows.expressions.is_not_null import IsNotNullExpression
|
268
268
|
|
269
269
|
return IsNotNullExpression(expression=self)
|
@@ -35,8 +35,8 @@ class FixtureState(BaseState):
|
|
35
35
|
(FixtureState.gamma.does_not_contain(FixtureState.delta), False),
|
36
36
|
(FixtureState.gamma.does_not_begin_with(FixtureState.delta), True),
|
37
37
|
(FixtureState.gamma.does_not_end_with(FixtureState.delta), True),
|
38
|
-
(FixtureState.alpha.
|
39
|
-
(FixtureState.alpha.
|
38
|
+
(FixtureState.alpha.is_null(), False),
|
39
|
+
(FixtureState.alpha.is_not_null(), True),
|
40
40
|
(FixtureState.delta.in_(FixtureState.gamma), True),
|
41
41
|
(FixtureState.delta.not_in(FixtureState.gamma), False),
|
42
42
|
(FixtureState.alpha.between(FixtureState.beta, FixtureState.epsilon), False),
|
@@ -66,8 +66,8 @@ class FixtureState(BaseState):
|
|
66
66
|
"does_not_contain",
|
67
67
|
"does_not_begin_with",
|
68
68
|
"does_not_end_with",
|
69
|
-
"
|
70
|
-
"
|
69
|
+
"is_null",
|
70
|
+
"is_not_null",
|
71
71
|
"in_",
|
72
72
|
"not_in",
|
73
73
|
"between",
|
vellum/workflows/errors/types.py
CHANGED
@@ -1,20 +1,63 @@
|
|
1
1
|
from dataclasses import dataclass
|
2
2
|
from enum import Enum
|
3
|
+
from typing import Dict
|
3
4
|
|
5
|
+
from vellum.client.types.vellum_error import VellumError
|
6
|
+
from vellum.client.types.vellum_error_code_enum import VellumErrorCodeEnum
|
7
|
+
from vellum.client.types.workflow_event_error import WorkflowEventError
|
8
|
+
from vellum.client.types.workflow_execution_event_error_code import WorkflowExecutionEventErrorCode
|
4
9
|
|
5
|
-
|
10
|
+
|
11
|
+
class WorkflowErrorCode(Enum):
|
6
12
|
INVALID_WORKFLOW = "INVALID_WORKFLOW"
|
7
13
|
INVALID_INPUTS = "INVALID_INPUTS"
|
8
14
|
INVALID_OUTPUTS = "INVALID_OUTPUTS"
|
9
15
|
INVALID_STATE = "INVALID_STATE"
|
10
16
|
INVALID_TEMPLATE = "INVALID_TEMPLATE"
|
11
17
|
INTERNAL_ERROR = "INTERNAL_ERROR"
|
18
|
+
NODE_EXECUTION = "NODE_EXECUTION"
|
12
19
|
PROVIDER_ERROR = "PROVIDER_ERROR"
|
13
20
|
USER_DEFINED_ERROR = "USER_DEFINED_ERROR"
|
14
21
|
WORKFLOW_CANCELLED = "WORKFLOW_CANCELLED"
|
15
22
|
|
16
23
|
|
17
24
|
@dataclass(frozen=True)
|
18
|
-
class
|
25
|
+
class WorkflowError:
|
19
26
|
message: str
|
20
|
-
code:
|
27
|
+
code: WorkflowErrorCode
|
28
|
+
|
29
|
+
|
30
|
+
_VELLUM_ERROR_CODE_TO_WORKFLOW_ERROR_CODE: Dict[VellumErrorCodeEnum, WorkflowErrorCode] = {
|
31
|
+
"INVALID_REQUEST": WorkflowErrorCode.INVALID_INPUTS,
|
32
|
+
"INVALID_INPUTS": WorkflowErrorCode.INVALID_INPUTS,
|
33
|
+
"PROVIDER_ERROR": WorkflowErrorCode.PROVIDER_ERROR,
|
34
|
+
"REQUEST_TIMEOUT": WorkflowErrorCode.PROVIDER_ERROR,
|
35
|
+
"INTERNAL_SERVER_ERROR": WorkflowErrorCode.INTERNAL_ERROR,
|
36
|
+
"USER_DEFINED_ERROR": WorkflowErrorCode.USER_DEFINED_ERROR,
|
37
|
+
}
|
38
|
+
|
39
|
+
|
40
|
+
def vellum_error_to_workflow_error(error: VellumError) -> WorkflowError:
|
41
|
+
return WorkflowError(
|
42
|
+
message=error.message,
|
43
|
+
code=_VELLUM_ERROR_CODE_TO_WORKFLOW_ERROR_CODE.get(error.code, WorkflowErrorCode.INTERNAL_ERROR),
|
44
|
+
)
|
45
|
+
|
46
|
+
|
47
|
+
_WORKFLOW_EVENT_ERROR_CODE_TO_WORKFLOW_ERROR_CODE: Dict[WorkflowExecutionEventErrorCode, WorkflowErrorCode] = {
|
48
|
+
"WORKFLOW_INITIALIZATION": WorkflowErrorCode.INVALID_WORKFLOW,
|
49
|
+
"WORKFLOW_CANCELLED": WorkflowErrorCode.WORKFLOW_CANCELLED,
|
50
|
+
"NODE_EXECUTION_COUNT_LIMIT_REACHED": WorkflowErrorCode.INVALID_STATE,
|
51
|
+
"INTERNAL_SERVER_ERROR": WorkflowErrorCode.INTERNAL_ERROR,
|
52
|
+
"NODE_EXECUTION": WorkflowErrorCode.NODE_EXECUTION,
|
53
|
+
"LLM_PROVIDER": WorkflowErrorCode.PROVIDER_ERROR,
|
54
|
+
"INVALID_TEMPLATE": WorkflowErrorCode.INVALID_TEMPLATE,
|
55
|
+
"USER_DEFINED_ERROR": WorkflowErrorCode.USER_DEFINED_ERROR,
|
56
|
+
}
|
57
|
+
|
58
|
+
|
59
|
+
def workflow_event_error_to_workflow_error(error: WorkflowEventError) -> WorkflowError:
|
60
|
+
return WorkflowError(
|
61
|
+
message=error.message,
|
62
|
+
code=_WORKFLOW_EVENT_ERROR_CODE_TO_WORKFLOW_ERROR_CODE.get(error.code, WorkflowErrorCode.INTERNAL_ERROR),
|
63
|
+
)
|
vellum/workflows/events/node.py
CHANGED
@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Any, Dict, Generic, List, Literal, Optional, S
|
|
3
3
|
from pydantic import SerializerFunctionWrapHandler, field_serializer, model_serializer
|
4
4
|
|
5
5
|
from vellum.core.pydantic_utilities import UniversalBaseModel
|
6
|
-
from vellum.workflows.errors import
|
6
|
+
from vellum.workflows.errors import WorkflowError
|
7
7
|
from vellum.workflows.expressions.accessor import AccessorExpression
|
8
8
|
from vellum.workflows.outputs.base import BaseOutput
|
9
9
|
from vellum.workflows.ports.port import Port
|
@@ -119,7 +119,7 @@ class NodeExecutionFulfilledEvent(_BaseNodeEvent, Generic[OutputsType]):
|
|
119
119
|
|
120
120
|
|
121
121
|
class NodeExecutionRejectedBody(_BaseNodeExecutionBody):
|
122
|
-
error:
|
122
|
+
error: WorkflowError
|
123
123
|
|
124
124
|
|
125
125
|
class NodeExecutionRejectedEvent(_BaseNodeEvent):
|
@@ -127,7 +127,7 @@ class NodeExecutionRejectedEvent(_BaseNodeEvent):
|
|
127
127
|
body: NodeExecutionRejectedBody
|
128
128
|
|
129
129
|
@property
|
130
|
-
def error(self) ->
|
130
|
+
def error(self) -> WorkflowError:
|
131
131
|
return self.body.error
|
132
132
|
|
133
133
|
|
@@ -4,7 +4,7 @@ from uuid import UUID
|
|
4
4
|
|
5
5
|
from deepdiff import DeepDiff
|
6
6
|
|
7
|
-
from vellum.workflows.errors.types import
|
7
|
+
from vellum.workflows.errors.types import WorkflowError, WorkflowErrorCode
|
8
8
|
from vellum.workflows.events.node import (
|
9
9
|
NodeExecutionFulfilledBody,
|
10
10
|
NodeExecutionFulfilledEvent,
|
@@ -213,9 +213,9 @@ module_root = name_parts[: name_parts.index("events")]
|
|
213
213
|
span_id=UUID("123e4567-e89b-12d3-a456-426614174000"),
|
214
214
|
body=WorkflowExecutionRejectedBody(
|
215
215
|
workflow_definition=MockWorkflow,
|
216
|
-
error=
|
216
|
+
error=WorkflowError(
|
217
217
|
message="Workflow failed",
|
218
|
-
code=
|
218
|
+
code=WorkflowErrorCode.USER_DEFINED_ERROR,
|
219
219
|
),
|
220
220
|
),
|
221
221
|
),
|
@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Any, Dict, Generator, Generic, Iterable, Liter
|
|
3
3
|
from pydantic import field_serializer
|
4
4
|
|
5
5
|
from vellum.core.pydantic_utilities import UniversalBaseModel
|
6
|
-
from vellum.workflows.errors import
|
6
|
+
from vellum.workflows.errors import WorkflowError
|
7
7
|
from vellum.workflows.outputs.base import BaseOutput
|
8
8
|
from vellum.workflows.references import ExternalInputReference
|
9
9
|
from vellum.workflows.types.generics import OutputsType, StateType, WorkflowInputsType
|
@@ -90,7 +90,7 @@ class WorkflowExecutionFulfilledEvent(_BaseWorkflowEvent, Generic[OutputsType]):
|
|
90
90
|
|
91
91
|
|
92
92
|
class WorkflowExecutionRejectedBody(_BaseWorkflowExecutionBody):
|
93
|
-
error:
|
93
|
+
error: WorkflowError
|
94
94
|
|
95
95
|
|
96
96
|
class WorkflowExecutionRejectedEvent(_BaseWorkflowEvent):
|
@@ -98,7 +98,7 @@ class WorkflowExecutionRejectedEvent(_BaseWorkflowEvent):
|
|
98
98
|
body: WorkflowExecutionRejectedBody
|
99
99
|
|
100
100
|
@property
|
101
|
-
def error(self) ->
|
101
|
+
def error(self) -> WorkflowError:
|
102
102
|
return self.body.error
|
103
103
|
|
104
104
|
|
vellum/workflows/exceptions.py
CHANGED
@@ -1,15 +1,19 @@
|
|
1
|
-
from vellum.workflows.errors import
|
1
|
+
from vellum.workflows.errors import WorkflowError, WorkflowErrorCode
|
2
2
|
|
3
3
|
|
4
4
|
class NodeException(Exception):
|
5
|
-
def __init__(self, message: str, code:
|
5
|
+
def __init__(self, message: str, code: WorkflowErrorCode):
|
6
6
|
self.message = message
|
7
7
|
self.code = code
|
8
8
|
super().__init__(message)
|
9
9
|
|
10
10
|
@property
|
11
|
-
def error(self) ->
|
12
|
-
return
|
11
|
+
def error(self) -> WorkflowError:
|
12
|
+
return WorkflowError(
|
13
13
|
message=self.message,
|
14
14
|
code=self.code,
|
15
15
|
)
|
16
|
+
|
17
|
+
@staticmethod
|
18
|
+
def of(workflow_error: WorkflowError) -> "NodeException":
|
19
|
+
return NodeException(message=workflow_error.message, code=workflow_error.code)
|
@@ -7,7 +7,7 @@ from typing import Any, Dict, Generic, Iterator, Optional, Set, Tuple, Type, Typ
|
|
7
7
|
from vellum.workflows.constants import UNDEF
|
8
8
|
from vellum.workflows.descriptors.base import BaseDescriptor
|
9
9
|
from vellum.workflows.descriptors.utils import is_unresolved, resolve_value
|
10
|
-
from vellum.workflows.errors.types import
|
10
|
+
from vellum.workflows.errors.types import WorkflowErrorCode
|
11
11
|
from vellum.workflows.exceptions import NodeException
|
12
12
|
from vellum.workflows.graph import Graph
|
13
13
|
from vellum.workflows.graph.graph import GraphTarget
|
@@ -23,6 +23,7 @@ from vellum.workflows.state.context import WorkflowContext
|
|
23
23
|
from vellum.workflows.types.core import MergeBehavior
|
24
24
|
from vellum.workflows.types.generics import StateType
|
25
25
|
from vellum.workflows.types.utils import get_class_attr_names, get_original_base, infer_types
|
26
|
+
from vellum.workflows.utils.uuids import uuid4_from_hash
|
26
27
|
|
27
28
|
|
28
29
|
def is_nested_class(nested: Any, parent: Type) -> bool:
|
@@ -99,6 +100,7 @@ class BaseNodeMeta(type):
|
|
99
100
|
node_class.Execution.node_class = node_class
|
100
101
|
node_class.Trigger.node_class = node_class
|
101
102
|
node_class.ExternalInputs.__parent_class__ = node_class
|
103
|
+
node_class.__id__ = uuid4_from_hash(node_class.__qualname__)
|
102
104
|
return node_class
|
103
105
|
|
104
106
|
@property
|
@@ -213,6 +215,7 @@ class _BaseNodeExecutionMeta(type):
|
|
213
215
|
|
214
216
|
|
215
217
|
class BaseNode(Generic[StateType], metaclass=BaseNodeMeta):
|
218
|
+
__id__: UUID = uuid4_from_hash(__qualname__)
|
216
219
|
state: StateType
|
217
220
|
_context: WorkflowContext
|
218
221
|
_inputs: MappingProxyType[NodeReference, Any]
|
@@ -284,7 +287,7 @@ class BaseNode(Generic[StateType], metaclass=BaseNodeMeta):
|
|
284
287
|
|
285
288
|
raise NodeException(
|
286
289
|
message="Invalid Trigger Node Specification",
|
287
|
-
code=
|
290
|
+
code=WorkflowErrorCode.INVALID_INPUTS,
|
288
291
|
)
|
289
292
|
|
290
293
|
class Execution(metaclass=_BaseNodeExecutionMeta):
|
@@ -352,3 +355,7 @@ class BaseNode(Generic[StateType], metaclass=BaseNodeMeta):
|
|
352
355
|
|
353
356
|
def __repr__(self) -> str:
|
354
357
|
return str(self.__class__)
|
358
|
+
|
359
|
+
|
360
|
+
class MyNode2(BaseNode):
|
361
|
+
pass
|
@@ -1,3 +1,4 @@
|
|
1
|
+
from uuid import UUID
|
1
2
|
from typing import Optional
|
2
3
|
|
3
4
|
from vellum.core.pydantic_utilities import UniversalBaseModel
|
@@ -122,3 +123,15 @@ def test_base_node__node_resolution__coalesce_constants():
|
|
122
123
|
|
123
124
|
# THEN the node is initialized with the correct data
|
124
125
|
assert node.value == "world"
|
126
|
+
|
127
|
+
|
128
|
+
def test_base_node__default_id():
|
129
|
+
# GIVEN a node
|
130
|
+
class MyNode(BaseNode):
|
131
|
+
pass
|
132
|
+
|
133
|
+
# WHEN the node is accessed
|
134
|
+
my_id = MyNode.__id__
|
135
|
+
|
136
|
+
# THEN it should equal the hash of `test_base_node__default_id.<locals>.MyNode`
|
137
|
+
assert my_id == UUID("8e71bea7-ce68-492f-9abe-477c788e6273")
|
@@ -1,6 +1,7 @@
|
|
1
1
|
from typing import ClassVar, Union
|
2
2
|
|
3
|
-
from vellum.
|
3
|
+
from vellum.client.types.vellum_error import VellumError
|
4
|
+
from vellum.workflows.errors.types import WorkflowError, WorkflowErrorCode, vellum_error_to_workflow_error
|
4
5
|
from vellum.workflows.exceptions import NodeException
|
5
6
|
from vellum.workflows.nodes.bases.base import BaseNode
|
6
7
|
|
@@ -12,15 +13,18 @@ class ErrorNode(BaseNode):
|
|
12
13
|
error: Union[str, VellumError] - The error to raise.
|
13
14
|
"""
|
14
15
|
|
15
|
-
error: ClassVar[Union[str, VellumError]]
|
16
|
+
error: ClassVar[Union[str, WorkflowError, VellumError]]
|
16
17
|
|
17
18
|
def run(self) -> BaseNode.Outputs:
|
18
19
|
if isinstance(self.error, str):
|
19
|
-
raise NodeException(message=self.error, code=
|
20
|
-
elif isinstance(self.error,
|
20
|
+
raise NodeException(message=self.error, code=WorkflowErrorCode.USER_DEFINED_ERROR)
|
21
|
+
elif isinstance(self.error, WorkflowError):
|
21
22
|
raise NodeException(message=self.error.message, code=self.error.code)
|
23
|
+
elif isinstance(self.error, VellumError):
|
24
|
+
workflow_error = vellum_error_to_workflow_error(self.error)
|
25
|
+
raise NodeException(message=workflow_error.message, code=workflow_error.code)
|
22
26
|
else:
|
23
27
|
raise NodeException(
|
24
28
|
message=f"Error node received an unexpected input type: {self.error.__class__}",
|
25
|
-
code=
|
29
|
+
code=WorkflowErrorCode.INVALID_INPUTS,
|
26
30
|
)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
from typing import TYPE_CHECKING, Generic, Iterator, Optional, Set, Type, TypeVar
|
2
2
|
|
3
3
|
from vellum.workflows.context import execution_context, get_parent_context
|
4
|
-
from vellum.workflows.errors.types import
|
4
|
+
from vellum.workflows.errors.types import WorkflowErrorCode
|
5
5
|
from vellum.workflows.exceptions import NodeException
|
6
6
|
from vellum.workflows.nodes.bases.base_subworkflow_node import BaseSubworkflowNode
|
7
7
|
from vellum.workflows.outputs.base import BaseOutput, BaseOutputs
|
@@ -49,22 +49,12 @@ class InlineSubworkflowNode(BaseSubworkflowNode[StateType], Generic[StateType, W
|
|
49
49
|
elif event.name == "workflow.execution.fulfilled":
|
50
50
|
outputs = event.outputs
|
51
51
|
elif event.name == "workflow.execution.rejected":
|
52
|
-
|
53
|
-
if error.code in VellumErrorCode._value2member_map_:
|
54
|
-
raise NodeException(
|
55
|
-
message=error.message,
|
56
|
-
code=VellumErrorCode(error.code),
|
57
|
-
)
|
58
|
-
else:
|
59
|
-
raise NodeException(
|
60
|
-
message=error.message,
|
61
|
-
code=VellumErrorCode.INTERNAL_ERROR,
|
62
|
-
)
|
52
|
+
raise NodeException.of(event.error)
|
63
53
|
|
64
54
|
if outputs is None:
|
65
55
|
raise NodeException(
|
66
56
|
message="Expected to receive outputs from Workflow Deployment",
|
67
|
-
code=
|
57
|
+
code=WorkflowErrorCode.INVALID_OUTPUTS,
|
68
58
|
)
|
69
59
|
|
70
60
|
# For any outputs somehow in our final fulfilled outputs array,
|
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Callable, Dict, Generic, List, Optional, Tuple
|
|
5
5
|
|
6
6
|
from vellum.workflows.context import execution_context, get_parent_context
|
7
7
|
from vellum.workflows.descriptors.base import BaseDescriptor
|
8
|
-
from vellum.workflows.errors.types import
|
8
|
+
from vellum.workflows.errors.types import WorkflowErrorCode
|
9
9
|
from vellum.workflows.events.types import ParentContext
|
10
10
|
from vellum.workflows.exceptions import NodeException
|
11
11
|
from vellum.workflows.inputs.base import BaseInputs
|
@@ -88,7 +88,7 @@ class MapNode(BaseNode, Generic[StateType, MapNodeItemType]):
|
|
88
88
|
break
|
89
89
|
elif terminal_event.name == "workflow.execution.paused":
|
90
90
|
raise NodeException(
|
91
|
-
code=
|
91
|
+
code=WorkflowErrorCode.INVALID_OUTPUTS,
|
92
92
|
message=f"Subworkflow unexpectedly paused on iteration {index}",
|
93
93
|
)
|
94
94
|
elif terminal_event.name == "workflow.execution.rejected":
|
@@ -1,6 +1,6 @@
|
|
1
1
|
from typing import TYPE_CHECKING, Any, Callable, Dict, Generic, Optional, Type
|
2
2
|
|
3
|
-
from vellum.workflows.errors.types import
|
3
|
+
from vellum.workflows.errors.types import WorkflowErrorCode
|
4
4
|
from vellum.workflows.exceptions import NodeException
|
5
5
|
from vellum.workflows.inputs.base import BaseInputs
|
6
6
|
from vellum.workflows.nodes.bases import BaseNode
|
@@ -31,7 +31,7 @@ class RetryNode(BaseNode[StateType], Generic[StateType], metaclass=_RetryNodeMet
|
|
31
31
|
"""
|
32
32
|
|
33
33
|
max_attempts: int
|
34
|
-
retry_on_error_code: Optional[
|
34
|
+
retry_on_error_code: Optional[WorkflowErrorCode] = None
|
35
35
|
subworkflow: Type["BaseWorkflow[SubworkflowInputs, BaseState]"]
|
36
36
|
|
37
37
|
class SubworkflowInputs(BaseInputs):
|
@@ -58,13 +58,13 @@ class RetryNode(BaseNode[StateType], Generic[StateType], metaclass=_RetryNodeMet
|
|
58
58
|
return node_outputs
|
59
59
|
elif terminal_event.name == "workflow.execution.paused":
|
60
60
|
last_exception = NodeException(
|
61
|
-
code=
|
61
|
+
code=WorkflowErrorCode.INVALID_OUTPUTS,
|
62
62
|
message=f"Subworkflow unexpectedly paused on attempt {attempt_number}",
|
63
63
|
)
|
64
64
|
break
|
65
65
|
elif self.retry_on_error_code and self.retry_on_error_code != terminal_event.error.code:
|
66
66
|
last_exception = NodeException(
|
67
|
-
code=
|
67
|
+
code=WorkflowErrorCode.INVALID_OUTPUTS,
|
68
68
|
message=f"""Unexpected rejection on attempt {attempt_number}: {terminal_event.error.code.value}.
|
69
69
|
Message: {terminal_event.error.message}""",
|
70
70
|
)
|
@@ -76,7 +76,7 @@ Message: {terminal_event.error.message}""",
|
|
76
76
|
|
77
77
|
@classmethod
|
78
78
|
def wrap(
|
79
|
-
cls, max_attempts: int, retry_on_error_code: Optional[
|
79
|
+
cls, max_attempts: int, retry_on_error_code: Optional[WorkflowErrorCode] = None
|
80
80
|
) -> Callable[..., Type["RetryNode"]]:
|
81
81
|
_max_attempts = max_attempts
|
82
82
|
_retry_on_error_code = retry_on_error_code
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import pytest
|
2
2
|
|
3
|
-
from vellum.workflows.errors.types import
|
3
|
+
from vellum.workflows.errors.types import WorkflowErrorCode
|
4
4
|
from vellum.workflows.exceptions import NodeException
|
5
5
|
from vellum.workflows.inputs.base import BaseInputs
|
6
6
|
from vellum.workflows.nodes.bases import BaseNode
|
@@ -11,7 +11,7 @@ from vellum.workflows.state.base import BaseState, StateMeta
|
|
11
11
|
|
12
12
|
def test_retry_node__retry_on_error_code__successfully_retried():
|
13
13
|
# GIVEN a retry node that is configured to retry on PROVIDER_ERROR
|
14
|
-
@RetryNode.wrap(max_attempts=3, retry_on_error_code=
|
14
|
+
@RetryNode.wrap(max_attempts=3, retry_on_error_code=WorkflowErrorCode.PROVIDER_ERROR)
|
15
15
|
class TestNode(BaseNode):
|
16
16
|
attempt_number = RetryNode.SubworkflowInputs.attempt_number
|
17
17
|
|
@@ -20,7 +20,7 @@ def test_retry_node__retry_on_error_code__successfully_retried():
|
|
20
20
|
|
21
21
|
def run(self) -> Outputs:
|
22
22
|
if self.attempt_number < 3:
|
23
|
-
raise NodeException(message="This will be retried", code=
|
23
|
+
raise NodeException(message="This will be retried", code=WorkflowErrorCode.PROVIDER_ERROR)
|
24
24
|
|
25
25
|
return self.Outputs(execution_count=self.attempt_number)
|
26
26
|
|
@@ -34,7 +34,7 @@ def test_retry_node__retry_on_error_code__successfully_retried():
|
|
34
34
|
|
35
35
|
def test_retry_node__retry_on_error_code__missed():
|
36
36
|
# GIVEN a retry node that is configured to retry on PROVIDER_ERROR
|
37
|
-
@RetryNode.wrap(max_attempts=3, retry_on_error_code=
|
37
|
+
@RetryNode.wrap(max_attempts=3, retry_on_error_code=WorkflowErrorCode.PROVIDER_ERROR)
|
38
38
|
class TestNode(BaseNode):
|
39
39
|
attempt_number = RetryNode.SubworkflowInputs.attempt_number
|
40
40
|
|
@@ -57,7 +57,7 @@ def test_retry_node__retry_on_error_code__missed():
|
|
57
57
|
exc_info.value.message
|
58
58
|
== "Unexpected rejection on attempt 1: INTERNAL_ERROR.\nMessage: This will not be retried"
|
59
59
|
)
|
60
|
-
assert exc_info.value.code ==
|
60
|
+
assert exc_info.value.code == WorkflowErrorCode.INVALID_OUTPUTS
|
61
61
|
|
62
62
|
|
63
63
|
def test_retry_node__use_parent_inputs_and_state():
|
@@ -69,7 +69,7 @@ def test_retry_node__use_parent_inputs_and_state():
|
|
69
69
|
bar: str
|
70
70
|
|
71
71
|
# AND a retry node that uses the parent's inputs and state
|
72
|
-
@RetryNode.wrap(max_attempts=3, retry_on_error_code=
|
72
|
+
@RetryNode.wrap(max_attempts=3, retry_on_error_code=WorkflowErrorCode.PROVIDER_ERROR)
|
73
73
|
class TestNode(BaseNode):
|
74
74
|
foo = Inputs.foo
|
75
75
|
bar = State.bar
|
@@ -10,7 +10,7 @@ import pydash
|
|
10
10
|
import pytz
|
11
11
|
import yaml
|
12
12
|
|
13
|
-
from vellum.workflows.errors import
|
13
|
+
from vellum.workflows.errors import WorkflowErrorCode
|
14
14
|
from vellum.workflows.exceptions import NodeException
|
15
15
|
from vellum.workflows.nodes.bases import BaseNode
|
16
16
|
from vellum.workflows.nodes.bases.base import BaseNodeMeta
|
@@ -130,4 +130,4 @@ class TemplatingNode(BaseNode[StateType], Generic[StateType, _OutputType], metac
|
|
130
130
|
jinja_globals=self.jinja_globals,
|
131
131
|
)
|
132
132
|
except JinjaTemplateError as e:
|
133
|
-
raise NodeException(message=str(e), code=
|
133
|
+
raise NodeException(message=str(e), code=WorkflowErrorCode.INVALID_TEMPLATE)
|
@@ -2,7 +2,7 @@ import sys
|
|
2
2
|
from types import ModuleType
|
3
3
|
from typing import TYPE_CHECKING, Any, Callable, Dict, Generic, Iterator, Optional, Set, Tuple, Type, TypeVar
|
4
4
|
|
5
|
-
from vellum.workflows.errors.types import
|
5
|
+
from vellum.workflows.errors.types import WorkflowError, WorkflowErrorCode
|
6
6
|
from vellum.workflows.exceptions import NodeException
|
7
7
|
from vellum.workflows.nodes.bases import BaseNode
|
8
8
|
from vellum.workflows.nodes.bases.base import BaseNodeMeta
|
@@ -64,11 +64,11 @@ class TryNode(BaseNode[StateType], Generic[StateType], metaclass=_TryNodeMeta):
|
|
64
64
|
"""
|
65
65
|
|
66
66
|
__wrapped_node__: Optional[Type["BaseNode"]] = None
|
67
|
-
on_error_code: Optional[
|
67
|
+
on_error_code: Optional[WorkflowErrorCode] = None
|
68
68
|
subworkflow: Type["BaseWorkflow"]
|
69
69
|
|
70
70
|
class Outputs(BaseNode.Outputs):
|
71
|
-
error: Optional[
|
71
|
+
error: Optional[WorkflowError] = None
|
72
72
|
|
73
73
|
def run(self) -> Iterator[BaseOutput]:
|
74
74
|
subworkflow = self.subworkflow(
|
@@ -98,13 +98,13 @@ class TryNode(BaseNode[StateType], Generic[StateType], metaclass=_TryNodeMeta):
|
|
98
98
|
outputs = event.outputs
|
99
99
|
elif event.name == "workflow.execution.paused":
|
100
100
|
exception = NodeException(
|
101
|
-
code=
|
101
|
+
code=WorkflowErrorCode.INVALID_OUTPUTS,
|
102
102
|
message="Subworkflow unexpectedly paused within Try Node",
|
103
103
|
)
|
104
104
|
elif event.name == "workflow.execution.rejected":
|
105
105
|
if self.on_error_code and self.on_error_code != event.error.code:
|
106
106
|
exception = NodeException(
|
107
|
-
code=
|
107
|
+
code=WorkflowErrorCode.INVALID_OUTPUTS,
|
108
108
|
message=f"""Unexpected rejection: {event.error.code.value}.
|
109
109
|
Message: {event.error.message}""",
|
110
110
|
)
|
@@ -116,7 +116,7 @@ Message: {event.error.message}""",
|
|
116
116
|
|
117
117
|
if outputs is None:
|
118
118
|
raise NodeException(
|
119
|
-
code=
|
119
|
+
code=WorkflowErrorCode.INVALID_OUTPUTS,
|
120
120
|
message="Expected to receive outputs from Try Node's subworkflow",
|
121
121
|
)
|
122
122
|
|
@@ -130,7 +130,7 @@ Message: {event.error.message}""",
|
|
130
130
|
)
|
131
131
|
|
132
132
|
@classmethod
|
133
|
-
def wrap(cls, on_error_code: Optional[
|
133
|
+
def wrap(cls, on_error_code: Optional[WorkflowErrorCode] = None) -> Callable[..., Type["TryNode"]]:
|
134
134
|
_on_error_code = on_error_code
|
135
135
|
|
136
136
|
def decorator(inner_cls: Type[BaseNode]) -> Type["TryNode"]:
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import pytest
|
2
2
|
|
3
3
|
from vellum.client import Vellum
|
4
|
-
from vellum.workflows.errors.types import
|
4
|
+
from vellum.workflows.errors.types import WorkflowError, WorkflowErrorCode
|
5
5
|
from vellum.workflows.exceptions import NodeException
|
6
6
|
from vellum.workflows.inputs.base import BaseInputs
|
7
7
|
from vellum.workflows.nodes.bases import BaseNode
|
@@ -14,13 +14,13 @@ from vellum.workflows.state.context import WorkflowContext
|
|
14
14
|
|
15
15
|
def test_try_node__on_error_code__successfully_caught():
|
16
16
|
# GIVEN a try node that is configured to catch PROVIDER_ERROR
|
17
|
-
@TryNode.wrap(on_error_code=
|
17
|
+
@TryNode.wrap(on_error_code=WorkflowErrorCode.PROVIDER_ERROR)
|
18
18
|
class TestNode(BaseNode):
|
19
19
|
class Outputs(BaseOutputs):
|
20
20
|
value: int
|
21
21
|
|
22
22
|
def run(self) -> Outputs:
|
23
|
-
raise NodeException(message="This will be caught", code=
|
23
|
+
raise NodeException(message="This will be caught", code=WorkflowErrorCode.PROVIDER_ERROR)
|
24
24
|
|
25
25
|
# WHEN the node is run and throws a PROVIDER_ERROR
|
26
26
|
node = TestNode(state=BaseState())
|
@@ -30,19 +30,21 @@ def test_try_node__on_error_code__successfully_caught():
|
|
30
30
|
assert len(outputs) == 2
|
31
31
|
assert set(outputs) == {
|
32
32
|
BaseOutput(name="value"),
|
33
|
-
BaseOutput(
|
33
|
+
BaseOutput(
|
34
|
+
name="error", value=WorkflowError(message="This will be caught", code=WorkflowErrorCode.PROVIDER_ERROR)
|
35
|
+
),
|
34
36
|
}
|
35
37
|
|
36
38
|
|
37
39
|
def test_try_node__retry_on_error_code__missed():
|
38
40
|
# GIVEN a try node that is configured to catch PROVIDER_ERROR
|
39
|
-
@TryNode.wrap(on_error_code=
|
41
|
+
@TryNode.wrap(on_error_code=WorkflowErrorCode.PROVIDER_ERROR)
|
40
42
|
class TestNode(BaseNode):
|
41
43
|
class Outputs(BaseOutputs):
|
42
44
|
value: int
|
43
45
|
|
44
46
|
def run(self) -> Outputs:
|
45
|
-
raise NodeException(message="This will be missed", code=
|
47
|
+
raise NodeException(message="This will be missed", code=WorkflowErrorCode.INTERNAL_ERROR)
|
46
48
|
|
47
49
|
# WHEN the node is run and throws a different exception
|
48
50
|
node = TestNode(state=BaseState())
|
@@ -51,7 +53,7 @@ def test_try_node__retry_on_error_code__missed():
|
|
51
53
|
|
52
54
|
# THEN the exception is not caught
|
53
55
|
assert exc_info.value.message == "Unexpected rejection: INTERNAL_ERROR.\nMessage: This will be missed"
|
54
|
-
assert exc_info.value.code ==
|
56
|
+
assert exc_info.value.code == WorkflowErrorCode.INVALID_OUTPUTS
|
55
57
|
|
56
58
|
|
57
59
|
def test_try_node__use_parent_inputs_and_state():
|
@@ -4,7 +4,7 @@ from requests import Request, RequestException, Session
|
|
4
4
|
from requests.exceptions import JSONDecodeError
|
5
5
|
|
6
6
|
from vellum.workflows.constants import APIRequestMethod
|
7
|
-
from vellum.workflows.errors.types import
|
7
|
+
from vellum.workflows.errors.types import WorkflowErrorCode
|
8
8
|
from vellum.workflows.exceptions import NodeException
|
9
9
|
from vellum.workflows.nodes.bases import BaseNode
|
10
10
|
from vellum.workflows.outputs import BaseOutputs
|
@@ -49,13 +49,13 @@ class BaseAPINode(BaseNode, Generic[StateType]):
|
|
49
49
|
try:
|
50
50
|
prepped = Request(method=method.value, url=url, data=data, json=json, headers=headers).prepare()
|
51
51
|
except Exception as e:
|
52
|
-
raise NodeException(f"Failed to prepare HTTP request: {e}", code=
|
52
|
+
raise NodeException(f"Failed to prepare HTTP request: {e}", code=WorkflowErrorCode.PROVIDER_ERROR)
|
53
53
|
|
54
54
|
try:
|
55
55
|
with Session() as session:
|
56
56
|
response = session.send(prepped)
|
57
57
|
except RequestException as e:
|
58
|
-
raise NodeException(f"HTTP request failed: {e}", code=
|
58
|
+
raise NodeException(f"HTTP request failed: {e}", code=WorkflowErrorCode.PROVIDER_ERROR)
|
59
59
|
|
60
60
|
try:
|
61
61
|
json = response.json()
|