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.
Files changed (52) hide show
  1. vellum/__init__.py +9 -0
  2. vellum/client/__init__.py +2 -6
  3. vellum/client/core/client_wrapper.py +1 -1
  4. vellum/client/environment.py +3 -3
  5. vellum/client/resources/ad_hoc/client.py +2 -6
  6. vellum/client/resources/container_images/client.py +0 -8
  7. vellum/client/resources/metric_definitions/client.py +2 -6
  8. vellum/client/resources/workflows/client.py +8 -8
  9. vellum/client/types/__init__.py +6 -0
  10. vellum/client/types/audio_prompt_block.py +29 -0
  11. vellum/client/types/function_call_prompt_block.py +30 -0
  12. vellum/client/types/image_prompt_block.py +29 -0
  13. vellum/client/types/prompt_block.py +12 -1
  14. vellum/client/types/workflow_push_response.py +1 -0
  15. vellum/plugins/pydantic.py +12 -2
  16. vellum/types/audio_prompt_block.py +3 -0
  17. vellum/types/function_call_prompt_block.py +3 -0
  18. vellum/types/image_prompt_block.py +3 -0
  19. vellum/workflows/descriptors/tests/test_utils.py +3 -0
  20. vellum/workflows/nodes/bases/base.py +4 -1
  21. vellum/workflows/nodes/bases/base_adornment_node.py +75 -0
  22. vellum/workflows/nodes/bases/tests/test_base_node.py +13 -0
  23. vellum/workflows/nodes/core/inline_subworkflow_node/node.py +2 -0
  24. vellum/workflows/nodes/core/map_node/node.py +49 -45
  25. vellum/workflows/nodes/core/retry_node/node.py +10 -45
  26. vellum/workflows/nodes/core/try_node/node.py +12 -84
  27. vellum/workflows/nodes/utils.py +44 -1
  28. vellum/workflows/references/constant.py +21 -0
  29. vellum/workflows/runner/runner.py +4 -3
  30. vellum/workflows/types/cycle_map.py +34 -0
  31. vellum/workflows/workflows/base.py +4 -11
  32. {vellum_ai-0.12.13.dist-info → vellum_ai-0.12.15.dist-info}/METADATA +2 -2
  33. {vellum_ai-0.12.13.dist-info → vellum_ai-0.12.15.dist-info}/RECORD +52 -39
  34. vellum_cli/config.py +4 -0
  35. vellum_cli/pull.py +20 -5
  36. vellum_cli/push.py +7 -0
  37. vellum_cli/tests/test_pull.py +19 -1
  38. vellum_ee/workflows/display/nodes/vellum/__init__.py +2 -0
  39. vellum_ee/workflows/display/nodes/vellum/base_node.py +18 -0
  40. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +10 -41
  41. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py +4 -14
  42. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_generic_node_serialization.py +174 -0
  43. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +2 -10
  44. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +2 -10
  45. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +5 -19
  46. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py +2 -8
  47. vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +14 -25
  48. vellum_ee/workflows/server/__init__.py +0 -0
  49. vellum_ee/workflows/server/virtual_file_loader.py +42 -0
  50. {vellum_ai-0.12.13.dist-info → vellum_ai-0.12.15.dist-info}/LICENSE +0 -0
  51. {vellum_ai-0.12.13.dist-info → vellum_ai-0.12.15.dist-info}/WHEEL +0 -0
  52. {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 BaseNode
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.state.base import BaseState
15
+ from vellum.workflows.references.output import OutputReference
15
16
  from vellum.workflows.state.context import WorkflowContext
16
- from vellum.workflows.types.generics import NodeType, StateType
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(BaseNode, Generic[StateType, MapNodeItemType]):
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(BaseOutputs):
40
- mapped_items: list
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
- thread.start()
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
- return self.Outputs(**mapped_items)
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(parent_state=self.state, context=context)
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
- _items = items
166
+ return create_adornment(cls, attributes={"items": items})
138
167
 
139
- def decorator(inner_cls: Type[NodeType]) -> Type["MapNode[StateType, MapNodeItemType]"]:
140
- # Investigate how to use dependency injection to avoid circular imports
141
- # https://app.shortcut.com/vellum/story/4116
142
- from vellum.workflows import BaseWorkflow
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
- return decorator
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 TYPE_CHECKING, Any, Callable, Dict, Generic, Optional, Type
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.base import BaseNodeMeta
8
- from vellum.workflows.state.base import BaseState
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=self._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
- _max_attempts = max_attempts
82
- _retry_on_error_code = retry_on_error_code
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 sys
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.base import BaseNodeMeta
9
- from vellum.workflows.nodes.utils import ADORNMENT_MODULE_NAME
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
- Subworkflow = Type["BaseWorkflow"]
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
- _on_error_code = on_error_code
133
-
134
- def decorator(inner_cls: Type[BaseNode]) -> Type["TryNode"]:
135
- # Investigate how to use dependency injection to avoid circular imports
136
- # https://app.shortcut.com/vellum/story/4116
137
- from vellum.workflows import BaseWorkflow
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
- return decorator
94
+ setattr(outputs_class, reference.name, reference)
@@ -1,5 +1,7 @@
1
1
  from functools import cache
2
- from typing import Type
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.13
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