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.
- 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)
|