vellum-ai 1.2.4__py3-none-any.whl → 1.3.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.
- vellum/__init__.py +56 -0
- vellum/client/README.md +1 -1
- vellum/client/core/client_wrapper.py +2 -2
- vellum/client/reference.md +0 -9
- vellum/client/resources/workflow_sandboxes/client.py +0 -12
- vellum/client/resources/workflow_sandboxes/raw_client.py +2 -10
- vellum/client/resources/workflows/client.py +20 -0
- vellum/client/resources/workflows/raw_client.py +20 -0
- vellum/client/types/__init__.py +56 -0
- vellum/client/types/audio_input.py +30 -0
- vellum/client/types/code_executor_input.py +8 -0
- vellum/client/types/deployment_read.py +5 -5
- vellum/client/types/document_input.py +30 -0
- vellum/client/types/image_input.py +30 -0
- vellum/client/types/named_scenario_input_audio_variable_value_request.py +22 -0
- vellum/client/types/named_scenario_input_document_variable_value_request.py +22 -0
- vellum/client/types/named_scenario_input_image_variable_value_request.py +22 -0
- vellum/client/types/named_scenario_input_request.py +8 -0
- vellum/client/types/named_scenario_input_video_variable_value_request.py +22 -0
- vellum/client/types/named_test_case_audio_variable_value.py +26 -0
- vellum/client/types/named_test_case_audio_variable_value_request.py +26 -0
- vellum/client/types/named_test_case_document_variable_value.py +22 -0
- vellum/client/types/named_test_case_document_variable_value_request.py +22 -0
- vellum/client/types/named_test_case_image_variable_value.py +22 -0
- vellum/client/types/named_test_case_image_variable_value_request.py +22 -0
- vellum/client/types/named_test_case_variable_value.py +8 -0
- vellum/client/types/named_test_case_variable_value_request.py +8 -0
- vellum/client/types/named_test_case_video_variable_value.py +22 -0
- vellum/client/types/named_test_case_video_variable_value_request.py +22 -0
- vellum/client/types/node_execution_span_attributes.py +1 -0
- vellum/client/types/scenario_input.py +11 -1
- vellum/client/types/scenario_input_audio_variable_value.py +22 -0
- vellum/client/types/scenario_input_document_variable_value.py +22 -0
- vellum/client/types/scenario_input_image_variable_value.py +22 -0
- vellum/client/types/scenario_input_video_variable_value.py +22 -0
- vellum/client/types/slim_deployment_read.py +5 -5
- vellum/client/types/slim_workflow_deployment.py +5 -5
- vellum/client/types/span_link.py +1 -1
- vellum/client/types/span_link_type_enum.py +1 -1
- vellum/client/types/test_case_audio_variable_value.py +27 -0
- vellum/client/types/test_case_document_variable_value.py +27 -0
- vellum/client/types/test_case_image_variable_value.py +27 -0
- vellum/client/types/test_case_variable_value.py +8 -0
- vellum/client/types/test_case_video_variable_value.py +27 -0
- vellum/client/types/video_input.py +30 -0
- vellum/client/types/workflow_deployment_read.py +5 -5
- vellum/client/types/workflow_push_deployment_config_request.py +1 -0
- vellum/client/types/workflow_request_audio_input_request.py +30 -0
- vellum/client/types/workflow_request_document_input_request.py +30 -0
- vellum/client/types/workflow_request_image_input_request.py +30 -0
- vellum/client/types/workflow_request_input_request.py +8 -0
- vellum/client/types/workflow_request_video_input_request.py +30 -0
- vellum/types/audio_input.py +3 -0
- vellum/types/document_input.py +3 -0
- vellum/types/image_input.py +3 -0
- vellum/types/named_scenario_input_audio_variable_value_request.py +3 -0
- vellum/types/named_scenario_input_document_variable_value_request.py +3 -0
- vellum/types/named_scenario_input_image_variable_value_request.py +3 -0
- vellum/types/named_scenario_input_video_variable_value_request.py +3 -0
- vellum/types/named_test_case_audio_variable_value.py +3 -0
- vellum/types/named_test_case_audio_variable_value_request.py +3 -0
- vellum/types/named_test_case_document_variable_value.py +3 -0
- vellum/types/named_test_case_document_variable_value_request.py +3 -0
- vellum/types/named_test_case_image_variable_value.py +3 -0
- vellum/types/named_test_case_image_variable_value_request.py +3 -0
- vellum/types/named_test_case_video_variable_value.py +3 -0
- vellum/types/named_test_case_video_variable_value_request.py +3 -0
- vellum/types/scenario_input_audio_variable_value.py +3 -0
- vellum/types/scenario_input_document_variable_value.py +3 -0
- vellum/types/scenario_input_image_variable_value.py +3 -0
- vellum/types/scenario_input_video_variable_value.py +3 -0
- vellum/types/test_case_audio_variable_value.py +3 -0
- vellum/types/test_case_document_variable_value.py +3 -0
- vellum/types/test_case_image_variable_value.py +3 -0
- vellum/types/test_case_video_variable_value.py +3 -0
- vellum/types/video_input.py +3 -0
- vellum/types/workflow_request_audio_input_request.py +3 -0
- vellum/types/workflow_request_document_input_request.py +3 -0
- vellum/types/workflow_request_image_input_request.py +3 -0
- vellum/types/workflow_request_video_input_request.py +3 -0
- vellum/workflows/events/types.py +6 -1
- vellum/workflows/integrations/tests/test_mcp_service.py +106 -1
- vellum/workflows/nodes/__init__.py +2 -0
- vellum/workflows/nodes/displayable/__init__.py +2 -0
- vellum/workflows/nodes/displayable/web_search_node/__init__.py +3 -0
- vellum/workflows/nodes/displayable/web_search_node/node.py +133 -0
- vellum/workflows/resolvers/base.py +19 -1
- vellum/workflows/resolvers/resolver.py +97 -0
- vellum/workflows/resolvers/tests/test_resolver.py +131 -0
- vellum/workflows/resolvers/types.py +11 -0
- vellum/workflows/runner/runner.py +49 -1
- vellum/workflows/state/context.py +41 -7
- vellum/workflows/utils/zip.py +46 -0
- vellum/workflows/workflows/base.py +13 -0
- {vellum_ai-1.2.4.dist-info → vellum_ai-1.3.0.dist-info}/METADATA +1 -1
- {vellum_ai-1.2.4.dist-info → vellum_ai-1.3.0.dist-info}/RECORD +105 -43
- vellum_cli/tests/test_init.py +7 -24
- vellum_cli/tests/test_pull.py +27 -52
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attributes_serialization.py +7 -33
- vellum_ee/workflows/display/utils/events.py +19 -1
- vellum_ee/workflows/display/utils/tests/test_events.py +42 -0
- vellum_ee/workflows/tests/test_server.py +115 -0
- {vellum_ai-1.2.4.dist-info → vellum_ai-1.3.0.dist-info}/LICENSE +0 -0
- {vellum_ai-1.2.4.dist-info → vellum_ai-1.3.0.dist-info}/WHEEL +0 -0
- {vellum_ai-1.2.4.dist-info → vellum_ai-1.3.0.dist-info}/entry_points.txt +0 -0
vellum_cli/tests/test_pull.py
CHANGED
@@ -1,34 +1,17 @@
|
|
1
1
|
import pytest
|
2
|
-
import io
|
3
2
|
import json
|
4
3
|
import os
|
5
4
|
import tempfile
|
6
5
|
from unittest import mock
|
7
6
|
from uuid import uuid4
|
8
|
-
import zipfile
|
9
7
|
|
10
8
|
from click.testing import CliRunner
|
11
9
|
|
12
10
|
from vellum.client.core.api_error import ApiError
|
11
|
+
from vellum.workflows.utils.zip import zip_file_map
|
13
12
|
from vellum_cli import main as cli_main
|
14
13
|
|
15
14
|
|
16
|
-
def _zip_file_map(file_map: dict[str, str]) -> bytes:
|
17
|
-
# Create an in-memory bytes buffer to store the zip
|
18
|
-
zip_buffer = io.BytesIO()
|
19
|
-
|
20
|
-
# Create zip file and add files from file_map
|
21
|
-
with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
|
22
|
-
for filename, content in file_map.items():
|
23
|
-
zip_file.writestr(filename, content)
|
24
|
-
|
25
|
-
# Get the bytes from the buffer
|
26
|
-
zip_bytes = zip_buffer.getvalue()
|
27
|
-
zip_buffer.close()
|
28
|
-
|
29
|
-
return zip_bytes
|
30
|
-
|
31
|
-
|
32
15
|
@pytest.mark.parametrize(
|
33
16
|
"base_command",
|
34
17
|
[
|
@@ -44,7 +27,7 @@ def test_pull(vellum_client, mock_module, base_command):
|
|
44
27
|
workflow_sandbox_id = mock_module.workflow_sandbox_id
|
45
28
|
|
46
29
|
# AND the workflow pull API call returns a zip file
|
47
|
-
vellum_client.workflows.pull.return_value = iter([
|
30
|
+
vellum_client.workflows.pull.return_value = iter([zip_file_map({"workflow.py": "print('hello')"})])
|
48
31
|
|
49
32
|
# WHEN the user runs the pull command
|
50
33
|
runner = CliRunner()
|
@@ -89,7 +72,7 @@ def test_pull__second_module(vellum_client, mock_module):
|
|
89
72
|
set_pyproject_toml = mock_module.set_pyproject_toml
|
90
73
|
|
91
74
|
# AND the workflow pull API call returns a zip file
|
92
|
-
vellum_client.workflows.pull.return_value = iter([
|
75
|
+
vellum_client.workflows.pull.return_value = iter([zip_file_map({"workflow.py": "print('hello')"})])
|
93
76
|
|
94
77
|
# AND the module we're about to pull is configured second
|
95
78
|
set_pyproject_toml(
|
@@ -134,7 +117,7 @@ def test_pull__with_target_dir(vellum_client, mock_module, base_command):
|
|
134
117
|
os.makedirs(target_dir, exist_ok=True)
|
135
118
|
|
136
119
|
# AND the workflow pull API call returns a zip file
|
137
|
-
vellum_client.workflows.pull.return_value = iter([
|
120
|
+
vellum_client.workflows.pull.return_value = iter([zip_file_map({"workflow.py": "print('hello')"})])
|
138
121
|
|
139
122
|
# WHEN the user runs the pull command with target-dir
|
140
123
|
runner = CliRunner()
|
@@ -195,7 +178,7 @@ def test_pull__with_nested_target_dir(vellum_client, mock_module, base_command):
|
|
195
178
|
nested_target_dir = os.path.join(temp_dir, "dir-1", "dir-2")
|
196
179
|
|
197
180
|
# AND the workflow pull API call returns a zip file
|
198
|
-
vellum_client.workflows.pull.return_value = iter([
|
181
|
+
vellum_client.workflows.pull.return_value = iter([zip_file_map({"workflow.py": "print('hello')"})])
|
199
182
|
|
200
183
|
# WHEN the user runs the pull command with nested target-dir
|
201
184
|
runner = CliRunner()
|
@@ -250,11 +233,7 @@ def test_pull__sandbox_id_with_no_config(vellum_client):
|
|
250
233
|
|
251
234
|
# AND the workflow pull API call returns a zip file
|
252
235
|
vellum_client.workflows.pull.return_value = iter(
|
253
|
-
[
|
254
|
-
_zip_file_map(
|
255
|
-
{"workflow.py": "print('hello')", "metadata.json": json.dumps({"label": "Super Cool Workflow"})}
|
256
|
-
)
|
257
|
-
]
|
236
|
+
[zip_file_map({"workflow.py": "print('hello')", "metadata.json": json.dumps({"label": "Super Cool Workflow"})})]
|
258
237
|
)
|
259
238
|
|
260
239
|
# AND we are currently in a new directory
|
@@ -309,11 +288,7 @@ def test_pull__sandbox_id_with_other_workflow_configured(vellum_client, mock_mod
|
|
309
288
|
|
310
289
|
# AND the workflow pull API call returns a zip file
|
311
290
|
vellum_client.workflows.pull.return_value = iter(
|
312
|
-
[
|
313
|
-
_zip_file_map(
|
314
|
-
{"workflow.py": "print('hello')", "metadata.json": json.dumps({"label": "Super Cool Workflow"})}
|
315
|
-
)
|
316
|
-
]
|
291
|
+
[zip_file_map({"workflow.py": "print('hello')", "metadata.json": json.dumps({"label": "Super Cool Workflow"})})]
|
317
292
|
)
|
318
293
|
|
319
294
|
# WHEN the user runs the pull command with the new workflow sandbox id
|
@@ -343,7 +318,7 @@ def test_pull__workflow_deployment_with_no_config(vellum_client):
|
|
343
318
|
# AND the workflow pull API call returns a zip file
|
344
319
|
vellum_client.workflows.pull.return_value = iter(
|
345
320
|
[
|
346
|
-
|
321
|
+
zip_file_map(
|
347
322
|
{
|
348
323
|
"workflow.py": "print('hello')",
|
349
324
|
"metadata.json": json.dumps(
|
@@ -414,7 +389,7 @@ def test_pull__both_workflow_sandbox_id_and_deployment(vellum_client):
|
|
414
389
|
workflow_deployment = "my-deployment"
|
415
390
|
|
416
391
|
# AND the workflow pull API call returns a zip file
|
417
|
-
vellum_client.workflows.pull.return_value = iter([
|
392
|
+
vellum_client.workflows.pull.return_value = iter([zip_file_map({"workflow.py": "print('hello')"})])
|
418
393
|
|
419
394
|
# AND we are currently in a new directory
|
420
395
|
current_dir = os.getcwd()
|
@@ -447,7 +422,7 @@ def test_pull__remove_missing_files(vellum_client, mock_module):
|
|
447
422
|
module = mock_module.module
|
448
423
|
|
449
424
|
# AND the workflow pull API call returns a zip file
|
450
|
-
vellum_client.workflows.pull.return_value = iter([
|
425
|
+
vellum_client.workflows.pull.return_value = iter([zip_file_map({"workflow.py": "print('hello')"})])
|
451
426
|
|
452
427
|
# AND there is already a different file in the module directory
|
453
428
|
other_file_path = os.path.join(temp_dir, *module.split("."), "other_file.py")
|
@@ -478,7 +453,7 @@ def test_pull__remove_missing_files__ignore_pattern(vellum_client, mock_module):
|
|
478
453
|
set_pyproject_toml = mock_module.set_pyproject_toml
|
479
454
|
|
480
455
|
# AND the workflow pull API call returns a zip file
|
481
|
-
vellum_client.workflows.pull.return_value = iter([
|
456
|
+
vellum_client.workflows.pull.return_value = iter([zip_file_map({"workflow.py": "print('hello')"})])
|
482
457
|
|
483
458
|
# AND there is already a different file in the module directory
|
484
459
|
other_file_path = os.path.join(temp_dir, *module.split("."), "other_file.py")
|
@@ -530,7 +505,7 @@ def test_pull__include_json(vellum_client, mock_module):
|
|
530
505
|
|
531
506
|
# AND the workflow pull API call returns a zip file
|
532
507
|
vellum_client.workflows.pull.return_value = iter(
|
533
|
-
[
|
508
|
+
[zip_file_map({"workflow.py": "print('hello')", "workflow.json": "{}"})]
|
534
509
|
)
|
535
510
|
|
536
511
|
# WHEN the user runs the pull command
|
@@ -552,7 +527,7 @@ def test_pull__exclude_code(vellum_client, mock_module):
|
|
552
527
|
|
553
528
|
# AND the workflow pull API call returns a zip file
|
554
529
|
vellum_client.workflows.pull.return_value = iter(
|
555
|
-
[
|
530
|
+
[zip_file_map({"workflow.py": "print('hello')", "workflow.json": "{}"})]
|
556
531
|
)
|
557
532
|
|
558
533
|
# WHEN the user runs the pull command
|
@@ -606,7 +581,7 @@ def test_pull__sandbox_id_with_other_workflow_deployment_in_lock(vellum_client,
|
|
606
581
|
# AND the workflow pull API call returns a zip file
|
607
582
|
vellum_client.workflows.pull.return_value = iter(
|
608
583
|
[
|
609
|
-
|
584
|
+
zip_file_map(
|
610
585
|
{
|
611
586
|
"workflow.py": "print('hello')",
|
612
587
|
"metadata.json": json.dumps(
|
@@ -671,7 +646,7 @@ def test_pull__handle_error_log(vellum_client, mock_module):
|
|
671
646
|
|
672
647
|
# AND the workflow pull API call returns a zip file with an error log
|
673
648
|
vellum_client.workflows.pull.return_value = iter(
|
674
|
-
[
|
649
|
+
[zip_file_map({"workflow.py": "print('hello')", "error.log": "test error"})]
|
675
650
|
)
|
676
651
|
|
677
652
|
# WHEN the user runs the pull command with the new workflow sandbox id
|
@@ -693,7 +668,7 @@ def test_pull__strict__with_error(vellum_client, mock_module):
|
|
693
668
|
workflow_sandbox_id = mock_module.workflow_sandbox_id
|
694
669
|
|
695
670
|
# AND the workflow pull API call returns a zip file
|
696
|
-
vellum_client.workflows.pull.return_value = iter([
|
671
|
+
vellum_client.workflows.pull.return_value = iter([zip_file_map({"workflow.py": "print('hello')"})])
|
697
672
|
|
698
673
|
# WHEN the user runs the pull command with the new workflow sandbox id
|
699
674
|
runner = CliRunner()
|
@@ -715,7 +690,7 @@ def test_pull__include_sandbox(vellum_client, mock_module):
|
|
715
690
|
|
716
691
|
# AND the workflow pull API call returns a zip file
|
717
692
|
vellum_client.workflows.pull.return_value = iter(
|
718
|
-
[
|
693
|
+
[zip_file_map({"workflow.py": "print('hello')", "sandbox.py": "print('hello')"})]
|
719
694
|
)
|
720
695
|
|
721
696
|
# WHEN the user runs the pull command
|
@@ -744,7 +719,7 @@ def test_pull__same_pull_twice__one_entry_in_lockfile(vellum_client, mock_module
|
|
744
719
|
workflow_sandbox_id = mock_module.workflow_sandbox_id
|
745
720
|
|
746
721
|
# AND the workflow pull API call returns a zip file both times
|
747
|
-
zip_contents =
|
722
|
+
zip_contents = zip_file_map({"workflow.py": "print('hello')"})
|
748
723
|
responses = iter([zip_contents, zip_contents])
|
749
724
|
|
750
725
|
def workflows_pull_side_effect(*_args, **_kwargs):
|
@@ -780,7 +755,7 @@ def test_pull__module_not_in_config(vellum_client, mock_module):
|
|
780
755
|
set_pyproject_toml({"workflows": []})
|
781
756
|
|
782
757
|
# AND the workflow pull API call returns a zip file
|
783
|
-
vellum_client.workflows.pull.return_value = iter([
|
758
|
+
vellum_client.workflows.pull.return_value = iter([zip_file_map({"workflow.py": "print('hello')"})])
|
784
759
|
|
785
760
|
# WHEN the user runs the pull command again with the workflow sandbox id and module
|
786
761
|
runner = CliRunner()
|
@@ -833,7 +808,7 @@ def test_pull__multiple_instances_of_same_module__keep_when_pulling_another_modu
|
|
833
808
|
json.dump(lock_data, f)
|
834
809
|
|
835
810
|
# AND the workflow pull API call returns a zip file
|
836
|
-
vellum_client.workflows.pull.return_value = iter([
|
811
|
+
vellum_client.workflows.pull.return_value = iter([zip_file_map({"workflow.py": "print('hello')"})])
|
837
812
|
|
838
813
|
# WHEN the user runs the pull command on the new module
|
839
814
|
runner = CliRunner()
|
@@ -856,7 +831,7 @@ def test_pull__module_name_from_deployment_name(vellum_client):
|
|
856
831
|
deployment_name = "Test Deployment"
|
857
832
|
vellum_client.workflows.pull.return_value = iter(
|
858
833
|
[
|
859
|
-
|
834
|
+
zip_file_map(
|
860
835
|
{
|
861
836
|
"workflow.py": "print('hello')",
|
862
837
|
"metadata.json": json.dumps({"deployment_name": deployment_name, "label": "Some Label"}),
|
@@ -995,7 +970,7 @@ def test_pull__workflow_deployment_adds_deployment_to_config(vellum_client, work
|
|
995
970
|
# AND the workflow pull API call returns a zip file with metadata
|
996
971
|
vellum_client.workflows.pull.return_value = iter(
|
997
972
|
[
|
998
|
-
|
973
|
+
zip_file_map(
|
999
974
|
{
|
1000
975
|
"workflow.py": "print('hello')",
|
1001
976
|
"metadata.json": json.dumps(
|
@@ -1082,7 +1057,7 @@ def test_pull__workflow_deployment_name_is_uuid(vellum_client):
|
|
1082
1057
|
updated_label = "Updated Label"
|
1083
1058
|
vellum_client.workflows.pull.return_value = iter(
|
1084
1059
|
[
|
1085
|
-
|
1060
|
+
zip_file_map(
|
1086
1061
|
{
|
1087
1062
|
"workflow.py": "print('hello')",
|
1088
1063
|
"metadata.json": json.dumps(
|
@@ -1170,7 +1145,7 @@ def test_pull__workflow_deployment_updates_existing_deployment(vellum_client, ge
|
|
1170
1145
|
updated_label = "Updated Label"
|
1171
1146
|
vellum_client.workflows.pull.return_value = iter(
|
1172
1147
|
[
|
1173
|
-
|
1148
|
+
zip_file_map(
|
1174
1149
|
{
|
1175
1150
|
"workflow.py": "print('hello')",
|
1176
1151
|
"metadata.json": json.dumps(
|
@@ -1218,7 +1193,7 @@ def test_pull__workflow_deployment_with_name_and_id(vellum_client):
|
|
1218
1193
|
# AND the workflow pull API call returns a zip file with metadata
|
1219
1194
|
vellum_client.workflows.pull.return_value = iter(
|
1220
1195
|
[
|
1221
|
-
|
1196
|
+
zip_file_map(
|
1222
1197
|
{
|
1223
1198
|
"workflow.py": "print('hello')",
|
1224
1199
|
"metadata.json": json.dumps(
|
@@ -1264,7 +1239,7 @@ def test_pull__workflow_deployment_with_name_and_id(vellum_client):
|
|
1264
1239
|
# AND pull with name will not add a new deployment to the config
|
1265
1240
|
vellum_client.workflows.pull.return_value = iter(
|
1266
1241
|
[
|
1267
|
-
|
1242
|
+
zip_file_map(
|
1268
1243
|
{
|
1269
1244
|
"workflow.py": "print('hello')",
|
1270
1245
|
"metadata.json": json.dumps(
|
@@ -1323,7 +1298,7 @@ MY_OTHER_VELLUM_API_KEY=aaabbbcccddd
|
|
1323
1298
|
|
1324
1299
|
# AND the workflow pull API call returns a zip file
|
1325
1300
|
vellum_client_class.return_value.workflows.pull.return_value = iter(
|
1326
|
-
[
|
1301
|
+
[zip_file_map({"workflow.py": "print('hello')"})]
|
1327
1302
|
)
|
1328
1303
|
|
1329
1304
|
# WHEN calling `vellum pull` with --workspace
|
@@ -142,41 +142,15 @@ def test_serialize_node__lazy_reference(serialize_node):
|
|
142
142
|
attr: str = LazyReference(lambda: ConstantValueReference("hello"))
|
143
143
|
|
144
144
|
serialized_node = serialize_node(LazyReferenceGenericNode)
|
145
|
+
attributes = serialized_node["attributes"]
|
145
146
|
|
146
|
-
assert
|
147
|
+
assert attributes == [
|
147
148
|
{
|
148
|
-
"id": "
|
149
|
-
"
|
150
|
-
"type": "
|
151
|
-
|
152
|
-
|
153
|
-
"definition": {
|
154
|
-
"name": "LazyReferenceGenericNode",
|
155
|
-
"module": [
|
156
|
-
"vellum_ee",
|
157
|
-
"workflows",
|
158
|
-
"display",
|
159
|
-
"tests",
|
160
|
-
"workflow_serialization",
|
161
|
-
"generic_nodes",
|
162
|
-
"test_attributes_serialization",
|
163
|
-
],
|
164
|
-
},
|
165
|
-
"trigger": {"id": "14ec4d19-13e5-4db3-94fa-4e15274bffc7", "merge_behavior": "AWAIT_ATTRIBUTES"},
|
166
|
-
"ports": [{"id": "2dba7224-a376-4780-8414-2b50601f9283", "name": "default", "type": "DEFAULT"}],
|
167
|
-
"adornments": None,
|
168
|
-
"attributes": [
|
169
|
-
{
|
170
|
-
"id": "7ae37eb4-18c8-49e1-b5ac-6369ce7ed5dd",
|
171
|
-
"name": "attr",
|
172
|
-
"value": {"type": "CONSTANT_VALUE", "value": {"type": "STRING", "value": "hello"}},
|
173
|
-
}
|
174
|
-
],
|
175
|
-
"outputs": [],
|
176
|
-
},
|
177
|
-
serialized_node,
|
178
|
-
ignore_order=True,
|
179
|
-
)
|
149
|
+
"id": "7ae37eb4-18c8-49e1-b5ac-6369ce7ed5dd",
|
150
|
+
"name": "attr",
|
151
|
+
"value": {"type": "CONSTANT_VALUE", "value": {"type": "STRING", "value": "hello"}},
|
152
|
+
}
|
153
|
+
]
|
180
154
|
|
181
155
|
|
182
156
|
def test_serialize_node__lazy_reference_with_string():
|
@@ -7,6 +7,24 @@ from vellum_ee.workflows.display.utils.registry import (
|
|
7
7
|
from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
|
8
8
|
|
9
9
|
|
10
|
+
def _should_mark_workflow_dynamic(event: WorkflowExecutionInitiatedEvent) -> bool:
|
11
|
+
"""
|
12
|
+
Check if workflow should be marked as dynamic based on execution context.
|
13
|
+
Returns True if parent.type == WORKFLOW_RELEASE_TAG and parent.parent.type == WORKFLOW_NODE.
|
14
|
+
"""
|
15
|
+
if not event.parent:
|
16
|
+
return False
|
17
|
+
|
18
|
+
parent = event.parent
|
19
|
+
if parent.type != "WORKFLOW_RELEASE_TAG":
|
20
|
+
return False
|
21
|
+
|
22
|
+
if not parent.parent or parent.parent.type != "WORKFLOW_NODE":
|
23
|
+
return False
|
24
|
+
|
25
|
+
return True
|
26
|
+
|
27
|
+
|
10
28
|
def event_enricher(event: WorkflowExecutionInitiatedEvent) -> WorkflowExecutionInitiatedEvent:
|
11
29
|
workflow_definition = event.body.workflow_definition
|
12
30
|
workflow_display = get_workflow_display(
|
@@ -16,7 +34,7 @@ def event_enricher(event: WorkflowExecutionInitiatedEvent) -> WorkflowExecutionI
|
|
16
34
|
)
|
17
35
|
register_workflow_display_context(event.span_id, workflow_display.display_context)
|
18
36
|
|
19
|
-
if event.body.workflow_definition.is_dynamic:
|
37
|
+
if event.body.workflow_definition.is_dynamic or _should_mark_workflow_dynamic(event):
|
20
38
|
register_workflow_display_class(workflow_definition, workflow_display.__class__)
|
21
39
|
workflow_version_exec_config = workflow_display.serialize()
|
22
40
|
setattr(event.body, "workflow_version_exec_config", workflow_version_exec_config)
|
@@ -2,8 +2,10 @@ import pytest
|
|
2
2
|
from uuid import uuid4
|
3
3
|
from typing import Optional
|
4
4
|
|
5
|
+
from vellum.workflows.events.types import NodeParentContext, WorkflowDeploymentParentContext
|
5
6
|
from vellum.workflows.events.workflow import WorkflowExecutionInitiatedBody, WorkflowExecutionInitiatedEvent
|
6
7
|
from vellum.workflows.inputs.base import BaseInputs
|
8
|
+
from vellum.workflows.outputs.base import BaseOutputs
|
7
9
|
from vellum.workflows.workflows.base import BaseWorkflow
|
8
10
|
from vellum_ee.workflows.display.utils.events import event_enricher
|
9
11
|
|
@@ -67,3 +69,43 @@ def test_event_enricher_static_workflow(is_dynamic: bool, expected_config: Optio
|
|
67
69
|
|
68
70
|
# AND workflow_version_exec_config is set to the expected config
|
69
71
|
assert event.body.workflow_version_exec_config == expected_config
|
72
|
+
|
73
|
+
|
74
|
+
def test_event_enricher_marks_subworkflow_deployment_as_dynamic():
|
75
|
+
"""Test that event_enricher treats subworkflow deployments as dynamic."""
|
76
|
+
|
77
|
+
class TestWorkflow(BaseWorkflow):
|
78
|
+
is_dynamic = False
|
79
|
+
|
80
|
+
class Outputs(BaseOutputs):
|
81
|
+
pass
|
82
|
+
|
83
|
+
event: WorkflowExecutionInitiatedEvent = WorkflowExecutionInitiatedEvent(
|
84
|
+
trace_id=uuid4(),
|
85
|
+
span_id=uuid4(),
|
86
|
+
parent=WorkflowDeploymentParentContext(
|
87
|
+
span_id=uuid4(),
|
88
|
+
deployment_id=uuid4(),
|
89
|
+
deployment_name="test-deployment",
|
90
|
+
deployment_history_item_id=uuid4(),
|
91
|
+
release_tag_id=uuid4(),
|
92
|
+
release_tag_name="test-tag",
|
93
|
+
workflow_version_id=uuid4(),
|
94
|
+
external_id=None,
|
95
|
+
metadata=None,
|
96
|
+
parent=NodeParentContext(
|
97
|
+
span_id=uuid4(),
|
98
|
+
node_definition=TestWorkflow,
|
99
|
+
parent=None,
|
100
|
+
),
|
101
|
+
),
|
102
|
+
body=WorkflowExecutionInitiatedBody(
|
103
|
+
workflow_definition=TestWorkflow,
|
104
|
+
inputs=BaseInputs(),
|
105
|
+
),
|
106
|
+
)
|
107
|
+
|
108
|
+
enriched_event = event_enricher(event)
|
109
|
+
|
110
|
+
assert hasattr(enriched_event.body, "workflow_version_exec_config")
|
111
|
+
assert enriched_event.body.workflow_version_exec_config is not None
|
@@ -9,6 +9,7 @@ from vellum.workflows import BaseWorkflow
|
|
9
9
|
from vellum.workflows.nodes import BaseNode
|
10
10
|
from vellum.workflows.state.context import WorkflowContext
|
11
11
|
from vellum.workflows.utils.uuids import generate_workflow_deployment_prefix
|
12
|
+
from vellum.workflows.utils.zip import zip_file_map
|
12
13
|
from vellum_ee.workflows.display.workflows.base_workflow_display import BaseWorkflowDisplay
|
13
14
|
from vellum_ee.workflows.server.virtual_file_loader import VirtualFileFinder
|
14
15
|
|
@@ -661,3 +662,117 @@ __all__ = ["TestNode"]
|
|
661
662
|
|
662
663
|
# AND the method should return a workflow (not None) - this will pass once implemented
|
663
664
|
assert event.name == "workflow.execution.fulfilled"
|
665
|
+
|
666
|
+
|
667
|
+
def test_resolve_workflow_deployment__uses_pull_api_with_inputs_deployment_name(vellum_client):
|
668
|
+
"""
|
669
|
+
Test that resolve_workflow_deployment uses the pull API to fetch subworkflow files
|
670
|
+
when the deployment name comes from Inputs.deployment_name.
|
671
|
+
"""
|
672
|
+
# GIVEN a deployment name and release tag
|
673
|
+
deployment_name = "test_deployment"
|
674
|
+
release_tag = "LATEST"
|
675
|
+
|
676
|
+
test_node_code = """
|
677
|
+
from vellum.workflows.nodes.bases.base import BaseNode
|
678
|
+
from vellum.workflows.outputs import BaseOutputs
|
679
|
+
|
680
|
+
class TestNode(BaseNode):
|
681
|
+
template = "Hello"
|
682
|
+
|
683
|
+
class Outputs(BaseOutputs):
|
684
|
+
result: str
|
685
|
+
|
686
|
+
def run(self):
|
687
|
+
return self.Outputs(result="Hello, {template}")
|
688
|
+
"""
|
689
|
+
|
690
|
+
mock_workflow_code = """
|
691
|
+
from vellum.workflows import BaseWorkflow
|
692
|
+
from .nodes.test_node import TestNode
|
693
|
+
|
694
|
+
class ResolvedWorkflow(BaseWorkflow):
|
695
|
+
graph = TestNode
|
696
|
+
"""
|
697
|
+
|
698
|
+
inputs_code = """
|
699
|
+
from vellum.workflows.inputs import BaseInputs
|
700
|
+
|
701
|
+
class Inputs(BaseInputs):
|
702
|
+
deployment_name: str
|
703
|
+
"""
|
704
|
+
|
705
|
+
parent_workflow_code = """
|
706
|
+
from vellum.workflows import BaseWorkflow
|
707
|
+
from .inputs import Inputs
|
708
|
+
from .nodes.subworkflow_deployment_node import TestSubworkflowDeploymentNode
|
709
|
+
from vellum.workflows.state import BaseState
|
710
|
+
|
711
|
+
class ParentWorkflow(BaseWorkflow[Inputs, BaseState]):
|
712
|
+
graph = TestSubworkflowDeploymentNode
|
713
|
+
"""
|
714
|
+
|
715
|
+
parent_node_code = """
|
716
|
+
from vellum.workflows.nodes import SubworkflowDeploymentNode
|
717
|
+
from vellum.workflows.outputs import BaseOutputs
|
718
|
+
from ..inputs import Inputs
|
719
|
+
|
720
|
+
class TestSubworkflowDeploymentNode(SubworkflowDeploymentNode):
|
721
|
+
deployment = Inputs.deployment_name
|
722
|
+
|
723
|
+
class Outputs(BaseOutputs):
|
724
|
+
result: str
|
725
|
+
|
726
|
+
subworkflow_inputs = {"message": "test"}
|
727
|
+
"""
|
728
|
+
|
729
|
+
subworkflow_files = {
|
730
|
+
"__init__.py": "",
|
731
|
+
"workflow.py": mock_workflow_code,
|
732
|
+
"nodes/__init__.py": """
|
733
|
+
from .test_node import TestNode
|
734
|
+
|
735
|
+
__all__ = ["TestNode"]
|
736
|
+
""",
|
737
|
+
"nodes/test_node.py": test_node_code,
|
738
|
+
}
|
739
|
+
|
740
|
+
parent_files = {
|
741
|
+
"__init__.py": "",
|
742
|
+
"inputs.py": inputs_code,
|
743
|
+
"workflow.py": parent_workflow_code,
|
744
|
+
"nodes/__init__.py": """
|
745
|
+
from .subworkflow_deployment_node import TestSubworkflowDeploymentNode
|
746
|
+
|
747
|
+
__all__ = ["TestSubworkflowDeploymentNode"]
|
748
|
+
""",
|
749
|
+
"nodes/subworkflow_deployment_node.py": parent_node_code,
|
750
|
+
}
|
751
|
+
|
752
|
+
namespace = str(uuid4())
|
753
|
+
|
754
|
+
# AND the virtual file loader is registered for the parent workflow
|
755
|
+
finder = VirtualFileFinder(parent_files, namespace)
|
756
|
+
sys.meta_path.append(finder)
|
757
|
+
|
758
|
+
vellum_client.workflows.pull.return_value = iter([zip_file_map(subworkflow_files)])
|
759
|
+
|
760
|
+
# WHEN we execute the root workflow with the mocked client
|
761
|
+
Workflow = BaseWorkflow.load_from_module(namespace)
|
762
|
+
Inputs = Workflow.get_inputs_class()
|
763
|
+
|
764
|
+
workflow = Workflow(context=WorkflowContext(namespace=namespace, generated_files=parent_files))
|
765
|
+
final_event = workflow.run(inputs=Inputs(deployment_name=deployment_name))
|
766
|
+
|
767
|
+
# THEN the method should return a workflow (not None) - this will pass once implemented
|
768
|
+
assert final_event.name == "workflow.execution.fulfilled", final_event
|
769
|
+
|
770
|
+
# AND the pull API should have been called with the correct deployment name, release tag, and version
|
771
|
+
args, kwargs = vellum_client.workflows.pull.call_args
|
772
|
+
assert args[0] == deployment_name
|
773
|
+
assert kwargs["release_tag"] == release_tag
|
774
|
+
assert kwargs["version"].startswith(">=")
|
775
|
+
assert ".0.0,<=" in kwargs["version"]
|
776
|
+
|
777
|
+
# AND the X-Vellum-Always-Success header should be included for graceful error handling
|
778
|
+
assert kwargs["request_options"]["additional_headers"]["X-Vellum-Always-Success"] == "true"
|
File without changes
|
File without changes
|
File without changes
|