vellum-ai 0.14.8__py3-none-any.whl → 0.14.10__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/utils/templating/render.py +4 -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 +21 -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/core/templating_node/node.py +0 -1
- vellum/workflows/nodes/core/templating_node/tests/test_templating_node.py +49 -0
- 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/nodes/utils.py +14 -3
- vellum/workflows/runner/runner.py +34 -9
- vellum/workflows/state/encoder.py +2 -1
- {vellum_ai-0.14.8.dist-info → vellum_ai-0.14.10.dist-info}/METADATA +1 -1
- {vellum_ai-0.14.8.dist-info → vellum_ai-0.14.10.dist-info}/RECORD +35 -31
- 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 +38 -18
- vellum_ee/workflows/display/workflows/tests/test_workflow_display.py +27 -0
- vellum_ee/workflows/tests/test_display_meta.py +2 -0
- vellum_ee/workflows/tests/test_server.py +1 -0
- {vellum_ai-0.14.8.dist-info → vellum_ai-0.14.10.dist-info}/LICENSE +0 -0
- {vellum_ai-0.14.8.dist-info → vellum_ai-0.14.10.dist-info}/WHEEL +0 -0
- {vellum_ai-0.14.8.dist-info → vellum_ai-0.14.10.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.10",
|
22
22
|
}
|
23
23
|
headers["X_API_KEY"] = self.api_key
|
24
24
|
return headers
|
@@ -2,6 +2,7 @@ import json
|
|
2
2
|
from typing import Any, Dict, Optional
|
3
3
|
|
4
4
|
from jinja2.sandbox import SandboxedEnvironment
|
5
|
+
from pydantic import BaseModel
|
5
6
|
|
6
7
|
from vellum.utils.templating.constants import FilterFunc
|
7
8
|
from vellum.utils.templating.exceptions import JinjaTemplateError
|
@@ -12,6 +13,9 @@ def finalize(obj: Any) -> str:
|
|
12
13
|
if isinstance(obj, (dict, list)):
|
13
14
|
return json.dumps(obj, cls=DefaultStateEncoder)
|
14
15
|
|
16
|
+
if isinstance(obj, BaseModel):
|
17
|
+
return json.dumps(obj.model_dump(), cls=DefaultStateEncoder)
|
18
|
+
|
15
19
|
return str(obj)
|
16
20
|
|
17
21
|
|
@@ -23,7 +27,6 @@ def render_sandboxed_jinja_template(
|
|
23
27
|
jinja_globals: Optional[Dict[str, Any]] = None,
|
24
28
|
) -> str:
|
25
29
|
"""Render a Jinja template within a sandboxed environment."""
|
26
|
-
|
27
30
|
try:
|
28
31
|
environment = SandboxedEnvironment(
|
29
32
|
keep_trailing_newline=True,
|
@@ -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,27 @@ class _BaseWorkflowEvent(BaseEvent):
|
|
38
39
|
return self.body.workflow_definition
|
39
40
|
|
40
41
|
|
42
|
+
class NodeEventDisplayContext(UniversalBaseModel):
|
43
|
+
input_display: Dict[str, UUID]
|
44
|
+
output_display: Dict[str, UUID]
|
45
|
+
port_display: Dict[str, UUID]
|
46
|
+
subworkflow_display: Optional["WorkflowEventDisplayContext"] = None
|
47
|
+
|
48
|
+
|
49
|
+
class WorkflowEventDisplayContext(UniversalBaseModel):
|
50
|
+
node_displays: Dict[str, NodeEventDisplayContext]
|
51
|
+
workflow_inputs: Dict[str, UUID]
|
52
|
+
workflow_outputs: Dict[str, UUID]
|
53
|
+
|
54
|
+
|
41
55
|
class WorkflowExecutionInitiatedBody(_BaseWorkflowExecutionBody, Generic[InputsType]):
|
42
56
|
inputs: InputsType
|
57
|
+
# It is still the responsibility of the workflow server to populate this context. The SDK's
|
58
|
+
# Workflow Runner will always leave this field None.
|
59
|
+
#
|
60
|
+
# It's conceivable in a future where all `id`s are agnostic to display and reside in a third location,
|
61
|
+
# that the Workflow Runner can begin populating this field then.
|
62
|
+
display_context: Optional[WorkflowEventDisplayContext] = None
|
43
63
|
|
44
64
|
@field_serializer("inputs")
|
45
65
|
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
|