vellum-ai 0.14.5__py3-none-any.whl → 0.14.7__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 (68) hide show
  1. vellum/__init__.py +18 -0
  2. vellum/client/__init__.py +8 -8
  3. vellum/client/core/client_wrapper.py +1 -1
  4. vellum/client/resources/__init__.py +2 -0
  5. vellum/client/resources/workflow_sandboxes/__init__.py +3 -0
  6. vellum/client/resources/workflow_sandboxes/client.py +146 -0
  7. vellum/client/resources/workflow_sandboxes/types/__init__.py +5 -0
  8. vellum/client/resources/workflow_sandboxes/types/list_workflow_sandbox_examples_request_tag.py +5 -0
  9. vellum/client/types/__init__.py +16 -0
  10. vellum/client/types/array_chat_message_content_item.py +6 -1
  11. vellum/client/types/array_chat_message_content_item_request.py +2 -0
  12. vellum/client/types/chat_message_content.py +2 -0
  13. vellum/client/types/chat_message_content_request.py +2 -0
  14. vellum/client/types/document_chat_message_content.py +25 -0
  15. vellum/client/types/document_chat_message_content_request.py +25 -0
  16. vellum/client/types/document_vellum_value.py +25 -0
  17. vellum/client/types/document_vellum_value_request.py +25 -0
  18. vellum/client/types/paginated_workflow_sandbox_example_list.py +23 -0
  19. vellum/client/types/vellum_document.py +20 -0
  20. vellum/client/types/vellum_document_request.py +20 -0
  21. vellum/client/types/vellum_value.py +2 -0
  22. vellum/client/types/vellum_value_request.py +2 -0
  23. vellum/client/types/vellum_variable_type.py +1 -0
  24. vellum/client/types/workflow_sandbox_example.py +22 -0
  25. vellum/resources/workflow_sandboxes/types/__init__.py +3 -0
  26. vellum/resources/workflow_sandboxes/types/list_workflow_sandbox_examples_request_tag.py +3 -0
  27. vellum/types/document_chat_message_content.py +3 -0
  28. vellum/types/document_chat_message_content_request.py +3 -0
  29. vellum/types/document_vellum_value.py +3 -0
  30. vellum/types/document_vellum_value_request.py +3 -0
  31. vellum/types/paginated_workflow_sandbox_example_list.py +3 -0
  32. vellum/types/vellum_document.py +3 -0
  33. vellum/types/vellum_document_request.py +3 -0
  34. vellum/types/workflow_sandbox_example.py +3 -0
  35. vellum/workflows/exceptions.py +18 -0
  36. vellum/workflows/inputs/base.py +27 -1
  37. vellum/workflows/inputs/tests/__init__.py +0 -0
  38. vellum/workflows/inputs/tests/test_inputs.py +49 -0
  39. vellum/workflows/nodes/core/inline_subworkflow_node/node.py +1 -1
  40. vellum/workflows/nodes/core/map_node/node.py +7 -7
  41. vellum/workflows/nodes/core/try_node/node.py +1 -1
  42. vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py +2 -2
  43. vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +5 -3
  44. vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py +5 -4
  45. vellum/workflows/nodes/displayable/inline_prompt_node/tests/test_node.py +4 -4
  46. vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +49 -15
  47. vellum/workflows/nodes/displayable/subworkflow_deployment_node/tests/test_node.py +165 -0
  48. vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py +3 -1
  49. vellum/workflows/outputs/base.py +1 -1
  50. vellum/workflows/runner/runner.py +16 -10
  51. vellum/workflows/state/context.py +7 -7
  52. vellum/workflows/workflows/base.py +61 -59
  53. vellum/workflows/workflows/tests/test_base_workflow.py +131 -40
  54. {vellum_ai-0.14.5.dist-info → vellum_ai-0.14.7.dist-info}/METADATA +1 -1
  55. {vellum_ai-0.14.5.dist-info → vellum_ai-0.14.7.dist-info}/RECORD +68 -44
  56. vellum_cli/__init__.py +36 -0
  57. vellum_cli/init.py +128 -0
  58. vellum_cli/pull.py +6 -3
  59. vellum_cli/tests/test_init.py +355 -0
  60. vellum_cli/tests/test_pull.py +127 -0
  61. vellum_ee/workflows/display/nodes/base_node_display.py +4 -4
  62. vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py +31 -0
  63. vellum_ee/workflows/display/nodes/vellum/utils.py +8 -0
  64. vellum_ee/workflows/display/vellum.py +0 -4
  65. vellum_ee/workflows/display/workflows/tests/test_workflow_display.py +29 -0
  66. {vellum_ai-0.14.5.dist-info → vellum_ai-0.14.7.dist-info}/LICENSE +0 -0
  67. {vellum_ai-0.14.5.dist-info → vellum_ai-0.14.7.dist-info}/WHEEL +0 -0
  68. {vellum_ai-0.14.5.dist-info → vellum_ai-0.14.7.dist-info}/entry_points.txt +0 -0
@@ -112,6 +112,133 @@ def test_pull__second_module(vellum_client, mock_module):
112
112
  assert f.read() == "print('hello')"
113
113
 
114
114
 
115
+ @pytest.mark.parametrize(
116
+ "base_command",
117
+ [
118
+ ["pull"],
119
+ ["workflows", "pull"],
120
+ ],
121
+ ids=["pull", "workflows_pull"],
122
+ )
123
+ def test_pull__with_target_dir(vellum_client, mock_module, base_command):
124
+ # GIVEN a module on the user's filesystem
125
+ temp_dir = mock_module.temp_dir
126
+ module = mock_module.module
127
+ workflow_sandbox_id = mock_module.workflow_sandbox_id
128
+
129
+ # AND a target directory
130
+ target_dir = os.path.join(temp_dir, "dir")
131
+ os.makedirs(target_dir, exist_ok=True)
132
+
133
+ # AND the workflow pull API call returns a zip file
134
+ vellum_client.workflows.pull.return_value = iter([_zip_file_map({"workflow.py": "print('hello')"})])
135
+
136
+ # WHEN the user runs the pull command with target-dir
137
+ runner = CliRunner()
138
+ result = runner.invoke(cli_main, base_command + [module, "--target-dir", target_dir])
139
+
140
+ # THEN the command returns successfully
141
+ assert result.exit_code == 0
142
+
143
+ # AND the workflow.py file is written to the target directory
144
+ module_path = os.path.join(target_dir, *module.split("."))
145
+ workflow_py = os.path.join(module_path, "workflow.py")
146
+ assert os.path.exists(workflow_py)
147
+ with open(workflow_py) as f:
148
+ assert f.read() == "print('hello')"
149
+
150
+ # AND the files are not in the default module directory
151
+ default_module_path = os.path.join(temp_dir, *module.split("."), "workflow.py")
152
+ assert not os.path.exists(default_module_path)
153
+
154
+ # AND the vellum.lock.json file is still updated
155
+ vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
156
+ assert os.path.exists(vellum_lock_json)
157
+ with open(vellum_lock_json) as f:
158
+ lock_data = json.load(f)
159
+ assert lock_data == {
160
+ "version": "1.0",
161
+ "workflows": [
162
+ {
163
+ "module": module,
164
+ "workflow_sandbox_id": workflow_sandbox_id,
165
+ "container_image_name": None,
166
+ "container_image_tag": None,
167
+ "ignore": None,
168
+ "deployments": [],
169
+ "workspace": "default",
170
+ }
171
+ ],
172
+ "workspaces": [],
173
+ }
174
+
175
+
176
+ @pytest.mark.parametrize(
177
+ "base_command",
178
+ [
179
+ ["pull"],
180
+ ["workflows", "pull"],
181
+ ],
182
+ ids=["pull", "workflows_pull"],
183
+ )
184
+ def test_pull__with_nested_target_dir(vellum_client, mock_module, base_command):
185
+ # GIVEN a module on the user's filesystem
186
+ temp_dir = mock_module.temp_dir
187
+ module = mock_module.module
188
+ workflow_sandbox_id = mock_module.workflow_sandbox_id
189
+
190
+ # AND a nested target directory that doesn't exist yet
191
+ nested_target_dir = os.path.join(temp_dir, "dir-1", "dir-2")
192
+
193
+ # AND the workflow pull API call returns a zip file
194
+ vellum_client.workflows.pull.return_value = iter([_zip_file_map({"workflow.py": "print('hello')"})])
195
+
196
+ # WHEN the user runs the pull command with nested target-dir
197
+ runner = CliRunner()
198
+ result = runner.invoke(cli_main, base_command + [module, "--target-dir", nested_target_dir])
199
+
200
+ # THEN the command returns successfully
201
+ assert result.exit_code == 0
202
+
203
+ # AND the nested directory with module subdirectory should be created
204
+ module_path = os.path.join(nested_target_dir, *module.split("."))
205
+ assert os.path.exists(module_path)
206
+
207
+ # AND the nested directory should be created
208
+ assert os.path.exists(module_path)
209
+
210
+ # AND the workflow.py file is written to the nested target directory
211
+ workflow_py = os.path.join(module_path, "workflow.py")
212
+ assert os.path.exists(workflow_py)
213
+ with open(workflow_py) as f:
214
+ assert f.read() == "print('hello')"
215
+
216
+ # AND the files are not in the default module directory
217
+ default_module_path = os.path.join(temp_dir, *module.split("."), "workflow.py")
218
+ assert not os.path.exists(default_module_path)
219
+
220
+ # AND the vellum.lock.json file is still updated
221
+ vellum_lock_json = os.path.join(temp_dir, "vellum.lock.json")
222
+ assert os.path.exists(vellum_lock_json)
223
+ with open(vellum_lock_json) as f:
224
+ lock_data = json.load(f)
225
+ assert lock_data == {
226
+ "version": "1.0",
227
+ "workflows": [
228
+ {
229
+ "module": module,
230
+ "workflow_sandbox_id": workflow_sandbox_id,
231
+ "container_image_name": None,
232
+ "container_image_tag": None,
233
+ "ignore": None,
234
+ "deployments": [],
235
+ "workspace": "default",
236
+ }
237
+ ],
238
+ "workspaces": [],
239
+ }
240
+
241
+
115
242
  def test_pull__sandbox_id_with_no_config(vellum_client):
116
243
  # GIVEN a workflow sandbox id
117
244
  workflow_sandbox_id = "87654321-0000-0000-0000-000000000000"
@@ -47,7 +47,7 @@ from vellum_ee.workflows.display.nodes.get_node_display_class import get_node_di
47
47
  from vellum_ee.workflows.display.nodes.types import NodeOutputDisplay, PortDisplay, PortDisplayOverrides
48
48
  from vellum_ee.workflows.display.utils.expressions import get_child_descriptor
49
49
  from vellum_ee.workflows.display.utils.vellum import convert_descriptor_to_operator, primitive_to_vellum_value
50
- from vellum_ee.workflows.display.vellum import CodeResourceDefinition, GenericNodeDisplayData
50
+ from vellum_ee.workflows.display.vellum import CodeResourceDefinition, NodeDisplayData
51
51
 
52
52
  if TYPE_CHECKING:
53
53
  from vellum_ee.workflows.display.types import WorkflowDisplayContext
@@ -295,9 +295,9 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
295
295
 
296
296
  cls._node_display_registry[node_class] = cls
297
297
 
298
- def _get_generic_node_display_data(self) -> GenericNodeDisplayData:
299
- explicit_value = self._get_explicit_node_display_attr("display_data", GenericNodeDisplayData)
300
- return explicit_value if explicit_value else GenericNodeDisplayData()
298
+ def _get_generic_node_display_data(self) -> NodeDisplayData:
299
+ explicit_value = self._get_explicit_node_display_attr("display_data", NodeDisplayData)
300
+ return explicit_value if explicit_value else NodeDisplayData()
301
301
 
302
302
  def serialize_condition(self, display_context: "WorkflowDisplayContext", condition: BaseDescriptor) -> JsonObject:
303
303
  if isinstance(
@@ -7,6 +7,7 @@ from vellum.workflows.descriptors.base import BaseDescriptor
7
7
  from vellum.workflows.inputs import BaseInputs
8
8
  from vellum.workflows.nodes.bases import BaseNode
9
9
  from vellum.workflows.outputs import BaseOutputs
10
+ from vellum.workflows.references import LazyReference
10
11
  from vellum_ee.workflows.display.nodes.base_node_vellum_display import BaseNodeVellumDisplay
11
12
  from vellum_ee.workflows.display.nodes.types import NodeOutputDisplay
12
13
  from vellum_ee.workflows.display.nodes.vellum.utils import create_node_input_value_pointer_rules
@@ -43,6 +44,10 @@ class MyNodeADisplay(BaseNodeVellumDisplay[MyNodeA]):
43
44
  class MyNodeB(BaseNode):
44
45
  example = MyNodeA.Outputs.output
45
46
  fallback_example = MyNodeA.Outputs.output.coalesce(Inputs.example_workflow_input).coalesce("fallback")
47
+ constant_coalesce = Inputs.example_workflow_input.coalesce("default_value")
48
+ lazy_coalesce: BaseDescriptor[str] = LazyReference(
49
+ lambda: MyNodeA.Outputs.output.coalesce(Inputs.example_workflow_input)
50
+ )
46
51
 
47
52
 
48
53
  @pytest.mark.parametrize(
@@ -76,6 +81,32 @@ class MyNodeB(BaseNode):
76
81
  ConstantValuePointer(type="CONSTANT_VALUE", data=StringVellumValue(value="fallback")),
77
82
  ],
78
83
  ),
84
+ (
85
+ MyNodeB.constant_coalesce,
86
+ [
87
+ InputVariablePointer(
88
+ type="INPUT_VARIABLE",
89
+ data=InputVariableData(input_variable_id="a154c29d-fac0-4cd0-ba88-bc52034f5470"),
90
+ ),
91
+ ConstantValuePointer(type="CONSTANT_VALUE", data=StringVellumValue(value="default_value")),
92
+ ],
93
+ ),
94
+ (
95
+ MyNodeB.lazy_coalesce,
96
+ [
97
+ NodeOutputPointer(
98
+ type="NODE_OUTPUT",
99
+ data=NodeOutputData(
100
+ node_id="b48fa5e0-d7d3-4fe3-ae48-615415011cc5",
101
+ output_id="4b16a629-11a1-4b3f-a965-a57b872d13b8",
102
+ ),
103
+ ),
104
+ InputVariablePointer(
105
+ type="INPUT_VARIABLE",
106
+ data=InputVariableData(input_variable_id="a154c29d-fac0-4cd0-ba88-bc52034f5470"),
107
+ ),
108
+ ],
109
+ ),
79
110
  ],
80
111
  )
81
112
  def test_create_node_input_value_pointer_rules(
@@ -5,8 +5,10 @@ from vellum.workflows.descriptors.base import BaseDescriptor
5
5
  from vellum.workflows.expressions.coalesce_expression import CoalesceExpression
6
6
  from vellum.workflows.nodes.displayable.bases.utils import primitive_to_vellum_value
7
7
  from vellum.workflows.references import NodeReference
8
+ from vellum.workflows.references.lazy import LazyReference
8
9
  from vellum.workflows.utils.uuids import uuid4_from_hash
9
10
  from vellum_ee.workflows.display.types import WorkflowDisplayContext
11
+ from vellum_ee.workflows.display.utils.expressions import get_child_descriptor
10
12
  from vellum_ee.workflows.display.utils.vellum import create_node_input_value_pointer_rule
11
13
  from vellum_ee.workflows.display.vellum import (
12
14
  ConstantValuePointer,
@@ -56,6 +58,12 @@ def create_node_input_value_pointer_rules(
56
58
  raise ValueError(f"Expected NodeReference {value.name} to have an instance")
57
59
  value = cast(BaseDescriptor, value.instance)
58
60
 
61
+ if isinstance(value, LazyReference):
62
+ child_descriptor = get_child_descriptor(value, display_context)
63
+ return create_node_input_value_pointer_rules(
64
+ child_descriptor, display_context, [], pointer_type=pointer_type
65
+ )
66
+
59
67
  if isinstance(value, CoalesceExpression):
60
68
  # Recursively handle the left-hand side
61
69
  lhs_rules = create_node_input_value_pointer_rules(value.lhs, display_context, [], pointer_type=pointer_type)
@@ -41,10 +41,6 @@ class NodeDisplayData(UniversalBaseModel):
41
41
  comment: Optional[NodeDisplayComment] = None
42
42
 
43
43
 
44
- class GenericNodeDisplayData(UniversalBaseModel):
45
- position: NodeDisplayPosition = Field(default_factory=NodeDisplayPosition)
46
-
47
-
48
44
  class CodeResourceDefinition(UniversalBaseModel):
49
45
  name: str
50
46
  module: List[str]
@@ -2,6 +2,8 @@ import pytest
2
2
 
3
3
  from vellum.workflows.nodes.bases.base import BaseNode
4
4
  from vellum.workflows.workflows.base import BaseWorkflow
5
+ from vellum_ee.workflows.display.nodes import BaseNodeDisplay
6
+ from vellum_ee.workflows.display.vellum import NodeDisplayData, NodeDisplayPosition
5
7
  from vellum_ee.workflows.display.workflows import VellumWorkflowDisplay
6
8
  from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
7
9
 
@@ -63,3 +65,30 @@ def test_serialize_workflow__workflow_outputs_reference_non_node_outputs():
63
65
  == """Failed to serialize output 'final': Reference to outputs \
64
66
  'test_serialize_workflow__workflow_outputs_reference_non_node_outputs.<locals>.FirstWorkflow.Outputs' is invalid."""
65
67
  )
68
+
69
+
70
+ def test_serialize_workflow__node_display_class_not_registered():
71
+ # GIVEN a workflow with a node that has a display class referencing display data
72
+ class StartNode(BaseNode):
73
+ class Outputs(BaseNode.Outputs):
74
+ result: str
75
+
76
+ class StartNodeDisplay(BaseNodeDisplay[StartNode]):
77
+ node_input_ids_by_name = {}
78
+ display_data = NodeDisplayData(position=NodeDisplayPosition(x=0, y=0), width=None, height=None)
79
+
80
+ class MyWorkflow(BaseWorkflow):
81
+ graph = StartNode
82
+
83
+ class Outputs(BaseWorkflow.Outputs):
84
+ answer = StartNode.Outputs.result
85
+
86
+ # WHEN we serialize it
87
+ workflow_display = get_workflow_display(
88
+ base_display_class=VellumWorkflowDisplay,
89
+ workflow_class=MyWorkflow,
90
+ )
91
+ data = workflow_display.serialize()
92
+
93
+ # THEN it should should succeed
94
+ assert data is not None