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.
- vellum/plugins/__init__.py +0 -0
- vellum/plugins/pydantic.py +74 -0
- vellum/plugins/utils.py +19 -0
- vellum/plugins/vellum_mypy.py +639 -3
- vellum/workflows/README.md +90 -0
- vellum/workflows/__init__.py +5 -0
- vellum/workflows/constants.py +43 -0
- vellum/workflows/descriptors/__init__.py +0 -0
- vellum/workflows/descriptors/base.py +339 -0
- vellum/workflows/descriptors/tests/test_utils.py +83 -0
- vellum/workflows/descriptors/utils.py +90 -0
- vellum/workflows/edges/__init__.py +5 -0
- vellum/workflows/edges/edge.py +23 -0
- vellum/workflows/emitters/__init__.py +5 -0
- vellum/workflows/emitters/base.py +14 -0
- vellum/workflows/environment/__init__.py +5 -0
- vellum/workflows/environment/environment.py +7 -0
- vellum/workflows/errors/__init__.py +6 -0
- vellum/workflows/errors/types.py +20 -0
- vellum/workflows/events/__init__.py +31 -0
- vellum/workflows/events/node.py +125 -0
- vellum/workflows/events/tests/__init__.py +0 -0
- vellum/workflows/events/tests/test_event.py +216 -0
- vellum/workflows/events/types.py +52 -0
- vellum/workflows/events/utils.py +5 -0
- vellum/workflows/events/workflow.py +139 -0
- vellum/workflows/exceptions.py +15 -0
- vellum/workflows/expressions/__init__.py +0 -0
- vellum/workflows/expressions/accessor.py +52 -0
- vellum/workflows/expressions/and_.py +32 -0
- vellum/workflows/expressions/begins_with.py +31 -0
- vellum/workflows/expressions/between.py +38 -0
- vellum/workflows/expressions/coalesce_expression.py +41 -0
- vellum/workflows/expressions/contains.py +30 -0
- vellum/workflows/expressions/does_not_begin_with.py +31 -0
- vellum/workflows/expressions/does_not_contain.py +30 -0
- vellum/workflows/expressions/does_not_end_with.py +31 -0
- vellum/workflows/expressions/does_not_equal.py +25 -0
- vellum/workflows/expressions/ends_with.py +31 -0
- vellum/workflows/expressions/equals.py +25 -0
- vellum/workflows/expressions/greater_than.py +33 -0
- vellum/workflows/expressions/greater_than_or_equal_to.py +33 -0
- vellum/workflows/expressions/in_.py +31 -0
- vellum/workflows/expressions/is_blank.py +24 -0
- vellum/workflows/expressions/is_not_blank.py +24 -0
- vellum/workflows/expressions/is_not_null.py +21 -0
- vellum/workflows/expressions/is_not_undefined.py +22 -0
- vellum/workflows/expressions/is_null.py +21 -0
- vellum/workflows/expressions/is_undefined.py +22 -0
- vellum/workflows/expressions/less_than.py +33 -0
- vellum/workflows/expressions/less_than_or_equal_to.py +33 -0
- vellum/workflows/expressions/not_between.py +38 -0
- vellum/workflows/expressions/not_in.py +31 -0
- vellum/workflows/expressions/or_.py +32 -0
- vellum/workflows/graph/__init__.py +3 -0
- vellum/workflows/graph/graph.py +131 -0
- vellum/workflows/graph/tests/__init__.py +0 -0
- vellum/workflows/graph/tests/test_graph.py +437 -0
- vellum/workflows/inputs/__init__.py +5 -0
- vellum/workflows/inputs/base.py +55 -0
- vellum/workflows/logging.py +14 -0
- vellum/workflows/nodes/__init__.py +46 -0
- vellum/workflows/nodes/bases/__init__.py +7 -0
- vellum/workflows/nodes/bases/base.py +332 -0
- vellum/workflows/nodes/bases/base_subworkflow_node/__init__.py +5 -0
- vellum/workflows/nodes/bases/base_subworkflow_node/node.py +10 -0
- vellum/workflows/nodes/bases/tests/__init__.py +0 -0
- vellum/workflows/nodes/bases/tests/test_base_node.py +125 -0
- vellum/workflows/nodes/core/__init__.py +16 -0
- vellum/workflows/nodes/core/error_node/__init__.py +5 -0
- vellum/workflows/nodes/core/error_node/node.py +26 -0
- vellum/workflows/nodes/core/inline_subworkflow_node/__init__.py +5 -0
- vellum/workflows/nodes/core/inline_subworkflow_node/node.py +73 -0
- vellum/workflows/nodes/core/map_node/__init__.py +5 -0
- vellum/workflows/nodes/core/map_node/node.py +147 -0
- vellum/workflows/nodes/core/map_node/tests/__init__.py +0 -0
- vellum/workflows/nodes/core/map_node/tests/test_node.py +65 -0
- vellum/workflows/nodes/core/retry_node/__init__.py +5 -0
- vellum/workflows/nodes/core/retry_node/node.py +106 -0
- vellum/workflows/nodes/core/retry_node/tests/__init__.py +0 -0
- vellum/workflows/nodes/core/retry_node/tests/test_node.py +93 -0
- vellum/workflows/nodes/core/templating_node/__init__.py +5 -0
- vellum/workflows/nodes/core/templating_node/custom_filters.py +12 -0
- vellum/workflows/nodes/core/templating_node/exceptions.py +2 -0
- vellum/workflows/nodes/core/templating_node/node.py +123 -0
- vellum/workflows/nodes/core/templating_node/render.py +55 -0
- vellum/workflows/nodes/core/templating_node/tests/test_templating_node.py +21 -0
- vellum/workflows/nodes/core/try_node/__init__.py +5 -0
- vellum/workflows/nodes/core/try_node/node.py +110 -0
- vellum/workflows/nodes/core/try_node/tests/__init__.py +0 -0
- vellum/workflows/nodes/core/try_node/tests/test_node.py +82 -0
- vellum/workflows/nodes/displayable/__init__.py +31 -0
- vellum/workflows/nodes/displayable/api_node/__init__.py +5 -0
- vellum/workflows/nodes/displayable/api_node/node.py +44 -0
- vellum/workflows/nodes/displayable/bases/__init__.py +11 -0
- vellum/workflows/nodes/displayable/bases/api_node/__init__.py +5 -0
- vellum/workflows/nodes/displayable/bases/api_node/node.py +70 -0
- vellum/workflows/nodes/displayable/bases/base_prompt_node/__init__.py +5 -0
- vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py +60 -0
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/__init__.py +5 -0
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/constants.py +13 -0
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +118 -0
- vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py +98 -0
- vellum/workflows/nodes/displayable/bases/search_node.py +90 -0
- vellum/workflows/nodes/displayable/code_execution_node/__init__.py +5 -0
- vellum/workflows/nodes/displayable/code_execution_node/node.py +197 -0
- vellum/workflows/nodes/displayable/code_execution_node/tests/__init__.py +0 -0
- vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/__init__.py +0 -0
- vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/main.py +3 -0
- vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py +111 -0
- vellum/workflows/nodes/displayable/code_execution_node/utils.py +10 -0
- vellum/workflows/nodes/displayable/conditional_node/__init__.py +5 -0
- vellum/workflows/nodes/displayable/conditional_node/node.py +25 -0
- vellum/workflows/nodes/displayable/final_output_node/__init__.py +5 -0
- vellum/workflows/nodes/displayable/final_output_node/node.py +43 -0
- vellum/workflows/nodes/displayable/guardrail_node/__init__.py +5 -0
- vellum/workflows/nodes/displayable/guardrail_node/node.py +97 -0
- vellum/workflows/nodes/displayable/inline_prompt_node/__init__.py +5 -0
- vellum/workflows/nodes/displayable/inline_prompt_node/node.py +41 -0
- vellum/workflows/nodes/displayable/merge_node/__init__.py +5 -0
- vellum/workflows/nodes/displayable/merge_node/node.py +10 -0
- vellum/workflows/nodes/displayable/prompt_deployment_node/__init__.py +5 -0
- vellum/workflows/nodes/displayable/prompt_deployment_node/node.py +45 -0
- vellum/workflows/nodes/displayable/search_node/__init__.py +5 -0
- vellum/workflows/nodes/displayable/search_node/node.py +26 -0
- vellum/workflows/nodes/displayable/subworkflow_deployment_node/__init__.py +5 -0
- vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +156 -0
- vellum/workflows/nodes/displayable/tests/__init__.py +0 -0
- vellum/workflows/nodes/displayable/tests/test_inline_text_prompt_node.py +148 -0
- vellum/workflows/nodes/displayable/tests/test_search_node_wth_text_output.py +134 -0
- vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py +80 -0
- vellum/workflows/nodes/utils.py +27 -0
- vellum/workflows/outputs/__init__.py +6 -0
- vellum/workflows/outputs/base.py +196 -0
- vellum/workflows/ports/__init__.py +7 -0
- vellum/workflows/ports/node_ports.py +75 -0
- vellum/workflows/ports/port.py +75 -0
- vellum/workflows/ports/utils.py +40 -0
- vellum/workflows/references/__init__.py +17 -0
- vellum/workflows/references/environment_variable.py +20 -0
- vellum/workflows/references/execution_count.py +20 -0
- vellum/workflows/references/external_input.py +49 -0
- vellum/workflows/references/input.py +7 -0
- vellum/workflows/references/lazy.py +55 -0
- vellum/workflows/references/node.py +43 -0
- vellum/workflows/references/output.py +78 -0
- vellum/workflows/references/state_value.py +23 -0
- vellum/workflows/references/vellum_secret.py +15 -0
- vellum/workflows/references/workflow_input.py +41 -0
- vellum/workflows/resolvers/__init__.py +5 -0
- vellum/workflows/resolvers/base.py +15 -0
- vellum/workflows/runner/__init__.py +5 -0
- vellum/workflows/runner/runner.py +588 -0
- vellum/workflows/runner/types.py +18 -0
- vellum/workflows/state/__init__.py +5 -0
- vellum/workflows/state/base.py +327 -0
- vellum/workflows/state/context.py +18 -0
- vellum/workflows/state/encoder.py +57 -0
- vellum/workflows/state/store.py +28 -0
- vellum/workflows/state/tests/__init__.py +0 -0
- vellum/workflows/state/tests/test_state.py +113 -0
- vellum/workflows/types/__init__.py +0 -0
- vellum/workflows/types/core.py +91 -0
- vellum/workflows/types/generics.py +14 -0
- vellum/workflows/types/stack.py +39 -0
- vellum/workflows/types/tests/__init__.py +0 -0
- vellum/workflows/types/tests/test_utils.py +76 -0
- vellum/workflows/types/utils.py +164 -0
- vellum/workflows/utils/__init__.py +0 -0
- vellum/workflows/utils/names.py +13 -0
- vellum/workflows/utils/tests/__init__.py +0 -0
- vellum/workflows/utils/tests/test_names.py +15 -0
- vellum/workflows/utils/tests/test_vellum_variables.py +25 -0
- vellum/workflows/utils/vellum_variables.py +81 -0
- vellum/workflows/vellum_client.py +18 -0
- vellum/workflows/workflows/__init__.py +5 -0
- vellum/workflows/workflows/base.py +365 -0
- {vellum_ai-0.9.16rc2.dist-info → vellum_ai-0.9.16rc4.dist-info}/METADATA +2 -1
- {vellum_ai-0.9.16rc2.dist-info → vellum_ai-0.9.16rc4.dist-info}/RECORD +245 -7
- vellum_cli/__init__.py +72 -0
- vellum_cli/aliased_group.py +103 -0
- vellum_cli/config.py +96 -0
- vellum_cli/image_push.py +112 -0
- vellum_cli/logger.py +36 -0
- vellum_cli/pull.py +73 -0
- vellum_cli/push.py +121 -0
- vellum_cli/tests/test_config.py +100 -0
- vellum_cli/tests/test_pull.py +152 -0
- vellum_ee/workflows/__init__.py +0 -0
- vellum_ee/workflows/display/__init__.py +0 -0
- vellum_ee/workflows/display/base.py +73 -0
- vellum_ee/workflows/display/nodes/__init__.py +4 -0
- vellum_ee/workflows/display/nodes/base_node_display.py +116 -0
- vellum_ee/workflows/display/nodes/base_node_vellum_display.py +36 -0
- vellum_ee/workflows/display/nodes/get_node_display_class.py +25 -0
- vellum_ee/workflows/display/nodes/tests/__init__.py +0 -0
- vellum_ee/workflows/display/nodes/tests/test_base_node_display.py +47 -0
- vellum_ee/workflows/display/nodes/types.py +18 -0
- vellum_ee/workflows/display/nodes/utils.py +33 -0
- vellum_ee/workflows/display/nodes/vellum/__init__.py +32 -0
- vellum_ee/workflows/display/nodes/vellum/api_node.py +205 -0
- vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +71 -0
- vellum_ee/workflows/display/nodes/vellum/conditional_node.py +217 -0
- vellum_ee/workflows/display/nodes/vellum/final_output_node.py +61 -0
- vellum_ee/workflows/display/nodes/vellum/guardrail_node.py +49 -0
- vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +170 -0
- vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +99 -0
- vellum_ee/workflows/display/nodes/vellum/map_node.py +100 -0
- vellum_ee/workflows/display/nodes/vellum/merge_node.py +48 -0
- vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py +68 -0
- vellum_ee/workflows/display/nodes/vellum/search_node.py +193 -0
- vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py +58 -0
- vellum_ee/workflows/display/nodes/vellum/templating_node.py +67 -0
- vellum_ee/workflows/display/nodes/vellum/tests/__init__.py +0 -0
- vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py +106 -0
- vellum_ee/workflows/display/nodes/vellum/try_node.py +38 -0
- vellum_ee/workflows/display/nodes/vellum/utils.py +76 -0
- vellum_ee/workflows/display/tests/__init__.py +0 -0
- vellum_ee/workflows/display/tests/workflow_serialization/__init__.py +0 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_api_node_serialization.py +426 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +607 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +1175 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_guardrail_node_serialization.py +235 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +511 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +372 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +272 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_prompt_deployment_serialization.py +289 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_subworkflow_deployment_serialization.py +354 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py +123 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py +84 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +233 -0
- vellum_ee/workflows/display/types.py +46 -0
- vellum_ee/workflows/display/utils/__init__.py +0 -0
- vellum_ee/workflows/display/utils/tests/__init__.py +0 -0
- vellum_ee/workflows/display/utils/tests/test_uuids.py +16 -0
- vellum_ee/workflows/display/utils/uuids.py +24 -0
- vellum_ee/workflows/display/utils/vellum.py +121 -0
- vellum_ee/workflows/display/vellum.py +357 -0
- vellum_ee/workflows/display/workflows/__init__.py +5 -0
- vellum_ee/workflows/display/workflows/base_workflow_display.py +302 -0
- vellum_ee/workflows/display/workflows/get_vellum_workflow_display_class.py +32 -0
- vellum_ee/workflows/display/workflows/vellum_workflow_display.py +386 -0
- {vellum_ai-0.9.16rc2.dist-info → vellum_ai-0.9.16rc4.dist-info}/LICENSE +0 -0
- {vellum_ai-0.9.16rc2.dist-info → vellum_ai-0.9.16rc4.dist-info}/WHEEL +0 -0
- {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,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,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,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)
|