vellum-ai 1.7.0__py3-none-any.whl → 1.7.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.
@@ -27,10 +27,10 @@ class BaseClientWrapper:
27
27
 
28
28
  def get_headers(self) -> typing.Dict[str, str]:
29
29
  headers: typing.Dict[str, str] = {
30
- "User-Agent": "vellum-ai/1.7.0",
30
+ "User-Agent": "vellum-ai/1.7.2",
31
31
  "X-Fern-Language": "Python",
32
32
  "X-Fern-SDK-Name": "vellum-ai",
33
- "X-Fern-SDK-Version": "1.7.0",
33
+ "X-Fern-SDK-Version": "1.7.2",
34
34
  **(self.get_custom_headers() or {}),
35
35
  }
36
36
  if self._api_version is not None:
@@ -12,6 +12,7 @@ from vellum.workflows.types.definition import VellumIntegrationToolDetails
12
12
 
13
13
  def test_vellum_integration_service_get_tool_definition_success(vellum_client):
14
14
  """Test that tool definitions are successfully retrieved from Vellum API"""
15
+ # GIVEN a mock client configured to return a tool definition
15
16
  mock_client = vellum_client
16
17
  tool_definition_response = ComponentsSchemasComposioToolDefinition(
17
18
  integration=ToolDefinitionIntegration(
@@ -33,7 +34,6 @@ def test_vellum_integration_service_get_tool_definition_success(vellum_client):
33
34
  },
34
35
  output_parameters={},
35
36
  )
36
-
37
37
  mock_client.integrations.retrieve_integration_tool_definition.return_value = tool_definition_response
38
38
 
39
39
  # WHEN we request a tool definition
@@ -66,6 +66,7 @@ def test_vellum_integration_service_get_tool_definition_success(vellum_client):
66
66
 
67
67
  def test_vellum_integration_service_get_tool_definition_api_error(vellum_client):
68
68
  """Test that API errors are properly handled when retrieving tool definitions"""
69
+ # GIVEN a mock client configured to raise an exception
69
70
  mock_client = vellum_client
70
71
  mock_client.integrations = mock.MagicMock()
71
72
  mock_client.integrations.retrieve_integration_tool_definition.side_effect = Exception("Tool not found")
@@ -87,6 +88,7 @@ def test_vellum_integration_service_get_tool_definition_api_error(vellum_client)
87
88
 
88
89
  def test_vellum_integration_service_execute_tool_success(vellum_client):
89
90
  """Test that tools are successfully executed via Vellum API"""
91
+ # GIVEN a mock client configured to return a successful response
90
92
  mock_client = vellum_client
91
93
  mock_client.integrations = mock.MagicMock()
92
94
 
@@ -96,7 +98,6 @@ def test_vellum_integration_service_execute_tool_success(vellum_client):
96
98
  "issue_id": 123,
97
99
  "issue_url": "https://github.com/user/repo/issues/123",
98
100
  }
99
-
100
101
  mock_client.integrations.execute_integration_tool.return_value = mock_response
101
102
 
102
103
  # WHEN we execute a tool with valid arguments
@@ -132,6 +133,7 @@ def test_vellum_integration_service_execute_tool_success(vellum_client):
132
133
 
133
134
  def test_vellum_integration_service_execute_tool_api_error(vellum_client):
134
135
  """Test that execution errors are properly handled"""
136
+ # GIVEN a mock client configured to raise an exception
135
137
  mock_client = vellum_client
136
138
  mock_client.integrations = mock.MagicMock()
137
139
  mock_client.integrations.execute_integration_tool.side_effect = Exception("Authentication failed")
@@ -154,12 +156,12 @@ def test_vellum_integration_service_execute_tool_api_error(vellum_client):
154
156
 
155
157
  def test_vellum_integration_service_execute_tool_empty_response(vellum_client):
156
158
  """Test that empty response data is handled gracefully"""
159
+ # GIVEN a mock client configured to return an empty response
157
160
  mock_client = vellum_client
158
161
  mock_client.integrations = mock.MagicMock()
159
162
 
160
163
  mock_response = mock.MagicMock()
161
164
  mock_response.data = {}
162
-
163
165
  mock_client.integrations.execute_integration_tool.return_value = mock_response
164
166
 
165
167
  # WHEN we execute a tool that returns empty data
@@ -180,6 +182,7 @@ def test_vellum_integration_service_execute_tool_empty_response(vellum_client):
180
182
 
181
183
  def test_vellum_integration_service_multiple_tool_executions(vellum_client):
182
184
  """Test that the service handles multiple sequential tool executions"""
185
+ # GIVEN a mock client configured to return different responses for multiple calls
183
186
  mock_client = vellum_client
184
187
  mock_client.integrations = mock.MagicMock()
185
188
 
@@ -214,16 +217,18 @@ def test_vellum_integration_service_multiple_tool_executions(vellum_client):
214
217
  assert mock_client.integrations.execute_integration_tool.call_count == 2
215
218
 
216
219
 
217
- def test_vellum_integration_service_execute_tool_structured_403_error(vellum_client):
218
- """Test that structured 403 responses with integration details are properly parsed"""
220
+ def test_vellum_integration_service_execute_tool_structured_403_with_integration(vellum_client):
221
+ """Test structured 403 responses with integration field (current backend format)"""
219
222
  from vellum.client.core.api_error import ApiError
220
223
  from vellum.workflows.errors.types import WorkflowErrorCode
221
224
 
225
+ # GIVEN a mock client configured to raise a structured 403 error with integration
222
226
  mock_client = vellum_client
223
227
  mock_client.integrations = mock.MagicMock()
224
228
 
225
- # Mock structured 403 response matching PR #14857 format
229
+ # Mock current backend structure with integration as direct field
226
230
  structured_error_body = {
231
+ "code": "INTEGRATION_CREDENTIALS_UNAVAILABLE",
227
232
  "message": "You must authenticate with this integration before you can execute this tool.",
228
233
  "integration": {
229
234
  "id": "550e8400-e29b-41d4-a716-446655440000",
@@ -231,15 +236,13 @@ def test_vellum_integration_service_execute_tool_structured_403_error(vellum_cli
231
236
  "name": "GITHUB",
232
237
  },
233
238
  }
234
-
235
239
  mock_client.integrations.execute_integration_tool.side_effect = ApiError(
236
240
  status_code=403,
237
241
  body=structured_error_body,
238
242
  )
239
243
 
240
- service = VellumIntegrationService(client=mock_client)
241
-
242
244
  # WHEN we attempt to execute a tool without credentials
245
+ service = VellumIntegrationService(client=mock_client)
243
246
  with pytest.raises(NodeException) as exc_info:
244
247
  service.execute_tool(
245
248
  integration="GITHUB",
@@ -266,19 +269,18 @@ def test_vellum_integration_service_execute_tool_legacy_403_error(vellum_client)
266
269
  from vellum.client.core.api_error import ApiError
267
270
  from vellum.workflows.errors.types import WorkflowErrorCode
268
271
 
272
+ # GIVEN a mock client configured to raise a legacy 403 error
269
273
  mock_client = vellum_client
270
274
  mock_client.integrations = mock.MagicMock()
271
275
 
272
- # Mock legacy 403 response format (just detail field)
273
276
  legacy_error_body = {"detail": "You do not have permission to execute this tool."}
274
-
275
277
  mock_client.integrations.execute_integration_tool.side_effect = ApiError(
276
278
  status_code=403,
277
279
  body=legacy_error_body,
278
280
  )
279
281
 
282
+ # WHEN we attempt to execute a tool that returns a legacy 403 error
280
283
  service = VellumIntegrationService(client=mock_client)
281
-
282
284
  with pytest.raises(NodeException) as exc_info:
283
285
  service.execute_tool(
284
286
  integration="GITHUB",
@@ -95,15 +95,15 @@ class VellumIntegrationService:
95
95
  except ApiError as e:
96
96
  # Handle structured 403 credential error responses
97
97
  if e.status_code == 403 and isinstance(e.body, dict):
98
- if "integration" in e.body and "message" in e.body:
99
- integration_details = e.body["integration"]
100
- error_message = e.body["message"]
101
-
102
- # Keep integration details nested under "integration" key to keep raw_data raw
103
- # and allow for future expansion
104
- raw_data = {
105
- "integration": integration_details,
106
- }
98
+ # Check for backend structure with integration as direct field
99
+ integration_from_backend = e.body.get("integration")
100
+ if integration_from_backend:
101
+ error_message = e.body.get(
102
+ "message", "You must authenticate with this integration before you can execute this tool."
103
+ )
104
+
105
+ # Wrap integration in raw_data for frontend consumption
106
+ raw_data = {"integration": integration_from_backend}
107
107
 
108
108
  raise NodeException(
109
109
  message=error_message,
@@ -117,13 +117,7 @@ class CodeExecutionNode(BaseNode[StateType], Generic[StateType, _OutputType], me
117
117
  request_options=self.request_options,
118
118
  )
119
119
  except ApiError as e:
120
- if e.status_code == 400 and isinstance(e.body, dict) and "message" in e.body:
121
- raise NodeException(
122
- message=e.body["message"],
123
- code=WorkflowErrorCode.INVALID_INPUTS,
124
- )
125
-
126
- raise
120
+ self._handle_api_error(e)
127
121
 
128
122
  if code_execution_result.output.type != expected_output_type:
129
123
  actual_type = code_execution_result.output.type
@@ -134,6 +128,24 @@ class CodeExecutionNode(BaseNode[StateType], Generic[StateType, _OutputType], me
134
128
 
135
129
  return self.Outputs(result=code_execution_result.output.value, log=code_execution_result.log)
136
130
 
131
+ def _handle_api_error(self, e: ApiError) -> None:
132
+ if e.status_code and e.status_code == 403 and isinstance(e.body, dict):
133
+ raise NodeException(
134
+ message=e.body.get("detail", "Provider credentials is missing or unavailable"),
135
+ code=WorkflowErrorCode.PROVIDER_CREDENTIALS_UNAVAILABLE,
136
+ ) from e
137
+
138
+ if e.status_code and e.status_code >= 400 and e.status_code < 500 and isinstance(e.body, dict):
139
+ raise NodeException(
140
+ message=e.body.get("message", e.body.get("detail", "Failed to execute code")),
141
+ code=WorkflowErrorCode.INVALID_INPUTS,
142
+ ) from e
143
+
144
+ raise NodeException(
145
+ message="Failed to execute code",
146
+ code=WorkflowErrorCode.INTERNAL_ERROR,
147
+ ) from e
148
+
137
149
  def _has_secrets_in_code_inputs(self) -> bool:
138
150
  """Check if any code_inputs contain VellumSecret instances that require API execution."""
139
151
  for input_value in self.code_inputs.values():
@@ -6,7 +6,11 @@ from typing import Any, List, Union
6
6
  from pydantic import BaseModel
7
7
 
8
8
  from vellum import ArrayInput, CodeExecutorResponse, NumberVellumValue, StringInput, StringVellumValue
9
+ from vellum.client.core.api_error import ApiError
9
10
  from vellum.client.errors.bad_request_error import BadRequestError
11
+ from vellum.client.errors.forbidden_error import ForbiddenError
12
+ from vellum.client.errors.internal_server_error import InternalServerError
13
+ from vellum.client.errors.not_found_error import NotFoundError
10
14
  from vellum.client.types.chat_message import ChatMessage
11
15
  from vellum.client.types.code_execution_package import CodeExecutionPackage
12
16
  from vellum.client.types.code_executor_secret_input import CodeExecutorSecretInput
@@ -792,6 +796,103 @@ Node.js v21.7.3
792
796
  assert exc_info.value.message == message
793
797
 
794
798
 
799
+ def test_run_node__execute_code_api_fails_403__provider_credentials_unavailable(vellum_client):
800
+ """
801
+ Tests that a 403 error from the API is handled with PROVIDER_CREDENTIALS_UNAVAILABLE error code.
802
+ """
803
+
804
+ class ExampleCodeExecutionNode(CodeExecutionNode[BaseState, str]):
805
+ code = "def main(): return 'test'"
806
+ runtime = "PYTHON_3_11_6"
807
+ packages = [CodeExecutionPackage(name="requests", version="2.28.0")]
808
+
809
+ vellum_client.execute_code.side_effect = ForbiddenError(
810
+ body={
811
+ "detail": "Provider credentials is missing or unavailable",
812
+ }
813
+ )
814
+
815
+ node = ExampleCodeExecutionNode()
816
+ with pytest.raises(NodeException) as exc_info:
817
+ node.run()
818
+
819
+ assert exc_info.value.code == WorkflowErrorCode.PROVIDER_CREDENTIALS_UNAVAILABLE
820
+ assert "Provider credentials is missing or unavailable" in exc_info.value.message
821
+
822
+
823
+ def test_run_node__execute_code_api_fails_404__invalid_inputs(vellum_client):
824
+ """
825
+ Tests that a 404 error from the API is handled with INVALID_INPUTS error code.
826
+ """
827
+
828
+ class ExampleCodeExecutionNode(CodeExecutionNode[BaseState, str]):
829
+ code = "def main(): return 'test'"
830
+ runtime = "PYTHON_3_11_6"
831
+ packages = [CodeExecutionPackage(name="requests", version="2.28.0")]
832
+
833
+ vellum_client.execute_code.side_effect = NotFoundError(
834
+ body={
835
+ "detail": "Resource not found",
836
+ }
837
+ )
838
+
839
+ node = ExampleCodeExecutionNode()
840
+ with pytest.raises(NodeException) as exc_info:
841
+ node.run()
842
+
843
+ assert exc_info.value.code == WorkflowErrorCode.INVALID_INPUTS
844
+ assert "Resource not found" in exc_info.value.message
845
+
846
+
847
+ def test_run_node__execute_code_api_fails_500__internal_error(vellum_client):
848
+ """
849
+ Tests that a 500 error from the API is handled with INTERNAL_ERROR error code.
850
+ """
851
+
852
+ class ExampleCodeExecutionNode(CodeExecutionNode[BaseState, str]):
853
+ code = "def main(): return 'test'"
854
+ runtime = "PYTHON_3_11_6"
855
+ packages = [CodeExecutionPackage(name="requests", version="2.28.0")]
856
+
857
+ vellum_client.execute_code.side_effect = InternalServerError(
858
+ body={
859
+ "detail": "Internal server error occurred",
860
+ }
861
+ )
862
+
863
+ node = ExampleCodeExecutionNode()
864
+ with pytest.raises(NodeException) as exc_info:
865
+ node.run()
866
+
867
+ assert exc_info.value.code == WorkflowErrorCode.INTERNAL_ERROR
868
+ assert exc_info.value.message == "Failed to execute code"
869
+
870
+
871
+ def test_run_node__execute_code_api_fails_4xx_no_message__uses_detail(vellum_client):
872
+ """
873
+ Tests that a 4xx error without a 'message' field falls back to 'detail' field.
874
+ """
875
+
876
+ class ExampleCodeExecutionNode(CodeExecutionNode[BaseState, str]):
877
+ code = "def main(): return 'test'"
878
+ runtime = "PYTHON_3_11_6"
879
+ packages = [CodeExecutionPackage(name="requests", version="2.28.0")]
880
+
881
+ vellum_client.execute_code.side_effect = ApiError(
882
+ status_code=422,
883
+ body={
884
+ "detail": "Invalid request parameters",
885
+ },
886
+ )
887
+
888
+ node = ExampleCodeExecutionNode()
889
+ with pytest.raises(NodeException) as exc_info:
890
+ node.run()
891
+
892
+ assert exc_info.value.code == WorkflowErrorCode.INVALID_INPUTS
893
+ assert "Invalid request parameters" in exc_info.value.message
894
+
895
+
795
896
  def test_run_node__execute_code__list_extends():
796
897
  # GIVEN a node that will return a list with output type Json
797
898
  class ExampleCodeExecutionNode(CodeExecutionNode[BaseState, Json]):
@@ -22,8 +22,8 @@ from vellum.workflows.nodes.displayable.tool_calling_node.utils import (
22
22
  )
23
23
  from vellum.workflows.outputs.base import BaseOutput, BaseOutputs
24
24
  from vellum.workflows.state.context import WorkflowContext
25
- from vellum.workflows.types.core import EntityInputsInterface, Tool
26
- from vellum.workflows.types.definition import MCPServer
25
+ from vellum.workflows.types.core import EntityInputsInterface
26
+ from vellum.workflows.types.definition import MCPServer, Tool
27
27
  from vellum.workflows.types.generics import StateType
28
28
  from vellum.workflows.utils.functions import compile_mcp_tool_definition, get_mcp_tool_name
29
29
  from vellum.workflows.workflows.event_filters import all_workflow_event_filter
@@ -31,12 +31,14 @@ from vellum.workflows.outputs.base import BaseOutput
31
31
  from vellum.workflows.ports.port import Port
32
32
  from vellum.workflows.state import BaseState
33
33
  from vellum.workflows.state.encoder import DefaultStateEncoder
34
- from vellum.workflows.types.core import EntityInputsInterface, MergeBehavior, Tool, ToolBase
34
+ from vellum.workflows.types.core import EntityInputsInterface, MergeBehavior
35
35
  from vellum.workflows.types.definition import (
36
36
  ComposioToolDefinition,
37
37
  DeploymentDefinition,
38
38
  MCPServer,
39
39
  MCPToolDefinition,
40
+ Tool,
41
+ ToolBase,
40
42
  VellumIntegrationToolDefinition,
41
43
  )
42
44
  from vellum.workflows.types.generics import is_workflow_class
@@ -203,7 +205,7 @@ class FunctionNode(BaseNode[ToolCallingState], FunctionCallNodeMixin):
203
205
  raise NodeException(
204
206
  message=f"Error executing function '{function_name}': {str(e)}",
205
207
  code=WorkflowErrorCode.NODE_EXECUTION,
206
- )
208
+ ) from e
207
209
 
208
210
  # Add the result to the chat history
209
211
  self._add_function_result_to_chat_history(result, self.state)
@@ -233,7 +235,7 @@ class ComposioNode(BaseNode[ToolCallingState], FunctionCallNodeMixin):
233
235
  raise NodeException(
234
236
  message=f"Error executing Composio tool '{self.composio_tool.action}': {str(e)}",
235
237
  code=WorkflowErrorCode.NODE_EXECUTION,
236
- )
238
+ ) from e
237
239
 
238
240
  # Add result to chat history
239
241
  self._add_function_result_to_chat_history(result, self.state)
@@ -256,7 +258,7 @@ class MCPNode(BaseNode[ToolCallingState], FunctionCallNodeMixin):
256
258
  raise NodeException(
257
259
  message=f"Error executing MCP tool '{self.mcp_tool.name}': {str(e)}",
258
260
  code=WorkflowErrorCode.NODE_EXECUTION,
259
- )
261
+ ) from e
260
262
 
261
263
  # Add result to chat history
262
264
  self._add_function_result_to_chat_history(result, self.state)
@@ -281,11 +283,18 @@ class VellumIntegrationNode(BaseNode[ToolCallingState], FunctionCallNodeMixin):
281
283
  tool_name=self.vellum_integration_tool.name,
282
284
  arguments=arguments,
283
285
  )
286
+ except NodeException as e:
287
+ # Preserve original error code and raw_data while adding context
288
+ raise NodeException(
289
+ message=f"Error executing Vellum Integration tool '{self.vellum_integration_tool.name}': {e.message}",
290
+ code=e.code,
291
+ raw_data=e.raw_data,
292
+ ) from e
284
293
  except Exception as e:
285
294
  raise NodeException(
286
295
  message=f"Error executing Vellum Integration tool '{self.vellum_integration_tool.name}': {str(e)}",
287
296
  code=WorkflowErrorCode.NODE_EXECUTION,
288
- )
297
+ ) from e
289
298
 
290
299
  # Add result to chat history
291
300
  self._add_function_result_to_chat_history(result, self.state)
@@ -1,9 +1,7 @@
1
1
  import logging
2
2
  from uuid import UUID
3
- from typing import Iterator, List, Optional, Tuple, Type, Union
3
+ from typing import Iterator, Optional, Type, Union
4
4
 
5
- from vellum.client.types.vellum_span import VellumSpan
6
- from vellum.client.types.workflow_execution_initiated_event import WorkflowExecutionInitiatedEvent
7
5
  from vellum.workflows.events.workflow import WorkflowEvent
8
6
  from vellum.workflows.nodes.utils import cast_to_output_type
9
7
  from vellum.workflows.resolvers.base import BaseWorkflowResolver
@@ -20,38 +18,6 @@ class VellumResolver(BaseWorkflowResolver):
20
18
  def get_state_snapshot_history(self) -> Iterator[BaseState]:
21
19
  return iter([])
22
20
 
23
- def _find_previous_and_root_span(
24
- self, execution_id: str, spans: List[VellumSpan]
25
- ) -> Tuple[Optional[str], Optional[str], Optional[str], Optional[str]]:
26
- previous_trace_id: Optional[str] = None
27
- root_trace_id: Optional[str] = None
28
- previous_span_id: Optional[str] = None
29
- root_span_id: Optional[str] = None
30
-
31
- for span in spans:
32
- # Look for workflow execution spans with matching ID first
33
- if span.name == "workflow.execution" and span.span_id == execution_id:
34
- # Find the WorkflowExecutionInitiatedEvent in the span's events
35
- initiated_event = next(
36
- (event for event in span.events if isinstance(event, WorkflowExecutionInitiatedEvent)), None
37
- )
38
- if initiated_event:
39
- previous_trace_id = initiated_event.trace_id
40
- previous_span_id = initiated_event.span_id
41
- links = initiated_event.links
42
- if links:
43
- root_span = next((link for link in links if link.type == "ROOT_SPAN"), None)
44
- if root_span:
45
- root_trace_id = root_span.trace_id
46
- root_span_id = root_span.span_context.span_id
47
- else:
48
- # no links means this is the first execution
49
- root_trace_id = initiated_event.trace_id
50
- root_span_id = initiated_event.span_id
51
- break
52
-
53
- return previous_trace_id, root_trace_id, previous_span_id, root_span_id
54
-
55
21
  def _deserialize_state(self, state_data: dict, state_class: Type[BaseState]) -> BaseState:
56
22
  """Deserialize state data with proper type conversion for complex types like List[ChatMessage]."""
57
23
  converted_data = {}
@@ -79,18 +45,19 @@ class VellumResolver(BaseWorkflowResolver):
79
45
  return None
80
46
 
81
47
  client = self._context.vellum_client
82
- response = client.workflow_executions.retrieve_workflow_execution_detail(
83
- execution_id=previous_execution_id,
48
+ response = client.workflows.retrieve_state(
49
+ span_id=previous_execution_id,
84
50
  )
85
51
 
86
52
  if response.state is None:
87
53
  return None
88
54
 
89
- previous_trace_id, root_trace_id, previous_span_id, root_span_id = self._find_previous_and_root_span(
90
- previous_execution_id, response.spans
91
- )
92
-
93
- if previous_trace_id is None or root_trace_id is None or previous_span_id is None or root_span_id is None:
55
+ if (
56
+ response.previous_trace_id is None
57
+ or response.root_trace_id is None
58
+ or response.previous_span_id is None
59
+ or response.root_span_id is None
60
+ ):
94
61
  logger.warning("Could not find required execution events for state loading")
95
62
  return None
96
63
 
@@ -106,8 +73,8 @@ class VellumResolver(BaseWorkflowResolver):
106
73
 
107
74
  return LoadStateResult(
108
75
  state=state,
109
- previous_trace_id=previous_trace_id,
110
- previous_span_id=previous_span_id,
111
- root_trace_id=root_trace_id,
112
- root_span_id=root_span_id,
76
+ previous_trace_id=response.previous_trace_id,
77
+ previous_span_id=response.previous_span_id,
78
+ root_trace_id=response.root_trace_id,
79
+ root_span_id=response.root_span_id,
113
80
  )
@@ -4,14 +4,7 @@ from uuid import uuid4
4
4
  from typing import List
5
5
 
6
6
  from vellum import ChatMessage
7
- from vellum.client.types.span_link import SpanLink
8
- from vellum.client.types.vellum_code_resource_definition import VellumCodeResourceDefinition
9
- from vellum.client.types.workflow_execution_detail import WorkflowExecutionDetail
10
- from vellum.client.types.workflow_execution_initiated_body import WorkflowExecutionInitiatedBody
11
- from vellum.client.types.workflow_execution_initiated_event import WorkflowExecutionInitiatedEvent
12
- from vellum.client.types.workflow_execution_span import WorkflowExecutionSpan
13
- from vellum.client.types.workflow_execution_span_attributes import WorkflowExecutionSpanAttributes
14
- from vellum.client.types.workflow_parent_context import WorkflowParentContext
7
+ from vellum.client.types.workflow_resolved_state import WorkflowResolvedState
15
8
  from vellum.workflows import BaseWorkflow
16
9
  from vellum.workflows.inputs.base import BaseInputs
17
10
  from vellum.workflows.resolvers.resolver import VellumResolver
@@ -24,7 +17,6 @@ def test_load_state_with_context_success():
24
17
  """Test load_state successfully loads state when context and client are available."""
25
18
  resolver = VellumResolver()
26
19
  execution_id = uuid4()
27
- root_execution_id = uuid4()
28
20
 
29
21
  class TestState(BaseState):
30
22
  test_key: str = "test_value"
@@ -50,60 +42,24 @@ def test_load_state_with_context_success():
50
42
  },
51
43
  }
52
44
 
53
- mock_workflow_definition = VellumCodeResourceDefinition(
54
- name="TestWorkflow", module=["test", "module"], id=str(uuid4())
55
- )
56
-
57
- mock_body = WorkflowExecutionInitiatedBody(workflow_definition=mock_workflow_definition, inputs={})
58
-
59
45
  previous_trace_id = str(uuid4())
46
+ previous_span_id = str(uuid4())
60
47
  root_trace_id = str(uuid4())
48
+ root_span_id = str(uuid4())
61
49
 
62
- previous_invocation = WorkflowExecutionInitiatedEvent(
63
- id=str(uuid4()),
50
+ mock_response = WorkflowResolvedState(
51
+ trace_id=str(uuid4()),
64
52
  timestamp=datetime.now(),
65
- trace_id=previous_trace_id,
66
53
  span_id=str(execution_id),
67
- body=mock_body,
68
- links=[
69
- SpanLink(
70
- trace_id=previous_trace_id,
71
- type="PREVIOUS_SPAN",
72
- span_context=WorkflowParentContext(workflow_definition=mock_workflow_definition, span_id=str(uuid4())),
73
- ),
74
- SpanLink(
75
- trace_id=root_trace_id,
76
- type="ROOT_SPAN",
77
- span_context=WorkflowParentContext(
78
- workflow_definition=mock_workflow_definition, span_id=str(root_execution_id)
79
- ),
80
- ),
81
- ],
82
- )
83
-
84
- root_invocation = WorkflowExecutionInitiatedEvent(
85
- id=str(uuid4()),
86
- timestamp=datetime.now(),
87
- trace_id=root_trace_id,
88
- span_id=str(root_execution_id),
89
- body=mock_body,
90
- links=None, # Root invocation has no links
91
- )
92
-
93
- mock_span = WorkflowExecutionSpan(
94
- span_id=str(execution_id), # Use the actual execution_id
95
- start_ts=datetime.now(),
96
- end_ts=datetime.now(),
97
- attributes=WorkflowExecutionSpanAttributes(label="Test Workflow", workflow_id=str(uuid4())),
98
- events=[previous_invocation, root_invocation],
99
- )
100
-
101
- mock_response = WorkflowExecutionDetail(
102
- span_id="test-span-id", start=datetime.now(), inputs=[], outputs=[], spans=[mock_span], state=state_dict
54
+ state=state_dict,
55
+ previous_trace_id=previous_trace_id,
56
+ previous_span_id=previous_span_id,
57
+ root_trace_id=root_trace_id,
58
+ root_span_id=root_span_id,
103
59
  )
104
60
 
105
61
  mock_client = Mock()
106
- mock_client.workflow_executions.retrieve_workflow_execution_detail.return_value = mock_response
62
+ mock_client.workflows.retrieve_state.return_value = mock_response
107
63
 
108
64
  # AND context with the test workflow class is set up
109
65
  context = WorkflowContext(vellum_client=mock_client)
@@ -123,21 +79,18 @@ def test_load_state_with_context_success():
123
79
  assert str(result.state.meta.span_id) != prev_span_id
124
80
 
125
81
  # AND should have span link info
126
- assert result.previous_trace_id == previous_invocation.trace_id
127
- assert result.previous_span_id == previous_invocation.span_id
128
- assert result.root_trace_id == root_invocation.trace_id
129
- assert result.root_span_id == root_invocation.span_id
82
+ assert result.previous_trace_id == previous_trace_id
83
+ assert result.previous_span_id == previous_span_id
84
+ assert result.root_trace_id == root_trace_id
85
+ assert result.root_span_id == root_span_id
130
86
 
131
- mock_client.workflow_executions.retrieve_workflow_execution_detail.assert_called_once_with(
132
- execution_id=str(execution_id)
133
- )
87
+ mock_client.workflows.retrieve_state.assert_called_once_with(span_id=str(execution_id))
134
88
 
135
89
 
136
90
  def test_load_state_with_chat_message_list():
137
91
  """Test load_state successfully loads state with chat_history containing ChatMessage list."""
138
92
  resolver = VellumResolver()
139
93
  execution_id = uuid4()
140
- root_execution_id = uuid4()
141
94
 
142
95
  class TestStateWithChatHistory(BaseState):
143
96
  test_key: str = "test_value"
@@ -169,60 +122,24 @@ def test_load_state_with_chat_message_list():
169
122
  },
170
123
  }
171
124
 
172
- mock_workflow_definition = VellumCodeResourceDefinition(
173
- name="TestWorkflow", module=["test", "module"], id=str(uuid4())
174
- )
175
-
176
- mock_body = WorkflowExecutionInitiatedBody(workflow_definition=mock_workflow_definition, inputs={})
177
-
178
125
  previous_trace_id = str(uuid4())
126
+ previous_span_id = str(uuid4())
179
127
  root_trace_id = str(uuid4())
128
+ root_span_id = str(uuid4())
180
129
 
181
- previous_invocation = WorkflowExecutionInitiatedEvent(
182
- id=str(uuid4()),
183
- timestamp=datetime.now(),
184
- trace_id=previous_trace_id,
185
- span_id=str(execution_id),
186
- body=mock_body,
187
- links=[
188
- SpanLink(
189
- trace_id=previous_trace_id,
190
- type="PREVIOUS_SPAN",
191
- span_context=WorkflowParentContext(workflow_definition=mock_workflow_definition, span_id=str(uuid4())),
192
- ),
193
- SpanLink(
194
- trace_id=root_trace_id,
195
- type="ROOT_SPAN",
196
- span_context=WorkflowParentContext(
197
- workflow_definition=mock_workflow_definition, span_id=str(root_execution_id)
198
- ),
199
- ),
200
- ],
201
- )
202
-
203
- root_invocation = WorkflowExecutionInitiatedEvent(
204
- id=str(uuid4()),
130
+ mock_response = WorkflowResolvedState(
131
+ trace_id=str(uuid4()),
205
132
  timestamp=datetime.now(),
206
- trace_id=root_trace_id,
207
- span_id=str(root_execution_id),
208
- body=mock_body,
209
- links=None,
210
- )
211
-
212
- mock_span = WorkflowExecutionSpan(
213
133
  span_id=str(execution_id),
214
- start_ts=datetime.now(),
215
- end_ts=datetime.now(),
216
- attributes=WorkflowExecutionSpanAttributes(label="Test Workflow", workflow_id=str(uuid4())),
217
- events=[previous_invocation, root_invocation],
218
- )
219
-
220
- mock_response = WorkflowExecutionDetail(
221
- span_id="test-span-id", start=datetime.now(), inputs=[], outputs=[], spans=[mock_span], state=state_dict
134
+ state=state_dict,
135
+ previous_trace_id=previous_trace_id,
136
+ previous_span_id=previous_span_id,
137
+ root_trace_id=root_trace_id,
138
+ root_span_id=root_span_id,
222
139
  )
223
140
 
224
141
  mock_client = Mock()
225
- mock_client.workflow_executions.retrieve_workflow_execution_detail.return_value = mock_response
142
+ mock_client.workflows.retrieve_state.return_value = mock_response
226
143
 
227
144
  # AND context with the test workflow class is set up
228
145
  context = WorkflowContext(vellum_client=mock_client)
@@ -247,6 +164,4 @@ def test_load_state_with_chat_message_list():
247
164
  assert result.state.chat_history[2].role == "USER"
248
165
  assert result.state.chat_history[2].text == "What can you help me with?"
249
166
 
250
- mock_client.workflow_executions.retrieve_workflow_execution_detail.assert_called_once_with(
251
- execution_id=str(execution_id)
252
- )
167
+ mock_client.workflows.retrieve_state.assert_called_once_with(span_id=str(execution_id))
@@ -442,14 +442,24 @@ class StateMeta(UniversalBaseModel):
442
442
  if not memo:
443
443
  memo = {}
444
444
 
445
+ node_output_keys = list(self.node_outputs.keys())
445
446
  new_node_outputs = {
446
- descriptor: value if isinstance(value, Queue) else deepcopy(value, memo)
447
- for descriptor, value in self.node_outputs.items()
447
+ descriptor: (
448
+ self.node_outputs[descriptor]
449
+ if isinstance(self.node_outputs[descriptor], Queue)
450
+ else deepcopy(self.node_outputs[descriptor], memo)
451
+ )
452
+ for descriptor in node_output_keys
448
453
  }
449
454
 
455
+ external_input_keys = list(self.external_inputs.keys())
450
456
  new_external_inputs = {
451
- descriptor: value if isinstance(value, Queue) else deepcopy(value, memo)
452
- for descriptor, value in self.external_inputs.items()
457
+ descriptor: (
458
+ self.external_inputs[descriptor]
459
+ if isinstance(self.external_inputs[descriptor], Queue)
460
+ else deepcopy(self.external_inputs[descriptor], memo)
461
+ )
462
+ for descriptor in external_input_keys
453
463
  }
454
464
 
455
465
  memo[id(self.node_outputs)] = new_node_outputs
@@ -1,11 +1,8 @@
1
1
  from enum import Enum
2
2
  from typing import ( # type: ignore[attr-defined]
3
- TYPE_CHECKING,
4
3
  Any,
5
- Callable,
6
4
  Dict,
7
5
  List,
8
- Type,
9
6
  Union,
10
7
  _GenericAlias,
11
8
  _SpecialGenericAlias,
@@ -13,16 +10,6 @@ from typing import ( # type: ignore[attr-defined]
13
10
  )
14
11
 
15
12
  from vellum.client.core.pydantic_utilities import UniversalBaseModel
16
- from vellum.workflows.types.definition import (
17
- ComposioToolDefinition,
18
- DeploymentDefinition,
19
- MCPServer,
20
- VellumIntegrationToolDefinition,
21
- )
22
-
23
- if TYPE_CHECKING:
24
- from vellum.workflows.workflows.base import BaseWorkflow
25
-
26
13
 
27
14
  JsonArray = List["Json"]
28
15
  JsonObject = Dict[str, "Json"]
@@ -53,14 +40,3 @@ class ConditionType(Enum):
53
40
  IF = "IF"
54
41
  ELIF = "ELIF"
55
42
  ELSE = "ELSE"
56
-
57
-
58
- # Type alias for functions that can be called in tool calling nodes
59
- ToolBase = Union[
60
- Callable[..., Any],
61
- DeploymentDefinition,
62
- Type["BaseWorkflow"],
63
- ComposioToolDefinition,
64
- VellumIntegrationToolDefinition,
65
- ]
66
- Tool = Union[ToolBase, MCPServer]
@@ -2,7 +2,7 @@ import importlib
2
2
  import inspect
3
3
  from types import FrameType
4
4
  from uuid import UUID
5
- from typing import Annotated, Any, Dict, List, Literal, Optional, Union
5
+ from typing import TYPE_CHECKING, Annotated, Any, Callable, Dict, List, Literal, Optional, Type, Union
6
6
 
7
7
  from pydantic import BeforeValidator, SerializationInfo, model_serializer
8
8
 
@@ -13,6 +13,9 @@ from vellum.client.types.vellum_variable import VellumVariable
13
13
  from vellum.workflows.constants import AuthorizationType, VellumIntegrationProviderType
14
14
  from vellum.workflows.references.environment_variable import EnvironmentVariableReference
15
15
 
16
+ if TYPE_CHECKING:
17
+ from vellum.workflows.workflows.base import BaseWorkflow
18
+
16
19
 
17
20
  def serialize_type_encoder(obj: type) -> Dict[str, Any]:
18
21
  return {
@@ -216,3 +219,14 @@ class MCPToolDefinition(UniversalBaseModel):
216
219
  server: MCPServer
217
220
  description: Optional[str] = None
218
221
  parameters: Dict[str, Any] = {}
222
+
223
+
224
+ # Type alias for functions that can be called in tool calling nodes
225
+ ToolBase = Union[
226
+ Callable[..., Any],
227
+ DeploymentDefinition,
228
+ Type["BaseWorkflow"],
229
+ ComposioToolDefinition,
230
+ VellumIntegrationToolDefinition,
231
+ ]
232
+ Tool = Union[ToolBase, MCPServer]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-ai
3
- Version: 1.7.0
3
+ Version: 1.7.2
4
4
  Summary:
5
5
  License: MIT
6
6
  Requires-Python: >=3.9,<4.0
@@ -22,7 +22,7 @@ vellum_cli/tests/test_ping.py,sha256=b3aQLd-N59_8w2rRiWqwpB1rlHaKEYVbAj1Y3hi7A-g
22
22
  vellum_cli/tests/test_pull.py,sha256=e2XHzcHIx9k-FyuNAl7wMSNsSSebPGyP6U05JGcddFs,49447
23
23
  vellum_cli/tests/test_push.py,sha256=2MjkNKr_9Guv5Exjsm3L1BeVXmPkKUcCSiKnp90HgW4,41996
24
24
  vellum_ee/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
- vellum_ee/assets/node-definitions.json,sha256=tinXep1k-FicJWSPQb5mlWRkdKul1WJtG4o94SqD4ds,29800
25
+ vellum_ee/assets/node-definitions.json,sha256=UkHixt8WAnHmupooOrtpxYCaNG-5pRon1c8QUlc0Vhk,30754
26
26
  vellum_ee/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
27
  vellum_ee/scripts/generate_node_definitions.py,sha256=FOYQsXIqU45I0OAcsyZUGODF9JK44yunf58rR6YaAdA,3303
28
28
  vellum_ee/workflows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -55,7 +55,7 @@ vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py,sha256=Ue727X
55
55
  vellum_ee/workflows/display/nodes/vellum/retry_node.py,sha256=5xv5OR4xRuI1R5yqJDZTNdCFYY-z8PkTdpWM4ziGjw0,3192
56
56
  vellum_ee/workflows/display/nodes/vellum/search_node.py,sha256=ZHqkwzCRsM0TkZmh3ePHreZAoQXeT-SFS7zbUrSjMsw,12037
57
57
  vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py,sha256=hC5hTSRf1f9ppiZ_wXzpcp-jl0fwNAUB9PY8s0AykHs,3131
58
- vellum_ee/workflows/display/nodes/vellum/templating_node.py,sha256=QExp4l2KMNBdqMVDL4CG_iFMpFf3RYiTP2q5P2DmEFk,3178
58
+ vellum_ee/workflows/display/nodes/vellum/templating_node.py,sha256=sWupmR7Ri2ZeCLx8713JbuawabR_kpWp_-HwT1tCrtU,3163
59
59
  vellum_ee/workflows/display/nodes/vellum/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
60
  vellum_ee/workflows/display/nodes/vellum/tests/test_api_node.py,sha256=DQAtsabvn6BE6xWwKNHzMOppzoy1-1dssNnrwbHUdRU,1490
61
61
  vellum_ee/workflows/display/nodes/vellum/tests/test_code_execution_node.py,sha256=6_1yVYftVnqXJn3hLUGHlcHvbgKbQgBfS6vYQ6R79oc,10891
@@ -79,7 +79,7 @@ vellum_ee/workflows/display/tests/test_base_workflow_display.py,sha256=-LYQw5nWq
79
79
  vellum_ee/workflows/display/tests/workflow_serialization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
80
80
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
81
81
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/conftest.py,sha256=Y-ajeT65b5varmrZCw6L3hir4hJCFq-eO0jZfRcrs7g,1886
82
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py,sha256=bD9AtBo7Pz1Drbh420NR0VSiHBfExw_UtNRPCiAqki0,13967
82
+ vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py,sha256=wdVaFvxicj48Kj-6VUlu61KR0oSArLTjownRi2p0NWQ,14941
83
83
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attributes_serialization.py,sha256=bPTwkLpB7trFLpAaDvXMfMP0c9H1u_c1cdnj7K-gtnw,24962
84
84
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_outputs_serialization.py,sha256=SwXRzjdoEZLvkzaRMvRV8_UqbBm0EB_UtAHD_zXKZBY,6303
85
85
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_ports_serialization.py,sha256=jubGYgLJImOqILd5LjnYJ4B1UMIrToDrQbPZOvaQCX4,40035
@@ -126,7 +126,7 @@ vellum_ee/workflows/display/utils/tests/test_events.py,sha256=42IEBnMbaQrH8gigw5
126
126
  vellum_ee/workflows/display/utils/vellum.py,sha256=Bt7kdLdXoBsHn5dVEY2uKcF542VL09jwu8J_30rl2vk,6413
127
127
  vellum_ee/workflows/display/vellum.py,sha256=J2mdJZ1sdLW535DDUkq_Vm8Z572vhuxHxVZF9deKSdk,391
128
128
  vellum_ee/workflows/display/workflows/__init__.py,sha256=JTB9ObEV3l4gGGdtfBHwVJtTTKC22uj-a-XjTVwXCyA,148
129
- vellum_ee/workflows/display/workflows/base_workflow_display.py,sha256=FAsEeqkQu4xGvdZ4Ri5H7LDEISQYziidPc2QHzbOVWs,44311
129
+ vellum_ee/workflows/display/workflows/base_workflow_display.py,sha256=uP3pqSloGjmUYBvVfKP0skZXJnqZxm7TEVxSLdrzU5c,44682
130
130
  vellum_ee/workflows/display/workflows/get_vellum_workflow_display_class.py,sha256=gxz76AeCqgAZ9D2lZeTiZzxY9eMgn3qOSfVgiqYcOh8,2028
131
131
  vellum_ee/workflows/display/workflows/tests/test_workflow_display.py,sha256=lg-c_3P3ldtqWq2VFsk_2Mkn3pVdXWuT59QpH7QwXVs,39764
132
132
  vellum_ee/workflows/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -160,7 +160,7 @@ vellum/client/README.md,sha256=flqu57ubZNTfpq60CdLtJC9gp4WEkyjb_n_eZ4OYf9w,6497
160
160
  vellum/client/__init__.py,sha256=rMnKRqL5-356SBc-rfm56MkO87PuAi2mtcfBszcJU1M,74316
161
161
  vellum/client/core/__init__.py,sha256=lTcqUPXcx4112yLDd70RAPeqq6tu3eFMe1pKOqkW9JQ,1562
162
162
  vellum/client/core/api_error.py,sha256=44vPoTyWN59gonCIZMdzw7M1uspygiLnr3GNFOoVL2Q,614
163
- vellum/client/core/client_wrapper.py,sha256=7jdvkc9bp52v55uLBImLe_fMXRZUgS2HqLAzuk-JDRg,2840
163
+ vellum/client/core/client_wrapper.py,sha256=4Sfjvux7hceG2zs5IHHn9McKc2YE_WIb9ctx_HfCyzA,2840
164
164
  vellum/client/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
165
165
  vellum/client/core/file.py,sha256=d4NNbX8XvXP32z8KpK2Xovv33nFfruIrpz0QWxlgpZk,2663
166
166
  vellum/client/core/force_multipart.py,sha256=awxh5MtcRYe74ehY8U76jzv6fYM_w_D3Rur7KQQzSDk,429
@@ -1876,8 +1876,8 @@ vellum/workflows/integrations/composio_service.py,sha256=rSliaZtNiBcDSvDxz9k5i1K
1876
1876
  vellum/workflows/integrations/mcp_service.py,sha256=9DYb8dg2_kgc1UOu830kxhaFlt9yTbhKPhK3L6kb1t4,9831
1877
1877
  vellum/workflows/integrations/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1878
1878
  vellum/workflows/integrations/tests/test_mcp_service.py,sha256=q_DYrDkIqI4sQBNgID4YdbM4e9tneLVWY8YmI4R26d8,8859
1879
- vellum/workflows/integrations/tests/test_vellum_integration_service.py,sha256=n_n42FnwbR7YMd1sEVMphdOUWsWXCPw_caY6FnxNxAU,11322
1880
- vellum/workflows/integrations/vellum_integration_service.py,sha256=JFkrXBhkTEn_tXnRbjt289Z56_SLDSsuU7Msx2Nh9AM,5192
1879
+ vellum/workflows/integrations/tests/test_vellum_integration_service.py,sha256=QGJmaW5EF7E2fkZotd4rc2HItQc-1z3cpgwaKUFWpgg,11956
1880
+ vellum/workflows/integrations/vellum_integration_service.py,sha256=qhFoLzHlMli1PC8oh5phvWuSpJ9IqL1g2eaGhypBTqs,5266
1881
1881
  vellum/workflows/logging.py,sha256=_a217XogktV4Ncz6xKFz7WfYmZAzkfVRVuC0rWob8ls,437
1882
1882
  vellum/workflows/nodes/__init__.py,sha256=zymtc3_iW2rFmMR-sayTLuN6ZsAw8VnJweWPsjQk2-Q,1197
1883
1883
  vellum/workflows/nodes/bases/__init__.py,sha256=cniHuz_RXdJ4TQgD8CBzoiKDiPxg62ErdVpCbWICX64,58
@@ -1931,11 +1931,11 @@ vellum/workflows/nodes/displayable/bases/tests/test_utils.py,sha256=eqdqbKNRWVMD
1931
1931
  vellum/workflows/nodes/displayable/bases/types.py,sha256=C37B2Qh2YP7s7pUjd-EYKc2Zl1TbnCgI_mENuUSb8bo,1706
1932
1932
  vellum/workflows/nodes/displayable/bases/utils.py,sha256=X1YSPmbBlxDrTAw6dy2-9HrIG8Vb_J-2k1lP3i-SOsk,6951
1933
1933
  vellum/workflows/nodes/displayable/code_execution_node/__init__.py,sha256=0FLWMMktpzSnmBMizQglBpcPrP80fzVsoJwJgf822Cg,76
1934
- vellum/workflows/nodes/displayable/code_execution_node/node.py,sha256=t4AuqyGOSgitYUUs6Tqzo7lDeyOyJ5g8x7Q9j4UkH7M,10426
1934
+ vellum/workflows/nodes/displayable/code_execution_node/node.py,sha256=JkJ_p9KKmrkUqc8TPcxfPoZ1AiEnWWwrdzLj-0TS9ds,10985
1935
1935
  vellum/workflows/nodes/displayable/code_execution_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1936
1936
  vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1937
1937
  vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/main.py,sha256=5QsbmkzSlSbcbWTG_JmIqcP-JNJzOPTKxGzdHos19W4,79
1938
- vellum/workflows/nodes/displayable/code_execution_node/tests/test_node.py,sha256=RQy7SsA39kTJAudzpYNN3jK_kuq6bWNZUld54H5pRZA,40966
1938
+ vellum/workflows/nodes/displayable/code_execution_node/tests/test_node.py,sha256=eCWBaAPzneDiOQe1xFyyb9h9LeQQOAdjRY1RJS0z8sE,44500
1939
1939
  vellum/workflows/nodes/displayable/code_execution_node/utils.py,sha256=0F_4PVvB7vjsV0RS48Brv_4djebWOMR_zgzHxzA9iV4,3308
1940
1940
  vellum/workflows/nodes/displayable/conditional_node/__init__.py,sha256=AS_EIqFdU1F9t8aLmbZU-rLh9ry6LCJ0uj0D8F0L5Uw,72
1941
1941
  vellum/workflows/nodes/displayable/conditional_node/node.py,sha256=2g32hWosE3zwVaK2UPFnVEer1Nbn04s3friF2rGztmE,1195
@@ -1975,13 +1975,13 @@ vellum/workflows/nodes/displayable/tests/test_search_node_error_handling.py,sha2
1975
1975
  vellum/workflows/nodes/displayable/tests/test_search_node_wth_text_output.py,sha256=VepO5z1277c1y5N6LLIC31nnWD1aak2m5oPFplfJHHs,6935
1976
1976
  vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py,sha256=Bjv-wZyFgNaVZb9KEMMZd9lFoLzbPEPjEMpANizMZw4,2413
1977
1977
  vellum/workflows/nodes/displayable/tool_calling_node/__init__.py,sha256=3n0-ysmFKsr40CVxPthc0rfJgqVJeZuUEsCmYudLVRg,117
1978
- vellum/workflows/nodes/displayable/tool_calling_node/node.py,sha256=RxwBjNMmpRBu2G6fnu37MvqRVxa7ZQzOPtqccCYlCqI,8420
1978
+ vellum/workflows/nodes/displayable/tool_calling_node/node.py,sha256=pwK9q1blgRv9Mz_LY-fv_hxHfOOkuKxJ6AgRlorUrXk,8420
1979
1979
  vellum/workflows/nodes/displayable/tool_calling_node/state.py,sha256=CcBVb_YtwfSSka4ze678k6-qwmzMSfjfVP8_Y95feSo,302
1980
1980
  vellum/workflows/nodes/displayable/tool_calling_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1981
1981
  vellum/workflows/nodes/displayable/tool_calling_node/tests/test_composio_service.py,sha256=in1fbEz5x1tx3uKv9YXdvOncsHucNL8Ro6Go7lBuuOQ,8962
1982
1982
  vellum/workflows/nodes/displayable/tool_calling_node/tests/test_node.py,sha256=GZoeybB9uM7ai8sBLAtUMHrMVgh-WrJDWrKZci6feDs,11892
1983
1983
  vellum/workflows/nodes/displayable/tool_calling_node/tests/test_utils.py,sha256=EmKFA-ELdTzlK0xMqWnuSZPoGNLYCwk6b0amTqirZo0,11305
1984
- vellum/workflows/nodes/displayable/tool_calling_node/utils.py,sha256=7xNiNPD4vzd-4NuIaeCZhs0AshkuxlWVdx1NnbX-fdg,23722
1984
+ vellum/workflows/nodes/displayable/tool_calling_node/utils.py,sha256=NDp2rEU-1QACxrSplocDKlPzILczzvPygkUOrysjU7Y,24109
1985
1985
  vellum/workflows/nodes/displayable/web_search_node/__init__.py,sha256=8FOnEP-n-U68cvxTlJW9wphIAGHq5aqjzLM-DoSSXnU,61
1986
1986
  vellum/workflows/nodes/displayable/web_search_node/node.py,sha256=NQYux2bOtuBF5E4tn-fXi5y3btURPRrNqMSM9MAZYI4,5091
1987
1987
  vellum/workflows/nodes/displayable/web_search_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -2017,14 +2017,14 @@ vellum/workflows/references/vellum_secret.py,sha256=Od4d19a5yletWMqNfJR5d_mZQUkV
2017
2017
  vellum/workflows/references/workflow_input.py,sha256=W3rOK1EPd2gYHb04WJwmNm1CUSdvZ9LKrs8RMKxACBs,1751
2018
2018
  vellum/workflows/resolvers/__init__.py,sha256=eH6hTvZO4IciDaf_cf7aM2vs-DkBDyJPycOQevJxQnI,82
2019
2019
  vellum/workflows/resolvers/base.py,sha256=wrQiSC02Bw4-dBwgFjJIHsjpe-4xz4rUJs_1RdErKA0,1164
2020
- vellum/workflows/resolvers/resolver.py,sha256=yK-oY0HDsFJcjlNKAm3vpsPKRIFerIh59FcTwuEN5RY,4839
2021
- vellum/workflows/resolvers/tests/test_resolver.py,sha256=jXkJBb9SwtoH__bBN-ECohpyD0aTIB9ErEvtFhuTMQM,9750
2020
+ vellum/workflows/resolvers/resolver.py,sha256=EIIiA2OlaVUPiKiSh6egQZxPA6ny1GDMdPq1AuN-mV8,2961
2021
+ vellum/workflows/resolvers/tests/test_resolver.py,sha256=PnUGzsulo1It_LjjhHsRNiILvvl5G_IaK8ZX56zKC28,6204
2022
2022
  vellum/workflows/resolvers/types.py,sha256=Hndhlk69g6EKLh_LYg5ILepW5U_h_BYNllfzhS9k8p4,237
2023
2023
  vellum/workflows/runner/__init__.py,sha256=i1iG5sAhtpdsrlvwgH6B-m49JsINkiWyPWs8vyT-bqM,72
2024
2024
  vellum/workflows/runner/runner.py,sha256=rwMZlQBkzK-EfewC6OysNCeuDeTCfpNuzaUlgYoFJMw,43329
2025
2025
  vellum/workflows/sandbox.py,sha256=mezSZmilR_fwR8164n8CEfzlMeQ55IqfapHp4ftImvQ,3212
2026
2026
  vellum/workflows/state/__init__.py,sha256=yUUdR-_Vl7UiixNDYQZ-GEM_kJI9dnOia75TtuNEsnE,60
2027
- vellum/workflows/state/base.py,sha256=m9fCqbZn21GshCVCjJTD1dPZEQjFrsMXqlg7tM9fIwM,24283
2027
+ vellum/workflows/state/base.py,sha256=A8s0PC8UvFjPpkHDY6u-yIeb2KHjoAmu-GW-GYrDl0E,24654
2028
2028
  vellum/workflows/state/context.py,sha256=khM30U1iDNts5Xp8LXa_WfpkITNITexrDUUFJ5wZ2W4,8445
2029
2029
  vellum/workflows/state/delta.py,sha256=7h8wR10lRCm15SykaPj-gSEvvsMjCwYLPsOx3nsvBQg,440
2030
2030
  vellum/workflows/state/encoder.py,sha256=EynuS9aCt9Neb-H6HRCinEVZX5olCzME03W1TSXfpxs,1961
@@ -2037,8 +2037,8 @@ vellum/workflows/tests/test_sandbox.py,sha256=JKwaluI-lODQo7Ek9sjDstjL_WTdSqUlVi
2037
2037
  vellum/workflows/tests/test_undefined.py,sha256=zMCVliCXVNLrlC6hEGyOWDnQADJ2g83yc5FIM33zuo8,353
2038
2038
  vellum/workflows/types/__init__.py,sha256=KxUTMBGzuRCfiMqzzsykOeVvrrkaZmTTo1a7SLu8gRM,68
2039
2039
  vellum/workflows/types/code_execution_node_wrappers.py,sha256=fewX9bqF_4TZuK-gZYIn12s31-k03vHMGRpvFAPm11Y,3206
2040
- vellum/workflows/types/core.py,sha256=yKm3sE02ult969q80DTmawiwYqodVjcAW-zlaUIgIv4,1495
2041
- vellum/workflows/types/definition.py,sha256=PWJF1SAopdOZcLcxz1ZtGYOa0oH0-dxjFaa_tuGb6SI,7682
2040
+ vellum/workflows/types/core.py,sha256=X8xNBLUhnea97t3qeeZJ2XyUwW_a38YiznXr73j_ppE,950
2041
+ vellum/workflows/types/definition.py,sha256=Qof2MAjSNB0AN2XkSKmk-owuY59YcxDVHYpno6-StPA,8058
2042
2042
  vellum/workflows/types/generics.py,sha256=8jptbEx1fnJV0Lhj0MpCJOT6yNiEWeTOYOwrEAb5CRU,1576
2043
2043
  vellum/workflows/types/stack.py,sha256=h7NE0vXR7l9DevFBIzIAk1Zh59K-kECQtDTKOUunwMY,1314
2044
2044
  vellum/workflows/types/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -2065,8 +2065,8 @@ vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnad
2065
2065
  vellum/workflows/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2066
2066
  vellum/workflows/workflows/tests/test_base_workflow.py,sha256=Boa-_m9ii2Qsa1RvVM-VYniF7zCpzGgEGy-OnPZkrHg,23941
2067
2067
  vellum/workflows/workflows/tests/test_context.py,sha256=VJBUcyWVtMa_lE5KxdhgMu0WYNYnUQUDvTF7qm89hJ0,2333
2068
- vellum_ai-1.7.0.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
2069
- vellum_ai-1.7.0.dist-info/METADATA,sha256=SMmQ-iMX4Dymys9Y_j7Jz4xq_QJGNUf8U9xYOgYKSw0,5547
2070
- vellum_ai-1.7.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
2071
- vellum_ai-1.7.0.dist-info/entry_points.txt,sha256=xVavzAKN4iF_NbmhWOlOkHluka0YLkbN_pFQ9pW3gLI,117
2072
- vellum_ai-1.7.0.dist-info/RECORD,,
2068
+ vellum_ai-1.7.2.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
2069
+ vellum_ai-1.7.2.dist-info/METADATA,sha256=KyFKrTqcT-6Pvl5zTeo1EsEP0-uY4WREJPAr8Vdkh4A,5547
2070
+ vellum_ai-1.7.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
2071
+ vellum_ai-1.7.2.dist-info/entry_points.txt,sha256=xVavzAKN4iF_NbmhWOlOkHluka0YLkbN_pFQ9pW3gLI,117
2072
+ vellum_ai-1.7.2.dist-info/RECORD,,
@@ -699,6 +699,51 @@
699
699
  }
700
700
  ]
701
701
  },
702
+ {
703
+ "id": "ee9b5234-247b-49d1-bed4-490312f18838",
704
+ "display_data": {
705
+ "position": {
706
+ "x": 0.0,
707
+ "y": 0.0
708
+ },
709
+ "comment": {
710
+ "value": "Used to render a Jinja template.\n\n Useful for lightweight data transformations and complex string templating.\n ",
711
+ "expanded": true
712
+ }
713
+ },
714
+ "base": {
715
+ "name": "BaseNode",
716
+ "module": [
717
+ "vellum",
718
+ "workflows",
719
+ "nodes",
720
+ "bases",
721
+ "base"
722
+ ]
723
+ },
724
+ "definition": {
725
+ "name": "TemplatingNode",
726
+ "module": [
727
+ "vellum",
728
+ "workflows",
729
+ "nodes",
730
+ "core",
731
+ "templating_node",
732
+ "node"
733
+ ]
734
+ },
735
+ "trigger": {
736
+ "id": "005e1cd2-5452-4e1f-be6a-ac9fe3c02b9b",
737
+ "merge_behavior": "AWAIT_ATTRIBUTES"
738
+ },
739
+ "ports": [
740
+ {
741
+ "id": "8f4460f0-717b-4972-a6f4-ac164e5e204e",
742
+ "name": "default",
743
+ "type": "DEFAULT"
744
+ }
745
+ ]
746
+ },
702
747
  {
703
748
  "id": "035842e0-34ed-43af-97de-a706e40912ae",
704
749
  "label": "Tool Calling Node",
@@ -1010,10 +1055,5 @@
1010
1055
  ]
1011
1056
  }
1012
1057
  ],
1013
- "errors": [
1014
- {
1015
- "node": "TemplatingNode",
1016
- "error": "KeyError: TemplatingNode.Outputs.result"
1017
- }
1018
- ]
1058
+ "errors": []
1019
1059
  }
@@ -50,7 +50,7 @@ class BaseTemplatingNodeDisplay(BaseNodeDisplay[_TemplatingNodeType], Generic[_T
50
50
  # Misc type ignore is due to `node.Outputs` being generic
51
51
  # https://app.shortcut.com/vellum/story/4784
52
52
  output_descriptor = node.Outputs.result # type: ignore [misc]
53
- output_display = display_context.global_node_output_displays[output_descriptor]
53
+ output_display = self.get_node_output_display(output_descriptor)
54
54
  inferred_output_type = primitive_type_to_vellum_variable_type(output_descriptor)
55
55
 
56
56
  return {
@@ -353,3 +353,33 @@ def test_serialize_node__adornment_order_matches_decorator_order():
353
353
  assert len(adornments) == 2
354
354
  assert adornments[0]["label"] == "Try Node"
355
355
  assert adornments[1]["label"] == "Retry Node"
356
+
357
+
358
+ def test_serialize_workflow__retry_node_edges():
359
+ """
360
+ Tests that both retry-adorned nodes are correctly serialized in the nodes array.
361
+ """
362
+
363
+ @RetryNode.wrap(max_attempts=3, delay=60)
364
+ class FirstNode(BaseNode):
365
+ class Outputs(BaseOutputs):
366
+ value: str
367
+
368
+ @RetryNode.wrap(max_attempts=5, delay=120)
369
+ class SecondNode(BaseNode):
370
+ class Outputs(BaseOutputs):
371
+ result: str
372
+
373
+ class MyWorkflow(BaseWorkflow):
374
+ graph = FirstNode >> SecondNode
375
+
376
+ workflow_display = get_workflow_display(workflow_class=MyWorkflow)
377
+ exec_config = cast(Dict[str, Any], workflow_display.serialize())
378
+
379
+ assert isinstance(exec_config["workflow_raw_data"], dict)
380
+ assert isinstance(exec_config["workflow_raw_data"]["nodes"], list)
381
+
382
+ nodes = cast(List[Dict[str, Any]], exec_config["workflow_raw_data"]["nodes"])
383
+
384
+ generic_nodes = [node for node in nodes if node["type"] == "GENERIC"]
385
+ assert len(generic_nodes) == 2
@@ -208,7 +208,15 @@ class BaseWorkflowDisplay(Generic[WorkflowType]):
208
208
  self.display_context.add_invalid_node(node)
209
209
  continue
210
210
 
211
- serialized_nodes[node_display.node_id] = serialized_node
211
+ # Use wrapped node's ID as dict key for adornment wrappers to prevent overwrites
212
+ wrapped_node = get_wrapped_node(node)
213
+ if wrapped_node:
214
+ wrapped_node_display = self.display_context.node_displays[wrapped_node]
215
+ dict_key = wrapped_node_display.node_id
216
+ else:
217
+ dict_key = node_display.node_id
218
+
219
+ serialized_nodes[dict_key] = serialized_node
212
220
 
213
221
  synthetic_output_edges: JsonArray = []
214
222
  output_variables: JsonArray = []