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.
Files changed (65) hide show
  1. vellum/__init__.py +14 -0
  2. vellum/client/core/client_wrapper.py +1 -1
  3. vellum/client/types/__init__.py +14 -0
  4. vellum/client/types/array_chat_message_content_item.py +6 -1
  5. vellum/client/types/array_chat_message_content_item_request.py +2 -0
  6. vellum/client/types/chat_message_content.py +2 -0
  7. vellum/client/types/chat_message_content_request.py +2 -0
  8. vellum/client/types/document_chat_message_content.py +25 -0
  9. vellum/client/types/document_chat_message_content_request.py +25 -0
  10. vellum/client/types/document_prompt_block.py +29 -0
  11. vellum/client/types/document_vellum_value.py +25 -0
  12. vellum/client/types/document_vellum_value_request.py +25 -0
  13. vellum/client/types/prompt_block.py +2 -0
  14. vellum/client/types/vellum_document.py +20 -0
  15. vellum/client/types/vellum_document_request.py +20 -0
  16. vellum/client/types/vellum_value.py +2 -0
  17. vellum/client/types/vellum_value_request.py +2 -0
  18. vellum/client/types/vellum_variable_type.py +1 -0
  19. vellum/types/document_chat_message_content.py +3 -0
  20. vellum/types/document_chat_message_content_request.py +3 -0
  21. vellum/types/document_prompt_block.py +3 -0
  22. vellum/types/document_vellum_value.py +3 -0
  23. vellum/types/document_vellum_value_request.py +3 -0
  24. vellum/types/vellum_document.py +3 -0
  25. vellum/types/vellum_document_request.py +3 -0
  26. vellum/workflows/exceptions.py +18 -0
  27. vellum/workflows/inputs/base.py +27 -1
  28. vellum/workflows/inputs/tests/__init__.py +0 -0
  29. vellum/workflows/inputs/tests/test_inputs.py +49 -0
  30. vellum/workflows/nodes/core/inline_subworkflow_node/node.py +1 -1
  31. vellum/workflows/nodes/core/map_node/node.py +7 -7
  32. vellum/workflows/nodes/core/try_node/node.py +1 -1
  33. vellum/workflows/nodes/displayable/api_node/tests/test_api_node.py +33 -0
  34. vellum/workflows/nodes/displayable/bases/api_node/node.py +1 -1
  35. vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py +2 -2
  36. vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +5 -3
  37. vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py +5 -4
  38. vellum/workflows/nodes/displayable/inline_prompt_node/tests/test_node.py +4 -4
  39. vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +39 -15
  40. vellum/workflows/nodes/displayable/subworkflow_deployment_node/tests/test_node.py +142 -0
  41. vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py +3 -1
  42. vellum/workflows/outputs/base.py +1 -1
  43. vellum/workflows/runner/runner.py +16 -10
  44. vellum/workflows/state/context.py +7 -7
  45. vellum/workflows/workflows/base.py +16 -5
  46. vellum/workflows/workflows/tests/test_base_workflow.py +131 -40
  47. {vellum_ai-0.14.6.dist-info → vellum_ai-0.14.8.dist-info}/METADATA +1 -1
  48. {vellum_ai-0.14.6.dist-info → vellum_ai-0.14.8.dist-info}/RECORD +65 -47
  49. vellum_cli/__init__.py +43 -0
  50. vellum_cli/config.py +1 -0
  51. vellum_cli/init.py +132 -0
  52. vellum_cli/pull.py +7 -3
  53. vellum_cli/tests/test_init.py +473 -0
  54. vellum_cli/tests/test_pull.py +135 -0
  55. vellum_cli/tests/test_push.py +1 -0
  56. vellum_ee/workflows/display/nodes/base_node_display.py +4 -4
  57. vellum_ee/workflows/display/tests/test_vellum_workflow_display.py +83 -0
  58. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +118 -3
  59. vellum_ee/workflows/display/vellum.py +0 -4
  60. vellum_ee/workflows/display/workflows/base_workflow_display.py +46 -13
  61. vellum_ee/workflows/display/workflows/tests/test_workflow_display.py +29 -0
  62. vellum_ee/workflows/display/workflows/vellum_workflow_display.py +12 -0
  63. {vellum_ai-0.14.6.dist-info → vellum_ai-0.14.8.dist-info}/LICENSE +0 -0
  64. {vellum_ai-0.14.6.dist-info → vellum_ai-0.14.8.dist-info}/WHEEL +0 -0
  65. {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 into {workflow_config.module}...")
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
- target_dir = os.path.join(os.getcwd(), *workflow_config.module.split("."))
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 {workflow_config.module}")
240
+ logger.info(f"Successfully pulled Workflow into {target_dir}")