vellum-ai 0.14.5__py3-none-any.whl → 0.14.7__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/__init__.py +18 -0
- vellum/client/__init__.py +8 -8
- vellum/client/core/client_wrapper.py +1 -1
- vellum/client/resources/__init__.py +2 -0
- vellum/client/resources/workflow_sandboxes/__init__.py +3 -0
- vellum/client/resources/workflow_sandboxes/client.py +146 -0
- vellum/client/resources/workflow_sandboxes/types/__init__.py +5 -0
- vellum/client/resources/workflow_sandboxes/types/list_workflow_sandbox_examples_request_tag.py +5 -0
- vellum/client/types/__init__.py +16 -0
- vellum/client/types/array_chat_message_content_item.py +6 -1
- vellum/client/types/array_chat_message_content_item_request.py +2 -0
- vellum/client/types/chat_message_content.py +2 -0
- vellum/client/types/chat_message_content_request.py +2 -0
- vellum/client/types/document_chat_message_content.py +25 -0
- vellum/client/types/document_chat_message_content_request.py +25 -0
- vellum/client/types/document_vellum_value.py +25 -0
- vellum/client/types/document_vellum_value_request.py +25 -0
- vellum/client/types/paginated_workflow_sandbox_example_list.py +23 -0
- vellum/client/types/vellum_document.py +20 -0
- vellum/client/types/vellum_document_request.py +20 -0
- vellum/client/types/vellum_value.py +2 -0
- vellum/client/types/vellum_value_request.py +2 -0
- vellum/client/types/vellum_variable_type.py +1 -0
- vellum/client/types/workflow_sandbox_example.py +22 -0
- vellum/resources/workflow_sandboxes/types/__init__.py +3 -0
- vellum/resources/workflow_sandboxes/types/list_workflow_sandbox_examples_request_tag.py +3 -0
- vellum/types/document_chat_message_content.py +3 -0
- vellum/types/document_chat_message_content_request.py +3 -0
- vellum/types/document_vellum_value.py +3 -0
- vellum/types/document_vellum_value_request.py +3 -0
- vellum/types/paginated_workflow_sandbox_example_list.py +3 -0
- vellum/types/vellum_document.py +3 -0
- vellum/types/vellum_document_request.py +3 -0
- vellum/types/workflow_sandbox_example.py +3 -0
- vellum/workflows/exceptions.py +18 -0
- vellum/workflows/inputs/base.py +27 -1
- vellum/workflows/inputs/tests/__init__.py +0 -0
- vellum/workflows/inputs/tests/test_inputs.py +49 -0
- vellum/workflows/nodes/core/inline_subworkflow_node/node.py +1 -1
- vellum/workflows/nodes/core/map_node/node.py +7 -7
- vellum/workflows/nodes/core/try_node/node.py +1 -1
- vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py +2 -2
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +5 -3
- vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py +5 -4
- vellum/workflows/nodes/displayable/inline_prompt_node/tests/test_node.py +4 -4
- vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +49 -15
- vellum/workflows/nodes/displayable/subworkflow_deployment_node/tests/test_node.py +165 -0
- vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py +3 -1
- vellum/workflows/outputs/base.py +1 -1
- vellum/workflows/runner/runner.py +16 -10
- vellum/workflows/state/context.py +7 -7
- vellum/workflows/workflows/base.py +61 -59
- vellum/workflows/workflows/tests/test_base_workflow.py +131 -40
- {vellum_ai-0.14.5.dist-info → vellum_ai-0.14.7.dist-info}/METADATA +1 -1
- {vellum_ai-0.14.5.dist-info → vellum_ai-0.14.7.dist-info}/RECORD +68 -44
- vellum_cli/__init__.py +36 -0
- vellum_cli/init.py +128 -0
- vellum_cli/pull.py +6 -3
- vellum_cli/tests/test_init.py +355 -0
- vellum_cli/tests/test_pull.py +127 -0
- vellum_ee/workflows/display/nodes/base_node_display.py +4 -4
- vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py +31 -0
- vellum_ee/workflows/display/nodes/vellum/utils.py +8 -0
- vellum_ee/workflows/display/vellum.py +0 -4
- vellum_ee/workflows/display/workflows/tests/test_workflow_display.py +29 -0
- {vellum_ai-0.14.5.dist-info → vellum_ai-0.14.7.dist-info}/LICENSE +0 -0
- {vellum_ai-0.14.5.dist-info → vellum_ai-0.14.7.dist-info}/WHEEL +0 -0
- {vellum_ai-0.14.5.dist-info → vellum_ai-0.14.7.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
# This file was auto-generated by Fern from our API Definition.
|
2
|
+
|
3
|
+
from ..core.pydantic_utilities import UniversalBaseModel
|
4
|
+
import typing
|
5
|
+
from ..core.pydantic_utilities import IS_PYDANTIC_V2
|
6
|
+
import pydantic
|
7
|
+
|
8
|
+
|
9
|
+
class VellumDocumentRequest(UniversalBaseModel):
|
10
|
+
src: str
|
11
|
+
metadata: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = None
|
12
|
+
|
13
|
+
if IS_PYDANTIC_V2:
|
14
|
+
model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
|
15
|
+
else:
|
16
|
+
|
17
|
+
class Config:
|
18
|
+
frozen = True
|
19
|
+
smart_union = True
|
20
|
+
extra = pydantic.Extra.allow
|
@@ -7,6 +7,7 @@ from .number_vellum_value import NumberVellumValue
|
|
7
7
|
from .json_vellum_value import JsonVellumValue
|
8
8
|
from .image_vellum_value import ImageVellumValue
|
9
9
|
from .audio_vellum_value import AudioVellumValue
|
10
|
+
from .document_vellum_value import DocumentVellumValue
|
10
11
|
from .function_call_vellum_value import FunctionCallVellumValue
|
11
12
|
from .error_vellum_value import ErrorVellumValue
|
12
13
|
from .chat_history_vellum_value import ChatHistoryVellumValue
|
@@ -21,6 +22,7 @@ VellumValue = typing.Union[
|
|
21
22
|
JsonVellumValue,
|
22
23
|
ImageVellumValue,
|
23
24
|
AudioVellumValue,
|
25
|
+
DocumentVellumValue,
|
24
26
|
FunctionCallVellumValue,
|
25
27
|
ErrorVellumValue,
|
26
28
|
"ArrayVellumValue",
|
@@ -7,6 +7,7 @@ from .number_vellum_value_request import NumberVellumValueRequest
|
|
7
7
|
from .json_vellum_value_request import JsonVellumValueRequest
|
8
8
|
from .image_vellum_value_request import ImageVellumValueRequest
|
9
9
|
from .audio_vellum_value_request import AudioVellumValueRequest
|
10
|
+
from .document_vellum_value_request import DocumentVellumValueRequest
|
10
11
|
from .function_call_vellum_value_request import FunctionCallVellumValueRequest
|
11
12
|
from .error_vellum_value_request import ErrorVellumValueRequest
|
12
13
|
from .chat_history_vellum_value_request import ChatHistoryVellumValueRequest
|
@@ -21,6 +22,7 @@ VellumValueRequest = typing.Union[
|
|
21
22
|
JsonVellumValueRequest,
|
22
23
|
ImageVellumValueRequest,
|
23
24
|
AudioVellumValueRequest,
|
25
|
+
DocumentVellumValueRequest,
|
24
26
|
FunctionCallVellumValueRequest,
|
25
27
|
ErrorVellumValueRequest,
|
26
28
|
"ArrayVellumValueRequest",
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# This file was auto-generated by Fern from our API Definition.
|
2
|
+
|
3
|
+
from ..core.pydantic_utilities import UniversalBaseModel
|
4
|
+
import typing
|
5
|
+
from ..core.pydantic_utilities import IS_PYDANTIC_V2
|
6
|
+
import pydantic
|
7
|
+
|
8
|
+
|
9
|
+
class WorkflowSandboxExample(UniversalBaseModel):
|
10
|
+
id: str
|
11
|
+
label: str
|
12
|
+
description: typing.Optional[str] = None
|
13
|
+
icon_name: typing.Optional[str] = None
|
14
|
+
|
15
|
+
if IS_PYDANTIC_V2:
|
16
|
+
model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
|
17
|
+
else:
|
18
|
+
|
19
|
+
class Config:
|
20
|
+
frozen = True
|
21
|
+
smart_union = True
|
22
|
+
extra = pydantic.Extra.allow
|
vellum/workflows/exceptions.py
CHANGED
@@ -17,3 +17,21 @@ class NodeException(Exception):
|
|
17
17
|
@staticmethod
|
18
18
|
def of(workflow_error: WorkflowError) -> "NodeException":
|
19
19
|
return NodeException(message=workflow_error.message, code=workflow_error.code)
|
20
|
+
|
21
|
+
|
22
|
+
class WorkflowInitializationException(Exception):
|
23
|
+
def __init__(self, message: str, code: WorkflowErrorCode = WorkflowErrorCode.INVALID_INPUTS):
|
24
|
+
self.message = message
|
25
|
+
self.code = code
|
26
|
+
super().__init__(message)
|
27
|
+
|
28
|
+
@property
|
29
|
+
def error(self) -> WorkflowError:
|
30
|
+
return WorkflowError(
|
31
|
+
message=self.message,
|
32
|
+
code=self.code,
|
33
|
+
)
|
34
|
+
|
35
|
+
@staticmethod
|
36
|
+
def of(workflow_error: WorkflowError) -> "WorkflowInitializationException":
|
37
|
+
return WorkflowInitializationException(message=workflow_error.message, code=workflow_error.code)
|
vellum/workflows/inputs/base.py
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
-
from typing import Any, Iterator, Tuple, Type
|
1
|
+
from typing import Any, Iterator, Tuple, Type, Union, get_args, get_origin
|
2
2
|
from typing_extensions import dataclass_transform
|
3
3
|
|
4
4
|
from pydantic import GetCoreSchemaHandler
|
5
5
|
from pydantic_core import core_schema
|
6
6
|
|
7
|
+
from vellum.workflows.errors.types import WorkflowErrorCode
|
8
|
+
from vellum.workflows.exceptions import WorkflowInitializationException
|
7
9
|
from vellum.workflows.references import ExternalInputReference, WorkflowInputReference
|
8
10
|
from vellum.workflows.references.input import InputReference
|
9
11
|
from vellum.workflows.types.utils import get_class_attr_names, infer_types
|
@@ -40,7 +42,20 @@ class BaseInputs(metaclass=_BaseInputsMeta):
|
|
40
42
|
__parent_class__: Type = type(None)
|
41
43
|
|
42
44
|
def __init__(self, **kwargs: Any) -> None:
|
45
|
+
for name, field_type in self.__class__.__annotations__.items():
|
46
|
+
if name not in kwargs and name not in vars(self.__class__):
|
47
|
+
origin = get_origin(field_type)
|
48
|
+
args = get_args(field_type)
|
49
|
+
if not (origin is Union and type(None) in args):
|
50
|
+
raise WorkflowInitializationException(
|
51
|
+
message="Required input variables should have defined value",
|
52
|
+
code=WorkflowErrorCode.INVALID_INPUTS,
|
53
|
+
)
|
54
|
+
|
43
55
|
for name, value in kwargs.items():
|
56
|
+
field_type = self.__class__.__annotations__.get(name)
|
57
|
+
if field_type:
|
58
|
+
self._validate_input(value, field_type)
|
44
59
|
setattr(self, name, value)
|
45
60
|
|
46
61
|
def __iter__(self) -> Iterator[Tuple[InputReference, Any]]:
|
@@ -48,6 +63,17 @@ class BaseInputs(metaclass=_BaseInputsMeta):
|
|
48
63
|
if hasattr(self, input_descriptor.name):
|
49
64
|
yield (input_descriptor, getattr(self, input_descriptor.name))
|
50
65
|
|
66
|
+
def _validate_input(self, value: Any, field_type: Any) -> None:
|
67
|
+
if value is None:
|
68
|
+
# Check if field_type is Optional
|
69
|
+
origin = get_origin(field_type)
|
70
|
+
args = get_args(field_type)
|
71
|
+
if not (origin is Union and type(None) in args):
|
72
|
+
raise WorkflowInitializationException(
|
73
|
+
message="Required input variables should have defined value",
|
74
|
+
code=WorkflowErrorCode.INVALID_INPUTS,
|
75
|
+
)
|
76
|
+
|
51
77
|
@classmethod
|
52
78
|
def __get_pydantic_core_schema__(
|
53
79
|
cls, source_type: Type[Any], handler: GetCoreSchemaHandler
|
File without changes
|
@@ -0,0 +1,49 @@
|
|
1
|
+
import pytest
|
2
|
+
from typing import Optional
|
3
|
+
|
4
|
+
from vellum.workflows.errors import WorkflowErrorCode
|
5
|
+
from vellum.workflows.exceptions import WorkflowInitializationException
|
6
|
+
from vellum.workflows.inputs import BaseInputs
|
7
|
+
|
8
|
+
|
9
|
+
def test_base_inputs_happy_path():
|
10
|
+
# GIVEN some input class with required and optional fields
|
11
|
+
class TestInputs(BaseInputs):
|
12
|
+
required_string: str
|
13
|
+
required_int: int
|
14
|
+
optional_string: Optional[str]
|
15
|
+
|
16
|
+
# WHEN we assign the inputs some valid values
|
17
|
+
inputs = TestInputs(required_string="hello", required_int=42, optional_string=None)
|
18
|
+
|
19
|
+
# THEN the inputs should have the correct values
|
20
|
+
assert inputs.required_string == "hello"
|
21
|
+
assert inputs.required_int == 42
|
22
|
+
assert inputs.optional_string is None
|
23
|
+
|
24
|
+
|
25
|
+
def test_base_inputs_empty_value():
|
26
|
+
# GIVEN some input class with required and optional string fields
|
27
|
+
class TestInputs(BaseInputs):
|
28
|
+
required_string: str
|
29
|
+
optional_string: Optional[str]
|
30
|
+
|
31
|
+
# WHEN we try to omit a required field
|
32
|
+
with pytest.raises(WorkflowInitializationException) as exc_info:
|
33
|
+
TestInputs(optional_string="ok") # type: ignore
|
34
|
+
|
35
|
+
# THEN it should raise a NodeException with the correct error message and code
|
36
|
+
assert exc_info.value.code == WorkflowErrorCode.INVALID_INPUTS
|
37
|
+
assert "Required input variables should have defined value" in str(exc_info.value)
|
38
|
+
|
39
|
+
|
40
|
+
def test_base_inputs_with_default():
|
41
|
+
# GIVEN some input class with a field that has a default value
|
42
|
+
class TestInputs(BaseInputs):
|
43
|
+
string_with_default: str = "default_value"
|
44
|
+
|
45
|
+
# WHEN we create an instance without providing the field
|
46
|
+
inputs = TestInputs()
|
47
|
+
|
48
|
+
# THEN it should use the default value
|
49
|
+
assert inputs.string_with_default == "default_value"
|
@@ -70,7 +70,7 @@ class InlineSubworkflowNode(
|
|
70
70
|
subworkflow_inputs: ClassVar[Union[EntityInputsInterface, BaseInputs, Type[undefined]]] = undefined
|
71
71
|
|
72
72
|
def run(self) -> Iterator[BaseOutput]:
|
73
|
-
with execution_context(parent_context=get_parent_context()
|
73
|
+
with execution_context(parent_context=get_parent_context()):
|
74
74
|
subworkflow = self.subworkflow(
|
75
75
|
parent_state=self.state,
|
76
76
|
context=WorkflowContext(vellum_client=self._context.vellum_client),
|
@@ -16,10 +16,9 @@ from typing import (
|
|
16
16
|
overload,
|
17
17
|
)
|
18
18
|
|
19
|
-
from vellum.workflows.context import execution_context,
|
19
|
+
from vellum.workflows.context import ExecutionContext, execution_context, get_execution_context
|
20
20
|
from vellum.workflows.descriptors.base import BaseDescriptor
|
21
21
|
from vellum.workflows.errors.types import WorkflowErrorCode
|
22
|
-
from vellum.workflows.events.types import ParentContext
|
23
22
|
from vellum.workflows.exceptions import NodeException
|
24
23
|
from vellum.workflows.inputs.base import BaseInputs
|
25
24
|
from vellum.workflows.nodes.bases.base_adornment_node import BaseAdornmentNode
|
@@ -76,13 +75,13 @@ class MapNode(BaseAdornmentNode[StateType], Generic[StateType, MapNodeItemType])
|
|
76
75
|
fulfilled_iterations: List[bool] = []
|
77
76
|
for index, item in enumerate(self.items):
|
78
77
|
fulfilled_iterations.append(False)
|
79
|
-
|
78
|
+
current_execution_context = get_execution_context()
|
80
79
|
thread = Thread(
|
81
80
|
target=self._context_run_subworkflow,
|
82
81
|
kwargs={
|
83
82
|
"item": item,
|
84
83
|
"index": index,
|
85
|
-
"
|
84
|
+
"current_execution_context": current_execution_context,
|
86
85
|
},
|
87
86
|
)
|
88
87
|
if self.max_concurrency is None:
|
@@ -143,10 +142,11 @@ class MapNode(BaseAdornmentNode[StateType], Generic[StateType, MapNodeItemType])
|
|
143
142
|
yield BaseOutput(name=output_name, value=output_list)
|
144
143
|
|
145
144
|
def _context_run_subworkflow(
|
146
|
-
self, *, item: MapNodeItemType, index: int,
|
145
|
+
self, *, item: MapNodeItemType, index: int, current_execution_context: ExecutionContext
|
147
146
|
) -> None:
|
148
|
-
parent_context =
|
149
|
-
|
147
|
+
parent_context = current_execution_context.parent_context
|
148
|
+
trace_id = current_execution_context.trace_id
|
149
|
+
with execution_context(parent_context=parent_context, trace_id=trace_id):
|
150
150
|
self._run_subworkflow(item=item, index=index)
|
151
151
|
|
152
152
|
def _run_subworkflow(self, *, item: MapNodeItemType, index: int) -> None:
|
@@ -27,7 +27,7 @@ class TryNode(BaseAdornmentNode[StateType], Generic[StateType]):
|
|
27
27
|
error: Optional[WorkflowError] = None
|
28
28
|
|
29
29
|
def run(self) -> Iterator[BaseOutput]:
|
30
|
-
parent_context = get_parent_context()
|
30
|
+
parent_context = get_parent_context()
|
31
31
|
with execution_context(parent_context=parent_context):
|
32
32
|
subworkflow = self.subworkflow(
|
33
33
|
parent_state=self.state,
|
@@ -67,11 +67,11 @@ class BasePromptNode(BaseNode, Generic[StateType]):
|
|
67
67
|
def _handle_api_error(self, e: ApiError):
|
68
68
|
if e.status_code and e.status_code >= 400 and e.status_code < 500 and isinstance(e.body, dict):
|
69
69
|
raise NodeException(
|
70
|
-
message=e.body.get("detail", "Failed to execute
|
70
|
+
message=e.body.get("detail", "Failed to execute Prompt"),
|
71
71
|
code=WorkflowErrorCode.INVALID_INPUTS,
|
72
72
|
) from e
|
73
73
|
|
74
74
|
raise NodeException(
|
75
|
-
message="Failed to execute
|
75
|
+
message="Failed to execute Prompt",
|
76
76
|
code=WorkflowErrorCode.INTERNAL_ERROR,
|
77
77
|
) from e
|
@@ -19,7 +19,7 @@ from vellum.client import RequestOptions
|
|
19
19
|
from vellum.client.types.chat_message_request import ChatMessageRequest
|
20
20
|
from vellum.client.types.prompt_settings import PromptSettings
|
21
21
|
from vellum.workflows.constants import OMIT
|
22
|
-
from vellum.workflows.context import
|
22
|
+
from vellum.workflows.context import get_execution_context
|
23
23
|
from vellum.workflows.errors import WorkflowErrorCode
|
24
24
|
from vellum.workflows.events.types import default_serializer
|
25
25
|
from vellum.workflows.exceptions import NodeException
|
@@ -61,11 +61,13 @@ class BaseInlinePromptNode(BasePromptNode[StateType], Generic[StateType]):
|
|
61
61
|
|
62
62
|
def _get_prompt_event_stream(self) -> Iterator[AdHocExecutePromptEvent]:
|
63
63
|
input_variables, input_values = self._compile_prompt_inputs()
|
64
|
-
|
64
|
+
current_context = get_execution_context()
|
65
|
+
parent_context = current_context.parent_context
|
66
|
+
trace_id = current_context.trace_id
|
65
67
|
request_options = self.request_options or RequestOptions()
|
66
68
|
|
67
69
|
request_options["additional_body_parameters"] = {
|
68
|
-
"execution_context": {"parent_context": parent_context},
|
70
|
+
"execution_context": {"parent_context": parent_context, "trace_id": trace_id},
|
69
71
|
**request_options.get("additional_body_parameters", {}),
|
70
72
|
}
|
71
73
|
normalized_functions = (
|
@@ -15,7 +15,7 @@ from vellum import (
|
|
15
15
|
from vellum.client import RequestOptions
|
16
16
|
from vellum.client.types.chat_message_request import ChatMessageRequest
|
17
17
|
from vellum.workflows.constants import LATEST_RELEASE_TAG, OMIT
|
18
|
-
from vellum.workflows.context import
|
18
|
+
from vellum.workflows.context import get_execution_context
|
19
19
|
from vellum.workflows.errors import WorkflowErrorCode
|
20
20
|
from vellum.workflows.events.types import default_serializer
|
21
21
|
from vellum.workflows.exceptions import NodeException
|
@@ -55,11 +55,12 @@ class BasePromptDeploymentNode(BasePromptNode, Generic[StateType]):
|
|
55
55
|
merge_behavior = MergeBehavior.AWAIT_ANY
|
56
56
|
|
57
57
|
def _get_prompt_event_stream(self) -> Iterator[ExecutePromptEvent]:
|
58
|
-
|
59
|
-
|
58
|
+
current_context = get_execution_context()
|
59
|
+
trace_id = current_context.trace_id
|
60
|
+
parent_context = current_context.parent_context.model_dump() if current_context.parent_context else None
|
60
61
|
request_options = self.request_options or RequestOptions()
|
61
62
|
request_options["additional_body_parameters"] = {
|
62
|
-
"execution_context": {"parent_context": parent_context},
|
63
|
+
"execution_context": {"parent_context": parent_context, "trace_id": trace_id},
|
63
64
|
**request_options.get("additional_body_parameters", {}),
|
64
65
|
}
|
65
66
|
return self._context.vellum_client.execute_prompt_stream(
|
@@ -146,22 +146,22 @@ def test_inline_prompt_node__function_definitions(vellum_adhoc_prompt_client):
|
|
146
146
|
(
|
147
147
|
ApiError(status_code=404, body={"message": "Model not found"}),
|
148
148
|
WorkflowErrorCode.INVALID_INPUTS,
|
149
|
-
"Failed to execute
|
149
|
+
"Failed to execute Prompt",
|
150
150
|
),
|
151
151
|
(
|
152
152
|
ApiError(status_code=404, body="Model not found"),
|
153
153
|
WorkflowErrorCode.INTERNAL_ERROR,
|
154
|
-
"Failed to execute
|
154
|
+
"Failed to execute Prompt",
|
155
155
|
),
|
156
156
|
(
|
157
157
|
ApiError(status_code=None, body={"detail": "Model not found"}),
|
158
158
|
WorkflowErrorCode.INTERNAL_ERROR,
|
159
|
-
"Failed to execute
|
159
|
+
"Failed to execute Prompt",
|
160
160
|
),
|
161
161
|
(
|
162
162
|
ApiError(status_code=500, body={"detail": "Model not found"}),
|
163
163
|
WorkflowErrorCode.INTERNAL_ERROR,
|
164
|
-
"Failed to execute
|
164
|
+
"Failed to execute Prompt",
|
165
165
|
),
|
166
166
|
],
|
167
167
|
ids=["404", "invalid_dict", "invalid_body", "no_status_code", "500"],
|
@@ -12,10 +12,11 @@ from vellum import (
|
|
12
12
|
WorkflowRequestNumberInputRequest,
|
13
13
|
WorkflowRequestStringInputRequest,
|
14
14
|
)
|
15
|
+
from vellum.client.core.api_error import ApiError
|
15
16
|
from vellum.client.types.chat_message_request import ChatMessageRequest
|
16
17
|
from vellum.core import RequestOptions
|
17
18
|
from vellum.workflows.constants import LATEST_RELEASE_TAG, OMIT
|
18
|
-
from vellum.workflows.context import
|
19
|
+
from vellum.workflows.context import get_execution_context
|
19
20
|
from vellum.workflows.errors import WorkflowErrorCode
|
20
21
|
from vellum.workflows.errors.types import workflow_event_error_to_workflow_error
|
21
22
|
from vellum.workflows.events.types import default_serializer
|
@@ -120,24 +121,45 @@ class SubworkflowDeploymentNode(BaseNode[StateType], Generic[StateType]):
|
|
120
121
|
return compiled_inputs
|
121
122
|
|
122
123
|
def run(self) -> Iterator[BaseOutput]:
|
123
|
-
|
124
|
-
parent_context =
|
124
|
+
current_context = get_execution_context()
|
125
|
+
parent_context = (
|
126
|
+
current_context.parent_context.model_dump(mode="json") if current_context.parent_context else None
|
127
|
+
)
|
125
128
|
request_options = self.request_options or RequestOptions()
|
126
129
|
request_options["additional_body_parameters"] = {
|
127
|
-
"execution_context": {"parent_context": parent_context},
|
130
|
+
"execution_context": {"parent_context": parent_context, "trace_id": current_context.trace_id},
|
128
131
|
**request_options.get("additional_body_parameters", {}),
|
129
132
|
}
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
133
|
+
|
134
|
+
try:
|
135
|
+
deployment_id = str(self.deployment) if isinstance(self.deployment, UUID) else None
|
136
|
+
deployment_name = self.deployment if isinstance(self.deployment, str) else None
|
137
|
+
except AttributeError:
|
138
|
+
raise NodeException(
|
139
|
+
code=WorkflowErrorCode.NODE_EXECUTION,
|
140
|
+
message="Expected subworkflow deployment attribute to be either a UUID or STR, got None instead",
|
141
|
+
)
|
142
|
+
|
143
|
+
try:
|
144
|
+
subworkflow_stream = self._context.vellum_client.execute_workflow_stream(
|
145
|
+
inputs=self._compile_subworkflow_inputs(),
|
146
|
+
workflow_deployment_id=deployment_id,
|
147
|
+
workflow_deployment_name=deployment_name,
|
148
|
+
release_tag=self.release_tag,
|
149
|
+
external_id=self.external_id,
|
150
|
+
event_types=["WORKFLOW"],
|
151
|
+
metadata=self.metadata,
|
152
|
+
request_options=request_options,
|
153
|
+
)
|
154
|
+
except ApiError as e:
|
155
|
+
self._handle_api_error(e)
|
156
|
+
|
157
|
+
# We don't use the INITIATED event anyway, so we can just skip it
|
158
|
+
# and use the exception handling to catch other api level errors
|
159
|
+
try:
|
160
|
+
next(subworkflow_stream)
|
161
|
+
except ApiError as e:
|
162
|
+
self._handle_api_error(e)
|
141
163
|
|
142
164
|
outputs: Optional[List[WorkflowOutput]] = None
|
143
165
|
fulfilled_output_names: Set[str] = set()
|
@@ -185,3 +207,15 @@ class SubworkflowDeploymentNode(BaseNode[StateType], Generic[StateType]):
|
|
185
207
|
name=output.name,
|
186
208
|
value=output.value,
|
187
209
|
)
|
210
|
+
|
211
|
+
def _handle_api_error(self, e: ApiError):
|
212
|
+
if e.status_code and e.status_code >= 400 and e.status_code < 500 and isinstance(e.body, dict):
|
213
|
+
raise NodeException(
|
214
|
+
message=e.body.get("detail", "Failed to execute Subworkflow Deployment"),
|
215
|
+
code=WorkflowErrorCode.INVALID_INPUTS,
|
216
|
+
) from e
|
217
|
+
|
218
|
+
raise NodeException(
|
219
|
+
message="Failed to execute Subworkflow Deployment",
|
220
|
+
code=WorkflowErrorCode.INTERNAL_ERROR,
|
221
|
+
) from e
|