vellum-ai 0.9.16rc2__py3-none-any.whl → 0.9.16rc4__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 (245) hide show
  1. vellum/plugins/__init__.py +0 -0
  2. vellum/plugins/pydantic.py +74 -0
  3. vellum/plugins/utils.py +19 -0
  4. vellum/plugins/vellum_mypy.py +639 -3
  5. vellum/workflows/README.md +90 -0
  6. vellum/workflows/__init__.py +5 -0
  7. vellum/workflows/constants.py +43 -0
  8. vellum/workflows/descriptors/__init__.py +0 -0
  9. vellum/workflows/descriptors/base.py +339 -0
  10. vellum/workflows/descriptors/tests/test_utils.py +83 -0
  11. vellum/workflows/descriptors/utils.py +90 -0
  12. vellum/workflows/edges/__init__.py +5 -0
  13. vellum/workflows/edges/edge.py +23 -0
  14. vellum/workflows/emitters/__init__.py +5 -0
  15. vellum/workflows/emitters/base.py +14 -0
  16. vellum/workflows/environment/__init__.py +5 -0
  17. vellum/workflows/environment/environment.py +7 -0
  18. vellum/workflows/errors/__init__.py +6 -0
  19. vellum/workflows/errors/types.py +20 -0
  20. vellum/workflows/events/__init__.py +31 -0
  21. vellum/workflows/events/node.py +125 -0
  22. vellum/workflows/events/tests/__init__.py +0 -0
  23. vellum/workflows/events/tests/test_event.py +216 -0
  24. vellum/workflows/events/types.py +52 -0
  25. vellum/workflows/events/utils.py +5 -0
  26. vellum/workflows/events/workflow.py +139 -0
  27. vellum/workflows/exceptions.py +15 -0
  28. vellum/workflows/expressions/__init__.py +0 -0
  29. vellum/workflows/expressions/accessor.py +52 -0
  30. vellum/workflows/expressions/and_.py +32 -0
  31. vellum/workflows/expressions/begins_with.py +31 -0
  32. vellum/workflows/expressions/between.py +38 -0
  33. vellum/workflows/expressions/coalesce_expression.py +41 -0
  34. vellum/workflows/expressions/contains.py +30 -0
  35. vellum/workflows/expressions/does_not_begin_with.py +31 -0
  36. vellum/workflows/expressions/does_not_contain.py +30 -0
  37. vellum/workflows/expressions/does_not_end_with.py +31 -0
  38. vellum/workflows/expressions/does_not_equal.py +25 -0
  39. vellum/workflows/expressions/ends_with.py +31 -0
  40. vellum/workflows/expressions/equals.py +25 -0
  41. vellum/workflows/expressions/greater_than.py +33 -0
  42. vellum/workflows/expressions/greater_than_or_equal_to.py +33 -0
  43. vellum/workflows/expressions/in_.py +31 -0
  44. vellum/workflows/expressions/is_blank.py +24 -0
  45. vellum/workflows/expressions/is_not_blank.py +24 -0
  46. vellum/workflows/expressions/is_not_null.py +21 -0
  47. vellum/workflows/expressions/is_not_undefined.py +22 -0
  48. vellum/workflows/expressions/is_null.py +21 -0
  49. vellum/workflows/expressions/is_undefined.py +22 -0
  50. vellum/workflows/expressions/less_than.py +33 -0
  51. vellum/workflows/expressions/less_than_or_equal_to.py +33 -0
  52. vellum/workflows/expressions/not_between.py +38 -0
  53. vellum/workflows/expressions/not_in.py +31 -0
  54. vellum/workflows/expressions/or_.py +32 -0
  55. vellum/workflows/graph/__init__.py +3 -0
  56. vellum/workflows/graph/graph.py +131 -0
  57. vellum/workflows/graph/tests/__init__.py +0 -0
  58. vellum/workflows/graph/tests/test_graph.py +437 -0
  59. vellum/workflows/inputs/__init__.py +5 -0
  60. vellum/workflows/inputs/base.py +55 -0
  61. vellum/workflows/logging.py +14 -0
  62. vellum/workflows/nodes/__init__.py +46 -0
  63. vellum/workflows/nodes/bases/__init__.py +7 -0
  64. vellum/workflows/nodes/bases/base.py +332 -0
  65. vellum/workflows/nodes/bases/base_subworkflow_node/__init__.py +5 -0
  66. vellum/workflows/nodes/bases/base_subworkflow_node/node.py +10 -0
  67. vellum/workflows/nodes/bases/tests/__init__.py +0 -0
  68. vellum/workflows/nodes/bases/tests/test_base_node.py +125 -0
  69. vellum/workflows/nodes/core/__init__.py +16 -0
  70. vellum/workflows/nodes/core/error_node/__init__.py +5 -0
  71. vellum/workflows/nodes/core/error_node/node.py +26 -0
  72. vellum/workflows/nodes/core/inline_subworkflow_node/__init__.py +5 -0
  73. vellum/workflows/nodes/core/inline_subworkflow_node/node.py +73 -0
  74. vellum/workflows/nodes/core/map_node/__init__.py +5 -0
  75. vellum/workflows/nodes/core/map_node/node.py +147 -0
  76. vellum/workflows/nodes/core/map_node/tests/__init__.py +0 -0
  77. vellum/workflows/nodes/core/map_node/tests/test_node.py +65 -0
  78. vellum/workflows/nodes/core/retry_node/__init__.py +5 -0
  79. vellum/workflows/nodes/core/retry_node/node.py +106 -0
  80. vellum/workflows/nodes/core/retry_node/tests/__init__.py +0 -0
  81. vellum/workflows/nodes/core/retry_node/tests/test_node.py +93 -0
  82. vellum/workflows/nodes/core/templating_node/__init__.py +5 -0
  83. vellum/workflows/nodes/core/templating_node/custom_filters.py +12 -0
  84. vellum/workflows/nodes/core/templating_node/exceptions.py +2 -0
  85. vellum/workflows/nodes/core/templating_node/node.py +123 -0
  86. vellum/workflows/nodes/core/templating_node/render.py +55 -0
  87. vellum/workflows/nodes/core/templating_node/tests/test_templating_node.py +21 -0
  88. vellum/workflows/nodes/core/try_node/__init__.py +5 -0
  89. vellum/workflows/nodes/core/try_node/node.py +110 -0
  90. vellum/workflows/nodes/core/try_node/tests/__init__.py +0 -0
  91. vellum/workflows/nodes/core/try_node/tests/test_node.py +82 -0
  92. vellum/workflows/nodes/displayable/__init__.py +31 -0
  93. vellum/workflows/nodes/displayable/api_node/__init__.py +5 -0
  94. vellum/workflows/nodes/displayable/api_node/node.py +44 -0
  95. vellum/workflows/nodes/displayable/bases/__init__.py +11 -0
  96. vellum/workflows/nodes/displayable/bases/api_node/__init__.py +5 -0
  97. vellum/workflows/nodes/displayable/bases/api_node/node.py +70 -0
  98. vellum/workflows/nodes/displayable/bases/base_prompt_node/__init__.py +5 -0
  99. vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py +60 -0
  100. vellum/workflows/nodes/displayable/bases/inline_prompt_node/__init__.py +5 -0
  101. vellum/workflows/nodes/displayable/bases/inline_prompt_node/constants.py +13 -0
  102. vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +118 -0
  103. vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py +98 -0
  104. vellum/workflows/nodes/displayable/bases/search_node.py +90 -0
  105. vellum/workflows/nodes/displayable/code_execution_node/__init__.py +5 -0
  106. vellum/workflows/nodes/displayable/code_execution_node/node.py +197 -0
  107. vellum/workflows/nodes/displayable/code_execution_node/tests/__init__.py +0 -0
  108. vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/__init__.py +0 -0
  109. vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/main.py +3 -0
  110. vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py +111 -0
  111. vellum/workflows/nodes/displayable/code_execution_node/utils.py +10 -0
  112. vellum/workflows/nodes/displayable/conditional_node/__init__.py +5 -0
  113. vellum/workflows/nodes/displayable/conditional_node/node.py +25 -0
  114. vellum/workflows/nodes/displayable/final_output_node/__init__.py +5 -0
  115. vellum/workflows/nodes/displayable/final_output_node/node.py +43 -0
  116. vellum/workflows/nodes/displayable/guardrail_node/__init__.py +5 -0
  117. vellum/workflows/nodes/displayable/guardrail_node/node.py +97 -0
  118. vellum/workflows/nodes/displayable/inline_prompt_node/__init__.py +5 -0
  119. vellum/workflows/nodes/displayable/inline_prompt_node/node.py +41 -0
  120. vellum/workflows/nodes/displayable/merge_node/__init__.py +5 -0
  121. vellum/workflows/nodes/displayable/merge_node/node.py +10 -0
  122. vellum/workflows/nodes/displayable/prompt_deployment_node/__init__.py +5 -0
  123. vellum/workflows/nodes/displayable/prompt_deployment_node/node.py +45 -0
  124. vellum/workflows/nodes/displayable/search_node/__init__.py +5 -0
  125. vellum/workflows/nodes/displayable/search_node/node.py +26 -0
  126. vellum/workflows/nodes/displayable/subworkflow_deployment_node/__init__.py +5 -0
  127. vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +156 -0
  128. vellum/workflows/nodes/displayable/tests/__init__.py +0 -0
  129. vellum/workflows/nodes/displayable/tests/test_inline_text_prompt_node.py +148 -0
  130. vellum/workflows/nodes/displayable/tests/test_search_node_wth_text_output.py +134 -0
  131. vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py +80 -0
  132. vellum/workflows/nodes/utils.py +27 -0
  133. vellum/workflows/outputs/__init__.py +6 -0
  134. vellum/workflows/outputs/base.py +196 -0
  135. vellum/workflows/ports/__init__.py +7 -0
  136. vellum/workflows/ports/node_ports.py +75 -0
  137. vellum/workflows/ports/port.py +75 -0
  138. vellum/workflows/ports/utils.py +40 -0
  139. vellum/workflows/references/__init__.py +17 -0
  140. vellum/workflows/references/environment_variable.py +20 -0
  141. vellum/workflows/references/execution_count.py +20 -0
  142. vellum/workflows/references/external_input.py +49 -0
  143. vellum/workflows/references/input.py +7 -0
  144. vellum/workflows/references/lazy.py +55 -0
  145. vellum/workflows/references/node.py +43 -0
  146. vellum/workflows/references/output.py +78 -0
  147. vellum/workflows/references/state_value.py +23 -0
  148. vellum/workflows/references/vellum_secret.py +15 -0
  149. vellum/workflows/references/workflow_input.py +41 -0
  150. vellum/workflows/resolvers/__init__.py +5 -0
  151. vellum/workflows/resolvers/base.py +15 -0
  152. vellum/workflows/runner/__init__.py +5 -0
  153. vellum/workflows/runner/runner.py +588 -0
  154. vellum/workflows/runner/types.py +18 -0
  155. vellum/workflows/state/__init__.py +5 -0
  156. vellum/workflows/state/base.py +327 -0
  157. vellum/workflows/state/context.py +18 -0
  158. vellum/workflows/state/encoder.py +57 -0
  159. vellum/workflows/state/store.py +28 -0
  160. vellum/workflows/state/tests/__init__.py +0 -0
  161. vellum/workflows/state/tests/test_state.py +113 -0
  162. vellum/workflows/types/__init__.py +0 -0
  163. vellum/workflows/types/core.py +91 -0
  164. vellum/workflows/types/generics.py +14 -0
  165. vellum/workflows/types/stack.py +39 -0
  166. vellum/workflows/types/tests/__init__.py +0 -0
  167. vellum/workflows/types/tests/test_utils.py +76 -0
  168. vellum/workflows/types/utils.py +164 -0
  169. vellum/workflows/utils/__init__.py +0 -0
  170. vellum/workflows/utils/names.py +13 -0
  171. vellum/workflows/utils/tests/__init__.py +0 -0
  172. vellum/workflows/utils/tests/test_names.py +15 -0
  173. vellum/workflows/utils/tests/test_vellum_variables.py +25 -0
  174. vellum/workflows/utils/vellum_variables.py +81 -0
  175. vellum/workflows/vellum_client.py +18 -0
  176. vellum/workflows/workflows/__init__.py +5 -0
  177. vellum/workflows/workflows/base.py +365 -0
  178. {vellum_ai-0.9.16rc2.dist-info → vellum_ai-0.9.16rc4.dist-info}/METADATA +2 -1
  179. {vellum_ai-0.9.16rc2.dist-info → vellum_ai-0.9.16rc4.dist-info}/RECORD +245 -7
  180. vellum_cli/__init__.py +72 -0
  181. vellum_cli/aliased_group.py +103 -0
  182. vellum_cli/config.py +96 -0
  183. vellum_cli/image_push.py +112 -0
  184. vellum_cli/logger.py +36 -0
  185. vellum_cli/pull.py +73 -0
  186. vellum_cli/push.py +121 -0
  187. vellum_cli/tests/test_config.py +100 -0
  188. vellum_cli/tests/test_pull.py +152 -0
  189. vellum_ee/workflows/__init__.py +0 -0
  190. vellum_ee/workflows/display/__init__.py +0 -0
  191. vellum_ee/workflows/display/base.py +73 -0
  192. vellum_ee/workflows/display/nodes/__init__.py +4 -0
  193. vellum_ee/workflows/display/nodes/base_node_display.py +116 -0
  194. vellum_ee/workflows/display/nodes/base_node_vellum_display.py +36 -0
  195. vellum_ee/workflows/display/nodes/get_node_display_class.py +25 -0
  196. vellum_ee/workflows/display/nodes/tests/__init__.py +0 -0
  197. vellum_ee/workflows/display/nodes/tests/test_base_node_display.py +47 -0
  198. vellum_ee/workflows/display/nodes/types.py +18 -0
  199. vellum_ee/workflows/display/nodes/utils.py +33 -0
  200. vellum_ee/workflows/display/nodes/vellum/__init__.py +32 -0
  201. vellum_ee/workflows/display/nodes/vellum/api_node.py +205 -0
  202. vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +71 -0
  203. vellum_ee/workflows/display/nodes/vellum/conditional_node.py +217 -0
  204. vellum_ee/workflows/display/nodes/vellum/final_output_node.py +61 -0
  205. vellum_ee/workflows/display/nodes/vellum/guardrail_node.py +49 -0
  206. vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +170 -0
  207. vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +99 -0
  208. vellum_ee/workflows/display/nodes/vellum/map_node.py +100 -0
  209. vellum_ee/workflows/display/nodes/vellum/merge_node.py +48 -0
  210. vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py +68 -0
  211. vellum_ee/workflows/display/nodes/vellum/search_node.py +193 -0
  212. vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py +58 -0
  213. vellum_ee/workflows/display/nodes/vellum/templating_node.py +67 -0
  214. vellum_ee/workflows/display/nodes/vellum/tests/__init__.py +0 -0
  215. vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py +106 -0
  216. vellum_ee/workflows/display/nodes/vellum/try_node.py +38 -0
  217. vellum_ee/workflows/display/nodes/vellum/utils.py +76 -0
  218. vellum_ee/workflows/display/tests/__init__.py +0 -0
  219. vellum_ee/workflows/display/tests/workflow_serialization/__init__.py +0 -0
  220. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_api_node_serialization.py +426 -0
  221. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +607 -0
  222. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +1175 -0
  223. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_guardrail_node_serialization.py +235 -0
  224. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +511 -0
  225. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +372 -0
  226. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +272 -0
  227. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_prompt_deployment_serialization.py +289 -0
  228. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_subworkflow_deployment_serialization.py +354 -0
  229. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py +123 -0
  230. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py +84 -0
  231. vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +233 -0
  232. vellum_ee/workflows/display/types.py +46 -0
  233. vellum_ee/workflows/display/utils/__init__.py +0 -0
  234. vellum_ee/workflows/display/utils/tests/__init__.py +0 -0
  235. vellum_ee/workflows/display/utils/tests/test_uuids.py +16 -0
  236. vellum_ee/workflows/display/utils/uuids.py +24 -0
  237. vellum_ee/workflows/display/utils/vellum.py +121 -0
  238. vellum_ee/workflows/display/vellum.py +357 -0
  239. vellum_ee/workflows/display/workflows/__init__.py +5 -0
  240. vellum_ee/workflows/display/workflows/base_workflow_display.py +302 -0
  241. vellum_ee/workflows/display/workflows/get_vellum_workflow_display_class.py +32 -0
  242. vellum_ee/workflows/display/workflows/vellum_workflow_display.py +386 -0
  243. {vellum_ai-0.9.16rc2.dist-info → vellum_ai-0.9.16rc4.dist-info}/LICENSE +0 -0
  244. {vellum_ai-0.9.16rc2.dist-info → vellum_ai-0.9.16rc4.dist-info}/WHEEL +0 -0
  245. {vellum_ai-0.9.16rc2.dist-info → vellum_ai-0.9.16rc4.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,100 @@
1
+ import os
2
+ import tempfile
3
+ from uuid import uuid4
4
+
5
+ from vellum_cli.config import VellumCliConfig, WorkflowConfig, load_vellum_cli_config
6
+
7
+
8
+ def test_load_vellum_cli_config__pyproject_toml_only():
9
+ # GIVEN a pyproject.toml file with vellum config
10
+ cwd = tempfile.mkdtemp()
11
+ with open(os.path.join(cwd, "pyproject.toml"), "w") as f:
12
+ f.write(
13
+ """[[tool.vellum.workflows]]
14
+ module = "module1.workflow1"
15
+ """
16
+ )
17
+
18
+ # WHEN the config is loaded
19
+ config = load_vellum_cli_config(cwd)
20
+
21
+ # THEN the config is loaded correctly
22
+ assert config == VellumCliConfig(
23
+ workflows=[WorkflowConfig(module="module1.workflow1")],
24
+ version="1.0",
25
+ )
26
+
27
+
28
+ def test_load_vellum_cli_config__pyproject_toml_and_lockfile():
29
+ # GIVEN a pyproject.toml file with vellum config
30
+ cwd = tempfile.mkdtemp()
31
+ with open(os.path.join(cwd, "pyproject.toml"), "w") as f:
32
+ f.write(
33
+ """[[tool.vellum.workflows]]
34
+ module = "module1.workflow1"
35
+ """
36
+ )
37
+
38
+ # AND a lockfile
39
+ workflow_sandbox_id = uuid4()
40
+ with open(os.path.join(cwd, "vellum.lock.json"), "w") as f:
41
+ f.write(
42
+ f"""{{
43
+ "version": "1.0",
44
+ "workflows": [
45
+ {{
46
+ "workflow_sandbox_id": "{workflow_sandbox_id}",
47
+ "module": "module1.workflow1"
48
+ }}
49
+ ]
50
+ }}
51
+ """
52
+ )
53
+
54
+ # WHEN the config is loaded
55
+ config = load_vellum_cli_config(cwd)
56
+
57
+ # THEN the config is loaded correctly
58
+ assert config == VellumCliConfig(
59
+ workflows=[WorkflowConfig(module="module1.workflow1", workflow_sandbox_id=str(workflow_sandbox_id))],
60
+ version="1.0",
61
+ )
62
+
63
+
64
+ def test_load_vellum_cli_config__pyproject_toml_and_lockfile__different_modules():
65
+ # GIVEN a pyproject.toml file with vellum config
66
+ cwd = tempfile.mkdtemp()
67
+ with open(os.path.join(cwd, "pyproject.toml"), "w") as f:
68
+ f.write(
69
+ """[[tool.vellum.workflows]]
70
+ module = "module1.workflow1"
71
+ """
72
+ )
73
+
74
+ # AND a lockfile
75
+ workflow_sandbox_id = uuid4()
76
+ with open(os.path.join(cwd, "vellum.lock.json"), "w") as f:
77
+ f.write(
78
+ f"""{{
79
+ "version": "1.0",
80
+ "workflows": [
81
+ {{
82
+ "workflow_sandbox_id": "{workflow_sandbox_id}",
83
+ "module": "module2.workflow2"
84
+ }}
85
+ ]
86
+ }}
87
+ """
88
+ )
89
+
90
+ # WHEN the config is loaded
91
+ config = load_vellum_cli_config(cwd)
92
+
93
+ # THEN the config is loaded correctly
94
+ assert config == VellumCliConfig(
95
+ workflows=[
96
+ WorkflowConfig(module="module1.workflow1"),
97
+ WorkflowConfig(module="module2.workflow2", workflow_sandbox_id=str(workflow_sandbox_id)),
98
+ ],
99
+ version="1.0",
100
+ )
@@ -0,0 +1,152 @@
1
+ import pytest
2
+ import io
3
+ import os
4
+ import shutil
5
+ import tempfile
6
+ from uuid import uuid4
7
+ import zipfile
8
+ from typing import Generator, Tuple
9
+
10
+ import tomli_w
11
+
12
+ from vellum_cli.pull import pull_command
13
+
14
+
15
+ def zip_file_map(file_map: dict[str, str]) -> bytes:
16
+ # Create an in-memory bytes buffer to store the zip
17
+ zip_buffer = io.BytesIO()
18
+
19
+ # Create zip file and add files from file_map
20
+ with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
21
+ for filename, content in file_map.items():
22
+ zip_file.writestr(filename, content)
23
+
24
+ # Get the bytes from the buffer
25
+ zip_bytes = zip_buffer.getvalue()
26
+ zip_buffer.close()
27
+
28
+ return zip_bytes
29
+
30
+
31
+ @pytest.fixture
32
+ def mock_module() -> Generator[Tuple[str, str], None, None]:
33
+ current_dir = os.getcwd()
34
+ temp_dir = tempfile.mkdtemp()
35
+ os.chdir(temp_dir)
36
+ module = "examples.mock"
37
+
38
+ with open(os.path.join(temp_dir, "pyproject.toml"), "wb") as f:
39
+ tomli_w.dump(
40
+ {
41
+ "tool": {
42
+ "vellum": {
43
+ "workflows": [
44
+ {
45
+ "module": module,
46
+ "workflow_sandbox_id": str(uuid4()),
47
+ }
48
+ ]
49
+ }
50
+ }
51
+ },
52
+ f,
53
+ )
54
+
55
+ yield temp_dir, module
56
+
57
+ os.chdir(current_dir)
58
+ shutil.rmtree(temp_dir)
59
+
60
+
61
+ def test_pull(vellum_client, mock_module):
62
+ # GIVEN a module on the user's filesystem
63
+ temp_dir, module = mock_module
64
+
65
+ # AND the workflow pull API call returns a zip file
66
+ vellum_client.workflows.pull.return_value = iter([zip_file_map({"workflow.py": "print('hello')"})])
67
+
68
+ # WHEN the user runs the pull command
69
+ pull_command(module)
70
+
71
+ # THEN the workflow.py file is written to the module directory
72
+ assert os.path.exists(os.path.join(temp_dir, *module.split("."), "workflow.py"))
73
+ with open(os.path.join(temp_dir, *module.split("."), "workflow.py")) as f:
74
+ assert f.read() == "print('hello')"
75
+
76
+
77
+ def test_pull__remove_missing_files(vellum_client, mock_module):
78
+ # GIVEN a module on the user's filesystem
79
+ temp_dir, module = mock_module
80
+
81
+ # AND the workflow pull API call returns a zip file
82
+ vellum_client.workflows.pull.return_value = iter([zip_file_map({"workflow.py": "print('hello')"})])
83
+
84
+ # AND there is already a different file in the module directory
85
+ other_file_path = os.path.join(temp_dir, *module.split("."), "other_file.py")
86
+ os.makedirs(os.path.dirname(other_file_path), exist_ok=True)
87
+ with open(other_file_path, "w") as f:
88
+ f.write("print('hello')")
89
+
90
+ # WHEN the user runs the pull command
91
+ pull_command(module)
92
+
93
+ # THEN the workflow.py file is written to the module directory
94
+ assert os.path.exists(os.path.join(temp_dir, *module.split("."), "workflow.py"))
95
+ with open(os.path.join(temp_dir, *module.split("."), "workflow.py")) as f:
96
+ assert f.read() == "print('hello')"
97
+
98
+ # AND the other_file.py file is deleted
99
+ assert not os.path.exists(other_file_path)
100
+
101
+
102
+ def test_pull__remove_missing_files__ignore_pattern(vellum_client, mock_module):
103
+ # GIVEN a module on the user's filesystem
104
+ temp_dir, module = mock_module
105
+
106
+ # AND the workflow pull API call returns a zip file
107
+ vellum_client.workflows.pull.return_value = iter([zip_file_map({"workflow.py": "print('hello')"})])
108
+
109
+ # AND there is already a different file in the module directory
110
+ other_file_path = os.path.join(temp_dir, *module.split("."), "other_file.py")
111
+ os.makedirs(os.path.dirname(other_file_path), exist_ok=True)
112
+ with open(other_file_path, "w") as f:
113
+ f.write("print('hello')")
114
+
115
+ # AND there is already a test file
116
+ test_file_path = os.path.join(temp_dir, *module.split("."), "tests", "test_workflow.py")
117
+ os.makedirs(os.path.dirname(test_file_path), exist_ok=True)
118
+ with open(test_file_path, "w") as f:
119
+ f.write("print('hello')")
120
+
121
+ # AND the ignore pattern is set to tests
122
+ with open(os.path.join(temp_dir, "pyproject.toml"), "wb") as f:
123
+ tomli_w.dump(
124
+ {
125
+ "tool": {
126
+ "vellum": {
127
+ "workflows": [
128
+ {
129
+ "module": module,
130
+ "workflow_sandbox_id": str(uuid4()),
131
+ "ignore": "tests/*",
132
+ }
133
+ ]
134
+ }
135
+ }
136
+ },
137
+ f,
138
+ )
139
+
140
+ # WHEN the user runs the pull command
141
+ pull_command(module)
142
+
143
+ # THEN the workflow.py file is written to the module directory
144
+ assert os.path.exists(os.path.join(temp_dir, *module.split("."), "workflow.py"))
145
+ with open(os.path.join(temp_dir, *module.split("."), "workflow.py")) as f:
146
+ assert f.read() == "print('hello')"
147
+
148
+ # AND the other_file.py file is deleted
149
+ assert not os.path.exists(other_file_path)
150
+
151
+ # AND the tests/test_workflow.py file is untouched
152
+ assert os.path.exists(test_file_path)
File without changes
File without changes
@@ -0,0 +1,73 @@
1
+ from dataclasses import dataclass
2
+ from uuid import UUID
3
+ from typing import TypeVar
4
+
5
+
6
+ @dataclass
7
+ class WorkflowMetaDisplayOverrides:
8
+ pass
9
+
10
+
11
+ @dataclass
12
+ class WorkflowMetaDisplay(WorkflowMetaDisplayOverrides):
13
+ pass
14
+
15
+
16
+ WorkflowMetaDisplayType = TypeVar("WorkflowMetaDisplayType", bound=WorkflowMetaDisplay)
17
+ WorkflowMetaDisplayOverridesType = TypeVar("WorkflowMetaDisplayOverridesType", bound=WorkflowMetaDisplayOverrides)
18
+
19
+
20
+ @dataclass
21
+ class WorkflowInputsDisplayOverrides:
22
+ id: UUID
23
+
24
+
25
+ @dataclass
26
+ class WorkflowInputsDisplay(WorkflowInputsDisplayOverrides):
27
+ pass
28
+
29
+
30
+ WorkflowInputsDisplayType = TypeVar("WorkflowInputsDisplayType", bound=WorkflowInputsDisplay)
31
+ WorkflowInputsDisplayOverridesType = TypeVar("WorkflowInputsDisplayOverridesType", bound=WorkflowInputsDisplayOverrides)
32
+
33
+
34
+ @dataclass
35
+ class EdgeDisplayOverrides:
36
+ id: UUID
37
+
38
+
39
+ @dataclass
40
+ class EdgeDisplay(EdgeDisplayOverrides):
41
+ pass
42
+
43
+
44
+ EdgeDisplayType = TypeVar("EdgeDisplayType", bound=EdgeDisplay)
45
+ EdgeDisplayOverridesType = TypeVar("EdgeDisplayOverridesType", bound=EdgeDisplayOverrides)
46
+
47
+
48
+ @dataclass
49
+ class EntrypointDisplayOverrides:
50
+ id: UUID
51
+
52
+
53
+ @dataclass
54
+ class EntrypointDisplay(EntrypointDisplayOverrides):
55
+ pass
56
+
57
+
58
+ EntrypointDisplayType = TypeVar("EntrypointDisplayType", bound=EntrypointDisplay)
59
+ EntrypointDisplayOverridesType = TypeVar("EntrypointDisplayOverridesType", bound=EntrypointDisplayOverrides)
60
+
61
+
62
+ @dataclass
63
+ class WorkflowOutputDisplayOverrides:
64
+ id: UUID
65
+
66
+
67
+ @dataclass
68
+ class WorkflowOutputDisplay(WorkflowOutputDisplayOverrides):
69
+ pass
70
+
71
+
72
+ WorkflowOutputDisplayType = TypeVar("WorkflowOutputDisplayType", bound=WorkflowOutputDisplay)
73
+ WorkflowOutputDisplayOverridesType = TypeVar("WorkflowOutputDisplayOverridesType", bound=WorkflowOutputDisplayOverrides)
@@ -0,0 +1,4 @@
1
+ # flake8: noqa: F401
2
+
3
+ # Force an import to ensure that all display classes are registered with the BaseNodeDisplay registry
4
+ from .vellum import * # noqa: F401
@@ -0,0 +1,116 @@
1
+ from functools import cached_property
2
+ import inspect
3
+ from uuid import UUID
4
+ from typing import TYPE_CHECKING, Any, Dict, Generic, Optional, Type, TypeVar, get_args
5
+
6
+ from vellum_ee.workflows.display.nodes.types import NodeOutputDisplay, PortDisplay, PortDisplayOverrides
7
+ from vellum_ee.workflows.display.utils.uuids import uuid4_from_hash
8
+ from vellum_ee.workflows.display.vellum import CodeResourceDefinition, NodeDefinition
9
+ from vellum.workflows.nodes.bases.base import BaseNode
10
+ from vellum.workflows.nodes.utils import get_wrapped_node, has_wrapped_node
11
+ from vellum.workflows.ports import Port
12
+ from vellum.workflows.references import OutputReference
13
+ from vellum.workflows.types.core import JsonObject
14
+ from vellum.workflows.types.generics import NodeType
15
+ from vellum.workflows.types.utils import get_original_base
16
+ from vellum.workflows.utils.names import pascal_to_title_case
17
+
18
+ if TYPE_CHECKING:
19
+ from vellum_ee.workflows.display.types import WorkflowDisplayContext
20
+
21
+ _NodeDisplayAttrType = TypeVar("_NodeDisplayAttrType")
22
+
23
+
24
+ class BaseNodeDisplay(Generic[NodeType]):
25
+ output_display: Dict[OutputReference, NodeOutputDisplay] = {}
26
+ port_displays: Dict[Port, PortDisplayOverrides] = {}
27
+
28
+ # Used to store the mapping between node types and their display classes
29
+ _node_display_registry: Dict[Type[NodeType], Type["BaseNodeDisplay"]] = {}
30
+
31
+ def __init__(self, node: Type[NodeType]):
32
+ self._node = node
33
+
34
+ def serialize(self, display_context: "WorkflowDisplayContext", **kwargs: Any) -> JsonObject:
35
+ raise NotImplementedError(f"Serialization for nodes of type {self._node.__name__} is not supported.")
36
+
37
+ def get_definition(self) -> NodeDefinition:
38
+ node = self._node
39
+ node_definition = NodeDefinition(
40
+ name=node.__name__,
41
+ module=node.__module__.split("."),
42
+ bases=[
43
+ CodeResourceDefinition(
44
+ name=base.__name__,
45
+ module=base.__module__.split("."),
46
+ )
47
+ for base in node.__bases__
48
+ ],
49
+ )
50
+ return node_definition
51
+
52
+ def get_node_output_display(self, output: OutputReference) -> NodeOutputDisplay:
53
+ explicit_display = self.output_display.get(output)
54
+ if explicit_display:
55
+ return explicit_display
56
+
57
+ return NodeOutputDisplay(id=uuid4_from_hash(f"{self.node_id}|{output.name}"), name=output.name)
58
+
59
+ def get_node_port_display(self, port: Port) -> PortDisplay:
60
+ overrides = self.port_displays.get(port)
61
+
62
+ port_id: UUID
63
+ if overrides:
64
+ port_id = overrides.id
65
+ else:
66
+ port_id = uuid4_from_hash(f"{self.node_id}|ports|{port.name}")
67
+
68
+ return PortDisplay(id=port_id, node_id=self.node_id)
69
+
70
+ @classmethod
71
+ def get_from_node_display_registry(cls, node_class: Type[NodeType]) -> Type["BaseNodeDisplay"]:
72
+ return cls._node_display_registry[node_class]
73
+
74
+ @cached_property
75
+ def node_id(self) -> UUID:
76
+ """Can be overridden as a class attribute to specify a custom node id."""
77
+ return uuid4_from_hash(self._node.__qualname__)
78
+
79
+ @cached_property
80
+ def label(self) -> str:
81
+ """Can be overridden as a class attribute to specify a custom label."""
82
+ return pascal_to_title_case(self._node.__name__)
83
+
84
+ @classmethod
85
+ def _get_explicit_node_display_attr(
86
+ cls,
87
+ attribute: str,
88
+ attribute_type: Type[_NodeDisplayAttrType],
89
+ ) -> Optional[_NodeDisplayAttrType]:
90
+ node_display_attribute: Optional[_NodeDisplayAttrType] = getattr(cls, attribute, None)
91
+
92
+ if node_display_attribute is None:
93
+ return None
94
+
95
+ if isinstance(node_display_attribute, attribute_type):
96
+ return node_display_attribute
97
+
98
+ raise ValueError(f"Node {cls.__name__} must define an explicit {attribute} of type {attribute_type.__name__}.")
99
+
100
+ def __init_subclass__(cls, **kwargs: Any) -> None:
101
+ super().__init_subclass__(**kwargs)
102
+
103
+ original_base = get_original_base(cls)
104
+ node_class = get_args(original_base)[0]
105
+ if isinstance(node_class, TypeVar):
106
+ bounded_class = node_class.__bound__
107
+ if inspect.isclass(bounded_class) and issubclass(bounded_class, BaseNode):
108
+ cls._node_display_registry[bounded_class] = cls
109
+ elif issubclass(node_class, BaseNode):
110
+ if has_wrapped_node(node_class):
111
+ wrapped_node = get_wrapped_node(node_class)
112
+ if wrapped_node._is_wrapped_node:
113
+ cls._node_display_registry[wrapped_node] = cls
114
+ return
115
+
116
+ cls._node_display_registry[node_class] = cls
@@ -0,0 +1,36 @@
1
+ from uuid import UUID
2
+ from typing import ClassVar, Dict, Optional
3
+
4
+ from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
5
+ from vellum_ee.workflows.display.nodes.types import PortDisplay
6
+ from vellum_ee.workflows.display.utils.uuids import uuid4_from_hash
7
+ from vellum_ee.workflows.display.vellum import NodeDisplayData
8
+ from vellum.workflows.ports import Port
9
+ from vellum.workflows.types.generics import NodeType
10
+
11
+
12
+ class BaseNodeVellumDisplay(BaseNodeDisplay[NodeType]):
13
+ # Used to explicitly set display data for a node
14
+ display_data: ClassVar[Optional[NodeDisplayData]] = None
15
+
16
+ # Used to explicitly set the target handle id for a node
17
+ target_handle_id: ClassVar[Optional[UUID]] = None
18
+
19
+ # Used to explicitly set the node input ids by name for a node
20
+ node_input_ids_by_name: ClassVar[Dict[str, UUID]] = {}
21
+
22
+ def _get_node_display_uuid(self, attribute: str) -> UUID:
23
+ explicit_value = self._get_explicit_node_display_attr(attribute, UUID)
24
+ return explicit_value if explicit_value else uuid4_from_hash(f"{self.node_id}|{attribute}")
25
+
26
+ def get_display_data(self) -> NodeDisplayData:
27
+ explicit_value = self._get_explicit_node_display_attr("display_data", NodeDisplayData)
28
+ return explicit_value if explicit_value else NodeDisplayData()
29
+
30
+ def get_target_handle_id(self) -> UUID:
31
+ return self._get_node_display_uuid("target_handle_id")
32
+
33
+ def get_source_handle_id(self, port_displays: Dict[Port, PortDisplay]) -> UUID:
34
+ default_port = self._node.Ports.default
35
+ default_port_display = port_displays[default_port]
36
+ return default_port_display.id
@@ -0,0 +1,25 @@
1
+ from typing import Optional, Type
2
+
3
+ from vellum_ee.workflows.display.types import NodeDisplayType
4
+ from vellum.workflows.types.generics import NodeType
5
+
6
+
7
+ def get_node_display_class(
8
+ base_class: Type[NodeDisplayType], node_class: Type[NodeType], root_node_class: Optional[Type[NodeType]] = None
9
+ ) -> Type[NodeDisplayType]:
10
+ try:
11
+ node_display_class = base_class.get_from_node_display_registry(node_class)
12
+ except KeyError:
13
+ try:
14
+ return get_node_display_class(
15
+ base_class, node_class.__bases__[0], node_class if root_node_class is None else root_node_class
16
+ )
17
+ except IndexError:
18
+ return base_class
19
+
20
+ if not issubclass(node_display_class, base_class):
21
+ raise TypeError(
22
+ f"Expected to find a subclass of '{base_class.__name__}' for node class '{node_class.__name__}'"
23
+ )
24
+
25
+ return node_display_class
File without changes
@@ -0,0 +1,47 @@
1
+ import pytest
2
+ from uuid import UUID
3
+
4
+ from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
5
+ from vellum.workflows.nodes.bases import BaseNode
6
+
7
+
8
+ @pytest.fixture
9
+ def node_with_implicit_properties():
10
+ class MyNode(BaseNode):
11
+ pass
12
+
13
+ class MyNodeDisplay(BaseNodeDisplay[MyNode]):
14
+ pass
15
+
16
+ expected_id = UUID("ace7f746-4fe6-45c7-8207-fc8a4d0c7f6f")
17
+
18
+ return MyNode, MyNodeDisplay, expected_id
19
+
20
+
21
+ @pytest.fixture
22
+ def node_with_explicit_properties():
23
+ explicit_id = UUID("a422f67a-1d37-43f0-bdfc-1e4618c9496d")
24
+
25
+ class MyNode(BaseNode):
26
+ pass
27
+
28
+ class MyNodeDisplay(BaseNodeDisplay[MyNode]):
29
+ node_id = explicit_id
30
+
31
+ return MyNode, MyNodeDisplay, explicit_id
32
+
33
+
34
+ @pytest.fixture(
35
+ params=[
36
+ "node_with_implicit_properties",
37
+ "node_with_explicit_properties",
38
+ ]
39
+ )
40
+ def node_info(request):
41
+ return request.getfixturevalue(request.param)
42
+
43
+
44
+ def test_get_id(node_info):
45
+ node, node_display, expected_id = node_info
46
+
47
+ assert node_display(node).node_id == expected_id
@@ -0,0 +1,18 @@
1
+ from dataclasses import dataclass
2
+ from uuid import UUID
3
+
4
+
5
+ @dataclass
6
+ class NodeOutputDisplay:
7
+ id: UUID
8
+ name: str
9
+
10
+
11
+ @dataclass
12
+ class PortDisplayOverrides:
13
+ id: UUID
14
+
15
+
16
+ @dataclass
17
+ class PortDisplay(PortDisplayOverrides):
18
+ node_id: UUID
@@ -0,0 +1,33 @@
1
+ import re
2
+ from typing import Optional, TypeVar, Union, overload
3
+
4
+ from vellum.workflows.descriptors.base import BaseDescriptor
5
+ from vellum.workflows.references.node import NodeReference
6
+
7
+ _T = TypeVar("_T")
8
+
9
+
10
+ @overload
11
+ def raise_if_descriptor(node_attr: BaseDescriptor[_T]) -> _T: ...
12
+
13
+
14
+ @overload
15
+ def raise_if_descriptor(node_attr: _T) -> _T: ...
16
+
17
+
18
+ def raise_if_descriptor(node_attr: Union[NodeReference[_T], _T]) -> Optional[_T]:
19
+ if not isinstance(node_attr, NodeReference):
20
+ raise AttributeError(f"Expected to find a Node descriptor, but found '{node_attr.__class__.__name__}'")
21
+
22
+ return node_attr.instance
23
+
24
+
25
+ def to_kebab_case(string: str) -> str:
26
+ """Converts a string from Capital Case to kebab-case."""
27
+ string = re.sub(r"(\s|_|-)+", " ", string)
28
+ string = re.sub(
29
+ r"[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+",
30
+ lambda mo: " " + mo.group(0).lower(),
31
+ string,
32
+ )
33
+ return "-".join(string.split())
@@ -0,0 +1,32 @@
1
+ from .api_node import BaseAPINodeDisplay
2
+ from .code_execution_node import BaseCodeExecutionNodeDisplay
3
+ from .conditional_node import BaseConditionalNodeDisplay
4
+ from .final_output_node import BaseFinalOutputNodeDisplay
5
+ from .guardrail_node import BaseGuardrailNodeDisplay
6
+ from .inline_prompt_node import BaseInlinePromptNodeDisplay
7
+ from .inline_subworkflow_node import BaseInlineSubworkflowNodeDisplay
8
+ from .map_node import BaseMapNodeDisplay
9
+ from .merge_node import BaseMergeNodeDisplay
10
+ from .prompt_deployment_node import BasePromptDeploymentNodeDisplay
11
+ from .search_node import BaseSearchNodeDisplay
12
+ from .subworkflow_deployment_node import BaseSubworkflowDeploymentNodeDisplay
13
+ from .templating_node import BaseTemplatingNodeDisplay
14
+ from .try_node import BaseTryNodeDisplay
15
+
16
+ # All node display classes must be imported here to be registered in BaseNodeDisplay's node display registry
17
+ __all__ = [
18
+ "BaseCodeExecutionNodeDisplay",
19
+ "BaseConditionalNodeDisplay",
20
+ "BaseGuardrailNodeDisplay",
21
+ "BaseInlinePromptNodeDisplay",
22
+ "BaseInlineSubworkflowNodeDisplay",
23
+ "BaseAPINodeDisplay",
24
+ "BaseMapNodeDisplay",
25
+ "BaseMergeNodeDisplay",
26
+ "BaseSearchNodeDisplay",
27
+ "BaseSubworkflowDeploymentNodeDisplay",
28
+ "BaseTemplatingNodeDisplay",
29
+ "BasePromptDeploymentNodeDisplay",
30
+ "BaseFinalOutputNodeDisplay",
31
+ "BaseTryNodeDisplay",
32
+ ]