vellum-ai 0.14.6__py3-none-any.whl → 0.14.8__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 +14 -0
- vellum/client/core/client_wrapper.py +1 -1
- vellum/client/types/__init__.py +14 -0
- vellum/client/types/array_chat_message_content_item.py +6 -1
- vellum/client/types/array_chat_message_content_item_request.py +2 -0
- vellum/client/types/chat_message_content.py +2 -0
- vellum/client/types/chat_message_content_request.py +2 -0
- vellum/client/types/document_chat_message_content.py +25 -0
- vellum/client/types/document_chat_message_content_request.py +25 -0
- vellum/client/types/document_prompt_block.py +29 -0
- vellum/client/types/document_vellum_value.py +25 -0
- vellum/client/types/document_vellum_value_request.py +25 -0
- vellum/client/types/prompt_block.py +2 -0
- vellum/client/types/vellum_document.py +20 -0
- vellum/client/types/vellum_document_request.py +20 -0
- vellum/client/types/vellum_value.py +2 -0
- vellum/client/types/vellum_value_request.py +2 -0
- vellum/client/types/vellum_variable_type.py +1 -0
- vellum/types/document_chat_message_content.py +3 -0
- vellum/types/document_chat_message_content_request.py +3 -0
- vellum/types/document_prompt_block.py +3 -0
- vellum/types/document_vellum_value.py +3 -0
- vellum/types/document_vellum_value_request.py +3 -0
- vellum/types/vellum_document.py +3 -0
- vellum/types/vellum_document_request.py +3 -0
- vellum/workflows/exceptions.py +18 -0
- vellum/workflows/inputs/base.py +27 -1
- vellum/workflows/inputs/tests/__init__.py +0 -0
- vellum/workflows/inputs/tests/test_inputs.py +49 -0
- vellum/workflows/nodes/core/inline_subworkflow_node/node.py +1 -1
- vellum/workflows/nodes/core/map_node/node.py +7 -7
- vellum/workflows/nodes/core/try_node/node.py +1 -1
- vellum/workflows/nodes/displayable/api_node/tests/test_api_node.py +33 -0
- vellum/workflows/nodes/displayable/bases/api_node/node.py +1 -1
- vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py +2 -2
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +5 -3
- vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py +5 -4
- vellum/workflows/nodes/displayable/inline_prompt_node/tests/test_node.py +4 -4
- vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +39 -15
- vellum/workflows/nodes/displayable/subworkflow_deployment_node/tests/test_node.py +142 -0
- vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py +3 -1
- vellum/workflows/outputs/base.py +1 -1
- vellum/workflows/runner/runner.py +16 -10
- vellum/workflows/state/context.py +7 -7
- vellum/workflows/workflows/base.py +16 -5
- vellum/workflows/workflows/tests/test_base_workflow.py +131 -40
- {vellum_ai-0.14.6.dist-info → vellum_ai-0.14.8.dist-info}/METADATA +1 -1
- {vellum_ai-0.14.6.dist-info → vellum_ai-0.14.8.dist-info}/RECORD +65 -47
- vellum_cli/__init__.py +43 -0
- vellum_cli/config.py +1 -0
- vellum_cli/init.py +132 -0
- vellum_cli/pull.py +7 -3
- vellum_cli/tests/test_init.py +473 -0
- vellum_cli/tests/test_pull.py +135 -0
- vellum_cli/tests/test_push.py +1 -0
- vellum_ee/workflows/display/nodes/base_node_display.py +4 -4
- vellum_ee/workflows/display/tests/test_vellum_workflow_display.py +83 -0
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +118 -3
- vellum_ee/workflows/display/vellum.py +0 -4
- vellum_ee/workflows/display/workflows/base_workflow_display.py +46 -13
- vellum_ee/workflows/display/workflows/tests/test_workflow_display.py +29 -0
- vellum_ee/workflows/display/workflows/vellum_workflow_display.py +12 -0
- {vellum_ai-0.14.6.dist-info → vellum_ai-0.14.8.dist-info}/LICENSE +0 -0
- {vellum_ai-0.14.6.dist-info → vellum_ai-0.14.8.dist-info}/WHEEL +0 -0
- {vellum_ai-0.14.6.dist-info → vellum_ai-0.14.8.dist-info}/entry_points.txt +0 -0
vellum_cli/__init__.py
CHANGED
@@ -4,6 +4,7 @@ import click
|
|
4
4
|
|
5
5
|
from vellum_cli.aliased_group import ClickAliasedGroup
|
6
6
|
from vellum_cli.image_push import image_push_command
|
7
|
+
from vellum_cli.init import init_command
|
7
8
|
from vellum_cli.ping import ping_command
|
8
9
|
from vellum_cli.pull import pull_command
|
9
10
|
from vellum_cli.push import push_command
|
@@ -193,12 +194,20 @@ Should only be used for debugging purposes.""",
|
|
193
194
|
help="""Generates a runnable sandbox.py file containing test data from the Resource's sandbox. \
|
194
195
|
Helpful for running and debugging workflows locally.""",
|
195
196
|
)
|
197
|
+
@click.option(
|
198
|
+
"--target-dir",
|
199
|
+
"target_directory", # Internal parameter name is target_directory
|
200
|
+
type=str,
|
201
|
+
help="""Directory to pull the workflow into. If not specified, \
|
202
|
+
the workflow will be pulled into the current working directory.""",
|
203
|
+
)
|
196
204
|
def pull(
|
197
205
|
ctx: click.Context,
|
198
206
|
include_json: Optional[bool],
|
199
207
|
exclude_code: Optional[bool],
|
200
208
|
strict: Optional[bool],
|
201
209
|
include_sandbox: Optional[bool],
|
210
|
+
target_directory: Optional[str],
|
202
211
|
) -> None:
|
203
212
|
"""Pull Resources from Vellum"""
|
204
213
|
|
@@ -208,6 +217,7 @@ def pull(
|
|
208
217
|
exclude_code=exclude_code,
|
209
218
|
strict=strict,
|
210
219
|
include_sandbox=include_sandbox,
|
220
|
+
target_directory=target_directory,
|
211
221
|
)
|
212
222
|
|
213
223
|
|
@@ -242,6 +252,13 @@ Should only be used for debugging purposes.""",
|
|
242
252
|
help="""Generates a runnable sandbox.py file containing test data from the Resource's sandbox. \
|
243
253
|
Helpful for running and debugging workflows locally.""",
|
244
254
|
)
|
255
|
+
@click.option(
|
256
|
+
"--target-dir",
|
257
|
+
"target_directory", # Internal parameter name is target_directory
|
258
|
+
type=str,
|
259
|
+
help="""Directory to pull the workflow into. If not specified, \
|
260
|
+
the workflow will be pulled into the current working directory.""",
|
261
|
+
)
|
245
262
|
def workflows_pull(
|
246
263
|
module: Optional[str],
|
247
264
|
include_json: Optional[bool],
|
@@ -250,6 +267,7 @@ def workflows_pull(
|
|
250
267
|
exclude_code: Optional[bool],
|
251
268
|
strict: Optional[bool],
|
252
269
|
include_sandbox: Optional[bool],
|
270
|
+
target_directory: Optional[str],
|
253
271
|
) -> None:
|
254
272
|
"""
|
255
273
|
Pull Workflows from Vellum. If a module is provided, only the Workflow for that module will be pulled.
|
@@ -264,6 +282,7 @@ def workflows_pull(
|
|
264
282
|
exclude_code=exclude_code,
|
265
283
|
strict=strict,
|
266
284
|
include_sandbox=include_sandbox,
|
285
|
+
target_directory=target_directory,
|
267
286
|
)
|
268
287
|
|
269
288
|
|
@@ -292,12 +311,20 @@ Should only be used for debugging purposes.""",
|
|
292
311
|
help="""Generates a runnable sandbox.py file containing test data from the Resource's sandbox. \
|
293
312
|
Helpful for running and debugging resources locally.""",
|
294
313
|
)
|
314
|
+
@click.option(
|
315
|
+
"--target-dir",
|
316
|
+
"target_directory", # Internal parameter name is target_directory
|
317
|
+
type=str,
|
318
|
+
help="""Directory to pull the workflow into. If not specified, \
|
319
|
+
the workflow will be pulled into the current working directory.""",
|
320
|
+
)
|
295
321
|
def pull_module(
|
296
322
|
ctx: click.Context,
|
297
323
|
include_json: Optional[bool],
|
298
324
|
exclude_code: Optional[bool],
|
299
325
|
strict: Optional[bool],
|
300
326
|
include_sandbox: Optional[bool],
|
327
|
+
target_directory: Optional[str],
|
301
328
|
) -> None:
|
302
329
|
"""Pull a specific module from Vellum"""
|
303
330
|
|
@@ -308,6 +335,7 @@ def pull_module(
|
|
308
335
|
exclude_code=exclude_code,
|
309
336
|
strict=strict,
|
310
337
|
include_sandbox=include_sandbox,
|
338
|
+
target_directory=target_directory,
|
311
339
|
)
|
312
340
|
|
313
341
|
|
@@ -331,5 +359,20 @@ def image_push(image: str, tag: Optional[List[str]] = None) -> None:
|
|
331
359
|
image_push_command(image, tag)
|
332
360
|
|
333
361
|
|
362
|
+
@workflows.command(name="init")
|
363
|
+
@click.argument("template_name", required=False)
|
364
|
+
@click.option(
|
365
|
+
"--target-dir",
|
366
|
+
"target_directory", # Internal parameter name is target_directory
|
367
|
+
type=str,
|
368
|
+
help="""Directory to pull the workflow into. If not specified, \
|
369
|
+
the workflow will be pulled into the current working directory.""",
|
370
|
+
)
|
371
|
+
def workflows_init(template_name: Optional[str] = None, target_directory: Optional[str] = None) -> None:
|
372
|
+
"""Initialize a new Vellum Workflow using a predefined template"""
|
373
|
+
|
374
|
+
init_command(template_name=template_name, target_directory=target_directory)
|
375
|
+
|
376
|
+
|
334
377
|
if __name__ == "__main__":
|
335
378
|
main()
|
vellum_cli/config.py
CHANGED
@@ -50,6 +50,7 @@ class WorkflowConfig(UniversalBaseModel):
|
|
50
50
|
container_image_name: Optional[str] = None
|
51
51
|
container_image_tag: Optional[str] = None
|
52
52
|
workspace: str = DEFAULT_WORKSPACE_CONFIG.name
|
53
|
+
target_directory: Optional[str] = None
|
53
54
|
|
54
55
|
def merge(self, other: "WorkflowConfig") -> "WorkflowConfig":
|
55
56
|
self_deployment_by_id = {
|
vellum_cli/init.py
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
import io
|
2
|
+
import json
|
3
|
+
import os
|
4
|
+
import zipfile
|
5
|
+
from typing import Optional, cast
|
6
|
+
|
7
|
+
import click
|
8
|
+
from dotenv import load_dotenv
|
9
|
+
from pydash import snake_case
|
10
|
+
|
11
|
+
from vellum.client.types.workflow_sandbox_example import WorkflowSandboxExample
|
12
|
+
from vellum.workflows.vellum_client import create_vellum_client
|
13
|
+
from vellum_cli.config import WorkflowConfig, load_vellum_cli_config
|
14
|
+
from vellum_cli.logger import load_cli_logger
|
15
|
+
from vellum_cli.pull import PullContentsMetadata, WorkflowConfigResolutionResult
|
16
|
+
|
17
|
+
ERROR_LOG_FILE_NAME = "error.log"
|
18
|
+
METADATA_FILE_NAME = "metadata.json"
|
19
|
+
|
20
|
+
|
21
|
+
def init_command(template_name: Optional[str] = None, target_directory: Optional[str] = None):
|
22
|
+
load_dotenv()
|
23
|
+
logger = load_cli_logger()
|
24
|
+
config = load_vellum_cli_config()
|
25
|
+
|
26
|
+
client = create_vellum_client()
|
27
|
+
templates_response = client.workflow_sandboxes.list_workflow_sandbox_examples(tag="TEMPLATES")
|
28
|
+
|
29
|
+
templates = templates_response.results
|
30
|
+
if not templates:
|
31
|
+
logger.error("No templates available")
|
32
|
+
return
|
33
|
+
|
34
|
+
if template_name:
|
35
|
+
selected_template = next((t for t in templates if snake_case(t.label) == template_name), None)
|
36
|
+
if not selected_template:
|
37
|
+
logger.error(f"Template {template_name} not found")
|
38
|
+
return
|
39
|
+
else:
|
40
|
+
click.echo(click.style("Available Templates", bold=True, fg="green"))
|
41
|
+
for idx, template in enumerate(templates, 1):
|
42
|
+
click.echo(f"{idx}. {template.label}")
|
43
|
+
|
44
|
+
choice = click.prompt(
|
45
|
+
f"Please select a template number (1-{len(templates)})", type=click.IntRange(1, len(templates))
|
46
|
+
)
|
47
|
+
selected_template = cast(WorkflowSandboxExample, templates[choice - 1])
|
48
|
+
|
49
|
+
click.echo(click.style(f"\nYou selected: {selected_template.label}\n", bold=True, fg="cyan"))
|
50
|
+
|
51
|
+
# Create workflow config with module name from template label
|
52
|
+
workflow_config = WorkflowConfig(
|
53
|
+
workflow_sandbox_id=selected_template.id,
|
54
|
+
module=snake_case(selected_template.label), # Set module name directly from template
|
55
|
+
)
|
56
|
+
config.workflows.append(workflow_config)
|
57
|
+
|
58
|
+
workflow_config_result = WorkflowConfigResolutionResult(
|
59
|
+
workflow_config=workflow_config,
|
60
|
+
pk=selected_template.id,
|
61
|
+
)
|
62
|
+
|
63
|
+
pk = workflow_config_result.pk
|
64
|
+
if not pk:
|
65
|
+
raise ValueError("No workflow sandbox ID found in project to pull from.")
|
66
|
+
|
67
|
+
# Use target_directory if provided, otherwise use current working directory
|
68
|
+
base_dir = os.path.join(os.getcwd(), target_directory) if target_directory else os.getcwd()
|
69
|
+
target_dir = os.path.join(base_dir, *workflow_config.module.split("."))
|
70
|
+
workflow_config.target_directory = target_dir if target_directory else None
|
71
|
+
|
72
|
+
if os.path.exists(target_dir):
|
73
|
+
click.echo(click.style(f"{target_dir} already exists.", fg="red"))
|
74
|
+
return
|
75
|
+
|
76
|
+
logger.info(f"Pulling workflow into {workflow_config.module}...")
|
77
|
+
|
78
|
+
query_parameters = {
|
79
|
+
"include_sandbox": True,
|
80
|
+
}
|
81
|
+
|
82
|
+
response = client.workflows.pull(
|
83
|
+
pk,
|
84
|
+
request_options={"additional_query_parameters": query_parameters},
|
85
|
+
)
|
86
|
+
|
87
|
+
zip_bytes = b"".join(response)
|
88
|
+
zip_buffer = io.BytesIO(zip_bytes)
|
89
|
+
|
90
|
+
error_content = ""
|
91
|
+
|
92
|
+
with zipfile.ZipFile(zip_buffer) as zip_file:
|
93
|
+
if METADATA_FILE_NAME in zip_file.namelist():
|
94
|
+
metadata_json: Optional[dict] = None
|
95
|
+
with zip_file.open(METADATA_FILE_NAME) as source:
|
96
|
+
metadata_json = json.load(source)
|
97
|
+
|
98
|
+
pull_contents_metadata = PullContentsMetadata.model_validate(metadata_json)
|
99
|
+
|
100
|
+
if pull_contents_metadata.runner_config:
|
101
|
+
workflow_config.container_image_name = pull_contents_metadata.runner_config.container_image_name
|
102
|
+
workflow_config.container_image_tag = pull_contents_metadata.runner_config.container_image_tag
|
103
|
+
if workflow_config.container_image_name and not workflow_config.container_image_tag:
|
104
|
+
workflow_config.container_image_tag = "latest"
|
105
|
+
|
106
|
+
if not workflow_config.module and pull_contents_metadata.label:
|
107
|
+
workflow_config.module = snake_case(pull_contents_metadata.label)
|
108
|
+
|
109
|
+
if not workflow_config.module:
|
110
|
+
raise ValueError(f"Failed to resolve a module name for Workflow {pk}")
|
111
|
+
|
112
|
+
for file_name in zip_file.namelist():
|
113
|
+
with zip_file.open(file_name) as source:
|
114
|
+
content = source.read().decode("utf-8")
|
115
|
+
if file_name == ERROR_LOG_FILE_NAME:
|
116
|
+
error_content = content
|
117
|
+
continue
|
118
|
+
if file_name == METADATA_FILE_NAME:
|
119
|
+
continue
|
120
|
+
|
121
|
+
target_file = os.path.join(target_dir, file_name)
|
122
|
+
os.makedirs(os.path.dirname(target_file), exist_ok=True)
|
123
|
+
with open(target_file, "w") as target:
|
124
|
+
logger.info(f"Writing to {target_file}...")
|
125
|
+
target.write(content)
|
126
|
+
|
127
|
+
config.save()
|
128
|
+
|
129
|
+
if error_content:
|
130
|
+
logger.error(error_content)
|
131
|
+
else:
|
132
|
+
logger.info(f"Successfully pulled Workflow into {target_dir}")
|
vellum_cli/pull.py
CHANGED
@@ -108,6 +108,7 @@ def pull_command(
|
|
108
108
|
exclude_code: Optional[bool] = None,
|
109
109
|
strict: Optional[bool] = None,
|
110
110
|
include_sandbox: Optional[bool] = None,
|
111
|
+
target_directory: Optional[str] = None,
|
111
112
|
) -> None:
|
112
113
|
load_dotenv()
|
113
114
|
logger = load_cli_logger()
|
@@ -129,7 +130,7 @@ def pull_command(
|
|
129
130
|
raise ValueError("No workflow sandbox ID found in project to pull from.")
|
130
131
|
|
131
132
|
if workflow_config.module:
|
132
|
-
logger.info(f"Pulling workflow
|
133
|
+
logger.info(f"Pulling workflow {workflow_config.module}...")
|
133
134
|
else:
|
134
135
|
logger.info(f"Pulling workflow from {pk}...")
|
135
136
|
|
@@ -175,7 +176,10 @@ def pull_command(
|
|
175
176
|
if not workflow_config.module:
|
176
177
|
raise ValueError(f"Failed to resolve a module name for Workflow {pk}")
|
177
178
|
|
178
|
-
|
179
|
+
# Use target_directory if provided, otherwise use current working directory
|
180
|
+
base_dir = os.path.join(os.getcwd(), target_directory) if target_directory else os.getcwd()
|
181
|
+
target_dir = os.path.join(base_dir, *workflow_config.module.split("."))
|
182
|
+
workflow_config.target_directory = target_dir if target_directory else None
|
179
183
|
|
180
184
|
# Delete files in target_dir that aren't in the zip file
|
181
185
|
if os.path.exists(target_dir):
|
@@ -233,4 +237,4 @@ Its schema should be considered unstable and subject to change at any time."""
|
|
233
237
|
if error_content:
|
234
238
|
logger.error(error_content)
|
235
239
|
else:
|
236
|
-
logger.info(f"Successfully pulled Workflow into {
|
240
|
+
logger.info(f"Successfully pulled Workflow into {target_dir}")
|