vellum-ai 0.12.13__py3-none-any.whl → 0.12.15__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 +9 -0
- vellum/client/__init__.py +2 -6
- vellum/client/core/client_wrapper.py +1 -1
- vellum/client/environment.py +3 -3
- vellum/client/resources/ad_hoc/client.py +2 -6
- vellum/client/resources/container_images/client.py +0 -8
- vellum/client/resources/metric_definitions/client.py +2 -6
- vellum/client/resources/workflows/client.py +8 -8
- vellum/client/types/__init__.py +6 -0
- vellum/client/types/audio_prompt_block.py +29 -0
- vellum/client/types/function_call_prompt_block.py +30 -0
- vellum/client/types/image_prompt_block.py +29 -0
- vellum/client/types/prompt_block.py +12 -1
- vellum/client/types/workflow_push_response.py +1 -0
- vellum/plugins/pydantic.py +12 -2
- vellum/types/audio_prompt_block.py +3 -0
- vellum/types/function_call_prompt_block.py +3 -0
- vellum/types/image_prompt_block.py +3 -0
- vellum/workflows/descriptors/tests/test_utils.py +3 -0
- vellum/workflows/nodes/bases/base.py +4 -1
- vellum/workflows/nodes/bases/base_adornment_node.py +75 -0
- vellum/workflows/nodes/bases/tests/test_base_node.py +13 -0
- vellum/workflows/nodes/core/inline_subworkflow_node/node.py +2 -0
- vellum/workflows/nodes/core/map_node/node.py +49 -45
- vellum/workflows/nodes/core/retry_node/node.py +10 -45
- vellum/workflows/nodes/core/try_node/node.py +12 -84
- vellum/workflows/nodes/utils.py +44 -1
- vellum/workflows/references/constant.py +21 -0
- vellum/workflows/runner/runner.py +4 -3
- vellum/workflows/types/cycle_map.py +34 -0
- vellum/workflows/workflows/base.py +4 -11
- {vellum_ai-0.12.13.dist-info → vellum_ai-0.12.15.dist-info}/METADATA +2 -2
- {vellum_ai-0.12.13.dist-info → vellum_ai-0.12.15.dist-info}/RECORD +52 -39
- vellum_cli/config.py +4 -0
- vellum_cli/pull.py +20 -5
- vellum_cli/push.py +7 -0
- vellum_cli/tests/test_pull.py +19 -1
- vellum_ee/workflows/display/nodes/vellum/__init__.py +2 -0
- vellum_ee/workflows/display/nodes/vellum/base_node.py +18 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +10 -41
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py +4 -14
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_generic_node_serialization.py +174 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +2 -10
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +2 -10
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +5 -19
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py +2 -8
- vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +14 -25
- vellum_ee/workflows/server/__init__.py +0 -0
- vellum_ee/workflows/server/virtual_file_loader.py +42 -0
- {vellum_ai-0.12.13.dist-info → vellum_ai-0.12.15.dist-info}/LICENSE +0 -0
- {vellum_ai-0.12.13.dist-info → vellum_ai-0.12.15.dist-info}/WHEEL +0 -0
- {vellum_ai-0.12.13.dist-info → vellum_ai-0.12.15.dist-info}/entry_points.txt +0 -0
@@ -10,6 +10,7 @@ from vellum.workflows.state.base import BaseState
|
|
10
10
|
from vellum.workflows.state.context import WorkflowContext
|
11
11
|
from vellum.workflows.types.core import EntityInputsInterface
|
12
12
|
from vellum.workflows.types.generics import StateType, WorkflowInputsType
|
13
|
+
from vellum.workflows.workflows.event_filters import all_workflow_event_filter
|
13
14
|
|
14
15
|
if TYPE_CHECKING:
|
15
16
|
from vellum.workflows.workflows.base import BaseWorkflow
|
@@ -36,6 +37,7 @@ class InlineSubworkflowNode(BaseNode[StateType], Generic[StateType, WorkflowInpu
|
|
36
37
|
)
|
37
38
|
subworkflow_stream = subworkflow.stream(
|
38
39
|
inputs=self._compile_subworkflow_inputs(),
|
40
|
+
event_filter=all_workflow_event_filter,
|
39
41
|
)
|
40
42
|
|
41
43
|
outputs: Optional[BaseOutputs] = None
|
@@ -9,21 +9,21 @@ from vellum.workflows.errors.types import WorkflowErrorCode
|
|
9
9
|
from vellum.workflows.events.types import ParentContext
|
10
10
|
from vellum.workflows.exceptions import NodeException
|
11
11
|
from vellum.workflows.inputs.base import BaseInputs
|
12
|
-
from vellum.workflows.nodes.bases import
|
12
|
+
from vellum.workflows.nodes.bases.base_adornment_node import BaseAdornmentNode
|
13
|
+
from vellum.workflows.nodes.utils import create_adornment
|
13
14
|
from vellum.workflows.outputs import BaseOutputs
|
14
|
-
from vellum.workflows.
|
15
|
+
from vellum.workflows.references.output import OutputReference
|
15
16
|
from vellum.workflows.state.context import WorkflowContext
|
16
|
-
from vellum.workflows.types.generics import
|
17
|
+
from vellum.workflows.types.generics import StateType
|
17
18
|
from vellum.workflows.workflows.event_filters import all_workflow_event_filter
|
18
19
|
|
19
20
|
if TYPE_CHECKING:
|
20
|
-
from vellum.workflows import BaseWorkflow
|
21
21
|
from vellum.workflows.events.workflow import WorkflowEvent
|
22
22
|
|
23
23
|
MapNodeItemType = TypeVar("MapNodeItemType")
|
24
24
|
|
25
25
|
|
26
|
-
class MapNode(
|
26
|
+
class MapNode(BaseAdornmentNode[StateType], Generic[StateType, MapNodeItemType]):
|
27
27
|
"""
|
28
28
|
Used to map over a list of items and execute a Subworkflow on each iteration.
|
29
29
|
|
@@ -33,11 +33,10 @@ class MapNode(BaseNode, Generic[StateType, MapNodeItemType]):
|
|
33
33
|
"""
|
34
34
|
|
35
35
|
items: List[MapNodeItemType]
|
36
|
-
subworkflow: Type["BaseWorkflow"]
|
37
36
|
concurrency: Optional[int] = None
|
38
37
|
|
39
|
-
class Outputs(
|
40
|
-
|
38
|
+
class Outputs(BaseAdornmentNode.Outputs):
|
39
|
+
pass
|
41
40
|
|
42
41
|
class SubworkflowInputs(BaseInputs):
|
43
42
|
# TODO: Both type: ignore's below are believed to be incorrect and both have the following error:
|
@@ -54,6 +53,7 @@ class MapNode(BaseNode, Generic[StateType, MapNodeItemType]):
|
|
54
53
|
mapped_items[output_descripter.name] = [None] * len(self.items)
|
55
54
|
|
56
55
|
self._event_queue: Queue[Tuple[int, WorkflowEvent]] = Queue()
|
56
|
+
self._concurrency_queue: Queue[Thread] = Queue()
|
57
57
|
fulfilled_iterations: List[bool] = []
|
58
58
|
for index, item in enumerate(self.items):
|
59
59
|
fulfilled_iterations.append(False)
|
@@ -66,11 +66,21 @@ class MapNode(BaseNode, Generic[StateType, MapNodeItemType]):
|
|
66
66
|
"parent_context": parent_context,
|
67
67
|
},
|
68
68
|
)
|
69
|
-
|
69
|
+
if self.concurrency is None:
|
70
|
+
thread.start()
|
71
|
+
else:
|
72
|
+
self._concurrency_queue.put(thread)
|
73
|
+
|
74
|
+
if self.concurrency is not None:
|
75
|
+
concurrency_count = 0
|
76
|
+
while concurrency_count < self.concurrency:
|
77
|
+
is_empty = self._start_thread()
|
78
|
+
if is_empty:
|
79
|
+
break
|
80
|
+
|
81
|
+
concurrency_count += 1
|
70
82
|
|
71
83
|
try:
|
72
|
-
# We should consolidate this logic with the logic workflow runner uses
|
73
|
-
# https://app.shortcut.com/vellum/story/4736
|
74
84
|
while map_node_event := self._event_queue.get():
|
75
85
|
index = map_node_event[0]
|
76
86
|
terminal_event = map_node_event[1]
|
@@ -86,6 +96,9 @@ class MapNode(BaseNode, Generic[StateType, MapNodeItemType]):
|
|
86
96
|
fulfilled_iterations[index] = True
|
87
97
|
if all(fulfilled_iterations):
|
88
98
|
break
|
99
|
+
|
100
|
+
if self.concurrency is not None:
|
101
|
+
self._start_thread()
|
89
102
|
elif terminal_event.name == "workflow.execution.paused":
|
90
103
|
raise NodeException(
|
91
104
|
code=WorkflowErrorCode.INVALID_OUTPUTS,
|
@@ -98,7 +111,12 @@ class MapNode(BaseNode, Generic[StateType, MapNodeItemType]):
|
|
98
111
|
)
|
99
112
|
except Empty:
|
100
113
|
pass
|
101
|
-
|
114
|
+
|
115
|
+
outputs = self.Outputs()
|
116
|
+
for output_name, output_list in mapped_items.items():
|
117
|
+
setattr(outputs, output_name, output_list)
|
118
|
+
|
119
|
+
return outputs
|
102
120
|
|
103
121
|
def _context_run_subworkflow(
|
104
122
|
self, *, item: MapNodeItemType, index: int, parent_context: Optional[ParentContext] = None
|
@@ -109,7 +127,10 @@ class MapNode(BaseNode, Generic[StateType, MapNodeItemType]):
|
|
109
127
|
|
110
128
|
def _run_subworkflow(self, *, item: MapNodeItemType, index: int) -> None:
|
111
129
|
context = WorkflowContext(vellum_client=self._context.vellum_client)
|
112
|
-
subworkflow = self.subworkflow(
|
130
|
+
subworkflow = self.subworkflow(
|
131
|
+
parent_state=self.state,
|
132
|
+
context=context,
|
133
|
+
)
|
113
134
|
events = subworkflow.stream(
|
114
135
|
inputs=self.SubworkflowInputs(index=index, item=item, all_items=self.items),
|
115
136
|
event_filter=all_workflow_event_filter,
|
@@ -118,6 +139,14 @@ class MapNode(BaseNode, Generic[StateType, MapNodeItemType]):
|
|
118
139
|
for event in events:
|
119
140
|
self._event_queue.put((index, event))
|
120
141
|
|
142
|
+
def _start_thread(self) -> bool:
|
143
|
+
if self._concurrency_queue.empty():
|
144
|
+
return False
|
145
|
+
|
146
|
+
thread = self._concurrency_queue.get()
|
147
|
+
thread.start()
|
148
|
+
return True
|
149
|
+
|
121
150
|
@overload
|
122
151
|
@classmethod
|
123
152
|
def wrap(cls, items: List[MapNodeItemType]) -> Callable[..., Type["MapNode[StateType, MapNodeItemType]"]]: ...
|
@@ -134,37 +163,12 @@ class MapNode(BaseNode, Generic[StateType, MapNodeItemType]):
|
|
134
163
|
def wrap(
|
135
164
|
cls, items: Union[List[MapNodeItemType], BaseDescriptor[List[MapNodeItemType]]]
|
136
165
|
) -> Callable[..., Type["MapNode[StateType, MapNodeItemType]"]]:
|
137
|
-
|
166
|
+
return create_adornment(cls, attributes={"items": items})
|
138
167
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
class Subworkflow(BaseWorkflow[MapNode.SubworkflowInputs, BaseState]):
|
145
|
-
graph = inner_cls
|
146
|
-
|
147
|
-
# mypy is wrong here, this works and is defined
|
148
|
-
class Outputs(inner_cls.Outputs): # type: ignore[name-defined]
|
149
|
-
pass
|
150
|
-
|
151
|
-
class WrappedNodeOutputs(BaseOutputs):
|
152
|
-
pass
|
153
|
-
|
154
|
-
WrappedNodeOutputs.__annotations__ = {
|
155
|
-
# TODO: We'll need to infer the type T of Subworkflow.Outputs[name] so we could do List[T] here
|
156
|
-
# https://app.shortcut.com/vellum/story/4119
|
157
|
-
descriptor.name: List
|
158
|
-
for descriptor in inner_cls.Outputs
|
159
|
-
}
|
160
|
-
|
161
|
-
class WrappedNode(MapNode[StateType, MapNodeItemType]):
|
162
|
-
items = _items
|
163
|
-
subworkflow = Subworkflow
|
164
|
-
|
165
|
-
class Outputs(WrappedNodeOutputs):
|
166
|
-
pass
|
167
|
-
|
168
|
-
return WrappedNode
|
168
|
+
@classmethod
|
169
|
+
def __annotate_outputs_class__(cls, outputs_class: Type[BaseOutputs], reference: OutputReference) -> None:
|
170
|
+
parameter_type = reference.types[0]
|
171
|
+
annotation = List[parameter_type] # type: ignore[valid-type]
|
169
172
|
|
170
|
-
|
173
|
+
previous_annotations = {prev: annotation for prev in outputs_class.__annotations__ if not prev.startswith("_")}
|
174
|
+
outputs_class.__annotations__ = {**previous_annotations, reference.name: annotation}
|
@@ -1,27 +1,16 @@
|
|
1
|
-
from typing import
|
1
|
+
from typing import Callable, Generic, Optional, Type
|
2
2
|
|
3
3
|
from vellum.workflows.errors.types import WorkflowErrorCode
|
4
4
|
from vellum.workflows.exceptions import NodeException
|
5
5
|
from vellum.workflows.inputs.base import BaseInputs
|
6
6
|
from vellum.workflows.nodes.bases import BaseNode
|
7
|
-
from vellum.workflows.nodes.bases.
|
8
|
-
from vellum.workflows.
|
7
|
+
from vellum.workflows.nodes.bases.base_adornment_node import BaseAdornmentNode
|
8
|
+
from vellum.workflows.nodes.utils import create_adornment
|
9
|
+
from vellum.workflows.state.context import WorkflowContext
|
9
10
|
from vellum.workflows.types.generics import StateType
|
10
11
|
|
11
|
-
if TYPE_CHECKING:
|
12
|
-
from vellum.workflows import BaseWorkflow
|
13
12
|
|
14
|
-
|
15
|
-
class _RetryNodeMeta(BaseNodeMeta):
|
16
|
-
@property
|
17
|
-
def _localns(cls) -> Dict[str, Any]:
|
18
|
-
return {
|
19
|
-
**super()._localns,
|
20
|
-
"SubworkflowInputs": getattr(cls, "SubworkflowInputs"),
|
21
|
-
}
|
22
|
-
|
23
|
-
|
24
|
-
class RetryNode(BaseNode[StateType], Generic[StateType], metaclass=_RetryNodeMeta):
|
13
|
+
class RetryNode(BaseAdornmentNode[StateType], Generic[StateType]):
|
25
14
|
"""
|
26
15
|
Used to retry a Subworkflow a specified number of times.
|
27
16
|
|
@@ -32,7 +21,6 @@ class RetryNode(BaseNode[StateType], Generic[StateType], metaclass=_RetryNodeMet
|
|
32
21
|
|
33
22
|
max_attempts: int
|
34
23
|
retry_on_error_code: Optional[WorkflowErrorCode] = None
|
35
|
-
subworkflow: Type["BaseWorkflow[SubworkflowInputs, BaseState]"]
|
36
24
|
|
37
25
|
class SubworkflowInputs(BaseInputs):
|
38
26
|
attempt_number: int
|
@@ -41,9 +29,10 @@ class RetryNode(BaseNode[StateType], Generic[StateType], metaclass=_RetryNodeMet
|
|
41
29
|
last_exception = Exception("max_attempts must be greater than 0")
|
42
30
|
for index in range(self.max_attempts):
|
43
31
|
attempt_number = index + 1
|
32
|
+
context = WorkflowContext(vellum_client=self._context.vellum_client)
|
44
33
|
subworkflow = self.subworkflow(
|
45
34
|
parent_state=self.state,
|
46
|
-
context=
|
35
|
+
context=context,
|
47
36
|
)
|
48
37
|
terminal_event = subworkflow.run(
|
49
38
|
inputs=self.SubworkflowInputs(attempt_number=attempt_number),
|
@@ -78,30 +67,6 @@ Message: {terminal_event.error.message}""",
|
|
78
67
|
def wrap(
|
79
68
|
cls, max_attempts: int, retry_on_error_code: Optional[WorkflowErrorCode] = None
|
80
69
|
) -> Callable[..., Type["RetryNode"]]:
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
def decorator(inner_cls: Type[BaseNode]) -> Type["RetryNode"]:
|
85
|
-
# Investigate how to use dependency injection to avoid circular imports
|
86
|
-
# https://app.shortcut.com/vellum/story/4116
|
87
|
-
from vellum.workflows import BaseWorkflow
|
88
|
-
|
89
|
-
class Subworkflow(BaseWorkflow[RetryNode.SubworkflowInputs, BaseState]):
|
90
|
-
graph = inner_cls
|
91
|
-
|
92
|
-
# mypy is wrong here, this works and is defined
|
93
|
-
class Outputs(inner_cls.Outputs): # type: ignore[name-defined]
|
94
|
-
pass
|
95
|
-
|
96
|
-
class WrappedNode(RetryNode[StateType]):
|
97
|
-
max_attempts = _max_attempts
|
98
|
-
retry_on_error_code = _retry_on_error_code
|
99
|
-
|
100
|
-
subworkflow = Subworkflow
|
101
|
-
|
102
|
-
class Outputs(Subworkflow.Outputs):
|
103
|
-
pass
|
104
|
-
|
105
|
-
return WrappedNode
|
106
|
-
|
107
|
-
return decorator
|
70
|
+
return create_adornment(
|
71
|
+
cls, attributes={"max_attempts": max_attempts, "retry_on_error_code": retry_on_error_code}
|
72
|
+
)
|
@@ -1,61 +1,18 @@
|
|
1
|
-
import
|
2
|
-
from types import ModuleType
|
3
|
-
from typing import TYPE_CHECKING, Any, Callable, Dict, Generic, Iterator, Optional, Set, Tuple, Type, TypeVar
|
1
|
+
from typing import Callable, Generic, Iterator, Optional, Set, Type
|
4
2
|
|
5
3
|
from vellum.workflows.errors.types import WorkflowError, WorkflowErrorCode
|
6
4
|
from vellum.workflows.exceptions import NodeException
|
7
5
|
from vellum.workflows.nodes.bases import BaseNode
|
8
|
-
from vellum.workflows.nodes.bases.
|
9
|
-
from vellum.workflows.nodes.utils import
|
6
|
+
from vellum.workflows.nodes.bases.base_adornment_node import BaseAdornmentNode
|
7
|
+
from vellum.workflows.nodes.utils import create_adornment
|
10
8
|
from vellum.workflows.outputs.base import BaseOutput, BaseOutputs
|
9
|
+
from vellum.workflows.references.output import OutputReference
|
11
10
|
from vellum.workflows.state.context import WorkflowContext
|
12
11
|
from vellum.workflows.types.generics import StateType
|
13
12
|
from vellum.workflows.workflows.event_filters import all_workflow_event_filter
|
14
13
|
|
15
|
-
if TYPE_CHECKING:
|
16
|
-
from vellum.workflows import BaseWorkflow
|
17
14
|
|
18
|
-
|
19
|
-
_T = TypeVar("_T", bound=BaseOutputs)
|
20
|
-
|
21
|
-
|
22
|
-
class _TryNodeMeta(BaseNodeMeta):
|
23
|
-
def __new__(cls, name: str, bases: Tuple[Type, ...], dct: Dict[str, Any]) -> Any:
|
24
|
-
node_class = super().__new__(cls, name, bases, dct)
|
25
|
-
|
26
|
-
subworkflow_attribute = dct.get("subworkflow")
|
27
|
-
if not subworkflow_attribute:
|
28
|
-
return node_class
|
29
|
-
|
30
|
-
subworkflow_outputs = getattr(subworkflow_attribute, "Outputs")
|
31
|
-
if not issubclass(subworkflow_outputs, BaseOutputs):
|
32
|
-
raise ValueError("subworkflow.Outputs must be a subclass of BaseOutputs")
|
33
|
-
|
34
|
-
outputs_class = dct.get("Outputs")
|
35
|
-
if not outputs_class:
|
36
|
-
raise ValueError("Outputs class not found in base classes")
|
37
|
-
|
38
|
-
if not issubclass(outputs_class, BaseNode.Outputs):
|
39
|
-
raise ValueError("Outputs class must be a subclass of BaseNode.Outputs")
|
40
|
-
|
41
|
-
for descriptor in subworkflow_outputs:
|
42
|
-
if descriptor.name == "error":
|
43
|
-
raise ValueError("`error` is a reserved name for TryNode.Outputs")
|
44
|
-
|
45
|
-
setattr(outputs_class, descriptor.name, descriptor)
|
46
|
-
|
47
|
-
return node_class
|
48
|
-
|
49
|
-
def __getattribute__(cls, name: str) -> Any:
|
50
|
-
try:
|
51
|
-
return super().__getattribute__(name)
|
52
|
-
except AttributeError:
|
53
|
-
if name != "__wrapped_node__" and issubclass(cls, TryNode):
|
54
|
-
return getattr(cls.__wrapped_node__, name)
|
55
|
-
raise
|
56
|
-
|
57
|
-
|
58
|
-
class TryNode(BaseNode[StateType], Generic[StateType], metaclass=_TryNodeMeta):
|
15
|
+
class TryNode(BaseAdornmentNode[StateType], Generic[StateType]):
|
59
16
|
"""
|
60
17
|
Used to execute a Subworkflow and handle errors.
|
61
18
|
|
@@ -63,9 +20,7 @@ class TryNode(BaseNode[StateType], Generic[StateType], metaclass=_TryNodeMeta):
|
|
63
20
|
subworkflow: Type["BaseWorkflow"] - The Subworkflow to execute
|
64
21
|
"""
|
65
22
|
|
66
|
-
__wrapped_node__: Optional[Type["BaseNode"]] = None
|
67
23
|
on_error_code: Optional[WorkflowErrorCode] = None
|
68
|
-
subworkflow: Type["BaseWorkflow"]
|
69
24
|
|
70
25
|
class Outputs(BaseNode.Outputs):
|
71
26
|
error: Optional[WorkflowError] = None
|
@@ -129,38 +84,11 @@ Message: {event.error.message}""",
|
|
129
84
|
|
130
85
|
@classmethod
|
131
86
|
def wrap(cls, on_error_code: Optional[WorkflowErrorCode] = None) -> Callable[..., Type["TryNode"]]:
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
inner_cls._is_wrapped_node = True
|
140
|
-
|
141
|
-
class Subworkflow(BaseWorkflow):
|
142
|
-
graph = inner_cls
|
143
|
-
|
144
|
-
# mypy is wrong here, this works and is defined
|
145
|
-
class Outputs(inner_cls.Outputs): # type: ignore[name-defined]
|
146
|
-
pass
|
147
|
-
|
148
|
-
dynamic_module = f"{inner_cls.__module__}.{inner_cls.__name__}.{ADORNMENT_MODULE_NAME}"
|
149
|
-
# This dynamic module allows calls to `type_hints` to work
|
150
|
-
sys.modules[dynamic_module] = ModuleType(dynamic_module)
|
151
|
-
|
152
|
-
# We use a dynamic wrapped node class to be uniquely tied to this `inner_cls` node during serialization
|
153
|
-
WrappedNode = type(
|
154
|
-
cls.__name__,
|
155
|
-
(TryNode,),
|
156
|
-
{
|
157
|
-
"__wrapped_node__": inner_cls,
|
158
|
-
"__module__": dynamic_module,
|
159
|
-
"on_error_code": _on_error_code,
|
160
|
-
"subworkflow": Subworkflow,
|
161
|
-
"Ports": type("Ports", (TryNode.Ports,), {port.name: port.copy() for port in inner_cls.Ports}),
|
162
|
-
},
|
163
|
-
)
|
164
|
-
return WrappedNode
|
87
|
+
return create_adornment(cls, attributes={"on_error_code": on_error_code})
|
88
|
+
|
89
|
+
@classmethod
|
90
|
+
def __annotate_outputs_class__(cls, outputs_class: Type[BaseOutputs], reference: OutputReference) -> None:
|
91
|
+
if reference.name == "error":
|
92
|
+
raise ValueError("`error` is a reserved name for TryNode.Outputs")
|
165
93
|
|
166
|
-
|
94
|
+
setattr(outputs_class, reference.name, reference)
|
vellum/workflows/nodes/utils.py
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
from functools import cache
|
2
|
-
|
2
|
+
import sys
|
3
|
+
from types import ModuleType
|
4
|
+
from typing import Any, Callable, Optional, Type, TypeVar
|
3
5
|
|
4
6
|
from vellum.workflows.nodes import BaseNode
|
5
7
|
from vellum.workflows.ports.port import Port
|
@@ -42,3 +44,44 @@ def has_wrapped_node(node: Type[NodeType]) -> bool:
|
|
42
44
|
return False
|
43
45
|
|
44
46
|
return True
|
47
|
+
|
48
|
+
|
49
|
+
AdornableNode = TypeVar("AdornableNode", bound=BaseNode)
|
50
|
+
|
51
|
+
|
52
|
+
def create_adornment(
|
53
|
+
adornable_cls: Type[AdornableNode], attributes: Optional[dict[str, Any]] = None
|
54
|
+
) -> Callable[..., Type["AdornableNode"]]:
|
55
|
+
def decorator(inner_cls: Type[BaseNode]) -> Type["AdornableNode"]:
|
56
|
+
# Investigate how to use dependency injection to avoid circular imports
|
57
|
+
# https://app.shortcut.com/vellum/story/4116
|
58
|
+
from vellum.workflows import BaseWorkflow
|
59
|
+
|
60
|
+
inner_cls._is_wrapped_node = True
|
61
|
+
|
62
|
+
class Subworkflow(BaseWorkflow):
|
63
|
+
graph = inner_cls
|
64
|
+
|
65
|
+
# mypy is wrong here, this works and is defined
|
66
|
+
class Outputs(inner_cls.Outputs): # type: ignore[name-defined]
|
67
|
+
pass
|
68
|
+
|
69
|
+
dynamic_module = f"{inner_cls.__module__}.{inner_cls.__name__}.{ADORNMENT_MODULE_NAME}"
|
70
|
+
# This dynamic module allows calls to `type_hints` to work
|
71
|
+
sys.modules[dynamic_module] = ModuleType(dynamic_module)
|
72
|
+
|
73
|
+
# We use a dynamic wrapped node class to be uniquely tied to this `inner_cls` node during serialization
|
74
|
+
WrappedNode = type(
|
75
|
+
adornable_cls.__name__,
|
76
|
+
(adornable_cls,),
|
77
|
+
{
|
78
|
+
"__wrapped_node__": inner_cls,
|
79
|
+
"__module__": dynamic_module,
|
80
|
+
"subworkflow": Subworkflow,
|
81
|
+
"Ports": type("Ports", (adornable_cls.Ports,), {port.name: port.copy() for port in inner_cls.Ports}),
|
82
|
+
**(attributes or {}),
|
83
|
+
},
|
84
|
+
)
|
85
|
+
return WrappedNode
|
86
|
+
|
87
|
+
return decorator
|
@@ -0,0 +1,21 @@
|
|
1
|
+
from typing import TYPE_CHECKING, Generic, TypeVar
|
2
|
+
|
3
|
+
from vellum.workflows.descriptors.base import BaseDescriptor
|
4
|
+
|
5
|
+
if TYPE_CHECKING:
|
6
|
+
from vellum.workflows.state.base import BaseState
|
7
|
+
|
8
|
+
_T = TypeVar("_T")
|
9
|
+
|
10
|
+
|
11
|
+
class ConstantValueReference(BaseDescriptor[_T], Generic[_T]):
|
12
|
+
def __init__(
|
13
|
+
self,
|
14
|
+
value: _T,
|
15
|
+
) -> None:
|
16
|
+
self._value = value
|
17
|
+
types = (type(self._value),)
|
18
|
+
super().__init__(name=str(self._value), types=types)
|
19
|
+
|
20
|
+
def resolve(self, state: "BaseState") -> _T:
|
21
|
+
return self._value
|
@@ -44,11 +44,13 @@ from vellum.workflows.events.workflow import (
|
|
44
44
|
)
|
45
45
|
from vellum.workflows.exceptions import NodeException
|
46
46
|
from vellum.workflows.nodes.bases import BaseNode
|
47
|
+
from vellum.workflows.nodes.bases.base import NodeRunResponse
|
47
48
|
from vellum.workflows.outputs import BaseOutputs
|
48
49
|
from vellum.workflows.outputs.base import BaseOutput
|
49
50
|
from vellum.workflows.ports.port import Port
|
50
51
|
from vellum.workflows.references import ExternalInputReference, OutputReference
|
51
52
|
from vellum.workflows.state.base import BaseState
|
53
|
+
from vellum.workflows.types.cycle_map import CycleMap
|
52
54
|
from vellum.workflows.types.generics import OutputsType, StateType, WorkflowInputsType
|
53
55
|
|
54
56
|
if TYPE_CHECKING:
|
@@ -124,9 +126,7 @@ class WorkflowRunner(Generic[StateType]):
|
|
124
126
|
|
125
127
|
self._dependencies: Dict[Type[BaseNode], Set[Type[BaseNode]]] = defaultdict(set)
|
126
128
|
self._state_forks: Set[StateType] = {self._initial_state}
|
127
|
-
self._mocks_by_node_outputs_class = (
|
128
|
-
{mock.__class__: mock for mock in node_output_mocks} if node_output_mocks else {}
|
129
|
-
)
|
129
|
+
self._mocks_by_node_outputs_class = CycleMap(items=node_output_mocks or [], key_by=lambda mock: mock.__class__)
|
130
130
|
|
131
131
|
self._active_nodes_by_execution_id: Dict[UUID, BaseNode[StateType]] = {}
|
132
132
|
self._cancel_signal = cancel_signal
|
@@ -182,6 +182,7 @@ class WorkflowRunner(Generic[StateType]):
|
|
182
182
|
node_definition=node.__class__,
|
183
183
|
parent=parent_context,
|
184
184
|
)
|
185
|
+
node_run_response: NodeRunResponse
|
185
186
|
if node.Outputs not in self._mocks_by_node_outputs_class:
|
186
187
|
with execution_context(parent_context=updated_parent_context):
|
187
188
|
node_run_response = node.run()
|
@@ -0,0 +1,34 @@
|
|
1
|
+
from typing import Callable, Dict, Generic, List, TypeVar
|
2
|
+
|
3
|
+
_K = TypeVar("_K")
|
4
|
+
_T = TypeVar("_T")
|
5
|
+
|
6
|
+
|
7
|
+
class CycleMap(Generic[_K, _T]):
|
8
|
+
"""
|
9
|
+
A map that cycles through a list of items for each key.
|
10
|
+
"""
|
11
|
+
|
12
|
+
def __init__(self, items: List[_T], key_by: Callable[[_T], _K]):
|
13
|
+
self._items: Dict[_K, List[_T]] = {}
|
14
|
+
for item in items:
|
15
|
+
self._add_item(key_by(item), item)
|
16
|
+
|
17
|
+
def _add_item(self, key: _K, item: _T):
|
18
|
+
if key not in self._items:
|
19
|
+
self._items[key] = []
|
20
|
+
self._items[key].append(item)
|
21
|
+
|
22
|
+
def _get_item(self, key: _K) -> _T:
|
23
|
+
item = self._items[key].pop(0)
|
24
|
+
self._items[key].append(item)
|
25
|
+
return item
|
26
|
+
|
27
|
+
def __getitem__(self, key: _K) -> _T:
|
28
|
+
return self._get_item(key)
|
29
|
+
|
30
|
+
def __setitem__(self, key: _K, value: _T):
|
31
|
+
self._add_item(key, value)
|
32
|
+
|
33
|
+
def __contains__(self, key: _K) -> bool:
|
34
|
+
return key in self._items
|
@@ -1,16 +1,7 @@
|
|
1
|
-
# flake8: noqa: E402
|
2
|
-
|
3
|
-
import importlib
|
4
|
-
import inspect
|
5
|
-
|
6
|
-
from vellum.plugins.utils import load_runtime_plugins
|
7
|
-
from vellum.workflows.utils.uuids import uuid4_from_hash
|
8
|
-
from vellum.workflows.workflows.event_filters import workflow_event_filter
|
9
|
-
|
10
|
-
load_runtime_plugins()
|
11
|
-
|
12
1
|
from datetime import datetime
|
13
2
|
from functools import lru_cache
|
3
|
+
import importlib
|
4
|
+
import inspect
|
14
5
|
from threading import Event as ThreadingEvent
|
15
6
|
from uuid import UUID, uuid4
|
16
7
|
from typing import (
|
@@ -79,6 +70,8 @@ from vellum.workflows.state.context import WorkflowContext
|
|
79
70
|
from vellum.workflows.state.store import Store
|
80
71
|
from vellum.workflows.types.generics import StateType, WorkflowInputsType
|
81
72
|
from vellum.workflows.types.utils import get_original_base
|
73
|
+
from vellum.workflows.utils.uuids import uuid4_from_hash
|
74
|
+
from vellum.workflows.workflows.event_filters import workflow_event_filter
|
82
75
|
|
83
76
|
|
84
77
|
class _BaseWorkflowMeta(type):
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: vellum-ai
|
3
|
-
Version: 0.12.
|
3
|
+
Version: 0.12.15
|
4
4
|
Summary:
|
5
5
|
License: MIT
|
6
6
|
Requires-Python: >=3.9,<4.0
|
@@ -95,7 +95,7 @@ more below](#workflows-sdk).
|
|
95
95
|
|
96
96
|
## Client SDK
|
97
97
|
|
98
|
-
The Vellum Client SDK, found within `src/client` is a low-level client used to interact directly with the Vellum API.
|
98
|
+
The Vellum Client SDK, found within `src/vellum/client` is a low-level client used to interact directly with the Vellum API.
|
99
99
|
Learn more and get started by visiting the [Vellum Client SDK README](/src/vellum/client/README.md).
|
100
100
|
|
101
101
|
## Workflows SDK
|