vellum-ai 0.14.45__py3-none-any.whl → 0.14.47__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/README.md +2 -2
- vellum/client/__init__.py +72 -6
- vellum/client/core/client_wrapper.py +1 -1
- vellum/client/core/file.py +13 -8
- vellum/client/core/http_client.py +26 -14
- vellum/client/core/pydantic_utilities.py +2 -2
- vellum/client/core/request_options.py +3 -0
- vellum/client/resources/ad_hoc/client.py +14 -2
- vellum/client/resources/container_images/client.py +6 -0
- vellum/client/resources/deployments/client.py +12 -0
- vellum/client/resources/document_indexes/client.py +18 -0
- vellum/client/resources/documents/client.py +6 -0
- vellum/client/resources/folder_entities/client.py +6 -0
- vellum/client/resources/metric_definitions/client.py +6 -0
- vellum/client/resources/prompts/client.py +6 -0
- vellum/client/resources/sandboxes/client.py +12 -0
- vellum/client/resources/test_suite_runs/client.py +6 -0
- vellum/client/resources/test_suites/client.py +2 -2
- vellum/client/resources/workflow_deployments/client.py +6 -0
- vellum/client/resources/workflow_sandboxes/client.py +6 -0
- vellum/client/resources/workflows/client.py +6 -4
- vellum/client/resources/workspace_secrets/client.py +6 -0
- vellum/client/types/api_request_parent_context.py +0 -6
- vellum/client/types/array_input.py +0 -5
- vellum/client/types/code_execution_node_array_result.py +0 -5
- vellum/client/types/code_execution_node_result.py +0 -5
- vellum/client/types/code_execution_node_result_data.py +0 -5
- vellum/client/types/code_executor_response.py +0 -5
- vellum/client/types/create_test_suite_test_case_request.py +0 -5
- vellum/client/types/deployment_history_item.py +0 -5
- vellum/client/types/deployment_read.py +0 -5
- vellum/client/types/execute_workflow_response.py +0 -5
- vellum/client/types/execution_array_vellum_value.py +0 -5
- vellum/client/types/external_test_case_execution.py +0 -5
- vellum/client/types/external_test_case_execution_request.py +0 -5
- vellum/client/types/fulfilled_execute_workflow_workflow_result_event.py +0 -7
- vellum/client/types/fulfilled_workflow_node_result_event.py +0 -5
- vellum/client/types/initiated_workflow_node_result_event.py +0 -5
- vellum/client/types/metadata_filter_config_request.py +0 -5
- vellum/client/types/metric_definition_execution.py +0 -5
- vellum/client/types/metric_definition_history_item.py +0 -5
- vellum/client/types/named_test_case_array_variable_value.py +0 -5
- vellum/client/types/named_test_case_array_variable_value_request.py +0 -7
- vellum/client/types/node_execution_fulfilled_event.py +0 -11
- vellum/client/types/node_execution_initiated_event.py +0 -11
- vellum/client/types/node_execution_paused_event.py +0 -11
- vellum/client/types/node_execution_rejected_event.py +0 -11
- vellum/client/types/node_execution_resumed_event.py +0 -11
- vellum/client/types/node_execution_span.py +0 -11
- vellum/client/types/node_execution_streaming_event.py +0 -11
- vellum/client/types/node_input_compiled_array_value.py +0 -5
- vellum/client/types/node_output_compiled_array_value.py +0 -5
- vellum/client/types/node_parent_context.py +0 -6
- vellum/client/types/paginated_slim_deployment_read_list.py +0 -5
- vellum/client/types/paginated_slim_workflow_deployment_list.py +0 -5
- vellum/client/types/paginated_test_suite_run_execution_list.py +0 -5
- vellum/client/types/paginated_test_suite_test_case_list.py +0 -5
- vellum/client/types/prompt_deployment_parent_context.py +0 -6
- vellum/client/types/prompt_exec_config.py +0 -6
- vellum/client/types/rejected_workflow_node_result_event.py +0 -5
- vellum/client/types/replace_test_suite_test_case_request.py +0 -5
- vellum/client/types/search_filters_request.py +0 -7
- vellum/client/types/search_request_options_request.py +0 -7
- vellum/client/types/slim_deployment_read.py +0 -5
- vellum/client/types/slim_workflow_deployment.py +0 -5
- vellum/client/types/slim_workflow_execution_read.py +0 -12
- vellum/client/types/span_link.py +0 -6
- vellum/client/types/streaming_workflow_node_result_event.py +0 -5
- vellum/client/types/templating_node_array_result.py +0 -5
- vellum/client/types/templating_node_result.py +0 -5
- vellum/client/types/templating_node_result_data.py +0 -5
- vellum/client/types/terminal_node_array_result.py +0 -5
- vellum/client/types/terminal_node_result.py +0 -5
- vellum/client/types/terminal_node_result_data.py +0 -5
- vellum/client/types/test_case_array_variable_value.py +0 -5
- vellum/client/types/test_suite_run_execution.py +0 -5
- vellum/client/types/test_suite_run_execution_array_output.py +0 -5
- vellum/client/types/test_suite_run_execution_metric_result.py +0 -5
- vellum/client/types/test_suite_run_external_exec_config.py +0 -5
- vellum/client/types/test_suite_run_external_exec_config_data.py +0 -5
- vellum/client/types/test_suite_run_external_exec_config_data_request.py +0 -7
- vellum/client/types/test_suite_run_external_exec_config_request.py +0 -7
- vellum/client/types/test_suite_run_metric_array_output.py +0 -5
- vellum/client/types/test_suite_run_read.py +0 -5
- vellum/client/types/test_suite_test_case.py +0 -5
- vellum/client/types/test_suite_test_case_create_bulk_operation_request.py +0 -7
- vellum/client/types/test_suite_test_case_replace_bulk_operation_request.py +0 -7
- vellum/client/types/test_suite_test_case_upsert_bulk_operation_request.py +0 -7
- vellum/client/types/upsert_test_suite_test_case_request.py +0 -5
- vellum/client/types/vellum_value_logical_condition_group_request.py +0 -3
- vellum/client/types/vellum_value_logical_condition_request.py +0 -5
- vellum/client/types/vellum_variable.py +0 -5
- vellum/client/types/workflow_deployment_event_executions_response.py +0 -26
- vellum/client/types/workflow_deployment_history_item.py +0 -5
- vellum/client/types/workflow_deployment_parent_context.py +0 -6
- vellum/client/types/workflow_deployment_read.py +0 -5
- vellum/client/types/workflow_deployment_release.py +0 -5
- vellum/client/types/workflow_deployment_release_workflow_version.py +0 -5
- vellum/client/types/workflow_event_execution_read.py +0 -12
- vellum/client/types/workflow_execution_actual.py +0 -5
- vellum/client/types/workflow_execution_fulfilled_event.py +0 -11
- vellum/client/types/workflow_execution_initiated_event.py +0 -11
- vellum/client/types/workflow_execution_node_result_event.py +0 -5
- vellum/client/types/workflow_execution_paused_event.py +0 -11
- vellum/client/types/workflow_execution_rejected_event.py +0 -11
- vellum/client/types/workflow_execution_resumed_event.py +0 -11
- vellum/client/types/workflow_execution_snapshotted_event.py +0 -13
- vellum/client/types/workflow_execution_span.py +0 -11
- vellum/client/types/workflow_execution_streaming_event.py +0 -11
- vellum/client/types/workflow_execution_view_online_eval_metric_result.py +0 -7
- vellum/client/types/workflow_execution_workflow_result_event.py +0 -5
- vellum/client/types/workflow_output_array.py +0 -5
- vellum/client/types/workflow_parent_context.py +0 -6
- vellum/client/types/workflow_result_event.py +0 -5
- vellum/client/types/workflow_result_event_output_data_array.py +0 -5
- vellum/client/types/workflow_sandbox_parent_context.py +0 -6
- vellum/workflows/nodes/bases/base.py +26 -6
- vellum/workflows/nodes/bases/tests/test_base_node.py +30 -0
- vellum/workflows/nodes/core/try_node/node.py +3 -6
- vellum/workflows/nodes/core/try_node/tests/test_node.py +0 -24
- vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py +8 -14
- vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py +112 -0
- vellum/workflows/nodes/displayable/code_execution_node/utils.py +3 -54
- vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py +5 -6
- vellum/workflows/nodes/utils.py +8 -0
- vellum/workflows/types/code_execution_node_wrappers.py +69 -0
- vellum/workflows/vellum_client.py +19 -7
- {vellum_ai-0.14.45.dist-info → vellum_ai-0.14.47.dist-info}/METADATA +1 -1
- {vellum_ai-0.14.45.dist-info → vellum_ai-0.14.47.dist-info}/RECORD +162 -161
- vellum_cli/config.py +7 -2
- vellum_cli/push.py +5 -1
- vellum_cli/tests/test_push.py +192 -8
- vellum_ee/workflows/display/nodes/base_node_display.py +17 -6
- vellum_ee/workflows/display/nodes/get_node_display_class.py +4 -5
- vellum_ee/workflows/display/nodes/vellum/api_node.py +11 -0
- vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +5 -0
- vellum_ee/workflows/display/nodes/vellum/error_node.py +22 -16
- vellum_ee/workflows/display/nodes/vellum/guardrail_node.py +2 -0
- vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +46 -12
- vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +2 -0
- vellum_ee/workflows/display/nodes/vellum/map_node.py +2 -0
- vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py +3 -5
- vellum_ee/workflows/display/nodes/vellum/search_node.py +8 -0
- vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py +2 -5
- vellum_ee/workflows/display/nodes/vellum/templating_node.py +2 -0
- vellum_ee/workflows/display/nodes/vellum/tests/test_code_execution_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/tests/test_error_node.py +4 -0
- vellum_ee/workflows/display/nodes/vellum/tests/test_prompt_deployment_node.py +4 -3
- vellum_ee/workflows/display/nodes/vellum/tests/test_prompt_node.py +36 -2
- vellum_ee/workflows/display/nodes/vellum/tests/test_subworkflow_deployment_node.py +5 -4
- vellum_ee/workflows/display/nodes/vellum/tests/test_templating_node.py +1 -1
- vellum_ee/workflows/display/tests/test_base_workflow_display.py +44 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py +2 -4
- vellum_ee/workflows/display/types.py +3 -0
- vellum_ee/workflows/display/utils/expressions.py +3 -3
- vellum_ee/workflows/display/utils/vellum.py +1 -3
- vellum_ee/workflows/display/workflows/base_workflow_display.py +10 -0
- vellum_ee/workflows/display/workflows/get_vellum_workflow_display_class.py +3 -0
- vellum_ee/workflows/display/workflows/tests/test_workflow_display.py +53 -0
- {vellum_ai-0.14.45.dist-info → vellum_ai-0.14.47.dist-info}/LICENSE +0 -0
- {vellum_ai-0.14.45.dist-info → vellum_ai-0.14.47.dist-info}/WHEEL +0 -0
- {vellum_ai-0.14.45.dist-info → vellum_ai-0.14.47.dist-info}/entry_points.txt +0 -0
vellum_cli/config.py
CHANGED
@@ -16,10 +16,15 @@ PYPROJECT_TOML_PATH = "pyproject.toml"
|
|
16
16
|
|
17
17
|
class WorkspaceConfig(UniversalBaseModel):
|
18
18
|
name: str
|
19
|
-
api_key: str
|
19
|
+
api_key: str = "VELLUM_API_KEY"
|
20
|
+
api_url: Optional[str] = None
|
20
21
|
|
21
22
|
def merge(self, other: "WorkspaceConfig") -> "WorkspaceConfig":
|
22
|
-
return WorkspaceConfig(
|
23
|
+
return WorkspaceConfig(
|
24
|
+
name=self.name or other.name,
|
25
|
+
api_key=self.api_key or other.api_key,
|
26
|
+
api_url=self.api_url or other.api_url,
|
27
|
+
)
|
23
28
|
|
24
29
|
|
25
30
|
DEFAULT_WORKSPACE_CONFIG = WorkspaceConfig(name="default", api_key="VELLUM_API_KEY")
|
vellum_cli/push.py
CHANGED
@@ -97,6 +97,7 @@ def push_command(
|
|
97
97
|
|
98
98
|
client = create_vellum_client(
|
99
99
|
api_key=api_key,
|
100
|
+
api_url=workspace_config.api_url,
|
100
101
|
)
|
101
102
|
sys.path.insert(0, os.getcwd())
|
102
103
|
|
@@ -105,6 +106,7 @@ def push_command(
|
|
105
106
|
workflow = BaseWorkflow.load_from_module(workflow_config.module)
|
106
107
|
workflow_display = get_workflow_display(
|
107
108
|
workflow_class=workflow,
|
109
|
+
client=client,
|
108
110
|
dry_run=dry_run or False,
|
109
111
|
)
|
110
112
|
exec_config = workflow_display.serialize()
|
@@ -234,9 +236,11 @@ def push_command(
|
|
234
236
|
"""
|
235
237
|
) # type: ignore[attr-defined]
|
236
238
|
else:
|
239
|
+
default_api_url = client._client_wrapper._environment.default
|
240
|
+
base_url = default_api_url.split("/v1")[0].replace("//api.", "//app.")
|
237
241
|
logger.info(
|
238
242
|
f"""Successfully pushed {workflow_config.module} to Vellum!
|
239
|
-
Visit at:
|
243
|
+
Visit at: {base_url}/workflow-sandboxes/{response.workflow_sandbox_id}"""
|
240
244
|
)
|
241
245
|
|
242
246
|
if not workflow_config.workflow_sandbox_id:
|
vellum_cli/tests/test_push.py
CHANGED
@@ -7,6 +7,7 @@ from unittest import mock
|
|
7
7
|
from uuid import uuid4
|
8
8
|
|
9
9
|
from click.testing import CliRunner
|
10
|
+
from httpx import Response
|
10
11
|
|
11
12
|
from vellum.client.core.api_error import ApiError
|
12
13
|
from vellum.client.types.workflow_push_response import WorkflowPushResponse
|
@@ -29,19 +30,25 @@ def _extract_tar_gz(tar_gz_bytes: bytes) -> dict[str, str]:
|
|
29
30
|
return files
|
30
31
|
|
31
32
|
|
32
|
-
def
|
33
|
-
|
33
|
+
def _ensure_file(temp_dir: str, module: str, file_name: str, content: str) -> str:
|
34
|
+
file_path = os.path.join(temp_dir, *module.split("."), file_name)
|
35
|
+
base_dir = os.path.dirname(file_path)
|
34
36
|
os.makedirs(base_dir, exist_ok=True)
|
37
|
+
|
38
|
+
with open(file_path, "w") as f:
|
39
|
+
f.write(content)
|
40
|
+
|
41
|
+
return content
|
42
|
+
|
43
|
+
|
44
|
+
def _ensure_workflow_py(temp_dir: str, module: str) -> str:
|
35
45
|
workflow_py_file_content = """\
|
36
46
|
from vellum.workflows import BaseWorkflow
|
37
47
|
|
38
48
|
class ExampleWorkflow(BaseWorkflow):
|
39
49
|
pass
|
40
50
|
"""
|
41
|
-
|
42
|
-
f.write(workflow_py_file_content)
|
43
|
-
|
44
|
-
return workflow_py_file_content
|
51
|
+
return _ensure_file(temp_dir, module, "workflow.py", workflow_py_file_content)
|
45
52
|
|
46
53
|
|
47
54
|
def test_push__no_config(mock_module):
|
@@ -133,6 +140,38 @@ def test_push__happy_path(mock_module, vellum_client, base_command):
|
|
133
140
|
assert extracted_files["workflow.py"] == workflow_py_file_content
|
134
141
|
|
135
142
|
|
143
|
+
def test_push__verify_default_url_in_raw_httpx_transport(mock_module, mock_httpx_transport):
|
144
|
+
# GIVEN a single workflow configured
|
145
|
+
module = mock_module.module
|
146
|
+
temp_dir = mock_module.temp_dir
|
147
|
+
_ensure_workflow_py(temp_dir, module)
|
148
|
+
|
149
|
+
# AND the push API call returns successfully
|
150
|
+
mock_httpx_transport.handle_request.return_value = Response(
|
151
|
+
status_code=200,
|
152
|
+
text=json.dumps(
|
153
|
+
{
|
154
|
+
"workflow_sandbox_id": str(uuid4()),
|
155
|
+
}
|
156
|
+
),
|
157
|
+
)
|
158
|
+
|
159
|
+
# WHEN calling `vellum push`
|
160
|
+
runner = CliRunner()
|
161
|
+
result = runner.invoke(cli_main, ["workflows", "push", module])
|
162
|
+
|
163
|
+
# THEN it should succeed
|
164
|
+
assert result.exit_code == 0
|
165
|
+
|
166
|
+
# AND we should have called the push API with the correct args
|
167
|
+
mock_httpx_transport.handle_request.assert_called_once()
|
168
|
+
request = mock_httpx_transport.handle_request.call_args[0][0]
|
169
|
+
assert str(request.url) == "https://api.vellum.ai/v1/workflows/push"
|
170
|
+
|
171
|
+
# AND the new URL is in the message at the end
|
172
|
+
assert "Visit at: https://app.vellum.ai/workflow-sandboxes/" in result.output
|
173
|
+
|
174
|
+
|
136
175
|
def test_push__no_config__module_found(mock_module, vellum_client):
|
137
176
|
# GIVEN no config file set
|
138
177
|
temp_dir = mock_module.temp_dir
|
@@ -486,7 +525,74 @@ Files that were different between the original project and the generated artifac
|
|
486
525
|
)
|
487
526
|
|
488
527
|
|
489
|
-
|
528
|
+
@pytest.mark.parametrize(
|
529
|
+
"file_data",
|
530
|
+
[
|
531
|
+
{
|
532
|
+
"workflow.py": """\
|
533
|
+
from vellum.workflows import BaseWorkflow
|
534
|
+
|
535
|
+
class ExampleWorkflow(BaseWorkflow):
|
536
|
+
pass
|
537
|
+
"""
|
538
|
+
},
|
539
|
+
{
|
540
|
+
"nodes/start_node.py": """\
|
541
|
+
from vellum.workflows.nodes import CodeExecutionNode
|
542
|
+
from vellum.workflows.references import VellumSecretReference
|
543
|
+
|
544
|
+
class StartNode(CodeExecutionNode):
|
545
|
+
code_inputs = {
|
546
|
+
"foo": VellumSecretReference("MY_SECRET_KEY"),
|
547
|
+
}
|
548
|
+
""",
|
549
|
+
"workflow.py": """\
|
550
|
+
from vellum.workflows import BaseWorkflow
|
551
|
+
from .nodes.start_node import StartNode
|
552
|
+
|
553
|
+
class ExampleWorkflow(BaseWorkflow):
|
554
|
+
graph = StartNode
|
555
|
+
""",
|
556
|
+
},
|
557
|
+
{
|
558
|
+
"nodes/start_node.py": """\
|
559
|
+
from vellum.workflows.nodes import PromptDeploymentNode
|
560
|
+
|
561
|
+
class StartNode(PromptDeploymentNode):
|
562
|
+
deployment = "my-deployment"
|
563
|
+
""",
|
564
|
+
"workflow.py": """\
|
565
|
+
from vellum.workflows import BaseWorkflow
|
566
|
+
from .nodes.start_node import StartNode
|
567
|
+
|
568
|
+
class ExampleWorkflow(BaseWorkflow):
|
569
|
+
graph = StartNode
|
570
|
+
""",
|
571
|
+
},
|
572
|
+
{
|
573
|
+
"nodes/start_node.py": """\
|
574
|
+
from vellum.workflows.nodes import SubworkflowDeploymentNode
|
575
|
+
|
576
|
+
class StartNode(SubworkflowDeploymentNode):
|
577
|
+
deployment = "my-deployment"
|
578
|
+
""",
|
579
|
+
"workflow.py": """\
|
580
|
+
from vellum.workflows import BaseWorkflow
|
581
|
+
from .nodes.start_node import StartNode
|
582
|
+
|
583
|
+
class ExampleWorkflow(BaseWorkflow):
|
584
|
+
graph = StartNode
|
585
|
+
""",
|
586
|
+
},
|
587
|
+
],
|
588
|
+
ids=[
|
589
|
+
"base_case",
|
590
|
+
"with_secret_reference",
|
591
|
+
"with_prompt_deployment",
|
592
|
+
"with_subworkflow_deployment",
|
593
|
+
],
|
594
|
+
)
|
595
|
+
def test_push__workspace_option__uses_different_api_key(mock_module, vellum_client_class, file_data):
|
490
596
|
# GIVEN a single workflow configured
|
491
597
|
temp_dir = mock_module.temp_dir
|
492
598
|
module = mock_module.module
|
@@ -521,7 +627,8 @@ MY_OTHER_VELLUM_API_KEY=aaabbbcccddd
|
|
521
627
|
)
|
522
628
|
|
523
629
|
# AND a workflow exists in the module successfully
|
524
|
-
|
630
|
+
for file_name, content in file_data.items():
|
631
|
+
_ensure_file(temp_dir, module, file_name, content)
|
525
632
|
|
526
633
|
# AND the push API call returns a new workflow sandbox id
|
527
634
|
new_workflow_sandbox_id = str(uuid4())
|
@@ -564,6 +671,83 @@ MY_OTHER_VELLUM_API_KEY=aaabbbcccddd
|
|
564
671
|
}
|
565
672
|
|
566
673
|
|
674
|
+
def test_push__workspace_option__uses_different_api_url_env(mock_module, mock_httpx_transport):
|
675
|
+
# GIVEN a single workflow configured
|
676
|
+
temp_dir = mock_module.temp_dir
|
677
|
+
module = mock_module.module
|
678
|
+
workflow_sandbox_id = mock_module.workflow_sandbox_id
|
679
|
+
set_pyproject_toml = mock_module.set_pyproject_toml
|
680
|
+
|
681
|
+
# AND a different workspace is set in the pyproject.toml
|
682
|
+
set_pyproject_toml(
|
683
|
+
{
|
684
|
+
"workflows": [
|
685
|
+
{
|
686
|
+
"module": module,
|
687
|
+
"workflow_sandbox_id": workflow_sandbox_id,
|
688
|
+
}
|
689
|
+
],
|
690
|
+
"workspaces": [
|
691
|
+
{
|
692
|
+
"name": "my_other_workspace",
|
693
|
+
"api_url": "MY_OTHER_VELLUM_API_URL",
|
694
|
+
}
|
695
|
+
],
|
696
|
+
}
|
697
|
+
)
|
698
|
+
|
699
|
+
# AND the .env file has the other api key stored
|
700
|
+
with open(os.path.join(temp_dir, ".env"), "w") as f:
|
701
|
+
f.write(
|
702
|
+
"""\
|
703
|
+
VELLUM_API_KEY=abcdef123456
|
704
|
+
MY_OTHER_VELLUM_API_URL=https://app.aws-vpc-staging.vellum.ai
|
705
|
+
"""
|
706
|
+
)
|
707
|
+
|
708
|
+
# AND a workflow exists in the module successfully
|
709
|
+
_ensure_workflow_py(temp_dir, module)
|
710
|
+
|
711
|
+
# AND the push API call returns a new workflow sandbox id
|
712
|
+
new_workflow_sandbox_id = str(uuid4())
|
713
|
+
mock_httpx_transport.handle_request.return_value = Response(
|
714
|
+
status_code=200,
|
715
|
+
text=json.dumps(
|
716
|
+
{
|
717
|
+
"workflow_sandbox_id": new_workflow_sandbox_id,
|
718
|
+
}
|
719
|
+
),
|
720
|
+
)
|
721
|
+
|
722
|
+
# WHEN calling `vellum push` on strict mode
|
723
|
+
runner = CliRunner()
|
724
|
+
result = runner.invoke(cli_main, ["push", module, "--workspace", "my_other_workspace"])
|
725
|
+
|
726
|
+
# THEN it should succeed
|
727
|
+
assert result.exit_code == 0, result.output
|
728
|
+
|
729
|
+
# AND we should have called the push API once with the correct api url
|
730
|
+
request = mock_httpx_transport.handle_request.call_args[0][0]
|
731
|
+
assert str(request.url) == "https://app.aws-vpc-staging.vellum.ai/v1/workflows/push"
|
732
|
+
|
733
|
+
# AND the vellum lock file should have been updated with the correct workspace
|
734
|
+
with open(os.path.join(temp_dir, "vellum.lock.json")) as f:
|
735
|
+
lock_file_content = json.load(f)
|
736
|
+
assert lock_file_content["workflows"][1] == {
|
737
|
+
"module": module,
|
738
|
+
"workflow_sandbox_id": new_workflow_sandbox_id,
|
739
|
+
"workspace": "my_other_workspace",
|
740
|
+
"container_image_name": None,
|
741
|
+
"container_image_tag": None,
|
742
|
+
"deployments": [],
|
743
|
+
"ignore": None,
|
744
|
+
"target_directory": None,
|
745
|
+
}
|
746
|
+
|
747
|
+
# AND the new URL is in the message at the end
|
748
|
+
assert "Visit at: https://app.aws-vpc-staging.vellum.ai/workflow-sandboxes/" in result.output
|
749
|
+
|
750
|
+
|
567
751
|
def test_push__workspace_option__both_options_already_configured(mock_module, vellum_client_class):
|
568
752
|
# GIVEN a single workflow configured
|
569
753
|
temp_dir = mock_module.temp_dir
|
@@ -9,6 +9,7 @@ from typing import (
|
|
9
9
|
ForwardRef,
|
10
10
|
Generic,
|
11
11
|
Optional,
|
12
|
+
Set,
|
12
13
|
Tuple,
|
13
14
|
Type,
|
14
15
|
TypeVar,
|
@@ -24,6 +25,7 @@ from vellum.workflows.nodes.bases.base import BaseNode
|
|
24
25
|
from vellum.workflows.nodes.utils import get_unadorned_node, get_wrapped_node
|
25
26
|
from vellum.workflows.ports import Port
|
26
27
|
from vellum.workflows.references import OutputReference
|
28
|
+
from vellum.workflows.references.node import NodeReference
|
27
29
|
from vellum.workflows.types.core import JsonArray, JsonObject
|
28
30
|
from vellum.workflows.types.generics import NodeType
|
29
31
|
from vellum.workflows.types.utils import get_original_base
|
@@ -33,7 +35,7 @@ from vellum.workflows.utils.vellum_variables import primitive_type_to_vellum_var
|
|
33
35
|
from vellum_ee.workflows.display.editor.types import NodeDisplayComment, NodeDisplayData
|
34
36
|
from vellum_ee.workflows.display.nodes.get_node_display_class import get_node_display_class
|
35
37
|
from vellum_ee.workflows.display.nodes.types import NodeOutputDisplay, PortDisplay, PortDisplayOverrides
|
36
|
-
from vellum_ee.workflows.display.utils.expressions import
|
38
|
+
from vellum_ee.workflows.display.utils.expressions import serialize_value
|
37
39
|
from vellum_ee.workflows.display.utils.registry import register_node_display_class
|
38
40
|
|
39
41
|
if TYPE_CHECKING:
|
@@ -98,11 +100,18 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
|
|
98
100
|
# Default values set by the metaclass
|
99
101
|
output_display: Dict[OutputReference, NodeOutputDisplay]
|
100
102
|
port_displays: Dict[Port, PortDisplayOverrides] = {}
|
101
|
-
|
103
|
+
attribute_ids_by_name: ClassVar[Dict[str, UUID]] = {}
|
102
104
|
|
105
|
+
# START: Attributes for backwards compatible serialization
|
103
106
|
# Used to explicitly set the target handle id for a node
|
104
107
|
# Once all nodes are Generic Nodes, we may replace this with a trigger_id or trigger attribute
|
105
108
|
target_handle_id: ClassVar[Optional[UUID]] = None
|
109
|
+
# Used to explicitly set the input ids for each node input
|
110
|
+
node_input_ids_by_name: ClassVar[Dict[str, UUID]] = {}
|
111
|
+
# Used by each class extending BaseNodeDisplay to specify which attributes are meant to be serialized
|
112
|
+
# as the former `"inputs"` field
|
113
|
+
__serializable_inputs__: Set[NodeReference] = set()
|
114
|
+
# END: Attributes for backwards compatible serialization
|
106
115
|
|
107
116
|
def serialize(self, display_context: "WorkflowDisplayContext", **kwargs: Any) -> JsonObject:
|
108
117
|
node = self._node
|
@@ -114,7 +123,11 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
|
|
114
123
|
# We don't need to serialize generic node attributes containing a subworkflow
|
115
124
|
continue
|
116
125
|
|
117
|
-
id =
|
126
|
+
id = (
|
127
|
+
str(self.attribute_ids_by_name[attribute.name])
|
128
|
+
if self.attribute_ids_by_name
|
129
|
+
else str(uuid4_from_hash(f"{node_id}|{attribute.name}"))
|
130
|
+
)
|
118
131
|
try:
|
119
132
|
attributes.append(
|
120
133
|
{
|
@@ -191,9 +204,7 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
|
|
191
204
|
"id": id,
|
192
205
|
"name": port.name,
|
193
206
|
"type": port._condition_type.value,
|
194
|
-
"expression": (
|
195
|
-
serialize_condition(display_context, port._condition) if port._condition else None
|
196
|
-
),
|
207
|
+
"expression": (serialize_value(display_context, port._condition) if port._condition else None),
|
197
208
|
}
|
198
209
|
)
|
199
210
|
else:
|
@@ -2,7 +2,6 @@ import types
|
|
2
2
|
from uuid import UUID
|
3
3
|
from typing import TYPE_CHECKING, Any, Dict, Generic, Type, TypeVar
|
4
4
|
|
5
|
-
from vellum.workflows.descriptors.base import BaseDescriptor
|
6
5
|
from vellum.workflows.types.generics import NodeType
|
7
6
|
from vellum.workflows.utils.uuids import uuid4_from_hash
|
8
7
|
from vellum_ee.workflows.display.utils.registry import get_from_node_display_registry
|
@@ -30,14 +29,14 @@ def get_node_display_class(node_class: Type[NodeType]) -> Type["BaseNodeDisplay"
|
|
30
29
|
node_input_ids_by_name.update(_get_node_input_ids_by_ref(f"{path}.{key}", value))
|
31
30
|
return node_input_ids_by_name
|
32
31
|
|
33
|
-
|
34
|
-
return {path: uuid4_from_hash(f"{node_class.__id__}|{path}")}
|
35
|
-
|
36
|
-
return {}
|
32
|
+
return {path: uuid4_from_hash(f"{node_class.__id__}|{path}")}
|
37
33
|
|
38
34
|
def exec_body(ns: Dict):
|
39
35
|
node_input_ids_by_name: Dict[str, UUID] = {}
|
40
36
|
for ref in node_class:
|
37
|
+
if ref not in base_node_display_class.__serializable_inputs__:
|
38
|
+
continue
|
39
|
+
|
41
40
|
node_input_ids_by_name.update(_get_node_input_ids_by_ref(ref.name, ref.instance))
|
42
41
|
|
43
42
|
if node_input_ids_by_name:
|
@@ -20,6 +20,17 @@ class BaseAPINodeDisplay(BaseNodeDisplay[_APINodeType], Generic[_APINodeType]):
|
|
20
20
|
# A mapping between node input keys and their ids for inputs representing additional header values
|
21
21
|
additional_header_value_input_ids: ClassVar[Optional[Dict[str, UUID]]] = None
|
22
22
|
|
23
|
+
__serializable_inputs__ = {
|
24
|
+
APINode.url,
|
25
|
+
APINode.method,
|
26
|
+
APINode.json,
|
27
|
+
APINode.headers,
|
28
|
+
APINode.api_key_header_key,
|
29
|
+
APINode.api_key_header_value,
|
30
|
+
APINode.bearer_token_value,
|
31
|
+
APINode.authorization_type,
|
32
|
+
}
|
33
|
+
|
23
34
|
def serialize(
|
24
35
|
self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **kwargs: Any
|
25
36
|
) -> JsonObject:
|
@@ -18,6 +18,11 @@ class BaseCodeExecutionNodeDisplay(BaseNodeDisplay[_CodeExecutionNodeType], Gene
|
|
18
18
|
output_id: ClassVar[Optional[UUID]] = None
|
19
19
|
log_output_id: ClassVar[Optional[UUID]] = None
|
20
20
|
|
21
|
+
__serializable_inputs__ = {
|
22
|
+
CodeExecutionNode.code,
|
23
|
+
CodeExecutionNode.code_inputs,
|
24
|
+
}
|
25
|
+
|
21
26
|
def serialize(
|
22
27
|
self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **kwargs
|
23
28
|
) -> JsonObject:
|
@@ -1,5 +1,5 @@
|
|
1
1
|
from uuid import UUID
|
2
|
-
from typing import ClassVar, Generic, Optional, TypeVar
|
2
|
+
from typing import Any, ClassVar, Generic, Optional, TypeVar
|
3
3
|
|
4
4
|
from vellum.workflows.nodes import ErrorNode
|
5
5
|
from vellum.workflows.types.core import JsonObject
|
@@ -9,47 +9,53 @@ from vellum_ee.workflows.display.nodes.vellum.utils import create_node_input
|
|
9
9
|
from vellum_ee.workflows.display.types import WorkflowDisplayContext
|
10
10
|
|
11
11
|
_ErrorNodeType = TypeVar("_ErrorNodeType", bound=ErrorNode)
|
12
|
+
LEGACY_INPUT_NAME = "error_source_input_id"
|
12
13
|
|
13
14
|
|
14
15
|
class BaseErrorNodeDisplay(BaseNodeDisplay[_ErrorNodeType], Generic[_ErrorNodeType]):
|
16
|
+
# DEPRECATED: Remove in 0.15.0 once removed from the vellum-side
|
15
17
|
error_output_id: ClassVar[Optional[UUID]] = None
|
18
|
+
# DEPRECATED: Remove in 0.15.0 once removed from the vellum-side
|
19
|
+
name: ClassVar[Optional[str]] = None
|
16
20
|
|
17
|
-
|
21
|
+
__serializable_inputs__ = {ErrorNode.error}
|
18
22
|
|
19
|
-
def serialize(
|
20
|
-
self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **kwargs
|
21
|
-
) -> JsonObject:
|
23
|
+
def serialize(self, display_context: WorkflowDisplayContext, **kwargs) -> JsonObject:
|
22
24
|
node_id = self.node_id
|
23
|
-
error_source_input_id = self.node_input_ids_by_name.get(
|
25
|
+
error_source_input_id = self.node_input_ids_by_name.get(
|
26
|
+
ErrorNode.error.name,
|
27
|
+
) or self.node_input_ids_by_name.get(LEGACY_INPUT_NAME)
|
24
28
|
|
25
29
|
error_attribute = raise_if_descriptor(self._node.error)
|
26
|
-
input_values_by_name = {
|
27
|
-
"error_source_input_id": error_attribute,
|
28
|
-
}
|
29
30
|
|
30
31
|
node_inputs = [
|
31
32
|
create_node_input(
|
32
33
|
node_id=node_id,
|
33
|
-
input_name=
|
34
|
-
value=
|
34
|
+
input_name=LEGACY_INPUT_NAME,
|
35
|
+
value=error_attribute,
|
35
36
|
display_context=display_context,
|
36
|
-
input_id=
|
37
|
+
input_id=error_source_input_id,
|
37
38
|
)
|
38
|
-
for variable_name, variable_value in input_values_by_name.items()
|
39
39
|
]
|
40
40
|
|
41
|
-
|
41
|
+
node_data: dict[str, Any] = {
|
42
42
|
"id": str(node_id),
|
43
43
|
"type": "ERROR",
|
44
44
|
"inputs": [node_input.dict() for node_input in node_inputs],
|
45
45
|
"data": {
|
46
|
-
"name": self.name,
|
47
46
|
"label": self.label,
|
48
47
|
"target_handle_id": str(self.get_target_handle_id()),
|
49
48
|
"error_source_input_id": str(error_source_input_id),
|
50
|
-
"error_output_id": str(self.error_output_id),
|
51
49
|
},
|
52
50
|
"display_data": self.get_display_data().dict(),
|
53
51
|
"base": self.get_base().dict(),
|
54
52
|
"definition": self.get_definition().dict(),
|
55
53
|
}
|
54
|
+
|
55
|
+
if self.name:
|
56
|
+
node_data["data"]["name"] = self.name
|
57
|
+
|
58
|
+
if self.error_output_id:
|
59
|
+
node_data["data"]["error_output_id"] = str(self.error_output_id)
|
60
|
+
|
61
|
+
return node_data
|
@@ -12,6 +12,8 @@ _GuardrailNodeType = TypeVar("_GuardrailNodeType", bound=GuardrailNode)
|
|
12
12
|
|
13
13
|
|
14
14
|
class BaseGuardrailNodeDisplay(BaseNodeDisplay[_GuardrailNodeType], Generic[_GuardrailNodeType]):
|
15
|
+
__serializable_inputs__ = {GuardrailNode.metric_inputs}
|
16
|
+
|
15
17
|
def serialize(
|
16
18
|
self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **kwargs
|
17
19
|
) -> JsonObject:
|
@@ -10,6 +10,7 @@ from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
|
|
10
10
|
from vellum_ee.workflows.display.nodes.utils import raise_if_descriptor
|
11
11
|
from vellum_ee.workflows.display.nodes.vellum.utils import create_node_input
|
12
12
|
from vellum_ee.workflows.display.types import WorkflowDisplayContext
|
13
|
+
from vellum_ee.workflows.display.utils.expressions import serialize_value
|
13
14
|
from vellum_ee.workflows.display.utils.vellum import infer_vellum_variable_type
|
14
15
|
from vellum_ee.workflows.display.vellum import NodeInput
|
15
16
|
|
@@ -17,6 +18,8 @@ _InlinePromptNodeType = TypeVar("_InlinePromptNodeType", bound=InlinePromptNode)
|
|
17
18
|
|
18
19
|
|
19
20
|
class BaseInlinePromptNodeDisplay(BaseNodeDisplay[_InlinePromptNodeType], Generic[_InlinePromptNodeType]):
|
21
|
+
__serializable_inputs__ = {InlinePromptNode.prompt_inputs}
|
22
|
+
|
20
23
|
def serialize(
|
21
24
|
self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **kwargs
|
22
25
|
) -> JsonObject:
|
@@ -26,12 +29,14 @@ class BaseInlinePromptNodeDisplay(BaseNodeDisplay[_InlinePromptNodeType], Generi
|
|
26
29
|
node_inputs, prompt_inputs = self._generate_node_and_prompt_inputs(node_id, node, display_context)
|
27
30
|
input_variable_id_by_name = {prompt_input.key: prompt_input.id for prompt_input in prompt_inputs}
|
28
31
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
+
output_display = self.output_display[node.Outputs.text]
|
33
|
+
array_display = self.output_display[node.Outputs.results]
|
34
|
+
json_display = self.output_display[node.Outputs.json]
|
32
35
|
node_blocks = raise_if_descriptor(node.blocks)
|
33
36
|
function_definitions = raise_if_descriptor(node.functions)
|
34
37
|
|
38
|
+
ml_model = str(raise_if_descriptor(node.ml_model))
|
39
|
+
|
35
40
|
blocks: list = [
|
36
41
|
self._generate_prompt_block(block, input_variable_id_by_name, [i]) for i, block in enumerate(node_blocks)
|
37
42
|
]
|
@@ -42,7 +47,7 @@ class BaseInlinePromptNodeDisplay(BaseNodeDisplay[_InlinePromptNodeType], Generi
|
|
42
47
|
)
|
43
48
|
blocks.extend(functions)
|
44
49
|
|
45
|
-
|
50
|
+
serialized_node: JsonObject = {
|
46
51
|
"id": str(node_id),
|
47
52
|
"type": "PROMPT",
|
48
53
|
"inputs": [node_input.dict() for node_input in node_inputs],
|
@@ -62,7 +67,7 @@ class BaseInlinePromptNodeDisplay(BaseNodeDisplay[_InlinePromptNodeType], Generi
|
|
62
67
|
"blocks": blocks,
|
63
68
|
},
|
64
69
|
},
|
65
|
-
"ml_model_name":
|
70
|
+
"ml_model_name": ml_model,
|
66
71
|
},
|
67
72
|
"display_data": self.get_display_data().dict(),
|
68
73
|
"base": self.get_base().dict(),
|
@@ -74,6 +79,10 @@ class BaseInlinePromptNodeDisplay(BaseNodeDisplay[_InlinePromptNodeType], Generi
|
|
74
79
|
],
|
75
80
|
"ports": self.serialize_ports(display_context),
|
76
81
|
}
|
82
|
+
attributes = self._serialize_attributes(display_context)
|
83
|
+
if attributes:
|
84
|
+
serialized_node["attributes"] = attributes
|
85
|
+
return serialized_node
|
77
86
|
|
78
87
|
def _generate_node_and_prompt_inputs(
|
79
88
|
self,
|
@@ -127,6 +136,7 @@ class BaseInlinePromptNodeDisplay(BaseNodeDisplay[_InlinePromptNodeType], Generi
|
|
127
136
|
path: List[int],
|
128
137
|
) -> JsonObject:
|
129
138
|
block: JsonObject
|
139
|
+
block_id = uuid4_from_hash(f"{self.node_id}-{prompt_block.block_type}-{'-'.join([str(i) for i in path])}")
|
130
140
|
if prompt_block.block_type == "JINJA":
|
131
141
|
block = {
|
132
142
|
"block_type": "JINJA",
|
@@ -162,10 +172,21 @@ class BaseInlinePromptNodeDisplay(BaseNodeDisplay[_InlinePromptNodeType], Generi
|
|
162
172
|
}
|
163
173
|
|
164
174
|
elif prompt_block.block_type == "VARIABLE":
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
175
|
+
input_variable_id = input_variable_id_by_name.get(prompt_block.input_variable)
|
176
|
+
if input_variable_id:
|
177
|
+
block = {
|
178
|
+
"block_type": "VARIABLE",
|
179
|
+
"input_variable_id": input_variable_id,
|
180
|
+
}
|
181
|
+
else:
|
182
|
+
# Even though this will likely fail in runtime, we want to allow serialization to succeed
|
183
|
+
# in case the block is work in progress or the node is not yet part of the graph
|
184
|
+
block = {
|
185
|
+
"block_type": "VARIABLE",
|
186
|
+
"input_variable_id": str(
|
187
|
+
uuid4_from_hash(f"{block_id}-input_variable-{prompt_block.input_variable}")
|
188
|
+
),
|
189
|
+
}
|
169
190
|
|
170
191
|
elif prompt_block.block_type == "PLAIN_TEXT":
|
171
192
|
block = {
|
@@ -184,9 +205,7 @@ class BaseInlinePromptNodeDisplay(BaseNodeDisplay[_InlinePromptNodeType], Generi
|
|
184
205
|
else:
|
185
206
|
raise NotImplementedError(f"Serialization for prompt block type {prompt_block.block_type} not implemented")
|
186
207
|
|
187
|
-
block["id"] = str(
|
188
|
-
uuid4_from_hash(f"{self.node_id}-{prompt_block.block_type}-{'-'.join([str(i) for i in path])}")
|
189
|
-
)
|
208
|
+
block["id"] = str(block_id)
|
190
209
|
if prompt_block.cache_config:
|
191
210
|
block["cache_config"] = prompt_block.cache_config.dict()
|
192
211
|
else:
|
@@ -198,3 +217,18 @@ class BaseInlinePromptNodeDisplay(BaseNodeDisplay[_InlinePromptNodeType], Generi
|
|
198
217
|
block["state"] = "ENABLED"
|
199
218
|
|
200
219
|
return block
|
220
|
+
|
221
|
+
def _serialize_attributes(self, display_context: "WorkflowDisplayContext"):
|
222
|
+
attribute_instances_by_name = {}
|
223
|
+
for attribute in self._node:
|
224
|
+
if attribute.name in self.attribute_ids_by_name:
|
225
|
+
attribute_instances_by_name[attribute.name] = attribute.instance
|
226
|
+
|
227
|
+
return [
|
228
|
+
{
|
229
|
+
"id": str(attr_id),
|
230
|
+
"name": attr_name,
|
231
|
+
"value": serialize_value(display_context, attribute_instances_by_name[attr_name]),
|
232
|
+
}
|
233
|
+
for attr_name, attr_id in self.attribute_ids_by_name.items()
|
234
|
+
]
|
@@ -21,6 +21,8 @@ class BaseInlineSubworkflowNodeDisplay(
|
|
21
21
|
):
|
22
22
|
workflow_input_ids_by_name: ClassVar[Dict[str, UUID]] = {}
|
23
23
|
|
24
|
+
__serializable_inputs__ = {InlineSubworkflowNode.subworkflow_inputs}
|
25
|
+
|
24
26
|
def serialize(
|
25
27
|
self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **kwargs
|
26
28
|
) -> JsonObject:
|
@@ -13,6 +13,8 @@ _MapNodeType = TypeVar("_MapNodeType", bound=MapNode)
|
|
13
13
|
|
14
14
|
|
15
15
|
class BaseMapNodeDisplay(BaseAdornmentNodeDisplay[_MapNodeType], Generic[_MapNodeType]):
|
16
|
+
__serializable_inputs__ = {MapNode.items} # type: ignore[misc]
|
17
|
+
|
16
18
|
def serialize(
|
17
19
|
self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **kwargs
|
18
20
|
) -> JsonObject:
|