vellum-ai 0.12.15__py3-none-any.whl → 0.12.16__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.
- vellum/client/core/client_wrapper.py +1 -1
- vellum/prompts/blocks/compilation.py +43 -0
- vellum/workflows/nodes/core/inline_subworkflow_node/node.py +10 -2
- vellum/workflows/nodes/core/inline_subworkflow_node/tests/test_node.py +16 -0
- {vellum_ai-0.12.15.dist-info → vellum_ai-0.12.16.dist-info}/METADATA +10 -8
- {vellum_ai-0.12.15.dist-info → vellum_ai-0.12.16.dist-info}/RECORD +39 -36
- vellum_cli/__init__.py +14 -0
- vellum_cli/push.py +26 -4
- vellum_cli/tests/test_push.py +63 -0
- vellum_ee/workflows/display/nodes/vellum/api_node.py +3 -3
- vellum_ee/workflows/display/nodes/vellum/base_node.py +17 -0
- vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +2 -2
- vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +2 -2
- vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +20 -6
- vellum_ee/workflows/display/nodes/vellum/map_node.py +1 -0
- vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py +2 -2
- vellum_ee/workflows/display/nodes/vellum/search_node.py +4 -2
- vellum_ee/workflows/display/nodes/vellum/templating_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py +3 -3
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/__init__.py +0 -0
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/conftest.py +28 -0
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_trigger_serialization.py +123 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +2 -11
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py +2 -14
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_generic_node_serialization.py +1 -7
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +17 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +17 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +1 -9
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py +1 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +1 -4
- vellum_ee/workflows/display/types.py +5 -1
- vellum_ee/workflows/display/utils/vellum.py +3 -3
- vellum_ee/workflows/display/vellum.py +4 -0
- vellum_ee/workflows/display/workflows/base_workflow_display.py +44 -16
- vellum_ee/workflows/display/workflows/get_vellum_workflow_display_class.py +3 -0
- vellum_ee/workflows/display/workflows/vellum_workflow_display.py +7 -8
- {vellum_ai-0.12.15.dist-info → vellum_ai-0.12.16.dist-info}/LICENSE +0 -0
- {vellum_ai-0.12.15.dist-info → vellum_ai-0.12.16.dist-info}/WHEEL +0 -0
- {vellum_ai-0.12.15.dist-info → vellum_ai-0.12.16.dist-info}/entry_points.txt +0 -0
@@ -38,8 +38,8 @@ class BasePromptDeploymentNodeDisplay(
|
|
38
38
|
for variable_name, variable_value in prompt_inputs.items()
|
39
39
|
]
|
40
40
|
|
41
|
-
_, output_display = display_context.
|
42
|
-
_, array_display = display_context.
|
41
|
+
_, output_display = display_context.global_node_output_displays[cast(OutputReference, node.Outputs.text)]
|
42
|
+
_, array_display = display_context.global_node_output_displays[cast(OutputReference, node.Outputs.results)]
|
43
43
|
|
44
44
|
# TODO: Pass through the name instead of retrieving the ID
|
45
45
|
# https://app.shortcut.com/vellum/story/4702
|
@@ -39,8 +39,10 @@ class BaseSearchNodeDisplay(BaseNodeVellumDisplay[_SearchNodeType], Generic[_Sea
|
|
39
39
|
node_id = self.node_id
|
40
40
|
node_inputs = self._generate_search_node_inputs(node_id, node, display_context)
|
41
41
|
|
42
|
-
_, results_output_display = display_context.
|
43
|
-
|
42
|
+
_, results_output_display = display_context.global_node_output_displays[
|
43
|
+
cast(OutputReference, node.Outputs.results)
|
44
|
+
]
|
45
|
+
_, text_output_display = display_context.global_node_output_displays[cast(OutputReference, node.Outputs.text)]
|
44
46
|
|
45
47
|
return {
|
46
48
|
"id": str(node_id),
|
@@ -56,7 +56,7 @@ class BaseTemplatingNodeDisplay(BaseNodeVellumDisplay[_TemplatingNodeType], Gene
|
|
56
56
|
# Misc type ignore is due to `node.Outputs` being generic
|
57
57
|
# https://app.shortcut.com/vellum/story/4784
|
58
58
|
output_descriptor = node.Outputs.result # type: ignore [misc]
|
59
|
-
_, output_display = display_context.
|
59
|
+
_, output_display = display_context.global_node_output_displays[output_descriptor]
|
60
60
|
inferred_output_type = primitive_type_to_vellum_variable_type(output_descriptor)
|
61
61
|
|
62
62
|
return {
|
@@ -91,18 +91,18 @@ def test_create_node_input_value_pointer_rules(
|
|
91
91
|
entrypoint_node_source_handle_id=uuid4(),
|
92
92
|
entrypoint_node_display=NodeDisplayData(),
|
93
93
|
),
|
94
|
-
|
94
|
+
global_workflow_input_displays={
|
95
95
|
cast(WorkflowInputReference, Inputs.example_workflow_input): WorkflowInputsVellumDisplayOverrides(
|
96
96
|
id=UUID("a154c29d-fac0-4cd0-ba88-bc52034f5470"),
|
97
97
|
),
|
98
98
|
},
|
99
|
-
|
99
|
+
global_node_output_displays={
|
100
100
|
cast(OutputReference, MyNodeA.Outputs.output): (
|
101
101
|
MyNodeA,
|
102
102
|
NodeOutputDisplay(id=UUID("4b16a629-11a1-4b3f-a965-a57b872d13b8"), name="output"),
|
103
103
|
),
|
104
104
|
},
|
105
|
-
|
105
|
+
global_node_displays={
|
106
106
|
MyNodeA: MyNodeADisplay(),
|
107
107
|
},
|
108
108
|
),
|
File without changes
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import pytest
|
2
|
+
from uuid import uuid4
|
3
|
+
|
4
|
+
from vellum_ee.workflows.display.nodes.get_node_display_class import get_node_display_class
|
5
|
+
from vellum_ee.workflows.display.nodes.vellum.base_node import BaseNodeDisplay
|
6
|
+
from vellum_ee.workflows.display.types import WorkflowDisplayContext
|
7
|
+
from vellum_ee.workflows.display.vellum import NodeDisplayData, WorkflowMetaVellumDisplay
|
8
|
+
from vellum_ee.workflows.display.workflows.vellum_workflow_display import VellumWorkflowDisplay
|
9
|
+
|
10
|
+
|
11
|
+
@pytest.fixture()
|
12
|
+
def serialize_node():
|
13
|
+
def _serialize_node(node_class) -> dict:
|
14
|
+
node_display_class = get_node_display_class(BaseNodeDisplay, node_class)
|
15
|
+
node_display = node_display_class()
|
16
|
+
|
17
|
+
context: WorkflowDisplayContext = WorkflowDisplayContext(
|
18
|
+
workflow_display_class=VellumWorkflowDisplay,
|
19
|
+
workflow_display=WorkflowMetaVellumDisplay(
|
20
|
+
entrypoint_node_id=uuid4(),
|
21
|
+
entrypoint_node_source_handle_id=uuid4(),
|
22
|
+
entrypoint_node_display=NodeDisplayData(),
|
23
|
+
),
|
24
|
+
node_displays={node_class: node_display},
|
25
|
+
)
|
26
|
+
return node_display.serialize(context)
|
27
|
+
|
28
|
+
return _serialize_node
|
vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_trigger_serialization.py
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
from deepdiff import DeepDiff
|
2
|
+
|
3
|
+
from vellum.workflows.inputs.base import BaseInputs
|
4
|
+
from vellum.workflows.nodes.bases.base import BaseNode
|
5
|
+
from vellum.workflows.types.core import MergeBehavior
|
6
|
+
|
7
|
+
|
8
|
+
class Inputs(BaseInputs):
|
9
|
+
input: str
|
10
|
+
|
11
|
+
|
12
|
+
class BasicGenericNode(BaseNode):
|
13
|
+
class Outputs(BaseNode.Outputs):
|
14
|
+
output = Inputs.input
|
15
|
+
|
16
|
+
|
17
|
+
class AwaitAnyGenericNode(BaseNode):
|
18
|
+
class Outputs(BaseNode.Outputs):
|
19
|
+
output = Inputs.input
|
20
|
+
|
21
|
+
class Trigger(BaseNode.Trigger):
|
22
|
+
merge_behavior = MergeBehavior.AWAIT_ANY
|
23
|
+
|
24
|
+
|
25
|
+
class AwaitAllGenericNode(BaseNode):
|
26
|
+
class Outputs(BaseNode.Outputs):
|
27
|
+
output = Inputs.input
|
28
|
+
|
29
|
+
class Trigger(BaseNode.Trigger):
|
30
|
+
merge_behavior = MergeBehavior.AWAIT_ALL
|
31
|
+
|
32
|
+
|
33
|
+
def test_serialize_node__basic(serialize_node):
|
34
|
+
serialized_node = serialize_node(BasicGenericNode)
|
35
|
+
assert not DeepDiff(
|
36
|
+
{
|
37
|
+
"id": "c2ed23f7-f6cb-4a56-a91c-2e5f9d8fda7f",
|
38
|
+
"label": "BasicGenericNode",
|
39
|
+
"type": "GENERIC",
|
40
|
+
"display_data": {"position": {"x": 0.0, "y": 0.0}},
|
41
|
+
"definition": {
|
42
|
+
"name": "BasicGenericNode",
|
43
|
+
"module": [
|
44
|
+
"vellum_ee",
|
45
|
+
"workflows",
|
46
|
+
"display",
|
47
|
+
"tests",
|
48
|
+
"workflow_serialization",
|
49
|
+
"generic_nodes",
|
50
|
+
"test_trigger_serialization",
|
51
|
+
],
|
52
|
+
"bases": [{"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]}],
|
53
|
+
},
|
54
|
+
"trigger": {"id": "9d3a1b3d-4a38-4f2e-bbf1-dd8be152bce8", "merge_behavior": "AWAIT_ANY"},
|
55
|
+
"ports": [],
|
56
|
+
"adornments": None,
|
57
|
+
"attributes": [],
|
58
|
+
},
|
59
|
+
serialized_node,
|
60
|
+
ignore_order=True,
|
61
|
+
)
|
62
|
+
|
63
|
+
|
64
|
+
def test_serialize_node__await_any(serialize_node):
|
65
|
+
serialized_node = serialize_node(AwaitAnyGenericNode)
|
66
|
+
assert not DeepDiff(
|
67
|
+
{
|
68
|
+
"id": "0ba67f76-aaff-4bd4-a20f-73a32ef5810d",
|
69
|
+
"label": "AwaitAnyGenericNode",
|
70
|
+
"type": "GENERIC",
|
71
|
+
"display_data": {"position": {"x": 0.0, "y": 0.0}},
|
72
|
+
"definition": {
|
73
|
+
"name": "AwaitAnyGenericNode",
|
74
|
+
"module": [
|
75
|
+
"vellum_ee",
|
76
|
+
"workflows",
|
77
|
+
"display",
|
78
|
+
"tests",
|
79
|
+
"workflow_serialization",
|
80
|
+
"generic_nodes",
|
81
|
+
"test_trigger_serialization",
|
82
|
+
],
|
83
|
+
"bases": [{"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]}],
|
84
|
+
},
|
85
|
+
"trigger": {"id": "ffa72187-9a18-453f-ae55-b77aad332630", "merge_behavior": "AWAIT_ANY"},
|
86
|
+
"ports": [],
|
87
|
+
"adornments": None,
|
88
|
+
"attributes": [],
|
89
|
+
},
|
90
|
+
serialized_node,
|
91
|
+
ignore_order=True,
|
92
|
+
)
|
93
|
+
|
94
|
+
|
95
|
+
def test_serialize_node__await_all(serialize_node):
|
96
|
+
serialized_node = serialize_node(AwaitAllGenericNode)
|
97
|
+
assert not DeepDiff(
|
98
|
+
{
|
99
|
+
"id": "09d06cd3-06ea-40cc-afd8-17ad88542271",
|
100
|
+
"label": "AwaitAllGenericNode",
|
101
|
+
"type": "GENERIC",
|
102
|
+
"display_data": {"position": {"x": 0.0, "y": 0.0}},
|
103
|
+
"definition": {
|
104
|
+
"name": "AwaitAllGenericNode",
|
105
|
+
"module": [
|
106
|
+
"vellum_ee",
|
107
|
+
"workflows",
|
108
|
+
"display",
|
109
|
+
"tests",
|
110
|
+
"workflow_serialization",
|
111
|
+
"generic_nodes",
|
112
|
+
"test_trigger_serialization",
|
113
|
+
],
|
114
|
+
"bases": [{"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]}],
|
115
|
+
},
|
116
|
+
"trigger": {"id": "62074276-c817-476d-b59d-da523ae3f218", "merge_behavior": "AWAIT_ALL"},
|
117
|
+
"ports": [],
|
118
|
+
"adornments": None,
|
119
|
+
"attributes": [],
|
120
|
+
},
|
121
|
+
serialized_node,
|
122
|
+
ignore_order=True,
|
123
|
+
)
|
@@ -448,17 +448,8 @@ def test_serialize_workflow():
|
|
448
448
|
ignore_order=True,
|
449
449
|
)
|
450
450
|
|
451
|
-
|
452
|
-
|
453
|
-
{"id": "148c61bd-e8b0-4d4b-8734-b043a72b90ed", "type": "GENERIC"},
|
454
|
-
{"id": "ed7caf01-9ae7-47a3-b15a-16697abaf486", "type": "GENERIC"},
|
455
|
-
{"id": "0d959311-c836-4641-a867-58f63df9dfea", "type": "GENERIC"},
|
456
|
-
{"id": "8df781b1-ff28-48a5-98a2-d7d796b932b0", "type": "GENERIC"},
|
457
|
-
{"id": "68c02b7c-5077-4087-803d-841474a8081f", "type": "GENERIC"},
|
458
|
-
],
|
459
|
-
workflow_raw_data["nodes"][2:7],
|
460
|
-
ignore_order=True,
|
461
|
-
)
|
451
|
+
passthrough_nodes = [node for node in workflow_raw_data["nodes"] if node["type"] == "GENERIC"]
|
452
|
+
assert len(passthrough_nodes) == 5
|
462
453
|
|
463
454
|
assert not DeepDiff(
|
464
455
|
[
|
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py
CHANGED
@@ -122,20 +122,8 @@ def test_serialize_workflow():
|
|
122
122
|
ignore_order=True,
|
123
123
|
)
|
124
124
|
|
125
|
-
|
126
|
-
|
127
|
-
for i, node in enumerate(workflow_raw_data["nodes"])
|
128
|
-
if i != error_index and i != 0 and i != len(workflow_raw_data["nodes"]) - 1
|
129
|
-
]
|
130
|
-
|
131
|
-
assert not DeepDiff(
|
132
|
-
[
|
133
|
-
{"id": "1381c078-efa2-4255-89a1-7b4cb742c7fc", "type": "GENERIC"},
|
134
|
-
{"id": "1eee9b4e-531f-45f2-a4b9-42207fac2c33", "type": "GENERIC"},
|
135
|
-
],
|
136
|
-
mocked_base_nodes,
|
137
|
-
ignore_order=True,
|
138
|
-
)
|
125
|
+
passthrough_nodes = [node for node in workflow_raw_data["nodes"] if node["type"] == "GENERIC"]
|
126
|
+
assert len(passthrough_nodes) == 2
|
139
127
|
|
140
128
|
terminal_node = workflow_raw_data["nodes"][-1]
|
141
129
|
assert not DeepDiff(
|
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_generic_node_serialization.py
CHANGED
@@ -79,13 +79,7 @@ def test_serialize_workflow(vellum_client):
|
|
79
79
|
}
|
80
80
|
|
81
81
|
api_node = workflow_raw_data["nodes"][1]
|
82
|
-
assert
|
83
|
-
{
|
84
|
-
"id": "c2ed23f7-f6cb-4a56-a91c-2e5f9d8fda7f",
|
85
|
-
"type": "GENERIC",
|
86
|
-
},
|
87
|
-
api_node,
|
88
|
-
)
|
82
|
+
assert api_node["id"] == "c2ed23f7-f6cb-4a56-a91c-2e5f9d8fda7f"
|
89
83
|
|
90
84
|
final_output_node = workflow_raw_data["nodes"][2]
|
91
85
|
assert not DeepDiff(
|
@@ -148,7 +148,23 @@ def test_serialize_workflow():
|
|
148
148
|
"bases": [],
|
149
149
|
},
|
150
150
|
},
|
151
|
-
{
|
151
|
+
{
|
152
|
+
"id": "1381c078-efa2-4255-89a1-7b4cb742c7fc",
|
153
|
+
"label": "StartNode",
|
154
|
+
"type": "GENERIC",
|
155
|
+
"display_data": {"position": {"x": 0.0, "y": 0.0}},
|
156
|
+
"definition": {
|
157
|
+
"name": "StartNode",
|
158
|
+
"module": ["tests", "workflows", "basic_inline_subworkflow", "workflow"],
|
159
|
+
"bases": [
|
160
|
+
{"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]}
|
161
|
+
],
|
162
|
+
},
|
163
|
+
"trigger": {"id": "a95a34f2-e894-4fb6-a2c9-15d12c1e3135", "merge_behavior": "AWAIT_ANY"},
|
164
|
+
"ports": [],
|
165
|
+
"adornments": None,
|
166
|
+
"attributes": [],
|
167
|
+
},
|
152
168
|
{
|
153
169
|
"id": "a773c3a5-78cb-4250-8d29-7282e8a579d3",
|
154
170
|
"type": "TERMINAL",
|
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py
CHANGED
@@ -133,7 +133,23 @@ def test_serialize_workflow():
|
|
133
133
|
"bases": [],
|
134
134
|
},
|
135
135
|
},
|
136
|
-
{
|
136
|
+
{
|
137
|
+
"id": "baf6d316-dc75-41e8-96c0-015aede96309",
|
138
|
+
"label": "Iteration",
|
139
|
+
"type": "GENERIC",
|
140
|
+
"display_data": {"position": {"x": 0.0, "y": 0.0}},
|
141
|
+
"definition": {
|
142
|
+
"name": "Iteration",
|
143
|
+
"module": ["tests", "workflows", "basic_map_node", "workflow"],
|
144
|
+
"bases": [
|
145
|
+
{"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]}
|
146
|
+
],
|
147
|
+
},
|
148
|
+
"trigger": {"id": "01324747-9bc0-4ecd-a8ab-40dca5a94e2e", "merge_behavior": "AWAIT_ANY"},
|
149
|
+
"ports": [],
|
150
|
+
"adornments": None,
|
151
|
+
"attributes": [],
|
152
|
+
},
|
137
153
|
{
|
138
154
|
"id": "6f4883b2-70b1-4e1c-ae15-7d0f5aec810b",
|
139
155
|
"type": "TERMINAL",
|
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py
CHANGED
@@ -69,15 +69,7 @@ def test_serialize_workflow__await_all():
|
|
69
69
|
}
|
70
70
|
|
71
71
|
passthrough_nodes = [node for node in workflow_raw_data["nodes"] if node["type"] == "GENERIC"]
|
72
|
-
assert
|
73
|
-
[
|
74
|
-
{"id": "127ef456-91bc-43c6-bd8b-1772db5e3cb5", "type": "GENERIC"},
|
75
|
-
{"id": "59243c65-053f-4ea6-9157-3f3edb1477bf", "type": "GENERIC"},
|
76
|
-
{"id": "634f0202-9ea9-4c62-b152-1a58c595cffb", "type": "GENERIC"},
|
77
|
-
],
|
78
|
-
passthrough_nodes,
|
79
|
-
ignore_order=True,
|
80
|
-
)
|
72
|
+
assert len(passthrough_nodes) == 3
|
81
73
|
|
82
74
|
merge_node = next(node for node in workflow_raw_data["nodes"] if node["type"] == "MERGE")
|
83
75
|
assert not DeepDiff(
|
vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py
CHANGED
@@ -91,10 +91,7 @@ def test_serialize_workflow__missing_final_output_node():
|
|
91
91
|
}
|
92
92
|
|
93
93
|
passthrough_node = next(node for node in workflow_raw_data["nodes"] if node["type"] == "GENERIC")
|
94
|
-
assert passthrough_node ==
|
95
|
-
"id": "32d88cab-e9fa-4a56-9bc2-fb6e1fd0897f",
|
96
|
-
"type": "GENERIC",
|
97
|
-
}
|
94
|
+
assert passthrough_node["id"] == "32d88cab-e9fa-4a56-9bc2-fb6e1fd0897f"
|
98
95
|
|
99
96
|
final_output_nodes = [node for node in workflow_raw_data["nodes"] if node["type"] == "TERMINAL"]
|
100
97
|
assert not DeepDiff(
|
@@ -36,8 +36,12 @@ class WorkflowDisplayContext(
|
|
36
36
|
workflow_display_class: Type["BaseWorkflowDisplay"]
|
37
37
|
workflow_display: WorkflowMetaDisplayType
|
38
38
|
workflow_input_displays: Dict[WorkflowInputReference, WorkflowInputsDisplayType] = field(default_factory=dict)
|
39
|
+
global_workflow_input_displays: Dict[WorkflowInputReference, WorkflowInputsDisplayType] = field(
|
40
|
+
default_factory=dict
|
41
|
+
)
|
39
42
|
node_displays: Dict[Type[BaseNode], "NodeDisplayType"] = field(default_factory=dict)
|
40
|
-
|
43
|
+
global_node_displays: Dict[Type[BaseNode], NodeDisplayType] = field(default_factory=dict)
|
44
|
+
global_node_output_displays: Dict[OutputReference, Tuple[Type[BaseNode], "NodeOutputDisplay"]] = field(
|
41
45
|
default_factory=dict
|
42
46
|
)
|
43
47
|
entrypoint_displays: Dict[Type[BaseNode], EntrypointDisplayType] = field(default_factory=dict)
|
@@ -58,13 +58,13 @@ def create_node_input_value_pointer_rule(
|
|
58
58
|
value: Any, display_context: WorkflowDisplayContext
|
59
59
|
) -> NodeInputValuePointerRule:
|
60
60
|
if isinstance(value, OutputReference):
|
61
|
-
upstream_node, output_display = display_context.
|
62
|
-
upstream_node_display = display_context.
|
61
|
+
upstream_node, output_display = display_context.global_node_output_displays[value]
|
62
|
+
upstream_node_display = display_context.global_node_displays[upstream_node]
|
63
63
|
return NodeOutputPointer(
|
64
64
|
data=NodeOutputData(node_id=str(upstream_node_display.node_id), output_id=str(output_display.id)),
|
65
65
|
)
|
66
66
|
if isinstance(value, WorkflowInputReference):
|
67
|
-
workflow_input_display = display_context.
|
67
|
+
workflow_input_display = display_context.global_workflow_input_displays[value]
|
68
68
|
return InputVariablePointer(data=InputVariableData(input_variable_id=str(workflow_input_display.id)))
|
69
69
|
if isinstance(value, VellumSecretReference):
|
70
70
|
# TODO: Pass through the name instead of retrieving the ID
|
@@ -38,6 +38,10 @@ class NodeDisplayData(UniversalBaseModel):
|
|
38
38
|
comment: Optional[NodeDisplayComment] = None
|
39
39
|
|
40
40
|
|
41
|
+
class GenericNodeDisplayData(UniversalBaseModel):
|
42
|
+
position: NodeDisplayPosition = Field(default_factory=NodeDisplayPosition)
|
43
|
+
|
44
|
+
|
41
45
|
class CodeResourceDefinition(UniversalBaseModel):
|
42
46
|
name: str
|
43
47
|
module: List[str]
|
@@ -1,9 +1,9 @@
|
|
1
1
|
from abc import abstractmethod
|
2
|
-
from copy import
|
2
|
+
from copy import copy
|
3
3
|
from functools import cached_property
|
4
4
|
import logging
|
5
5
|
from uuid import UUID
|
6
|
-
from typing import Any, Dict, Generic, Optional, Tuple, Type, get_args
|
6
|
+
from typing import Any, Dict, Generic, Iterator, List, Optional, Tuple, Type, get_args
|
7
7
|
|
8
8
|
from vellum.workflows.descriptors.base import BaseDescriptor
|
9
9
|
from vellum.workflows.edges import Edge
|
@@ -71,6 +71,10 @@ class BaseWorkflowDisplay(
|
|
71
71
|
# Used to store the mapping between workflows and their display classes
|
72
72
|
_workflow_display_registry: Dict[Type[WorkflowType], Type["BaseWorkflowDisplay"]] = {}
|
73
73
|
|
74
|
+
_errors: List[Exception]
|
75
|
+
|
76
|
+
_dry_run: bool
|
77
|
+
|
74
78
|
def __init__(
|
75
79
|
self,
|
76
80
|
workflow: Type[WorkflowType],
|
@@ -85,12 +89,15 @@ class BaseWorkflowDisplay(
|
|
85
89
|
EdgeDisplayType,
|
86
90
|
]
|
87
91
|
] = None,
|
92
|
+
dry_run: bool = False,
|
88
93
|
):
|
89
94
|
self._workflow = workflow
|
90
95
|
self._parent_display_context = parent_display_context
|
96
|
+
self._errors: List[Exception] = []
|
97
|
+
self._dry_run = dry_run
|
91
98
|
|
92
99
|
@abstractmethod
|
93
|
-
def serialize(self
|
100
|
+
def serialize(self) -> JsonObject:
|
94
101
|
pass
|
95
102
|
|
96
103
|
@classmethod
|
@@ -110,7 +117,18 @@ class BaseWorkflowDisplay(
|
|
110
117
|
def node_display_base_class(self) -> Type[NodeDisplayType]:
|
111
118
|
pass
|
112
119
|
|
113
|
-
def
|
120
|
+
def add_error(self, error: Exception) -> None:
|
121
|
+
if self._dry_run:
|
122
|
+
self._errors.append(error)
|
123
|
+
return
|
124
|
+
|
125
|
+
raise error
|
126
|
+
|
127
|
+
@property
|
128
|
+
def errors(self) -> Iterator[Exception]:
|
129
|
+
return iter(self._errors)
|
130
|
+
|
131
|
+
def _enrich_global_node_output_displays(
|
114
132
|
self,
|
115
133
|
node: Type[BaseNode],
|
116
134
|
node_display: NodeDisplayType,
|
@@ -126,7 +144,7 @@ class BaseWorkflowDisplay(
|
|
126
144
|
inner_node = get_wrapped_node(node)
|
127
145
|
if inner_node._is_wrapped_node:
|
128
146
|
inner_node_display = self._get_node_display(inner_node)
|
129
|
-
self.
|
147
|
+
self._enrich_global_node_output_displays(inner_node, inner_node_display, node_output_displays)
|
130
148
|
|
131
149
|
# TODO: Make sure this output ID matches the workflow output ID of the subworkflow node's workflow
|
132
150
|
# https://app.shortcut.com/vellum/story/5660/fix-output-id-in-subworkflow-nodes
|
@@ -174,19 +192,26 @@ class BaseWorkflowDisplay(
|
|
174
192
|
]:
|
175
193
|
workflow_display = self._generate_workflow_meta_display()
|
176
194
|
|
177
|
-
|
178
|
-
|
179
|
-
deepcopy(self._parent_display_context.node_output_displays) if self._parent_display_context else {}
|
195
|
+
global_node_output_displays: Dict[OutputReference, Tuple[Type[BaseNode], NodeOutputDisplay]] = (
|
196
|
+
copy(self._parent_display_context.global_node_output_displays) if self._parent_display_context else {}
|
180
197
|
)
|
181
198
|
|
182
199
|
node_displays: Dict[Type[BaseNode], NodeDisplayType] = {}
|
200
|
+
|
201
|
+
global_node_displays: Dict[Type[BaseNode], NodeDisplayType] = (
|
202
|
+
copy(self._parent_display_context.global_node_displays) if self._parent_display_context else {}
|
203
|
+
)
|
204
|
+
|
183
205
|
port_displays: Dict[Port, PortDisplay] = {}
|
184
206
|
|
185
207
|
# TODO: We should still serialize nodes that are in the workflow's directory but aren't used in the graph.
|
186
208
|
# https://app.shortcut.com/vellum/story/5394
|
187
209
|
for node in self._workflow.get_nodes():
|
210
|
+
if node in global_node_displays:
|
211
|
+
continue
|
188
212
|
node_display = self._get_node_display(node)
|
189
213
|
node_displays[node] = node_display
|
214
|
+
global_node_displays[node] = node_display
|
190
215
|
|
191
216
|
# Nodes wrapped in a decorator need to be in our node display dictionary for later retrieval
|
192
217
|
if has_wrapped_node(node):
|
@@ -195,22 +220,23 @@ class BaseWorkflowDisplay(
|
|
195
220
|
|
196
221
|
if inner_node._is_wrapped_node:
|
197
222
|
node_displays[inner_node] = inner_node_display
|
223
|
+
global_node_displays[inner_node] = inner_node_display
|
198
224
|
|
199
|
-
self.
|
225
|
+
self._enrich_global_node_output_displays(node, node_display, global_node_output_displays)
|
200
226
|
self._enrich_node_port_displays(node, node_display, port_displays)
|
201
227
|
|
228
|
+
workflow_input_displays: Dict[WorkflowInputReference, WorkflowInputsDisplayType] = {}
|
202
229
|
# If we're dealing with a nested workflow, then it should have access to the inputs of its parents.
|
203
|
-
|
204
|
-
|
230
|
+
global_workflow_input_displays = (
|
231
|
+
copy(self._parent_display_context.workflow_input_displays) if self._parent_display_context else {}
|
205
232
|
)
|
206
233
|
for workflow_input in self._workflow.get_inputs_class():
|
207
|
-
if workflow_input in workflow_input_displays:
|
208
|
-
continue
|
209
|
-
|
210
234
|
workflow_input_display_overrides = self.inputs_display.get(workflow_input)
|
211
|
-
|
235
|
+
input_display = self._generate_workflow_input_display(
|
212
236
|
workflow_input, overrides=workflow_input_display_overrides
|
213
237
|
)
|
238
|
+
workflow_input_displays[workflow_input] = input_display
|
239
|
+
global_workflow_input_displays[workflow_input] = input_display
|
214
240
|
|
215
241
|
entrypoint_displays: Dict[Type[BaseNode], EntrypointDisplayType] = {}
|
216
242
|
for entrypoint in self._workflow.get_entrypoints():
|
@@ -253,8 +279,10 @@ class BaseWorkflowDisplay(
|
|
253
279
|
return WorkflowDisplayContext(
|
254
280
|
workflow_display=workflow_display,
|
255
281
|
workflow_input_displays=workflow_input_displays,
|
282
|
+
global_workflow_input_displays=global_workflow_input_displays,
|
256
283
|
node_displays=node_displays,
|
257
|
-
|
284
|
+
global_node_output_displays=global_node_output_displays,
|
285
|
+
global_node_displays=global_node_displays,
|
258
286
|
entrypoint_displays=entrypoint_displays,
|
259
287
|
workflow_output_displays=workflow_output_displays,
|
260
288
|
edge_displays=edge_displays,
|
@@ -10,6 +10,7 @@ def get_workflow_display(
|
|
10
10
|
workflow_class: Type[WorkflowType],
|
11
11
|
root_workflow_class: Optional[Type[WorkflowType]] = None,
|
12
12
|
parent_display_context: Optional[WorkflowDisplayContext] = None,
|
13
|
+
dry_run: bool = False,
|
13
14
|
) -> WorkflowDisplayType:
|
14
15
|
try:
|
15
16
|
workflow_display_class = base_display_class.get_from_workflow_display_registry(workflow_class)
|
@@ -20,6 +21,7 @@ def get_workflow_display(
|
|
20
21
|
workflow_class=workflow_class.__bases__[0],
|
21
22
|
root_workflow_class=workflow_class if root_workflow_class is None else root_workflow_class,
|
22
23
|
parent_display_context=parent_display_context,
|
24
|
+
dry_run=dry_run,
|
23
25
|
)
|
24
26
|
except IndexError:
|
25
27
|
return base_display_class(workflow_class)
|
@@ -27,4 +29,5 @@ def get_workflow_display(
|
|
27
29
|
return workflow_display_class( # type: ignore[return-value]
|
28
30
|
workflow_class,
|
29
31
|
parent_display_context=parent_display_context,
|
32
|
+
dry_run=dry_run,
|
30
33
|
)
|
@@ -53,7 +53,7 @@ class VellumWorkflowDisplay(
|
|
53
53
|
):
|
54
54
|
node_display_base_class = BaseNodeVellumDisplay
|
55
55
|
|
56
|
-
def serialize(self
|
56
|
+
def serialize(self) -> JsonObject:
|
57
57
|
input_variables: JsonArray = []
|
58
58
|
for workflow_input, workflow_input_display in self.display_context.workflow_input_displays.items():
|
59
59
|
default = primitive_to_vellum_value(workflow_input.instance) if workflow_input.instance else None
|
@@ -106,12 +106,9 @@ class VellumWorkflowDisplay(
|
|
106
106
|
|
107
107
|
try:
|
108
108
|
serialized_node = node_display.serialize(self.display_context)
|
109
|
-
except NotImplementedError:
|
110
|
-
|
111
|
-
|
112
|
-
raise
|
113
|
-
else:
|
114
|
-
continue
|
109
|
+
except NotImplementedError as e:
|
110
|
+
self.add_error(e)
|
111
|
+
continue
|
115
112
|
|
116
113
|
nodes.append(serialized_node)
|
117
114
|
|
@@ -205,7 +202,9 @@ class VellumWorkflowDisplay(
|
|
205
202
|
# If there are terminal nodes with no workflow output reference,
|
206
203
|
# raise a serialization error
|
207
204
|
if len(unreferenced_final_output_node_outputs) > 0:
|
208
|
-
|
205
|
+
self.add_error(
|
206
|
+
ValueError("Unable to serialize terminal nodes that are not referenced by workflow outputs.")
|
207
|
+
)
|
209
208
|
|
210
209
|
# Add an edge for each edge in the workflow
|
211
210
|
all_edge_displays: List[EdgeVellumDisplay] = [
|
File without changes
|
File without changes
|
File without changes
|