vellum-ai 0.14.7__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/__init__.py +2 -0
- vellum/client/core/client_wrapper.py +1 -1
- vellum/client/types/__init__.py +2 -0
- vellum/client/types/document_prompt_block.py +29 -0
- vellum/client/types/prompt_block.py +2 -0
- vellum/types/document_prompt_block.py +3 -0
- 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 +34 -2
- vellum/workflows/nodes/displayable/bases/api_node/node.py +1 -1
- 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.7.dist-info → vellum_ai-0.14.9.dist-info}/METADATA +1 -1
- {vellum_ai-0.14.7.dist-info → vellum_ai-0.14.9.dist-info}/RECORD +44 -38
- vellum_cli/__init__.py +9 -2
- vellum_cli/config.py +1 -0
- vellum_cli/init.py +6 -2
- vellum_cli/pull.py +1 -0
- vellum_cli/tests/test_init.py +194 -76
- vellum_cli/tests/test_pull.py +8 -0
- vellum_cli/tests/test_push.py +1 -0
- vellum_ee/workflows/display/nodes/base_node_display.py +4 -0
- vellum_ee/workflows/display/tests/test_vellum_workflow_display.py +114 -0
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +118 -3
- vellum_ee/workflows/display/types.py +1 -14
- vellum_ee/workflows/display/workflows/base_workflow_display.py +48 -19
- vellum_ee/workflows/display/workflows/vellum_workflow_display.py +12 -0
- vellum_ee/workflows/tests/test_server.py +1 -0
- {vellum_ai-0.14.7.dist-info → vellum_ai-0.14.9.dist-info}/LICENSE +0 -0
- {vellum_ai-0.14.7.dist-info → vellum_ai-0.14.9.dist-info}/WHEEL +0 -0
- {vellum_ai-0.14.7.dist-info → vellum_ai-0.14.9.dist-info}/entry_points.txt +0 -0
vellum/__init__.py
CHANGED
@@ -83,6 +83,7 @@ from .types import (
|
|
83
83
|
DocumentIndexIndexingConfigRequest,
|
84
84
|
DocumentIndexRead,
|
85
85
|
DocumentProcessingState,
|
86
|
+
DocumentPromptBlock,
|
86
87
|
DocumentRead,
|
87
88
|
DocumentStatus,
|
88
89
|
DocumentVellumValue,
|
@@ -623,6 +624,7 @@ __all__ = [
|
|
623
624
|
"DocumentIndexRead",
|
624
625
|
"DocumentIndexesListRequestStatus",
|
625
626
|
"DocumentProcessingState",
|
627
|
+
"DocumentPromptBlock",
|
626
628
|
"DocumentRead",
|
627
629
|
"DocumentStatus",
|
628
630
|
"DocumentVellumValue",
|
@@ -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
|
vellum/client/types/__init__.py
CHANGED
@@ -87,6 +87,7 @@ from .document_index_indexing_config import DocumentIndexIndexingConfig
|
|
87
87
|
from .document_index_indexing_config_request import DocumentIndexIndexingConfigRequest
|
88
88
|
from .document_index_read import DocumentIndexRead
|
89
89
|
from .document_processing_state import DocumentProcessingState
|
90
|
+
from .document_prompt_block import DocumentPromptBlock
|
90
91
|
from .document_read import DocumentRead
|
91
92
|
from .document_status import DocumentStatus
|
92
93
|
from .document_vellum_value import DocumentVellumValue
|
@@ -611,6 +612,7 @@ __all__ = [
|
|
611
612
|
"DocumentIndexIndexingConfigRequest",
|
612
613
|
"DocumentIndexRead",
|
613
614
|
"DocumentProcessingState",
|
615
|
+
"DocumentPromptBlock",
|
614
616
|
"DocumentRead",
|
615
617
|
"DocumentStatus",
|
616
618
|
"DocumentVellumValue",
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# This file was auto-generated by Fern from our API Definition.
|
2
|
+
|
3
|
+
from ..core.pydantic_utilities import UniversalBaseModel
|
4
|
+
import typing
|
5
|
+
from .prompt_block_state import PromptBlockState
|
6
|
+
from .ephemeral_prompt_cache_config import EphemeralPromptCacheConfig
|
7
|
+
from ..core.pydantic_utilities import IS_PYDANTIC_V2
|
8
|
+
import pydantic
|
9
|
+
|
10
|
+
|
11
|
+
class DocumentPromptBlock(UniversalBaseModel):
|
12
|
+
"""
|
13
|
+
A block that represents a document in a prompt template.
|
14
|
+
"""
|
15
|
+
|
16
|
+
block_type: typing.Literal["DOCUMENT"] = "DOCUMENT"
|
17
|
+
state: typing.Optional[PromptBlockState] = None
|
18
|
+
cache_config: typing.Optional[EphemeralPromptCacheConfig] = None
|
19
|
+
src: str
|
20
|
+
metadata: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = None
|
21
|
+
|
22
|
+
if IS_PYDANTIC_V2:
|
23
|
+
model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
|
24
|
+
else:
|
25
|
+
|
26
|
+
class Config:
|
27
|
+
frozen = True
|
28
|
+
smart_union = True
|
29
|
+
extra = pydantic.Extra.allow
|
@@ -8,6 +8,7 @@ from .rich_text_prompt_block import RichTextPromptBlock
|
|
8
8
|
from .audio_prompt_block import AudioPromptBlock
|
9
9
|
from .function_call_prompt_block import FunctionCallPromptBlock
|
10
10
|
from .image_prompt_block import ImagePromptBlock
|
11
|
+
from .document_prompt_block import DocumentPromptBlock
|
11
12
|
import typing
|
12
13
|
|
13
14
|
if typing.TYPE_CHECKING:
|
@@ -20,4 +21,5 @@ PromptBlock = typing.Union[
|
|
20
21
|
AudioPromptBlock,
|
21
22
|
FunctionCallPromptBlock,
|
22
23
|
ImagePromptBlock,
|
24
|
+
DocumentPromptBlock,
|
23
25
|
]
|
@@ -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
|