vellum-ai 0.13.15__py3-none-any.whl → 0.13.19__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/client/resources/workflows/client.py +0 -10
- vellum/workflows/nodes/core/retry_node/node.py +13 -7
- vellum/workflows/nodes/core/templating_node/node.py +4 -47
- vellum/workflows/nodes/displayable/code_execution_node/node.py +29 -23
- vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py +169 -5
- vellum/workflows/nodes/displayable/code_execution_node/utils.py +98 -1
- vellum/workflows/nodes/utils.py +50 -1
- vellum/workflows/outputs/base.py +11 -0
- vellum/workflows/references/external_input.py +14 -0
- vellum/workflows/state/base.py +7 -0
- vellum/workflows/state/tests/test_state.py +42 -0
- {vellum_ai-0.13.15.dist-info → vellum_ai-0.13.19.dist-info}/METADATA +1 -1
- {vellum_ai-0.13.15.dist-info → vellum_ai-0.13.19.dist-info}/RECORD +28 -27
- vellum_cli/config.py +69 -11
- vellum_cli/pull.py +57 -20
- vellum_cli/push.py +1 -5
- vellum_cli/tests/test_pull.py +157 -9
- vellum_cli/tests/test_push.py +0 -8
- vellum_ee/workflows/display/nodes/base_node_display.py +2 -2
- vellum_ee/workflows/display/nodes/get_node_display_class.py +16 -20
- vellum_ee/workflows/display/nodes/vellum/__init__.py +2 -0
- vellum_ee/workflows/display/nodes/vellum/retry_node.py +10 -0
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +28 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +2 -2
- {vellum_ai-0.13.15.dist-info → vellum_ai-0.13.19.dist-info}/LICENSE +0 -0
- {vellum_ai-0.13.15.dist-info → vellum_ai-0.13.19.dist-info}/WHEEL +0 -0
- {vellum_ai-0.13.15.dist-info → vellum_ai-0.13.19.dist-info}/entry_points.txt +0 -0
vellum_cli/push.py
CHANGED
@@ -12,7 +12,6 @@ from dotenv import load_dotenv
|
|
12
12
|
from vellum.client.core.api_error import ApiError
|
13
13
|
from vellum.resources.workflows.client import OMIT
|
14
14
|
from vellum.types import WorkflowPushDeploymentConfigRequest
|
15
|
-
from vellum.workflows.utils.names import snake_to_title_case
|
16
15
|
from vellum.workflows.vellum_client import create_vellum_client
|
17
16
|
from vellum.workflows.workflows.base import BaseWorkflow
|
18
17
|
from vellum_cli.config import DEFAULT_WORKSPACE_CONFIG, WorkflowConfig, WorkflowDeploymentConfig, load_vellum_cli_config
|
@@ -111,8 +110,6 @@ def push_command(
|
|
111
110
|
"container_image_name": workflow_config.container_image_name,
|
112
111
|
}
|
113
112
|
|
114
|
-
label = snake_to_title_case(workflow_config.module.split(".")[-1])
|
115
|
-
|
116
113
|
deployment_config: WorkflowPushDeploymentConfigRequest = OMIT
|
117
114
|
deployment_config_serialized: str = OMIT
|
118
115
|
if deploy:
|
@@ -158,7 +155,6 @@ def push_command(
|
|
158
155
|
# Remove this once we could serialize using the artifact in Vembda
|
159
156
|
# https://app.shortcut.com/vellum/story/5585
|
160
157
|
exec_config=json.dumps(exec_config),
|
161
|
-
label=label,
|
162
158
|
workflow_sandbox_id=workflow_config.workflow_sandbox_id or workflow_sandbox_id,
|
163
159
|
artifact=artifact,
|
164
160
|
# We should check with fern if we could auto-serialize typed object fields for us
|
@@ -230,7 +226,7 @@ def push_command(
|
|
230
226
|
) # type: ignore[attr-defined]
|
231
227
|
else:
|
232
228
|
logger.info(
|
233
|
-
f"""Successfully pushed {
|
229
|
+
f"""Successfully pushed {workflow_config.module} to Vellum!
|
234
230
|
Visit at: https://app.vellum.ai/workflow-sandboxes/{response.workflow_sandbox_id}"""
|
235
231
|
)
|
236
232
|
|
vellum_cli/tests/test_pull.py
CHANGED
@@ -39,6 +39,7 @@ def test_pull(vellum_client, mock_module, base_command):
|
|
39
39
|
# GIVEN a module on the user's filesystem
|
40
40
|
temp_dir = mock_module.temp_dir
|
41
41
|
module = mock_module.module
|
42
|
+
workflow_sandbox_id = mock_module.workflow_sandbox_id
|
42
43
|
|
43
44
|
# AND the workflow pull API call returns a zip file
|
44
45
|
vellum_client.workflows.pull.return_value = iter([_zip_file_map({"workflow.py": "print('hello')"})])
|
@@ -56,6 +57,27 @@ def test_pull(vellum_client, mock_module, base_command):
|
|
56
57
|
with open(workflow_py) as f:
|
57
58
|
assert f.read() == "print('hello')"
|
58
59
|
|
60
|
+
# AND the vellum.lock.json file is created
|
61
|
+
vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
|
62
|
+
assert os.path.exists(vellum_lock_json)
|
63
|
+
with open(vellum_lock_json) as f:
|
64
|
+
lock_data = json.load(f)
|
65
|
+
assert lock_data == {
|
66
|
+
"version": "1.0",
|
67
|
+
"workflows": [
|
68
|
+
{
|
69
|
+
"module": module,
|
70
|
+
"workflow_sandbox_id": workflow_sandbox_id,
|
71
|
+
"container_image_name": None,
|
72
|
+
"container_image_tag": None,
|
73
|
+
"ignore": None,
|
74
|
+
"deployments": [],
|
75
|
+
"workspace": "default",
|
76
|
+
}
|
77
|
+
],
|
78
|
+
"workspaces": [],
|
79
|
+
}
|
80
|
+
|
59
81
|
|
60
82
|
def test_pull__second_module(vellum_client, mock_module):
|
61
83
|
# GIVEN a module on the user's filesystem
|
@@ -95,7 +117,13 @@ def test_pull__sandbox_id_with_no_config(vellum_client):
|
|
95
117
|
workflow_sandbox_id = "87654321-0000-0000-0000-000000000000"
|
96
118
|
|
97
119
|
# AND the workflow pull API call returns a zip file
|
98
|
-
vellum_client.workflows.pull.return_value = iter(
|
120
|
+
vellum_client.workflows.pull.return_value = iter(
|
121
|
+
[
|
122
|
+
_zip_file_map(
|
123
|
+
{"workflow.py": "print('hello')", "metadata.json": json.dumps({"label": "Super Cool Workflow"})}
|
124
|
+
)
|
125
|
+
]
|
126
|
+
)
|
99
127
|
|
100
128
|
# AND we are currently in a new directory
|
101
129
|
current_dir = os.getcwd()
|
@@ -112,7 +140,7 @@ def test_pull__sandbox_id_with_no_config(vellum_client):
|
|
112
140
|
|
113
141
|
# AND the pull api is called with the workflow sandbox id
|
114
142
|
vellum_client.workflows.pull.assert_called_once()
|
115
|
-
workflow_py = os.path.join(temp_dir, "
|
143
|
+
workflow_py = os.path.join(temp_dir, "super_cool_workflow", "workflow.py")
|
116
144
|
assert os.path.exists(workflow_py)
|
117
145
|
with open(workflow_py) as f:
|
118
146
|
assert f.read() == "print('hello')"
|
@@ -127,7 +155,7 @@ def test_pull__sandbox_id_with_no_config(vellum_client):
|
|
127
155
|
"workspaces": [],
|
128
156
|
"workflows": [
|
129
157
|
{
|
130
|
-
"module": "
|
158
|
+
"module": "super_cool_workflow",
|
131
159
|
"workflow_sandbox_id": "87654321-0000-0000-0000-000000000000",
|
132
160
|
"ignore": None,
|
133
161
|
"deployments": [],
|
@@ -147,7 +175,13 @@ def test_pull__sandbox_id_with_other_workflow_configured(vellum_client, mock_mod
|
|
147
175
|
workflow_sandbox_id = "87654321-0000-0000-0000-000000000000"
|
148
176
|
|
149
177
|
# AND the workflow pull API call returns a zip file
|
150
|
-
vellum_client.workflows.pull.return_value = iter(
|
178
|
+
vellum_client.workflows.pull.return_value = iter(
|
179
|
+
[
|
180
|
+
_zip_file_map(
|
181
|
+
{"workflow.py": "print('hello')", "metadata.json": json.dumps({"label": "Super Cool Workflow"})}
|
182
|
+
)
|
183
|
+
]
|
184
|
+
)
|
151
185
|
|
152
186
|
# WHEN the user runs the pull command with the new workflow sandbox id
|
153
187
|
runner = CliRunner()
|
@@ -162,7 +196,7 @@ def test_pull__sandbox_id_with_other_workflow_configured(vellum_client, mock_mod
|
|
162
196
|
assert call_args[0] == workflow_sandbox_id
|
163
197
|
|
164
198
|
# AND the workflow.py file is written to the module directory
|
165
|
-
workflow_py = os.path.join(temp_dir, "
|
199
|
+
workflow_py = os.path.join(temp_dir, "super_cool_workflow", "workflow.py")
|
166
200
|
assert os.path.exists(workflow_py)
|
167
201
|
with open(workflow_py) as f:
|
168
202
|
assert f.read() == "print('hello')"
|
@@ -394,7 +428,7 @@ def test_pull__sandbox_id_with_other_workflow_deployment_in_lock(vellum_client,
|
|
394
428
|
"workflows": [
|
395
429
|
{
|
396
430
|
"module": module,
|
397
|
-
"workflow_sandbox_id":
|
431
|
+
"workflow_sandbox_id": workflow_sandbox_id,
|
398
432
|
"ignore": "tests/*",
|
399
433
|
"deployments": [
|
400
434
|
{
|
@@ -420,8 +454,12 @@ def test_pull__sandbox_id_with_other_workflow_deployment_in_lock(vellum_client,
|
|
420
454
|
_zip_file_map(
|
421
455
|
{
|
422
456
|
"workflow.py": "print('hello')",
|
423
|
-
"metadata.json":
|
424
|
-
|
457
|
+
"metadata.json": json.dumps(
|
458
|
+
{
|
459
|
+
"runner_config": {"container_image_name": "test", "container_image_tag": "1.0"},
|
460
|
+
"label": "Super Cool Workflow",
|
461
|
+
}
|
462
|
+
),
|
425
463
|
}
|
426
464
|
)
|
427
465
|
]
|
@@ -456,7 +494,7 @@ def test_pull__sandbox_id_with_other_workflow_deployment_in_lock(vellum_client,
|
|
456
494
|
"workspace": "default",
|
457
495
|
},
|
458
496
|
{
|
459
|
-
"module": "
|
497
|
+
"module": "super_cool_workflow",
|
460
498
|
"workflow_sandbox_id": new_workflow_sandbox_id,
|
461
499
|
"ignore": None,
|
462
500
|
"deployments": [],
|
@@ -539,3 +577,113 @@ def test_pull__include_sandbox(vellum_client, mock_module):
|
|
539
577
|
with open(lock_json) as f:
|
540
578
|
lock_data = json.load(f)
|
541
579
|
assert lock_data["workflows"][0]["ignore"] == "sandbox.py"
|
580
|
+
|
581
|
+
|
582
|
+
def test_pull__same_pull_twice__one_entry_in_lockfile(vellum_client, mock_module):
|
583
|
+
# GIVEN a module on the user's filesystem
|
584
|
+
module = mock_module.module
|
585
|
+
temp_dir = mock_module.temp_dir
|
586
|
+
workflow_sandbox_id = mock_module.workflow_sandbox_id
|
587
|
+
|
588
|
+
# AND the workflow pull API call returns a zip file both times
|
589
|
+
zip_contents = _zip_file_map({"workflow.py": "print('hello')"})
|
590
|
+
responses = iter([zip_contents, zip_contents])
|
591
|
+
|
592
|
+
def workflows_pull_side_effect(*args, **kwargs):
|
593
|
+
return iter([next(responses)])
|
594
|
+
|
595
|
+
vellum_client.workflows.pull.side_effect = workflows_pull_side_effect
|
596
|
+
|
597
|
+
# AND the user runs the pull command once
|
598
|
+
runner = CliRunner()
|
599
|
+
runner.invoke(cli_main, ["pull", module])
|
600
|
+
|
601
|
+
# WHEN the user runs the pull command again but with the workflow sandbox id
|
602
|
+
result = runner.invoke(cli_main, ["workflows", "pull", "--workflow-sandbox-id", workflow_sandbox_id])
|
603
|
+
|
604
|
+
# THEN the command returns successfully
|
605
|
+
assert result.exit_code == 0, (result.output, result.exception)
|
606
|
+
|
607
|
+
# AND the lockfile should only have one entry
|
608
|
+
lock_json = os.path.join(temp_dir, "vellum.lock.json")
|
609
|
+
with open(lock_json) as f:
|
610
|
+
lock_data = json.load(f)
|
611
|
+
assert len(lock_data["workflows"]) == 1
|
612
|
+
|
613
|
+
|
614
|
+
def test_pull__module_not_in_config(vellum_client, mock_module):
|
615
|
+
# GIVEN a module on the user's filesystem
|
616
|
+
module = mock_module.module
|
617
|
+
temp_dir = mock_module.temp_dir
|
618
|
+
workflow_sandbox_id = mock_module.workflow_sandbox_id
|
619
|
+
set_pyproject_toml = mock_module.set_pyproject_toml
|
620
|
+
|
621
|
+
# AND the pyproject.toml does not have the module configured
|
622
|
+
set_pyproject_toml({"workflows": []})
|
623
|
+
|
624
|
+
# AND the workflow pull API call returns a zip file
|
625
|
+
vellum_client.workflows.pull.return_value = iter([_zip_file_map({"workflow.py": "print('hello')"})])
|
626
|
+
|
627
|
+
# WHEN the user runs the pull command again with the workflow sandbox id and module
|
628
|
+
runner = CliRunner()
|
629
|
+
result = runner.invoke(cli_main, ["workflows", "pull", module, "--workflow-sandbox-id", workflow_sandbox_id])
|
630
|
+
|
631
|
+
# THEN the command returns successfully
|
632
|
+
assert result.exit_code == 0, (result.output, result.exception)
|
633
|
+
|
634
|
+
# AND the lockfile should have the new entry
|
635
|
+
lock_json = os.path.join(temp_dir, "vellum.lock.json")
|
636
|
+
with open(lock_json) as f:
|
637
|
+
lock_data = json.load(f)
|
638
|
+
assert lock_data["workflows"] == [
|
639
|
+
{
|
640
|
+
"module": module,
|
641
|
+
"workflow_sandbox_id": workflow_sandbox_id,
|
642
|
+
"ignore": None,
|
643
|
+
"deployments": [],
|
644
|
+
"container_image_name": None,
|
645
|
+
"container_image_tag": None,
|
646
|
+
"workspace": "default",
|
647
|
+
}
|
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
|
vellum_cli/tests/test_push.py
CHANGED
@@ -119,14 +119,12 @@ def test_push__happy_path(mock_module, vellum_client, base_command):
|
|
119
119
|
assert result.exit_code == 0
|
120
120
|
|
121
121
|
# Get the last part of the module path and format it
|
122
|
-
expected_label = mock_module.module.split(".")[-1].replace("_", " ").title()
|
123
122
|
expected_artifact_name = f"{mock_module.module.replace('.', '__')}.tar.gz"
|
124
123
|
|
125
124
|
# AND we should have called the push API with the correct args
|
126
125
|
vellum_client.workflows.push.assert_called_once()
|
127
126
|
call_args = vellum_client.workflows.push.call_args.kwargs
|
128
127
|
assert json.loads(call_args["exec_config"])["workflow_raw_data"]["definition"]["name"] == "ExampleWorkflow"
|
129
|
-
assert call_args["label"] == expected_label
|
130
128
|
assert is_valid_uuid(call_args["workflow_sandbox_id"])
|
131
129
|
assert call_args["artifact"].name == expected_artifact_name
|
132
130
|
assert "deplyment_config" not in call_args
|
@@ -165,14 +163,12 @@ def test_push__workflow_sandbox_option__existing_id(mock_module, vellum_client,
|
|
165
163
|
assert result.exit_code == 0
|
166
164
|
|
167
165
|
# Get the last part of the module path and format it
|
168
|
-
expected_label = mock_module.module.split(".")[-1].replace("_", " ").title()
|
169
166
|
expected_artifact_name = f"{mock_module.module.replace('.', '__')}.tar.gz"
|
170
167
|
|
171
168
|
# AND we should have called the push API with the correct args
|
172
169
|
vellum_client.workflows.push.assert_called_once()
|
173
170
|
call_args = vellum_client.workflows.push.call_args.kwargs
|
174
171
|
assert json.loads(call_args["exec_config"])["workflow_raw_data"]["definition"]["name"] == "ExampleWorkflow"
|
175
|
-
assert call_args["label"] == expected_label
|
176
172
|
assert call_args["workflow_sandbox_id"] == existing_workflow_sandbox_id
|
177
173
|
assert call_args["artifact"].name == expected_artifact_name
|
178
174
|
assert "deplyment_config" not in call_args
|
@@ -216,14 +212,12 @@ def test_push__workflow_sandbox_option__existing_no_module(mock_module, vellum_c
|
|
216
212
|
assert result.exit_code == 0
|
217
213
|
|
218
214
|
# Get the last part of the module path and format it
|
219
|
-
expected_label = second_module.split(".")[-1].replace("_", " ").title()
|
220
215
|
expected_artifact_name = f"{second_module.replace('.', '__')}.tar.gz"
|
221
216
|
|
222
217
|
# AND we should have called the push API with the correct args
|
223
218
|
vellum_client.workflows.push.assert_called_once()
|
224
219
|
call_args = vellum_client.workflows.push.call_args.kwargs
|
225
220
|
assert json.loads(call_args["exec_config"])["workflow_raw_data"]["definition"]["name"] == "ExampleWorkflow"
|
226
|
-
assert call_args["label"] == expected_label
|
227
221
|
assert call_args["workflow_sandbox_id"] == second_workflow_sandbox_id
|
228
222
|
assert call_args["artifact"].name == expected_artifact_name
|
229
223
|
assert "deplyment_config" not in call_args
|
@@ -297,14 +291,12 @@ def test_push__deployment(mock_module, vellum_client, base_command):
|
|
297
291
|
assert result.exit_code == 0
|
298
292
|
|
299
293
|
# Get the last part of the module path and format it
|
300
|
-
expected_label = mock_module.module.split(".")[-1].replace("_", " ").title()
|
301
294
|
expected_artifact_name = f"{mock_module.module.replace('.', '__')}.tar.gz"
|
302
295
|
|
303
296
|
# AND we should have called the push API with the correct args
|
304
297
|
vellum_client.workflows.push.assert_called_once()
|
305
298
|
call_args = vellum_client.workflows.push.call_args.kwargs
|
306
299
|
assert json.loads(call_args["exec_config"])["workflow_raw_data"]["definition"]["name"] == "ExampleWorkflow"
|
307
|
-
assert call_args["label"] == expected_label
|
308
300
|
assert is_valid_uuid(call_args["workflow_sandbox_id"])
|
309
301
|
assert call_args["artifact"].name == expected_artifact_name
|
310
302
|
assert call_args["deployment_config"] == "{}"
|
@@ -208,8 +208,8 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
|
|
208
208
|
return uuid4_from_hash(f"{self.node_id}|trigger")
|
209
209
|
|
210
210
|
@classmethod
|
211
|
-
def get_from_node_display_registry(cls, node_class: Type[NodeType]) -> Type["BaseNodeDisplay"]:
|
212
|
-
return cls._node_display_registry
|
211
|
+
def get_from_node_display_registry(cls, node_class: Type[NodeType]) -> Optional[Type["BaseNodeDisplay"]]:
|
212
|
+
return cls._node_display_registry.get(node_class)
|
213
213
|
|
214
214
|
@classmethod
|
215
215
|
def infer_node_class(cls) -> Type[NodeType]:
|
@@ -10,27 +10,23 @@ if TYPE_CHECKING:
|
|
10
10
|
def get_node_display_class(
|
11
11
|
base_class: Type["NodeDisplayType"], node_class: Type[NodeType], root_node_class: Optional[Type[NodeType]] = None
|
12
12
|
) -> Type["NodeDisplayType"]:
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
base_class, node_class.__bases__[0], node_class if root_node_class is None else root_node_class
|
13
|
+
node_display_class = base_class.get_from_node_display_registry(node_class)
|
14
|
+
if node_display_class:
|
15
|
+
if not issubclass(node_display_class, base_class):
|
16
|
+
raise TypeError(
|
17
|
+
f"Expected to find a subclass of '{base_class.__name__}' for node class '{node_class.__name__}'"
|
19
18
|
)
|
20
19
|
|
21
|
-
|
22
|
-
NodeDisplayBaseClass = base_node_display_class[node_class] # type: ignore[index]
|
23
|
-
NodeDisplayClass = types.new_class(
|
24
|
-
f"{node_class.__name__}Display",
|
25
|
-
bases=(NodeDisplayBaseClass,),
|
26
|
-
)
|
27
|
-
return NodeDisplayClass
|
28
|
-
except IndexError:
|
29
|
-
return base_class
|
20
|
+
return node_display_class
|
30
21
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
)
|
22
|
+
base_node_display_class = get_node_display_class(
|
23
|
+
base_class, node_class.__bases__[0], node_class if root_node_class is None else root_node_class
|
24
|
+
)
|
35
25
|
|
36
|
-
|
26
|
+
# `base_node_display_class` is always a Generic class, so it's safe to index into it
|
27
|
+
NodeDisplayBaseClass = base_node_display_class[node_class] # type: ignore[index]
|
28
|
+
NodeDisplayClass = types.new_class(
|
29
|
+
f"{node_class.__name__}Display",
|
30
|
+
bases=(NodeDisplayBaseClass,),
|
31
|
+
)
|
32
|
+
return NodeDisplayClass
|
@@ -10,6 +10,7 @@ from .map_node import BaseMapNodeDisplay
|
|
10
10
|
from .merge_node import BaseMergeNodeDisplay
|
11
11
|
from .note_node import BaseNoteNodeDisplay
|
12
12
|
from .prompt_deployment_node import BasePromptDeploymentNodeDisplay
|
13
|
+
from .retry_node import BaseRetryNodeDisplay
|
13
14
|
from .search_node import BaseSearchNodeDisplay
|
14
15
|
from .subworkflow_deployment_node import BaseSubworkflowDeploymentNodeDisplay
|
15
16
|
from .templating_node import BaseTemplatingNodeDisplay
|
@@ -29,6 +30,7 @@ __all__ = [
|
|
29
30
|
"BaseMergeNodeDisplay",
|
30
31
|
"BaseNoteNodeDisplay",
|
31
32
|
"BasePromptDeploymentNodeDisplay",
|
33
|
+
"BaseRetryNodeDisplay",
|
32
34
|
"BaseSearchNodeDisplay",
|
33
35
|
"BaseSubworkflowDeploymentNodeDisplay",
|
34
36
|
"BaseTemplatingNodeDisplay",
|
@@ -0,0 +1,10 @@
|
|
1
|
+
from typing import Generic, TypeVar
|
2
|
+
|
3
|
+
from vellum.workflows.nodes.core.retry_node.node import RetryNode
|
4
|
+
from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
|
5
|
+
|
6
|
+
_RetryNodeType = TypeVar("_RetryNodeType", bound=RetryNode)
|
7
|
+
|
8
|
+
|
9
|
+
class BaseRetryNodeDisplay(BaseNodeDisplay[_RetryNodeType], Generic[_RetryNodeType]):
|
10
|
+
pass
|
@@ -7,10 +7,13 @@ from vellum.workflows.nodes.bases.base import BaseNode
|
|
7
7
|
from vellum.workflows.nodes.core.retry_node.node import RetryNode
|
8
8
|
from vellum.workflows.nodes.core.try_node.node import TryNode
|
9
9
|
from vellum.workflows.outputs.base import BaseOutputs
|
10
|
+
from vellum.workflows.workflows.base import BaseWorkflow
|
10
11
|
from vellum_ee.workflows.display.base import WorkflowInputsDisplay
|
11
12
|
from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
|
12
13
|
from vellum_ee.workflows.display.nodes.base_node_vellum_display import BaseNodeVellumDisplay
|
13
14
|
from vellum_ee.workflows.display.nodes.vellum.try_node import BaseTryNodeDisplay
|
15
|
+
from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
|
16
|
+
from vellum_ee.workflows.display.workflows.vellum_workflow_display import VellumWorkflowDisplay
|
14
17
|
|
15
18
|
|
16
19
|
class Inputs(BaseInputs):
|
@@ -77,6 +80,11 @@ def test_serialize_node__retry(serialize_node):
|
|
77
80
|
"module": ["vellum", "workflows", "nodes", "core", "retry_node", "node"],
|
78
81
|
},
|
79
82
|
"attributes": [
|
83
|
+
{
|
84
|
+
"id": "8a07dc58-3fed-41d4-8ca6-31ee0bb86c61",
|
85
|
+
"name": "delay",
|
86
|
+
"value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
|
87
|
+
},
|
80
88
|
{
|
81
89
|
"id": "f388e93b-8c68-4f54-8577-bbd0c9091557",
|
82
90
|
"name": "max_attempts",
|
@@ -110,6 +118,26 @@ def test_serialize_node__retry(serialize_node):
|
|
110
118
|
)
|
111
119
|
|
112
120
|
|
121
|
+
def test_serialize_node__retry__no_display(): # GIVEN an adornment node
|
122
|
+
@RetryNode.wrap(max_attempts=5)
|
123
|
+
class StartNode(BaseNode):
|
124
|
+
pass
|
125
|
+
|
126
|
+
# AND a workflow that uses the adornment node
|
127
|
+
class MyWorkflow(BaseWorkflow):
|
128
|
+
graph = StartNode
|
129
|
+
|
130
|
+
# WHEN we serialize the workflow
|
131
|
+
workflow_display = get_workflow_display(
|
132
|
+
base_display_class=VellumWorkflowDisplay,
|
133
|
+
workflow_class=MyWorkflow,
|
134
|
+
)
|
135
|
+
exec_config = workflow_display.serialize()
|
136
|
+
|
137
|
+
# THEN the workflow display is created successfully
|
138
|
+
assert exec_config is not None
|
139
|
+
|
140
|
+
|
113
141
|
@TryNode.wrap()
|
114
142
|
class InnerTryGenericNode(BaseNode):
|
115
143
|
input = Inputs.input
|
@@ -93,7 +93,7 @@ def test_serialize_workflow_with_filepath():
|
|
93
93
|
"code_input_id": "f2e8a4fa-b54e-41e9-b314-0e5443519ac7",
|
94
94
|
"runtime_input_id": "19d64948-f22b-4103-a7f5-3add184b31cc",
|
95
95
|
"output_type": "NUMBER",
|
96
|
-
"packages": [],
|
96
|
+
"packages": [{"name": "openai", "version": "1.0.0"}],
|
97
97
|
"output_id": "0fde9607-353f-42c2-85c4-20f720ebc1ec",
|
98
98
|
"log_output_id": "7cac05e3-b7c3-475e-8df8-422b496c3398",
|
99
99
|
},
|
@@ -566,7 +566,7 @@ def test_serialize_workflow__try_wrapped():
|
|
566
566
|
"code_input_id": "f2e8a4fa-b54e-41e9-b314-0e5443519ac7",
|
567
567
|
"runtime_input_id": "19d64948-f22b-4103-a7f5-3add184b31cc",
|
568
568
|
"output_type": "NUMBER",
|
569
|
-
"packages": [],
|
569
|
+
"packages": [{"name": "openai", "version": "1.0.0"}],
|
570
570
|
"output_id": "0fde9607-353f-42c2-85c4-20f720ebc1ec",
|
571
571
|
"log_output_id": "7cac05e3-b7c3-475e-8df8-422b496c3398",
|
572
572
|
},
|
File without changes
|
File without changes
|
File without changes
|