vellum-ai 0.14.8__py3-none-any.whl → 0.14.9__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/descriptors/base.py +6 -0
- vellum/workflows/descriptors/tests/test_utils.py +14 -0
- vellum/workflows/events/tests/test_event.py +40 -0
- vellum/workflows/events/workflow.py +20 -1
- vellum/workflows/expressions/greater_than.py +15 -8
- vellum/workflows/expressions/greater_than_or_equal_to.py +14 -8
- vellum/workflows/expressions/less_than.py +14 -8
- vellum/workflows/expressions/less_than_or_equal_to.py +14 -8
- vellum/workflows/expressions/parse_json.py +30 -0
- vellum/workflows/expressions/tests/__init__.py +0 -0
- vellum/workflows/expressions/tests/test_expressions.py +310 -0
- vellum/workflows/expressions/tests/test_parse_json.py +31 -0
- vellum/workflows/nodes/bases/base.py +5 -2
- vellum/workflows/nodes/displayable/api_node/tests/test_api_node.py +6 -7
- vellum/workflows/nodes/displayable/code_execution_node/node.py +18 -8
- vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py +53 -0
- vellum/workflows/runner/runner.py +33 -4
- vellum/workflows/state/encoder.py +2 -1
- {vellum_ai-0.14.8.dist-info → vellum_ai-0.14.9.dist-info}/METADATA +1 -1
- {vellum_ai-0.14.8.dist-info → vellum_ai-0.14.9.dist-info}/RECORD +29 -25
- vellum_ee/workflows/display/nodes/base_node_display.py +4 -0
- vellum_ee/workflows/display/tests/test_vellum_workflow_display.py +31 -0
- vellum_ee/workflows/display/types.py +1 -14
- vellum_ee/workflows/display/workflows/base_workflow_display.py +2 -6
- vellum_ee/workflows/tests/test_server.py +1 -0
- {vellum_ai-0.14.8.dist-info → vellum_ai-0.14.9.dist-info}/LICENSE +0 -0
- {vellum_ai-0.14.8.dist-info → vellum_ai-0.14.9.dist-info}/WHEEL +0 -0
- {vellum_ai-0.14.8.dist-info → vellum_ai-0.14.9.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.9",
|
22
22
|
}
|
23
23
|
headers["X_API_KEY"] = self.api_key
|
24
24
|
return headers
|
@@ -29,6 +29,7 @@ if TYPE_CHECKING:
|
|
29
29
|
from vellum.workflows.expressions.not_between import NotBetweenExpression
|
30
30
|
from vellum.workflows.expressions.not_in import NotInExpression
|
31
31
|
from vellum.workflows.expressions.or_ import OrExpression
|
32
|
+
from vellum.workflows.expressions.parse_json import ParseJsonExpression
|
32
33
|
from vellum.workflows.nodes.bases import BaseNode
|
33
34
|
from vellum.workflows.state.base import BaseState
|
34
35
|
|
@@ -349,3 +350,8 @@ class BaseDescriptor(Generic[_T]):
|
|
349
350
|
from vellum.workflows.expressions.is_not_blank import IsNotBlankExpression
|
350
351
|
|
351
352
|
return IsNotBlankExpression(expression=self)
|
353
|
+
|
354
|
+
def parse_json(self) -> "ParseJsonExpression[_T]":
|
355
|
+
from vellum.workflows.expressions.parse_json import ParseJsonExpression
|
356
|
+
|
357
|
+
return ParseJsonExpression(expression=self)
|
@@ -96,6 +96,13 @@ class DummyNode(BaseNode[FixtureState]):
|
|
96
96
|
).does_not_contain("test"),
|
97
97
|
False,
|
98
98
|
),
|
99
|
+
(ConstantValueReference('{"foo": "bar"}').parse_json(), {"foo": "bar"}),
|
100
|
+
(ConstantValueReference('{"foo": "bar"}').parse_json()["foo"], "bar"),
|
101
|
+
(ConstantValueReference("[1, 2, 3]").parse_json(), [1, 2, 3]),
|
102
|
+
(ConstantValueReference("[1, 2, 3]").parse_json()[0], 1),
|
103
|
+
(ConstantValueReference(b'{"foo": "bar"}').parse_json(), {"foo": "bar"}),
|
104
|
+
(ConstantValueReference(bytearray(b'{"foo": "bar"}')).parse_json(), {"foo": "bar"}),
|
105
|
+
(ConstantValueReference(b'{"key": "\xf0\x9f\x8c\x9f"}').parse_json(), {"key": "🌟"}),
|
99
106
|
],
|
100
107
|
ids=[
|
101
108
|
"or",
|
@@ -143,6 +150,13 @@ class DummyNode(BaseNode[FixtureState]):
|
|
143
150
|
"list_index",
|
144
151
|
"error_contains",
|
145
152
|
"error_does_not_contain",
|
153
|
+
"parse_json_constant",
|
154
|
+
"parse_json_accessor",
|
155
|
+
"parse_json_list",
|
156
|
+
"parse_json_list_index",
|
157
|
+
"parse_json_bytes",
|
158
|
+
"parse_json_bytearray",
|
159
|
+
"parse_json_bytes_with_utf8_chars",
|
146
160
|
],
|
147
161
|
)
|
148
162
|
def test_resolve_value__happy_path(descriptor, expected_value):
|
@@ -4,6 +4,7 @@ from uuid import UUID
|
|
4
4
|
|
5
5
|
from deepdiff import DeepDiff
|
6
6
|
|
7
|
+
from vellum.workflows.constants import undefined
|
7
8
|
from vellum.workflows.errors.types import WorkflowError, WorkflowErrorCode
|
8
9
|
from vellum.workflows.events.node import (
|
9
10
|
NodeExecutionFulfilledBody,
|
@@ -86,6 +87,7 @@ mock_node_uuid = str(uuid4_from_hash(MockNode.__qualname__))
|
|
86
87
|
"inputs": {
|
87
88
|
"foo": "bar",
|
88
89
|
},
|
90
|
+
"display_context": None,
|
89
91
|
},
|
90
92
|
"parent": None,
|
91
93
|
},
|
@@ -330,6 +332,43 @@ mock_node_uuid = str(uuid4_from_hash(MockNode.__qualname__))
|
|
330
332
|
"parent": None,
|
331
333
|
},
|
332
334
|
),
|
335
|
+
(
|
336
|
+
NodeExecutionFulfilledEvent(
|
337
|
+
id=UUID("123e4567-e89b-12d3-a456-426614174000"),
|
338
|
+
timestamp=datetime(2024, 1, 1, 12, 0, 0),
|
339
|
+
trace_id=UUID("123e4567-e89b-12d3-a456-426614174000"),
|
340
|
+
span_id=UUID("123e4567-e89b-12d3-a456-426614174000"),
|
341
|
+
body=NodeExecutionFulfilledBody(
|
342
|
+
node_definition=MockNode,
|
343
|
+
outputs=MockNode.Outputs(
|
344
|
+
example=undefined, # type: ignore[arg-type]
|
345
|
+
),
|
346
|
+
invoked_ports={MockNode.Ports.default},
|
347
|
+
),
|
348
|
+
),
|
349
|
+
{
|
350
|
+
"id": "123e4567-e89b-12d3-a456-426614174000",
|
351
|
+
"api_version": "2024-10-25",
|
352
|
+
"timestamp": "2024-01-01T12:00:00",
|
353
|
+
"trace_id": "123e4567-e89b-12d3-a456-426614174000",
|
354
|
+
"span_id": "123e4567-e89b-12d3-a456-426614174000",
|
355
|
+
"name": "node.execution.fulfilled",
|
356
|
+
"body": {
|
357
|
+
"node_definition": {
|
358
|
+
"id": mock_node_uuid,
|
359
|
+
"name": "MockNode",
|
360
|
+
"module": module_root + ["events", "tests", "test_event"],
|
361
|
+
},
|
362
|
+
"outputs": {},
|
363
|
+
"invoked_ports": [
|
364
|
+
{
|
365
|
+
"name": "default",
|
366
|
+
}
|
367
|
+
],
|
368
|
+
},
|
369
|
+
"parent": None,
|
370
|
+
},
|
371
|
+
),
|
333
372
|
],
|
334
373
|
ids=[
|
335
374
|
"workflow.execution.initiated",
|
@@ -339,6 +378,7 @@ mock_node_uuid = str(uuid4_from_hash(MockNode.__qualname__))
|
|
339
378
|
"workflow.execution.rejected",
|
340
379
|
"node.execution.streaming",
|
341
380
|
"node.execution.fulfilled",
|
381
|
+
"fulfilled_node_with_undefined_outputs",
|
342
382
|
],
|
343
383
|
)
|
344
384
|
def test_event_serialization(event, expected_json):
|
@@ -1,4 +1,5 @@
|
|
1
|
-
from
|
1
|
+
from uuid import UUID
|
2
|
+
from typing import TYPE_CHECKING, Any, Dict, Generator, Generic, Iterable, Literal, Optional, Type, Union
|
2
3
|
|
3
4
|
from pydantic import field_serializer
|
4
5
|
|
@@ -38,8 +39,26 @@ class _BaseWorkflowEvent(BaseEvent):
|
|
38
39
|
return self.body.workflow_definition
|
39
40
|
|
40
41
|
|
42
|
+
class NodeDisplay(UniversalBaseModel):
|
43
|
+
input_display: Dict[str, UUID]
|
44
|
+
output_display: Dict[str, UUID]
|
45
|
+
port_display: Dict[str, UUID]
|
46
|
+
|
47
|
+
|
48
|
+
class WorkflowEventDisplayContext(UniversalBaseModel):
|
49
|
+
node_displays: Dict[str, NodeDisplay]
|
50
|
+
workflow_inputs: Dict[str, UUID]
|
51
|
+
workflow_outputs: Dict[str, UUID]
|
52
|
+
|
53
|
+
|
41
54
|
class WorkflowExecutionInitiatedBody(_BaseWorkflowExecutionBody, Generic[InputsType]):
|
42
55
|
inputs: InputsType
|
56
|
+
# It is still the responsibility of the workflow server to populate this context. The SDK's
|
57
|
+
# Workflow Runner will always leave this field None.
|
58
|
+
#
|
59
|
+
# It's conceivable in a future where all `id`s are agnostic to display and reside in a third location,
|
60
|
+
# that the Workflow Runner can begin populating this field then.
|
61
|
+
display_context: Optional[WorkflowEventDisplayContext] = None
|
43
62
|
|
44
63
|
@field_serializer("inputs")
|
45
64
|
def serialize_inputs(self, inputs: InputsType, _info: Any) -> Dict[str, Any]:
|
@@ -1,10 +1,21 @@
|
|
1
|
-
from typing import Generic, TypeVar, Union
|
1
|
+
from typing import Any, Generic, Protocol, TypeVar, Union, runtime_checkable
|
2
|
+
from typing_extensions import TypeGuard
|
2
3
|
|
3
4
|
from vellum.workflows.descriptors.base import BaseDescriptor
|
4
5
|
from vellum.workflows.descriptors.exceptions import InvalidExpressionException
|
5
6
|
from vellum.workflows.descriptors.utils import resolve_value
|
6
7
|
from vellum.workflows.state.base import BaseState
|
7
8
|
|
9
|
+
|
10
|
+
@runtime_checkable
|
11
|
+
class SupportsGreaterThan(Protocol):
|
12
|
+
def __gt__(self, other: Any) -> bool: ...
|
13
|
+
|
14
|
+
|
15
|
+
def has_gt(obj: Any) -> TypeGuard[SupportsGreaterThan]:
|
16
|
+
return hasattr(obj, "__gt__")
|
17
|
+
|
18
|
+
|
8
19
|
LHS = TypeVar("LHS")
|
9
20
|
RHS = TypeVar("RHS")
|
10
21
|
|
@@ -21,14 +32,10 @@ class GreaterThanExpression(BaseDescriptor[bool], Generic[LHS, RHS]):
|
|
21
32
|
self._rhs = rhs
|
22
33
|
|
23
34
|
def resolve(self, state: "BaseState") -> bool:
|
24
|
-
# Support any type that implements the > operator
|
25
|
-
# https://app.shortcut.com/vellum/story/4658
|
26
35
|
lhs = resolve_value(self._lhs, state)
|
27
|
-
if not isinstance(lhs, (int, float)):
|
28
|
-
raise InvalidExpressionException(f"Expected a numeric lhs value, got: {lhs.__class__.__name__}")
|
29
|
-
|
30
36
|
rhs = resolve_value(self._rhs, state)
|
31
|
-
|
32
|
-
|
37
|
+
|
38
|
+
if not has_gt(lhs):
|
39
|
+
raise InvalidExpressionException(f"'{lhs.__class__.__name__}' must support the '>' operator")
|
33
40
|
|
34
41
|
return lhs > rhs
|
@@ -1,4 +1,5 @@
|
|
1
|
-
from typing import Generic, TypeVar, Union
|
1
|
+
from typing import Any, Generic, Protocol, TypeVar, Union, runtime_checkable
|
2
|
+
from typing_extensions import TypeGuard
|
2
3
|
|
3
4
|
from vellum.workflows.descriptors.base import BaseDescriptor
|
4
5
|
from vellum.workflows.descriptors.exceptions import InvalidExpressionException
|
@@ -9,6 +10,15 @@ LHS = TypeVar("LHS")
|
|
9
10
|
RHS = TypeVar("RHS")
|
10
11
|
|
11
12
|
|
13
|
+
@runtime_checkable
|
14
|
+
class SupportsGreaterThanOrEqualTo(Protocol):
|
15
|
+
def __ge__(self, other: Any) -> bool: ...
|
16
|
+
|
17
|
+
|
18
|
+
def has_ge(obj: Any) -> TypeGuard[SupportsGreaterThanOrEqualTo]:
|
19
|
+
return hasattr(obj, "__ge__")
|
20
|
+
|
21
|
+
|
12
22
|
class GreaterThanOrEqualToExpression(BaseDescriptor[bool], Generic[LHS, RHS]):
|
13
23
|
def __init__(
|
14
24
|
self,
|
@@ -21,14 +31,10 @@ class GreaterThanOrEqualToExpression(BaseDescriptor[bool], Generic[LHS, RHS]):
|
|
21
31
|
self._rhs = rhs
|
22
32
|
|
23
33
|
def resolve(self, state: "BaseState") -> bool:
|
24
|
-
# Support any type that implements the >= operator
|
25
|
-
# https://app.shortcut.com/vellum/story/4658
|
26
34
|
lhs = resolve_value(self._lhs, state)
|
27
|
-
if not isinstance(lhs, (int, float)):
|
28
|
-
raise InvalidExpressionException(f"Expected a numeric lhs value, got: {lhs.__class__.__name__}")
|
29
|
-
|
30
35
|
rhs = resolve_value(self._rhs, state)
|
31
|
-
|
32
|
-
|
36
|
+
|
37
|
+
if not has_ge(lhs):
|
38
|
+
raise InvalidExpressionException(f"'{lhs.__class__.__name__}' must support the '>=' operator")
|
33
39
|
|
34
40
|
return lhs >= rhs
|
@@ -1,4 +1,5 @@
|
|
1
|
-
from typing import Generic, TypeVar, Union
|
1
|
+
from typing import Any, Generic, Protocol, TypeVar, Union, runtime_checkable
|
2
|
+
from typing_extensions import TypeGuard
|
2
3
|
|
3
4
|
from vellum.workflows.descriptors.base import BaseDescriptor
|
4
5
|
from vellum.workflows.descriptors.exceptions import InvalidExpressionException
|
@@ -9,6 +10,15 @@ LHS = TypeVar("LHS")
|
|
9
10
|
RHS = TypeVar("RHS")
|
10
11
|
|
11
12
|
|
13
|
+
@runtime_checkable
|
14
|
+
class SupportsLessThan(Protocol):
|
15
|
+
def __lt__(self, other: Any) -> bool: ...
|
16
|
+
|
17
|
+
|
18
|
+
def has_lt(obj: Any) -> TypeGuard[SupportsLessThan]:
|
19
|
+
return hasattr(obj, "__lt__")
|
20
|
+
|
21
|
+
|
12
22
|
class LessThanExpression(BaseDescriptor[bool], Generic[LHS, RHS]):
|
13
23
|
def __init__(
|
14
24
|
self,
|
@@ -21,14 +31,10 @@ class LessThanExpression(BaseDescriptor[bool], Generic[LHS, RHS]):
|
|
21
31
|
self._rhs = rhs
|
22
32
|
|
23
33
|
def resolve(self, state: "BaseState") -> bool:
|
24
|
-
# Support any type that implements the < operator
|
25
|
-
# https://app.shortcut.com/vellum/story/4658
|
26
34
|
lhs = resolve_value(self._lhs, state)
|
27
|
-
if not isinstance(lhs, (int, float)):
|
28
|
-
raise InvalidExpressionException(f"Expected a numeric lhs value, got: {lhs.__class__.__name__}")
|
29
|
-
|
30
35
|
rhs = resolve_value(self._rhs, state)
|
31
|
-
|
32
|
-
|
36
|
+
|
37
|
+
if not has_lt(lhs):
|
38
|
+
raise InvalidExpressionException(f"'{lhs.__class__.__name__}' must support the '<' operator")
|
33
39
|
|
34
40
|
return lhs < rhs
|
@@ -1,4 +1,5 @@
|
|
1
|
-
from typing import Generic, TypeVar, Union
|
1
|
+
from typing import Any, Generic, Protocol, TypeVar, Union, runtime_checkable
|
2
|
+
from typing_extensions import TypeGuard
|
2
3
|
|
3
4
|
from vellum.workflows.descriptors.base import BaseDescriptor
|
4
5
|
from vellum.workflows.descriptors.exceptions import InvalidExpressionException
|
@@ -9,6 +10,15 @@ LHS = TypeVar("LHS")
|
|
9
10
|
RHS = TypeVar("RHS")
|
10
11
|
|
11
12
|
|
13
|
+
@runtime_checkable
|
14
|
+
class SupportsLessThanOrEqualTo(Protocol):
|
15
|
+
def __le__(self, other: Any) -> bool: ...
|
16
|
+
|
17
|
+
|
18
|
+
def has_le(obj: Any) -> TypeGuard[SupportsLessThanOrEqualTo]:
|
19
|
+
return hasattr(obj, "__le__")
|
20
|
+
|
21
|
+
|
12
22
|
class LessThanOrEqualToExpression(BaseDescriptor[bool], Generic[LHS, RHS]):
|
13
23
|
def __init__(
|
14
24
|
self,
|
@@ -21,14 +31,10 @@ class LessThanOrEqualToExpression(BaseDescriptor[bool], Generic[LHS, RHS]):
|
|
21
31
|
self._rhs = rhs
|
22
32
|
|
23
33
|
def resolve(self, state: "BaseState") -> bool:
|
24
|
-
# Support any type that implements the <= operator
|
25
|
-
# https://app.shortcut.com/vellum/story/4658
|
26
34
|
lhs = resolve_value(self._lhs, state)
|
27
|
-
if not isinstance(lhs, (int, float)):
|
28
|
-
raise InvalidExpressionException(f"Expected a numeric lhs value, got: {lhs.__class__.__name__}")
|
29
|
-
|
30
35
|
rhs = resolve_value(self._rhs, state)
|
31
|
-
|
32
|
-
|
36
|
+
|
37
|
+
if not has_le(lhs):
|
38
|
+
raise InvalidExpressionException(f"'{lhs.__class__.__name__}' must support the '<=' operator")
|
33
39
|
|
34
40
|
return lhs <= rhs
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import json
|
2
|
+
from typing import Any, Generic, TypeVar, Union
|
3
|
+
|
4
|
+
from vellum.workflows.descriptors.base import BaseDescriptor
|
5
|
+
from vellum.workflows.descriptors.exceptions import InvalidExpressionException
|
6
|
+
from vellum.workflows.descriptors.utils import resolve_value
|
7
|
+
from vellum.workflows.state.base import BaseState
|
8
|
+
|
9
|
+
_T = TypeVar("_T")
|
10
|
+
|
11
|
+
|
12
|
+
class ParseJsonExpression(BaseDescriptor[Any], Generic[_T]):
|
13
|
+
def __init__(
|
14
|
+
self,
|
15
|
+
*,
|
16
|
+
expression: Union[BaseDescriptor[_T], _T],
|
17
|
+
) -> None:
|
18
|
+
super().__init__(name=f"parse_json({expression})", types=(Any,)) # type: ignore[arg-type]
|
19
|
+
self._expression = expression
|
20
|
+
|
21
|
+
def resolve(self, state: "BaseState") -> Any:
|
22
|
+
value = resolve_value(self._expression, state)
|
23
|
+
|
24
|
+
if not isinstance(value, (str, bytes, bytearray)):
|
25
|
+
raise InvalidExpressionException(f"Expected a string, but got {value} of type {type(value)}")
|
26
|
+
|
27
|
+
try:
|
28
|
+
return json.loads(value)
|
29
|
+
except json.JSONDecodeError as e:
|
30
|
+
raise InvalidExpressionException(f"Failed to parse JSON: {e}") from e
|
File without changes
|
@@ -0,0 +1,310 @@
|
|
1
|
+
import pytest
|
2
|
+
|
3
|
+
from vellum.workflows.expressions.greater_than import GreaterThanExpression
|
4
|
+
from vellum.workflows.expressions.greater_than_or_equal_to import GreaterThanOrEqualToExpression
|
5
|
+
from vellum.workflows.expressions.less_than import LessThanExpression
|
6
|
+
from vellum.workflows.expressions.less_than_or_equal_to import LessThanOrEqualToExpression
|
7
|
+
from vellum.workflows.state.base import BaseState
|
8
|
+
|
9
|
+
|
10
|
+
class Comparable:
|
11
|
+
"""A custom class with two values, where comparisons use a computed metric (multiplication)."""
|
12
|
+
|
13
|
+
def __init__(self, value1, value2):
|
14
|
+
self.value1 = value1 # First numerical value
|
15
|
+
self.value2 = value2 # Second numerical value
|
16
|
+
|
17
|
+
def computed_value(self):
|
18
|
+
return self.value1 * self.value2 # Multiply for comparison
|
19
|
+
|
20
|
+
def __ge__(self, other):
|
21
|
+
if isinstance(other, Comparable):
|
22
|
+
return self.computed_value() >= other.computed_value()
|
23
|
+
elif isinstance(other, (int, float)):
|
24
|
+
return self.computed_value() >= other
|
25
|
+
return NotImplemented
|
26
|
+
|
27
|
+
def __gt__(self, other):
|
28
|
+
if isinstance(other, Comparable):
|
29
|
+
return self.computed_value() > other.computed_value()
|
30
|
+
elif isinstance(other, (int, float)):
|
31
|
+
return self.computed_value() > other
|
32
|
+
return NotImplemented
|
33
|
+
|
34
|
+
def __le__(self, other):
|
35
|
+
if isinstance(other, Comparable):
|
36
|
+
return self.computed_value() <= other.computed_value()
|
37
|
+
elif isinstance(other, (int, float)):
|
38
|
+
return self.computed_value() <= other
|
39
|
+
return NotImplemented
|
40
|
+
|
41
|
+
def __lt__(self, other):
|
42
|
+
if isinstance(other, Comparable):
|
43
|
+
return self.computed_value() < other.computed_value()
|
44
|
+
elif isinstance(other, (int, float)):
|
45
|
+
return self.computed_value() < other
|
46
|
+
return NotImplemented
|
47
|
+
|
48
|
+
|
49
|
+
class NonComparable:
|
50
|
+
"""A custom class that does not support comparisons."""
|
51
|
+
|
52
|
+
def __init__(self, value1, value2):
|
53
|
+
self.value1 = value1
|
54
|
+
self.value2 = value2
|
55
|
+
|
56
|
+
|
57
|
+
class TestState(BaseState):
|
58
|
+
pass
|
59
|
+
|
60
|
+
|
61
|
+
def test_greater_than_or_equal_to():
|
62
|
+
# GIVEN objects with two values
|
63
|
+
obj1 = Comparable(4, 5) # Computed: 4 × 5 = 20
|
64
|
+
obj2 = Comparable(2, 10) # Computed: 2 × 10 = 20
|
65
|
+
obj3 = Comparable(3, 6) # Computed: 3 × 6 = 18
|
66
|
+
obj4 = Comparable(5, 5) # Computed: 5 × 5 = 25
|
67
|
+
|
68
|
+
state = TestState()
|
69
|
+
|
70
|
+
# WHEN comparing objects
|
71
|
+
assert GreaterThanOrEqualToExpression(lhs=obj1, rhs=obj2).resolve(state) is True # 20 >= 20
|
72
|
+
assert GreaterThanOrEqualToExpression(lhs=obj1, rhs=obj3).resolve(state) is True # 20 >= 18
|
73
|
+
assert GreaterThanOrEqualToExpression(lhs=obj3, rhs=obj4).resolve(state) is False # 18 < 25
|
74
|
+
|
75
|
+
# WHEN comparing to raw numbers
|
76
|
+
assert GreaterThanOrEqualToExpression(lhs=obj1, rhs=19).resolve(state) is True # 20 >= 19
|
77
|
+
assert GreaterThanOrEqualToExpression(lhs=obj3, rhs=20).resolve(state) is False # 18 < 20
|
78
|
+
|
79
|
+
|
80
|
+
def test_greater_than_or_equal_to_invalid():
|
81
|
+
# GIVEN objects with two values
|
82
|
+
obj1 = Comparable(4, 5)
|
83
|
+
obj2 = "invalid"
|
84
|
+
|
85
|
+
state = TestState()
|
86
|
+
|
87
|
+
# WHEN comparing objects with incompatible types
|
88
|
+
with pytest.raises(TypeError) as exc_info:
|
89
|
+
GreaterThanOrEqualToExpression(lhs=obj1, rhs=obj2).resolve(state)
|
90
|
+
|
91
|
+
# THEN the expected error is raised
|
92
|
+
assert str(exc_info.value) == "'>=' not supported between instances of 'Comparable' and 'str'"
|
93
|
+
|
94
|
+
# WHEN comparing objects with incompatible types
|
95
|
+
with pytest.raises(TypeError) as exc_info:
|
96
|
+
GreaterThanOrEqualToExpression(lhs=obj2, rhs=obj1).resolve(state)
|
97
|
+
|
98
|
+
# THEN the expected error is raised
|
99
|
+
assert str(exc_info.value) == "'>=' not supported between instances of 'str' and 'Comparable'"
|
100
|
+
|
101
|
+
|
102
|
+
def test_greater_than_or_equal_to_non_comparable():
|
103
|
+
# GIVEN objects with two values
|
104
|
+
obj1 = Comparable(4, 5)
|
105
|
+
obj2 = NonComparable(2, 10)
|
106
|
+
|
107
|
+
state = TestState()
|
108
|
+
|
109
|
+
# WHEN comparing objects with incompatible types
|
110
|
+
with pytest.raises(TypeError) as exc_info:
|
111
|
+
GreaterThanOrEqualToExpression(lhs=obj1, rhs=obj2).resolve(state)
|
112
|
+
|
113
|
+
# THEN the expected error is raised
|
114
|
+
assert str(exc_info.value) == "'>=' not supported between instances of 'Comparable' and 'NonComparable'"
|
115
|
+
|
116
|
+
# WHEN comparing objects with incompatible types
|
117
|
+
with pytest.raises(TypeError) as exc_info:
|
118
|
+
GreaterThanOrEqualToExpression(lhs=obj2, rhs=obj1).resolve(state)
|
119
|
+
|
120
|
+
# THEN the expected error is raised
|
121
|
+
assert str(exc_info.value) == "'>=' not supported between instances of 'NonComparable' and 'Comparable'"
|
122
|
+
|
123
|
+
|
124
|
+
def test_greater_than():
|
125
|
+
# GIVEN objects with two values
|
126
|
+
obj1 = Comparable(4, 5) # Computed: 4 × 5 = 20
|
127
|
+
obj2 = Comparable(2, 10) # Computed: 2 × 10 = 20
|
128
|
+
obj3 = Comparable(3, 6) # Computed: 3 × 6 = 18
|
129
|
+
obj4 = Comparable(5, 5) # Computed: 5 × 5 = 25
|
130
|
+
|
131
|
+
state = TestState()
|
132
|
+
|
133
|
+
# WHEN comparing objects
|
134
|
+
assert GreaterThanExpression(lhs=obj1, rhs=obj2).resolve(state) is False # 20 > 20
|
135
|
+
assert GreaterThanExpression(lhs=obj1, rhs=obj3).resolve(state) is True # 20 > 18
|
136
|
+
assert GreaterThanExpression(lhs=obj3, rhs=obj4).resolve(state) is False # 18 < 25
|
137
|
+
|
138
|
+
# WHEN comparing to raw numbers
|
139
|
+
assert GreaterThanExpression(lhs=obj1, rhs=19).resolve(state) is True # 20 > 19
|
140
|
+
assert GreaterThanExpression(lhs=obj3, rhs=20).resolve(state) is False # 18 < 20
|
141
|
+
|
142
|
+
|
143
|
+
def test_greater_than_invalid():
|
144
|
+
# GIVEN objects with two values
|
145
|
+
obj1 = Comparable(4, 5)
|
146
|
+
obj2 = "invalid"
|
147
|
+
|
148
|
+
state = TestState()
|
149
|
+
|
150
|
+
# WHEN comparing objects with incompatible types
|
151
|
+
with pytest.raises(TypeError) as exc_info:
|
152
|
+
GreaterThanExpression(lhs=obj1, rhs=obj2).resolve(state)
|
153
|
+
|
154
|
+
# THEN the expected error is raised
|
155
|
+
assert str(exc_info.value) == "'>' not supported between instances of 'Comparable' and 'str'"
|
156
|
+
|
157
|
+
# WHEN comparing objects with incompatible types
|
158
|
+
with pytest.raises(TypeError) as exc_info:
|
159
|
+
GreaterThanExpression(lhs=obj2, rhs=obj1).resolve(state)
|
160
|
+
|
161
|
+
# THEN the expected error is raised
|
162
|
+
assert str(exc_info.value) == "'>' not supported between instances of 'str' and 'Comparable'"
|
163
|
+
|
164
|
+
|
165
|
+
def test_greater_than_non_comparable():
|
166
|
+
# GIVEN objects with two values
|
167
|
+
obj1 = Comparable(4, 5)
|
168
|
+
obj2 = NonComparable(2, 10)
|
169
|
+
|
170
|
+
state = TestState()
|
171
|
+
|
172
|
+
# WHEN comparing objects with incompatible types
|
173
|
+
with pytest.raises(TypeError) as exc_info:
|
174
|
+
GreaterThanExpression(lhs=obj1, rhs=obj2).resolve(state)
|
175
|
+
|
176
|
+
# THEN the expected error is raised
|
177
|
+
assert str(exc_info.value) == "'>' not supported between instances of 'Comparable' and 'NonComparable'"
|
178
|
+
|
179
|
+
# WHEN comparing objects with incompatible types
|
180
|
+
with pytest.raises(TypeError) as exc_info:
|
181
|
+
GreaterThanExpression(lhs=obj2, rhs=obj1).resolve(state)
|
182
|
+
|
183
|
+
# THEN the expected error is raised
|
184
|
+
assert str(exc_info.value) == "'>' not supported between instances of 'NonComparable' and 'Comparable'"
|
185
|
+
|
186
|
+
|
187
|
+
def test_less_than_or_equal_to():
|
188
|
+
# GIVEN objects with two values
|
189
|
+
obj1 = Comparable(4, 5) # Computed: 4 × 5 = 20
|
190
|
+
obj2 = Comparable(2, 10) # Computed: 2 × 10 = 20
|
191
|
+
obj3 = Comparable(3, 6) # Computed: 3 × 6 = 18
|
192
|
+
obj4 = Comparable(5, 5) # Computed: 5 × 5 = 25
|
193
|
+
|
194
|
+
state = TestState()
|
195
|
+
|
196
|
+
# WHEN comparing objects
|
197
|
+
assert LessThanOrEqualToExpression(lhs=obj1, rhs=obj2).resolve(state) is True # 20 <= 20
|
198
|
+
assert LessThanOrEqualToExpression(lhs=obj1, rhs=obj3).resolve(state) is False # 20 > 18
|
199
|
+
assert LessThanOrEqualToExpression(lhs=obj3, rhs=obj4).resolve(state) is True # 18 <= 25
|
200
|
+
|
201
|
+
# WHEN comparing to raw numbers
|
202
|
+
assert LessThanOrEqualToExpression(lhs=obj1, rhs=21).resolve(state) is True # 20 <= 21
|
203
|
+
assert LessThanOrEqualToExpression(lhs=obj3, rhs=17).resolve(state) is False # 18 > 17
|
204
|
+
|
205
|
+
|
206
|
+
def test_less_than_or_equal_to_invalid():
|
207
|
+
# GIVEN objects with two values
|
208
|
+
obj1 = Comparable(4, 5)
|
209
|
+
obj2 = "invalid"
|
210
|
+
|
211
|
+
state = TestState()
|
212
|
+
|
213
|
+
# WHEN comparing objects with incompatible types
|
214
|
+
with pytest.raises(TypeError) as exc_info:
|
215
|
+
LessThanOrEqualToExpression(lhs=obj1, rhs=obj2).resolve(state)
|
216
|
+
|
217
|
+
# THEN the expected error is raised
|
218
|
+
assert str(exc_info.value) == "'<=' not supported between instances of 'Comparable' and 'str'"
|
219
|
+
|
220
|
+
# WHEN comparing objects with incompatible types
|
221
|
+
with pytest.raises(TypeError) as exc_info:
|
222
|
+
LessThanOrEqualToExpression(lhs=obj2, rhs=obj1).resolve(state)
|
223
|
+
|
224
|
+
# THEN the expected error is raised
|
225
|
+
assert str(exc_info.value) == "'<=' not supported between instances of 'str' and 'Comparable'"
|
226
|
+
|
227
|
+
|
228
|
+
def test_less_than_or_equal_to_non_comparable():
|
229
|
+
# GIVEN objects with two values
|
230
|
+
obj1 = Comparable(4, 5)
|
231
|
+
obj2 = NonComparable(2, 10)
|
232
|
+
|
233
|
+
state = TestState()
|
234
|
+
|
235
|
+
# WHEN comparing objects with incompatible types
|
236
|
+
with pytest.raises(TypeError) as exc_info:
|
237
|
+
LessThanOrEqualToExpression(lhs=obj1, rhs=obj2).resolve(state)
|
238
|
+
|
239
|
+
# THEN the expected error is raised
|
240
|
+
assert str(exc_info.value) == "'<=' not supported between instances of 'Comparable' and 'NonComparable'"
|
241
|
+
|
242
|
+
# WHEN comparing objects with incompatible types
|
243
|
+
with pytest.raises(TypeError) as exc_info:
|
244
|
+
LessThanOrEqualToExpression(lhs=obj2, rhs=obj1).resolve(state)
|
245
|
+
|
246
|
+
# THEN the expected error is raised
|
247
|
+
assert str(exc_info.value) == "'<=' not supported between instances of 'NonComparable' and 'Comparable'"
|
248
|
+
|
249
|
+
|
250
|
+
def test_less_than():
|
251
|
+
# GIVEN objects with two values
|
252
|
+
obj1 = Comparable(4, 5) # Computed: 4 × 5 = 20
|
253
|
+
obj2 = Comparable(2, 10) # Computed: 2 × 10 = 20
|
254
|
+
obj3 = Comparable(3, 6) # Computed: 3 × 6 = 18
|
255
|
+
obj4 = Comparable(5, 5) # Computed: 5 × 5 = 25
|
256
|
+
|
257
|
+
state = TestState()
|
258
|
+
|
259
|
+
# WHEN comparing objects
|
260
|
+
assert LessThanExpression(lhs=obj1, rhs=obj2).resolve(state) is False # 20 < 20
|
261
|
+
assert LessThanExpression(lhs=obj1, rhs=obj3).resolve(state) is False # 20 < 18
|
262
|
+
assert LessThanExpression(lhs=obj3, rhs=obj4).resolve(state) is True # 18 < 25
|
263
|
+
|
264
|
+
# WHEN comparing to raw numbers
|
265
|
+
assert LessThanExpression(lhs=obj1, rhs=21).resolve(state) is True # 20 < 21
|
266
|
+
assert LessThanExpression(lhs=obj3, rhs=17).resolve(state) is False # 18 > 17
|
267
|
+
|
268
|
+
|
269
|
+
def test_less_than_invalid():
|
270
|
+
# GIVEN objects with two values
|
271
|
+
obj1 = Comparable(4, 5)
|
272
|
+
obj2 = "invalid"
|
273
|
+
|
274
|
+
state = TestState()
|
275
|
+
|
276
|
+
# WHEN comparing objects with incompatible types
|
277
|
+
with pytest.raises(TypeError) as exc_info:
|
278
|
+
LessThanExpression(lhs=obj1, rhs=obj2).resolve(state)
|
279
|
+
|
280
|
+
# THEN the expected error is raised
|
281
|
+
assert str(exc_info.value) == "'<' not supported between instances of 'Comparable' and 'str'"
|
282
|
+
|
283
|
+
# WHEN comparing objects with incompatible types
|
284
|
+
with pytest.raises(TypeError) as exc_info:
|
285
|
+
LessThanExpression(lhs=obj2, rhs=obj1).resolve(state)
|
286
|
+
|
287
|
+
# THEN the expected error is raised
|
288
|
+
assert str(exc_info.value) == "'<' not supported between instances of 'str' and 'Comparable'"
|
289
|
+
|
290
|
+
|
291
|
+
def test_less_than_non_comparable():
|
292
|
+
# GIVEN objects with two values
|
293
|
+
obj1 = Comparable(4, 5)
|
294
|
+
obj2 = NonComparable(2, 10)
|
295
|
+
|
296
|
+
state = TestState()
|
297
|
+
|
298
|
+
# WHEN comparing objects with incompatible types
|
299
|
+
with pytest.raises(TypeError) as exc_info:
|
300
|
+
LessThanExpression(lhs=obj1, rhs=obj2).resolve(state)
|
301
|
+
|
302
|
+
# THEN the expected error is raised
|
303
|
+
assert str(exc_info.value) == "'<' not supported between instances of 'Comparable' and 'NonComparable'"
|
304
|
+
|
305
|
+
# WHEN comparing objects with incompatible types
|
306
|
+
with pytest.raises(TypeError) as exc_info:
|
307
|
+
LessThanExpression(lhs=obj2, rhs=obj1).resolve(state)
|
308
|
+
|
309
|
+
# THEN the expected error is raised
|
310
|
+
assert str(exc_info.value) == "'<' not supported between instances of 'NonComparable' and 'Comparable'"
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import pytest
|
2
|
+
|
3
|
+
from vellum.workflows.descriptors.exceptions import InvalidExpressionException
|
4
|
+
from vellum.workflows.references.constant import ConstantValueReference
|
5
|
+
from vellum.workflows.state.base import BaseState
|
6
|
+
|
7
|
+
|
8
|
+
def test_parse_json_invalid_json():
|
9
|
+
# GIVEN an invalid JSON string
|
10
|
+
state = BaseState()
|
11
|
+
expression = ConstantValueReference('{"key": value}').parse_json()
|
12
|
+
|
13
|
+
# WHEN we attempt to resolve the expression
|
14
|
+
with pytest.raises(InvalidExpressionException) as exc_info:
|
15
|
+
expression.resolve(state)
|
16
|
+
|
17
|
+
# THEN an exception should be raised
|
18
|
+
assert "Failed to parse JSON" in str(exc_info.value)
|
19
|
+
|
20
|
+
|
21
|
+
def test_parse_json_invalid_type():
|
22
|
+
# GIVEN a non-string value
|
23
|
+
state = BaseState()
|
24
|
+
expression = ConstantValueReference(123).parse_json()
|
25
|
+
|
26
|
+
# WHEN we attempt to resolve the expression
|
27
|
+
with pytest.raises(InvalidExpressionException) as exc_info:
|
28
|
+
expression.resolve(state)
|
29
|
+
|
30
|
+
# THEN an exception should be raised
|
31
|
+
assert "Expected a string, but got 123 of type <class 'int'>" == str(exc_info.value)
|
@@ -318,9 +318,12 @@ class BaseNode(Generic[StateType], metaclass=BaseNodeMeta):
|
|
318
318
|
original_base = get_original_base(self.__class__)
|
319
319
|
|
320
320
|
args = get_args(original_base)
|
321
|
-
state_type = args[0]
|
322
321
|
|
323
|
-
if
|
322
|
+
if args and len(args) > 0:
|
323
|
+
state_type = args[0]
|
324
|
+
if isinstance(state_type, TypeVar):
|
325
|
+
state_type = BaseState
|
326
|
+
else:
|
324
327
|
state_type = BaseState
|
325
328
|
|
326
329
|
self.state = state_type()
|
@@ -5,7 +5,6 @@ from vellum.client.core.api_error import ApiError
|
|
5
5
|
from vellum.workflows.constants import APIRequestMethod, AuthorizationType
|
6
6
|
from vellum.workflows.exceptions import NodeException
|
7
7
|
from vellum.workflows.nodes import APINode
|
8
|
-
from vellum.workflows.state import BaseState
|
9
8
|
from vellum.workflows.types.core import VellumSecret
|
10
9
|
|
11
10
|
|
@@ -29,7 +28,7 @@ def test_run_workflow__secrets(vellum_client):
|
|
29
28
|
}
|
30
29
|
bearer_token_value = VellumSecret(name="secret")
|
31
30
|
|
32
|
-
node = SimpleBaseAPINode(
|
31
|
+
node = SimpleBaseAPINode()
|
33
32
|
terminal = node.run()
|
34
33
|
|
35
34
|
assert vellum_client.execute_api.call_count == 1
|
@@ -39,7 +38,7 @@ def test_run_workflow__secrets(vellum_client):
|
|
39
38
|
|
40
39
|
|
41
40
|
def test_api_node_raises_error_when_api_call_fails(vellum_client):
|
42
|
-
#
|
41
|
+
# GIVEN an API call that fails
|
43
42
|
vellum_client.execute_api.side_effect = ApiError(status_code=400, body="API Error")
|
44
43
|
|
45
44
|
class SimpleAPINode(APINode):
|
@@ -54,14 +53,14 @@ def test_api_node_raises_error_when_api_call_fails(vellum_client):
|
|
54
53
|
}
|
55
54
|
bearer_token_value = VellumSecret(name="api_key")
|
56
55
|
|
57
|
-
node = SimpleAPINode(
|
56
|
+
node = SimpleAPINode()
|
58
57
|
|
59
|
-
#
|
58
|
+
# WHEN we run the node
|
60
59
|
with pytest.raises(NodeException) as excinfo:
|
61
60
|
node.run()
|
62
61
|
|
63
|
-
#
|
62
|
+
# THEN an exception should be raised
|
64
63
|
assert "Failed to prepare HTTP request" in str(excinfo.value)
|
65
64
|
|
66
|
-
#
|
65
|
+
# AND the API call should have been made
|
67
66
|
assert vellum_client.execute_api.call_count == 1
|
@@ -19,6 +19,7 @@ from vellum import (
|
|
19
19
|
VellumError,
|
20
20
|
VellumValue,
|
21
21
|
)
|
22
|
+
from vellum.client.core.api_error import ApiError
|
22
23
|
from vellum.client.types.code_executor_secret_input import CodeExecutorSecretInput
|
23
24
|
from vellum.core import RequestOptions
|
24
25
|
from vellum.workflows.errors.types import WorkflowErrorCode
|
@@ -103,14 +104,23 @@ class CodeExecutionNode(BaseNode[StateType], Generic[StateType, _OutputType], me
|
|
103
104
|
input_values = self._compile_code_inputs()
|
104
105
|
expected_output_type = primitive_type_to_vellum_variable_type(output_type)
|
105
106
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
107
|
+
try:
|
108
|
+
code_execution_result = self._context.vellum_client.execute_code(
|
109
|
+
input_values=input_values,
|
110
|
+
code=code,
|
111
|
+
runtime=self.runtime,
|
112
|
+
output_type=expected_output_type,
|
113
|
+
packages=self.packages or [],
|
114
|
+
request_options=self.request_options,
|
115
|
+
)
|
116
|
+
except ApiError as e:
|
117
|
+
if e.status_code == 400 and isinstance(e.body, dict) and "message" in e.body:
|
118
|
+
raise NodeException(
|
119
|
+
message=e.body["message"],
|
120
|
+
code=WorkflowErrorCode.INVALID_INPUTS,
|
121
|
+
)
|
122
|
+
|
123
|
+
raise
|
114
124
|
|
115
125
|
if code_execution_result.output.type != expected_output_type:
|
116
126
|
actual_type = code_execution_result.output.type
|
@@ -5,6 +5,7 @@ from typing import Any, List, Union
|
|
5
5
|
from pydantic import BaseModel
|
6
6
|
|
7
7
|
from vellum import CodeExecutorResponse, NumberVellumValue, StringInput, StringVellumValue
|
8
|
+
from vellum.client.errors.bad_request_error import BadRequestError
|
8
9
|
from vellum.client.types.chat_message import ChatMessage
|
9
10
|
from vellum.client.types.code_execution_package import CodeExecutionPackage
|
10
11
|
from vellum.client.types.code_executor_secret_input import CodeExecutorSecretInput
|
@@ -17,6 +18,7 @@ from vellum.workflows.inputs.base import BaseInputs
|
|
17
18
|
from vellum.workflows.nodes.displayable.code_execution_node import CodeExecutionNode
|
18
19
|
from vellum.workflows.references.vellum_secret import VellumSecretReference
|
19
20
|
from vellum.workflows.state.base import BaseState, StateMeta
|
21
|
+
from vellum.workflows.types.core import Json
|
20
22
|
|
21
23
|
|
22
24
|
def test_run_node__happy_path(vellum_client):
|
@@ -690,3 +692,54 @@ def main():
|
|
690
692
|
"result": [ChatMessage(role="USER", content=StringChatMessageContent(value="Hello, world!"))],
|
691
693
|
"log": "",
|
692
694
|
}
|
695
|
+
|
696
|
+
|
697
|
+
def test_run_node__execute_code_api_fails__node_exception(vellum_client):
|
698
|
+
# GIVEN a node that will throw a JSON.parse error
|
699
|
+
class ExampleCodeExecutionNode(CodeExecutionNode[BaseState, Json]):
|
700
|
+
code = """\
|
701
|
+
async function main(inputs: {
|
702
|
+
data: string,
|
703
|
+
}): Promise<string> {
|
704
|
+
return JSON.parse(inputs.data)
|
705
|
+
}
|
706
|
+
"""
|
707
|
+
code_inputs = {
|
708
|
+
"data": "not a valid json string",
|
709
|
+
}
|
710
|
+
runtime = "TYPESCRIPT_5_3_3"
|
711
|
+
|
712
|
+
# AND the execute_code API will fail
|
713
|
+
message = """\
|
714
|
+
Code execution error (exit code 1): undefined:1
|
715
|
+
not a valid json string
|
716
|
+
^
|
717
|
+
|
718
|
+
SyntaxError: Unexpected token 'o', \"not a valid\"... is not valid JSON
|
719
|
+
at JSON.parse (<anonymous>)
|
720
|
+
at Object.eval (eval at execute (/workdir/runner.js:16:18), <anonymous>:40:40)
|
721
|
+
at step (eval at execute (/workdir/runner.js:16:18), <anonymous>:32:23)
|
722
|
+
at Object.eval [as next] (eval at execute (/workdir/runner.js:16:18), <anonymous>:13:53)
|
723
|
+
at eval (eval at execute (/workdir/runner.js:16:18), <anonymous>:7:71)
|
724
|
+
at new Promise (<anonymous>)
|
725
|
+
at __awaiter (eval at execute (/workdir/runner.js:16:18), <anonymous>:3:12)
|
726
|
+
at Object.main (eval at execute (/workdir/runner.js:16:18), <anonymous>:38:12)
|
727
|
+
at execute (/workdir/runner.js:17:33)
|
728
|
+
at Interface.<anonymous> (/workdir/runner.js:58:5)
|
729
|
+
|
730
|
+
Node.js v21.7.3
|
731
|
+
"""
|
732
|
+
vellum_client.execute_code.side_effect = BadRequestError(
|
733
|
+
body={
|
734
|
+
"message": message,
|
735
|
+
"log": "",
|
736
|
+
}
|
737
|
+
)
|
738
|
+
|
739
|
+
# WHEN we run the node
|
740
|
+
node = ExampleCodeExecutionNode()
|
741
|
+
with pytest.raises(NodeException) as exc_info:
|
742
|
+
node.run()
|
743
|
+
|
744
|
+
# AND the error should contain the execution error details
|
745
|
+
assert exc_info.value.message == message
|
@@ -1,5 +1,6 @@
|
|
1
1
|
from collections import defaultdict
|
2
2
|
from copy import deepcopy
|
3
|
+
from dataclasses import dataclass
|
3
4
|
import logging
|
4
5
|
from queue import Empty, Queue
|
5
6
|
from threading import Event as ThreadingEvent, Thread
|
@@ -63,6 +64,12 @@ ExternalInputsArg = Dict[ExternalInputReference, Any]
|
|
63
64
|
BackgroundThreadItem = Union[BaseState, WorkflowEvent, None]
|
64
65
|
|
65
66
|
|
67
|
+
@dataclass
|
68
|
+
class ActiveNode(Generic[StateType]):
|
69
|
+
node: BaseNode[StateType]
|
70
|
+
was_outputs_streamed: bool = False
|
71
|
+
|
72
|
+
|
66
73
|
class WorkflowRunner(Generic[StateType]):
|
67
74
|
_entrypoints: Iterable[Type[BaseNode]]
|
68
75
|
|
@@ -136,7 +143,7 @@ class WorkflowRunner(Generic[StateType]):
|
|
136
143
|
self._dependencies: Dict[Type[BaseNode], Set[Type[BaseNode]]] = defaultdict(set)
|
137
144
|
self._state_forks: Set[StateType] = {self._initial_state}
|
138
145
|
|
139
|
-
self._active_nodes_by_execution_id: Dict[UUID,
|
146
|
+
self._active_nodes_by_execution_id: Dict[UUID, ActiveNode[StateType]] = {}
|
140
147
|
self._cancel_signal = cancel_signal
|
141
148
|
self._execution_context = init_execution_context or get_execution_context()
|
142
149
|
self._parent_context = self._execution_context.parent_context
|
@@ -404,7 +411,7 @@ class WorkflowRunner(Generic[StateType]):
|
|
404
411
|
current_parent = get_parent_context()
|
405
412
|
node = node_class(state=state, context=self.workflow.context)
|
406
413
|
state.meta.node_execution_cache.initiate_node_execution(node_class, node_span_id)
|
407
|
-
self._active_nodes_by_execution_id[node_span_id] = node
|
414
|
+
self._active_nodes_by_execution_id[node_span_id] = ActiveNode(node=node)
|
408
415
|
|
409
416
|
worker_thread = Thread(
|
410
417
|
target=self._context_run_work_item,
|
@@ -413,10 +420,11 @@ class WorkflowRunner(Generic[StateType]):
|
|
413
420
|
worker_thread.start()
|
414
421
|
|
415
422
|
def _handle_work_item_event(self, event: WorkflowEvent) -> Optional[WorkflowError]:
|
416
|
-
|
417
|
-
if not
|
423
|
+
active_node = self._active_nodes_by_execution_id.get(event.span_id)
|
424
|
+
if not active_node:
|
418
425
|
return None
|
419
426
|
|
427
|
+
node = active_node.node
|
420
428
|
if event.name == "node.execution.rejected":
|
421
429
|
self._active_nodes_by_execution_id.pop(event.span_id)
|
422
430
|
return event.error
|
@@ -431,6 +439,7 @@ class WorkflowRunner(Generic[StateType]):
|
|
431
439
|
if node_output_descriptor.name != event.output.name:
|
432
440
|
continue
|
433
441
|
|
442
|
+
active_node.was_outputs_streamed = True
|
434
443
|
self._workflow_event_outer_queue.put(
|
435
444
|
self._stream_workflow_event(
|
436
445
|
BaseOutput(
|
@@ -447,6 +456,26 @@ class WorkflowRunner(Generic[StateType]):
|
|
447
456
|
|
448
457
|
if event.name == "node.execution.fulfilled":
|
449
458
|
self._active_nodes_by_execution_id.pop(event.span_id)
|
459
|
+
if not active_node.was_outputs_streamed:
|
460
|
+
for event_node_output_descriptor, node_output_value in event.outputs:
|
461
|
+
for workflow_output_descriptor in self.workflow.Outputs:
|
462
|
+
node_output_descriptor = workflow_output_descriptor.instance
|
463
|
+
if not isinstance(node_output_descriptor, OutputReference):
|
464
|
+
continue
|
465
|
+
if node_output_descriptor.outputs_class != event.node_definition.Outputs:
|
466
|
+
continue
|
467
|
+
if node_output_descriptor.name != event_node_output_descriptor.name:
|
468
|
+
continue
|
469
|
+
|
470
|
+
self._workflow_event_outer_queue.put(
|
471
|
+
self._stream_workflow_event(
|
472
|
+
BaseOutput(
|
473
|
+
name=workflow_output_descriptor.name,
|
474
|
+
value=node_output_value,
|
475
|
+
)
|
476
|
+
)
|
477
|
+
)
|
478
|
+
|
450
479
|
self._handle_invoked_ports(node.state, event.invoked_ports)
|
451
480
|
|
452
481
|
return None
|
@@ -8,6 +8,7 @@ from typing import Any, Callable, Dict, Type
|
|
8
8
|
|
9
9
|
from pydantic import BaseModel
|
10
10
|
|
11
|
+
from vellum.workflows.constants import undefined
|
11
12
|
from vellum.workflows.inputs.base import BaseInputs
|
12
13
|
from vellum.workflows.outputs.base import BaseOutput, BaseOutputs
|
13
14
|
from vellum.workflows.ports.port import Port
|
@@ -22,7 +23,7 @@ class DefaultStateEncoder(JSONEncoder):
|
|
22
23
|
return dict(obj)
|
23
24
|
|
24
25
|
if isinstance(obj, (BaseInputs, BaseOutputs)):
|
25
|
-
return {descriptor.name: value for descriptor, value in obj}
|
26
|
+
return {descriptor.name: value for descriptor, value in obj if value is not undefined}
|
26
27
|
|
27
28
|
if isinstance(obj, (BaseOutput, Port)):
|
28
29
|
return obj.serialize()
|
@@ -23,7 +23,7 @@ vellum_ee/workflows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
|
|
23
23
|
vellum_ee/workflows/display/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
24
24
|
vellum_ee/workflows/display/base.py,sha256=ak29FIsawhaFa9_paZUHThlZRFJ1xB486JWKuSt1PYY,1965
|
25
25
|
vellum_ee/workflows/display/nodes/__init__.py,sha256=436iSAh_Ex5tC68oEYvNgPu05ZVIAVXnS4PKGrQeZ0Y,321
|
26
|
-
vellum_ee/workflows/display/nodes/base_node_display.py,sha256=
|
26
|
+
vellum_ee/workflows/display/nodes/base_node_display.py,sha256=h4_JqwH_AsT92OK14S3kqssZDjL-uyXnJfo0XcLijK0,16492
|
27
27
|
vellum_ee/workflows/display/nodes/base_node_vellum_display.py,sha256=pLO0dORfRu--Ne9NgoyFT_CNjfpr5fGCsgbsMkUF5GM,2845
|
28
28
|
vellum_ee/workflows/display/nodes/get_node_display_class.py,sha256=0S6ksPp53WXWh1RQVH71nj2bkCWBj4ZaFYhTxW3N2F4,1230
|
29
29
|
vellum_ee/workflows/display/nodes/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -56,7 +56,7 @@ vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py,sha256=4YUaTeD_OWF-
|
|
56
56
|
vellum_ee/workflows/display/nodes/vellum/try_node.py,sha256=HBfGz4yt9GlmMW9JxzaCacPnHBDNIeXE8Jhqr9DqLLw,6191
|
57
57
|
vellum_ee/workflows/display/nodes/vellum/utils.py,sha256=F_0BrlSszllK_BhryPbojIleLq2dGXOfQD1rVp3fNFg,4733
|
58
58
|
vellum_ee/workflows/display/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
59
|
-
vellum_ee/workflows/display/tests/test_vellum_workflow_display.py,sha256=
|
59
|
+
vellum_ee/workflows/display/tests/test_vellum_workflow_display.py,sha256=cdpUoDNli8ULlNTrU6rnT4TZAIR8mzsG9ZbLrikPybU,8518
|
60
60
|
vellum_ee/workflows/display/tests/workflow_serialization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
61
61
|
vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
62
62
|
vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/conftest.py,sha256=A1-tIpC5KIKG9JA_rkd1nLS8zUG3Kb4QiVdvb3boFxE,2509
|
@@ -83,13 +83,13 @@ vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_n
|
|
83
83
|
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py,sha256=NdhE3lm7RMQ8DqkraPSq24IbOxNla9unbs4tsMWRzm4,3781
|
84
84
|
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py,sha256=eD5686C9nWC5s6t08vbAnm9qf9t53gYQM-E1FwAa75c,3035
|
85
85
|
vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py,sha256=huKAOeMJ2MKmp6XtbvMJTUadqynoV40Ypoz9jsBEBEQ,7431
|
86
|
-
vellum_ee/workflows/display/types.py,sha256=
|
86
|
+
vellum_ee/workflows/display/types.py,sha256=ixfmcQn51Rhsm4_0hWfG0_WpzLE89ZrDZpeYBklsP1Q,2592
|
87
87
|
vellum_ee/workflows/display/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
88
88
|
vellum_ee/workflows/display/utils/expressions.py,sha256=9FpOslDI-RCR5m4TgAu9KCHh4aTVnh7CHR2ykyMUDw0,1151
|
89
89
|
vellum_ee/workflows/display/utils/vellum.py,sha256=UjK_RxnSEmlIu9klGCPWU5RAQBmgZ7cRbRdgxaTbubE,8081
|
90
90
|
vellum_ee/workflows/display/vellum.py,sha256=7mqQaKZPPrLMcXSAQkPIxCy5x8HkKs5PbCu3GRaC2o8,8507
|
91
91
|
vellum_ee/workflows/display/workflows/__init__.py,sha256=kapXsC67VJcgSuiBMa86FdePG5A9kMB5Pi4Uy1O2ob4,207
|
92
|
-
vellum_ee/workflows/display/workflows/base_workflow_display.py,sha256=
|
92
|
+
vellum_ee/workflows/display/workflows/base_workflow_display.py,sha256=p9Lt1AypyC0Y6WHOUCsk4lYmlgODwg5oz-IDo0UA-1o,18678
|
93
93
|
vellum_ee/workflows/display/workflows/get_vellum_workflow_display_class.py,sha256=kp0u8LN_2IwshLrhMImhpZx1hRyAcD5gXY-kDuuaGMQ,1269
|
94
94
|
vellum_ee/workflows/display/workflows/tests/test_workflow_display.py,sha256=_yB3-u7_bWdD4lUBWpRdWztJmJL-DXkkZaw9Vy9HH6g,3245
|
95
95
|
vellum_ee/workflows/display/workflows/vellum_workflow_display.py,sha256=mbAzCpswOek34ITeTkesbVreCXpulj4NFjIg3RcdVZ8,18243
|
@@ -115,14 +115,14 @@ vellum_ee/workflows/tests/local_workflow/nodes/final_output.py,sha256=ZX7zBv87zi
|
|
115
115
|
vellum_ee/workflows/tests/local_workflow/nodes/templating_node.py,sha256=NQwFN61QkHfI3Vssz-B0NKGfupK8PU0FDSAIAhYBLi0,325
|
116
116
|
vellum_ee/workflows/tests/local_workflow/workflow.py,sha256=A4qOzOPNwePYxWbcAgIPLsmrVS_aVEZEc-wULSv787Q,393
|
117
117
|
vellum_ee/workflows/tests/test_display_meta.py,sha256=pzdqND4KLWs7EUIbpXuqgso7BIRpoUsO3T_bgeENs0Q,2205
|
118
|
-
vellum_ee/workflows/tests/test_server.py,sha256=
|
118
|
+
vellum_ee/workflows/tests/test_server.py,sha256=M6vvQ2hjIpDWtQdDM9EPbMvUrZ93niAuYnxMNJWOjPA,511
|
119
119
|
vellum_ee/workflows/tests/test_virtual_files.py,sha256=TJEcMR0v2S8CkloXNmCHA0QW0K6pYNGaIjraJz7sFvY,2762
|
120
120
|
vellum/__init__.py,sha256=a_aM1_A04XGma4MAIDNeBF9BKzWbiQaVVMRzImHuxjA,36438
|
121
121
|
vellum/client/README.md,sha256=JkCJjmMZl4jrPj46pkmL9dpK4gSzQQmP5I7z4aME4LY,4749
|
122
122
|
vellum/client/__init__.py,sha256=tKtdM1_GqmGq1gpi9ydWD_T-MM7fPn8QdHh8ww19cNI,117564
|
123
123
|
vellum/client/core/__init__.py,sha256=SQ85PF84B9MuKnBwHNHWemSGuy-g_515gFYNFhvEE0I,1438
|
124
124
|
vellum/client/core/api_error.py,sha256=RE8LELok2QCjABadECTvtDp7qejA1VmINCh6TbqPwSE,426
|
125
|
-
vellum/client/core/client_wrapper.py,sha256=
|
125
|
+
vellum/client/core/client_wrapper.py,sha256=RhAfDORGTXyVqWFHTHUIONrGJRy7OCD049LPC0pwN_k,1868
|
126
126
|
vellum/client/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
|
127
127
|
vellum/client/core/file.py,sha256=X9IbmkZmB2bB_DpmZAO3crWdXagOakAyn6UCOCImCPg,2322
|
128
128
|
vellum/client/core/http_client.py,sha256=R0pQpCppnEtxccGvXl4uJ76s7ro_65Fo_erlNNLp_AI,19228
|
@@ -1298,9 +1298,9 @@ vellum/workflows/__init__.py,sha256=CssPsbNvN6rDhoLuqpEv7MMKGa51vE6dvAh6U31Pcio,
|
|
1298
1298
|
vellum/workflows/constants.py,sha256=2yg4_uo5gpqViy3ZLSwfC8qTybleYCtOnhA4Rj6bacM,1310
|
1299
1299
|
vellum/workflows/context.py,sha256=DwSf8lO9NHABiqOoD3exgrjUoRuNsKtutaL5TgRbD-A,1441
|
1300
1300
|
vellum/workflows/descriptors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1301
|
-
vellum/workflows/descriptors/base.py,sha256=
|
1301
|
+
vellum/workflows/descriptors/base.py,sha256=jtT11I5oB-CPWlJBZmc1RcVe4_xOKjimBthiHQHQuT0,14597
|
1302
1302
|
vellum/workflows/descriptors/exceptions.py,sha256=gUy4UD9JFUKSeQnQpeuDSLiRqWjWiIsxLahB7p_q3JY,54
|
1303
|
-
vellum/workflows/descriptors/tests/test_utils.py,sha256=
|
1303
|
+
vellum/workflows/descriptors/tests/test_utils.py,sha256=xoojJMyG5WLG9xGtmUjirz3lDFCcDsAcxjrtbdG8dNE,6060
|
1304
1304
|
vellum/workflows/descriptors/utils.py,sha256=gmVXJjf2yWmvlYey41J2FZHeSou0JuCHKb3826K_Jok,3838
|
1305
1305
|
vellum/workflows/edges/__init__.py,sha256=wSkmAnz9xyi4vZwtDbKxwlplt2skD7n3NsxkvR_pUus,50
|
1306
1306
|
vellum/workflows/edges/edge.py,sha256=N0SnY3gKVuxImPAdCbPMPlHJIXbkQ3fwq_LbJRvVMFc,677
|
@@ -1313,9 +1313,9 @@ vellum/workflows/errors/types.py,sha256=tVW7Il9zalnwWzdoDLqYPIvRTOhXIv6FPORZAbU7
|
|
1313
1313
|
vellum/workflows/events/__init__.py,sha256=6pxxceJo2dcaRkWtkDAYlUQZ-PHBQSZytIoyuUK48Qw,759
|
1314
1314
|
vellum/workflows/events/node.py,sha256=uHT6If0esgZ3nLjrjmUPTKf3qbjGhoV_x5YKpjDBDcU,5280
|
1315
1315
|
vellum/workflows/events/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1316
|
-
vellum/workflows/events/tests/test_event.py,sha256=
|
1316
|
+
vellum/workflows/events/tests/test_event.py,sha256=uRfMwSOqU-ROeZKCEngGvvJYlOZuxBhnC3qH5AGi3fM,15399
|
1317
1317
|
vellum/workflows/events/types.py,sha256=cjRE8WL8tYCFradd9NOGl_H0mN3LiWWnA1uHmyT2Q0Q,3412
|
1318
|
-
vellum/workflows/events/workflow.py,sha256=
|
1318
|
+
vellum/workflows/events/workflow.py,sha256=XtmGG7NRp0TQ4memOJPcaNOs7qMUBbd4WKSZVlxWrCk,5937
|
1319
1319
|
vellum/workflows/exceptions.py,sha256=NiBiR3ggfmPxBVqD-H1SqmjI-7mIn0EStSN1BqApvCM,1213
|
1320
1320
|
vellum/workflows/expressions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1321
1321
|
vellum/workflows/expressions/accessor.py,sha256=ItZF7fMLzVTqsdAiaXb5SiDupXmX0X9xbIus1W6hRds,1870
|
@@ -1330,8 +1330,8 @@ vellum/workflows/expressions/does_not_end_with.py,sha256=idw2OSuIk02XwBM5CXYOESf
|
|
1330
1330
|
vellum/workflows/expressions/does_not_equal.py,sha256=LNiCibRZZIxaIrwotjW3SIsKYHWR7BpOItFI-x5KuKU,764
|
1331
1331
|
vellum/workflows/expressions/ends_with.py,sha256=FkWZjAudc_DFM-fK-A3_tr6WXavfs_Qi7F6JtVgMglw,1117
|
1332
1332
|
vellum/workflows/expressions/equals.py,sha256=gbI6BKQR7cLQih226-ge_BFSLprgEjqbdiIxo7WFg_E,758
|
1333
|
-
vellum/workflows/expressions/greater_than.py,sha256=
|
1334
|
-
vellum/workflows/expressions/greater_than_or_equal_to.py,sha256=
|
1333
|
+
vellum/workflows/expressions/greater_than.py,sha256=1sbUH6Obf-VoBgs7ilIncwYBHYfXliKmShppTuLMtCI,1247
|
1334
|
+
vellum/workflows/expressions/greater_than_or_equal_to.py,sha256=tsD5ZalB4SlryvEsvVtDkSr5Z13B2pABmHB8oHD8ojs,1276
|
1335
1335
|
vellum/workflows/expressions/in_.py,sha256=RgiAIFntXGN4eWoOVBj1gqLymnBxSiw5hYD3TngF3dk,1123
|
1336
1336
|
vellum/workflows/expressions/is_blank.py,sha256=vOOmK5poXmiNRVH7MR0feIFnL4rwKn7vmmTkJ9TcfVU,904
|
1337
1337
|
vellum/workflows/expressions/is_nil.py,sha256=xCHwhKlm2UnfC-bVedmGgENCrzNtcn4ZeCYwNflVWbU,748
|
@@ -1341,11 +1341,15 @@ vellum/workflows/expressions/is_not_null.py,sha256=EoHXFgZScKP_BM2a5Z7YFQN6l7RME
|
|
1341
1341
|
vellum/workflows/expressions/is_not_undefined.py,sha256=9s-RUQBqM17-_nIRvwsHuarLdHVtrxVuwnqBNJEtmh0,735
|
1342
1342
|
vellum/workflows/expressions/is_null.py,sha256=C75ALGlG_sTGcxI46tm9HtgPVfJ7DwTIyKzX8qtEiDU,660
|
1343
1343
|
vellum/workflows/expressions/is_undefined.py,sha256=uUBK3rxYbwoeRq36AGFc7d61hXzTp8UacQAi-1JbaW0,724
|
1344
|
-
vellum/workflows/expressions/less_than.py,sha256=
|
1345
|
-
vellum/workflows/expressions/less_than_or_equal_to.py,sha256=
|
1344
|
+
vellum/workflows/expressions/less_than.py,sha256=chY9puJ6jDB2EinjfyGNrSplJ1NJC-BB-GGSSB33bqI,1237
|
1345
|
+
vellum/workflows/expressions/less_than_or_equal_to.py,sha256=JtTDBa8yFKy3fGaCOA1tb_5s1JkY8FFnH6kpoeXGnT4,1267
|
1346
1346
|
vellum/workflows/expressions/not_between.py,sha256=ZtRJeJDSSlOvajL8YoBoh5o_khjIn9xSSeQCnXYbHFE,1506
|
1347
1347
|
vellum/workflows/expressions/not_in.py,sha256=pFvwkFPsn3WJw61ssFgM2U1dqWEeglfz4FVT4xwm5Mc,1144
|
1348
1348
|
vellum/workflows/expressions/or_.py,sha256=s-8YdMSSCDS2yijR38kguwok3iqmDMMgDYKV93b4O4s,914
|
1349
|
+
vellum/workflows/expressions/parse_json.py,sha256=xsk6j3HF7bU1yF6fwt5P9Ugcyd5D9ZXrdng11FRilUI,1088
|
1350
|
+
vellum/workflows/expressions/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1351
|
+
vellum/workflows/expressions/tests/test_expressions.py,sha256=3b6k8xs-CItBBw95NygFLUNoNPKxI4VA1GyWbkMtqyI,11623
|
1352
|
+
vellum/workflows/expressions/tests/test_parse_json.py,sha256=zpB_qE5_EwWQL7ULQUJm0o1PRSfWZdAqZNW6Ah13oJE,1059
|
1349
1353
|
vellum/workflows/graph/__init__.py,sha256=3sHlay5d_-uD7j3QJXiGl0WHFZZ_QScRvgyDhN2GhHY,74
|
1350
1354
|
vellum/workflows/graph/graph.py,sha256=GGR8cGpSuNWPIiTWNWsj6l70upb5nIxAyFcn7VdaJIs,5506
|
1351
1355
|
vellum/workflows/graph/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -1357,7 +1361,7 @@ vellum/workflows/inputs/tests/test_inputs.py,sha256=Haa0Px0obef7rgIddO6wwHF_bzmv
|
|
1357
1361
|
vellum/workflows/logging.py,sha256=_a217XogktV4Ncz6xKFz7WfYmZAzkfVRVuC0rWob8ls,437
|
1358
1362
|
vellum/workflows/nodes/__init__.py,sha256=aVdQVv7Y3Ro3JlqXGpxwaU2zrI06plDHD2aumH5WUIs,1157
|
1359
1363
|
vellum/workflows/nodes/bases/__init__.py,sha256=cniHuz_RXdJ4TQgD8CBzoiKDiPxg62ErdVpCbWICX64,58
|
1360
|
-
vellum/workflows/nodes/bases/base.py,sha256=
|
1364
|
+
vellum/workflows/nodes/bases/base.py,sha256=lVHODc1mjUFCorhZfK01zHohOWz9CYz7dnUzA-KENJ4,15036
|
1361
1365
|
vellum/workflows/nodes/bases/base_adornment_node.py,sha256=eFTgsPCYb3eyGS0-kw7C6crFnwFx437R5wh9-8bWYts,2905
|
1362
1366
|
vellum/workflows/nodes/bases/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1363
1367
|
vellum/workflows/nodes/bases/tests/test_base_node.py,sha256=4SOdZzvugVtN8CIuo5RrapAxSYGXnxUwQ77dXJ64oTU,6295
|
@@ -1387,7 +1391,7 @@ vellum/workflows/nodes/displayable/__init__.py,sha256=6F_4DlSwvHuilWnIalp8iDjjDX
|
|
1387
1391
|
vellum/workflows/nodes/displayable/api_node/__init__.py,sha256=MoxdQSnidIj1Nf_d-hTxlOxcZXaZnsWFDbE-PkTK24o,56
|
1388
1392
|
vellum/workflows/nodes/displayable/api_node/node.py,sha256=QdpsyGVxo5PcN8nwGZpcpW_YMKHr3_VvmbK1BlrdOFk,2547
|
1389
1393
|
vellum/workflows/nodes/displayable/api_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1390
|
-
vellum/workflows/nodes/displayable/api_node/tests/test_api_node.py,sha256=
|
1394
|
+
vellum/workflows/nodes/displayable/api_node/tests/test_api_node.py,sha256=RtQWnFfq0HNqY2A6c5SdwHl8A91K4Vq8un5UJwwnkSw,2180
|
1391
1395
|
vellum/workflows/nodes/displayable/bases/__init__.py,sha256=0mWIx3qUrzllV7jqt7wN03vWGMuI1WrrLZeMLT2Cl2c,304
|
1392
1396
|
vellum/workflows/nodes/displayable/bases/api_node/__init__.py,sha256=1jwx4WC358CLA1jgzl_UD-rZmdMm2v9Mps39ndwCD7U,64
|
1393
1397
|
vellum/workflows/nodes/displayable/bases/api_node/node.py,sha256=-LOKjU_rY1UWgD0DS5LJwAClBI8N7zrdmwigE3y5rhc,4000
|
@@ -1403,11 +1407,11 @@ vellum/workflows/nodes/displayable/bases/tests/test_utils.py,sha256=eqdqbKNRWVMD
|
|
1403
1407
|
vellum/workflows/nodes/displayable/bases/types.py,sha256=C37B2Qh2YP7s7pUjd-EYKc2Zl1TbnCgI_mENuUSb8bo,1706
|
1404
1408
|
vellum/workflows/nodes/displayable/bases/utils.py,sha256=ckMUenSsNkiYmSw6FmjSMHYaCk8Y8_sUjL6lkFFEqts,5412
|
1405
1409
|
vellum/workflows/nodes/displayable/code_execution_node/__init__.py,sha256=0FLWMMktpzSnmBMizQglBpcPrP80fzVsoJwJgf822Cg,76
|
1406
|
-
vellum/workflows/nodes/displayable/code_execution_node/node.py,sha256=
|
1410
|
+
vellum/workflows/nodes/displayable/code_execution_node/node.py,sha256=pdDrjI8wdqMyf52kK6TlSjSU-MRxV2SKDwKcK2LCgkU,9547
|
1407
1411
|
vellum/workflows/nodes/displayable/code_execution_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1408
1412
|
vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1409
1413
|
vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/main.py,sha256=5QsbmkzSlSbcbWTG_JmIqcP-JNJzOPTKxGzdHos19W4,79
|
1410
|
-
vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py,sha256=
|
1414
|
+
vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py,sha256=OTnw5jgX90xWVB3vKryL5QSIr2YnGBz-W0q9C9LpNoc,22471
|
1411
1415
|
vellum/workflows/nodes/displayable/code_execution_node/utils.py,sha256=BQraIN4I3DCzXLEuBlRYCyp7ote7hQmnnKHu4jFHCCA,5174
|
1412
1416
|
vellum/workflows/nodes/displayable/conditional_node/__init__.py,sha256=AS_EIqFdU1F9t8aLmbZU-rLh9ry6LCJ0uj0D8F0L5Uw,72
|
1413
1417
|
vellum/workflows/nodes/displayable/conditional_node/node.py,sha256=Qjfl33gZ3JEgxBA1EgzSUebboGvsARthIxxcQyvx5Gg,1152
|
@@ -1466,12 +1470,12 @@ vellum/workflows/references/workflow_input.py,sha256=86IuhlBz-9cGxeUzizyjdp482aj
|
|
1466
1470
|
vellum/workflows/resolvers/__init__.py,sha256=eH6hTvZO4IciDaf_cf7aM2vs-DkBDyJPycOQevJxQnI,82
|
1467
1471
|
vellum/workflows/resolvers/base.py,sha256=WHra9LRtlTuB1jmuNqkfVE2JUgB61Cyntn8f0b0WZg4,411
|
1468
1472
|
vellum/workflows/runner/__init__.py,sha256=i1iG5sAhtpdsrlvwgH6B-m49JsINkiWyPWs8vyT-bqM,72
|
1469
|
-
vellum/workflows/runner/runner.py,sha256=
|
1473
|
+
vellum/workflows/runner/runner.py,sha256=VUGw-QlUNyfwRWjXgBZ1VqKRuYdFC7YdtAmQcsgR6I0,31206
|
1470
1474
|
vellum/workflows/sandbox.py,sha256=GVJzVjMuYzOBnSrboB0_6MMRZWBluAyQ2o7syeaeBd0,2235
|
1471
1475
|
vellum/workflows/state/__init__.py,sha256=yUUdR-_Vl7UiixNDYQZ-GEM_kJI9dnOia75TtuNEsnE,60
|
1472
1476
|
vellum/workflows/state/base.py,sha256=Vkhneko3VlQrPsMLU1PYSzXU_W1u7_AraJsghiv5O-4,15512
|
1473
1477
|
vellum/workflows/state/context.py,sha256=yePVr4CCTQn5bjo1697JOO24fKFQpVNzooL07xL4gL0,2702
|
1474
|
-
vellum/workflows/state/encoder.py,sha256=
|
1478
|
+
vellum/workflows/state/encoder.py,sha256=TnOQojc5lTQ83g9QbpA4UCqShJvutmTMxbpKt-9gNe4,1911
|
1475
1479
|
vellum/workflows/state/store.py,sha256=VYGBQgN1bpd1as5eGiouV_7scg8QsRs4_1aqZAGIsRQ,793
|
1476
1480
|
vellum/workflows/state/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1477
1481
|
vellum/workflows/state/tests/test_state.py,sha256=jBynFR4m74Vn51DdmKBLkxb1loTy1CnJPtzPmdAFQUo,5159
|
@@ -1502,8 +1506,8 @@ vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnad
|
|
1502
1506
|
vellum/workflows/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1503
1507
|
vellum/workflows/workflows/tests/test_base_workflow.py,sha256=NRteiICyJvDM5zrtUfq2fZoXcGQVaWC9xmNlLLVW0cU,7979
|
1504
1508
|
vellum/workflows/workflows/tests/test_context.py,sha256=VJBUcyWVtMa_lE5KxdhgMu0WYNYnUQUDvTF7qm89hJ0,2333
|
1505
|
-
vellum_ai-0.14.
|
1506
|
-
vellum_ai-0.14.
|
1507
|
-
vellum_ai-0.14.
|
1508
|
-
vellum_ai-0.14.
|
1509
|
-
vellum_ai-0.14.
|
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,,
|
@@ -27,6 +27,7 @@ from vellum.workflows.expressions.is_not_undefined import IsNotUndefinedExpressi
|
|
27
27
|
from vellum.workflows.expressions.is_null import IsNullExpression
|
28
28
|
from vellum.workflows.expressions.is_undefined import IsUndefinedExpression
|
29
29
|
from vellum.workflows.expressions.not_between import NotBetweenExpression
|
30
|
+
from vellum.workflows.expressions.parse_json import ParseJsonExpression
|
30
31
|
from vellum.workflows.nodes.bases.base import BaseNode
|
31
32
|
from vellum.workflows.nodes.utils import get_wrapped_node
|
32
33
|
from vellum.workflows.ports import Port
|
@@ -386,6 +387,9 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
|
|
386
387
|
"node_id": str(node_class_display.node_id),
|
387
388
|
}
|
388
389
|
|
390
|
+
if isinstance(value, ParseJsonExpression):
|
391
|
+
raise ValueError("ParseJsonExpression is not supported in the UI")
|
392
|
+
|
389
393
|
if not isinstance(value, BaseDescriptor):
|
390
394
|
vellum_value = primitive_to_vellum_value(value)
|
391
395
|
return {
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import pytest
|
1
2
|
from uuid import UUID
|
2
3
|
from typing import Dict
|
3
4
|
|
@@ -228,3 +229,33 @@ def test_vellum_workflow_display__serialize_with_unused_nodes_and_edges():
|
|
228
229
|
break
|
229
230
|
|
230
231
|
assert edge_found, "Edge between unused nodes NodeB and NodeC not found in serialized output"
|
232
|
+
|
233
|
+
|
234
|
+
def test_parse_json_not_supported_in_ui():
|
235
|
+
"""
|
236
|
+
Test that verifies ParseJsonExpression is not yet supported in the UI.
|
237
|
+
This test should fail once UI support is added, at which point it should be updated.
|
238
|
+
"""
|
239
|
+
# GIVEN a workflow that uses the parse_json function
|
240
|
+
from vellum.workflows.references.constant import ConstantValueReference
|
241
|
+
|
242
|
+
class JsonNode(BaseNode):
|
243
|
+
class Outputs(BaseNode.Outputs):
|
244
|
+
json_result = ConstantValueReference('{"key": "value"}').parse_json()
|
245
|
+
|
246
|
+
class Workflow(BaseWorkflow):
|
247
|
+
graph = JsonNode
|
248
|
+
|
249
|
+
class Outputs(BaseWorkflow.Outputs):
|
250
|
+
final = JsonNode.Outputs.json_result
|
251
|
+
|
252
|
+
# WHEN we attempt to serialize it
|
253
|
+
workflow_display = get_workflow_display(
|
254
|
+
base_display_class=VellumWorkflowDisplay,
|
255
|
+
workflow_class=Workflow,
|
256
|
+
)
|
257
|
+
|
258
|
+
with pytest.raises(ValueError) as exc_info:
|
259
|
+
workflow_display.serialize()
|
260
|
+
|
261
|
+
assert "ParseJsonExpression is not supported in the UI" == str(exc_info.value)
|
@@ -1,9 +1,8 @@
|
|
1
1
|
from dataclasses import dataclass, field
|
2
|
-
from uuid import UUID
|
3
2
|
from typing import TYPE_CHECKING, Dict, Generic, Tuple, Type, TypeVar
|
4
3
|
|
5
|
-
from vellum.client.core import UniversalBaseModel
|
6
4
|
from vellum.workflows.descriptors.base import BaseDescriptor
|
5
|
+
from vellum.workflows.events.workflow import WorkflowEventDisplayContext # noqa: F401
|
7
6
|
from vellum.workflows.nodes import BaseNode
|
8
7
|
from vellum.workflows.ports import Port
|
9
8
|
from vellum.workflows.references import OutputReference, StateValueReference, WorkflowInputReference
|
@@ -25,18 +24,6 @@ NodeDisplayType = TypeVar("NodeDisplayType", bound="BaseNodeDisplay")
|
|
25
24
|
WorkflowDisplayType = TypeVar("WorkflowDisplayType", bound="BaseWorkflowDisplay")
|
26
25
|
|
27
26
|
|
28
|
-
class NodeDisplay(UniversalBaseModel):
|
29
|
-
input_display: Dict[str, UUID]
|
30
|
-
output_display: Dict[str, UUID]
|
31
|
-
port_display: Dict[str, UUID]
|
32
|
-
|
33
|
-
|
34
|
-
class WorkflowEventDisplayContext(UniversalBaseModel):
|
35
|
-
node_displays: Dict[str, NodeDisplay]
|
36
|
-
workflow_inputs: Dict[str, UUID]
|
37
|
-
workflow_outputs: Dict[str, UUID]
|
38
|
-
|
39
|
-
|
40
27
|
@dataclass
|
41
28
|
class WorkflowDisplayContext(
|
42
29
|
Generic[
|
@@ -9,6 +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
13
|
from vellum.workflows.expressions.coalesce_expression import CoalesceExpression
|
13
14
|
from vellum.workflows.nodes.bases import BaseNode
|
14
15
|
from vellum.workflows.nodes.utils import get_wrapped_node
|
@@ -33,12 +34,7 @@ from vellum_ee.workflows.display.base import (
|
|
33
34
|
from vellum_ee.workflows.display.nodes.base_node_vellum_display import BaseNodeVellumDisplay
|
34
35
|
from vellum_ee.workflows.display.nodes.get_node_display_class import get_node_display_class
|
35
36
|
from vellum_ee.workflows.display.nodes.types import NodeOutputDisplay, PortDisplay, PortDisplayOverrides
|
36
|
-
from vellum_ee.workflows.display.types import
|
37
|
-
NodeDisplay,
|
38
|
-
NodeDisplayType,
|
39
|
-
WorkflowDisplayContext,
|
40
|
-
WorkflowEventDisplayContext,
|
41
|
-
)
|
37
|
+
from vellum_ee.workflows.display.types import NodeDisplayType, WorkflowDisplayContext
|
42
38
|
|
43
39
|
logger = logging.getLogger(__name__)
|
44
40
|
|
@@ -2,6 +2,7 @@ from vellum.client.core.pydantic_utilities import UniversalBaseModel
|
|
2
2
|
|
3
3
|
|
4
4
|
def test_load_workflow_event_display_context():
|
5
|
+
# DEPRECATED: Use `vellum.workflows.events.workflow.WorkflowEventDisplayContext` instead. Will be removed in 0.15.0
|
5
6
|
from vellum_ee.workflows.display.types import WorkflowEventDisplayContext
|
6
7
|
|
7
8
|
# We are actually just ensuring there are no circular dependencies when
|
File without changes
|
File without changes
|
File without changes
|