vellum-ai 0.13.18__py3-none-any.whl → 0.13.20__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 (39) hide show
  1. vellum/__init__.py +2 -0
  2. vellum/client/core/client_wrapper.py +1 -1
  3. vellum/client/resources/test_suites/client.py +44 -8
  4. vellum/client/types/__init__.py +2 -0
  5. vellum/client/types/container_image_container_image_tag.py +21 -0
  6. vellum/client/types/container_image_read.py +2 -1
  7. vellum/types/container_image_container_image_tag.py +3 -0
  8. vellum/workflows/nodes/core/inline_subworkflow_node/node.py +48 -3
  9. vellum/workflows/nodes/core/retry_node/node.py +13 -7
  10. vellum/workflows/outputs/base.py +11 -0
  11. {vellum_ai-0.13.18.dist-info → vellum_ai-0.13.20.dist-info}/METADATA +1 -1
  12. {vellum_ai-0.13.18.dist-info → vellum_ai-0.13.20.dist-info}/RECORD +39 -37
  13. vellum_cli/config.py +69 -11
  14. vellum_cli/tests/test_pull.py +42 -1
  15. vellum_ee/workflows/display/nodes/vellum/__init__.py +1 -1
  16. vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py +2 -3
  17. vellum_ee/workflows/display/nodes/vellum/utils.py +1 -1
  18. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +5 -0
  19. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_api_node_serialization.py +9 -30
  20. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +22 -36
  21. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +35 -70
  22. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py +2 -2
  23. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_generic_node_serialization.py +2 -2
  24. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_guardrail_node_serialization.py +2 -2
  25. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +24 -57
  26. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +4 -4
  27. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +2 -2
  28. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_prompt_deployment_serialization.py +2 -2
  29. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_search_node_serialization.py +2 -2
  30. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_subworkflow_deployment_serialization.py +4 -4
  31. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_node_serialization.py +2 -2
  32. vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +2 -2
  33. vellum_ee/workflows/display/utils/vellum.py +0 -2
  34. vellum_ee/workflows/display/vellum.py +0 -2
  35. vellum_ee/workflows/display/workflows/vellum_workflow_display.py +1 -8
  36. vellum_ee/workflows/tests/local_workflow/display/workflow.py +0 -2
  37. {vellum_ai-0.13.18.dist-info → vellum_ai-0.13.20.dist-info}/LICENSE +0 -0
  38. {vellum_ai-0.13.18.dist-info → vellum_ai-0.13.20.dist-info}/WHEEL +0 -0
  39. {vellum_ai-0.13.18.dist-info → vellum_ai-0.13.20.dist-info}/entry_points.txt +0 -0
vellum_cli/config.py CHANGED
@@ -1,3 +1,4 @@
1
+ from collections import defaultdict
1
2
  from dataclasses import field
2
3
  import json
3
4
  import os
@@ -88,6 +89,50 @@ class WorkflowConfig(UniversalBaseModel):
88
89
  )
89
90
 
90
91
 
92
+ def merge_workflows_by_sandbox_id(
93
+ workflows: List[WorkflowConfig], other_workflows: List[WorkflowConfig]
94
+ ) -> List[WorkflowConfig]:
95
+ merged_workflows: List[WorkflowConfig] = []
96
+ for self_workflow in workflows:
97
+ if self_workflow.workflow_sandbox_id is None:
98
+ # If the user defines a workflow in the pyproject.toml without a sandbox_id,
99
+ # we merge the workflow with one of the ones in the lockfile.
100
+ other_workflow = next(
101
+ (
102
+ other_workflow
103
+ for other_workflow in other_workflows
104
+ if self_workflow.workspace == other_workflow.workspace
105
+ ),
106
+ None,
107
+ )
108
+ if other_workflow is not None:
109
+ merged_workflows.append(self_workflow.merge(other_workflow))
110
+ else:
111
+ merged_workflows.append(self_workflow)
112
+ else:
113
+ # If the user defines a workflow in the pyproject.toml with a sandbox_id,
114
+ # we merge the workflow with one of the ones in the lockfile with the same sandbox_id.
115
+ other_workflow = next(
116
+ (
117
+ other_workflow
118
+ for other_workflow in other_workflows
119
+ if self_workflow.workflow_sandbox_id == other_workflow.workflow_sandbox_id
120
+ ),
121
+ None,
122
+ )
123
+ if other_workflow is not None:
124
+ merged_workflows.append(self_workflow.merge(other_workflow))
125
+ else:
126
+ merged_workflows.append(self_workflow)
127
+
128
+ workflow_sandbox_ids_so_far = {workflow.workflow_sandbox_id for workflow in merged_workflows}
129
+ for other_workflow in other_workflows:
130
+ if other_workflow.workflow_sandbox_id not in workflow_sandbox_ids_so_far:
131
+ merged_workflows.append(other_workflow)
132
+
133
+ return merged_workflows
134
+
135
+
91
136
  class VellumCliConfig(UniversalBaseModel):
92
137
  version: Literal["1.0"] = "1.0"
93
138
  workflows: List[WorkflowConfig] = field(default_factory=list)
@@ -97,24 +142,31 @@ class VellumCliConfig(UniversalBaseModel):
97
142
  lockfile_path = os.path.join(os.getcwd(), LOCKFILE_PATH)
98
143
  with open(lockfile_path, "w") as f:
99
144
  json.dump(self.model_dump(), f, indent=2, cls=DefaultStateEncoder)
145
+ # Makes sure the file ends with a newline, consistent with most autoformatters
146
+ f.write("\n")
100
147
 
101
148
  def merge(self, other: "VellumCliConfig") -> "VellumCliConfig":
102
149
  if other.version != self.version:
103
150
  raise ValueError("Lockfile version mismatch")
104
151
 
105
- self_workflow_by_module = {workflow.module: workflow for workflow in self.workflows}
106
- other_workflow_by_module = {workflow.module: workflow for workflow in other.workflows}
107
- all_modules = sorted(set(self_workflow_by_module.keys()).union(set(other_workflow_by_module.keys())))
152
+ self_workflows_by_module = self.get_workflows_by_module_mapping()
153
+ other_workflows_by_module = other.get_workflows_by_module_mapping()
154
+ all_modules = sorted(set(self_workflows_by_module.keys()).union(set(other_workflows_by_module.keys())))
108
155
  merged_workflows = []
109
156
  for module in all_modules:
110
- self_workflow = self_workflow_by_module.get(module)
111
- other_workflow = other_workflow_by_module.get(module)
112
- if self_workflow and other_workflow:
113
- merged_workflows.append(self_workflow.merge(other_workflow))
114
- elif self_workflow:
115
- merged_workflows.append(self_workflow)
116
- elif other_workflow:
117
- merged_workflows.append(other_workflow)
157
+ self_workflows = self_workflows_by_module.get(module)
158
+ other_workflows = other_workflows_by_module.get(module)
159
+ if self_workflows and other_workflows:
160
+ merged_workflows.extend(
161
+ merge_workflows_by_sandbox_id(
162
+ self_workflows,
163
+ other_workflows,
164
+ )
165
+ )
166
+ elif self_workflows:
167
+ merged_workflows.extend(self_workflows)
168
+ elif other_workflows:
169
+ merged_workflows.extend(other_workflows)
118
170
 
119
171
  self_workspace_by_name = {workspace.name: workspace for workspace in self.workspaces}
120
172
  other_workspace_by_name = {workspace.name: workspace for workspace in other.workspaces}
@@ -132,6 +184,12 @@ class VellumCliConfig(UniversalBaseModel):
132
184
 
133
185
  return VellumCliConfig(workflows=merged_workflows, workspaces=merged_workspaces, version=self.version)
134
186
 
187
+ def get_workflows_by_module_mapping(self) -> Dict[str, List[WorkflowConfig]]:
188
+ workflows_by_module = defaultdict(list)
189
+ for workflow in self.workflows:
190
+ workflows_by_module[workflow.module].append(workflow)
191
+ return workflows_by_module
192
+
135
193
 
136
194
  def load_vellum_cli_config(root_dir: Optional[str] = None) -> VellumCliConfig:
137
195
  if root_dir is None:
@@ -428,7 +428,7 @@ def test_pull__sandbox_id_with_other_workflow_deployment_in_lock(vellum_client,
428
428
  "workflows": [
429
429
  {
430
430
  "module": module,
431
- "workflow_sandbox_id": "0edc07cd-45b9-43e8-99bc-1f181972a857",
431
+ "workflow_sandbox_id": workflow_sandbox_id,
432
432
  "ignore": "tests/*",
433
433
  "deployments": [
434
434
  {
@@ -646,3 +646,44 @@ def test_pull__module_not_in_config(vellum_client, mock_module):
646
646
  "workspace": "default",
647
647
  }
648
648
  ]
649
+
650
+
651
+ def test_pull__multiple_instances_of_same_module__keep_when_pulling_another_module(vellum_client, mock_module):
652
+ # GIVEN a module on the user's filesystem
653
+ module = mock_module.module
654
+ temp_dir = mock_module.temp_dir
655
+ workflow_sandbox_id = mock_module.workflow_sandbox_id
656
+
657
+ # AND the vellum lock file has two instances of some other module
658
+ lock_data = {
659
+ "workflows": [
660
+ {
661
+ "module": "some_other_module",
662
+ "workflow_sandbox_id": str(uuid4()),
663
+ "workspace": "default",
664
+ },
665
+ {
666
+ "module": "some_other_module",
667
+ "workflow_sandbox_id": str(uuid4()),
668
+ "workspace": "other",
669
+ },
670
+ ]
671
+ }
672
+ lock_json = os.path.join(temp_dir, "vellum.lock.json")
673
+ with open(lock_json, "w") as f:
674
+ json.dump(lock_data, f)
675
+
676
+ # AND the workflow pull API call returns a zip file
677
+ vellum_client.workflows.pull.return_value = iter([_zip_file_map({"workflow.py": "print('hello')"})])
678
+
679
+ # WHEN the user runs the pull command on the new module
680
+ runner = CliRunner()
681
+ result = runner.invoke(cli_main, ["workflows", "pull", module, "--workflow-sandbox-id", workflow_sandbox_id])
682
+
683
+ # THEN the command returns successfully
684
+ assert result.exit_code == 0, (result.output, result.exception)
685
+
686
+ # AND the lockfile should have all three entries
687
+ with open(lock_json) as f:
688
+ lock_data = json.load(f)
689
+ assert len(lock_data["workflows"]) == 3
@@ -18,7 +18,6 @@ from .try_node import BaseTryNodeDisplay
18
18
 
19
19
  # All node display classes must be imported here to be registered in BaseNodeDisplay's node display registry
20
20
  __all__ = [
21
- "BaseRetryNodeDisplay",
22
21
  "BaseAPINodeDisplay",
23
22
  "BaseCodeExecutionNodeDisplay",
24
23
  "BaseConditionalNodeDisplay",
@@ -31,6 +30,7 @@ __all__ = [
31
30
  "BaseMergeNodeDisplay",
32
31
  "BaseNoteNodeDisplay",
33
32
  "BasePromptDeploymentNodeDisplay",
33
+ "BaseRetryNodeDisplay",
34
34
  "BaseSearchNodeDisplay",
35
35
  "BaseSubworkflowDeploymentNodeDisplay",
36
36
  "BaseTemplatingNodeDisplay",
@@ -1,5 +1,5 @@
1
1
  from uuid import UUID
2
- from typing import ClassVar, Dict, Generic, Optional, TypeVar
2
+ from typing import Generic, Optional, TypeVar
3
3
 
4
4
  from vellum.workflows.nodes import SubworkflowDeploymentNode
5
5
  from vellum.workflows.types.core import JsonObject
@@ -15,7 +15,6 @@ _SubworkflowDeploymentNodeType = TypeVar("_SubworkflowDeploymentNodeType", bound
15
15
  class BaseSubworkflowDeploymentNodeDisplay(
16
16
  BaseNodeVellumDisplay[_SubworkflowDeploymentNodeType], Generic[_SubworkflowDeploymentNodeType]
17
17
  ):
18
- subworkflow_input_ids_by_name: ClassVar[Dict[str, UUID]] = {}
19
18
 
20
19
  def serialize(
21
20
  self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **kwargs
@@ -30,7 +29,7 @@ class BaseSubworkflowDeploymentNodeDisplay(
30
29
  input_name=variable_name,
31
30
  value=variable_value,
32
31
  display_context=display_context,
33
- input_id=self.subworkflow_input_ids_by_name.get(variable_name),
32
+ input_id=self.node_input_ids_by_name.get(variable_name),
34
33
  )
35
34
  for variable_name, variable_value in subworkflow_inputs.items()
36
35
  ]
@@ -27,7 +27,7 @@ def create_node_input(
27
27
  input_name: str,
28
28
  value: Any,
29
29
  display_context: WorkflowDisplayContext,
30
- input_id: Union[Optional[UUID], Optional[str]],
30
+ input_id: Union[Optional[UUID], Optional[str]] = None,
31
31
  pointer_type: Optional[Type[NodeInputValuePointerRule]] = ConstantValuePointer,
32
32
  ) -> NodeInput:
33
33
  input_id = str(input_id) if input_id else str(uuid4_from_hash(f"{node_id}|{input_name}"))
@@ -80,6 +80,11 @@ def test_serialize_node__retry(serialize_node):
80
80
  "module": ["vellum", "workflows", "nodes", "core", "retry_node", "node"],
81
81
  },
82
82
  "attributes": [
83
+ {
84
+ "id": "8a07dc58-3fed-41d4-8ca6-31ee0bb86c61",
85
+ "name": "delay",
86
+ "value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
87
+ },
83
88
  {
84
89
  "id": "f388e93b-8c68-4f54-8577-bbd0c9091557",
85
90
  "name": "max_attempts",
@@ -210,11 +210,11 @@ def test_serialize_workflow(vellum_client):
210
210
  "target_handle_id": "06853542-e1a1-4a00-bd1e-4ac40f347b32",
211
211
  "output_id": "9a37bf7d-484e-4725-903e-f3254df38a0a",
212
212
  "output_type": "JSON",
213
- "node_input_id": "b544fc9a-2747-47e9-b28b-3e88d87b0f95",
213
+ "node_input_id": "b49f0f85-37fc-4686-81a7-287c06634661",
214
214
  },
215
215
  "inputs": [
216
216
  {
217
- "id": "b544fc9a-2747-47e9-b28b-3e88d87b0f95",
217
+ "id": "b49f0f85-37fc-4686-81a7-287c06634661",
218
218
  "key": "node_input",
219
219
  "value": {
220
220
  "rules": [
@@ -233,14 +233,7 @@ def test_serialize_workflow(vellum_client):
233
233
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
234
234
  "base": {
235
235
  "name": "FinalOutputNode",
236
- "module": [
237
- "vellum",
238
- "workflows",
239
- "nodes",
240
- "displayable",
241
- "final_output_node",
242
- "node",
243
- ],
236
+ "module": ["vellum", "workflows", "nodes", "displayable", "final_output_node", "node"],
244
237
  },
245
238
  "definition": None,
246
239
  },
@@ -253,11 +246,11 @@ def test_serialize_workflow(vellum_client):
253
246
  "target_handle_id": "80d0894f-642e-4d2e-b43a-f236e7bedb3c",
254
247
  "output_id": "5090e96d-5787-4a08-bf58-129101cf2548",
255
248
  "output_type": "JSON",
256
- "node_input_id": "6828b56e-80b7-4699-b9dd-fd1f0820732e",
249
+ "node_input_id": "5e892e5b-0004-4a04-bd2e-1ea9e0e5d3f9",
257
250
  },
258
251
  "inputs": [
259
252
  {
260
- "id": "6828b56e-80b7-4699-b9dd-fd1f0820732e",
253
+ "id": "5e892e5b-0004-4a04-bd2e-1ea9e0e5d3f9",
261
254
  "key": "node_input",
262
255
  "value": {
263
256
  "rules": [
@@ -276,14 +269,7 @@ def test_serialize_workflow(vellum_client):
276
269
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
277
270
  "base": {
278
271
  "name": "FinalOutputNode",
279
- "module": [
280
- "vellum",
281
- "workflows",
282
- "nodes",
283
- "displayable",
284
- "final_output_node",
285
- "node",
286
- ],
272
+ "module": ["vellum", "workflows", "nodes", "displayable", "final_output_node", "node"],
287
273
  },
288
274
  "definition": None,
289
275
  },
@@ -296,11 +282,11 @@ def test_serialize_workflow(vellum_client):
296
282
  "target_handle_id": "0c98c306-b519-40d7-8b05-321b1dfd7f11",
297
283
  "output_id": "44ea8d75-e2a8-4627-85b1-8504b65d25c9",
298
284
  "output_type": "NUMBER",
299
- "node_input_id": "500ff745-344f-4094-9425-48c4b40b7a5d",
285
+ "node_input_id": "14345321-7e6b-4e2a-918a-7a5b0064f047",
300
286
  },
301
287
  "inputs": [
302
288
  {
303
- "id": "500ff745-344f-4094-9425-48c4b40b7a5d",
289
+ "id": "14345321-7e6b-4e2a-918a-7a5b0064f047",
304
290
  "key": "node_input",
305
291
  "value": {
306
292
  "rules": [
@@ -319,14 +305,7 @@ def test_serialize_workflow(vellum_client):
319
305
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
320
306
  "base": {
321
307
  "name": "FinalOutputNode",
322
- "module": [
323
- "vellum",
324
- "workflows",
325
- "nodes",
326
- "displayable",
327
- "final_output_node",
328
- "node",
329
- ],
308
+ "module": ["vellum", "workflows", "nodes", "displayable", "final_output_node", "node"],
330
309
  },
331
310
  "definition": None,
332
311
  },
@@ -125,11 +125,11 @@ def test_serialize_workflow_with_filepath():
125
125
  "target_handle_id": "30fb0f4a-61c3-49de-a0aa-7dfdcee6ea07",
126
126
  "output_id": "f6a3e3e0-f83f-4491-8b7a-b20fddd7160c",
127
127
  "output_type": "NUMBER",
128
- "node_input_id": "ae302487-ff2a-457a-81ed-9e0348e91833",
128
+ "node_input_id": "529bdd20-0985-4c99-87dc-590907937c1d",
129
129
  },
130
130
  "inputs": [
131
131
  {
132
- "id": "ae302487-ff2a-457a-81ed-9e0348e91833",
132
+ "id": "529bdd20-0985-4c99-87dc-590907937c1d",
133
133
  "key": "node_input",
134
134
  "value": {
135
135
  "rules": [
@@ -161,11 +161,11 @@ def test_serialize_workflow_with_filepath():
161
161
  "target_handle_id": "1e126004-9de7-42c0-b1e1-87f9eb0642e2",
162
162
  "output_id": "1cee930f-342f-421c-89fc-ff212b3764bb",
163
163
  "output_type": "STRING",
164
- "node_input_id": "c6593516-ffc5-49a8-8a65-1038cccec3f8",
164
+ "node_input_id": "09501b65-d9b3-4920-81d4-96f93c840667",
165
165
  },
166
166
  "inputs": [
167
167
  {
168
- "id": "c6593516-ffc5-49a8-8a65-1038cccec3f8",
168
+ "id": "09501b65-d9b3-4920-81d4-96f93c840667",
169
169
  "key": "node_input",
170
170
  "value": {
171
171
  "rules": [
@@ -358,11 +358,11 @@ def test_serialize_workflow_with_code():
358
358
  "target_handle_id": "de8f2cc2-8c32-4782-87d5-4eb5afcd42e3",
359
359
  "output_id": "283d6849-f3ed-4beb-b261-cf70f90e8d10",
360
360
  "output_type": "NUMBER",
361
- "node_input_id": "b38ba7a8-0b2a-4146-8d58-9fa0bcba8cd5",
361
+ "node_input_id": "101d5222-8578-413a-be80-b1acf7559a0d",
362
362
  },
363
363
  "inputs": [
364
364
  {
365
- "id": "b38ba7a8-0b2a-4146-8d58-9fa0bcba8cd5",
365
+ "id": "101d5222-8578-413a-be80-b1acf7559a0d",
366
366
  "key": "node_input",
367
367
  "value": {
368
368
  "rules": [
@@ -394,11 +394,11 @@ def test_serialize_workflow_with_code():
394
394
  "target_handle_id": "6b7d7f2c-5cc8-4005-9e66-cdb2c97b1998",
395
395
  "output_id": "4c136180-050b-4422-a7a4-2a1c6729042c",
396
396
  "output_type": "STRING",
397
- "node_input_id": "76d49710-1ed0-4105-a1d7-9190c0408558",
397
+ "node_input_id": "de7ea5c4-da2f-4cfd-9192-0257d0f9ba53",
398
398
  },
399
399
  "inputs": [
400
400
  {
401
- "id": "76d49710-1ed0-4105-a1d7-9190c0408558",
401
+ "id": "de7ea5c4-da2f-4cfd-9192-0257d0f9ba53",
402
402
  "key": "node_input",
403
403
  "value": {
404
404
  "rules": [
@@ -601,29 +601,17 @@ def test_serialize_workflow__try_wrapped():
601
601
  {
602
602
  "id": "af4fc1ef-7701-43df-b5e7-4f354f707db2",
603
603
  "type": "TERMINAL",
604
- "base": {
605
- "module": [
606
- "vellum",
607
- "workflows",
608
- "nodes",
609
- "displayable",
610
- "final_output_node",
611
- "node",
612
- ],
613
- "name": "FinalOutputNode",
614
- },
615
- "definition": None,
616
604
  "data": {
617
605
  "label": "Final Output",
618
606
  "name": "log",
619
607
  "target_handle_id": "d243df8d-46f6-4928-ac31-7c775c5d73a9",
620
608
  "output_id": "5fbd27a0-9831-49c7-93c8-9c2a28c78696",
621
609
  "output_type": "STRING",
622
- "node_input_id": "569e1c05-53fa-413b-abf0-0353eaa44208",
610
+ "node_input_id": "a6ffaacf-1284-4e70-907e-042bb281e6fa",
623
611
  },
624
612
  "inputs": [
625
613
  {
626
- "id": "569e1c05-53fa-413b-abf0-0353eaa44208",
614
+ "id": "a6ffaacf-1284-4e70-907e-042bb281e6fa",
627
615
  "key": "node_input",
628
616
  "value": {
629
617
  "rules": [
@@ -640,33 +628,26 @@ def test_serialize_workflow__try_wrapped():
640
628
  }
641
629
  ],
642
630
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
643
- },
644
- {
645
- "id": "4cbfa5f7-fc12-4ab2-81cb-168c5caef4f0",
646
- "type": "TERMINAL",
647
631
  "base": {
648
- "module": [
649
- "vellum",
650
- "workflows",
651
- "nodes",
652
- "displayable",
653
- "final_output_node",
654
- "node",
655
- ],
656
632
  "name": "FinalOutputNode",
633
+ "module": ["vellum", "workflows", "nodes", "displayable", "final_output_node", "node"],
657
634
  },
658
635
  "definition": None,
636
+ },
637
+ {
638
+ "id": "4cbfa5f7-fc12-4ab2-81cb-168c5caef4f0",
639
+ "type": "TERMINAL",
659
640
  "data": {
660
641
  "label": "Final Output",
661
642
  "name": "result",
662
643
  "target_handle_id": "9c43709e-25cb-4548-b840-3fcf6a1c9f3e",
663
644
  "output_id": "400f9ffe-e700-4204-a810-e06123565947",
664
645
  "output_type": "NUMBER",
665
- "node_input_id": "3d6cb7ef-985f-48f1-ad23-de49be60666a",
646
+ "node_input_id": "2cbb7351-a3e8-4a6b-b33e-204b04da7ad6",
666
647
  },
667
648
  "inputs": [
668
649
  {
669
- "id": "3d6cb7ef-985f-48f1-ad23-de49be60666a",
650
+ "id": "2cbb7351-a3e8-4a6b-b33e-204b04da7ad6",
670
651
  "key": "node_input",
671
652
  "value": {
672
653
  "rules": [
@@ -683,6 +664,11 @@ def test_serialize_workflow__try_wrapped():
683
664
  }
684
665
  ],
685
666
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
667
+ "base": {
668
+ "name": "FinalOutputNode",
669
+ "module": ["vellum", "workflows", "nodes", "displayable", "final_output_node", "node"],
670
+ },
671
+ "definition": None,
686
672
  },
687
673
  ],
688
674
  sorted(final_output_nodes, key=lambda x: x["id"], reverse=True),