vellum-ai 1.11.2__py3-none-any.whl → 1.13.5__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.

Potentially problematic release.


This version of vellum-ai might be problematic. Click here for more details.

Files changed (275) hide show
  1. vellum/__init__.py +18 -0
  2. vellum/client/README.md +1 -1
  3. vellum/client/core/client_wrapper.py +2 -2
  4. vellum/client/core/force_multipart.py +4 -2
  5. vellum/client/core/http_response.py +1 -1
  6. vellum/client/core/pydantic_utilities.py +7 -4
  7. vellum/client/errors/too_many_requests_error.py +1 -2
  8. vellum/client/reference.md +677 -76
  9. vellum/client/resources/container_images/client.py +299 -0
  10. vellum/client/resources/container_images/raw_client.py +286 -0
  11. vellum/client/resources/documents/client.py +20 -10
  12. vellum/client/resources/documents/raw_client.py +20 -10
  13. vellum/client/resources/events/raw_client.py +4 -4
  14. vellum/client/resources/integration_auth_configs/client.py +2 -0
  15. vellum/client/resources/integration_auth_configs/raw_client.py +2 -0
  16. vellum/client/resources/integration_providers/client.py +28 -2
  17. vellum/client/resources/integration_providers/raw_client.py +24 -0
  18. vellum/client/resources/integrations/client.py +52 -4
  19. vellum/client/resources/integrations/raw_client.py +61 -0
  20. vellum/client/resources/workflow_deployments/client.py +156 -0
  21. vellum/client/resources/workflow_deployments/raw_client.py +334 -0
  22. vellum/client/resources/workflows/client.py +212 -8
  23. vellum/client/resources/workflows/raw_client.py +343 -6
  24. vellum/client/types/__init__.py +18 -0
  25. vellum/client/types/api_actor_type_enum.py +1 -1
  26. vellum/client/types/check_workflow_execution_status_error.py +21 -0
  27. vellum/client/types/check_workflow_execution_status_response.py +29 -0
  28. vellum/client/types/code_execution_package_request.py +21 -0
  29. vellum/client/types/composio_execute_tool_request.py +5 -0
  30. vellum/client/types/composio_tool_definition.py +1 -0
  31. vellum/client/types/container_image_build_config.py +1 -0
  32. vellum/client/types/container_image_container_image_tag.py +1 -0
  33. vellum/client/types/dataset_row_push_request.py +3 -0
  34. vellum/client/types/document_document_to_document_index.py +1 -0
  35. vellum/client/types/integration_name.py +24 -0
  36. vellum/client/types/node_execution_fulfilled_body.py +1 -0
  37. vellum/client/types/node_execution_log_body.py +24 -0
  38. vellum/client/types/node_execution_log_event.py +47 -0
  39. vellum/client/types/prompt_deployment_release_prompt_deployment.py +1 -0
  40. vellum/client/types/runner_config_request.py +24 -0
  41. vellum/client/types/severity_enum.py +5 -0
  42. vellum/client/types/slim_composio_tool_definition.py +1 -0
  43. vellum/client/types/slim_document_document_to_document_index.py +2 -0
  44. vellum/client/types/type_checker_enum.py +5 -0
  45. vellum/client/types/vellum_audio.py +5 -1
  46. vellum/client/types/vellum_audio_request.py +5 -1
  47. vellum/client/types/vellum_document.py +5 -1
  48. vellum/client/types/vellum_document_request.py +5 -1
  49. vellum/client/types/vellum_image.py +5 -1
  50. vellum/client/types/vellum_image_request.py +5 -1
  51. vellum/client/types/vellum_node_execution_event.py +2 -0
  52. vellum/client/types/vellum_variable.py +5 -0
  53. vellum/client/types/vellum_variable_extensions.py +1 -0
  54. vellum/client/types/vellum_variable_type.py +1 -0
  55. vellum/client/types/vellum_video.py +5 -1
  56. vellum/client/types/vellum_video_request.py +5 -1
  57. vellum/client/types/workflow_deployment_release_workflow_deployment.py +1 -0
  58. vellum/client/types/workflow_event.py +2 -0
  59. vellum/client/types/workflow_execution_fulfilled_body.py +1 -0
  60. vellum/client/types/workflow_result_event_output_data_array.py +1 -1
  61. vellum/client/types/workflow_result_event_output_data_chat_history.py +1 -1
  62. vellum/client/types/workflow_result_event_output_data_error.py +1 -1
  63. vellum/client/types/workflow_result_event_output_data_function_call.py +1 -1
  64. vellum/client/types/workflow_result_event_output_data_json.py +1 -1
  65. vellum/client/types/workflow_result_event_output_data_number.py +1 -1
  66. vellum/client/types/workflow_result_event_output_data_search_results.py +1 -1
  67. vellum/client/types/workflow_result_event_output_data_string.py +1 -1
  68. vellum/client/types/workflow_sandbox_execute_node_response.py +8 -0
  69. vellum/plugins/vellum_mypy.py +37 -2
  70. vellum/types/check_workflow_execution_status_error.py +3 -0
  71. vellum/types/check_workflow_execution_status_response.py +3 -0
  72. vellum/types/code_execution_package_request.py +3 -0
  73. vellum/types/node_execution_log_body.py +3 -0
  74. vellum/types/node_execution_log_event.py +3 -0
  75. vellum/types/runner_config_request.py +3 -0
  76. vellum/types/severity_enum.py +3 -0
  77. vellum/types/type_checker_enum.py +3 -0
  78. vellum/types/workflow_sandbox_execute_node_response.py +3 -0
  79. vellum/utils/files/mixin.py +26 -0
  80. vellum/utils/files/tests/test_mixin.py +62 -0
  81. vellum/utils/tests/test_vellum_client.py +95 -0
  82. vellum/utils/uuid.py +19 -2
  83. vellum/utils/vellum_client.py +10 -3
  84. vellum/workflows/__init__.py +7 -1
  85. vellum/workflows/descriptors/base.py +86 -0
  86. vellum/workflows/descriptors/tests/test_utils.py +9 -0
  87. vellum/workflows/errors/tests/__init__.py +0 -0
  88. vellum/workflows/errors/tests/test_types.py +52 -0
  89. vellum/workflows/errors/types.py +1 -0
  90. vellum/workflows/events/node.py +24 -0
  91. vellum/workflows/events/tests/test_event.py +123 -0
  92. vellum/workflows/events/types.py +2 -1
  93. vellum/workflows/events/workflow.py +28 -2
  94. vellum/workflows/expressions/add.py +3 -0
  95. vellum/workflows/expressions/tests/test_add.py +24 -0
  96. vellum/workflows/graph/graph.py +26 -5
  97. vellum/workflows/graph/tests/test_graph.py +228 -1
  98. vellum/workflows/inputs/base.py +22 -6
  99. vellum/workflows/inputs/dataset_row.py +121 -16
  100. vellum/workflows/inputs/tests/test_inputs.py +3 -3
  101. vellum/workflows/integrations/tests/test_vellum_integration_service.py +84 -0
  102. vellum/workflows/integrations/vellum_integration_service.py +12 -1
  103. vellum/workflows/loaders/base.py +2 -0
  104. vellum/workflows/nodes/bases/base.py +37 -16
  105. vellum/workflows/nodes/bases/tests/test_base_node.py +104 -1
  106. vellum/workflows/nodes/core/inline_subworkflow_node/node.py +1 -0
  107. vellum/workflows/nodes/core/inline_subworkflow_node/tests/test_node.py +1 -1
  108. vellum/workflows/nodes/core/map_node/node.py +7 -5
  109. vellum/workflows/nodes/core/map_node/tests/test_node.py +33 -0
  110. vellum/workflows/nodes/core/retry_node/node.py +1 -0
  111. vellum/workflows/nodes/core/try_node/node.py +1 -0
  112. vellum/workflows/nodes/displayable/api_node/node.py +3 -2
  113. vellum/workflows/nodes/displayable/api_node/tests/test_api_node.py +38 -0
  114. vellum/workflows/nodes/displayable/bases/api_node/node.py +1 -1
  115. vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py +18 -1
  116. vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +109 -2
  117. vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py +13 -2
  118. vellum/workflows/nodes/displayable/code_execution_node/node.py +9 -15
  119. vellum/workflows/nodes/displayable/code_execution_node/tests/test_node.py +65 -24
  120. vellum/workflows/nodes/displayable/code_execution_node/utils.py +3 -0
  121. vellum/workflows/nodes/displayable/final_output_node/node.py +24 -69
  122. vellum/workflows/nodes/displayable/final_output_node/tests/test_node.py +53 -3
  123. vellum/workflows/nodes/displayable/note_node/node.py +4 -1
  124. vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +16 -5
  125. vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py +47 -0
  126. vellum/workflows/nodes/displayable/tool_calling_node/node.py +74 -34
  127. vellum/workflows/nodes/displayable/tool_calling_node/tests/test_node.py +204 -8
  128. vellum/workflows/nodes/displayable/tool_calling_node/utils.py +92 -71
  129. vellum/workflows/nodes/mocks.py +47 -213
  130. vellum/workflows/nodes/tests/test_mocks.py +0 -177
  131. vellum/workflows/nodes/utils.py +23 -8
  132. vellum/workflows/outputs/base.py +36 -3
  133. vellum/workflows/references/environment_variable.py +1 -11
  134. vellum/workflows/references/lazy.py +8 -0
  135. vellum/workflows/references/state_value.py +24 -1
  136. vellum/workflows/references/tests/test_lazy.py +58 -0
  137. vellum/workflows/references/trigger.py +8 -3
  138. vellum/workflows/references/workflow_input.py +8 -0
  139. vellum/workflows/resolvers/resolver.py +13 -3
  140. vellum/workflows/resolvers/tests/test_resolver.py +31 -0
  141. vellum/workflows/runner/runner.py +159 -14
  142. vellum/workflows/runner/tests/__init__.py +0 -0
  143. vellum/workflows/runner/tests/test_runner.py +170 -0
  144. vellum/workflows/sandbox.py +7 -8
  145. vellum/workflows/state/base.py +89 -30
  146. vellum/workflows/state/context.py +74 -3
  147. vellum/workflows/state/tests/test_state.py +269 -1
  148. vellum/workflows/tests/test_dataset_row.py +8 -7
  149. vellum/workflows/tests/test_sandbox.py +97 -8
  150. vellum/workflows/triggers/__init__.py +2 -1
  151. vellum/workflows/triggers/base.py +160 -28
  152. vellum/workflows/triggers/chat_message.py +141 -0
  153. vellum/workflows/triggers/integration.py +12 -0
  154. vellum/workflows/triggers/manual.py +3 -1
  155. vellum/workflows/triggers/schedule.py +3 -1
  156. vellum/workflows/triggers/tests/test_chat_message.py +257 -0
  157. vellum/workflows/types/core.py +18 -0
  158. vellum/workflows/types/definition.py +6 -13
  159. vellum/workflows/types/generics.py +12 -0
  160. vellum/workflows/types/tests/test_utils.py +12 -0
  161. vellum/workflows/types/utils.py +32 -2
  162. vellum/workflows/types/workflow_metadata.py +124 -0
  163. vellum/workflows/utils/functions.py +152 -16
  164. vellum/workflows/utils/pydantic_schema.py +19 -1
  165. vellum/workflows/utils/tests/test_functions.py +123 -8
  166. vellum/workflows/utils/tests/test_validate.py +79 -0
  167. vellum/workflows/utils/tests/test_vellum_variables.py +62 -2
  168. vellum/workflows/utils/uuids.py +90 -0
  169. vellum/workflows/utils/validate.py +108 -0
  170. vellum/workflows/utils/vellum_variables.py +96 -16
  171. vellum/workflows/workflows/base.py +177 -35
  172. vellum/workflows/workflows/tests/test_base_workflow.py +51 -0
  173. {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/METADATA +6 -1
  174. {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/RECORD +274 -227
  175. vellum_cli/__init__.py +21 -0
  176. vellum_cli/config.py +16 -2
  177. vellum_cli/pull.py +2 -0
  178. vellum_cli/push.py +23 -10
  179. vellum_cli/tests/conftest.py +8 -13
  180. vellum_cli/tests/test_image_push.py +4 -11
  181. vellum_cli/tests/test_pull.py +83 -68
  182. vellum_cli/tests/test_push.py +251 -2
  183. vellum_ee/assets/node-definitions.json +225 -12
  184. vellum_ee/scripts/generate_node_definitions.py +15 -3
  185. vellum_ee/workflows/display/base.py +4 -3
  186. vellum_ee/workflows/display/nodes/base_node_display.py +44 -11
  187. vellum_ee/workflows/display/nodes/tests/test_base_node_display.py +93 -0
  188. vellum_ee/workflows/display/nodes/types.py +1 -0
  189. vellum_ee/workflows/display/nodes/vellum/__init__.py +0 -2
  190. vellum_ee/workflows/display/nodes/vellum/base_adornment_node.py +5 -2
  191. vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +1 -1
  192. vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +10 -2
  193. vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +17 -14
  194. vellum_ee/workflows/display/nodes/vellum/map_node.py +2 -0
  195. vellum_ee/workflows/display/nodes/vellum/note_node.py +18 -3
  196. vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py +37 -14
  197. vellum_ee/workflows/display/nodes/vellum/tests/test_code_execution_node.py +62 -2
  198. vellum_ee/workflows/display/nodes/vellum/tests/test_final_output_node.py +136 -0
  199. vellum_ee/workflows/display/nodes/vellum/tests/test_note_node.py +44 -7
  200. vellum_ee/workflows/display/nodes/vellum/tests/test_prompt_node.py +5 -13
  201. vellum_ee/workflows/display/nodes/vellum/tests/test_subworkflow_deployment_node.py +27 -17
  202. vellum_ee/workflows/display/nodes/vellum/tests/test_tool_calling_node.py +145 -22
  203. vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py +107 -2
  204. vellum_ee/workflows/display/nodes/vellum/utils.py +54 -12
  205. vellum_ee/workflows/display/tests/test_base_workflow_display.py +13 -16
  206. vellum_ee/workflows/display/tests/test_json_schema_validation.py +190 -0
  207. vellum_ee/workflows/display/tests/test_mocks.py +912 -0
  208. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +14 -2
  209. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attributes_serialization.py +109 -0
  210. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_outputs_serialization.py +3 -0
  211. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_ports_serialization.py +187 -1
  212. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_api_node_serialization.py +34 -325
  213. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +42 -393
  214. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +13 -315
  215. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_default_state_serialization.py +2 -122
  216. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py +24 -115
  217. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_generic_node_serialization.py +4 -93
  218. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_guardrail_node_serialization.py +7 -80
  219. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_prompt_node_serialization.py +9 -101
  220. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +77 -308
  221. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +62 -324
  222. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +3 -82
  223. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_prompt_deployment_serialization.py +4 -142
  224. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_search_node_serialization.py +1 -61
  225. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_set_state_node_serialization.py +4 -4
  226. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_subworkflow_deployment_serialization.py +205 -134
  227. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_node_serialization.py +34 -146
  228. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py +2 -0
  229. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_composio_serialization.py +8 -6
  230. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_serialization.py +137 -266
  231. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_tool_wrapper_serialization.py +84 -0
  232. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_mcp_serialization.py +55 -16
  233. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py +15 -1
  234. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_tool_wrapper_serialization.py +71 -0
  235. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_vellum_integration_serialization.py +119 -0
  236. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_workflow_deployment_serialization.py +1 -1
  237. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py +0 -2
  238. vellum_ee/workflows/display/tests/workflow_serialization/test_chat_message_dict_reference_serialization.py +22 -1
  239. vellum_ee/workflows/display/tests/workflow_serialization/test_chat_message_trigger_serialization.py +412 -0
  240. vellum_ee/workflows/display/tests/workflow_serialization/test_code_tool_node_reference_error.py +106 -0
  241. vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +9 -41
  242. vellum_ee/workflows/display/tests/workflow_serialization/test_duplicate_trigger_name_validation.py +208 -0
  243. vellum_ee/workflows/display/tests/workflow_serialization/test_final_output_node_not_referenced_by_workflow_outputs.py +45 -0
  244. vellum_ee/workflows/display/tests/workflow_serialization/test_infinite_loop_validation.py +66 -0
  245. vellum_ee/workflows/display/tests/workflow_serialization/test_int_input_serialization.py +40 -0
  246. vellum_ee/workflows/display/tests/workflow_serialization/test_integration_trigger_serialization.py +8 -14
  247. vellum_ee/workflows/display/tests/workflow_serialization/test_integration_trigger_validation.py +173 -0
  248. vellum_ee/workflows/display/tests/workflow_serialization/test_integration_trigger_with_entrypoint_node_id.py +16 -13
  249. vellum_ee/workflows/display/tests/workflow_serialization/test_list_vellum_document_serialization.py +5 -1
  250. vellum_ee/workflows/display/tests/workflow_serialization/test_manual_trigger_serialization.py +12 -2
  251. vellum_ee/workflows/display/tests/workflow_serialization/test_multi_trigger_same_node_serialization.py +111 -0
  252. vellum_ee/workflows/display/tests/workflow_serialization/test_no_triggers_no_entrypoint_validation.py +64 -0
  253. vellum_ee/workflows/display/tests/workflow_serialization/test_partial_workflow_meta_display_override.py +55 -0
  254. vellum_ee/workflows/display/tests/workflow_serialization/test_sandbox_dataset_mocks_serialization.py +268 -0
  255. vellum_ee/workflows/display/tests/workflow_serialization/test_sandbox_invalid_pdf_data_url.py +49 -0
  256. vellum_ee/workflows/display/tests/workflow_serialization/test_sandbox_validation_errors.py +112 -0
  257. vellum_ee/workflows/display/tests/workflow_serialization/test_scheduled_trigger_serialization.py +25 -16
  258. vellum_ee/workflows/display/tests/workflow_serialization/test_terminal_node_in_unused_graphs_serialization.py +53 -0
  259. vellum_ee/workflows/display/utils/exceptions.py +34 -0
  260. vellum_ee/workflows/display/utils/expressions.py +463 -52
  261. vellum_ee/workflows/display/utils/metadata.py +98 -33
  262. vellum_ee/workflows/display/utils/tests/test_metadata.py +31 -0
  263. vellum_ee/workflows/display/utils/triggers.py +153 -0
  264. vellum_ee/workflows/display/utils/vellum.py +59 -5
  265. vellum_ee/workflows/display/workflows/base_workflow_display.py +656 -254
  266. vellum_ee/workflows/display/workflows/get_vellum_workflow_display_class.py +26 -0
  267. vellum_ee/workflows/display/workflows/tests/test_workflow_display.py +77 -29
  268. vellum_ee/workflows/server/namespaces.py +18 -0
  269. vellum_ee/workflows/tests/test_display_meta.py +2 -0
  270. vellum_ee/workflows/tests/test_serialize_module.py +174 -7
  271. vellum_ee/workflows/tests/test_server.py +0 -3
  272. vellum_ee/workflows/display/nodes/vellum/function_node.py +0 -14
  273. {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/LICENSE +0 -0
  274. {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/WHEEL +0 -0
  275. {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,268 @@
1
+ import pytest
2
+ import sys
3
+ from uuid import uuid4
4
+ from typing import Callable
5
+
6
+ from vellum_ee.workflows.display.workflows.base_workflow_display import BaseWorkflowDisplay
7
+ from vellum_ee.workflows.server.namespaces import get_random_namespace
8
+ from vellum_ee.workflows.server.virtual_file_loader import VirtualFileFinder
9
+
10
+
11
+ def _generate_uuid_namespace() -> str:
12
+ """Generate a UUID namespace like the workflow server uses for some requests."""
13
+ return str(uuid4())
14
+
15
+
16
+ @pytest.mark.parametrize(
17
+ "namespace_generator",
18
+ [
19
+ pytest.param(_generate_uuid_namespace, id="uuid_namespace"),
20
+ pytest.param(get_random_namespace, id="workflow_tmp_namespace"),
21
+ ],
22
+ )
23
+ def test_serialize_module__dataset_mocks_are_stable(namespace_generator: Callable[[], str]):
24
+ """
25
+ Tests that serialization produces stable, deterministic output for DatasetRow mocks
26
+ across multiple serializations, regardless of the namespace format used.
27
+ """
28
+
29
+ # GIVEN a workflow module with a sandbox.py that has DatasetRow with mocks
30
+ files = {
31
+ "__init__.py": "",
32
+ "workflow.py": """\
33
+ from vellum.workflows import BaseWorkflow
34
+ from vellum.workflows.inputs import BaseInputs
35
+ from vellum.workflows.nodes import BaseNode
36
+ from vellum.workflows.state import BaseState
37
+
38
+
39
+ class Inputs(BaseInputs):
40
+ message: str
41
+
42
+
43
+ class CodeExecution(BaseNode[BaseState]):
44
+ class Outputs(BaseNode.Outputs):
45
+ result: str
46
+ log: str
47
+
48
+ def run(self) -> Outputs:
49
+ return self.Outputs(result="executed", log="")
50
+
51
+
52
+ class Workflow(BaseWorkflow[Inputs, BaseState]):
53
+ graph = CodeExecution
54
+
55
+ class Outputs(BaseWorkflow.Outputs):
56
+ final_result = CodeExecution.Outputs.result
57
+ """,
58
+ "sandbox.py": """\
59
+ from vellum.workflows import DatasetRow
60
+
61
+ from .workflow import CodeExecution, Inputs
62
+
63
+ dataset = [
64
+ DatasetRow(
65
+ label="With mocked code node",
66
+ inputs=Inputs(message="hello"),
67
+ mocks=[
68
+ CodeExecution.Outputs(result="MOCKED_RESULT", log=""),
69
+ ],
70
+ ),
71
+ ]
72
+ """,
73
+ }
74
+
75
+ # AND two different namespaces (simulating how the workflow server creates a new namespace per request)
76
+ namespace_1 = namespace_generator()
77
+ namespace_2 = namespace_generator()
78
+
79
+ # AND the virtual file loaders are registered for both namespaces
80
+ sys.meta_path.append(VirtualFileFinder(files, namespace_1))
81
+ sys.meta_path.append(VirtualFileFinder(files, namespace_2))
82
+
83
+ try:
84
+ # WHEN we serialize the module with different namespaces (like the workflow server does)
85
+ result_1 = BaseWorkflowDisplay.serialize_module(namespace_1)
86
+ result_2 = BaseWorkflowDisplay.serialize_module(namespace_2)
87
+
88
+ # THEN both serializations should succeed without errors
89
+ assert len(result_1.errors) == 0, f"First serialization had errors: {result_1.errors}"
90
+ assert len(result_2.errors) == 0, f"Second serialization had errors: {result_2.errors}"
91
+
92
+ # AND both should have a dataset
93
+ assert result_1.dataset is not None, "First serialization should have a dataset"
94
+ assert result_2.dataset is not None, "Second serialization should have a dataset"
95
+
96
+ # AND the datasets should be identical (stable serialization)
97
+ assert result_1.dataset == result_2.dataset, (
98
+ f"Datasets should be identical across serializations.\n"
99
+ f"First: {result_1.dataset}\n"
100
+ f"Second: {result_2.dataset}"
101
+ )
102
+ finally:
103
+ # Clean up the virtual file finders
104
+ sys.meta_path = [finder for finder in sys.meta_path if not isinstance(finder, VirtualFileFinder)]
105
+
106
+
107
+ def test_serialize_module__dataset_with_node_output_mocks_field_name():
108
+ """
109
+ Tests that DatasetRow with node_output_mocks field (legacy field name) properly
110
+ serializes the mocks. This reproduces an issue where using node_output_mocks
111
+ instead of mocks caused the dataset to return None for the mocks field.
112
+ """
113
+
114
+ # GIVEN a workflow module with parallel code execution nodes
115
+ files = {
116
+ "__init__.py": "",
117
+ "inputs.py": """\
118
+ from typing import Any, Optional
119
+
120
+ from vellum.workflows.inputs import BaseInputs
121
+
122
+
123
+ class Inputs(BaseInputs):
124
+ json: Optional[Any] = None
125
+ """,
126
+ "nodes/__init__.py": "",
127
+ "nodes/code_execution/__init__.py": """\
128
+ from vellum.workflows.nodes.displayable import CodeExecutionNode
129
+ from vellum.workflows.state import BaseState
130
+ from vellum.workflows.types.core import MergeBehavior
131
+
132
+
133
+ class CodeExecution(CodeExecutionNode[BaseState, str]):
134
+ filepath = "./script.py"
135
+ code_inputs = {}
136
+ runtime = "PYTHON_3_11_6"
137
+ packages = []
138
+
139
+ class Trigger(CodeExecutionNode.Trigger):
140
+ merge_behavior = MergeBehavior.AWAIT_ATTRIBUTES
141
+ """,
142
+ "nodes/code_execution/script.py": '''\
143
+ """
144
+ You must define a function called `main` whose arguments are named after the
145
+ Input Variables.
146
+ """
147
+
148
+ def main() -> str:
149
+ return "5".upper()
150
+ ''',
151
+ "nodes/code_execution_2/__init__.py": """\
152
+ from vellum.workflows.nodes.displayable import CodeExecutionNode
153
+ from vellum.workflows.state import BaseState
154
+ from vellum.workflows.types.core import MergeBehavior
155
+
156
+
157
+ class CodeExecution2(CodeExecutionNode[BaseState, str]):
158
+ filepath = "./script.py"
159
+ code_inputs = {}
160
+ runtime = "PYTHON_3_11_6"
161
+ packages = []
162
+
163
+ class Trigger(CodeExecutionNode.Trigger):
164
+ merge_behavior = MergeBehavior.AWAIT_ATTRIBUTES
165
+ """,
166
+ "nodes/code_execution_2/script.py": '''\
167
+ """
168
+ You must define a function called `main` whose arguments are named after the
169
+ Input Variables.
170
+ """
171
+
172
+ def main() -> str:
173
+ return "TODO"
174
+ ''',
175
+ "nodes/output.py": """\
176
+ from vellum.workflows.nodes.displayable import FinalOutputNode
177
+ from vellum.workflows.state import BaseState
178
+
179
+
180
+ class Output(FinalOutputNode[BaseState, str]):
181
+ class Outputs(FinalOutputNode.Outputs):
182
+ value = None
183
+ """,
184
+ "nodes/output_2.py": """\
185
+ from vellum.workflows.nodes.displayable import FinalOutputNode
186
+ from vellum.workflows.state import BaseState
187
+ from vellum.workflows.types.core import MergeBehavior
188
+
189
+
190
+ class Output2(FinalOutputNode[BaseState, str]):
191
+ class Outputs(FinalOutputNode.Outputs):
192
+ value = ""
193
+
194
+ class Trigger(FinalOutputNode.Trigger):
195
+ merge_behavior = MergeBehavior.AWAIT_ATTRIBUTES
196
+ """,
197
+ "sandbox.py": """\
198
+ from vellum.workflows.inputs import DatasetRow
199
+
200
+ from .inputs import Inputs
201
+ from .nodes.code_execution import CodeExecution
202
+ from .nodes.code_execution_2 import CodeExecution2
203
+
204
+ dataset = [
205
+ DatasetRow(
206
+ label="With mocked code nodes",
207
+ inputs=Inputs(json={}),
208
+ node_output_mocks=[
209
+ CodeExecution.Outputs(result="hello world", log=""),
210
+ CodeExecution2.Outputs(result="hello world", log=""),
211
+ ],
212
+ ),
213
+ ]
214
+ """,
215
+ "workflow.py": """\
216
+ from vellum.workflows import BaseWorkflow
217
+ from vellum.workflows.state import BaseState
218
+
219
+ from .inputs import Inputs
220
+ from .nodes.code_execution import CodeExecution
221
+ from .nodes.code_execution_2 import CodeExecution2
222
+ from .nodes.output import Output
223
+ from .nodes.output_2 import Output2
224
+
225
+
226
+ class Workflow(BaseWorkflow[Inputs, BaseState]):
227
+ graph = {
228
+ CodeExecution >> Output,
229
+ CodeExecution2 >> Output2,
230
+ }
231
+
232
+ class Outputs(BaseWorkflow.Outputs):
233
+ output = Output.Outputs.value
234
+ output_2 = Output2.Outputs.value
235
+ """,
236
+ }
237
+
238
+ # AND a namespace for the virtual file loader
239
+ namespace = str(uuid4())
240
+ sys.meta_path.append(VirtualFileFinder(files, namespace))
241
+
242
+ try:
243
+ # WHEN we serialize the module
244
+ result = BaseWorkflowDisplay.serialize_module(namespace)
245
+
246
+ # THEN the serialization should return exactly 1 deprecation error
247
+ assert len(result.errors) == 1, f"Expected 1 deprecation error, got {len(result.errors)}: {result.errors}"
248
+
249
+ # AND the error should prompt the user to use 'mocks' instead of 'node_output_mocks'
250
+ error_message = str(result.errors[0])
251
+ assert "node_output_mocks" in error_message, f"Error should mention 'node_output_mocks': {error_message}"
252
+ assert "mocks" in error_message, f"Error should mention 'mocks': {error_message}"
253
+ assert "deprecated" in error_message.lower(), f"Error should mention 'deprecated': {error_message}"
254
+
255
+ # AND the dataset should not be None (the deprecated field should still work)
256
+ assert result.dataset is not None, "Dataset should not be None"
257
+
258
+ # AND the dataset should have one row
259
+ assert len(result.dataset) == 1, f"Dataset should have 1 row, got {len(result.dataset)}"
260
+
261
+ # AND the row should have mocks serialized
262
+ row = result.dataset[0]
263
+ assert "mocks" in row, f"Row should have 'mocks' field, got keys: {row.keys()}"
264
+ assert row["mocks"] is not None, "Mocks should not be None"
265
+ assert len(row["mocks"]) == 2, f"Should have 2 mocks, got {len(row['mocks'])}"
266
+ finally:
267
+ # Clean up the virtual file finders
268
+ sys.meta_path = [finder for finder in sys.meta_path if not isinstance(finder, VirtualFileFinder)]
@@ -0,0 +1,49 @@
1
+ from vellum_ee.workflows.display.workflows.base_workflow_display import BaseWorkflowDisplay
2
+
3
+
4
+ def test_serialize_module__invalid_pdf_data_url():
5
+ """
6
+ Tests that serialization returns an error when sandbox.py has an invalid PDF data URL
7
+ without wrecking the whole serialize process, and that valid dataset rows are still returned.
8
+ """
9
+
10
+ # GIVEN a workflow module with a sandbox.py that has one invalid and one valid PDF data URL
11
+ module = "tests.workflows.test_sandbox_invalid_pdf_data_url"
12
+
13
+ # WHEN we serialize the module
14
+ result = BaseWorkflowDisplay.serialize_module(module)
15
+
16
+ # THEN the result should still contain a valid exec_config
17
+ assert result.exec_config is not None
18
+ assert isinstance(result.exec_config, dict)
19
+
20
+ # AND the result should contain exactly one error about the invalid PDF data URL
21
+ assert len(result.errors) == 1
22
+ error_message = result.errors[0].message
23
+
24
+ # AND the error message should include the dataset row label
25
+ assert 'Dataset row "Scenario with invalid PDF"' in error_message
26
+
27
+ # AND the error message should include the input name
28
+ assert 'input "document"' in error_message
29
+
30
+ # AND the error message should mention the invalid base64 encoding
31
+ assert "Invalid base64 encoding in PDF data URL" in error_message
32
+
33
+ # AND the result should contain both dataset rows
34
+ assert result.dataset is not None
35
+ assert len(result.dataset) == 2
36
+
37
+ # AND the first row should have the valid input (name) serialized but the invalid input (document) skipped
38
+ invalid_row = result.dataset[0]
39
+ assert invalid_row["label"] == "Scenario with invalid PDF"
40
+ assert "document" not in invalid_row["inputs"]
41
+ assert "name" in invalid_row["inputs"]
42
+ assert invalid_row["inputs"]["name"] == "Test User"
43
+
44
+ # AND the second row (valid) should have both inputs serialized
45
+ valid_row = result.dataset[1]
46
+ assert valid_row["label"] == "Scenario with valid PDF"
47
+ assert "document" in valid_row["inputs"]
48
+ assert "name" in valid_row["inputs"]
49
+ assert valid_row["inputs"]["name"] == "Another User"
@@ -0,0 +1,112 @@
1
+ from vellum_ee.workflows.display.workflows.base_workflow_display import BaseWorkflowDisplay
2
+
3
+
4
+ def test_serialize_module__invalid_mocks_reference():
5
+ """
6
+ Tests that serialization returns an error when sandbox.py has an invalid Mocks reference on a node.
7
+ """
8
+
9
+ # GIVEN a workflow module with a sandbox.py that references CodeExecution.Mocks.Outputs
10
+ # which doesn't exist - nodes don't have a Mocks nested class
11
+ module = "tests.workflows.test_sandbox_invalid_mocks_reference"
12
+
13
+ # WHEN we serialize the module
14
+ result = BaseWorkflowDisplay.serialize_module(module)
15
+
16
+ # THEN the result should contain an error about the invalid attribute access
17
+ assert len(result.errors) > 0
18
+
19
+ # AND the error should be an AttributeError from trying to access a non-existent attribute
20
+ error_messages = [error.message for error in result.errors]
21
+ assert any(
22
+ "__annotations__" in msg or "has no attribute" in msg for msg in error_messages
23
+ ), f"Expected attribute error in error messages, got: {error_messages}"
24
+
25
+
26
+ def test_serialize_module__invalid_runner_kwarg():
27
+ """
28
+ Tests that serialization returns an error when sandbox.py uses an invalid kwarg on WorkflowSandboxRunner.
29
+ """
30
+
31
+ # GIVEN a workflow module with a sandbox.py that uses 'scenarios' kwarg instead of 'dataset'
32
+ module = "tests.workflows.test_sandbox_invalid_runner_kwarg"
33
+
34
+ # WHEN we serialize the module
35
+ result = BaseWorkflowDisplay.serialize_module(module)
36
+
37
+ # THEN the result should contain an error about the invalid keyword argument
38
+ assert len(result.errors) > 0
39
+
40
+ # AND the error message should mention the unexpected keyword argument 'scenarios'
41
+ error_messages = [error.message for error in result.errors]
42
+ assert any(
43
+ "scenarios" in msg for msg in error_messages
44
+ ), f"Expected 'scenarios' in error messages, got: {error_messages}"
45
+
46
+
47
+ def test_serialize_module__state_mutable_default_validation():
48
+ """
49
+ Tests that serialization returns a validation error when a state variable uses a mutable default
50
+ (e.g., = []) instead of Field(default_factory=list).
51
+ """
52
+
53
+ # GIVEN a workflow module with a state class that uses a mutable default (= [])
54
+ module = "tests.workflows.test_state_mutable_default_validation"
55
+
56
+ # WHEN we serialize the module
57
+ result = BaseWorkflowDisplay.serialize_module(module)
58
+
59
+ # THEN the result should contain an error about mutable defaults
60
+ assert len(result.errors) > 0
61
+
62
+ # AND the error message should mention mutable default, Field(default_factory=list), and the attribute name
63
+ error_messages = [error.message for error in result.errors]
64
+ assert any(
65
+ "Mutable default value detected" in msg and "Field(default_factory=list)" in msg and "State.chat_history" in msg
66
+ for msg in error_messages
67
+ ), f"Expected mutable default error in error messages, got: {error_messages}"
68
+
69
+
70
+ def test_serialize_module__orphan_node_in_workflow_file():
71
+ """
72
+ Tests that serialization returns an error when a node is defined in workflow.py
73
+ but not included in the workflow's graph or unused_graphs.
74
+ """
75
+
76
+ # GIVEN a workflow module with a node defined in workflow.py that is not in graph or unused_graphs
77
+ module = "tests.workflows.test_orphan_node_serialization_error"
78
+
79
+ # WHEN we serialize the module
80
+ result = BaseWorkflowDisplay.serialize_module(module)
81
+
82
+ # THEN the result should contain an error about the orphan node
83
+ assert len(result.errors) > 0
84
+
85
+ # AND the error message should mention the orphan node and that it's not in graph or unused_graphs
86
+ error_messages = [error.message for error in result.errors]
87
+ assert any(
88
+ "OrphanNode" in msg and "not included in" in msg and "graph or unused_graphs" in msg for msg in error_messages
89
+ ), f"Expected orphan node error in error messages, got: {error_messages}"
90
+
91
+
92
+ def test_serialize_module__orphan_node_in_nodes_directory():
93
+ """
94
+ Tests that serialization returns an error when a node is defined in the nodes/ directory
95
+ but not included in the workflow's graph or unused_graphs.
96
+ """
97
+
98
+ # GIVEN a workflow module with a node defined in nodes/orphan_node.py that is not in graph or unused_graphs
99
+ module = "tests.workflows.test_orphan_node_in_nodes_dir"
100
+
101
+ # WHEN we serialize the module
102
+ result = BaseWorkflowDisplay.serialize_module(module)
103
+
104
+ # THEN the result should contain an error about the orphan node
105
+ assert len(result.errors) > 0
106
+
107
+ # AND the error message should mention the orphan node and that it's not in graph or unused_graphs
108
+ error_messages = [error.message for error in result.errors]
109
+ assert any(
110
+ "OrphanNodeInNodesDir" in msg and "not included in" in msg and "graph or unused_graphs" in msg
111
+ for msg in error_messages
112
+ ), f"Expected orphan node error in error messages, got: {error_messages}"
@@ -244,24 +244,33 @@ def test_scheduled_trigger_serialization_full():
244
244
  # THEN we get the expected trigger
245
245
  assert len(result["triggers"]) == 1
246
246
  trigger = result["triggers"][0]
247
+
248
+ # AND the trigger has the expected structure with attributes
249
+ assert trigger["id"] == "f3e5eddb-75da-42e6-9abf-d616f30c145c"
250
+ assert trigger["type"] == "SCHEDULED"
251
+ assert trigger["cron"] == "0 9 * * *"
252
+ assert trigger["timezone"] == "UTC"
253
+
254
+ # AND attributes are serialized (current_run_at and next_run_at from ScheduleTrigger)
255
+ assert "attributes" in trigger
256
+ attributes = trigger["attributes"]
257
+ assert isinstance(attributes, list)
258
+ assert len(attributes) == 2
259
+ attribute_keys = {attr["key"] for attr in attributes}
260
+ assert attribute_keys == {"current_run_at", "next_run_at"}
261
+
262
+ # AND display_data is serialized correctly
247
263
  assert not DeepDiff(
248
- trigger,
264
+ trigger["display_data"],
249
265
  {
250
- "id": "f3e5eddb-75da-42e6-9abf-d616f30c145c",
251
- "type": "SCHEDULED",
252
- "cron": "0 9 * * *",
253
- "timezone": "UTC",
254
- "attributes": [],
255
- "display_data": {
256
- "label": "Daily Schedule",
257
- "position": {"x": 100.5, "y": 200.75},
258
- "z_index": 3,
259
- "icon": "vellum:icon:calendar",
260
- "color": "#4A90E2",
261
- "comment": {
262
- "value": "This is scheduled trigger",
263
- "expanded": True,
264
- },
266
+ "label": "Daily Schedule",
267
+ "position": {"x": 100.5, "y": 200.75},
268
+ "z_index": 3,
269
+ "icon": "vellum:icon:calendar",
270
+ "color": "#4A90E2",
271
+ "comment": {
272
+ "value": "This is scheduled trigger",
273
+ "expanded": True,
265
274
  },
266
275
  },
267
276
  )
@@ -0,0 +1,53 @@
1
+ from vellum.workflows.inputs.base import BaseInputs
2
+ from vellum.workflows.nodes.displayable.final_output_node import FinalOutputNode
3
+ from vellum.workflows.state.base import BaseState
4
+ from vellum.workflows.workflows.base import BaseWorkflow
5
+ from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
6
+
7
+
8
+ class Inputs(BaseInputs):
9
+ input_value: str
10
+
11
+
12
+ class UnusedTerminalNode(FinalOutputNode):
13
+ class Outputs(FinalOutputNode.Outputs):
14
+ value = Inputs.input_value
15
+
16
+
17
+ class TerminalNodeInUnusedGraphsWorkflow(BaseWorkflow[Inputs, BaseState]):
18
+ unused_graphs = {UnusedTerminalNode}
19
+
20
+ class Outputs(BaseWorkflow.Outputs):
21
+ result = UnusedTerminalNode.Outputs.value
22
+
23
+
24
+ def test_serialize_workflow__terminal_node_in_unused_graphs():
25
+ """
26
+ Tests that a workflow with a single terminal node in unused_graphs serializes correctly.
27
+ """
28
+
29
+ # GIVEN a Workflow with a single terminal node in unused_graphs
30
+ # WHEN we serialize it
31
+ workflow_display = get_workflow_display(workflow_class=TerminalNodeInUnusedGraphsWorkflow)
32
+ serialized_workflow: dict = workflow_display.serialize()
33
+
34
+ # THEN we should get a serialized representation of the Workflow
35
+ assert serialized_workflow.keys() == {
36
+ "workflow_raw_data",
37
+ "input_variables",
38
+ "state_variables",
39
+ "output_variables",
40
+ }
41
+
42
+ # AND its output variables should contain the terminal node's output
43
+ output_variables = serialized_workflow["output_variables"]
44
+ assert len(output_variables) == 1
45
+ assert output_variables[0]["key"] == "result"
46
+ assert output_variables[0]["type"] == "STRING"
47
+
48
+ # AND its output values should reference the terminal node's output
49
+ workflow_raw_data = serialized_workflow["workflow_raw_data"]
50
+ output_values = workflow_raw_data["output_values"]
51
+ assert len(output_values) == 1
52
+ assert output_values[0]["output_variable_id"] == output_variables[0]["id"]
53
+ assert output_values[0]["value"]["type"] == "NODE_OUTPUT"
@@ -24,3 +24,37 @@ class InvalidInputReferenceError(UserFacingException):
24
24
  self.inputs_class_name = inputs_class_name
25
25
  self.attribute_name = attribute_name
26
26
  super().__init__(f"Invalid input reference in {inputs_class_name}.{attribute_name}: {message}")
27
+
28
+
29
+ class InvalidOutputReferenceError(UserFacingException):
30
+ """Exception raised when a node references a non-existent output."""
31
+
32
+ pass
33
+
34
+
35
+ class WorkflowValidationError(UserFacingException):
36
+ """Exception raised when a workflow fails validation during display serialization."""
37
+
38
+ def __init__(self, message: str, workflow_class_name: str):
39
+ self.message = message
40
+ self.workflow_class_name = workflow_class_name
41
+ super().__init__(f"Workflow validation error in {workflow_class_name}: {message}")
42
+
43
+
44
+ class TriggerValidationError(UserFacingException):
45
+ """Exception raised when a trigger fails validation during serialization."""
46
+
47
+ def __init__(self, message: str, trigger_class_name: str):
48
+ self.message = message
49
+ self.trigger_class_name = trigger_class_name
50
+ super().__init__(f"Trigger validation error in {trigger_class_name}: {message}")
51
+
52
+
53
+ class StateValidationError(UserFacingException):
54
+ """Exception raised when a state class fails validation during serialization."""
55
+
56
+ def __init__(self, message: str, state_class_name: str, attribute_name: str):
57
+ self.message = message
58
+ self.state_class_name = state_class_name
59
+ self.attribute_name = attribute_name
60
+ super().__init__(f"State validation error in {state_class_name}.{attribute_name}: {message}")