vellum-ai 0.9.16rc2__py3-none-any.whl → 0.9.16rc4__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.
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
+ ]