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.
Files changed (68) hide show
  1. vellum/__init__.py +18 -0
  2. vellum/client/__init__.py +8 -8
  3. vellum/client/core/client_wrapper.py +1 -1
  4. vellum/client/resources/__init__.py +2 -0
  5. vellum/client/resources/workflow_sandboxes/__init__.py +3 -0
  6. vellum/client/resources/workflow_sandboxes/client.py +146 -0
  7. vellum/client/resources/workflow_sandboxes/types/__init__.py +5 -0
  8. vellum/client/resources/workflow_sandboxes/types/list_workflow_sandbox_examples_request_tag.py +5 -0
  9. vellum/client/types/__init__.py +16 -0
  10. vellum/client/types/array_chat_message_content_item.py +6 -1
  11. vellum/client/types/array_chat_message_content_item_request.py +2 -0
  12. vellum/client/types/chat_message_content.py +2 -0
  13. vellum/client/types/chat_message_content_request.py +2 -0
  14. vellum/client/types/document_chat_message_content.py +25 -0
  15. vellum/client/types/document_chat_message_content_request.py +25 -0
  16. vellum/client/types/document_vellum_value.py +25 -0
  17. vellum/client/types/document_vellum_value_request.py +25 -0
  18. vellum/client/types/paginated_workflow_sandbox_example_list.py +23 -0
  19. vellum/client/types/vellum_document.py +20 -0
  20. vellum/client/types/vellum_document_request.py +20 -0
  21. vellum/client/types/vellum_value.py +2 -0
  22. vellum/client/types/vellum_value_request.py +2 -0
  23. vellum/client/types/vellum_variable_type.py +1 -0
  24. vellum/client/types/workflow_sandbox_example.py +22 -0
  25. vellum/resources/workflow_sandboxes/types/__init__.py +3 -0
  26. vellum/resources/workflow_sandboxes/types/list_workflow_sandbox_examples_request_tag.py +3 -0
  27. vellum/types/document_chat_message_content.py +3 -0
  28. vellum/types/document_chat_message_content_request.py +3 -0
  29. vellum/types/document_vellum_value.py +3 -0
  30. vellum/types/document_vellum_value_request.py +3 -0
  31. vellum/types/paginated_workflow_sandbox_example_list.py +3 -0
  32. vellum/types/vellum_document.py +3 -0
  33. vellum/types/vellum_document_request.py +3 -0
  34. vellum/types/workflow_sandbox_example.py +3 -0
  35. vellum/workflows/exceptions.py +18 -0
  36. vellum/workflows/inputs/base.py +27 -1
  37. vellum/workflows/inputs/tests/__init__.py +0 -0
  38. vellum/workflows/inputs/tests/test_inputs.py +49 -0
  39. vellum/workflows/nodes/core/inline_subworkflow_node/node.py +1 -1
  40. vellum/workflows/nodes/core/map_node/node.py +7 -7
  41. vellum/workflows/nodes/core/try_node/node.py +1 -1
  42. vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py +2 -2
  43. vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +5 -3
  44. vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py +5 -4
  45. vellum/workflows/nodes/displayable/inline_prompt_node/tests/test_node.py +4 -4
  46. vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +49 -15
  47. vellum/workflows/nodes/displayable/subworkflow_deployment_node/tests/test_node.py +165 -0
  48. vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py +3 -1
  49. vellum/workflows/outputs/base.py +1 -1
  50. vellum/workflows/runner/runner.py +16 -10
  51. vellum/workflows/state/context.py +7 -7
  52. vellum/workflows/workflows/base.py +61 -59
  53. vellum/workflows/workflows/tests/test_base_workflow.py +131 -40
  54. {vellum_ai-0.14.5.dist-info → vellum_ai-0.14.7.dist-info}/METADATA +1 -1
  55. {vellum_ai-0.14.5.dist-info → vellum_ai-0.14.7.dist-info}/RECORD +68 -44
  56. vellum_cli/__init__.py +36 -0
  57. vellum_cli/init.py +128 -0
  58. vellum_cli/pull.py +6 -3
  59. vellum_cli/tests/test_init.py +355 -0
  60. vellum_cli/tests/test_pull.py +127 -0
  61. vellum_ee/workflows/display/nodes/base_node_display.py +4 -4
  62. vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py +31 -0
  63. vellum_ee/workflows/display/nodes/vellum/utils.py +8 -0
  64. vellum_ee/workflows/display/vellum.py +0 -4
  65. vellum_ee/workflows/display/workflows/tests/test_workflow_display.py +29 -0
  66. {vellum_ai-0.14.5.dist-info → vellum_ai-0.14.7.dist-info}/LICENSE +0 -0
  67. {vellum_ai-0.14.5.dist-info → vellum_ai-0.14.7.dist-info}/WHEEL +0 -0
  68. {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",
@@ -14,6 +14,7 @@ VellumVariableType = typing.Union[
14
14
  "FUNCTION_CALL",
15
15
  "IMAGE",
16
16
  "AUDIO",
17
+ "DOCUMENT",
17
18
  "NULL",
18
19
  ],
19
20
  typing.Any,
@@ -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
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.resources.workflow_sandboxes.types import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.resources.workflow_sandboxes.types.list_workflow_sandbox_examples_request_tag import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.document_chat_message_content import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.document_chat_message_content_request import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.document_vellum_value import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.document_vellum_value_request import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.paginated_workflow_sandbox_example_list import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.vellum_document import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.vellum_document_request import *
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.workflow_sandbox_example import *
@@ -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)
@@ -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() or self._context.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, get_parent_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
- parent_context = get_parent_context() or self._context.parent_context
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
- "parent_context": parent_context,
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, parent_context: Optional[ParentContext] = None
145
+ self, *, item: MapNodeItemType, index: int, current_execution_context: ExecutionContext
147
146
  ) -> None:
148
- parent_context = parent_context or self._context.parent_context
149
- with execution_context(parent_context=parent_context):
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() or self._context.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 prompt"),
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 prompt",
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 get_parent_context
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
- parent_context = get_parent_context()
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 get_parent_context
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
- current_parent_context = get_parent_context()
59
- parent_context = current_parent_context.model_dump() if current_parent_context else None
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 prompt",
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 prompt",
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 prompt",
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 prompt",
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 get_parent_context
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
- current_parent_context = get_parent_context()
124
- parent_context = current_parent_context.model_dump(mode="json") if current_parent_context else None
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
- subworkflow_stream = self._context.vellum_client.execute_workflow_stream(
131
- inputs=self._compile_subworkflow_inputs(),
132
- workflow_deployment_id=str(self.deployment) if isinstance(self.deployment, UUID) else None,
133
- workflow_deployment_name=self.deployment if isinstance(self.deployment, str) else None,
134
- release_tag=self.release_tag,
135
- external_id=self.external_id,
136
- event_types=["WORKFLOW"],
137
- metadata=self.metadata,
138
- request_options=request_options,
139
- )
140
- # for some reason execution context isn't showing as an option? ^ failing mypy
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