vellum-ai 0.12.13__py3-none-any.whl → 0.12.15__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. vellum/__init__.py +9 -0
  2. vellum/client/__init__.py +2 -6
  3. vellum/client/core/client_wrapper.py +1 -1
  4. vellum/client/environment.py +3 -3
  5. vellum/client/resources/ad_hoc/client.py +2 -6
  6. vellum/client/resources/container_images/client.py +0 -8
  7. vellum/client/resources/metric_definitions/client.py +2 -6
  8. vellum/client/resources/workflows/client.py +8 -8
  9. vellum/client/types/__init__.py +6 -0
  10. vellum/client/types/audio_prompt_block.py +29 -0
  11. vellum/client/types/function_call_prompt_block.py +30 -0
  12. vellum/client/types/image_prompt_block.py +29 -0
  13. vellum/client/types/prompt_block.py +12 -1
  14. vellum/client/types/workflow_push_response.py +1 -0
  15. vellum/plugins/pydantic.py +12 -2
  16. vellum/types/audio_prompt_block.py +3 -0
  17. vellum/types/function_call_prompt_block.py +3 -0
  18. vellum/types/image_prompt_block.py +3 -0
  19. vellum/workflows/descriptors/tests/test_utils.py +3 -0
  20. vellum/workflows/nodes/bases/base.py +4 -1
  21. vellum/workflows/nodes/bases/base_adornment_node.py +75 -0
  22. vellum/workflows/nodes/bases/tests/test_base_node.py +13 -0
  23. vellum/workflows/nodes/core/inline_subworkflow_node/node.py +2 -0
  24. vellum/workflows/nodes/core/map_node/node.py +49 -45
  25. vellum/workflows/nodes/core/retry_node/node.py +10 -45
  26. vellum/workflows/nodes/core/try_node/node.py +12 -84
  27. vellum/workflows/nodes/utils.py +44 -1
  28. vellum/workflows/references/constant.py +21 -0
  29. vellum/workflows/runner/runner.py +4 -3
  30. vellum/workflows/types/cycle_map.py +34 -0
  31. vellum/workflows/workflows/base.py +4 -11
  32. {vellum_ai-0.12.13.dist-info → vellum_ai-0.12.15.dist-info}/METADATA +2 -2
  33. {vellum_ai-0.12.13.dist-info → vellum_ai-0.12.15.dist-info}/RECORD +52 -39
  34. vellum_cli/config.py +4 -0
  35. vellum_cli/pull.py +20 -5
  36. vellum_cli/push.py +7 -0
  37. vellum_cli/tests/test_pull.py +19 -1
  38. vellum_ee/workflows/display/nodes/vellum/__init__.py +2 -0
  39. vellum_ee/workflows/display/nodes/vellum/base_node.py +18 -0
  40. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +10 -41
  41. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py +4 -14
  42. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_generic_node_serialization.py +174 -0
  43. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +2 -10
  44. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +2 -10
  45. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +5 -19
  46. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py +2 -8
  47. vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +14 -25
  48. vellum_ee/workflows/server/__init__.py +0 -0
  49. vellum_ee/workflows/server/virtual_file_loader.py +42 -0
  50. {vellum_ai-0.12.13.dist-info → vellum_ai-0.12.15.dist-info}/LICENSE +0 -0
  51. {vellum_ai-0.12.13.dist-info → vellum_ai-0.12.15.dist-info}/WHEEL +0 -0
  52. {vellum_ai-0.12.13.dist-info → vellum_ai-0.12.15.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,174 @@
1
+ from deepdiff import DeepDiff
2
+
3
+ from vellum_ee.workflows.display.workflows import VellumWorkflowDisplay
4
+ from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
5
+
6
+ from tests.workflows.basic_generic_node.workflow import BasicGenericNodeWorkflow
7
+
8
+
9
+ def test_serialize_workflow(vellum_client):
10
+ # GIVEN a Workflow that uses a generic node
11
+ # WHEN we serialize it
12
+ workflow_display = get_workflow_display(
13
+ base_display_class=VellumWorkflowDisplay, workflow_class=BasicGenericNodeWorkflow
14
+ )
15
+
16
+ serialized_workflow: dict = workflow_display.serialize()
17
+
18
+ # THEN we should get a serialized representation of the Workflow
19
+ assert serialized_workflow.keys() == {
20
+ "workflow_raw_data",
21
+ "input_variables",
22
+ "output_variables",
23
+ }
24
+
25
+ # AND its input variables should be what we expect
26
+ input_variables = serialized_workflow["input_variables"]
27
+ assert len(input_variables) == 1
28
+ assert not DeepDiff(
29
+ [
30
+ {
31
+ "id": "a07c2273-34a7-42b5-bcad-143b6127cc8a",
32
+ "key": "input",
33
+ "type": "STRING",
34
+ "default": None,
35
+ "required": True,
36
+ "extensions": {"color": None},
37
+ },
38
+ ],
39
+ input_variables,
40
+ ignore_order=True,
41
+ )
42
+
43
+ # AND its output variables should be what we expect
44
+ output_variables = serialized_workflow["output_variables"]
45
+ assert len(output_variables) == 1
46
+ assert not DeepDiff(
47
+ [
48
+ {"id": "2b6389d0-266a-4be4-843e-4e543dd3d727", "key": "output", "type": "STRING"},
49
+ ],
50
+ output_variables,
51
+ ignore_order=True,
52
+ )
53
+
54
+ # AND its raw data should be what we expect
55
+ workflow_raw_data = serialized_workflow["workflow_raw_data"]
56
+ assert workflow_raw_data.keys() == {"edges", "nodes", "display_data", "definition"}
57
+ assert len(workflow_raw_data["edges"]) == 2
58
+ assert len(workflow_raw_data["nodes"]) == 3
59
+
60
+ # AND each node should be serialized correctly
61
+ entrypoint_node = workflow_raw_data["nodes"][0]
62
+ assert entrypoint_node == {
63
+ "id": "f1e4678f-c470-400b-a40e-c8922cc99a86",
64
+ "type": "ENTRYPOINT",
65
+ "inputs": [],
66
+ "data": {"label": "Entrypoint Node", "source_handle_id": "40201804-8beb-43ad-8873-a027759512f1"},
67
+ "display_data": {"position": {"x": 0.0, "y": 0.0}},
68
+ "definition": {
69
+ "name": "BaseNode",
70
+ "module": [
71
+ "vellum",
72
+ "workflows",
73
+ "nodes",
74
+ "bases",
75
+ "base",
76
+ ],
77
+ "bases": [],
78
+ },
79
+ }
80
+
81
+ api_node = workflow_raw_data["nodes"][1]
82
+ assert not DeepDiff(
83
+ {
84
+ "id": "c2ed23f7-f6cb-4a56-a91c-2e5f9d8fda7f",
85
+ "type": "GENERIC",
86
+ },
87
+ api_node,
88
+ )
89
+
90
+ final_output_node = workflow_raw_data["nodes"][2]
91
+ assert not DeepDiff(
92
+ {
93
+ "id": "50e3b446-afcd-4a5d-8c6f-5f05eaf2200e",
94
+ "type": "TERMINAL",
95
+ "data": {
96
+ "label": "Final Output",
97
+ "name": "output",
98
+ "target_handle_id": "8bd9f4f3-9f66-4d95-8e84-529b0002c531",
99
+ "output_id": "2b6389d0-266a-4be4-843e-4e543dd3d727",
100
+ "output_type": "STRING",
101
+ "node_input_id": "7a9f2d3a-0b23-4bd4-b567-e9493135b727",
102
+ },
103
+ "inputs": [
104
+ {
105
+ "id": "7a9f2d3a-0b23-4bd4-b567-e9493135b727",
106
+ "key": "node_input",
107
+ "value": {
108
+ "rules": [
109
+ {
110
+ "type": "NODE_OUTPUT",
111
+ "data": {
112
+ "node_id": "c2ed23f7-f6cb-4a56-a91c-2e5f9d8fda7f",
113
+ "output_id": "0a9c7a80-fc89-4a71-aac0-66489e4ddb85",
114
+ },
115
+ }
116
+ ],
117
+ "combinator": "OR",
118
+ },
119
+ }
120
+ ],
121
+ "display_data": {"position": {"x": 0.0, "y": 0.0}},
122
+ "definition": {
123
+ "name": "FinalOutputNode",
124
+ "module": ["vellum", "workflows", "nodes", "displayable", "final_output_node", "node"],
125
+ "bases": [
126
+ {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"], "bases": []}
127
+ ],
128
+ },
129
+ },
130
+ final_output_node,
131
+ ignore_order=True,
132
+ )
133
+
134
+ # AND each edge should be serialized correctly
135
+ serialized_edges = workflow_raw_data["edges"]
136
+ assert not DeepDiff(
137
+ [
138
+ {
139
+ "id": "445dd2de-82b2-482b-89f6-5f49d8eb21a9",
140
+ "source_node_id": "f1e4678f-c470-400b-a40e-c8922cc99a86",
141
+ "source_handle_id": "40201804-8beb-43ad-8873-a027759512f1",
142
+ "target_node_id": "c2ed23f7-f6cb-4a56-a91c-2e5f9d8fda7f",
143
+ "target_handle_id": "b7bfb298-959a-4d2b-8b85-bbd0d2522703",
144
+ "type": "DEFAULT",
145
+ },
146
+ {
147
+ "id": "b741c861-cf67-4649-b9ef-b43a4add72b1",
148
+ "source_node_id": "c2ed23f7-f6cb-4a56-a91c-2e5f9d8fda7f",
149
+ "source_handle_id": "89dccfa5-cc1a-4612-bd87-86cb444f6dd4",
150
+ "target_node_id": "50e3b446-afcd-4a5d-8c6f-5f05eaf2200e",
151
+ "target_handle_id": "8bd9f4f3-9f66-4d95-8e84-529b0002c531",
152
+ "type": "DEFAULT",
153
+ },
154
+ ],
155
+ serialized_edges,
156
+ ignore_order=True,
157
+ )
158
+
159
+ # AND the display data should be what we expect
160
+ display_data = workflow_raw_data["display_data"]
161
+ assert display_data == {
162
+ "viewport": {
163
+ "x": 0.0,
164
+ "y": 0.0,
165
+ "zoom": 1.0,
166
+ }
167
+ }
168
+
169
+ # AND the definition should be what we expect
170
+ definition = workflow_raw_data["definition"]
171
+ assert definition == {
172
+ "name": "BasicGenericNodeWorkflow",
173
+ "module": ["tests", "workflows", "basic_generic_node", "workflow"],
174
+ }
@@ -1,8 +1,5 @@
1
- from unittest import mock
2
-
3
1
  from deepdiff import DeepDiff
4
2
 
5
- from vellum_ee.workflows.display.nodes.base_node_vellum_display import BaseNodeVellumDisplay
6
3
  from vellum_ee.workflows.display.workflows import VellumWorkflowDisplay
7
4
  from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
8
5
 
@@ -15,12 +12,7 @@ def test_serialize_workflow():
15
12
  workflow_display = get_workflow_display(
16
13
  base_display_class=VellumWorkflowDisplay, workflow_class=BasicInlineSubworkflowWorkflow
17
14
  )
18
-
19
- # TODO: Support serialization of BaseNode
20
- # https://app.shortcut.com/vellum/story/4871/support-serialization-of-base-node
21
- with mock.patch.object(BaseNodeVellumDisplay, "serialize") as mocked_serialize:
22
- mocked_serialize.return_value = {"type": "MOCKED"}
23
- serialized_workflow: dict = workflow_display.serialize()
15
+ serialized_workflow: dict = workflow_display.serialize()
24
16
 
25
17
  # THEN we should get a serialized representation of the Workflow
26
18
  assert serialized_workflow.keys() == {
@@ -156,7 +148,7 @@ def test_serialize_workflow():
156
148
  "bases": [],
157
149
  },
158
150
  },
159
- {"type": "MOCKED"},
151
+ {"id": "1381c078-efa2-4255-89a1-7b4cb742c7fc", "type": "GENERIC"},
160
152
  {
161
153
  "id": "a773c3a5-78cb-4250-8d29-7282e8a579d3",
162
154
  "type": "TERMINAL",
@@ -1,8 +1,5 @@
1
- from unittest import mock
2
-
3
1
  from deepdiff import DeepDiff
4
2
 
5
- from vellum_ee.workflows.display.nodes.base_node_vellum_display import BaseNodeVellumDisplay
6
3
  from vellum_ee.workflows.display.workflows import VellumWorkflowDisplay
7
4
  from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
8
5
 
@@ -13,12 +10,7 @@ def test_serialize_workflow():
13
10
  # GIVEN a Workflow that uses a MapNode
14
11
  # WHEN we serialize it
15
12
  workflow_display = get_workflow_display(base_display_class=VellumWorkflowDisplay, workflow_class=SimpleMapExample)
16
-
17
- # TODO: Support serialization of BaseNode
18
- # https://app.shortcut.com/vellum/story/4871/support-serialization-of-base-node
19
- with mock.patch.object(BaseNodeVellumDisplay, "serialize") as mocked_serialize:
20
- mocked_serialize.return_value = {"type": "MOCKED"}
21
- serialized_workflow: dict = workflow_display.serialize()
13
+ serialized_workflow: dict = workflow_display.serialize()
22
14
 
23
15
  # THEN we should get a serialized representation of the Workflow
24
16
  assert serialized_workflow.keys() == {
@@ -141,7 +133,7 @@ def test_serialize_workflow():
141
133
  "bases": [],
142
134
  },
143
135
  },
144
- {"type": "MOCKED"},
136
+ {"id": "baf6d316-dc75-41e8-96c0-015aede96309", "type": "GENERIC"},
145
137
  {
146
138
  "id": "6f4883b2-70b1-4e1c-ae15-7d0f5aec810b",
147
139
  "type": "TERMINAL",
@@ -1,8 +1,5 @@
1
- from unittest import mock
2
-
3
1
  from deepdiff import DeepDiff
4
2
 
5
- from vellum_ee.workflows.display.nodes.base_node_vellum_display import BaseNodeVellumDisplay
6
3
  from vellum_ee.workflows.display.workflows import VellumWorkflowDisplay
7
4
  from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
8
5
 
@@ -15,12 +12,7 @@ def test_serialize_workflow__await_all():
15
12
  workflow_display = get_workflow_display(
16
13
  base_display_class=VellumWorkflowDisplay, workflow_class=AwaitAllPassingWorkflow
17
14
  )
18
-
19
- # TODO: Support serialization of BaseNode
20
- # https://app.shortcut.com/vellum/story/4871/support-serialization-of-base-node
21
- with mock.patch.object(BaseNodeVellumDisplay, "serialize") as mocked_serialize:
22
- mocked_serialize.return_value = {"type": "MOCKED"}
23
- serialized_workflow: dict = workflow_display.serialize()
15
+ serialized_workflow: dict = workflow_display.serialize()
24
16
 
25
17
  # THEN we should get a serialized representation of the Workflow
26
18
  assert serialized_workflow.keys() == {
@@ -76,18 +68,12 @@ def test_serialize_workflow__await_all():
76
68
  },
77
69
  }
78
70
 
79
- passthrough_nodes = [node for node in workflow_raw_data["nodes"] if node["type"] == "MOCKED"]
71
+ passthrough_nodes = [node for node in workflow_raw_data["nodes"] if node["type"] == "GENERIC"]
80
72
  assert not DeepDiff(
81
73
  [
82
- {
83
- "type": "MOCKED",
84
- },
85
- {
86
- "type": "MOCKED",
87
- },
88
- {
89
- "type": "MOCKED",
90
- },
74
+ {"id": "127ef456-91bc-43c6-bd8b-1772db5e3cb5", "type": "GENERIC"},
75
+ {"id": "59243c65-053f-4ea6-9157-3f3edb1477bf", "type": "GENERIC"},
76
+ {"id": "634f0202-9ea9-4c62-b152-1a58c595cffb", "type": "GENERIC"},
91
77
  ],
92
78
  passthrough_nodes,
93
79
  ignore_order=True,
@@ -1,8 +1,5 @@
1
- from unittest import mock
2
-
3
1
  from deepdiff import DeepDiff
4
2
 
5
- from vellum_ee.workflows.display.nodes.base_node_vellum_display import BaseNodeVellumDisplay
6
3
  from vellum_ee.workflows.display.workflows import VellumWorkflowDisplay
7
4
  from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
8
5
 
@@ -13,10 +10,7 @@ def test_serialize_workflow():
13
10
  # GIVEN a Workflow with a TryNode
14
11
  # WHEN we serialize it
15
12
  workflow_display = get_workflow_display(base_display_class=VellumWorkflowDisplay, workflow_class=SimpleTryExample)
16
-
17
- with mock.patch.object(BaseNodeVellumDisplay, "serialize") as mocked_serialize:
18
- mocked_serialize.return_value = {"type": "MOCKED"}
19
- serialized_workflow: dict = workflow_display.serialize()
13
+ serialized_workflow: dict = workflow_display.serialize()
20
14
 
21
15
  # THEN we should get a serialized representation of the Workflow
22
16
  assert serialized_workflow.keys() == {
@@ -82,4 +76,4 @@ def test_serialize_workflow():
82
76
  }
83
77
 
84
78
  try_node = workflow_raw_data["nodes"][1]
85
- assert try_node == {"type": "MOCKED"}
79
+ assert try_node == {"id": "1381c078-efa2-4255-89a1-7b4cb742c7fc", "type": "GENERIC"}
@@ -1,9 +1,7 @@
1
1
  import pytest
2
- from unittest import mock
3
2
 
4
3
  from deepdiff import DeepDiff
5
4
 
6
- from vellum_ee.workflows.display.nodes.base_node_vellum_display import BaseNodeVellumDisplay
7
5
  from vellum_ee.workflows.display.workflows import VellumWorkflowDisplay
8
6
  from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
9
7
 
@@ -13,17 +11,12 @@ from tests.workflows.complex_final_output_node.missing_workflow_output import Mi
13
11
 
14
12
  def test_serialize_workflow__missing_final_output_node():
15
13
  # GIVEN a Workflow that is missing a Terminal Node
14
+ workflow_display = get_workflow_display(
15
+ base_display_class=VellumWorkflowDisplay, workflow_class=MissingFinalOutputNodeWorkflow
16
+ )
16
17
 
17
- # TODO: Support serialization of BaseNode
18
- # https://app.shortcut.com/vellum/story/4871/support-serialization-of-base-node
19
- with mock.patch.object(BaseNodeVellumDisplay, "serialize") as mocked_serialize:
20
- mocked_serialize.return_value = {"type": "MOCKED"}
21
- workflow_display = get_workflow_display(
22
- base_display_class=VellumWorkflowDisplay, workflow_class=MissingFinalOutputNodeWorkflow
23
- )
24
-
25
- # WHEN we serialize it
26
- serialized_workflow: dict = workflow_display.serialize()
18
+ # WHEN we serialize it
19
+ serialized_workflow: dict = workflow_display.serialize()
27
20
 
28
21
  # THEN we should get a serialized representation of the Workflow
29
22
  assert serialized_workflow.keys() == {
@@ -97,9 +90,10 @@ def test_serialize_workflow__missing_final_output_node():
97
90
  },
98
91
  }
99
92
 
100
- passthrough_node = next(node for node in workflow_raw_data["nodes"] if node["type"] == "MOCKED")
93
+ passthrough_node = next(node for node in workflow_raw_data["nodes"] if node["type"] == "GENERIC")
101
94
  assert passthrough_node == {
102
- "type": "MOCKED",
95
+ "id": "32d88cab-e9fa-4a56-9bc2-fb6e1fd0897f",
96
+ "type": "GENERIC",
103
97
  }
104
98
 
105
99
  final_output_nodes = [node for node in workflow_raw_data["nodes"] if node["type"] == "TERMINAL"]
@@ -218,17 +212,12 @@ def test_serialize_workflow__missing_final_output_node():
218
212
 
219
213
  def test_serialize_workflow__missing_workflow_output():
220
214
  # GIVEN a Workflow that contains a terminal node that is unreferenced by the Workflow's Outputs
215
+ workflow_display = get_workflow_display(
216
+ base_display_class=VellumWorkflowDisplay, workflow_class=MissingWorkflowOutputWorkflow
217
+ )
221
218
 
222
- # TODO: Support serialization of BaseNode
223
- # https://app.shortcut.com/vellum/story/4871/support-serialization-of-base-node
224
- with mock.patch.object(BaseNodeVellumDisplay, "serialize") as mocked_serialize:
225
- mocked_serialize.return_value = {"type": "MOCKED"}
226
- workflow_display = get_workflow_display(
227
- base_display_class=VellumWorkflowDisplay, workflow_class=MissingWorkflowOutputWorkflow
228
- )
229
-
230
- # WHEN we serialize it, it should throw an error
231
- with pytest.raises(ValueError) as exc_info:
232
- workflow_display.serialize()
219
+ # WHEN we serialize it, it should throw an error
220
+ with pytest.raises(ValueError) as exc_info:
221
+ workflow_display.serialize()
233
222
 
234
223
  assert exc_info.value.args[0] == "Unable to serialize terminal nodes that are not referenced by workflow outputs."
File without changes
@@ -0,0 +1,42 @@
1
+ import importlib
2
+
3
+
4
+ class VirtualFileLoader(importlib.abc.Loader):
5
+ def __init__(self, code: str, is_package: bool):
6
+ self.code = code
7
+ self.is_package = is_package
8
+
9
+ def create_module(self, spec):
10
+ return None # use default module creation
11
+
12
+ def exec_module(self, module):
13
+ if not self.is_package:
14
+ exec(self.code, module.__dict__)
15
+
16
+
17
+ class VirtualFileFinder(importlib.abc.MetaPathFinder, importlib.abc.Loader):
18
+ def __init__(self, files: dict[str, str], namespace: str):
19
+ self.files = files
20
+ self.namespace = namespace
21
+
22
+ def find_spec(self, fullname, path, target=None):
23
+ # Do the namespacing on the fly to avoid having to copy the file dict
24
+ prefixed_name = fullname if fullname.startswith(self.namespace) else f"{self.namespace}.{fullname}"
25
+
26
+ key_name = "__init__" if fullname == self.namespace else fullname.replace(f"{self.namespace}.", "")
27
+
28
+ files_key = f"{key_name.replace('.', '/')}.py"
29
+ if not self.files.get(files_key):
30
+ files_key = f"{key_name.replace('.', '/')}/__init__.py"
31
+
32
+ file = self.files.get(files_key)
33
+ is_package = "__init__" in files_key
34
+
35
+ if file:
36
+ return importlib.machinery.ModuleSpec(
37
+ prefixed_name,
38
+ VirtualFileLoader(file, is_package),
39
+ origin=prefixed_name,
40
+ is_package=is_package,
41
+ )
42
+ return None