vellum-ai 0.9.16rc2__py3-none-any.whl → 0.9.16rc4__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (245) hide show
  1. vellum/plugins/__init__.py +0 -0
  2. vellum/plugins/pydantic.py +74 -0
  3. vellum/plugins/utils.py +19 -0
  4. vellum/plugins/vellum_mypy.py +639 -3
  5. vellum/workflows/README.md +90 -0
  6. vellum/workflows/__init__.py +5 -0
  7. vellum/workflows/constants.py +43 -0
  8. vellum/workflows/descriptors/__init__.py +0 -0
  9. vellum/workflows/descriptors/base.py +339 -0
  10. vellum/workflows/descriptors/tests/test_utils.py +83 -0
  11. vellum/workflows/descriptors/utils.py +90 -0
  12. vellum/workflows/edges/__init__.py +5 -0
  13. vellum/workflows/edges/edge.py +23 -0
  14. vellum/workflows/emitters/__init__.py +5 -0
  15. vellum/workflows/emitters/base.py +14 -0
  16. vellum/workflows/environment/__init__.py +5 -0
  17. vellum/workflows/environment/environment.py +7 -0
  18. vellum/workflows/errors/__init__.py +6 -0
  19. vellum/workflows/errors/types.py +20 -0
  20. vellum/workflows/events/__init__.py +31 -0
  21. vellum/workflows/events/node.py +125 -0
  22. vellum/workflows/events/tests/__init__.py +0 -0
  23. vellum/workflows/events/tests/test_event.py +216 -0
  24. vellum/workflows/events/types.py +52 -0
  25. vellum/workflows/events/utils.py +5 -0
  26. vellum/workflows/events/workflow.py +139 -0
  27. vellum/workflows/exceptions.py +15 -0
  28. vellum/workflows/expressions/__init__.py +0 -0
  29. vellum/workflows/expressions/accessor.py +52 -0
  30. vellum/workflows/expressions/and_.py +32 -0
  31. vellum/workflows/expressions/begins_with.py +31 -0
  32. vellum/workflows/expressions/between.py +38 -0
  33. vellum/workflows/expressions/coalesce_expression.py +41 -0
  34. vellum/workflows/expressions/contains.py +30 -0
  35. vellum/workflows/expressions/does_not_begin_with.py +31 -0
  36. vellum/workflows/expressions/does_not_contain.py +30 -0
  37. vellum/workflows/expressions/does_not_end_with.py +31 -0
  38. vellum/workflows/expressions/does_not_equal.py +25 -0
  39. vellum/workflows/expressions/ends_with.py +31 -0
  40. vellum/workflows/expressions/equals.py +25 -0
  41. vellum/workflows/expressions/greater_than.py +33 -0
  42. vellum/workflows/expressions/greater_than_or_equal_to.py +33 -0
  43. vellum/workflows/expressions/in_.py +31 -0
  44. vellum/workflows/expressions/is_blank.py +24 -0
  45. vellum/workflows/expressions/is_not_blank.py +24 -0
  46. vellum/workflows/expressions/is_not_null.py +21 -0
  47. vellum/workflows/expressions/is_not_undefined.py +22 -0
  48. vellum/workflows/expressions/is_null.py +21 -0
  49. vellum/workflows/expressions/is_undefined.py +22 -0
  50. vellum/workflows/expressions/less_than.py +33 -0
  51. vellum/workflows/expressions/less_than_or_equal_to.py +33 -0
  52. vellum/workflows/expressions/not_between.py +38 -0
  53. vellum/workflows/expressions/not_in.py +31 -0
  54. vellum/workflows/expressions/or_.py +32 -0
  55. vellum/workflows/graph/__init__.py +3 -0
  56. vellum/workflows/graph/graph.py +131 -0
  57. vellum/workflows/graph/tests/__init__.py +0 -0
  58. vellum/workflows/graph/tests/test_graph.py +437 -0
  59. vellum/workflows/inputs/__init__.py +5 -0
  60. vellum/workflows/inputs/base.py +55 -0
  61. vellum/workflows/logging.py +14 -0
  62. vellum/workflows/nodes/__init__.py +46 -0
  63. vellum/workflows/nodes/bases/__init__.py +7 -0
  64. vellum/workflows/nodes/bases/base.py +332 -0
  65. vellum/workflows/nodes/bases/base_subworkflow_node/__init__.py +5 -0
  66. vellum/workflows/nodes/bases/base_subworkflow_node/node.py +10 -0
  67. vellum/workflows/nodes/bases/tests/__init__.py +0 -0
  68. vellum/workflows/nodes/bases/tests/test_base_node.py +125 -0
  69. vellum/workflows/nodes/core/__init__.py +16 -0
  70. vellum/workflows/nodes/core/error_node/__init__.py +5 -0
  71. vellum/workflows/nodes/core/error_node/node.py +26 -0
  72. vellum/workflows/nodes/core/inline_subworkflow_node/__init__.py +5 -0
  73. vellum/workflows/nodes/core/inline_subworkflow_node/node.py +73 -0
  74. vellum/workflows/nodes/core/map_node/__init__.py +5 -0
  75. vellum/workflows/nodes/core/map_node/node.py +147 -0
  76. vellum/workflows/nodes/core/map_node/tests/__init__.py +0 -0
  77. vellum/workflows/nodes/core/map_node/tests/test_node.py +65 -0
  78. vellum/workflows/nodes/core/retry_node/__init__.py +5 -0
  79. vellum/workflows/nodes/core/retry_node/node.py +106 -0
  80. vellum/workflows/nodes/core/retry_node/tests/__init__.py +0 -0
  81. vellum/workflows/nodes/core/retry_node/tests/test_node.py +93 -0
  82. vellum/workflows/nodes/core/templating_node/__init__.py +5 -0
  83. vellum/workflows/nodes/core/templating_node/custom_filters.py +12 -0
  84. vellum/workflows/nodes/core/templating_node/exceptions.py +2 -0
  85. vellum/workflows/nodes/core/templating_node/node.py +123 -0
  86. vellum/workflows/nodes/core/templating_node/render.py +55 -0
  87. vellum/workflows/nodes/core/templating_node/tests/test_templating_node.py +21 -0
  88. vellum/workflows/nodes/core/try_node/__init__.py +5 -0
  89. vellum/workflows/nodes/core/try_node/node.py +110 -0
  90. vellum/workflows/nodes/core/try_node/tests/__init__.py +0 -0
  91. vellum/workflows/nodes/core/try_node/tests/test_node.py +82 -0
  92. vellum/workflows/nodes/displayable/__init__.py +31 -0
  93. vellum/workflows/nodes/displayable/api_node/__init__.py +5 -0
  94. vellum/workflows/nodes/displayable/api_node/node.py +44 -0
  95. vellum/workflows/nodes/displayable/bases/__init__.py +11 -0
  96. vellum/workflows/nodes/displayable/bases/api_node/__init__.py +5 -0
  97. vellum/workflows/nodes/displayable/bases/api_node/node.py +70 -0
  98. vellum/workflows/nodes/displayable/bases/base_prompt_node/__init__.py +5 -0
  99. vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py +60 -0
  100. vellum/workflows/nodes/displayable/bases/inline_prompt_node/__init__.py +5 -0
  101. vellum/workflows/nodes/displayable/bases/inline_prompt_node/constants.py +13 -0
  102. vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +118 -0
  103. vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py +98 -0
  104. vellum/workflows/nodes/displayable/bases/search_node.py +90 -0
  105. vellum/workflows/nodes/displayable/code_execution_node/__init__.py +5 -0
  106. vellum/workflows/nodes/displayable/code_execution_node/node.py +197 -0
  107. vellum/workflows/nodes/displayable/code_execution_node/tests/__init__.py +0 -0
  108. vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/__init__.py +0 -0
  109. vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/main.py +3 -0
  110. vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py +111 -0
  111. vellum/workflows/nodes/displayable/code_execution_node/utils.py +10 -0
  112. vellum/workflows/nodes/displayable/conditional_node/__init__.py +5 -0
  113. vellum/workflows/nodes/displayable/conditional_node/node.py +25 -0
  114. vellum/workflows/nodes/displayable/final_output_node/__init__.py +5 -0
  115. vellum/workflows/nodes/displayable/final_output_node/node.py +43 -0
  116. vellum/workflows/nodes/displayable/guardrail_node/__init__.py +5 -0
  117. vellum/workflows/nodes/displayable/guardrail_node/node.py +97 -0
  118. vellum/workflows/nodes/displayable/inline_prompt_node/__init__.py +5 -0
  119. vellum/workflows/nodes/displayable/inline_prompt_node/node.py +41 -0
  120. vellum/workflows/nodes/displayable/merge_node/__init__.py +5 -0
  121. vellum/workflows/nodes/displayable/merge_node/node.py +10 -0
  122. vellum/workflows/nodes/displayable/prompt_deployment_node/__init__.py +5 -0
  123. vellum/workflows/nodes/displayable/prompt_deployment_node/node.py +45 -0
  124. vellum/workflows/nodes/displayable/search_node/__init__.py +5 -0
  125. vellum/workflows/nodes/displayable/search_node/node.py +26 -0
  126. vellum/workflows/nodes/displayable/subworkflow_deployment_node/__init__.py +5 -0
  127. vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +156 -0
  128. vellum/workflows/nodes/displayable/tests/__init__.py +0 -0
  129. vellum/workflows/nodes/displayable/tests/test_inline_text_prompt_node.py +148 -0
  130. vellum/workflows/nodes/displayable/tests/test_search_node_wth_text_output.py +134 -0
  131. vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py +80 -0
  132. vellum/workflows/nodes/utils.py +27 -0
  133. vellum/workflows/outputs/__init__.py +6 -0
  134. vellum/workflows/outputs/base.py +196 -0
  135. vellum/workflows/ports/__init__.py +7 -0
  136. vellum/workflows/ports/node_ports.py +75 -0
  137. vellum/workflows/ports/port.py +75 -0
  138. vellum/workflows/ports/utils.py +40 -0
  139. vellum/workflows/references/__init__.py +17 -0
  140. vellum/workflows/references/environment_variable.py +20 -0
  141. vellum/workflows/references/execution_count.py +20 -0
  142. vellum/workflows/references/external_input.py +49 -0
  143. vellum/workflows/references/input.py +7 -0
  144. vellum/workflows/references/lazy.py +55 -0
  145. vellum/workflows/references/node.py +43 -0
  146. vellum/workflows/references/output.py +78 -0
  147. vellum/workflows/references/state_value.py +23 -0
  148. vellum/workflows/references/vellum_secret.py +15 -0
  149. vellum/workflows/references/workflow_input.py +41 -0
  150. vellum/workflows/resolvers/__init__.py +5 -0
  151. vellum/workflows/resolvers/base.py +15 -0
  152. vellum/workflows/runner/__init__.py +5 -0
  153. vellum/workflows/runner/runner.py +588 -0
  154. vellum/workflows/runner/types.py +18 -0
  155. vellum/workflows/state/__init__.py +5 -0
  156. vellum/workflows/state/base.py +327 -0
  157. vellum/workflows/state/context.py +18 -0
  158. vellum/workflows/state/encoder.py +57 -0
  159. vellum/workflows/state/store.py +28 -0
  160. vellum/workflows/state/tests/__init__.py +0 -0
  161. vellum/workflows/state/tests/test_state.py +113 -0
  162. vellum/workflows/types/__init__.py +0 -0
  163. vellum/workflows/types/core.py +91 -0
  164. vellum/workflows/types/generics.py +14 -0
  165. vellum/workflows/types/stack.py +39 -0
  166. vellum/workflows/types/tests/__init__.py +0 -0
  167. vellum/workflows/types/tests/test_utils.py +76 -0
  168. vellum/workflows/types/utils.py +164 -0
  169. vellum/workflows/utils/__init__.py +0 -0
  170. vellum/workflows/utils/names.py +13 -0
  171. vellum/workflows/utils/tests/__init__.py +0 -0
  172. vellum/workflows/utils/tests/test_names.py +15 -0
  173. vellum/workflows/utils/tests/test_vellum_variables.py +25 -0
  174. vellum/workflows/utils/vellum_variables.py +81 -0
  175. vellum/workflows/vellum_client.py +18 -0
  176. vellum/workflows/workflows/__init__.py +5 -0
  177. vellum/workflows/workflows/base.py +365 -0
  178. {vellum_ai-0.9.16rc2.dist-info → vellum_ai-0.9.16rc4.dist-info}/METADATA +2 -1
  179. {vellum_ai-0.9.16rc2.dist-info → vellum_ai-0.9.16rc4.dist-info}/RECORD +245 -7
  180. vellum_cli/__init__.py +72 -0
  181. vellum_cli/aliased_group.py +103 -0
  182. vellum_cli/config.py +96 -0
  183. vellum_cli/image_push.py +112 -0
  184. vellum_cli/logger.py +36 -0
  185. vellum_cli/pull.py +73 -0
  186. vellum_cli/push.py +121 -0
  187. vellum_cli/tests/test_config.py +100 -0
  188. vellum_cli/tests/test_pull.py +152 -0
  189. vellum_ee/workflows/__init__.py +0 -0
  190. vellum_ee/workflows/display/__init__.py +0 -0
  191. vellum_ee/workflows/display/base.py +73 -0
  192. vellum_ee/workflows/display/nodes/__init__.py +4 -0
  193. vellum_ee/workflows/display/nodes/base_node_display.py +116 -0
  194. vellum_ee/workflows/display/nodes/base_node_vellum_display.py +36 -0
  195. vellum_ee/workflows/display/nodes/get_node_display_class.py +25 -0
  196. vellum_ee/workflows/display/nodes/tests/__init__.py +0 -0
  197. vellum_ee/workflows/display/nodes/tests/test_base_node_display.py +47 -0
  198. vellum_ee/workflows/display/nodes/types.py +18 -0
  199. vellum_ee/workflows/display/nodes/utils.py +33 -0
  200. vellum_ee/workflows/display/nodes/vellum/__init__.py +32 -0
  201. vellum_ee/workflows/display/nodes/vellum/api_node.py +205 -0
  202. vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +71 -0
  203. vellum_ee/workflows/display/nodes/vellum/conditional_node.py +217 -0
  204. vellum_ee/workflows/display/nodes/vellum/final_output_node.py +61 -0
  205. vellum_ee/workflows/display/nodes/vellum/guardrail_node.py +49 -0
  206. vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +170 -0
  207. vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +99 -0
  208. vellum_ee/workflows/display/nodes/vellum/map_node.py +100 -0
  209. vellum_ee/workflows/display/nodes/vellum/merge_node.py +48 -0
  210. vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py +68 -0
  211. vellum_ee/workflows/display/nodes/vellum/search_node.py +193 -0
  212. vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py +58 -0
  213. vellum_ee/workflows/display/nodes/vellum/templating_node.py +67 -0
  214. vellum_ee/workflows/display/nodes/vellum/tests/__init__.py +0 -0
  215. vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py +106 -0
  216. vellum_ee/workflows/display/nodes/vellum/try_node.py +38 -0
  217. vellum_ee/workflows/display/nodes/vellum/utils.py +76 -0
  218. vellum_ee/workflows/display/tests/__init__.py +0 -0
  219. vellum_ee/workflows/display/tests/workflow_serialization/__init__.py +0 -0
  220. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_api_node_serialization.py +426 -0
  221. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +607 -0
  222. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +1175 -0
  223. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_guardrail_node_serialization.py +235 -0
  224. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +511 -0
  225. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +372 -0
  226. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +272 -0
  227. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_prompt_deployment_serialization.py +289 -0
  228. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_subworkflow_deployment_serialization.py +354 -0
  229. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py +123 -0
  230. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py +84 -0
  231. vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +233 -0
  232. vellum_ee/workflows/display/types.py +46 -0
  233. vellum_ee/workflows/display/utils/__init__.py +0 -0
  234. vellum_ee/workflows/display/utils/tests/__init__.py +0 -0
  235. vellum_ee/workflows/display/utils/tests/test_uuids.py +16 -0
  236. vellum_ee/workflows/display/utils/uuids.py +24 -0
  237. vellum_ee/workflows/display/utils/vellum.py +121 -0
  238. vellum_ee/workflows/display/vellum.py +357 -0
  239. vellum_ee/workflows/display/workflows/__init__.py +5 -0
  240. vellum_ee/workflows/display/workflows/base_workflow_display.py +302 -0
  241. vellum_ee/workflows/display/workflows/get_vellum_workflow_display_class.py +32 -0
  242. vellum_ee/workflows/display/workflows/vellum_workflow_display.py +386 -0
  243. {vellum_ai-0.9.16rc2.dist-info → vellum_ai-0.9.16rc4.dist-info}/LICENSE +0 -0
  244. {vellum_ai-0.9.16rc2.dist-info → vellum_ai-0.9.16rc4.dist-info}/WHEEL +0 -0
  245. {vellum_ai-0.9.16rc2.dist-info → vellum_ai-0.9.16rc4.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,332 @@
1
+ from functools import cached_property, reduce
2
+ import inspect
3
+ from types import MappingProxyType
4
+ from typing import Any, Dict, Generic, Iterator, Optional, Set, Tuple, Type, TypeVar, Union, cast, get_args
5
+
6
+ from vellum.workflows.constants import UNDEF
7
+ from vellum.workflows.descriptors.base import BaseDescriptor
8
+ from vellum.workflows.descriptors.utils import resolve_value
9
+ from vellum.workflows.edges.edge import Edge
10
+ from vellum.workflows.errors.types import VellumErrorCode
11
+ from vellum.workflows.exceptions import NodeException
12
+ from vellum.workflows.graph import Graph
13
+ from vellum.workflows.graph.graph import GraphTarget
14
+ from vellum.workflows.inputs.base import BaseInputs
15
+ from vellum.workflows.outputs import BaseOutput, BaseOutputs
16
+ from vellum.workflows.ports.node_ports import NodePorts
17
+ from vellum.workflows.ports.port import Port
18
+ from vellum.workflows.references import ExternalInputReference
19
+ from vellum.workflows.references.execution_count import ExecutionCountReference
20
+ from vellum.workflows.references.node import NodeReference
21
+ from vellum.workflows.state.base import BaseState
22
+ from vellum.workflows.state.context import WorkflowContext
23
+ from vellum.workflows.types.core import MergeBehavior
24
+ from vellum.workflows.types.generics import StateType
25
+ from vellum.workflows.types.utils import get_class_attr_names, get_original_base, infer_types
26
+
27
+
28
+ def is_nested_class(nested: Any, parent: Type) -> bool:
29
+ return (
30
+ inspect.isclass(nested)
31
+ # If a class is defined within a function, we don't consider it nested in the class defining that function
32
+ # The example of this is a Subworkflow defined within TryNode.wrap()
33
+ and (len(nested.__qualname__.split(".")) < 2 or nested.__qualname__.split(".")[-2] != "<locals>")
34
+ and nested.__module__ == parent.__module__
35
+ and (nested.__qualname__.startswith(parent.__name__) or nested.__qualname__.startswith(parent.__qualname__))
36
+ ) or any(is_nested_class(nested, base) for base in parent.__bases__)
37
+
38
+
39
+ class BaseNodeMeta(type):
40
+ def __new__(mcs, name: str, bases: Tuple[Type, ...], dct: Dict[str, Any]) -> Any:
41
+ # TODO: Inherit the inner Output classes from every base class.
42
+ # https://app.shortcut.com/vellum/story/4007/support-auto-inheriting-parent-node-outputs
43
+
44
+ if "Outputs" not in dct:
45
+ for base in reversed(bases):
46
+ if hasattr(base, "Outputs"):
47
+ dct["Outputs"] = type(f"{name}.Outputs", (base.Outputs,), {"__module__": dct["__module__"]})
48
+ break
49
+ else:
50
+ raise ValueError("Outputs class not found in base classes")
51
+
52
+ if "Ports" in dct:
53
+ dct["Ports"] = type(
54
+ f"{name}.Ports",
55
+ (NodePorts,),
56
+ dict(dct["Ports"].__dict__),
57
+ )
58
+ else:
59
+ for base in reversed(bases):
60
+ if issubclass(base, BaseNode):
61
+ ports_dct = {p.name: Port(default=p.default) for p in base.Ports}
62
+ ports_dct["__module__"] = dct["__module__"]
63
+ dct["Ports"] = type(f"{name}.Ports", (NodePorts,), ports_dct)
64
+ break
65
+
66
+ if "Execution" not in dct:
67
+ for base in reversed(bases):
68
+ if issubclass(base, BaseNode):
69
+ dct["Execution"] = type(f"{name}.Execution", (base.Execution,), {"__module__": dct["__module__"]})
70
+ break
71
+
72
+ if "Trigger" not in dct:
73
+ for base in reversed(bases):
74
+ if issubclass(base, BaseNode):
75
+ trigger_dct = {**base.Trigger.__dict__, "__module__": dct["__module__"]}
76
+ dct["Trigger"] = type(f"{name}.Trigger", (base.Trigger,), trigger_dct)
77
+ break
78
+
79
+ cls = super().__new__(mcs, name, bases, dct)
80
+ node_class = cast(Type["BaseNode"], cls)
81
+
82
+ node_class.Outputs._node_class = node_class
83
+
84
+ # Add cls to relevant nested classes, since python should've been doing this by default
85
+ for port in node_class.Ports:
86
+ port.node_class = node_class
87
+
88
+ node_class.Execution.node_class = node_class
89
+ node_class.Trigger.node_class = node_class
90
+ node_class.ExternalInputs.__parent_class__ = node_class
91
+ return node_class
92
+
93
+ @property
94
+ def _localns(cls) -> Dict[str, Any]:
95
+ from vellum.workflows.workflows.base import BaseWorkflow
96
+
97
+ return {"BaseWorkflow": BaseWorkflow}
98
+
99
+ def __getattribute__(cls, name: str) -> Any:
100
+ attribute = super().__getattribute__(name)
101
+ if (
102
+ name.startswith("_")
103
+ or inspect.isfunction(attribute)
104
+ or inspect.ismethod(attribute)
105
+ or is_nested_class(attribute, cls)
106
+ or isinstance(attribute, (property, cached_property))
107
+ or not issubclass(cls, BaseNode)
108
+ ):
109
+ return attribute
110
+
111
+ types = infer_types(cls, name, cls._localns)
112
+ return NodeReference(
113
+ name=name,
114
+ types=types,
115
+ instance=attribute,
116
+ node_class=cls,
117
+ )
118
+
119
+ def __rshift__(cls, other_cls: GraphTarget) -> Graph:
120
+ if not issubclass(cls, BaseNode):
121
+ raise ValueError("BaseNodeMeta can only be extended from subclasses of BaseNode")
122
+
123
+ if not cls.Ports._default_port:
124
+ raise ValueError("No default port found on node")
125
+
126
+ if isinstance(other_cls, Graph) or isinstance(other_cls, set):
127
+ return Graph.from_node(cls) >> other_cls
128
+
129
+ return cls.Ports._default_port >> other_cls
130
+
131
+ def __rrshift__(cls, other_cls: GraphTarget) -> Graph:
132
+ if not issubclass(cls, BaseNode):
133
+ raise ValueError("BaseNodeMeta can only be extended from subclasses of BaseNode")
134
+
135
+ if not isinstance(other_cls, set):
136
+ other_cls = {other_cls}
137
+
138
+ return Graph.from_set(other_cls) >> cls
139
+
140
+ def __repr__(self) -> str:
141
+ return f"{self.__module__}.{self.__qualname__}"
142
+
143
+ def __iter__(cls) -> Iterator[NodeReference]:
144
+ # We iterate through the inheritance hierarchy to find all the OutputDescriptors attached to this Outputs class.
145
+ # __mro__ is the method resolution order, which is the order in which base classes are resolved.
146
+ yielded_attr_names: Set[str] = {"state"}
147
+
148
+ for resolved_cls in cls.__mro__:
149
+ attr_names = get_class_attr_names(resolved_cls)
150
+ for attr_name in attr_names:
151
+ if attr_name in yielded_attr_names:
152
+ continue
153
+
154
+ attr_value = getattr(resolved_cls, attr_name, UNDEF)
155
+ if not isinstance(attr_value, NodeReference):
156
+ continue
157
+
158
+ yield attr_value
159
+ yielded_attr_names.add(attr_name)
160
+
161
+
162
+ class _BaseNodeTriggerMeta(type):
163
+ def __eq__(self, other: Any) -> bool:
164
+ """
165
+ We need to include custom eq logic to prevent infinite loops during ipython reloading.
166
+ """
167
+
168
+ if not isinstance(other, _BaseNodeTriggerMeta):
169
+ return False
170
+
171
+ if not self.__name__.endswith(".Trigger") or not other.__name__.endswith(".Trigger"):
172
+ return super().__eq__(other)
173
+
174
+ self_trigger_class = cast(Type["BaseNode.Trigger"], self)
175
+ other_trigger_class = cast(Type["BaseNode.Trigger"], other)
176
+
177
+ return self_trigger_class.node_class.__name__ == other_trigger_class.node_class.__name__
178
+
179
+
180
+ class _BaseNodeExecutionMeta(type):
181
+ def __getattribute__(cls, name: str) -> Any:
182
+ if name.startswith("count") and issubclass(cls, BaseNode.Execution):
183
+ return ExecutionCountReference(cls.node_class)
184
+
185
+ return super().__getattribute__(name)
186
+
187
+ def __eq__(self, other: Any) -> bool:
188
+ """
189
+ We need to include custom eq logic to prevent infinite loops during ipython reloading.
190
+ """
191
+
192
+ if not isinstance(other, _BaseNodeExecutionMeta):
193
+ return False
194
+
195
+ if not self.__name__.endswith(".Execution") or not other.__name__.endswith(".Execution"):
196
+ return super().__eq__(other)
197
+
198
+ self_execution_class = cast(Type["BaseNode.Execution"], self)
199
+ other_execution_class = cast(Type["BaseNode.Execution"], other)
200
+
201
+ return self_execution_class.node_class.__name__ == other_execution_class.node_class.__name__
202
+
203
+
204
+ class BaseNode(Generic[StateType], metaclass=BaseNodeMeta):
205
+ state: StateType
206
+ _context: WorkflowContext
207
+ _inputs: MappingProxyType[NodeReference, Any]
208
+ _is_wrapped_node: bool = False
209
+
210
+ class ExternalInputs(BaseInputs):
211
+ __descriptor_class__ = ExternalInputReference
212
+
213
+ # TODO: Consider using metaclasses to prevent the need for users to specify that the
214
+ # "Outputs" class inherits from "BaseOutputs" and do so automatically.
215
+ # https://app.shortcut.com/vellum/story/4008/auto-inherit-basenodeoutputs-in-outputs-classes
216
+ class Outputs(BaseOutputs):
217
+ _node_class: Optional[Type["BaseNode"]] = None
218
+ pass
219
+
220
+ class Ports(NodePorts):
221
+ default = Port(default=True)
222
+
223
+ class Trigger(metaclass=_BaseNodeTriggerMeta):
224
+ node_class: Type["BaseNode"]
225
+ merge_behavior = MergeBehavior.AWAIT_ANY
226
+
227
+ @classmethod
228
+ def should_initiate(
229
+ cls, state: StateType, dependencies: Set["Type[BaseNode]"], invoked_by: "Optional[Edge]" = None
230
+ ) -> bool:
231
+ """
232
+ Determines whether a Node's execution should be initiated. Override this method to define custom
233
+ trigger criteria.
234
+ """
235
+
236
+ if cls.merge_behavior == MergeBehavior.AWAIT_ANY:
237
+ if not invoked_by:
238
+ return True
239
+
240
+ is_ready = not state.meta.node_execution_cache.is_node_initiated(cls.node_class)
241
+
242
+ invoked_identifier = str(invoked_by.from_port.node_class)
243
+ node_identifier = str(cls.node_class)
244
+
245
+ dependencies_invoked = state.meta.node_execution_cache.dependencies_invoked[node_identifier]
246
+ dependencies_invoked.add(invoked_identifier)
247
+ if all(str(dep) in dependencies_invoked for dep in dependencies):
248
+ del state.meta.node_execution_cache.dependencies_invoked[node_identifier]
249
+
250
+ return is_ready
251
+
252
+ if cls.merge_behavior == MergeBehavior.AWAIT_ALL:
253
+ if not invoked_by:
254
+ return True
255
+
256
+ if state.meta.node_execution_cache.is_node_initiated(cls.node_class):
257
+ return False
258
+
259
+ """
260
+ A node utilizing an AWAIT_ALL merge strategy will only be considered ready for the Nth time
261
+ when all of its dependencies have been executed N times.
262
+ """
263
+ current_node_execution_count = state.meta.node_execution_cache.get_execution_count(cls.node_class)
264
+ is_ready_outcome = all(
265
+ state.meta.node_execution_cache.get_execution_count(dep) == current_node_execution_count + 1
266
+ for dep in dependencies
267
+ )
268
+
269
+ return is_ready_outcome
270
+
271
+ raise NodeException(message="Invalid Trigger Node Specification", code=VellumErrorCode.INVALID_INPUTS)
272
+
273
+ class Execution(metaclass=_BaseNodeExecutionMeta):
274
+ node_class: Type["BaseNode"]
275
+ count: int
276
+
277
+ def __init__(self, *, state: Optional[StateType] = None, context: Optional[WorkflowContext] = None):
278
+ if state:
279
+ self.state = state
280
+ else:
281
+ # Instantiate a default state class if one is not provided, for ease of testing
282
+
283
+ original_base = get_original_base(self.__class__)
284
+
285
+ args = get_args(original_base)
286
+ state_type = args[0]
287
+
288
+ if isinstance(state_type, TypeVar):
289
+ state_type = BaseState
290
+
291
+ self.state = state_type()
292
+
293
+ self._context = context or WorkflowContext()
294
+ inputs: Dict[str, Any] = {}
295
+ for descriptor in self.__class__:
296
+ if not descriptor.instance:
297
+ continue
298
+
299
+ resolved_value = resolve_value(descriptor.instance, self.state, path=descriptor.name, memo=inputs)
300
+ setattr(self, descriptor.name, resolved_value)
301
+
302
+ # Resolve descriptors set as defaults to the outputs class
303
+ def _outputs_post_init(outputs_self: "BaseNode.Outputs", **kwargs: Any) -> None:
304
+ for node_output_descriptor in self.Outputs:
305
+ if node_output_descriptor.name in kwargs:
306
+ continue
307
+
308
+ if isinstance(node_output_descriptor.instance, BaseDescriptor):
309
+ setattr(
310
+ outputs_self,
311
+ node_output_descriptor.name,
312
+ node_output_descriptor.instance.resolve(self.state),
313
+ )
314
+ delattr(self.Outputs, "_outputs_post_init")
315
+
316
+ setattr(self.Outputs, "_outputs_post_init", _outputs_post_init)
317
+
318
+ # We only want to store the attributes that were actually set as inputs, not every attribute that exists.
319
+ all_inputs = {}
320
+ for key, value in inputs.items():
321
+ path_parts = key.split(".")
322
+ node_attribute_discriptor = getattr(self.__class__, path_parts[0])
323
+ inputs_key = reduce(lambda acc, part: acc[part], path_parts[1:], node_attribute_discriptor)
324
+ all_inputs[inputs_key] = value
325
+
326
+ self._inputs = MappingProxyType(all_inputs)
327
+
328
+ def run(self) -> Union[BaseOutputs, Iterator[BaseOutput]]:
329
+ return self.Outputs()
330
+
331
+ def __repr__(self) -> str:
332
+ return str(self.__class__)
@@ -0,0 +1,5 @@
1
+ from .node import BaseSubworkflowNode
2
+
3
+ __all__ = [
4
+ "BaseSubworkflowNode",
5
+ ]
@@ -0,0 +1,10 @@
1
+ from typing import ClassVar, Generic
2
+
3
+ from vellum.workflows.nodes.bases import BaseNode
4
+ from vellum.workflows.types.core import EntityInputsInterface
5
+ from vellum.workflows.types.generics import StateType
6
+
7
+
8
+ class BaseSubworkflowNode(BaseNode[StateType], Generic[StateType]):
9
+ # Inputs that are passed to the Subworkflow
10
+ subworkflow_inputs: ClassVar[EntityInputsInterface] = {}
File without changes
@@ -0,0 +1,125 @@
1
+ from typing import Optional
2
+
3
+ from vellum.core.pydantic_utilities import UniversalBaseModel
4
+
5
+ from vellum.workflows.inputs.base import BaseInputs
6
+ from vellum.workflows.nodes.bases.base import BaseNode
7
+ from vellum.workflows.state.base import BaseState, StateMeta
8
+
9
+
10
+ def test_base_node__node_resolution__unset_pydantic_fields():
11
+ # GIVEN a pydantic class with an optional field
12
+ class Data(UniversalBaseModel):
13
+ hello: str
14
+ world: Optional[str] = None
15
+
16
+ # AND a node that uses the pydantic class only setting one field
17
+ my_data = Data(hello="hi")
18
+
19
+ class MyNode(BaseNode):
20
+ data = my_data
21
+
22
+ # WHEN the node is initialized
23
+ node = MyNode()
24
+
25
+ # THEN the node is initialized with the correct data
26
+ assert node.data.dict() == my_data.dict()
27
+
28
+
29
+ def test_base_node__node_resolution__descriptor_in_dict():
30
+ # GIVEN an Input and State class
31
+ class Inputs(BaseInputs):
32
+ hello: str
33
+
34
+ class State(BaseState):
35
+ pass
36
+
37
+ # AND a node referencing a descriptor in a dict
38
+ class MyNode(BaseNode):
39
+ data = {"world": Inputs.hello}
40
+
41
+ # WHEN the node is initialized
42
+ node = MyNode(
43
+ state=State(
44
+ meta=StateMeta(
45
+ workflow_inputs=Inputs(hello="hi"),
46
+ )
47
+ )
48
+ )
49
+
50
+ # THEN the node is initialized with the correct data
51
+ assert node.data["world"] == "hi"
52
+
53
+ # AND there are inputs compiled
54
+ assert node._inputs == {
55
+ MyNode.data["world"]: "hi",
56
+ }
57
+
58
+
59
+ def test_base_node__node_resolution__descriptor_in_pydantic():
60
+ # GIVEN an Input and State class
61
+ class Inputs(BaseInputs):
62
+ hello: str
63
+
64
+ class State(BaseState):
65
+ pass
66
+
67
+ class Data(UniversalBaseModel):
68
+ world: str
69
+
70
+ class MyNode(BaseNode):
71
+ data = Data(world=Inputs.hello)
72
+
73
+ # WHEN the node is initialized
74
+ node = MyNode(
75
+ state=State(
76
+ meta=StateMeta(
77
+ workflow_inputs=Inputs(hello="hi"),
78
+ )
79
+ )
80
+ )
81
+
82
+ # THEN the node is initialized with the correct data
83
+ assert node.data.world == "hi"
84
+
85
+ # AND there are inputs compiled
86
+ assert node._inputs == {
87
+ MyNode.data["world"]: "hi",
88
+ }
89
+
90
+
91
+ def test_base_node__node_resolution__no_inputs():
92
+ # GIVEN a node that defines some attributes
93
+ class MyNode(BaseNode):
94
+ foo = "bar"
95
+ baz = 1
96
+
97
+ # WHEN the node is initialized
98
+ node = MyNode()
99
+
100
+ # THEN the node is initialized with the correct data
101
+ assert node.foo == "bar"
102
+ assert node.baz == 1
103
+
104
+ # AND there are no inputs
105
+ assert node._inputs == {}
106
+
107
+
108
+ def test_base_node__node_resolution__coalesce_constants():
109
+ # GIVEN a State class
110
+ class State(BaseState):
111
+ pass
112
+
113
+ # AND a node that uses the coalesce operator with constants
114
+ class FirstNode(BaseNode):
115
+ class Outputs(BaseNode.Outputs):
116
+ empty: str
117
+
118
+ class MyNode(BaseNode):
119
+ value = FirstNode.Outputs.empty.coalesce("world")
120
+
121
+ # WHEN the node is initialized
122
+ node = MyNode(state=State())
123
+
124
+ # THEN the node is initialized with the correct data
125
+ assert node.value == "world"
@@ -0,0 +1,16 @@
1
+ from vellum.workflows.nodes.displayable.bases.api_node import BaseAPINode
2
+
3
+ from .error_node import ErrorNode
4
+ from .inline_subworkflow_node import InlineSubworkflowNode
5
+ from .map_node import MapNode
6
+ from .retry_node import RetryNode
7
+ from .templating_node import TemplatingNode
8
+
9
+ __all__ = [
10
+ "BaseAPINode",
11
+ "ErrorNode",
12
+ "InlineSubworkflowNode",
13
+ "MapNode",
14
+ "RetryNode",
15
+ "TemplatingNode",
16
+ ]
@@ -0,0 +1,5 @@
1
+ from .node import ErrorNode
2
+
3
+ __all__ = [
4
+ "ErrorNode",
5
+ ]
@@ -0,0 +1,26 @@
1
+ from typing import ClassVar, Union
2
+
3
+ from vellum.workflows.errors.types import VellumError, VellumErrorCode
4
+ from vellum.workflows.exceptions import NodeException
5
+ from vellum.workflows.nodes.bases.base import BaseNode
6
+
7
+
8
+ class ErrorNode(BaseNode):
9
+ """
10
+ Used to raise an error to reject the surrounding Workflow.
11
+
12
+ error: Union[str, VellumError] - The error to raise.
13
+ """
14
+
15
+ error: ClassVar[Union[str, VellumError]]
16
+
17
+ def run(self) -> BaseNode.Outputs:
18
+ if isinstance(self.error, str):
19
+ raise NodeException(message=self.error, code=VellumErrorCode.USER_DEFINED_ERROR)
20
+ elif isinstance(self.error, VellumError):
21
+ raise NodeException(message=self.error.message, code=self.error.code)
22
+ else:
23
+ raise NodeException(
24
+ message=f"Error node received an unexpected input type: {self.error.__class__}",
25
+ code=VellumErrorCode.INVALID_INPUTS,
26
+ )
@@ -0,0 +1,5 @@
1
+ from .node import InlineSubworkflowNode
2
+
3
+ __all__ = [
4
+ "InlineSubworkflowNode",
5
+ ]
@@ -0,0 +1,73 @@
1
+ from typing import TYPE_CHECKING, Generic, Iterator, Optional, Set, Type, TypeVar
2
+
3
+ from vellum.workflows.errors.types import VellumErrorCode
4
+ from vellum.workflows.exceptions import NodeException
5
+ from vellum.workflows.nodes.bases.base_subworkflow_node import BaseSubworkflowNode
6
+ from vellum.workflows.outputs.base import BaseOutput, BaseOutputs
7
+ from vellum.workflows.state.base import BaseState
8
+ from vellum.workflows.types.generics import StateType, WorkflowInputsType
9
+
10
+ if TYPE_CHECKING:
11
+ from vellum.workflows.workflows.base import BaseWorkflow
12
+
13
+ InnerStateType = TypeVar("InnerStateType", bound=BaseState)
14
+
15
+
16
+ class InlineSubworkflowNode(BaseSubworkflowNode[StateType], Generic[StateType, WorkflowInputsType, InnerStateType]):
17
+ """
18
+ Used to execute a Subworkflow defined inline.
19
+
20
+ subworkflow: Type["BaseWorkflow[WorkflowInputsType, InnerStateType]"] - The Subworkflow to execute
21
+ """
22
+
23
+ subworkflow: Type["BaseWorkflow[WorkflowInputsType, InnerStateType]"]
24
+
25
+ def run(self) -> Iterator[BaseOutput]:
26
+ subworkflow = self.subworkflow(
27
+ parent_state=self.state,
28
+ )
29
+ subworkflow_stream = subworkflow.stream(
30
+ inputs=self._compile_subworkflow_inputs(),
31
+ )
32
+
33
+ outputs: Optional[BaseOutputs] = None
34
+ fulfilled_output_names: Set[str] = set()
35
+
36
+ for event in subworkflow_stream:
37
+ if event.name == "workflow.execution.streaming":
38
+ if event.output.is_fulfilled:
39
+ fulfilled_output_names.add(event.output.name)
40
+ yield event.output
41
+ elif event.name == "workflow.execution.fulfilled":
42
+ outputs = event.outputs
43
+ elif event.name == "workflow.execution.rejected":
44
+ error = event.error
45
+ if error.code in VellumErrorCode._value2member_map_:
46
+ raise NodeException(
47
+ message=error.message,
48
+ code=VellumErrorCode(error.code),
49
+ )
50
+ else:
51
+ raise NodeException(
52
+ message=error.message,
53
+ code=VellumErrorCode.INTERNAL_ERROR,
54
+ )
55
+
56
+ if outputs is None:
57
+ raise NodeException(
58
+ message="Expected to receive outputs from Workflow Deployment",
59
+ code=VellumErrorCode.INTERNAL_ERROR,
60
+ )
61
+
62
+ # For any outputs somehow in our final fulfilled outputs array,
63
+ # but not fulfilled by the stream.
64
+ for output_descriptor, output_value in outputs:
65
+ if output_descriptor.name not in fulfilled_output_names:
66
+ yield BaseOutput(
67
+ name=output_descriptor.name,
68
+ value=output_value,
69
+ )
70
+
71
+ def _compile_subworkflow_inputs(self) -> WorkflowInputsType:
72
+ inputs_class = self.subworkflow.get_inputs_class()
73
+ return inputs_class(**self.subworkflow_inputs)
@@ -0,0 +1,5 @@
1
+ from .node import MapNode
2
+
3
+ __all__ = [
4
+ "MapNode",
5
+ ]