vellum-ai 1.1.4__py3-none-any.whl → 1.2.0__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 (32) hide show
  1. vellum/client/core/client_wrapper.py +2 -2
  2. vellum/workflows/emitters/vellum_emitter.py +5 -0
  3. vellum/workflows/sandbox.py +22 -5
  4. vellum/workflows/state/context.py +8 -52
  5. vellum/workflows/workflows/base.py +1 -1
  6. {vellum_ai-1.1.4.dist-info → vellum_ai-1.2.0.dist-info}/METADATA +1 -1
  7. {vellum_ai-1.1.4.dist-info → vellum_ai-1.2.0.dist-info}/RECORD +32 -32
  8. vellum_ee/workflows/display/nodes/base_node_display.py +5 -5
  9. vellum_ee/workflows/display/nodes/tests/test_base_node_display.py +1 -1
  10. vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +4 -0
  11. vellum_ee/workflows/display/nodes/vellum/retry_node.py +1 -1
  12. vellum_ee/workflows/display/nodes/vellum/tests/test_retry_node.py +1 -1
  13. vellum_ee/workflows/display/nodes/vellum/tests/test_tool_calling_node.py +75 -1
  14. vellum_ee/workflows/display/nodes/vellum/try_node.py +1 -1
  15. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +9 -9
  16. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attributes_serialization.py +59 -9
  17. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_outputs_serialization.py +3 -3
  18. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_ports_serialization.py +14 -15
  19. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_trigger_serialization.py +58 -3
  20. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +1 -1
  21. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_prompt_node_serialization.py +4 -0
  22. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +1 -1
  23. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_serialization.py +6 -3
  24. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py +5 -2
  25. vellum_ee/workflows/display/types.py +13 -2
  26. vellum_ee/workflows/display/utils/expressions.py +5 -1
  27. vellum_ee/workflows/display/workflows/base_workflow_display.py +35 -0
  28. vellum_ee/workflows/display/workflows/tests/test_workflow_display.py +44 -2
  29. vellum_ee/workflows/tests/test_serialize_module.py +31 -0
  30. {vellum_ai-1.1.4.dist-info → vellum_ai-1.2.0.dist-info}/LICENSE +0 -0
  31. {vellum_ai-1.1.4.dist-info → vellum_ai-1.2.0.dist-info}/WHEEL +0 -0
  32. {vellum_ai-1.1.4.dist-info → vellum_ai-1.2.0.dist-info}/entry_points.txt +0 -0
@@ -34,7 +34,7 @@ def test_serialize_node__constant_value(serialize_node):
34
34
  assert not DeepDiff(
35
35
  {
36
36
  "id": "67e07859-7f67-4287-9854-06ab4199e576",
37
- "label": "test_serialize_node__constant_value.<locals>.ConstantValueGenericNode",
37
+ "label": "Constant Value Generic Node",
38
38
  "type": "GENERIC",
39
39
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
40
40
  "base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
@@ -104,7 +104,7 @@ def test_serialize_node__constant_value_reference(serialize_node):
104
104
  assert not DeepDiff(
105
105
  {
106
106
  "id": "73643f17-e49e-47d2-bd01-bb9c3eab6ae9",
107
- "label": "test_serialize_node__constant_value_reference.<locals>.ConstantValueReferenceGenericNode",
107
+ "label": "Constant Value Reference Generic Node",
108
108
  "type": "GENERIC",
109
109
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
110
110
  "base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
@@ -146,7 +146,7 @@ def test_serialize_node__lazy_reference(serialize_node):
146
146
  assert not DeepDiff(
147
147
  {
148
148
  "id": "3d6bfe3b-263a-40a6-8a05-98288e9559a4",
149
- "label": "test_serialize_node__lazy_reference.<locals>.LazyReferenceGenericNode",
149
+ "label": "Lazy Reference Generic Node",
150
150
  "type": "GENERIC",
151
151
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
152
152
  "base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
@@ -229,7 +229,7 @@ def test_serialize_node__workflow_input(serialize_node):
229
229
  assert not DeepDiff(
230
230
  {
231
231
  "id": "30116483-6f38-40e0-baf2-32de0e14e9a3",
232
- "label": "test_serialize_node__workflow_input.<locals>.WorkflowInputGenericNode",
232
+ "label": "Workflow Input Generic Node",
233
233
  "type": "GENERIC",
234
234
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
235
235
  "base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
@@ -292,7 +292,7 @@ def test_serialize_node__workflow_input_as_nested_chat_history():
292
292
  assert not DeepDiff(
293
293
  {
294
294
  "id": "11be9d37-0069-4695-a317-14a3b6519d4e",
295
- "label": "test_serialize_node__workflow_input_as_nested_chat_history.<locals>.GenericNode",
295
+ "label": "Generic Node",
296
296
  "type": "GENERIC",
297
297
  "display_data": {"position": {"x": 200.0, "y": -50.0}},
298
298
  "base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
@@ -362,7 +362,7 @@ def test_serialize_node__node_output(serialize_node):
362
362
  assert not DeepDiff(
363
363
  {
364
364
  "id": "7210742f-8c3e-4379-9800-8b4b7f5dd7ed",
365
- "label": "test_serialize_node__node_output.<locals>.GenericNodeReferencingOutput",
365
+ "label": "Generic Node Referencing Output",
366
366
  "type": "GENERIC",
367
367
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
368
368
  "base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
@@ -412,7 +412,7 @@ def test_serialize_node__vellum_secret(serialize_node):
412
412
  assert not DeepDiff(
413
413
  {
414
414
  "id": "0e75bd8f-882e-4ab7-8348-061319b574f7",
415
- "label": "test_serialize_node__vellum_secret.<locals>.VellumSecretGenericNode",
415
+ "label": "Vellum Secret Generic Node",
416
416
  "type": "GENERIC",
417
417
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
418
418
  "base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
@@ -465,7 +465,7 @@ def test_serialize_node__node_execution(serialize_node):
465
465
  assert not DeepDiff(
466
466
  {
467
467
  "id": "f42dda6b-e856-49bd-b203-46c9dd66c08b",
468
- "label": "test_serialize_node__node_execution.<locals>.GenericNodeReferencingExecutions",
468
+ "label": "Generic Node Referencing Executions",
469
469
  "type": "GENERIC",
470
470
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
471
471
  "base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
@@ -561,7 +561,7 @@ def test_serialize_node__coalesce(serialize_node):
561
561
  assert not DeepDiff(
562
562
  {
563
563
  "id": "bb99f326-7d2a-4b5e-95f3-6039114798da",
564
- "label": "test_serialize_node__coalesce.<locals>.CoalesceNodeFinal",
564
+ "label": "Coalesce Node Final",
565
565
  "type": "GENERIC",
566
566
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
567
567
  "base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
@@ -670,3 +670,53 @@ def test_serialize_node__pydantic_with_node_output_reference(serialize_node):
670
670
  assert any(
671
671
  entry["key"] == "node_ref" and entry["value"]["type"] == "NODE_OUTPUT" for entry in attr_value["entries"]
672
672
  )
673
+
674
+
675
+ def test_serialize_node__comment_expanded_true_when_content_exists(serialize_node):
676
+ """
677
+ Tests that node comment serialization sets expanded=True when comment has content.
678
+ """
679
+
680
+ class NodeWithComment(BaseNode):
681
+ """This is a test comment for the node."""
682
+
683
+ pass
684
+
685
+ serialized_node = serialize_node(NodeWithComment)
686
+
687
+ # WHEN the node is serialized
688
+ display_data = serialized_node["display_data"]
689
+
690
+ # THEN the comment should have expanded=True
691
+ assert "comment" in display_data
692
+ assert display_data["comment"]["value"] == "This is a test comment for the node."
693
+ assert display_data["comment"]["expanded"] is True
694
+
695
+
696
+ def test_serialize_node__comment_expanded_preserved_when_explicitly_set(serialize_node):
697
+ """
698
+ Tests that explicitly set expanded value is preserved during serialization.
699
+ """
700
+ from vellum_ee.workflows.display.editor.types import NodeDisplayComment, NodeDisplayData
701
+ from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
702
+
703
+ class NodeWithExplicitComment(BaseNode):
704
+ """This is a test comment."""
705
+
706
+ pass
707
+
708
+ class NodeWithExplicitCommentDisplay(BaseNodeDisplay[NodeWithExplicitComment]):
709
+ display_data = NodeDisplayData(comment=NodeDisplayComment(value="Custom comment", expanded=False))
710
+
711
+ serialized_node = serialize_node(
712
+ NodeWithExplicitComment,
713
+ global_node_displays={NodeWithExplicitComment: NodeWithExplicitCommentDisplay()},
714
+ )
715
+
716
+ # WHEN the node is serialized
717
+ display_data = serialized_node["display_data"]
718
+
719
+ # THEN the comment should preserve expanded=False
720
+ assert "comment" in display_data
721
+ assert display_data["comment"]["value"] == "This is a test comment."
722
+ assert display_data["comment"]["expanded"] is False
@@ -23,7 +23,7 @@ def test_serialize_node__annotated_output(serialize_node):
23
23
  assert not DeepDiff(
24
24
  {
25
25
  "id": "e33ddf79-f48c-4057-ba17-d41a3a60ac98",
26
- "label": "test_serialize_node__annotated_output.<locals>.AnnotatedOutputGenericNode",
26
+ "label": "Annotated Output Generic Node",
27
27
  "type": "GENERIC",
28
28
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
29
29
  "base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
@@ -71,7 +71,7 @@ def test_serialize_node__workflow_input(serialize_node):
71
71
  assert not DeepDiff(
72
72
  {
73
73
  "id": "30116483-6f38-40e0-baf2-32de0e14e9a3",
74
- "label": "test_serialize_node__workflow_input.<locals>.WorkflowInputGenericNode",
74
+ "label": "Workflow Input Generic Node",
75
75
  "type": "GENERIC",
76
76
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
77
77
  "base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
@@ -134,7 +134,7 @@ def test_serialize_node__node_output_reference(serialize_node):
134
134
  assert not DeepDiff(
135
135
  {
136
136
  "id": "ac067acc-6a6f-44b1-ae84-428e965ce691",
137
- "label": "test_serialize_node__node_output_reference.<locals>.GenericNodeReferencingOutput",
137
+ "label": "Generic Node Referencing Output",
138
138
  "type": "GENERIC",
139
139
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
140
140
  "base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
@@ -20,11 +20,10 @@ def test_serialize_node__basic(serialize_node):
20
20
  pass
21
21
 
22
22
  serialized_node = serialize_node(BasicGenericNode)
23
-
24
23
  assert not DeepDiff(
25
24
  {
26
25
  "id": "8d7cbfe4-72ca-4367-a401-8d28723d2f00",
27
- "label": "test_serialize_node__basic.<locals>.BasicGenericNode",
26
+ "label": "Basic Generic Node",
28
27
  "type": "GENERIC",
29
28
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
30
29
  "base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
@@ -70,7 +69,7 @@ def test_serialize_node__if(serialize_node):
70
69
  assert not DeepDiff(
71
70
  {
72
71
  "id": "bba4b15a-dea0-48c9-a79b-4e12e99db00f",
73
- "label": "test_serialize_node__if.<locals>.IfGenericNode",
72
+ "label": "If Generic Node",
74
73
  "type": "GENERIC",
75
74
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
76
75
  "base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
@@ -132,7 +131,7 @@ def test_serialize_node__if_else(serialize_node):
132
131
  assert not DeepDiff(
133
132
  {
134
133
  "id": "25c9c3f1-4014-47ac-90cf-5216de10d05c",
135
- "label": "test_serialize_node__if_else.<locals>.IfElseGenericNode",
134
+ "label": "If Else Generic Node",
136
135
  "type": "GENERIC",
137
136
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
138
137
  "base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
@@ -201,7 +200,7 @@ def test_serialize_node__if_elif_else(serialize_node):
201
200
  assert not DeepDiff(
202
201
  {
203
202
  "id": "7b2b9cfc-12aa-432c-940d-cbe53e71de9c",
204
- "label": "test_serialize_node__if_elif_else.<locals>.IfElifElseGenericNode",
203
+ "label": "If Elif Else Generic Node",
205
204
  "type": "GENERIC",
206
205
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
207
206
  "base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
@@ -300,7 +299,7 @@ def test_serialize_node__node_output_reference(serialize_node):
300
299
  assert not DeepDiff(
301
300
  {
302
301
  "id": "ac067acc-6a6f-44b1-ae84-428e965ce691",
303
- "label": "test_serialize_node__node_output_reference.<locals>.GenericNodeReferencingOutput",
302
+ "label": "Generic Node Referencing Output",
304
303
  "type": "GENERIC",
305
304
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
306
305
  "definition": {
@@ -363,7 +362,7 @@ def test_serialize_node__vellum_secret_reference(serialize_node):
363
362
  assert not DeepDiff(
364
363
  {
365
364
  "id": "feb4b331-e25f-4a5c-9840-c5575b1efd5c",
366
- "label": "test_serialize_node__vellum_secret_reference.<locals>.GenericNodeReferencingSecret",
365
+ "label": "Generic Node Referencing Secret",
367
366
  "type": "GENERIC",
368
367
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
369
368
  "definition": {
@@ -429,7 +428,7 @@ def test_serialize_node__execution_count_reference(serialize_node):
429
428
  assert not DeepDiff(
430
429
  {
431
430
  "id": "0b4fe8a6-6d0c-464e-9372-10110e2b0e13",
432
- "label": "test_serialize_node__execution_count_reference.<locals>.GenericNodeReferencingExecutions",
431
+ "label": "Generic Node Referencing Executions",
433
432
  "type": "GENERIC",
434
433
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
435
434
  "definition": {
@@ -490,7 +489,7 @@ def test_serialize_node__null(serialize_node):
490
489
  assert not DeepDiff(
491
490
  {
492
491
  "id": "1838ce1f-9c07-4fd0-9fd4-2a3a841ea402",
493
- "label": "test_serialize_node__null.<locals>.NullGenericNode",
492
+ "label": "Null Generic Node",
494
493
  "type": "GENERIC",
495
494
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
496
495
  "base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
@@ -548,7 +547,7 @@ def test_serialize_node__between(serialize_node):
548
547
  assert not DeepDiff(
549
548
  {
550
549
  "id": "f2f5a1f2-a12d-4ce0-bfe9-42190ffe5328",
551
- "label": "test_serialize_node__between.<locals>.BetweenGenericNode",
550
+ "label": "Between Generic Node",
552
551
  "type": "GENERIC",
553
552
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
554
553
  "base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
@@ -617,7 +616,7 @@ def test_serialize_node__or(serialize_node):
617
616
  assert not DeepDiff(
618
617
  {
619
618
  "id": "5386abad-3378-4378-b3a8-831b4b77dc23",
620
- "label": "test_serialize_node__or.<locals>.OrGenericNode",
619
+ "label": "Or Generic Node",
621
620
  "type": "GENERIC",
622
621
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
623
622
  "base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
@@ -700,7 +699,7 @@ def test_serialize_node__and_then_or(serialize_node):
700
699
  assert not DeepDiff(
701
700
  {
702
701
  "id": "4d3995b1-437b-48d9-8878-9f57a8b725f1",
703
- "label": "test_serialize_node__and_then_or.<locals>.AndThenOrGenericNode",
702
+ "label": "And Then Or Generic Node",
704
703
  "type": "GENERIC",
705
704
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
706
705
  "base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
@@ -802,7 +801,7 @@ def test_serialize_node__parenthesized_and_then_or(serialize_node):
802
801
  assert not DeepDiff(
803
802
  {
804
803
  "id": "223864c9-0088-4c05-9b7d-e5b1c9ec936d",
805
- "label": "test_serialize_node__parenthesized_and_then_or.<locals>.ParenthesizedAndThenOrGenericNode",
804
+ "label": "Parenthesized And Then Or Generic Node",
806
805
  "type": "GENERIC",
807
806
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
808
807
  "base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
@@ -904,7 +903,7 @@ def test_serialize_node__or_then_and(serialize_node):
904
903
  assert not DeepDiff(
905
904
  {
906
905
  "id": "a946342e-4ede-4e96-8e3d-f396748d9f7c",
907
- "label": "test_serialize_node__or_then_and.<locals>.OrThenAndGenericNode",
906
+ "label": "Or Then And Generic Node",
908
907
  "type": "GENERIC",
909
908
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
910
909
  "base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
@@ -1005,7 +1004,7 @@ def test_serialize_node__parse_json(serialize_node):
1005
1004
  assert not DeepDiff(
1006
1005
  {
1007
1006
  "id": "bfc3f81b-242a-4f43-9e1c-648223d77768",
1008
- "label": "test_serialize_node__parse_json.<locals>.ParseJsonGenericNode",
1007
+ "label": "Parse Json Generic Node",
1009
1008
  "type": "GENERIC",
1010
1009
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
1011
1010
  "base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
@@ -2,7 +2,11 @@ from deepdiff import DeepDiff
2
2
 
3
3
  from vellum.workflows.inputs.base import BaseInputs
4
4
  from vellum.workflows.nodes.bases.base import BaseNode
5
+ from vellum.workflows.nodes.displayable.inline_prompt_node.node import InlinePromptNode
6
+ from vellum.workflows.state.base import BaseState
5
7
  from vellum.workflows.types.core import MergeBehavior
8
+ from vellum.workflows.workflows.base import BaseWorkflow
9
+ from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
6
10
 
7
11
 
8
12
  class Inputs(BaseInputs):
@@ -17,7 +21,7 @@ def test_serialize_node__basic(serialize_node):
17
21
  assert not DeepDiff(
18
22
  {
19
23
  "id": "8d7cbfe4-72ca-4367-a401-8d28723d2f00",
20
- "label": "test_serialize_node__basic.<locals>.BasicGenericNode",
24
+ "label": "Basic Generic Node",
21
25
  "type": "GENERIC",
22
26
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
23
27
  "base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
@@ -59,7 +63,7 @@ def test_serialize_node__await_any(serialize_node):
59
63
  assert not DeepDiff(
60
64
  {
61
65
  "id": "42e17f0e-8496-415f-9c72-f85250ba6f0b",
62
- "label": "test_serialize_node__await_any.<locals>.AwaitAnyGenericNode",
66
+ "label": "Await Any Generic Node",
63
67
  "type": "GENERIC",
64
68
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
65
69
  "base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
@@ -101,7 +105,7 @@ def test_serialize_node__await_all(serialize_node):
101
105
  assert not DeepDiff(
102
106
  {
103
107
  "id": "b3e1145a-5f41-456b-9382-6d0a1e828c2f",
104
- "label": "test_serialize_node__await_all.<locals>.AwaitAllGenericNode",
108
+ "label": "Await All Generic Node",
105
109
  "type": "GENERIC",
106
110
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
107
111
  "base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
@@ -132,3 +136,54 @@ def test_serialize_node__await_all(serialize_node):
132
136
  serialized_node,
133
137
  ignore_order=True,
134
138
  )
139
+
140
+
141
+ def test_serialize_node__inline_prompt_await_all():
142
+ """
143
+ Tests that InlinePromptNode with AWAIT_ALL merge behavior can be defined and serializes without errors.
144
+ """
145
+
146
+ # GIVEN an InlinePromptNode with AWAIT_ALL merge behavior
147
+ class AwaitAllInlinePromptNode(InlinePromptNode):
148
+ ml_model = "gpt-4o"
149
+ blocks = []
150
+
151
+ class Trigger(InlinePromptNode.Trigger):
152
+ merge_behavior = MergeBehavior.AWAIT_ALL
153
+
154
+ class TestWorkflow(BaseWorkflow[Inputs, BaseState]):
155
+ graph = AwaitAllInlinePromptNode
156
+
157
+ # WHEN we serialize the workflow containing the node
158
+ workflow_display = get_workflow_display(workflow_class=TestWorkflow)
159
+ serialized = workflow_display.serialize()
160
+
161
+ # THEN the workflow should serialize successfully
162
+ assert "workflow_raw_data" in serialized # type: ignore
163
+ assert "nodes" in serialized["workflow_raw_data"] # type: ignore
164
+
165
+ # AND the workflow should contain the InlinePromptNode
166
+ nodes = serialized["workflow_raw_data"]["nodes"] # type: ignore
167
+ prompt_nodes = [node for node in nodes if node["type"] == "PROMPT"] # type: ignore
168
+ assert len(prompt_nodes) == 1
169
+
170
+ prompt_node = prompt_nodes[0]
171
+
172
+ # AND the node should have the correct type and base
173
+ assert prompt_node["type"] == "PROMPT" # type: ignore
174
+ assert prompt_node["base"]["name"] == "InlinePromptNode" # type: ignore
175
+ assert prompt_node["base"]["module"] == [ # type: ignore
176
+ "vellum",
177
+ "workflows",
178
+ "nodes",
179
+ "displayable",
180
+ "inline_prompt_node",
181
+ "node",
182
+ ]
183
+
184
+ # AND the node should have the expected structure (InlinePromptNode doesn't serialize trigger info)
185
+ assert "data" in prompt_node # type: ignore
186
+ assert "ml_model_name" in prompt_node["data"] # type: ignore
187
+ assert prompt_node["data"]["ml_model_name"] == "gpt-4o" # type: ignore
188
+
189
+ assert prompt_node["trigger"]["merge_behavior"] == "AWAIT_ALL" # type: ignore
@@ -591,7 +591,7 @@ def test_serialize_workflow__try_wrapped():
591
591
  "adornments": [
592
592
  {
593
593
  "id": "3344083c-a32c-4a32-920b-0fb5093448fa",
594
- "label": "TryNode",
594
+ "label": "Try Node",
595
595
  "base": {"name": "TryNode", "module": ["vellum", "workflows", "nodes", "core", "try_node", "node"]},
596
596
  "attributes": [
597
597
  {
@@ -165,6 +165,10 @@ def test_serialize_workflow():
165
165
  "name": "ExampleBaseInlinePromptNodeWithFunctions",
166
166
  "module": ["tests", "workflows", "basic_inline_prompt_node_with_functions", "workflow"],
167
167
  },
168
+ "trigger": {
169
+ "id": "c2dccecb-8a41-40a8-95af-325d3ab8bfe5",
170
+ "merge_behavior": "AWAIT_ANY",
171
+ },
168
172
  "outputs": [
169
173
  {"id": "9557bd86-702d-4b45-b8c1-c3980bffe28f", "name": "json", "type": "JSON", "value": None},
170
174
  {"id": "ead0ccb5-092f-4d9b-a9ec-5eb83d498188", "name": "text", "type": "STRING", "value": None},
@@ -129,7 +129,7 @@ def test_serialize_workflow():
129
129
  },
130
130
  {
131
131
  "id": "1381c078-efa2-4255-89a1-7b4cb742c7fc",
132
- "label": "StartNode",
132
+ "label": "Start Node",
133
133
  "type": "GENERIC",
134
134
  "display_data": {"position": {"x": 200.0, "y": -50.0}},
135
135
  "base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
@@ -40,11 +40,14 @@ def test_serialize_workflow():
40
40
  tool_calling_node = workflow_raw_data["nodes"][1]
41
41
  assert tool_calling_node == {
42
42
  "id": "21f29cac-da87-495f-bba1-093d423f4e46",
43
- "label": "GetCurrentWeatherNode",
43
+ "label": "Get Current Weather Node",
44
44
  "type": "GENERIC",
45
45
  "display_data": {
46
46
  "position": {"x": 200.0, "y": -50.0},
47
- "comment": {"value": "\n A tool calling node that calls the get_current_weather function.\n "},
47
+ "comment": {
48
+ "expanded": True,
49
+ "value": "\n A tool calling node that calls the get_current_weather function.\n ",
50
+ },
48
51
  },
49
52
  "base": {
50
53
  "name": "ToolCallingNode",
@@ -150,7 +153,7 @@ def test_serialize_workflow():
150
153
  },
151
154
  {
152
155
  "id": "1381c078-efa2-4255-89a1-7b4cb742c7fc",
153
- "label": "StartNode",
156
+ "label": "Start Node",
154
157
  "type": "GENERIC",
155
158
  "display_data": {"position": {"x": 200.0, "y": -50.0}},
156
159
  "base": {
@@ -40,11 +40,14 @@ def test_serialize_workflow():
40
40
  tool_calling_node = workflow_raw_data["nodes"][1]
41
41
  assert tool_calling_node == {
42
42
  "id": "21f29cac-da87-495f-bba1-093d423f4e46",
43
- "label": "GetCurrentWeatherNode",
43
+ "label": "Get Current Weather Node",
44
44
  "type": "GENERIC",
45
45
  "display_data": {
46
46
  "position": {"x": 200.0, "y": -50.0},
47
- "comment": {"value": "\n A tool calling node that calls the get_current_weather function.\n "},
47
+ "comment": {
48
+ "expanded": True,
49
+ "value": "\n A tool calling node that calls the get_current_weather function.\n ",
50
+ },
48
51
  },
49
52
  "base": {
50
53
  "name": "ToolCallingNode",
@@ -1,5 +1,5 @@
1
1
  from dataclasses import dataclass, field
2
- from typing import TYPE_CHECKING, Dict, Iterator, List, Tuple, Type
2
+ from typing import TYPE_CHECKING, Dict, Iterator, List, Optional, Tuple, Type
3
3
 
4
4
  from vellum.client import Vellum as VellumClient
5
5
  from vellum.workflows.descriptors.base import BaseDescriptor
@@ -52,15 +52,26 @@ class WorkflowDisplayContext:
52
52
  edge_displays: EdgeDisplays = field(default_factory=dict)
53
53
  port_displays: PortDisplays = field(default_factory=dict)
54
54
  _errors: List[Exception] = field(default_factory=list)
55
+ _invalid_nodes: List[Type[BaseNode]] = field(default_factory=list)
55
56
  _dry_run: bool = False
56
57
 
57
- def add_error(self, error: Exception) -> None:
58
+ def add_error(self, error: Exception, node: Optional[Type[BaseNode]] = None) -> None:
58
59
  if self._dry_run:
59
60
  self._errors.append(error)
60
61
  return
61
62
 
62
63
  raise error
63
64
 
65
+ def add_invalid_node(self, node: Type[BaseNode]) -> None:
66
+ """Track a node that failed to serialize."""
67
+ if node not in self._invalid_nodes:
68
+ self._invalid_nodes.append(node)
69
+
64
70
  @property
65
71
  def errors(self) -> Iterator[Exception]:
66
72
  return iter(self._errors)
73
+
74
+ @property
75
+ def invalid_nodes(self) -> Iterator[Type[BaseNode]]:
76
+ """Get an iterator over nodes that failed to serialize."""
77
+ return iter(self._invalid_nodes)
@@ -349,7 +349,11 @@ def serialize_value(display_context: "WorkflowDisplayContext", value: Any) -> Js
349
349
  if is_workflow_class(value):
350
350
  from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
351
351
 
352
- workflow_display = get_workflow_display(workflow_class=value)
352
+ # Pass the parent display context so the subworkflow can resolve parent workflow inputs
353
+ workflow_display = get_workflow_display(
354
+ workflow_class=value,
355
+ parent_display_context=display_context,
356
+ )
353
357
  serialized_value: dict = workflow_display.serialize()
354
358
  name = serialized_value["workflow_raw_data"]["definition"]["name"]
355
359
  description = value.__doc__ or ""
@@ -3,6 +3,7 @@ import fnmatch
3
3
  from functools import cached_property
4
4
  import importlib
5
5
  import inspect
6
+ import json
6
7
  import logging
7
8
  import os
8
9
  from uuid import UUID
@@ -15,12 +16,14 @@ from vellum.workflows.constants import undefined
15
16
  from vellum.workflows.descriptors.base import BaseDescriptor
16
17
  from vellum.workflows.edges import Edge
17
18
  from vellum.workflows.events.workflow import NodeEventDisplayContext, WorkflowEventDisplayContext
19
+ from vellum.workflows.inputs.base import BaseInputs
18
20
  from vellum.workflows.nodes.bases import BaseNode
19
21
  from vellum.workflows.nodes.displayable.bases.utils import primitive_to_vellum_value
20
22
  from vellum.workflows.nodes.displayable.final_output_node.node import FinalOutputNode
21
23
  from vellum.workflows.nodes.utils import get_unadorned_node, get_unadorned_port, get_wrapped_node
22
24
  from vellum.workflows.ports import Port
23
25
  from vellum.workflows.references import OutputReference, WorkflowInputReference
26
+ from vellum.workflows.state.encoder import DefaultStateEncoder
24
27
  from vellum.workflows.types.core import Json, JsonArray, JsonObject
25
28
  from vellum.workflows.types.generics import WorkflowType
26
29
  from vellum.workflows.types.utils import get_original_base
@@ -72,6 +75,7 @@ IGNORE_PATTERNS = [
72
75
  class WorkflowSerializationResult(UniversalBaseModel):
73
76
  exec_config: Dict[str, Any]
74
77
  errors: List[str]
78
+ dataset: Optional[List[Dict[str, Any]]] = None
75
79
 
76
80
 
77
81
  class BaseWorkflowDisplay(Generic[WorkflowType]):
@@ -194,6 +198,7 @@ class BaseWorkflowDisplay(Generic[WorkflowType]):
194
198
  serialized_node = node_display.serialize(self.display_context)
195
199
  except (NotImplementedError, NodeValidationError) as e:
196
200
  self.display_context.add_error(e)
201
+ self.display_context.add_invalid_node(node)
197
202
  continue
198
203
 
199
204
  serialized_nodes[node_display.node_id] = serialized_node
@@ -319,6 +324,10 @@ class BaseWorkflowDisplay(Generic[WorkflowType]):
319
324
  # Add an edge for each edge in the workflow
320
325
  for target_node, entrypoint_display in self.display_context.entrypoint_displays.items():
321
326
  unadorned_target_node = get_unadorned_node(target_node)
327
+ # Skip edges to invalid nodes
328
+ if self._is_node_invalid(unadorned_target_node):
329
+ continue
330
+
322
331
  target_node_display = self.display_context.node_displays[unadorned_target_node]
323
332
  edges.append(
324
333
  {
@@ -335,6 +344,12 @@ class BaseWorkflowDisplay(Generic[WorkflowType]):
335
344
  unadorned_source_node_port = get_unadorned_port(source_node_port)
336
345
  unadorned_target_node = get_unadorned_node(target_node)
337
346
 
347
+ # Skip edges that reference invalid nodes
348
+ if self._is_node_invalid(unadorned_target_node) or self._is_node_invalid(
349
+ unadorned_source_node_port.node_class
350
+ ):
351
+ continue
352
+
338
353
  source_node_port_display = self.display_context.port_displays[unadorned_source_node_port]
339
354
  target_node_display = self.display_context.node_displays[unadorned_target_node]
340
355
 
@@ -881,9 +896,25 @@ class BaseWorkflowDisplay(Generic[WorkflowType]):
881
896
  if additional_files:
882
897
  exec_config["module_data"] = {"additional_files": cast(JsonObject, additional_files)}
883
898
 
899
+ dataset = None
900
+ try:
901
+ sandbox_module_path = f"{module}.sandbox"
902
+ sandbox_module = importlib.import_module(sandbox_module_path)
903
+ if hasattr(sandbox_module, "dataset"):
904
+ dataset_attr = getattr(sandbox_module, "dataset")
905
+ if dataset_attr and isinstance(dataset_attr, list):
906
+ dataset = []
907
+ for i, inputs_obj in enumerate(dataset_attr):
908
+ if isinstance(inputs_obj, BaseInputs):
909
+ serialized_inputs = json.loads(json.dumps(inputs_obj, cls=DefaultStateEncoder))
910
+ dataset.append({"label": f"Scenario {i + 1}", "inputs": serialized_inputs})
911
+ except (ImportError, AttributeError):
912
+ pass
913
+
884
914
  return WorkflowSerializationResult(
885
915
  exec_config=exec_config,
886
916
  errors=[str(error) for error in workflow_display.display_context.errors],
917
+ dataset=dataset,
887
918
  )
888
919
 
889
920
  def _gather_additional_module_files(self, module_path: str) -> Dict[str, str]:
@@ -939,5 +970,9 @@ class BaseWorkflowDisplay(Generic[WorkflowType]):
939
970
  is_required = not has_default and not is_optional
940
971
  return is_required
941
972
 
973
+ def _is_node_invalid(self, node: Type[BaseNode]) -> bool:
974
+ """Check if a node failed to serialize and should be considered invalid."""
975
+ return node in self.display_context.invalid_nodes
976
+
942
977
 
943
978
  register_workflow_display_class(workflow_class=BaseWorkflow, workflow_display_class=BaseWorkflowDisplay)