vellum-ai 0.12.13__py3-none-any.whl → 0.12.15__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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
|