vellum-ai 0.14.46__py3-none-any.whl → 0.14.48__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/displayable/code_execution_node/tests/test_code_execution_node.py +50 -0
- vellum/workflows/nodes/utils.py +5 -1
- vellum/workflows/types/code_execution_node_wrappers.py +5 -0
- {vellum_ai-0.14.46.dist-info → vellum_ai-0.14.48.dist-info}/METADATA +1 -1
- {vellum_ai-0.14.46.dist-info → vellum_ai-0.14.48.dist-info}/RECORD +152 -152
- vellum_cli/__init__.py +3 -2
- vellum_cli/image_push.py +15 -3
- vellum_cli/tests/test_image_push.py +109 -0
- vellum_ee/workflows/display/nodes/base_node_display.py +25 -9
- 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 +47 -13
- 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 +2 -0
- vellum_ee/workflows/display/nodes/vellum/search_node.py +8 -0
- vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py +1 -0
- 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 +67 -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/utils/expressions.py +31 -4
- vellum_ee/workflows/display/workflows/tests/test_workflow_display.py +162 -0
- {vellum_ai-0.14.46.dist-info → vellum_ai-0.14.48.dist-info}/LICENSE +0 -0
- {vellum_ai-0.14.46.dist-info → vellum_ai-0.14.48.dist-info}/WHEEL +0 -0
- {vellum_ai-0.14.46.dist-info → vellum_ai-0.14.48.dist-info}/entry_points.txt +0 -0
vellum_cli/__init__.py
CHANGED
@@ -354,9 +354,10 @@ def images() -> None:
|
|
354
354
|
help="Tags the provided image inside of Vellum's repo. "
|
355
355
|
"This field does not push multiple local tags of the passed in image.",
|
356
356
|
)
|
357
|
-
|
357
|
+
@click.option("--workspace", type=str, help="The specific Workspace config to use when pushing")
|
358
|
+
def image_push(image: str, tag: Optional[List[str]] = None, workspace: Optional[str] = None) -> None:
|
358
359
|
"""Push Docker image to Vellum"""
|
359
|
-
image_push_command(image, tag)
|
360
|
+
image_push_command(image, tag, workspace)
|
360
361
|
|
361
362
|
|
362
363
|
@workflows.command(name="init")
|
vellum_cli/image_push.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import json
|
2
2
|
import logging
|
3
|
+
import os
|
3
4
|
import re
|
4
5
|
import subprocess
|
5
6
|
from typing import List, Optional
|
@@ -9,15 +10,26 @@ from docker import DockerClient
|
|
9
10
|
from dotenv import load_dotenv
|
10
11
|
|
11
12
|
from vellum.workflows.vellum_client import create_vellum_client, create_vellum_environment
|
13
|
+
from vellum_cli.config import DEFAULT_WORKSPACE_CONFIG, load_vellum_cli_config
|
12
14
|
from vellum_cli.logger import load_cli_logger
|
13
15
|
|
14
16
|
_SUPPORTED_ARCHITECTURE = "amd64"
|
15
17
|
|
16
18
|
|
17
|
-
def image_push_command(image: str, tags: Optional[List[str]] = None) -> None:
|
18
|
-
load_dotenv()
|
19
|
+
def image_push_command(image: str, tags: Optional[List[str]] = None, workspace: Optional[str] = None) -> None:
|
20
|
+
load_dotenv(dotenv_path=os.path.join(os.getcwd(), ".env"))
|
19
21
|
logger = load_cli_logger()
|
20
|
-
|
22
|
+
config = load_vellum_cli_config()
|
23
|
+
workspace_config = next((w for w in config.workspaces if w.name == workspace), DEFAULT_WORKSPACE_CONFIG)
|
24
|
+
|
25
|
+
api_key = os.getenv(workspace_config.api_key, None)
|
26
|
+
if not api_key:
|
27
|
+
raise ValueError(f"API key {workspace_config.api_key} for workspace {workspace} not found")
|
28
|
+
|
29
|
+
vellum_client = create_vellum_client(
|
30
|
+
api_key=api_key,
|
31
|
+
api_url=workspace_config.api_url,
|
32
|
+
)
|
21
33
|
|
22
34
|
# Check if we are self hosted by looking at our base url
|
23
35
|
api_url = create_vellum_environment().default
|
@@ -1,16 +1,37 @@
|
|
1
|
+
import pytest
|
2
|
+
import json
|
3
|
+
import os
|
4
|
+
import shutil
|
1
5
|
import subprocess
|
6
|
+
import tempfile
|
2
7
|
from unittest.mock import MagicMock, patch
|
8
|
+
from uuid import uuid4
|
9
|
+
from typing import Generator
|
3
10
|
|
4
11
|
from click.testing import CliRunner
|
12
|
+
from httpx import Response
|
5
13
|
|
6
14
|
from vellum_cli import main as cli_main
|
7
15
|
|
8
16
|
|
17
|
+
@pytest.fixture
|
18
|
+
def mock_temp_dir() -> Generator[str, None, None]:
|
19
|
+
current_dir = os.getcwd()
|
20
|
+
temp_dir = tempfile.mkdtemp()
|
21
|
+
os.chdir(temp_dir)
|
22
|
+
|
23
|
+
yield temp_dir
|
24
|
+
|
25
|
+
os.chdir(current_dir)
|
26
|
+
shutil.rmtree(temp_dir)
|
27
|
+
|
28
|
+
|
9
29
|
@patch("subprocess.run")
|
10
30
|
@patch("docker.from_env")
|
11
31
|
def test_image_push__self_hosted_happy_path(mock_docker_from_env, mock_run, vellum_client, monkeypatch):
|
12
32
|
# GIVEN a self hosted vellum api URL env var
|
13
33
|
monkeypatch.setenv("VELLUM_API_URL", "mycompany.api.com")
|
34
|
+
monkeypatch.setenv("VELLUM_API_KEY", "123456abcdef")
|
14
35
|
|
15
36
|
# Mock Docker client
|
16
37
|
mock_docker_client = MagicMock()
|
@@ -35,6 +56,94 @@ def test_image_push__self_hosted_happy_path(mock_docker_from_env, mock_run, vell
|
|
35
56
|
assert "Image successfully pushed" in result.output
|
36
57
|
|
37
58
|
|
59
|
+
@patch("subprocess.run")
|
60
|
+
@patch("docker.from_env")
|
61
|
+
def test_image_push__self_hosted_happy_path__workspace_option(
|
62
|
+
mock_docker_from_env, mock_run, mock_httpx_transport, mock_temp_dir
|
63
|
+
):
|
64
|
+
# GIVEN a workspace config with a new env for url
|
65
|
+
with open(os.path.join(mock_temp_dir, "vellum.lock.json"), "w") as f:
|
66
|
+
f.write(
|
67
|
+
json.dumps(
|
68
|
+
{
|
69
|
+
"workspaces": [
|
70
|
+
{
|
71
|
+
"name": "my_workspace",
|
72
|
+
"api_url": "MY_WORKSPACE_VELLUM_API_URL",
|
73
|
+
"api_key": "MY_WORKSPACE_VELLUM_API_KEY",
|
74
|
+
}
|
75
|
+
]
|
76
|
+
}
|
77
|
+
)
|
78
|
+
)
|
79
|
+
|
80
|
+
# AND a .env file with the workspace api key and url
|
81
|
+
with open(os.path.join(mock_temp_dir, ".env"), "w") as f:
|
82
|
+
f.write(
|
83
|
+
"VELLUM_API_KEY=123456abcdef\n"
|
84
|
+
"VELLUM_API_URL=https://api.vellum.ai\n"
|
85
|
+
"MY_WORKSPACE_VELLUM_API_KEY=789012ghijkl\n"
|
86
|
+
"MY_WORKSPACE_VELLUM_API_URL=https://api.vellum.mycompany.ai\n"
|
87
|
+
)
|
88
|
+
|
89
|
+
# AND the Docker client returns the correct response
|
90
|
+
mock_docker_client = MagicMock()
|
91
|
+
mock_docker_from_env.return_value = mock_docker_client
|
92
|
+
|
93
|
+
mock_run.side_effect = [
|
94
|
+
subprocess.CompletedProcess(
|
95
|
+
args="", returncode=0, stdout=b'{"manifests": [{"platform": {"architecture": "amd64"}}]}'
|
96
|
+
),
|
97
|
+
subprocess.CompletedProcess(args="", returncode=0, stdout=b"sha256:hellosha"),
|
98
|
+
]
|
99
|
+
|
100
|
+
# AND the vellum client returns the correct response for
|
101
|
+
mock_httpx_transport.handle_request.side_effect = [
|
102
|
+
# First call to get the docker service token
|
103
|
+
Response(
|
104
|
+
status_code=200,
|
105
|
+
text=json.dumps(
|
106
|
+
{
|
107
|
+
"access_token": "345678mnopqr",
|
108
|
+
"organization_id": str(uuid4()),
|
109
|
+
"repository": "myrepo.net",
|
110
|
+
}
|
111
|
+
),
|
112
|
+
),
|
113
|
+
# Second call to push the image
|
114
|
+
Response(
|
115
|
+
status_code=200,
|
116
|
+
text=json.dumps(
|
117
|
+
{
|
118
|
+
"id": str(uuid4()),
|
119
|
+
"name": "myrepo.net/myimage",
|
120
|
+
"visibility": "PRIVATE",
|
121
|
+
"created": "2021-01-01T00:00:00Z",
|
122
|
+
"modified": "2021-01-01T00:00:00Z",
|
123
|
+
"repository": "myrepo.net",
|
124
|
+
"sha": "sha256:hellosha",
|
125
|
+
"tags": [],
|
126
|
+
}
|
127
|
+
),
|
128
|
+
),
|
129
|
+
]
|
130
|
+
|
131
|
+
# WHEN the user runs the image push command
|
132
|
+
runner = CliRunner()
|
133
|
+
result = runner.invoke(cli_main, ["image", "push", "myrepo.net/myimage:latest", "--workspace", "my_workspace"])
|
134
|
+
|
135
|
+
# THEN the command exits successfully
|
136
|
+
assert result.exit_code == 0, (result.output, str(result.exception))
|
137
|
+
|
138
|
+
# AND gives the success message
|
139
|
+
assert "Image successfully pushed" in result.output
|
140
|
+
|
141
|
+
# AND the vellum client was called with the correct api key and url
|
142
|
+
request = mock_httpx_transport.handle_request.call_args[0][0]
|
143
|
+
assert request.headers["X-API-KEY"] == "789012ghijkl", result.stdout
|
144
|
+
assert str(request.url) == "https://api.vellum.mycompany.ai/v1/container-images/push"
|
145
|
+
|
146
|
+
|
38
147
|
@patch("subprocess.run")
|
39
148
|
@patch("docker.from_env")
|
40
149
|
def test_image_push__self_hosted_blocks_repo(mock_docker_from_env, mock_run, vellum_client, monkeypatch):
|
@@ -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:
|
@@ -249,10 +260,15 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
|
|
249
260
|
|
250
261
|
def get_source_handle_id(self, port_displays: Dict[Port, PortDisplay]) -> UUID:
|
251
262
|
unadorned_node = get_unadorned_node(self._node)
|
252
|
-
default_port = unadorned_node.Ports.default
|
263
|
+
default_port = next((port for port in unadorned_node.Ports if port.default), None)
|
264
|
+
if default_port in port_displays:
|
265
|
+
return port_displays[default_port].id
|
266
|
+
|
267
|
+
first_port = next((port for port in unadorned_node.Ports), None)
|
268
|
+
if not first_port:
|
269
|
+
raise ValueError(f"Node {self._node.__name__} must have at least one port.")
|
253
270
|
|
254
|
-
|
255
|
-
return default_port_display.id
|
271
|
+
return port_displays[first_port].id
|
256
272
|
|
257
273
|
def get_trigger_id(self) -> UUID:
|
258
274
|
return self.get_target_handle_id()
|
@@ -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
|
-
node_blocks = raise_if_descriptor(node.blocks)
|
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]
|
35
|
+
node_blocks = raise_if_descriptor(node.blocks) or []
|
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:
|
@@ -13,6 +13,8 @@ _PromptDeploymentNodeType = TypeVar("_PromptDeploymentNodeType", bound=PromptDep
|
|
13
13
|
|
14
14
|
|
15
15
|
class BasePromptDeploymentNodeDisplay(BaseNodeDisplay[_PromptDeploymentNodeType], Generic[_PromptDeploymentNodeType]):
|
16
|
+
__serializable_inputs__ = {PromptDeploymentNode.prompt_inputs}
|
17
|
+
|
16
18
|
def serialize(
|
17
19
|
self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **kwargs
|
18
20
|
) -> JsonObject:
|
@@ -32,6 +32,14 @@ class BaseSearchNodeDisplay(BaseNodeDisplay[_SearchNodeType], Generic[_SearchNod
|
|
32
32
|
# A mapping between the id of the operand (e.g. "lhs_variable_id" or "rhs_variable_id") and the id of the node input
|
33
33
|
# that the operand is pointing to.
|
34
34
|
metadata_filter_input_id_by_operand_id: Dict[UUID, UUID] = {}
|
35
|
+
__serializable_inputs__ = {
|
36
|
+
SearchNode.query,
|
37
|
+
SearchNode.document_index,
|
38
|
+
SearchNode.weights,
|
39
|
+
SearchNode.chunk_separator,
|
40
|
+
SearchNode.limit,
|
41
|
+
SearchNode.result_merging,
|
42
|
+
}
|
35
43
|
|
36
44
|
def serialize(
|
37
45
|
self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **kwargs
|
@@ -14,6 +14,7 @@ _SubworkflowDeploymentNodeType = TypeVar("_SubworkflowDeploymentNodeType", bound
|
|
14
14
|
class BaseSubworkflowDeploymentNodeDisplay(
|
15
15
|
BaseNodeDisplay[_SubworkflowDeploymentNodeType], Generic[_SubworkflowDeploymentNodeType]
|
16
16
|
):
|
17
|
+
__serializable_inputs__ = {SubworkflowDeploymentNode.subworkflow_inputs}
|
17
18
|
|
18
19
|
def serialize(
|
19
20
|
self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **kwargs
|
@@ -15,6 +15,8 @@ TEMPLATE_INPUT_NAME = TemplatingNode.template.name
|
|
15
15
|
|
16
16
|
|
17
17
|
class BaseTemplatingNodeDisplay(BaseNodeDisplay[_TemplatingNodeType], Generic[_TemplatingNodeType]):
|
18
|
+
__serializable_inputs__ = {TemplatingNode.inputs}
|
19
|
+
|
18
20
|
def serialize(
|
19
21
|
self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **kwargs
|
20
22
|
) -> JsonObject:
|
@@ -29,7 +29,7 @@ def _display_class_with_node_input_ids_by_name_with_inputs_prefix(Node: Type[Cod
|
|
29
29
|
@pytest.mark.parametrize(
|
30
30
|
["GetDisplayClass", "expected_input_id"],
|
31
31
|
[
|
32
|
-
(_no_display_class, "
|
32
|
+
(_no_display_class, "a5dbe403-0b00-4df6-b8f7-ed5f7794b003"),
|
33
33
|
(_display_class_with_node_input_ids_by_name, "fba6a4d5-835a-4e99-afb7-f6a4aed15110"),
|
34
34
|
(_display_class_with_node_input_ids_by_name_with_inputs_prefix, "fba6a4d5-835a-4e99-afb7-f6a4aed15110"),
|
35
35
|
],
|
@@ -5,6 +5,7 @@ from typing import Type
|
|
5
5
|
|
6
6
|
from vellum.workflows import BaseWorkflow
|
7
7
|
from vellum.workflows.nodes import PromptDeploymentNode
|
8
|
+
from vellum_ee.workflows.display.nodes.vellum.prompt_deployment_node import BasePromptDeploymentNodeDisplay
|
8
9
|
from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
|
9
10
|
|
10
11
|
|
@@ -13,14 +14,14 @@ def _no_display_class(Node: Type[PromptDeploymentNode]): # type: ignore
|
|
13
14
|
|
14
15
|
|
15
16
|
def _display_class_with_node_input_ids_by_name(Node: Type[PromptDeploymentNode]):
|
16
|
-
class PromptDeploymentNodeDisplay(
|
17
|
+
class PromptDeploymentNodeDisplay(BasePromptDeploymentNodeDisplay[Node]): # type: ignore[valid-type]
|
17
18
|
node_input_ids_by_name = {"foo": UUID("6037747a-1d35-4094-b363-4369fc92c5d4")}
|
18
19
|
|
19
20
|
return PromptDeploymentNodeDisplay
|
20
21
|
|
21
22
|
|
22
23
|
def _display_class_with_node_input_ids_by_name_with_inputs_prefix(Node: Type[PromptDeploymentNode]):
|
23
|
-
class PromptDeploymentNodeDisplay(
|
24
|
+
class PromptDeploymentNodeDisplay(BasePromptDeploymentNodeDisplay[Node]): # type: ignore[valid-type]
|
24
25
|
node_input_ids_by_name = {"prompt_inputs.foo": UUID("6037747a-1d35-4094-b363-4369fc92c5d4")}
|
25
26
|
|
26
27
|
return PromptDeploymentNodeDisplay
|
@@ -51,7 +52,7 @@ def mock_fetch_deployment(mocker):
|
|
51
52
|
@pytest.mark.parametrize(
|
52
53
|
["GetDisplayClass", "expected_input_id"],
|
53
54
|
[
|
54
|
-
(_no_display_class, "
|
55
|
+
(_no_display_class, "016187d6-2830-4256-a61d-e52f9bf6355e"),
|
55
56
|
(_display_class_with_node_input_ids_by_name, "6037747a-1d35-4094-b363-4369fc92c5d4"),
|
56
57
|
(_display_class_with_node_input_ids_by_name_with_inputs_prefix, "6037747a-1d35-4094-b363-4369fc92c5d4"),
|
57
58
|
],
|