vellum-ai 1.2.0__py3-none-any.whl → 1.2.2__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 (94) hide show
  1. vellum/__init__.py +18 -1
  2. vellum/client/__init__.py +3 -0
  3. vellum/client/core/client_wrapper.py +2 -2
  4. vellum/client/errors/__init__.py +10 -1
  5. vellum/client/errors/too_many_requests_error.py +11 -0
  6. vellum/client/errors/unauthorized_error.py +11 -0
  7. vellum/client/reference.md +94 -0
  8. vellum/client/resources/__init__.py +2 -0
  9. vellum/client/resources/events/__init__.py +4 -0
  10. vellum/client/resources/events/client.py +165 -0
  11. vellum/client/resources/events/raw_client.py +207 -0
  12. vellum/client/types/__init__.py +6 -0
  13. vellum/client/types/error_detail_response.py +22 -0
  14. vellum/client/types/event_create_response.py +26 -0
  15. vellum/client/types/execution_thinking_vellum_value.py +1 -1
  16. vellum/client/types/thinking_vellum_value.py +1 -1
  17. vellum/client/types/thinking_vellum_value_request.py +1 -1
  18. vellum/client/types/workflow_event.py +33 -0
  19. vellum/errors/too_many_requests_error.py +3 -0
  20. vellum/errors/unauthorized_error.py +3 -0
  21. vellum/prompts/blocks/compilation.py +13 -11
  22. vellum/resources/events/__init__.py +3 -0
  23. vellum/resources/events/client.py +3 -0
  24. vellum/resources/events/raw_client.py +3 -0
  25. vellum/types/error_detail_response.py +3 -0
  26. vellum/types/event_create_response.py +3 -0
  27. vellum/types/workflow_event.py +3 -0
  28. vellum/workflows/emitters/vellum_emitter.py +16 -69
  29. vellum/workflows/events/tests/test_event.py +1 -0
  30. vellum/workflows/events/workflow.py +3 -0
  31. vellum/workflows/nodes/bases/base.py +0 -1
  32. vellum/workflows/nodes/core/inline_subworkflow_node/tests/test_node.py +35 -0
  33. vellum/workflows/nodes/displayable/bases/api_node/node.py +4 -0
  34. vellum/workflows/nodes/displayable/bases/api_node/tests/test_node.py +26 -0
  35. vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +6 -1
  36. vellum/workflows/nodes/displayable/bases/inline_prompt_node/tests/test_inline_prompt_node.py +22 -0
  37. vellum/workflows/nodes/displayable/bases/utils.py +4 -2
  38. vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +88 -2
  39. vellum/workflows/nodes/displayable/tool_calling_node/node.py +1 -0
  40. vellum/workflows/nodes/displayable/tool_calling_node/tests/test_node.py +85 -1
  41. vellum/workflows/nodes/displayable/tool_calling_node/tests/test_utils.py +12 -0
  42. vellum/workflows/nodes/displayable/tool_calling_node/utils.py +5 -2
  43. vellum/workflows/ports/port.py +1 -11
  44. vellum/workflows/sandbox.py +6 -3
  45. vellum/workflows/state/context.py +14 -0
  46. vellum/workflows/state/encoder.py +19 -1
  47. vellum/workflows/types/definition.py +4 -4
  48. vellum/workflows/utils/hmac.py +44 -0
  49. vellum/workflows/utils/vellum_variables.py +5 -3
  50. vellum/workflows/workflows/base.py +1 -0
  51. {vellum_ai-1.2.0.dist-info → vellum_ai-1.2.2.dist-info}/METADATA +1 -1
  52. {vellum_ai-1.2.0.dist-info → vellum_ai-1.2.2.dist-info}/RECORD +94 -76
  53. vellum_ee/workflows/display/nodes/base_node_display.py +19 -10
  54. vellum_ee/workflows/display/nodes/vellum/api_node.py +1 -4
  55. vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +1 -4
  56. vellum_ee/workflows/display/nodes/vellum/conditional_node.py +1 -4
  57. vellum_ee/workflows/display/nodes/vellum/error_node.py +6 -4
  58. vellum_ee/workflows/display/nodes/vellum/final_output_node.py +6 -4
  59. vellum_ee/workflows/display/nodes/vellum/guardrail_node.py +1 -4
  60. vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +34 -15
  61. vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +1 -4
  62. vellum_ee/workflows/display/nodes/vellum/map_node.py +1 -4
  63. vellum_ee/workflows/display/nodes/vellum/merge_node.py +1 -4
  64. vellum_ee/workflows/display/nodes/vellum/note_node.py +2 -4
  65. vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py +1 -4
  66. vellum_ee/workflows/display/nodes/vellum/search_node.py +1 -4
  67. vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py +1 -4
  68. vellum_ee/workflows/display/nodes/vellum/templating_node.py +1 -4
  69. vellum_ee/workflows/display/nodes/vellum/tests/test_code_execution_node.py +1 -0
  70. vellum_ee/workflows/display/nodes/vellum/tests/test_tool_calling_node.py +239 -1
  71. vellum_ee/workflows/display/tests/test_base_workflow_display.py +53 -1
  72. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_api_node_serialization.py +4 -0
  73. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +12 -0
  74. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +16 -0
  75. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py +5 -0
  76. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_guardrail_node_serialization.py +4 -0
  77. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +4 -0
  78. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +4 -0
  79. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +4 -0
  80. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_prompt_deployment_serialization.py +12 -0
  81. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_search_node_serialization.py +4 -0
  82. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_subworkflow_deployment_serialization.py +4 -0
  83. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_node_serialization.py +4 -0
  84. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py +5 -0
  85. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_composio_serialization.py +1 -0
  86. vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +5 -0
  87. vellum_ee/workflows/display/utils/expressions.py +4 -0
  88. vellum_ee/workflows/display/utils/registry.py +46 -0
  89. vellum_ee/workflows/display/workflows/base_workflow_display.py +1 -1
  90. vellum_ee/workflows/tests/test_registry.py +169 -0
  91. vellum_ee/workflows/tests/test_server.py +72 -0
  92. {vellum_ai-1.2.0.dist-info → vellum_ai-1.2.2.dist-info}/LICENSE +0 -0
  93. {vellum_ai-1.2.0.dist-info → vellum_ai-1.2.2.dist-info}/WHEEL +0 -0
  94. {vellum_ai-1.2.0.dist-info → vellum_ai-1.2.2.dist-info}/entry_points.txt +0 -0
@@ -2,7 +2,7 @@ from uuid import UUID
2
2
  from typing import ClassVar, Generic, Optional, TypeVar
3
3
 
4
4
  from vellum.workflows.nodes.displayable.final_output_node import FinalOutputNode
5
- from vellum.workflows.types.core import JsonObject
5
+ from vellum.workflows.types.core import JsonArray, JsonObject
6
6
  from vellum.workflows.utils.uuids import uuid4_from_hash
7
7
  from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
8
8
  from vellum_ee.workflows.display.nodes.utils import to_kebab_case
@@ -19,6 +19,10 @@ NODE_INPUT_KEY = "node_input"
19
19
  class BaseFinalOutputNodeDisplay(BaseNodeDisplay[_FinalOutputNodeType], Generic[_FinalOutputNodeType]):
20
20
  output_name: ClassVar[Optional[str]] = None
21
21
 
22
+ def serialize_ports(self, display_context: "WorkflowDisplayContext") -> JsonArray:
23
+ """Final output nodes have no ports."""
24
+ return []
25
+
22
26
  def serialize(self, display_context: WorkflowDisplayContext, **_kwargs) -> JsonObject:
23
27
  node = self._node
24
28
  node_id = self.node_id
@@ -45,9 +49,7 @@ class BaseFinalOutputNodeDisplay(BaseNodeDisplay[_FinalOutputNodeType], Generic[
45
49
  "node_input_id": str(node_input.id),
46
50
  },
47
51
  "inputs": [node_input.dict()],
48
- "display_data": self.get_display_data().dict(),
49
- "base": self.get_base().dict(),
50
- "definition": self.get_definition().dict(),
52
+ **self.serialize_generic_fields(display_context),
51
53
  "outputs": [
52
54
  {
53
55
  "id": str(self._get_output_id()),
@@ -45,8 +45,5 @@ class BaseGuardrailNodeDisplay(BaseNodeDisplay[_GuardrailNodeType], Generic[_Gua
45
45
  "metric_definition_id": str(raise_if_descriptor(node.metric_definition)),
46
46
  "release_tag": raise_if_descriptor(node.release_tag),
47
47
  },
48
- "display_data": self.get_display_data().dict(),
49
- "base": self.get_base().dict(),
50
- "definition": self.get_definition().dict(),
51
- "ports": self.serialize_ports(display_context),
48
+ **self.serialize_generic_fields(display_context),
52
49
  }
@@ -1,11 +1,17 @@
1
1
  from uuid import UUID
2
- from typing import Callable, Dict, Generic, List, Optional, Tuple, Type, TypeVar, Union
2
+ from typing import TYPE_CHECKING, Callable, Dict, Generic, List, Optional, Tuple, Type, TypeVar, Union
3
3
 
4
4
  from vellum import FunctionDefinition, PromptBlock, RichTextChildBlock, VellumVariable
5
5
  from vellum.workflows.descriptors.base import BaseDescriptor
6
6
  from vellum.workflows.nodes import InlinePromptNode
7
7
  from vellum.workflows.types.core import JsonObject
8
- from vellum.workflows.utils.functions import compile_function_definition
8
+ from vellum.workflows.types.definition import DeploymentDefinition
9
+ from vellum.workflows.types.generics import is_workflow_class
10
+ from vellum.workflows.utils.functions import (
11
+ compile_function_definition,
12
+ compile_inline_workflow_function_definition,
13
+ compile_workflow_deployment_function_definition,
14
+ )
9
15
  from vellum.workflows.utils.uuids import uuid4_from_hash
10
16
  from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
11
17
  from vellum_ee.workflows.display.nodes.utils import raise_if_descriptor
@@ -14,6 +20,9 @@ from vellum_ee.workflows.display.types import WorkflowDisplayContext
14
20
  from vellum_ee.workflows.display.utils.vellum import infer_vellum_variable_type
15
21
  from vellum_ee.workflows.display.vellum import NodeInput
16
22
 
23
+ if TYPE_CHECKING:
24
+ from vellum.workflows.workflows.base import BaseWorkflow
25
+
17
26
 
18
27
  def _contains_descriptors(obj):
19
28
  """Check if an object contains any descriptors or references that need special handling."""
@@ -69,7 +78,10 @@ class BaseInlinePromptNodeDisplay(BaseNodeDisplay[_InlinePromptNodeType], Generi
69
78
  ]
70
79
 
71
80
  functions = (
72
- [self._generate_function_tools(function, i) for i, function in enumerate(function_definitions)]
81
+ [
82
+ self._generate_function_tools(function, i, display_context)
83
+ for i, function in enumerate(function_definitions)
84
+ ]
73
85
  if isinstance(function_definitions, list)
74
86
  else []
75
87
  )
@@ -97,19 +109,12 @@ class BaseInlinePromptNodeDisplay(BaseNodeDisplay[_InlinePromptNodeType], Generi
97
109
  },
98
110
  "ml_model_name": ml_model,
99
111
  },
100
- "display_data": self.get_display_data().dict(),
101
- "base": self.get_base().dict(),
102
- "definition": self.get_definition().dict(),
103
- "trigger": {
104
- "id": str(self.get_target_handle_id()),
105
- "merge_behavior": node.Trigger.merge_behavior.value,
106
- },
112
+ **self.serialize_generic_fields(display_context),
107
113
  "outputs": [
108
114
  {"id": str(json_display.id), "name": "json", "type": "JSON", "value": None},
109
115
  {"id": str(output_display.id), "name": "text", "type": "STRING", "value": None},
110
116
  {"id": str(array_display.id), "name": "results", "type": "ARRAY", "value": None},
111
117
  ],
112
- "ports": self.serialize_ports(display_context),
113
118
  }
114
119
  attributes = self._serialize_attributes(display_context)
115
120
  if attributes:
@@ -145,10 +150,24 @@ class BaseInlinePromptNodeDisplay(BaseNodeDisplay[_InlinePromptNodeType], Generi
145
150
 
146
151
  return node_inputs, prompt_inputs
147
152
 
148
- def _generate_function_tools(self, function: Union[FunctionDefinition, Callable], index: int) -> JsonObject:
149
- normalized_functions = (
150
- function if isinstance(function, FunctionDefinition) else compile_function_definition(function)
151
- )
153
+ def _generate_function_tools(
154
+ self,
155
+ function: Union[FunctionDefinition, Callable, DeploymentDefinition, Type["BaseWorkflow"]],
156
+ index: int,
157
+ display_context: WorkflowDisplayContext,
158
+ ) -> JsonObject:
159
+ if isinstance(function, FunctionDefinition):
160
+ normalized_functions = function
161
+ elif is_workflow_class(function):
162
+ normalized_functions = compile_inline_workflow_function_definition(function)
163
+ elif callable(function):
164
+ normalized_functions = compile_function_definition(function)
165
+ elif isinstance(function, DeploymentDefinition):
166
+ normalized_functions = compile_workflow_deployment_function_definition(
167
+ function.model_dump(), display_context.client
168
+ )
169
+ else:
170
+ raise ValueError(f"Unsupported function type: {type(function)}")
152
171
  return {
153
172
  "id": str(uuid4_from_hash(f"{self.node_id}-FUNCTION_DEFINITION-{index}")),
154
173
  "block_type": "FUNCTION_DEFINITION",
@@ -55,10 +55,7 @@ class BaseInlineSubworkflowNodeDisplay(
55
55
  "input_variables": [workflow_input.dict() for workflow_input in workflow_inputs],
56
56
  "output_variables": [workflow_output.dict() for workflow_output in workflow_outputs],
57
57
  },
58
- "display_data": self.get_display_data().dict(),
59
- "base": self.get_base().dict(),
60
- "definition": self.get_definition().dict(),
61
- "ports": self.serialize_ports(display_context),
58
+ **self.serialize_generic_fields(display_context),
62
59
  }
63
60
 
64
61
  def _generate_node_and_workflow_inputs(
@@ -79,8 +79,5 @@ class BaseMapNodeDisplay(BaseAdornmentNodeDisplay[_MapNodeType], Generic[_MapNod
79
79
  "item_input_id": item_workflow_input_id,
80
80
  "index_input_id": index_workflow_input_id,
81
81
  },
82
- "display_data": self.get_display_data().dict(),
83
- "base": self.get_base().dict(),
84
- "definition": self.get_definition().dict(),
85
- "ports": self.serialize_ports(display_context),
82
+ **self.serialize_generic_fields(display_context),
86
83
  }
@@ -47,10 +47,7 @@ class BaseMergeNodeDisplay(BaseNodeDisplay[_MergeNodeType], Generic[_MergeNodeTy
47
47
  "target_handles": [{"id": str(target_handle_id)} for target_handle_id in target_handle_ids],
48
48
  "source_handle_id": str(self.get_source_handle_id(display_context.port_displays)),
49
49
  },
50
- "display_data": self.get_display_data().dict(),
51
- "base": self.get_base().dict(),
52
- "definition": self.get_definition().dict(),
53
- "ports": self.serialize_ports(display_context),
50
+ **self.serialize_generic_fields(display_context),
54
51
  }
55
52
 
56
53
  def get_target_handle_ids(self) -> Optional[List[UUID]]:
@@ -13,7 +13,7 @@ class BaseNoteNodeDisplay(BaseNodeDisplay[_NoteNodeType], Generic[_NoteNodeType]
13
13
  style: ClassVar[Union[Dict[str, Any], None]] = None
14
14
 
15
15
  def serialize(self, display_context: WorkflowDisplayContext, **kwargs: Any) -> JsonObject:
16
- del display_context, kwargs # Unused parameters
16
+ del kwargs # Unused parameters
17
17
  node_id = self.node_id
18
18
 
19
19
  return {
@@ -25,7 +25,5 @@ class BaseNoteNodeDisplay(BaseNodeDisplay[_NoteNodeType], Generic[_NoteNodeType]
25
25
  "text": self.text,
26
26
  "style": self.style,
27
27
  },
28
- "display_data": self.get_display_data().dict(),
29
- "base": self.get_base().dict(),
30
- "definition": self.get_definition().dict(),
28
+ **self.serialize_generic_fields(display_context),
31
29
  }
@@ -64,10 +64,7 @@ class BasePromptDeploymentNodeDisplay(BaseNodeDisplay[_PromptDeploymentNodeType]
64
64
  "release_tag": raise_if_descriptor(node.release_tag),
65
65
  "ml_model_fallbacks": list(ml_model_fallbacks) if ml_model_fallbacks else None,
66
66
  },
67
- "display_data": self.get_display_data().dict(),
68
- "base": self.get_base().dict(),
69
- "definition": self.get_definition().dict(),
70
- "ports": self.serialize_ports(display_context),
67
+ **self.serialize_generic_fields(display_context),
71
68
  "outputs": [
72
69
  {"id": str(json_display.id), "name": "json", "type": "JSON", "value": None},
73
70
  {"id": str(output_display.id), "name": "text", "type": "STRING", "value": None},
@@ -74,10 +74,7 @@ class BaseSearchNodeDisplay(BaseNodeDisplay[_SearchNodeType], Generic[_SearchNod
74
74
  "external_id_filters_node_input_id": str(node_inputs["external_id_filters"].id),
75
75
  "metadata_filters_node_input_id": str(node_inputs["metadata_filters"].id),
76
76
  },
77
- "display_data": self.get_display_data().dict(),
78
- "base": self.get_base().dict(),
79
- "definition": self.get_definition().dict(),
80
- "ports": self.serialize_ports(display_context),
77
+ **self.serialize_generic_fields(display_context),
81
78
  }
82
79
 
83
80
  def _generate_search_node_inputs(
@@ -61,8 +61,5 @@ class BaseSubworkflowDeploymentNodeDisplay(
61
61
  "workflow_deployment_id": str(deployment.id),
62
62
  "release_tag": raise_if_descriptor(node.release_tag),
63
63
  },
64
- "display_data": self.get_display_data().dict(),
65
- "base": self.get_base().dict(),
66
- "definition": self.get_definition().dict(),
67
- "ports": self.serialize_ports(display_context),
64
+ **self.serialize_generic_fields(display_context),
68
65
  }
@@ -66,8 +66,5 @@ class BaseTemplatingNodeDisplay(BaseNodeDisplay[_TemplatingNodeType], Generic[_T
66
66
  "template_node_input_id": str(template_node_input.id),
67
67
  "output_type": inferred_output_type,
68
68
  },
69
- "display_data": self.get_display_data().dict(),
70
- "base": self.get_base().dict(),
71
- "definition": self.get_definition().dict(),
72
- "ports": self.serialize_ports(display_context),
69
+ **self.serialize_generic_fields(display_context),
73
70
  }
@@ -251,6 +251,7 @@ def test_serialize_node__with_non_exist_code_input_path_with_dry_run():
251
251
  ],
252
252
  },
253
253
  "ports": [{"id": "7afa3858-f50c-4116-936a-a401e3b2c60f", "name": "default", "type": "DEFAULT"}],
254
+ "trigger": {"id": "3a39ea63-9f86-4891-a902-0216a7190720", "merge_behavior": "AWAIT_ANY"},
254
255
  },
255
256
  ],
256
257
  "edges": [
@@ -1,13 +1,31 @@
1
+ from datetime import datetime
2
+
1
3
  from vellum.client.types.prompt_parameters import PromptParameters
4
+ from vellum.client.types.release_review_reviewer import ReleaseReviewReviewer
5
+ from vellum.client.types.workflow_deployment_release import (
6
+ ReleaseEnvironment,
7
+ ReleaseReleaseTag,
8
+ SlimReleaseReview,
9
+ WorkflowDeploymentRelease,
10
+ WorkflowDeploymentReleaseWorkflowDeployment,
11
+ WorkflowDeploymentReleaseWorkflowVersion,
12
+ )
2
13
  from vellum.workflows import BaseWorkflow
3
14
  from vellum.workflows.inputs import BaseInputs
15
+ from vellum.workflows.nodes.bases import BaseNode
4
16
  from vellum.workflows.nodes.displayable.code_execution_node.node import CodeExecutionNode
5
17
  from vellum.workflows.nodes.displayable.inline_prompt_node.node import InlinePromptNode
6
18
  from vellum.workflows.nodes.displayable.tool_calling_node.node import ToolCallingNode
7
19
  from vellum.workflows.nodes.displayable.tool_calling_node.state import ToolCallingState
8
20
  from vellum.workflows.nodes.displayable.tool_calling_node.utils import create_router_node, create_tool_prompt_node
21
+ from vellum.workflows.outputs.base import BaseOutputs
9
22
  from vellum.workflows.state.base import BaseState
10
- from vellum.workflows.types.definition import AuthorizationType, EnvironmentVariableReference, MCPServer
23
+ from vellum.workflows.types.definition import (
24
+ AuthorizationType,
25
+ DeploymentDefinition,
26
+ EnvironmentVariableReference,
27
+ MCPServer,
28
+ )
11
29
  from vellum_ee.workflows.display.nodes.get_node_display_class import get_node_display_class
12
30
  from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
13
31
 
@@ -185,6 +203,58 @@ def test_serialize_node__tool_calling_node__mcp_server_api_key():
185
203
  }
186
204
 
187
205
 
206
+ def test_serialize_node__tool_calling_node__mcp_server_no_authorization():
207
+ # GIVEN a tool calling node with an mcp server
208
+ class MyToolCallingNode(ToolCallingNode):
209
+ functions = [
210
+ MCPServer(
211
+ name="my-mcp-server",
212
+ url="https://my-mcp-server.com",
213
+ )
214
+ ]
215
+
216
+ # AND a workflow with the tool calling node
217
+ class Workflow(BaseWorkflow):
218
+ graph = MyToolCallingNode
219
+
220
+ # WHEN the workflow is serialized
221
+ workflow_display = get_workflow_display(workflow_class=Workflow)
222
+ serialized_workflow: dict = workflow_display.serialize()
223
+
224
+ # THEN the node should properly serialize the mcp server
225
+ my_tool_calling_node = next(
226
+ node
227
+ for node in serialized_workflow["workflow_raw_data"]["nodes"]
228
+ if node["id"] == str(MyToolCallingNode.__id__)
229
+ )
230
+
231
+ functions_attribute = next(
232
+ attribute for attribute in my_tool_calling_node["attributes"] if attribute["name"] == "functions"
233
+ )
234
+
235
+ assert functions_attribute == {
236
+ "id": "c8957551-cb3d-49af-8053-acd256c1d852",
237
+ "name": "functions",
238
+ "value": {
239
+ "type": "CONSTANT_VALUE",
240
+ "value": {
241
+ "type": "JSON",
242
+ "value": [
243
+ {
244
+ "type": "MCP_SERVER",
245
+ "name": "my-mcp-server",
246
+ "url": "https://my-mcp-server.com",
247
+ "authorization_type": None,
248
+ "bearer_token_value": None,
249
+ "api_key_header_key": None,
250
+ "api_key_header_value": None,
251
+ }
252
+ ],
253
+ },
254
+ },
255
+ }
256
+
257
+
188
258
  def test_serialize_tool_router_node():
189
259
  """
190
260
  Test that the tool router node created by create_router_node serializes successfully.
@@ -406,3 +476,171 @@ def test_serialize_node__tool_calling_node__subworkflow_with_parent_input_refere
406
476
  "combinator": "OR",
407
477
  },
408
478
  }
479
+
480
+
481
+ def test_serialize_tool_prompt_node_with_inline_workflow():
482
+ """
483
+ Test that the tool prompt node created by create_tool_prompt_node serializes successfully with inline workflow.
484
+ """
485
+
486
+ # GIVEN a simple inline workflow for tool calling
487
+ class SimpleWorkflowInputs(BaseInputs):
488
+ message: str
489
+
490
+ class SimpleNode(BaseNode):
491
+ message = SimpleWorkflowInputs.message
492
+
493
+ class Outputs(BaseOutputs):
494
+ result: str
495
+
496
+ def run(self) -> Outputs:
497
+ return self.Outputs(result=f"Processed: {self.message}")
498
+
499
+ class SimpleInlineWorkflow(BaseWorkflow[SimpleWorkflowInputs, BaseState]):
500
+ """A simple workflow for testing inline tool serialization."""
501
+
502
+ graph = SimpleNode
503
+
504
+ class Outputs(BaseOutputs):
505
+ result = SimpleNode.Outputs.result
506
+
507
+ # WHEN we create a tool prompt node using create_tool_prompt_node with inline workflow
508
+ tool_prompt_node = create_tool_prompt_node(
509
+ ml_model="gpt-4o-mini",
510
+ blocks=[],
511
+ functions=[SimpleInlineWorkflow],
512
+ prompt_inputs=None,
513
+ parameters=PromptParameters(),
514
+ )
515
+
516
+ tool_prompt_node_display_class = get_node_display_class(tool_prompt_node)
517
+ tool_prompt_node_display = tool_prompt_node_display_class()
518
+
519
+ # AND we create a workflow that uses this tool prompt node
520
+ class TestWorkflow(BaseWorkflow[BaseInputs, ToolCallingState]):
521
+ graph = tool_prompt_node
522
+
523
+ # WHEN we serialize the entire workflow
524
+ workflow_display = get_workflow_display(workflow_class=TestWorkflow)
525
+ display_context = workflow_display.display_context
526
+ serialized_tool_prompt_node = tool_prompt_node_display.serialize(display_context)
527
+
528
+ # THEN prompt inputs should be serialized correctly
529
+ attributes = serialized_tool_prompt_node["attributes"]
530
+ assert isinstance(attributes, list)
531
+ prompt_inputs_attr = next(
532
+ (attr for attr in attributes if isinstance(attr, dict) and attr["name"] == "prompt_inputs"), None
533
+ )
534
+ assert prompt_inputs_attr == {
535
+ "id": "bc1320a2-23e4-4238-8b00-efbf88e91856",
536
+ "name": "prompt_inputs",
537
+ "value": {
538
+ "type": "DICTIONARY_REFERENCE",
539
+ "entries": [
540
+ {
541
+ "id": "76ceec7b-ec37-474f-ba38-2bfd27cecc5d",
542
+ "key": "chat_history",
543
+ "value": {
544
+ "type": "BINARY_EXPRESSION",
545
+ "lhs": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": []}},
546
+ "operator": "concat",
547
+ "rhs": {
548
+ "type": "WORKFLOW_STATE",
549
+ "state_variable_id": "7a1caaf5-99df-487a-8b2d-6512df2d871a",
550
+ },
551
+ },
552
+ }
553
+ ],
554
+ },
555
+ }
556
+
557
+
558
+ def test_serialize_tool_prompt_node_with_workflow_deployment(vellum_client):
559
+ """
560
+ Test that the tool prompt node serializes successfully with a workflow deployment.
561
+ """
562
+ vellum_client.workflow_deployments.retrieve_workflow_deployment_release.return_value = WorkflowDeploymentRelease(
563
+ id="test-id",
564
+ created=datetime.now(),
565
+ environment=ReleaseEnvironment(
566
+ id="test-id",
567
+ name="test-name",
568
+ label="test-label",
569
+ ),
570
+ created_by=None,
571
+ workflow_version=WorkflowDeploymentReleaseWorkflowVersion(
572
+ id="test-id",
573
+ input_variables=[],
574
+ output_variables=[],
575
+ ),
576
+ deployment=WorkflowDeploymentReleaseWorkflowDeployment(name="test-name"),
577
+ description="test-description",
578
+ release_tags=[
579
+ ReleaseReleaseTag(
580
+ name="test-name",
581
+ source="USER",
582
+ )
583
+ ],
584
+ reviews=[
585
+ SlimReleaseReview(
586
+ id="test-id",
587
+ created=datetime.now(),
588
+ reviewer=ReleaseReviewReviewer(
589
+ id="test-id",
590
+ full_name="test-name",
591
+ ),
592
+ state="APPROVED",
593
+ )
594
+ ],
595
+ )
596
+
597
+ # GIVEN a workflow deployment
598
+ workflow_deployment = DeploymentDefinition(
599
+ deployment="test-deployment",
600
+ release_tag="test-release-tag",
601
+ )
602
+
603
+ # WHEN we create a tool prompt node using create_tool_prompt_node with a workflow deployment
604
+ tool_prompt_node = create_tool_prompt_node(
605
+ ml_model="gpt-4o-mini",
606
+ blocks=[],
607
+ functions=[workflow_deployment],
608
+ prompt_inputs=None,
609
+ parameters=PromptParameters(),
610
+ )
611
+
612
+ tool_prompt_node_display_class = get_node_display_class(tool_prompt_node)
613
+ tool_prompt_node_display = tool_prompt_node_display_class()
614
+
615
+ # AND we create a workflow that uses this tool prompt node
616
+ class TestWorkflow(BaseWorkflow[BaseInputs, ToolCallingState]):
617
+ graph = tool_prompt_node
618
+
619
+ # WHEN we serialize the entire workflow
620
+ workflow_display = get_workflow_display(workflow_class=TestWorkflow)
621
+ display_context = workflow_display.display_context
622
+ serialized_tool_prompt_node = tool_prompt_node_display.serialize(display_context)
623
+
624
+ # THEN functions attribute should be serialized correctly
625
+ attributes = serialized_tool_prompt_node["attributes"]
626
+ assert isinstance(attributes, list)
627
+ functions_attr = next((attr for attr in attributes if isinstance(attr, dict) and attr["name"] == "functions"), None)
628
+ assert functions_attr == {
629
+ "id": "6326ccc4-7cf6-4235-ba3c-a6e860b0c48b",
630
+ "name": "functions",
631
+ "value": {
632
+ "type": "CONSTANT_VALUE",
633
+ "value": {
634
+ "type": "JSON",
635
+ "value": [
636
+ {
637
+ "type": "WORKFLOW_DEPLOYMENT",
638
+ "name": "test-name",
639
+ "description": "test-description",
640
+ "deployment": "test-deployment",
641
+ "release_tag": "test-release-tag",
642
+ }
643
+ ],
644
+ },
645
+ },
646
+ }
@@ -2,7 +2,8 @@ from uuid import UUID
2
2
  from typing import Dict
3
3
 
4
4
  from vellum.workflows.inputs import BaseInputs
5
- from vellum.workflows.nodes import BaseNode
5
+ from vellum.workflows.nodes import BaseNode, InlineSubworkflowNode
6
+ from vellum.workflows.outputs.base import BaseOutputs
6
7
  from vellum.workflows.ports.port import Port
7
8
  from vellum.workflows.references.lazy import LazyReference
8
9
  from vellum.workflows.state import BaseState
@@ -327,3 +328,54 @@ def test_serialize__port_with_lazy_reference():
327
328
  },
328
329
  }
329
330
  ]
331
+
332
+
333
+ def test_global_propagation_deep_nested_subworkflows():
334
+ # GIVEN the root workflow, a middle workflow, and an inner workflow
335
+
336
+ class RootInputs(BaseInputs):
337
+ root_param: str
338
+
339
+ class MiddleInputs(BaseInputs):
340
+ middle_param: str
341
+
342
+ class InnerInputs(BaseInputs):
343
+ inner_param: str
344
+
345
+ class InnerNode(BaseNode):
346
+ class Outputs(BaseOutputs):
347
+ done: bool
348
+
349
+ def run(self) -> Outputs:
350
+ return self.Outputs(done=True)
351
+
352
+ class InnerWorkflow(BaseWorkflow[InnerInputs, BaseState]):
353
+ graph = InnerNode
354
+
355
+ class MiddleInlineSubworkflowNode(InlineSubworkflowNode):
356
+ subworkflow_inputs = {"inner_param": "x"}
357
+ subworkflow = InnerWorkflow
358
+
359
+ class MiddleWorkflow(BaseWorkflow[MiddleInputs, BaseState]):
360
+ graph = MiddleInlineSubworkflowNode
361
+
362
+ class OuterInlineSubworkflowNode(InlineSubworkflowNode):
363
+ subworkflow_inputs = {"middle_param": "y"}
364
+ subworkflow = MiddleWorkflow
365
+
366
+ class RootWorkflow(BaseWorkflow[RootInputs, BaseState]):
367
+ graph = OuterInlineSubworkflowNode
368
+
369
+ # WHEN we build the displays
370
+ root_display = get_workflow_display(workflow_class=RootWorkflow)
371
+ middle_display = get_workflow_display(
372
+ workflow_class=MiddleWorkflow, parent_display_context=root_display.display_context
373
+ )
374
+ inner_display = get_workflow_display(
375
+ workflow_class=InnerWorkflow, parent_display_context=middle_display.display_context
376
+ )
377
+
378
+ # THEN the deepest display must include root + middle + inner inputs in its GLOBAL view
379
+ inner_global_names = {ref.name for ref in inner_display.display_context.global_workflow_input_displays.keys()}
380
+
381
+ assert inner_global_names == {"middle_param", "inner_param", "root_param"}
@@ -203,6 +203,10 @@ def test_serialize_workflow(vellum_client):
203
203
  "name": "SimpleAPINode",
204
204
  "module": ["tests", "workflows", "basic_api_node", "workflow"],
205
205
  },
206
+ "trigger": {
207
+ "id": "14b538a5-aedb-41f3-b579-039956b7c7ed",
208
+ "merge_behavior": "AWAIT_ANY",
209
+ },
206
210
  "ports": [{"id": "7c33b4d3-9204-4bd5-9371-80ee34f83073", "name": "default", "type": "DEFAULT"}],
207
211
  },
208
212
  api_node,
@@ -110,6 +110,10 @@ def test_serialize_workflow_with_filepath():
110
110
  "module": ["tests", "workflows", "basic_code_execution_node", "workflow"],
111
111
  "name": "SimpleCodeExecutionNode",
112
112
  },
113
+ "trigger": {
114
+ "id": "e02a2701-22c0-4533-8b00-175998e7350a",
115
+ "merge_behavior": "AWAIT_ANY",
116
+ },
113
117
  "ports": [{"id": "832f81ec-427b-42a8-825c-e62c43c1f961", "name": "default", "type": "DEFAULT"}],
114
118
  }
115
119
  assert not DeepDiff(
@@ -342,6 +346,10 @@ def test_serialize_workflow_with_code():
342
346
  "name": "SimpleCodeExecutionNode",
343
347
  "module": ["tests", "workflows", "basic_code_execution_node", "workflow_with_code"],
344
348
  },
349
+ "trigger": {
350
+ "id": "e02a2701-22c0-4533-8b00-175998e7350a",
351
+ "merge_behavior": "AWAIT_ANY",
352
+ },
345
353
  "ports": [{"id": "832f81ec-427b-42a8-825c-e62c43c1f961", "name": "default", "type": "DEFAULT"}],
346
354
  }
347
355
  assert not DeepDiff(
@@ -602,6 +610,10 @@ def test_serialize_workflow__try_wrapped():
602
610
  ],
603
611
  }
604
612
  ],
613
+ "trigger": {
614
+ "id": "e02a2701-22c0-4533-8b00-175998e7350a",
615
+ "merge_behavior": "AWAIT_ANY",
616
+ },
605
617
  "ports": [{"id": "832f81ec-427b-42a8-825c-e62c43c1f961", "name": "default", "type": "DEFAULT"}],
606
618
  }
607
619
 
@@ -424,6 +424,10 @@ def test_serialize_workflow():
424
424
  "name": "CategoryConditionalNode",
425
425
  "module": ["tests", "workflows", "basic_conditional_node", "workflow"],
426
426
  },
427
+ "trigger": {
428
+ "id": "dd89e228-a23e-422b-80b2-34362c1c050e",
429
+ "merge_behavior": "AWAIT_ANY",
430
+ },
427
431
  "ports": [
428
432
  {
429
433
  "id": "3a45b81f-95e4-4cbd-8997-bfdbe30251e8",
@@ -952,6 +956,10 @@ def test_conditional_node_serialize_all_operators_with_lhs_and_rhs(descriptor, o
952
956
  "name": "SimpleConditionalNode",
953
957
  "module": ["tests", "workflows", "basic_conditional_node", "workflow_with_only_one_conditional_node"],
954
958
  },
959
+ "trigger": {
960
+ "id": "c6e99e94-bc8e-47a4-b75c-cc96c6bedbb0",
961
+ "merge_behavior": "AWAIT_ANY",
962
+ },
955
963
  "ports": [
956
964
  {
957
965
  "id": "2ff87aa6-37cf-43dd-af9d-13b9198ab70a",
@@ -1063,6 +1071,10 @@ def test_conditional_node_serialize_all_operators_with_expression(descriptor, op
1063
1071
  "name": "SimpleConditionalNode",
1064
1072
  "module": ["tests", "workflows", "basic_conditional_node", "workflow_with_only_one_conditional_node"],
1065
1073
  },
1074
+ "trigger": {
1075
+ "id": "c6e99e94-bc8e-47a4-b75c-cc96c6bedbb0",
1076
+ "merge_behavior": "AWAIT_ANY",
1077
+ },
1066
1078
  "ports": [
1067
1079
  {
1068
1080
  "id": "2ff87aa6-37cf-43dd-af9d-13b9198ab70a",
@@ -1186,6 +1198,10 @@ def test_conditional_node_serialize_all_operators_with_value_and_start_and_end(d
1186
1198
  "name": "SimpleConditionalNode",
1187
1199
  "module": ["tests", "workflows", "basic_conditional_node", "workflow_with_only_one_conditional_node"],
1188
1200
  },
1201
+ "trigger": {
1202
+ "id": "c6e99e94-bc8e-47a4-b75c-cc96c6bedbb0",
1203
+ "merge_behavior": "AWAIT_ANY",
1204
+ },
1189
1205
  "ports": [
1190
1206
  {
1191
1207
  "id": "2ff87aa6-37cf-43dd-af9d-13b9198ab70a",