vellum-ai 0.14.12__py3-none-any.whl → 0.14.13__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 +3 -0
- vellum/workflows/events/workflow.py +23 -0
- vellum/workflows/inputs/base.py +26 -18
- vellum/workflows/inputs/tests/test_inputs.py +1 -1
- vellum/workflows/nodes/bases/base.py +7 -0
- vellum/workflows/nodes/core/inline_subworkflow_node/node.py +7 -0
- vellum/workflows/nodes/core/inline_subworkflow_node/tests/test_node.py +32 -0
- vellum/workflows/nodes/core/map_node/node.py +28 -7
- vellum/workflows/nodes/core/map_node/tests/test_node.py +31 -0
- vellum/workflows/nodes/core/try_node/node.py +7 -0
- vellum/workflows/nodes/core/try_node/tests/test_node.py +32 -0
- vellum/workflows/nodes/mocks.py +229 -2
- vellum/workflows/nodes/tests/__init__.py +0 -0
- vellum/workflows/nodes/tests/test_mocks.py +207 -0
- vellum/workflows/outputs/base.py +1 -1
- {vellum_ai-0.14.12.dist-info → vellum_ai-0.14.13.dist-info}/METADATA +1 -1
- {vellum_ai-0.14.12.dist-info → vellum_ai-0.14.13.dist-info}/RECORD +26 -24
- vellum_ee/workflows/display/nodes/base_node_display.py +20 -4
- vellum_ee/workflows/display/nodes/get_node_display_class.py +9 -0
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +24 -1
- vellum_ee/workflows/display/workflows/base_workflow_display.py +2 -2
- vellum_ee/workflows/display/workflows/tests/test_workflow_display.py +20 -0
- {vellum_ai-0.14.12.dist-info → vellum_ai-0.14.13.dist-info}/LICENSE +0 -0
- {vellum_ai-0.14.12.dist-info → vellum_ai-0.14.13.dist-info}/WHEEL +0 -0
- {vellum_ai-0.14.12.dist-info → vellum_ai-0.14.13.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.13",
|
22
22
|
}
|
23
23
|
headers["X_API_KEY"] = self.api_key
|
24
24
|
return headers
|
@@ -1,5 +1,6 @@
|
|
1
1
|
from uuid import UUID
|
2
2
|
from typing import TYPE_CHECKING, Any, Dict, Generator, Generic, Iterable, Literal, Optional, Type, Union
|
3
|
+
from typing_extensions import TypeGuard
|
3
4
|
|
4
5
|
from pydantic import field_serializer
|
5
6
|
|
@@ -182,3 +183,25 @@ WorkflowEvent = Union[
|
|
182
183
|
]
|
183
184
|
|
184
185
|
WorkflowEventStream = Generator[WorkflowEvent, None, None]
|
186
|
+
|
187
|
+
WorkflowExecutionEvent = Union[
|
188
|
+
WorkflowExecutionInitiatedEvent,
|
189
|
+
WorkflowExecutionStreamingEvent,
|
190
|
+
WorkflowExecutionRejectedEvent,
|
191
|
+
WorkflowExecutionPausedEvent,
|
192
|
+
WorkflowExecutionResumedEvent,
|
193
|
+
WorkflowExecutionFulfilledEvent,
|
194
|
+
WorkflowExecutionSnapshottedEvent,
|
195
|
+
]
|
196
|
+
|
197
|
+
|
198
|
+
def is_workflow_event(event: WorkflowEvent) -> TypeGuard[WorkflowExecutionEvent]:
|
199
|
+
return (
|
200
|
+
event.name == "workflow.execution.initiated"
|
201
|
+
or event.name == "workflow.execution.fulfilled"
|
202
|
+
or event.name == "workflow.execution.streaming"
|
203
|
+
or event.name == "workflow.execution.snapshotted"
|
204
|
+
or event.name == "workflow.execution.paused"
|
205
|
+
or event.name == "workflow.execution.resumed"
|
206
|
+
or event.name == "workflow.execution.rejected"
|
207
|
+
)
|
vellum/workflows/inputs/base.py
CHANGED
@@ -42,38 +42,46 @@ class BaseInputs(metaclass=_BaseInputsMeta):
|
|
42
42
|
__parent_class__: Type = type(None)
|
43
43
|
|
44
44
|
def __init__(self, **kwargs: Any) -> None:
|
45
|
+
"""
|
46
|
+
Initialize BaseInputs with provided keyword arguments.
|
47
|
+
|
48
|
+
Validation logic:
|
49
|
+
1. Ensures all required fields (non-Optional types) either:
|
50
|
+
- Have a value provided in kwargs, or
|
51
|
+
- Have a default value defined in the class
|
52
|
+
2. Validates that no None values are provided for required fields
|
53
|
+
3. Sets all provided values as attributes on the instance
|
54
|
+
|
55
|
+
Args:
|
56
|
+
**kwargs: Keyword arguments corresponding to the class's type annotations
|
57
|
+
|
58
|
+
Raises:
|
59
|
+
WorkflowInitializationException: If a required field is missing or None
|
60
|
+
"""
|
45
61
|
for name, field_type in self.__class__.__annotations__.items():
|
46
|
-
|
62
|
+
# Get the value (either from kwargs or class default)
|
63
|
+
value = kwargs.get(name)
|
64
|
+
has_default = name in vars(self.__class__)
|
65
|
+
|
66
|
+
if value is None and not has_default:
|
67
|
+
# Check if field_type allows None
|
47
68
|
origin = get_origin(field_type)
|
48
69
|
args = get_args(field_type)
|
49
70
|
if not (origin is Union and type(None) in args):
|
50
71
|
raise WorkflowInitializationException(
|
51
|
-
message="Required input variables should have defined value",
|
72
|
+
message=f"Required input variables {name} should have defined value",
|
52
73
|
code=WorkflowErrorCode.INVALID_INPUTS,
|
53
74
|
)
|
54
75
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
self._validate_input(value, field_type)
|
59
|
-
setattr(self, name, value)
|
76
|
+
# If value provided in kwargs, set it on the instance
|
77
|
+
if name in kwargs:
|
78
|
+
setattr(self, name, value)
|
60
79
|
|
61
80
|
def __iter__(self) -> Iterator[Tuple[InputReference, Any]]:
|
62
81
|
for input_descriptor in self.__class__:
|
63
82
|
if hasattr(self, input_descriptor.name):
|
64
83
|
yield (input_descriptor, getattr(self, input_descriptor.name))
|
65
84
|
|
66
|
-
def _validate_input(self, value: Any, field_type: Any) -> None:
|
67
|
-
if value is None:
|
68
|
-
# Check if field_type is Optional
|
69
|
-
origin = get_origin(field_type)
|
70
|
-
args = get_args(field_type)
|
71
|
-
if not (origin is Union and type(None) in args):
|
72
|
-
raise WorkflowInitializationException(
|
73
|
-
message="Required input variables should have defined value",
|
74
|
-
code=WorkflowErrorCode.INVALID_INPUTS,
|
75
|
-
)
|
76
|
-
|
77
85
|
@classmethod
|
78
86
|
def __get_pydantic_core_schema__(
|
79
87
|
cls, source_type: Type[Any], handler: GetCoreSchemaHandler
|
@@ -34,7 +34,7 @@ def test_base_inputs_empty_value():
|
|
34
34
|
|
35
35
|
# THEN it should raise a NodeException with the correct error message and code
|
36
36
|
assert exc_info.value.code == WorkflowErrorCode.INVALID_INPUTS
|
37
|
-
assert "Required input variables should have defined value"
|
37
|
+
assert "Required input variables required_string should have defined value" == str(exc_info.value)
|
38
38
|
|
39
39
|
|
40
40
|
def test_base_inputs_with_default():
|
@@ -19,6 +19,7 @@ from vellum.workflows.ports.port import Port
|
|
19
19
|
from vellum.workflows.references import ExternalInputReference
|
20
20
|
from vellum.workflows.references.execution_count import ExecutionCountReference
|
21
21
|
from vellum.workflows.references.node import NodeReference
|
22
|
+
from vellum.workflows.references.output import OutputReference
|
22
23
|
from vellum.workflows.state.base import BaseState
|
23
24
|
from vellum.workflows.state.context import WorkflowContext
|
24
25
|
from vellum.workflows.types.core import MergeBehavior
|
@@ -118,6 +119,11 @@ class BaseNodeMeta(type):
|
|
118
119
|
node_class.Trigger.node_class = node_class
|
119
120
|
node_class.ExternalInputs.__parent_class__ = node_class
|
120
121
|
node_class.__id__ = uuid4_from_hash(node_class.__qualname__)
|
122
|
+
node_class.__output_ids__ = {
|
123
|
+
ref.name: uuid4_from_hash(f"{node_class.__id__}|{ref.name}")
|
124
|
+
for ref in node_class.Outputs
|
125
|
+
if isinstance(ref, OutputReference)
|
126
|
+
}
|
121
127
|
return node_class
|
122
128
|
|
123
129
|
@property
|
@@ -236,6 +242,7 @@ NodeRunResponse = Union[BaseOutputs, Iterator[BaseOutput]]
|
|
236
242
|
|
237
243
|
class BaseNode(Generic[StateType], metaclass=BaseNodeMeta):
|
238
244
|
__id__: UUID = uuid4_from_hash(__qualname__)
|
245
|
+
__output_ids__: Dict[str, UUID] = {}
|
239
246
|
state: StateType
|
240
247
|
_context: WorkflowContext
|
241
248
|
_inputs: MappingProxyType[NodeReference, Any]
|
@@ -3,6 +3,7 @@ from typing import TYPE_CHECKING, Any, ClassVar, Dict, Generic, Iterator, Option
|
|
3
3
|
from vellum.workflows.constants import undefined
|
4
4
|
from vellum.workflows.context import execution_context, get_parent_context
|
5
5
|
from vellum.workflows.errors.types import WorkflowErrorCode
|
6
|
+
from vellum.workflows.events.workflow import is_workflow_event
|
6
7
|
from vellum.workflows.exceptions import NodeException
|
7
8
|
from vellum.workflows.inputs.base import BaseInputs
|
8
9
|
from vellum.workflows.nodes.bases.base import BaseNode, BaseNodeMeta
|
@@ -86,6 +87,12 @@ class InlineSubworkflowNode(
|
|
86
87
|
|
87
88
|
for event in subworkflow_stream:
|
88
89
|
self._context._emit_subworkflow_event(event)
|
90
|
+
|
91
|
+
if not is_workflow_event(event):
|
92
|
+
continue
|
93
|
+
if event.workflow_definition != self.subworkflow:
|
94
|
+
continue
|
95
|
+
|
89
96
|
if event.name == "workflow.execution.streaming":
|
90
97
|
if event.output.is_fulfilled:
|
91
98
|
fulfilled_output_names.add(event.output.name)
|
@@ -3,6 +3,7 @@ import pytest
|
|
3
3
|
from vellum.workflows.inputs.base import BaseInputs
|
4
4
|
from vellum.workflows.nodes.bases.base import BaseNode
|
5
5
|
from vellum.workflows.nodes.core.inline_subworkflow_node.node import InlineSubworkflowNode
|
6
|
+
from vellum.workflows.nodes.core.try_node.node import TryNode
|
6
7
|
from vellum.workflows.outputs.base import BaseOutput
|
7
8
|
from vellum.workflows.state.base import BaseState
|
8
9
|
from vellum.workflows.workflows.base import BaseWorkflow
|
@@ -55,3 +56,34 @@ def test_inline_subworkflow_node__support_inputs_as_attributes():
|
|
55
56
|
assert events == [
|
56
57
|
BaseOutput(name="out", value="bar"),
|
57
58
|
]
|
59
|
+
|
60
|
+
|
61
|
+
def test_inline_subworkflow_node__nested_try():
|
62
|
+
"""
|
63
|
+
Ensure that the nested try node doesn't affect the subworkflow node's outputs
|
64
|
+
"""
|
65
|
+
|
66
|
+
# GIVEN a nested try node
|
67
|
+
@TryNode.wrap()
|
68
|
+
class InnerNode(BaseNode):
|
69
|
+
class Outputs:
|
70
|
+
foo = "hello"
|
71
|
+
|
72
|
+
# AND a subworkflow
|
73
|
+
class Subworkflow(BaseWorkflow):
|
74
|
+
graph = InnerNode
|
75
|
+
|
76
|
+
class Outputs(BaseWorkflow.Outputs):
|
77
|
+
bar = InnerNode.Outputs.foo
|
78
|
+
|
79
|
+
# AND an outer try node referencing that subworkflow
|
80
|
+
class OuterNode(InlineSubworkflowNode):
|
81
|
+
subworkflow = Subworkflow
|
82
|
+
|
83
|
+
# WHEN we run the try node
|
84
|
+
stream = OuterNode().run()
|
85
|
+
events = list(stream)
|
86
|
+
|
87
|
+
# THEN we only have the outer node's outputs
|
88
|
+
valid_events = [e for e in events if e.name == "bar"]
|
89
|
+
assert len(valid_events) == len(events)
|
@@ -1,4 +1,5 @@
|
|
1
1
|
from collections import defaultdict
|
2
|
+
import logging
|
2
3
|
from queue import Empty, Queue
|
3
4
|
from threading import Thread
|
4
5
|
from typing import (
|
@@ -19,6 +20,7 @@ from typing import (
|
|
19
20
|
from vellum.workflows.context import ExecutionContext, execution_context, get_execution_context
|
20
21
|
from vellum.workflows.descriptors.base import BaseDescriptor
|
21
22
|
from vellum.workflows.errors.types import WorkflowErrorCode
|
23
|
+
from vellum.workflows.events.workflow import is_workflow_event
|
22
24
|
from vellum.workflows.exceptions import NodeException
|
23
25
|
from vellum.workflows.inputs.base import BaseInputs
|
24
26
|
from vellum.workflows.nodes.bases.base_adornment_node import BaseAdornmentNode
|
@@ -33,6 +35,8 @@ from vellum.workflows.workflows.event_filters import all_workflow_event_filter
|
|
33
35
|
if TYPE_CHECKING:
|
34
36
|
from vellum.workflows.events.workflow import WorkflowEvent
|
35
37
|
|
38
|
+
logger = logging.getLogger(__name__)
|
39
|
+
|
36
40
|
MapNodeItemType = TypeVar("MapNodeItemType")
|
37
41
|
|
38
42
|
|
@@ -104,19 +108,36 @@ class MapNode(BaseAdornmentNode[StateType], Generic[StateType, MapNodeItemType])
|
|
104
108
|
subworkflow_event = map_node_event[1]
|
105
109
|
self._context._emit_subworkflow_event(subworkflow_event)
|
106
110
|
|
111
|
+
if not is_workflow_event(subworkflow_event):
|
112
|
+
continue
|
113
|
+
|
114
|
+
if subworkflow_event.workflow_definition != self.subworkflow:
|
115
|
+
continue
|
116
|
+
|
107
117
|
if subworkflow_event.name == "workflow.execution.initiated":
|
108
118
|
for output_name in mapped_items.keys():
|
109
119
|
yield BaseOutput(name=output_name, delta=(None, index, "INITIATED"))
|
110
120
|
|
111
121
|
elif subworkflow_event.name == "workflow.execution.fulfilled":
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
122
|
+
for output_reference, output_value in subworkflow_event.outputs:
|
123
|
+
if not isinstance(output_reference, OutputReference):
|
124
|
+
logger.error(
|
125
|
+
"Invalid key to map node's subworkflow event outputs",
|
126
|
+
extra={"output_reference_type": type(output_reference)},
|
127
|
+
)
|
128
|
+
continue
|
129
|
+
|
130
|
+
output_mapped_items = mapped_items[output_reference.name]
|
131
|
+
if index < 0 or index >= len(output_mapped_items):
|
132
|
+
logger.error(
|
133
|
+
"Invalid map node index", extra={"index": index, "output_name": output_reference.name}
|
134
|
+
)
|
135
|
+
continue
|
136
|
+
|
137
|
+
output_mapped_items[index] = output_value
|
117
138
|
yield BaseOutput(
|
118
|
-
name=
|
119
|
-
delta=(
|
139
|
+
name=output_reference.name,
|
140
|
+
delta=(output_value, index, "FULFILLED"),
|
120
141
|
)
|
121
142
|
|
122
143
|
fulfilled_iterations[index] = True
|
@@ -3,8 +3,10 @@ import time
|
|
3
3
|
from vellum.workflows.inputs.base import BaseInputs
|
4
4
|
from vellum.workflows.nodes.bases import BaseNode
|
5
5
|
from vellum.workflows.nodes.core.map_node.node import MapNode
|
6
|
+
from vellum.workflows.nodes.core.try_node.node import TryNode
|
6
7
|
from vellum.workflows.outputs.base import BaseOutput, BaseOutputs
|
7
8
|
from vellum.workflows.state.base import BaseState, StateMeta
|
9
|
+
from vellum.workflows.workflows.base import BaseWorkflow
|
8
10
|
|
9
11
|
|
10
12
|
def test_map_node__use_parent_inputs_and_state():
|
@@ -85,3 +87,32 @@ def test_map_node__empty_list():
|
|
85
87
|
# THEN the node should return an empty output
|
86
88
|
fulfilled_output = outputs[-1]
|
87
89
|
assert fulfilled_output == BaseOutput(name="value", value=[])
|
90
|
+
|
91
|
+
|
92
|
+
def test_map_node__inner_try():
|
93
|
+
# GIVEN a try wrapped node
|
94
|
+
@TryNode.wrap()
|
95
|
+
class InnerNode(BaseNode):
|
96
|
+
class Outputs(BaseNode.Outputs):
|
97
|
+
foo: str
|
98
|
+
|
99
|
+
# AND a workflow using that node
|
100
|
+
class SimpleMapNodeWorkflow(BaseWorkflow[MapNode.SubworkflowInputs, BaseState]):
|
101
|
+
graph = InnerNode
|
102
|
+
|
103
|
+
class Outputs(BaseWorkflow.Outputs):
|
104
|
+
final_output = InnerNode.Outputs.foo
|
105
|
+
|
106
|
+
# AND a map node referencing that workflow
|
107
|
+
class SimpleMapNode(MapNode):
|
108
|
+
items = ["hello", "world"]
|
109
|
+
subworkflow = SimpleMapNodeWorkflow
|
110
|
+
max_concurrency = 4
|
111
|
+
|
112
|
+
# WHEN we run the workflow
|
113
|
+
stream = SimpleMapNode().run()
|
114
|
+
outputs = list(stream)
|
115
|
+
|
116
|
+
# THEN the workflow should succeed
|
117
|
+
assert outputs[-1].name == "final_output"
|
118
|
+
assert len(outputs[-1].value) == 2
|
@@ -2,6 +2,7 @@ from typing import Callable, Generic, Iterator, Optional, Set, Type
|
|
2
2
|
|
3
3
|
from vellum.workflows.context import execution_context, get_parent_context
|
4
4
|
from vellum.workflows.errors.types import WorkflowError, WorkflowErrorCode
|
5
|
+
from vellum.workflows.events.workflow import is_workflow_event
|
5
6
|
from vellum.workflows.exceptions import NodeException
|
6
7
|
from vellum.workflows.nodes.bases import BaseNode
|
7
8
|
from vellum.workflows.nodes.bases.base_adornment_node import BaseAdornmentNode
|
@@ -47,6 +48,12 @@ class TryNode(BaseAdornmentNode[StateType], Generic[StateType]):
|
|
47
48
|
if exception:
|
48
49
|
continue
|
49
50
|
|
51
|
+
if not is_workflow_event(event):
|
52
|
+
continue
|
53
|
+
|
54
|
+
if event.workflow_definition != self.subworkflow:
|
55
|
+
continue
|
56
|
+
|
50
57
|
if event.name == "workflow.execution.streaming":
|
51
58
|
if event.output.is_fulfilled:
|
52
59
|
fulfilled_output_names.add(event.output.name)
|
@@ -10,6 +10,7 @@ from vellum.workflows.outputs import BaseOutputs
|
|
10
10
|
from vellum.workflows.outputs.base import BaseOutput
|
11
11
|
from vellum.workflows.state.base import BaseState, StateMeta
|
12
12
|
from vellum.workflows.state.context import WorkflowContext
|
13
|
+
from vellum.workflows.workflows.base import BaseWorkflow
|
13
14
|
|
14
15
|
|
15
16
|
def test_try_node__on_error_code__successfully_caught():
|
@@ -126,3 +127,34 @@ def test_try_node__resolved_inputs():
|
|
126
127
|
foo = State.counter
|
127
128
|
|
128
129
|
assert MyNode.foo.types == (float,)
|
130
|
+
|
131
|
+
|
132
|
+
def test_try_node__nested_try():
|
133
|
+
"""
|
134
|
+
Ensure that the nested try node doesn't affect the outer try node's outputs
|
135
|
+
"""
|
136
|
+
|
137
|
+
# GIVEN a nested try node
|
138
|
+
@TryNode.wrap()
|
139
|
+
class InnerNode(BaseNode):
|
140
|
+
class Outputs:
|
141
|
+
foo = "hello"
|
142
|
+
|
143
|
+
# AND a subworkflow
|
144
|
+
class Subworkflow(BaseWorkflow):
|
145
|
+
graph = InnerNode
|
146
|
+
|
147
|
+
class Outputs(BaseWorkflow.Outputs):
|
148
|
+
bar = InnerNode.Outputs.foo
|
149
|
+
|
150
|
+
# AND an outer try node referencing that subworkflow
|
151
|
+
class OuterNode(TryNode):
|
152
|
+
subworkflow = Subworkflow
|
153
|
+
|
154
|
+
# WHEN we run the try node
|
155
|
+
stream = OuterNode().run()
|
156
|
+
events = list(stream)
|
157
|
+
|
158
|
+
# THEN we only have the outer node's outputs
|
159
|
+
valid_events = [e for e in events if e.name == "bar"]
|
160
|
+
assert len(valid_events) == len(events)
|
vellum/workflows/nodes/mocks.py
CHANGED
@@ -1,10 +1,102 @@
|
|
1
|
-
from
|
1
|
+
from functools import reduce
|
2
|
+
from uuid import UUID
|
3
|
+
from typing import TYPE_CHECKING, Any, List, Literal, Optional, Sequence, Type, Union
|
2
4
|
|
3
|
-
from pydantic import ConfigDict
|
5
|
+
from pydantic import ConfigDict, ValidationError
|
4
6
|
|
5
7
|
from vellum.client.core.pydantic_utilities import UniversalBaseModel
|
8
|
+
from vellum.client.types.array_vellum_value import ArrayVellumValue
|
9
|
+
from vellum.client.types.vellum_value import VellumValue
|
6
10
|
from vellum.workflows.descriptors.base import BaseDescriptor
|
11
|
+
from vellum.workflows.errors.types import WorkflowErrorCode
|
12
|
+
from vellum.workflows.exceptions import WorkflowInitializationException
|
7
13
|
from vellum.workflows.outputs.base import BaseOutputs
|
14
|
+
from vellum.workflows.references.constant import ConstantValueReference
|
15
|
+
|
16
|
+
if TYPE_CHECKING:
|
17
|
+
from vellum.workflows import BaseWorkflow
|
18
|
+
|
19
|
+
import logging
|
20
|
+
|
21
|
+
logger = logging.getLogger(__name__)
|
22
|
+
|
23
|
+
|
24
|
+
class _RawLogicalCondition(UniversalBaseModel):
|
25
|
+
type: Literal["LOGICAL_CONDITION"] = "LOGICAL_CONDITION"
|
26
|
+
lhs_variable_id: UUID
|
27
|
+
operator: Literal["==", ">", ">=", "<", "<=", "!="]
|
28
|
+
rhs_variable_id: UUID
|
29
|
+
|
30
|
+
|
31
|
+
class _RawLogicalConditionGroup(UniversalBaseModel):
|
32
|
+
type: Literal["LOGICAL_CONDITION_GROUP"] = "LOGICAL_CONDITION_GROUP"
|
33
|
+
conditions: List["_RawLogicalExpression"]
|
34
|
+
combinator: Literal["AND", "OR"]
|
35
|
+
negated: bool
|
36
|
+
|
37
|
+
|
38
|
+
_RawLogicalExpression = Union[_RawLogicalCondition, _RawLogicalConditionGroup]
|
39
|
+
|
40
|
+
|
41
|
+
class _RawLogicalExpressionVariable(UniversalBaseModel):
|
42
|
+
id: UUID
|
43
|
+
|
44
|
+
|
45
|
+
class _RawMockWorkflowNodeExecutionConstantValuePointer(_RawLogicalExpressionVariable):
|
46
|
+
type: Literal["CONSTANT_VALUE"] = "CONSTANT_VALUE"
|
47
|
+
variable_value: VellumValue
|
48
|
+
|
49
|
+
|
50
|
+
class _RawMockWorkflowNodeExecutionNodeExecutionCounterPointer(_RawLogicalExpressionVariable):
|
51
|
+
type: Literal["EXECUTION_COUNTER"] = "EXECUTION_COUNTER"
|
52
|
+
node_id: UUID
|
53
|
+
|
54
|
+
|
55
|
+
class _RawMockWorkflowNodeExecutionInputVariablePointer(_RawLogicalExpressionVariable):
|
56
|
+
type: Literal["INPUT_VARIABLE"] = "INPUT_VARIABLE"
|
57
|
+
input_variable_id: UUID
|
58
|
+
|
59
|
+
|
60
|
+
class _RawMockWorkflowNodeExecutionNodeOutputPointer(_RawLogicalExpressionVariable):
|
61
|
+
type: Literal["NODE_OUTPUT"] = "NODE_OUTPUT"
|
62
|
+
node_id: UUID
|
63
|
+
input_id: UUID
|
64
|
+
|
65
|
+
|
66
|
+
class _RawMockWorkflowNodeExecutionNodeInputPointer(_RawLogicalExpressionVariable):
|
67
|
+
type: Literal["NODE_INPUT"] = "NODE_INPUT"
|
68
|
+
node_id: UUID
|
69
|
+
input_id: UUID
|
70
|
+
|
71
|
+
|
72
|
+
_RawMockWorkflowNodeExecutionValuePointer = Union[
|
73
|
+
_RawMockWorkflowNodeExecutionConstantValuePointer,
|
74
|
+
_RawMockWorkflowNodeExecutionNodeExecutionCounterPointer,
|
75
|
+
_RawMockWorkflowNodeExecutionInputVariablePointer,
|
76
|
+
_RawMockWorkflowNodeExecutionNodeOutputPointer,
|
77
|
+
_RawMockWorkflowNodeExecutionNodeInputPointer,
|
78
|
+
]
|
79
|
+
|
80
|
+
|
81
|
+
class _RawMockWorkflowNodeWhenCondition(UniversalBaseModel):
|
82
|
+
expression: _RawLogicalExpression
|
83
|
+
variables: List[_RawMockWorkflowNodeExecutionValuePointer]
|
84
|
+
|
85
|
+
|
86
|
+
class _RawMockWorkflowNodeThenOutput(UniversalBaseModel):
|
87
|
+
output_id: UUID
|
88
|
+
value: _RawMockWorkflowNodeExecutionValuePointer
|
89
|
+
|
90
|
+
|
91
|
+
class _RawMockWorkflowNodeExecution(UniversalBaseModel):
|
92
|
+
when_condition: _RawMockWorkflowNodeWhenCondition
|
93
|
+
then_outputs: List[_RawMockWorkflowNodeThenOutput]
|
94
|
+
|
95
|
+
|
96
|
+
class _RawMockWorkflowNodeConfig(UniversalBaseModel):
|
97
|
+
type: Literal["WORKFLOW_NODE_OUTPUT"] = "WORKFLOW_NODE_OUTPUT"
|
98
|
+
node_id: UUID
|
99
|
+
mock_executions: List[_RawMockWorkflowNodeExecution]
|
8
100
|
|
9
101
|
|
10
102
|
class MockNodeExecution(UniversalBaseModel):
|
@@ -13,5 +105,140 @@ class MockNodeExecution(UniversalBaseModel):
|
|
13
105
|
|
14
106
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
15
107
|
|
108
|
+
@staticmethod
|
109
|
+
def validate_all(
|
110
|
+
raw_mock_workflow_node_configs: Optional[List[Any]],
|
111
|
+
workflow: Type["BaseWorkflow"],
|
112
|
+
) -> Optional[List["MockNodeExecution"]]:
|
113
|
+
if not raw_mock_workflow_node_configs:
|
114
|
+
return None
|
115
|
+
|
116
|
+
ArrayVellumValue.model_rebuild()
|
117
|
+
try:
|
118
|
+
mock_workflow_node_configs = [
|
119
|
+
_RawMockWorkflowNodeConfig.model_validate(raw_mock_workflow_node_config)
|
120
|
+
for raw_mock_workflow_node_config in raw_mock_workflow_node_configs
|
121
|
+
]
|
122
|
+
except ValidationError as e:
|
123
|
+
raise WorkflowInitializationException(
|
124
|
+
message="Failed to validate mock node executions",
|
125
|
+
code=WorkflowErrorCode.INVALID_INPUTS,
|
126
|
+
) from e
|
127
|
+
|
128
|
+
nodes = {node.__id__: node for node in workflow.get_nodes()}
|
129
|
+
node_output_name_by_id = {
|
130
|
+
node.__output_ids__[output.name]: output.name for node in workflow.get_nodes() for output in node.Outputs
|
131
|
+
}
|
132
|
+
|
133
|
+
# We need to support the old way that the Vellum App's WorkflowRunner used to define Node Mocks in order to
|
134
|
+
# avoid needing to update the mock resolution strategy that it and the frontend uses. The path towards
|
135
|
+
# cleaning this up will go as follows:
|
136
|
+
# 1. Release Mock support in SDK-Enabled Workflows
|
137
|
+
# 2. Deprecate Mock support in non-SDK enabled Workflows, encouraging users to migrate to SDK-enabled Workflows
|
138
|
+
# 3. Remove the old mock resolution strategy
|
139
|
+
# 4. Update this SDK to handle the new mock resolution strategy with WorkflowValueDescriptors
|
140
|
+
# 5. Cutover the Vellum App to the new mock resolution strategy
|
141
|
+
# 6. Remove the old mock resolution strategy from this SDK
|
142
|
+
def _translate_raw_logical_expression(
|
143
|
+
raw_logical_expression: _RawLogicalExpression,
|
144
|
+
raw_variables: List[_RawMockWorkflowNodeExecutionValuePointer],
|
145
|
+
) -> BaseDescriptor:
|
146
|
+
if raw_logical_expression.type == "LOGICAL_CONDITION":
|
147
|
+
return _translate_raw_logical_condition(raw_logical_expression, raw_variables)
|
148
|
+
else:
|
149
|
+
return _translate_raw_logical_condition_group(raw_logical_expression, raw_variables)
|
150
|
+
|
151
|
+
def _translate_raw_logical_condition_group(
|
152
|
+
raw_logical_condition_group: _RawLogicalConditionGroup,
|
153
|
+
raw_variables: List[_RawMockWorkflowNodeExecutionValuePointer],
|
154
|
+
) -> BaseDescriptor:
|
155
|
+
if not raw_logical_condition_group.conditions:
|
156
|
+
return ConstantValueReference(True)
|
157
|
+
|
158
|
+
conditions = [
|
159
|
+
_translate_raw_logical_expression(condition, raw_variables)
|
160
|
+
for condition in raw_logical_condition_group.conditions
|
161
|
+
]
|
162
|
+
return reduce(
|
163
|
+
lambda acc, condition: (
|
164
|
+
acc and condition if raw_logical_condition_group.combinator == "AND" else acc or condition
|
165
|
+
),
|
166
|
+
conditions,
|
167
|
+
)
|
168
|
+
|
169
|
+
def _translate_raw_logical_condition(
|
170
|
+
raw_logical_condition: _RawLogicalCondition,
|
171
|
+
raw_variables: List[_RawMockWorkflowNodeExecutionValuePointer],
|
172
|
+
) -> BaseDescriptor:
|
173
|
+
variable_by_id = {v.id: v for v in raw_variables}
|
174
|
+
lhs = _translate_raw_logical_expression_variable(variable_by_id[raw_logical_condition.lhs_variable_id])
|
175
|
+
rhs = _translate_raw_logical_expression_variable(variable_by_id[raw_logical_condition.rhs_variable_id])
|
176
|
+
if raw_logical_condition.operator == ">":
|
177
|
+
return lhs.greater_than(rhs)
|
178
|
+
elif raw_logical_condition.operator == ">=":
|
179
|
+
return lhs.greater_than_or_equal_to(rhs)
|
180
|
+
elif raw_logical_condition.operator == "<":
|
181
|
+
return lhs.less_than(rhs)
|
182
|
+
elif raw_logical_condition.operator == "<=":
|
183
|
+
return lhs.less_than_or_equal_to(rhs)
|
184
|
+
elif raw_logical_condition.operator == "==":
|
185
|
+
return lhs.equals(rhs)
|
186
|
+
elif raw_logical_condition.operator == "!=":
|
187
|
+
return lhs.does_not_equal(rhs)
|
188
|
+
else:
|
189
|
+
raise WorkflowInitializationException(f"Unsupported logical operator: {raw_logical_condition.operator}")
|
190
|
+
|
191
|
+
def _translate_raw_logical_expression_variable(
|
192
|
+
raw_variable: _RawMockWorkflowNodeExecutionValuePointer,
|
193
|
+
) -> BaseDescriptor:
|
194
|
+
if raw_variable.type == "CONSTANT_VALUE":
|
195
|
+
return ConstantValueReference(raw_variable.variable_value.value)
|
196
|
+
elif raw_variable.type == "EXECUTION_COUNTER":
|
197
|
+
node = nodes[raw_variable.node_id]
|
198
|
+
return node.Execution.count
|
199
|
+
else:
|
200
|
+
raise WorkflowInitializationException(f"Unsupported logical expression type: {raw_variable.type}")
|
201
|
+
|
202
|
+
mock_node_executions = []
|
203
|
+
for mock_workflow_node_config in mock_workflow_node_configs:
|
204
|
+
for mock_execution in mock_workflow_node_config.mock_executions:
|
205
|
+
try:
|
206
|
+
when_condition = _translate_raw_logical_expression(
|
207
|
+
mock_execution.when_condition.expression,
|
208
|
+
mock_execution.when_condition.variables,
|
209
|
+
)
|
210
|
+
|
211
|
+
then_outputs = nodes[mock_workflow_node_config.node_id].Outputs()
|
212
|
+
for then_output in mock_execution.then_outputs:
|
213
|
+
node_output_name = node_output_name_by_id.get(then_output.output_id)
|
214
|
+
if node_output_name is None:
|
215
|
+
raise WorkflowInitializationException(
|
216
|
+
f"Output {then_output.output_id} not found in node {mock_workflow_node_config.node_id}"
|
217
|
+
)
|
218
|
+
|
219
|
+
resolved_output_reference = _translate_raw_logical_expression_variable(then_output.value)
|
220
|
+
if isinstance(resolved_output_reference, ConstantValueReference):
|
221
|
+
setattr(
|
222
|
+
then_outputs,
|
223
|
+
node_output_name,
|
224
|
+
resolved_output_reference._value,
|
225
|
+
)
|
226
|
+
else:
|
227
|
+
raise WorkflowInitializationException(
|
228
|
+
f"Unsupported resolved output reference type: {type(resolved_output_reference)}"
|
229
|
+
)
|
230
|
+
|
231
|
+
mock_node_executions.append(
|
232
|
+
MockNodeExecution(
|
233
|
+
when_condition=when_condition,
|
234
|
+
then_outputs=then_outputs,
|
235
|
+
)
|
236
|
+
)
|
237
|
+
except Exception as e:
|
238
|
+
logger.exception("Failed to validate mock node execution", exc_info=e)
|
239
|
+
continue
|
240
|
+
|
241
|
+
return mock_node_executions
|
242
|
+
|
16
243
|
|
17
244
|
MockNodeExecutionArg = Sequence[Union[BaseOutputs, MockNodeExecution]]
|
File without changes
|
@@ -0,0 +1,207 @@
|
|
1
|
+
import uuid
|
2
|
+
|
3
|
+
from vellum.client.types.string_vellum_value import StringVellumValue
|
4
|
+
from vellum.workflows import BaseWorkflow
|
5
|
+
from vellum.workflows.nodes import InlinePromptNode
|
6
|
+
from vellum.workflows.nodes.bases.base import BaseNode
|
7
|
+
from vellum.workflows.nodes.mocks import MockNodeExecution
|
8
|
+
from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
|
9
|
+
from vellum_ee.workflows.display.nodes.types import NodeOutputDisplay
|
10
|
+
|
11
|
+
|
12
|
+
def test_mocks__parse_from_app():
|
13
|
+
# GIVEN a PromptNode
|
14
|
+
class PromptNode(InlinePromptNode):
|
15
|
+
pass
|
16
|
+
|
17
|
+
# AND a workflow class with that PromptNode
|
18
|
+
class MyWorkflow(BaseWorkflow):
|
19
|
+
graph = PromptNode
|
20
|
+
|
21
|
+
# AND a mock workflow node execution from the app
|
22
|
+
raw_mock_workflow_node_execution = [
|
23
|
+
{
|
24
|
+
"type": "WORKFLOW_NODE_OUTPUT",
|
25
|
+
"node_id": str(PromptNode.__id__),
|
26
|
+
"mock_executions": [
|
27
|
+
{
|
28
|
+
"when_condition": {
|
29
|
+
"expression": {
|
30
|
+
"type": "LOGICAL_CONDITION_GROUP",
|
31
|
+
"combinator": "AND",
|
32
|
+
"negated": False,
|
33
|
+
"conditions": [
|
34
|
+
{
|
35
|
+
"type": "LOGICAL_CONDITION",
|
36
|
+
"lhs_variable_id": "e60902d5-6892-4916-80c1-f0130af52322",
|
37
|
+
"operator": ">=",
|
38
|
+
"rhs_variable_id": "5c1bbb24-c288-49cb-a9b7-0c6f38a86037",
|
39
|
+
}
|
40
|
+
],
|
41
|
+
},
|
42
|
+
"variables": [
|
43
|
+
{
|
44
|
+
"type": "EXECUTION_COUNTER",
|
45
|
+
"node_id": str(PromptNode.__id__),
|
46
|
+
"id": "e60902d5-6892-4916-80c1-f0130af52322",
|
47
|
+
},
|
48
|
+
{
|
49
|
+
"type": "CONSTANT_VALUE",
|
50
|
+
"variable_value": {"type": "NUMBER", "value": 0},
|
51
|
+
"id": "5c1bbb24-c288-49cb-a9b7-0c6f38a86037",
|
52
|
+
},
|
53
|
+
],
|
54
|
+
},
|
55
|
+
"then_outputs": [
|
56
|
+
{
|
57
|
+
"output_id": "9e6dc5d3-8ea0-4346-8a2a-7cce5495755b",
|
58
|
+
"value": {
|
59
|
+
"id": "27006b2a-fa81-430c-a0b2-c66a9351fc68",
|
60
|
+
"type": "CONSTANT_VALUE",
|
61
|
+
"variable_value": {"type": "STRING", "value": "Hello"},
|
62
|
+
},
|
63
|
+
},
|
64
|
+
{
|
65
|
+
"output_id": "60305ffd-60b0-42aa-b54e-4fdae0f8c28a",
|
66
|
+
"value": {
|
67
|
+
"id": "4559c778-6e27-4cfe-a460-734ba62a5082",
|
68
|
+
"type": "CONSTANT_VALUE",
|
69
|
+
"variable_value": {"type": "ARRAY", "value": [{"type": "STRING", "value": "Hello"}]},
|
70
|
+
},
|
71
|
+
},
|
72
|
+
],
|
73
|
+
}
|
74
|
+
],
|
75
|
+
}
|
76
|
+
]
|
77
|
+
|
78
|
+
# WHEN we parse the mock workflow node execution
|
79
|
+
node_output_mocks = MockNodeExecution.validate_all(
|
80
|
+
raw_mock_workflow_node_execution,
|
81
|
+
MyWorkflow,
|
82
|
+
)
|
83
|
+
|
84
|
+
# THEN we get a list of MockNodeExecution objects
|
85
|
+
assert node_output_mocks
|
86
|
+
assert len(node_output_mocks) == 1
|
87
|
+
assert node_output_mocks[0] == MockNodeExecution(
|
88
|
+
when_condition=PromptNode.Execution.count.greater_than_or_equal_to(0.0),
|
89
|
+
then_outputs=PromptNode.Outputs(
|
90
|
+
text="Hello",
|
91
|
+
results=[
|
92
|
+
StringVellumValue(value="Hello"),
|
93
|
+
],
|
94
|
+
),
|
95
|
+
)
|
96
|
+
|
97
|
+
|
98
|
+
def test_mocks__parse_none_still_runs():
|
99
|
+
# GIVEN a Base Node
|
100
|
+
class StartNode(BaseNode):
|
101
|
+
class Outputs(BaseNode.Outputs):
|
102
|
+
foo: str
|
103
|
+
|
104
|
+
# AND a workflow class with that Node
|
105
|
+
class MyWorkflow(BaseWorkflow):
|
106
|
+
graph = StartNode
|
107
|
+
|
108
|
+
class Outputs(BaseWorkflow.Outputs):
|
109
|
+
final_value = StartNode.Outputs.foo
|
110
|
+
|
111
|
+
# AND we parsed `None` on `MockNodeExecution`
|
112
|
+
node_output_mocks = MockNodeExecution.validate_all(
|
113
|
+
None,
|
114
|
+
MyWorkflow,
|
115
|
+
)
|
116
|
+
|
117
|
+
# WHEN we run the workflow
|
118
|
+
workflow = MyWorkflow()
|
119
|
+
final_event = workflow.run(node_output_mocks=node_output_mocks)
|
120
|
+
|
121
|
+
# THEN it was successful
|
122
|
+
assert final_event.name == "workflow.execution.fulfilled"
|
123
|
+
|
124
|
+
|
125
|
+
def test_mocks__use_id_from_display():
|
126
|
+
# GIVEN a Base Node
|
127
|
+
class StartNode(BaseNode):
|
128
|
+
class Outputs(BaseNode.Outputs):
|
129
|
+
foo: str
|
130
|
+
|
131
|
+
# AND a workflow class with that Node
|
132
|
+
class MyWorkflow(BaseWorkflow):
|
133
|
+
graph = StartNode
|
134
|
+
|
135
|
+
class Outputs(BaseWorkflow.Outputs):
|
136
|
+
final_value = StartNode.Outputs.foo
|
137
|
+
|
138
|
+
# AND a display class on that Base Node
|
139
|
+
node_output_id = uuid.uuid4()
|
140
|
+
|
141
|
+
class StartNodeDisplay(BaseNodeDisplay[StartNode]):
|
142
|
+
output_display = {StartNode.Outputs.foo: NodeOutputDisplay(id=node_output_id, name="foo")}
|
143
|
+
|
144
|
+
# AND a mock workflow node execution from the app
|
145
|
+
raw_mock_workflow_node_execution = [
|
146
|
+
{
|
147
|
+
"type": "WORKFLOW_NODE_OUTPUT",
|
148
|
+
"node_id": str(StartNode.__id__),
|
149
|
+
"mock_executions": [
|
150
|
+
{
|
151
|
+
"when_condition": {
|
152
|
+
"expression": {
|
153
|
+
"type": "LOGICAL_CONDITION_GROUP",
|
154
|
+
"combinator": "AND",
|
155
|
+
"negated": False,
|
156
|
+
"conditions": [
|
157
|
+
{
|
158
|
+
"type": "LOGICAL_CONDITION",
|
159
|
+
"lhs_variable_id": "e60902d5-6892-4916-80c1-f0130af52322",
|
160
|
+
"operator": ">=",
|
161
|
+
"rhs_variable_id": "5c1bbb24-c288-49cb-a9b7-0c6f38a86037",
|
162
|
+
}
|
163
|
+
],
|
164
|
+
},
|
165
|
+
"variables": [
|
166
|
+
{
|
167
|
+
"type": "EXECUTION_COUNTER",
|
168
|
+
"node_id": str(StartNode.__id__),
|
169
|
+
"id": "e60902d5-6892-4916-80c1-f0130af52322",
|
170
|
+
},
|
171
|
+
{
|
172
|
+
"type": "CONSTANT_VALUE",
|
173
|
+
"variable_value": {"type": "NUMBER", "value": 0},
|
174
|
+
"id": "5c1bbb24-c288-49cb-a9b7-0c6f38a86037",
|
175
|
+
},
|
176
|
+
],
|
177
|
+
},
|
178
|
+
"then_outputs": [
|
179
|
+
{
|
180
|
+
"output_id": str(node_output_id),
|
181
|
+
"value": {
|
182
|
+
"id": "27006b2a-fa81-430c-a0b2-c66a9351fc68",
|
183
|
+
"type": "CONSTANT_VALUE",
|
184
|
+
"variable_value": {"type": "STRING", "value": "Hello"},
|
185
|
+
},
|
186
|
+
},
|
187
|
+
],
|
188
|
+
}
|
189
|
+
],
|
190
|
+
}
|
191
|
+
]
|
192
|
+
|
193
|
+
# WHEN we parsed the raw data on `MockNodeExecution`
|
194
|
+
node_output_mocks = MockNodeExecution.validate_all(
|
195
|
+
raw_mock_workflow_node_execution,
|
196
|
+
MyWorkflow,
|
197
|
+
)
|
198
|
+
|
199
|
+
# THEN we get the expected list of MockNodeExecution objects
|
200
|
+
assert node_output_mocks
|
201
|
+
assert len(node_output_mocks) == 1
|
202
|
+
assert node_output_mocks[0] == MockNodeExecution(
|
203
|
+
when_condition=StartNode.Execution.count.greater_than_or_equal_to(0.0),
|
204
|
+
then_outputs=StartNode.Outputs(
|
205
|
+
foo="Hello",
|
206
|
+
),
|
207
|
+
)
|
vellum/workflows/outputs/base.py
CHANGED
@@ -201,7 +201,7 @@ class BaseOutputs(metaclass=_BaseOutputsMeta):
|
|
201
201
|
self._outputs_post_init(**kwargs)
|
202
202
|
|
203
203
|
def __eq__(self, other: object) -> bool:
|
204
|
-
if not isinstance(other, dict):
|
204
|
+
if not isinstance(other, (dict, BaseOutputs)):
|
205
205
|
return super().__eq__(other)
|
206
206
|
|
207
207
|
outputs = {ref.name: value for ref, value in self if value is not undefined}
|
@@ -23,9 +23,9 @@ 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=p6orKsqEo8u6PWDbpp60D_E4UtjWNzWI8aVe8aknpyc,17097
|
27
27
|
vellum_ee/workflows/display/nodes/base_node_vellum_display.py,sha256=pLO0dORfRu--Ne9NgoyFT_CNjfpr5fGCsgbsMkUF5GM,2845
|
28
|
-
vellum_ee/workflows/display/nodes/get_node_display_class.py,sha256=
|
28
|
+
vellum_ee/workflows/display/nodes/get_node_display_class.py,sha256=67J_TGFNoISq8wZqOBCu5BNMY4kpQBq3Lt65FPH3Gt0,1594
|
29
29
|
vellum_ee/workflows/display/nodes/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
30
30
|
vellum_ee/workflows/display/nodes/tests/test_base_node_display.py,sha256=QqR3Ly0RNrXwOeLdW5nERDFt0gRPf76n1bPES6o5UN4,1093
|
31
31
|
vellum_ee/workflows/display/nodes/types.py,sha256=St1BB6no528OyELGiyRabWao0GGw6mLhstQAvEACbGk,247
|
@@ -61,7 +61,7 @@ vellum_ee/workflows/display/tests/test_vellum_workflow_display.py,sha256=cdpUoDN
|
|
61
61
|
vellum_ee/workflows/display/tests/workflow_serialization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
62
62
|
vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
63
63
|
vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/conftest.py,sha256=A1-tIpC5KIKG9JA_rkd1nLS8zUG3Kb4QiVdvb3boFxE,2509
|
64
|
-
vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py,sha256=
|
64
|
+
vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py,sha256=mFmxXfnUPrwndaBurW9E-VSBUQjF3cGv3JpRuNmwWh8,15475
|
65
65
|
vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attributes_serialization.py,sha256=1cszL6N6FNGVm61MOa7AEiHnF0QjZWqDQuPOp4yiG94,18277
|
66
66
|
vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_outputs_serialization.py,sha256=-12ZkZb3f5gyoNASV2yeQtMo5HmNsVEo8nXwL6IC-I8,6261
|
67
67
|
vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_ports_serialization.py,sha256=6th6kCwzql6lddjkTQx4Jbvvs4ChqtJwctW-B4QuBhI,37352
|
@@ -90,9 +90,9 @@ vellum_ee/workflows/display/utils/expressions.py,sha256=9FpOslDI-RCR5m4TgAu9KCHh
|
|
90
90
|
vellum_ee/workflows/display/utils/vellum.py,sha256=UjK_RxnSEmlIu9klGCPWU5RAQBmgZ7cRbRdgxaTbubE,8081
|
91
91
|
vellum_ee/workflows/display/vellum.py,sha256=7mqQaKZPPrLMcXSAQkPIxCy5x8HkKs5PbCu3GRaC2o8,8507
|
92
92
|
vellum_ee/workflows/display/workflows/__init__.py,sha256=kapXsC67VJcgSuiBMa86FdePG5A9kMB5Pi4Uy1O2ob4,207
|
93
|
-
vellum_ee/workflows/display/workflows/base_workflow_display.py,sha256=
|
93
|
+
vellum_ee/workflows/display/workflows/base_workflow_display.py,sha256=yDjMk2tml2TzitcFdP5M7PFXm4G_Bz6aYLYby7riXGQ,20137
|
94
94
|
vellum_ee/workflows/display/workflows/get_vellum_workflow_display_class.py,sha256=kp0u8LN_2IwshLrhMImhpZx1hRyAcD5gXY-kDuuaGMQ,1269
|
95
|
-
vellum_ee/workflows/display/workflows/tests/test_workflow_display.py,sha256=
|
95
|
+
vellum_ee/workflows/display/workflows/tests/test_workflow_display.py,sha256=JRTTM8gX182pBMP5eI_-B_X7LaK0kiSS07KqDdIfrQc,4962
|
96
96
|
vellum_ee/workflows/display/workflows/vellum_workflow_display.py,sha256=mbAzCpswOek34ITeTkesbVreCXpulj4NFjIg3RcdVZ8,18243
|
97
97
|
vellum_ee/workflows/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
98
98
|
vellum_ee/workflows/server/virtual_file_loader.py,sha256=X_DdNK7MfyOjKWekk6YQpOSCT6klKcdjT6nVJcBH1sM,1481
|
@@ -123,7 +123,7 @@ vellum/client/README.md,sha256=JkCJjmMZl4jrPj46pkmL9dpK4gSzQQmP5I7z4aME4LY,4749
|
|
123
123
|
vellum/client/__init__.py,sha256=tKtdM1_GqmGq1gpi9ydWD_T-MM7fPn8QdHh8ww19cNI,117564
|
124
124
|
vellum/client/core/__init__.py,sha256=SQ85PF84B9MuKnBwHNHWemSGuy-g_515gFYNFhvEE0I,1438
|
125
125
|
vellum/client/core/api_error.py,sha256=RE8LELok2QCjABadECTvtDp7qejA1VmINCh6TbqPwSE,426
|
126
|
-
vellum/client/core/client_wrapper.py,sha256=
|
126
|
+
vellum/client/core/client_wrapper.py,sha256=rm58d4E5Lrfg-SwnN1AtE5vFfVb1Qy6a5GyVyEmNWvU,1869
|
127
127
|
vellum/client/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
|
128
128
|
vellum/client/core/file.py,sha256=X9IbmkZmB2bB_DpmZAO3crWdXagOakAyn6UCOCImCPg,2322
|
129
129
|
vellum/client/core/http_client.py,sha256=R0pQpCppnEtxccGvXl4uJ76s7ro_65Fo_erlNNLp_AI,19228
|
@@ -1299,7 +1299,7 @@ vellum/workflows/__init__.py,sha256=CssPsbNvN6rDhoLuqpEv7MMKGa51vE6dvAh6U31Pcio,
|
|
1299
1299
|
vellum/workflows/constants.py,sha256=2yg4_uo5gpqViy3ZLSwfC8qTybleYCtOnhA4Rj6bacM,1310
|
1300
1300
|
vellum/workflows/context.py,sha256=DwSf8lO9NHABiqOoD3exgrjUoRuNsKtutaL5TgRbD-A,1441
|
1301
1301
|
vellum/workflows/descriptors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1302
|
-
vellum/workflows/descriptors/base.py,sha256=
|
1302
|
+
vellum/workflows/descriptors/base.py,sha256=bvF3MWsc4Xyw5Z2s1A0fbsfMCebIbPYcGvbQ9uoa_Pg,14655
|
1303
1303
|
vellum/workflows/descriptors/exceptions.py,sha256=gUy4UD9JFUKSeQnQpeuDSLiRqWjWiIsxLahB7p_q3JY,54
|
1304
1304
|
vellum/workflows/descriptors/tests/test_utils.py,sha256=xoojJMyG5WLG9xGtmUjirz3lDFCcDsAcxjrtbdG8dNE,6060
|
1305
1305
|
vellum/workflows/descriptors/utils.py,sha256=gmVXJjf2yWmvlYey41J2FZHeSou0JuCHKb3826K_Jok,3838
|
@@ -1316,7 +1316,7 @@ vellum/workflows/events/node.py,sha256=uHT6If0esgZ3nLjrjmUPTKf3qbjGhoV_x5YKpjDBD
|
|
1316
1316
|
vellum/workflows/events/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1317
1317
|
vellum/workflows/events/tests/test_event.py,sha256=uRfMwSOqU-ROeZKCEngGvvJYlOZuxBhnC3qH5AGi3fM,15399
|
1318
1318
|
vellum/workflows/events/types.py,sha256=AeTJaQt_fNHDLI4nyBzo7XrW9QQybRC09AKzu3kEYEE,3575
|
1319
|
-
vellum/workflows/events/workflow.py,sha256=
|
1319
|
+
vellum/workflows/events/workflow.py,sha256=QoSHyIOpuVacbR7POf7h104miTOhCjtO2udnYximJGs,6851
|
1320
1320
|
vellum/workflows/exceptions.py,sha256=NiBiR3ggfmPxBVqD-H1SqmjI-7mIn0EStSN1BqApvCM,1213
|
1321
1321
|
vellum/workflows/expressions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1322
1322
|
vellum/workflows/expressions/accessor.py,sha256=ItZF7fMLzVTqsdAiaXb5SiDupXmX0X9xbIus1W6hRds,1870
|
@@ -1356,13 +1356,13 @@ vellum/workflows/graph/graph.py,sha256=GGR8cGpSuNWPIiTWNWsj6l70upb5nIxAyFcn7VdaJ
|
|
1356
1356
|
vellum/workflows/graph/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1357
1357
|
vellum/workflows/graph/tests/test_graph.py,sha256=q0wxLvPPxc-6en4a_XuAZwVfLURMd9Ikvoreq4bpJ9s,11839
|
1358
1358
|
vellum/workflows/inputs/__init__.py,sha256=AbFEteIYEvCb14fM3EK7bhM-40-6s494rSlIhQ4Dsss,62
|
1359
|
-
vellum/workflows/inputs/base.py,sha256=
|
1359
|
+
vellum/workflows/inputs/base.py,sha256=eVTjtn6GielKYogQ-LPA6n4iZO3-WIfyxWpBKkN-dPs,3959
|
1360
1360
|
vellum/workflows/inputs/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1361
|
-
vellum/workflows/inputs/tests/test_inputs.py,sha256=
|
1361
|
+
vellum/workflows/inputs/tests/test_inputs.py,sha256=g--YqWTNWzMk5Ktoj__gq988kvBReefc2tsyUl6H2kg,1775
|
1362
1362
|
vellum/workflows/logging.py,sha256=_a217XogktV4Ncz6xKFz7WfYmZAzkfVRVuC0rWob8ls,437
|
1363
1363
|
vellum/workflows/nodes/__init__.py,sha256=aVdQVv7Y3Ro3JlqXGpxwaU2zrI06plDHD2aumH5WUIs,1157
|
1364
1364
|
vellum/workflows/nodes/bases/__init__.py,sha256=cniHuz_RXdJ4TQgD8CBzoiKDiPxg62ErdVpCbWICX64,58
|
1365
|
-
vellum/workflows/nodes/bases/base.py,sha256=
|
1365
|
+
vellum/workflows/nodes/bases/base.py,sha256=Y5xv0tFSSDafKDhVsxC5xTNrH15sj93jok6POzgWO0E,15351
|
1366
1366
|
vellum/workflows/nodes/bases/base_adornment_node.py,sha256=eFTgsPCYb3eyGS0-kw7C6crFnwFx437R5wh9-8bWYts,2905
|
1367
1367
|
vellum/workflows/nodes/bases/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1368
1368
|
vellum/workflows/nodes/bases/tests/test_base_node.py,sha256=4SOdZzvugVtN8CIuo5RrapAxSYGXnxUwQ77dXJ64oTU,6295
|
@@ -1370,13 +1370,13 @@ vellum/workflows/nodes/core/__init__.py,sha256=5zDMCmyt1v0HTJzlUBwq3U9L825yZGZhT
|
|
1370
1370
|
vellum/workflows/nodes/core/error_node/__init__.py,sha256=g7RRnlHhqu4qByfLjBwCunmgGA8dI5gNsjS3h6TwlSI,60
|
1371
1371
|
vellum/workflows/nodes/core/error_node/node.py,sha256=MFHU5vITYSK-L9CuMZ49In2ZeNLWnhZD0f8r5dWvb5Y,1270
|
1372
1372
|
vellum/workflows/nodes/core/inline_subworkflow_node/__init__.py,sha256=nKNEH1QTl-1PcvmYoqSWEl0-t6gAur8GLTXHzklRQfM,84
|
1373
|
-
vellum/workflows/nodes/core/inline_subworkflow_node/node.py,sha256=
|
1373
|
+
vellum/workflows/nodes/core/inline_subworkflow_node/node.py,sha256=B1ant-Pwg1AGFs5BYXynHf2i4rAen1bkr7nbLmiVwHo,6175
|
1374
1374
|
vellum/workflows/nodes/core/inline_subworkflow_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1375
|
-
vellum/workflows/nodes/core/inline_subworkflow_node/tests/test_node.py,sha256=
|
1375
|
+
vellum/workflows/nodes/core/inline_subworkflow_node/tests/test_node.py,sha256=6AG-oTyJaw5a1KWGQFNZaKl6_Pu_fW3nI_WA3XNRFWY,2439
|
1376
1376
|
vellum/workflows/nodes/core/map_node/__init__.py,sha256=MXpZYmGfhsMJHqqlpd64WiJRtbAtAMQz-_3fCU_cLV0,56
|
1377
|
-
vellum/workflows/nodes/core/map_node/node.py,sha256=
|
1377
|
+
vellum/workflows/nodes/core/map_node/node.py,sha256=dY27Xm11LHsqD7hnZnVYYDIazZ-XfL4_zatvWKTi6CU,9243
|
1378
1378
|
vellum/workflows/nodes/core/map_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1379
|
-
vellum/workflows/nodes/core/map_node/tests/test_node.py,sha256=
|
1379
|
+
vellum/workflows/nodes/core/map_node/tests/test_node.py,sha256=uMR0AyIFn539LqTKHdwuBswnx1i-PHyqPpgtYrnmYMY,3496
|
1380
1380
|
vellum/workflows/nodes/core/retry_node/__init__.py,sha256=lN2bIy5a3Uzhs_FYCrooADyYU6ZGShtvLKFWpelwPvo,60
|
1381
1381
|
vellum/workflows/nodes/core/retry_node/node.py,sha256=Vt3fx4G-DRIb9a-IHIUfaAclgfbzOPEQVkcumwhl9HE,4355
|
1382
1382
|
vellum/workflows/nodes/core/retry_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -1385,9 +1385,9 @@ vellum/workflows/nodes/core/templating_node/__init__.py,sha256=GmyuYo81_A1_Bz6id
|
|
1385
1385
|
vellum/workflows/nodes/core/templating_node/node.py,sha256=-JIqLUv6Xpx_LTVZt7whQ2X2VatgHDdTxjMrz64luEs,3721
|
1386
1386
|
vellum/workflows/nodes/core/templating_node/tests/test_templating_node.py,sha256=ldnmSASx0TfAnT3ZvU0AXtN0diZGrfySiXipuJIIzWU,9055
|
1387
1387
|
vellum/workflows/nodes/core/try_node/__init__.py,sha256=JVD4DrldTIqFQQFrubs9KtWCCc0YCAc7Fzol5ZWIWeM,56
|
1388
|
-
vellum/workflows/nodes/core/try_node/node.py,sha256=
|
1388
|
+
vellum/workflows/nodes/core/try_node/node.py,sha256=5ux1l2HO12FBFFyhz6j-4yfBYVrqgT2maTAne_GnNDk,4434
|
1389
1389
|
vellum/workflows/nodes/core/try_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1390
|
-
vellum/workflows/nodes/core/try_node/tests/test_node.py,sha256=
|
1390
|
+
vellum/workflows/nodes/core/try_node/tests/test_node.py,sha256=h6eUc3SggvhzBWlOD0PrPUlkoCSQHwjqYn81VkxSIxU,4948
|
1391
1391
|
vellum/workflows/nodes/displayable/__init__.py,sha256=6F_4DlSwvHuilWnIalp8iDjjDXl0Nmz4QzJV2PYe5RI,1023
|
1392
1392
|
vellum/workflows/nodes/displayable/api_node/__init__.py,sha256=MoxdQSnidIj1Nf_d-hTxlOxcZXaZnsWFDbE-PkTK24o,56
|
1393
1393
|
vellum/workflows/nodes/displayable/api_node/node.py,sha256=QdpsyGVxo5PcN8nwGZpcpW_YMKHr3_VvmbK1BlrdOFk,2547
|
@@ -1450,10 +1450,12 @@ vellum/workflows/nodes/experimental/README.md,sha256=eF6DfIL8t-HbF9-mcofOMymKrra
|
|
1450
1450
|
vellum/workflows/nodes/experimental/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1451
1451
|
vellum/workflows/nodes/experimental/openai_chat_completion_node/__init__.py,sha256=lsyD9laR9p7kx5-BXGH2gUTM242UhKy8SMV0SR6S2iE,90
|
1452
1452
|
vellum/workflows/nodes/experimental/openai_chat_completion_node/node.py,sha256=1EGeiaT-Zoo6pttQFKKBcdf3dmhAbjKGaErYD5FFwlc,10185
|
1453
|
-
vellum/workflows/nodes/mocks.py,sha256=
|
1453
|
+
vellum/workflows/nodes/mocks.py,sha256=a1FjWEIocseMfjzM-i8DNozpUsaW0IONRpZmXBoWlyc,10455
|
1454
|
+
vellum/workflows/nodes/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1455
|
+
vellum/workflows/nodes/tests/test_mocks.py,sha256=mfPvrs75PKcsNsbJLQAN6PDFoVqs9TmQxpdyFKDdO60,7837
|
1454
1456
|
vellum/workflows/nodes/utils.py,sha256=uaTPGYp4utenz_QDghqQ23Q1iCsGHQ40nNZh1g9H9WI,7117
|
1455
1457
|
vellum/workflows/outputs/__init__.py,sha256=AyZ4pRh_ACQIGvkf0byJO46EDnSix1ZCAXfvh-ms1QE,94
|
1456
|
-
vellum/workflows/outputs/base.py,sha256=
|
1458
|
+
vellum/workflows/outputs/base.py,sha256=b4Dnha1miKu3uFJYptKKflIHNuajPF2BNKy0BTt8Tjc,8622
|
1457
1459
|
vellum/workflows/ports/__init__.py,sha256=bZuMt-R7z5bKwpu4uPW7LlJeePOQWmCcDSXe5frUY5g,101
|
1458
1460
|
vellum/workflows/ports/node_ports.py,sha256=g4A-8iUAvEJSkaWppbvzAR8XU02R9U-qLN4rP2Kq4Aw,2743
|
1459
1461
|
vellum/workflows/ports/port.py,sha256=eI2SOZPZ5rsC3jMsxW6Rbn0NpaYQsrR7AapiIbbiy8Q,3635
|
@@ -1509,8 +1511,8 @@ vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnad
|
|
1509
1511
|
vellum/workflows/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1510
1512
|
vellum/workflows/workflows/tests/test_base_workflow.py,sha256=NRteiICyJvDM5zrtUfq2fZoXcGQVaWC9xmNlLLVW0cU,7979
|
1511
1513
|
vellum/workflows/workflows/tests/test_context.py,sha256=VJBUcyWVtMa_lE5KxdhgMu0WYNYnUQUDvTF7qm89hJ0,2333
|
1512
|
-
vellum_ai-0.14.
|
1513
|
-
vellum_ai-0.14.
|
1514
|
-
vellum_ai-0.14.
|
1515
|
-
vellum_ai-0.14.
|
1516
|
-
vellum_ai-0.14.
|
1514
|
+
vellum_ai-0.14.13.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
|
1515
|
+
vellum_ai-0.14.13.dist-info/METADATA,sha256=y44fYKH3DaYDbx8hhtZFAtWbGESUBWDUV-2W_JL-XiI,5408
|
1516
|
+
vellum_ai-0.14.13.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
1517
|
+
vellum_ai-0.14.13.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
|
1518
|
+
vellum_ai-0.14.13.dist-info/RECORD,,
|
@@ -59,11 +59,27 @@ _NodeDisplayAttrType = TypeVar("_NodeDisplayAttrType")
|
|
59
59
|
class BaseNodeDisplayMeta(type):
|
60
60
|
def __new__(mcs, name: str, bases: Tuple[Type, ...], dct: Dict[str, Any]) -> Any:
|
61
61
|
cls = super().__new__(mcs, name, bases, dct)
|
62
|
-
|
62
|
+
base_node_display_class = cast(Type["BaseNodeDisplay"], cls)
|
63
|
+
node_class = base_node_display_class.infer_node_class()
|
64
|
+
if not issubclass(node_class, BaseNode):
|
65
|
+
return cls
|
66
|
+
|
67
|
+
display_node_id = dct.get("node_id")
|
68
|
+
if isinstance(display_node_id, UUID):
|
63
69
|
# Display classes are able to override the id of the node class it's parameterized by
|
64
|
-
|
65
|
-
|
66
|
-
|
70
|
+
node_class.__id__ = display_node_id
|
71
|
+
|
72
|
+
output_display = dct.get("output_display")
|
73
|
+
if isinstance(output_display, dict):
|
74
|
+
# And the node class' output ids
|
75
|
+
for reference, node_output_display in output_display.items():
|
76
|
+
if not isinstance(reference, OutputReference):
|
77
|
+
continue
|
78
|
+
if not isinstance(node_output_display, NodeOutputDisplay):
|
79
|
+
continue
|
80
|
+
|
81
|
+
node_class.__output_ids__[reference.name] = node_output_display.id
|
82
|
+
|
67
83
|
return cls
|
68
84
|
|
69
85
|
|
@@ -2,6 +2,7 @@ import types
|
|
2
2
|
from typing import TYPE_CHECKING, Optional, Type
|
3
3
|
|
4
4
|
from vellum.workflows.types.generics import NodeType
|
5
|
+
from vellum_ee.workflows.display.nodes.types import NodeOutputDisplay
|
5
6
|
|
6
7
|
if TYPE_CHECKING:
|
7
8
|
from vellum_ee.workflows.display.types import NodeDisplayType
|
@@ -29,4 +30,12 @@ def get_node_display_class(
|
|
29
30
|
f"{node_class.__name__}Display",
|
30
31
|
bases=(NodeDisplayBaseClass,),
|
31
32
|
)
|
33
|
+
output_display = {
|
34
|
+
ref: NodeOutputDisplay(id=node_class.__output_ids__[ref.name], name=ref.name)
|
35
|
+
for ref in node_class.Outputs
|
36
|
+
if ref.name in node_class.__output_ids__
|
37
|
+
}
|
38
|
+
if output_display:
|
39
|
+
setattr(NodeDisplayClass, "output_display", output_display)
|
40
|
+
|
32
41
|
return NodeDisplayClass
|
@@ -115,7 +115,8 @@ def test_serialize_node__retry(serialize_node):
|
|
115
115
|
)
|
116
116
|
|
117
117
|
|
118
|
-
def test_serialize_node__retry__no_display():
|
118
|
+
def test_serialize_node__retry__no_display():
|
119
|
+
# GIVEN an adornment node
|
119
120
|
@RetryNode.wrap(max_attempts=5)
|
120
121
|
class StartNode(BaseNode):
|
121
122
|
pass
|
@@ -212,6 +213,28 @@ def test_serialize_node__try(serialize_node):
|
|
212
213
|
)
|
213
214
|
|
214
215
|
|
216
|
+
def test_serialize_node__try__no_display():
|
217
|
+
# GIVEN an adornment node
|
218
|
+
@TryNode.wrap()
|
219
|
+
class StartNode(BaseNode):
|
220
|
+
pass
|
221
|
+
|
222
|
+
# AND a workflow that uses the adornment node
|
223
|
+
class MyWorkflow(BaseWorkflow):
|
224
|
+
graph = StartNode
|
225
|
+
|
226
|
+
# WHEN we serialize the workflow
|
227
|
+
workflow_display = get_workflow_display(
|
228
|
+
base_display_class=VellumWorkflowDisplay,
|
229
|
+
workflow_class=MyWorkflow,
|
230
|
+
)
|
231
|
+
|
232
|
+
exec_config = workflow_display.serialize()
|
233
|
+
|
234
|
+
# THEN the workflow display is created successfully
|
235
|
+
assert exec_config is not None
|
236
|
+
|
237
|
+
|
215
238
|
def test_serialize_node__stacked():
|
216
239
|
@TryNode.wrap()
|
217
240
|
@RetryNode.wrap(max_attempts=5)
|
@@ -411,7 +411,7 @@ class BaseWorkflowDisplay(
|
|
411
411
|
input_display = {}
|
412
412
|
if isinstance(current_node_display, BaseNodeVellumDisplay):
|
413
413
|
input_display = current_node_display.node_input_ids_by_name
|
414
|
-
|
414
|
+
output_display = {
|
415
415
|
output.name: current_node_display.output_display[output].id
|
416
416
|
for output in current_node_display.output_display
|
417
417
|
}
|
@@ -434,7 +434,7 @@ class BaseWorkflowDisplay(
|
|
434
434
|
|
435
435
|
node_event_displays[node_id] = NodeEventDisplayContext(
|
436
436
|
input_display=input_display,
|
437
|
-
output_display=
|
437
|
+
output_display=output_display,
|
438
438
|
port_display=port_display_meta,
|
439
439
|
subworkflow_display=subworkflow_display_context,
|
440
440
|
)
|
@@ -95,6 +95,26 @@ def test_serialize_workflow__node_display_class_not_registered():
|
|
95
95
|
assert data is not None
|
96
96
|
|
97
97
|
|
98
|
+
def test_get_event_display_context__node_display_filled_without_base_display():
|
99
|
+
# GIVEN a simple workflow
|
100
|
+
class StartNode(BaseNode):
|
101
|
+
class Outputs(BaseNode.Outputs):
|
102
|
+
foo: str
|
103
|
+
|
104
|
+
class MyWorkflow(BaseWorkflow):
|
105
|
+
graph = StartNode
|
106
|
+
|
107
|
+
# WHEN we gather the event display context
|
108
|
+
display_context = VellumWorkflowDisplay(MyWorkflow).get_event_display_context()
|
109
|
+
|
110
|
+
# THEN the node display should be included
|
111
|
+
assert str(StartNode.__id__) in display_context.node_displays
|
112
|
+
node_event_display = display_context.node_displays[str(StartNode.__id__)]
|
113
|
+
|
114
|
+
# AND so should their output ids
|
115
|
+
assert StartNode.__output_ids__ == node_event_display.output_display
|
116
|
+
|
117
|
+
|
98
118
|
def test_get_event_display_context__node_display_to_include_subworkflow_display():
|
99
119
|
# GIVEN a simple workflow
|
100
120
|
class InnerNode(BaseNode):
|
File without changes
|
File without changes
|
File without changes
|