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,106 @@
1
+ import pytest
2
+
3
+ from vellum.workflows import BaseWorkflow
4
+ from vellum.workflows.nodes.displayable.inline_prompt_node.node import InlinePromptNode
5
+ from vellum.workflows.nodes.displayable.tool_calling_node.node import ToolCallingNode
6
+ from vellum_ee.workflows.display.utils.exceptions import UnsupportedSerializationException
7
+ from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
8
+
9
+
10
+ def test_serialize_workflow__code_tool_with_node_reference__raises_error():
11
+ """
12
+ Tests that a serialization error is raised when a code tool references a workflow node.
13
+ """
14
+
15
+ # GIVEN a function that references a node class
16
+ def my_tool_with_node_reference(query: str) -> str:
17
+ return str(InlinePromptNode)
18
+
19
+ # AND a tool calling node that uses this function
20
+ class MyToolCallingNode(ToolCallingNode):
21
+ functions = [my_tool_with_node_reference]
22
+
23
+ # AND a workflow with the tool calling node
24
+ class TestWorkflow(BaseWorkflow):
25
+ graph = MyToolCallingNode
26
+
27
+ class Outputs(BaseWorkflow.Outputs):
28
+ result = MyToolCallingNode.Outputs.text
29
+
30
+ # WHEN we try to serialize the workflow
31
+ workflow_display = get_workflow_display(workflow_class=TestWorkflow)
32
+
33
+ # THEN a serialization error should be raised
34
+ with pytest.raises(UnsupportedSerializationException) as exc_info:
35
+ workflow_display.serialize()
36
+
37
+ # AND the error message should mention the node class and provide guidance
38
+ error_message = str(exc_info.value)
39
+ assert "InlinePromptNode" in error_message
40
+ assert "Code tools cannot reference workflow nodes" in error_message
41
+ assert "Inline Subworkflow tool" in error_message
42
+
43
+
44
+ def test_serialize_workflow__code_tool_without_node_reference__no_error():
45
+ """
46
+ Tests that no error is raised when a code tool does not reference any workflow nodes,
47
+ even if the module has node imports at the top level.
48
+ """
49
+
50
+ # GIVEN a function that does NOT reference any node class (even though InlinePromptNode
51
+ # is imported at the module level)
52
+ def my_simple_tool(query: str) -> str:
53
+ return query.upper()
54
+
55
+ # AND a tool calling node that uses this function
56
+ class MyToolCallingNode(ToolCallingNode):
57
+ functions = [my_simple_tool]
58
+
59
+ # AND a workflow with the tool calling node
60
+ class TestWorkflow(BaseWorkflow):
61
+ graph = MyToolCallingNode
62
+
63
+ class Outputs(BaseWorkflow.Outputs):
64
+ result = MyToolCallingNode.Outputs.text
65
+
66
+ # WHEN we serialize the workflow
67
+ workflow_display = get_workflow_display(workflow_class=TestWorkflow)
68
+ serialized_workflow = workflow_display.serialize()
69
+
70
+ # THEN no error should be raised and the workflow should serialize successfully
71
+ assert serialized_workflow is not None
72
+ assert "workflow_raw_data" in serialized_workflow
73
+
74
+
75
+ def test_serialize_workflow__code_tool_with_node_instantiation__raises_error():
76
+ """
77
+ Tests that a serialization error is raised when a code tool instantiates a node and calls its run method.
78
+ """
79
+
80
+ # GIVEN a function that instantiates a node and calls its run method
81
+ def my_tool():
82
+ node: InlinePromptNode = InlinePromptNode()
83
+ return node.run()
84
+
85
+ # AND a tool calling node that uses this function
86
+ class MyToolCallingNode(ToolCallingNode):
87
+ functions = [my_tool]
88
+
89
+ # AND a workflow with the tool calling node
90
+ class TestWorkflow(BaseWorkflow):
91
+ graph = MyToolCallingNode
92
+
93
+ class Outputs(BaseWorkflow.Outputs):
94
+ result = MyToolCallingNode.Outputs.text
95
+
96
+ # WHEN we try to serialize the workflow
97
+ workflow_display = get_workflow_display(workflow_class=TestWorkflow)
98
+
99
+ # THEN a serialization error should be raised
100
+ with pytest.raises(UnsupportedSerializationException) as exc_info:
101
+ workflow_display.serialize()
102
+
103
+ # AND the error message should mention the node class
104
+ error_message = str(exc_info.value)
105
+ assert "InlinePromptNode" in error_message
106
+ assert "Code tools cannot reference workflow nodes" in error_message
@@ -2,6 +2,7 @@ import pytest
2
2
 
3
3
  from deepdiff import DeepDiff
4
4
 
5
+ from vellum_ee.workflows.display.utils.exceptions import WorkflowValidationError
5
6
  from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
6
7
 
7
8
  from tests.workflows.complex_final_output_node.missing_final_output_node import MissingFinalOutputNodeWorkflow
@@ -31,7 +32,7 @@ def test_serialize_workflow__missing_final_output_node():
31
32
  workflow_raw_data = serialized_workflow["workflow_raw_data"]
32
33
  assert isinstance(workflow_raw_data, dict)
33
34
 
34
- # AND we should create synthetic terminal nodes for each output variable
35
+ # AND we should NOT create synthetic terminal nodes for each output variable
35
36
  final_output_nodes = [node for node in workflow_raw_data["nodes"] if node["type"] == "TERMINAL"]
36
37
  assert not DeepDiff(
37
38
  [
@@ -79,6 +80,7 @@ def test_serialize_workflow__missing_final_output_node():
79
80
  "type": "WORKFLOW_INPUT",
80
81
  "input_variable_id": "da086239-d743-4246-b666-5c91e22fb88c",
81
82
  },
83
+ "schema": {"type": "string"},
82
84
  }
83
85
  ],
84
86
  "trigger": {
@@ -87,45 +89,8 @@ def test_serialize_workflow__missing_final_output_node():
87
89
  },
88
90
  "ports": [],
89
91
  },
90
- {
91
- "id": "bb88768d-472e-4997-b7ea-de09163d1b4c",
92
- "type": "TERMINAL",
93
- "data": {
94
- "label": "Final Output",
95
- "name": "beta",
96
- "target_handle_id": "5e337b19-cef6-45af-802b-46da4ad7e794",
97
- "output_id": "5e6d3ea6-ef91-4937-8fff-f33e07446e6a",
98
- "output_type": "STRING",
99
- "node_input_id": "590161f1-20ed-4339-9ce3-61aade3a142a",
100
- },
101
- "inputs": [
102
- {
103
- "id": "590161f1-20ed-4339-9ce3-61aade3a142a",
104
- "key": "node_input",
105
- "value": {
106
- "rules": [
107
- {
108
- "type": "NODE_OUTPUT",
109
- "data": {
110
- "node_id": "74b891e0-5573-4c7a-a0ef-c3c210187738",
111
- "output_id": "ae21fc3f-dd81-4174-b08a-97bc96422a8f",
112
- },
113
- }
114
- ],
115
- "combinator": "OR",
116
- },
117
- }
118
- ],
119
- "display_data": {"position": {"x": 400.0, "y": -50.0}},
120
- "base": {
121
- "name": "FinalOutputNode",
122
- "module": ["vellum", "workflows", "nodes", "displayable", "final_output_node", "node"],
123
- },
124
- "definition": None,
125
- },
126
92
  ],
127
93
  final_output_nodes,
128
- ignore_order=True,
129
94
  )
130
95
 
131
96
 
@@ -133,8 +98,11 @@ def test_serialize_workflow__missing_workflow_output():
133
98
  # GIVEN a Workflow that contains a terminal node that is unreferenced by the Workflow's Outputs
134
99
  workflow_display = get_workflow_display(workflow_class=MissingWorkflowOutputWorkflow)
135
100
 
136
- # WHEN we serialize it, it should throw an error
137
- with pytest.raises(ValueError) as exc_info:
101
+ # WHEN we serialize it, it should throw a WorkflowValidationError
102
+ with pytest.raises(WorkflowValidationError) as exc_info:
138
103
  workflow_display.serialize()
139
104
 
140
- assert exc_info.value.args[0] == "Unable to serialize terminal nodes that are not referenced by workflow outputs."
105
+ # THEN the error message should indicate the terminal node is not referenced
106
+ error_message = str(exc_info.value)
107
+ assert "MissingWorkflowOutputWorkflow" in error_message
108
+ assert "terminal nodes that are not referenced by workflow outputs" in error_message
@@ -0,0 +1,208 @@
1
+ """Tests for duplicate trigger name validation during serialization."""
2
+
3
+ from vellum.workflows import BaseWorkflow
4
+ from vellum.workflows.inputs.base import BaseInputs
5
+ from vellum.workflows.nodes.bases.base import BaseNode
6
+ from vellum.workflows.state.base import BaseState
7
+ from vellum.workflows.triggers.integration import IntegrationTrigger
8
+ from vellum_ee.workflows.display.utils.exceptions import TriggerValidationError
9
+ from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
10
+
11
+
12
+ def test_duplicate_trigger_names__same_slug_produces_error():
13
+ """
14
+ Tests that two integration triggers with the same slug produce a validation error.
15
+ """
16
+
17
+ # GIVEN two IntegrationTrigger subclasses with the same slug
18
+ class SlackTrigger1(IntegrationTrigger):
19
+ message: str
20
+
21
+ class Config:
22
+ provider = "COMPOSIO"
23
+ integration_name = "SLACK"
24
+ slug = "slack_new_message"
25
+
26
+ class SlackTrigger2(IntegrationTrigger):
27
+ channel: str
28
+
29
+ class Config:
30
+ provider = "COMPOSIO"
31
+ integration_name = "SLACK"
32
+ slug = "slack_new_message"
33
+
34
+ # AND nodes for each trigger
35
+ class ProcessNode1(BaseNode):
36
+ pass
37
+
38
+ class ProcessNode2(BaseNode):
39
+ pass
40
+
41
+ # AND a workflow with both triggers
42
+ class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
43
+ graph = {
44
+ SlackTrigger1 >> ProcessNode1,
45
+ SlackTrigger2 >> ProcessNode2,
46
+ }
47
+
48
+ # WHEN we serialize the workflow
49
+ workflow_display = get_workflow_display(workflow_class=TestWorkflow)
50
+ workflow_display.serialize()
51
+
52
+ # THEN the display_context should contain a TriggerValidationError
53
+ errors = list(workflow_display.display_context.errors)
54
+ trigger_errors = [e for e in errors if isinstance(e, TriggerValidationError)]
55
+ assert len(trigger_errors) == 1
56
+
57
+ # AND the error should mention duplicate trigger name
58
+ error = trigger_errors[0]
59
+ assert "Duplicate trigger name" in str(error)
60
+ assert "slack_new_message" in str(error)
61
+
62
+
63
+ def test_different_trigger_names__no_error():
64
+ """
65
+ Tests that two integration triggers with different slugs do not produce a validation error.
66
+ """
67
+
68
+ # GIVEN two IntegrationTrigger subclasses with different slugs
69
+ class SlackTrigger(IntegrationTrigger):
70
+ message: str
71
+
72
+ class Config:
73
+ provider = "COMPOSIO"
74
+ integration_name = "SLACK"
75
+ slug = "slack_new_message"
76
+
77
+ class GmailTrigger(IntegrationTrigger):
78
+ email: str
79
+
80
+ class Config:
81
+ provider = "COMPOSIO"
82
+ integration_name = "GMAIL"
83
+ slug = "gmail_new_email"
84
+
85
+ # AND nodes for each trigger
86
+ class ProcessSlack(BaseNode):
87
+ pass
88
+
89
+ class ProcessGmail(BaseNode):
90
+ pass
91
+
92
+ # AND a workflow with both triggers
93
+ class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
94
+ graph = {
95
+ SlackTrigger >> ProcessSlack,
96
+ GmailTrigger >> ProcessGmail,
97
+ }
98
+
99
+ # WHEN we serialize the workflow
100
+ workflow_display = get_workflow_display(workflow_class=TestWorkflow)
101
+ result: dict = workflow_display.serialize()
102
+
103
+ # THEN there should be no TriggerValidationError about duplicate names
104
+ errors = list(workflow_display.display_context.errors)
105
+ duplicate_errors = [
106
+ e for e in errors if isinstance(e, TriggerValidationError) and "Duplicate trigger name" in str(e)
107
+ ]
108
+ assert len(duplicate_errors) == 0
109
+
110
+ # AND both triggers should be serialized with their respective names
111
+ triggers = result["triggers"]
112
+ assert isinstance(triggers, list)
113
+ assert len(triggers) == 2
114
+ trigger_names = {t["name"] for t in triggers}
115
+ assert trigger_names == {"slack_new_message", "gmail_new_email"}
116
+
117
+
118
+ def test_trigger_name_serialization__chat_trigger():
119
+ """
120
+ Tests that ChatMessageTrigger serializes with name 'chat'.
121
+ """
122
+ from vellum.workflows.triggers.chat_message import ChatMessageTrigger
123
+
124
+ # GIVEN a ChatMessageTrigger subclass
125
+ class MyChatTrigger(ChatMessageTrigger):
126
+ pass
127
+
128
+ # AND a simple node
129
+ class ProcessNode(BaseNode):
130
+ pass
131
+
132
+ # AND a workflow with the chat trigger
133
+ class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
134
+ graph = MyChatTrigger >> ProcessNode
135
+
136
+ # WHEN we serialize the workflow
137
+ workflow_display = get_workflow_display(workflow_class=TestWorkflow)
138
+ result: dict = workflow_display.serialize()
139
+
140
+ # THEN the trigger should have name 'chat'
141
+ triggers = result["triggers"]
142
+ assert isinstance(triggers, list)
143
+ assert len(triggers) == 1
144
+ assert triggers[0]["name"] == "chat"
145
+
146
+
147
+ def test_trigger_name_serialization__scheduled_trigger():
148
+ """
149
+ Tests that ScheduleTrigger serializes with name 'scheduled'.
150
+ """
151
+ from vellum.workflows.triggers.schedule import ScheduleTrigger
152
+
153
+ # GIVEN a ScheduleTrigger subclass
154
+ class MyScheduledTrigger(ScheduleTrigger):
155
+ class Config(ScheduleTrigger.Config):
156
+ cron = "0 9 * * *"
157
+ timezone = "UTC"
158
+
159
+ # AND a simple node
160
+ class ProcessNode(BaseNode):
161
+ pass
162
+
163
+ # AND a workflow with the scheduled trigger
164
+ class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
165
+ graph = MyScheduledTrigger >> ProcessNode
166
+
167
+ # WHEN we serialize the workflow
168
+ workflow_display = get_workflow_display(workflow_class=TestWorkflow)
169
+ result: dict = workflow_display.serialize()
170
+
171
+ # THEN the trigger should have name 'scheduled'
172
+ triggers = result["triggers"]
173
+ assert isinstance(triggers, list)
174
+ assert len(triggers) == 1
175
+ assert triggers[0]["name"] == "scheduled"
176
+
177
+
178
+ def test_trigger_name_serialization__integration_trigger():
179
+ """
180
+ Tests that IntegrationTrigger serializes with name equal to its slug.
181
+ """
182
+
183
+ # GIVEN an IntegrationTrigger subclass with a specific slug
184
+ class MySlackTrigger(IntegrationTrigger):
185
+ message: str
186
+
187
+ class Config:
188
+ provider = "COMPOSIO"
189
+ integration_name = "SLACK"
190
+ slug = "slack_new_message"
191
+
192
+ # AND a simple node
193
+ class ProcessNode(BaseNode):
194
+ pass
195
+
196
+ # AND a workflow with the integration trigger
197
+ class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
198
+ graph = MySlackTrigger >> ProcessNode
199
+
200
+ # WHEN we serialize the workflow
201
+ workflow_display = get_workflow_display(workflow_class=TestWorkflow)
202
+ result: dict = workflow_display.serialize()
203
+
204
+ # THEN the trigger should have name equal to the slug
205
+ triggers = result["triggers"]
206
+ assert isinstance(triggers, list)
207
+ assert len(triggers) == 1
208
+ assert triggers[0]["name"] == "slack_new_message"
@@ -0,0 +1,45 @@
1
+ import pytest
2
+
3
+ from vellum.workflows.inputs.base import BaseInputs
4
+ from vellum.workflows.nodes.displayable.final_output_node import FinalOutputNode
5
+ from vellum.workflows.state.base import BaseState
6
+ from vellum.workflows.workflows.base import BaseWorkflow
7
+ from vellum_ee.workflows.display.utils.exceptions import WorkflowValidationError
8
+ from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
9
+
10
+
11
+ class Inputs(BaseInputs):
12
+ input_value: str
13
+
14
+
15
+ class UnreferencedFinalOutputNode(FinalOutputNode):
16
+ class Outputs(FinalOutputNode.Outputs):
17
+ value = Inputs.input_value
18
+
19
+
20
+ class WorkflowWithUnreferencedFinalOutputNode(BaseWorkflow[Inputs, BaseState]):
21
+ graph = UnreferencedFinalOutputNode
22
+
23
+ class Outputs(BaseWorkflow.Outputs):
24
+ # Intentionally NOT referencing UnreferencedFinalOutputNode.Outputs.value
25
+ result = Inputs.input_value
26
+
27
+
28
+ def test_serialize_workflow__final_output_node_not_referenced_by_workflow_outputs():
29
+ """
30
+ Tests that serialization raises a WorkflowValidationError when a workflow has a final output node
31
+ but the workflow outputs don't reference it.
32
+ """
33
+
34
+ # GIVEN a Workflow with a FinalOutputNode that is not referenced by workflow outputs
35
+ workflow_display = get_workflow_display(workflow_class=WorkflowWithUnreferencedFinalOutputNode)
36
+
37
+ # WHEN we serialize it
38
+ # THEN it should raise a WorkflowValidationError about unreferenced terminal nodes
39
+ with pytest.raises(WorkflowValidationError) as exc_info:
40
+ workflow_display.serialize()
41
+
42
+ # AND the error message should indicate the terminal node is not referenced
43
+ error_message = str(exc_info.value)
44
+ assert "WorkflowWithUnreferencedFinalOutputNode" in error_message
45
+ assert "terminal nodes that are not referenced by workflow outputs" in error_message
@@ -0,0 +1,66 @@
1
+ import pytest
2
+
3
+ from vellum.workflows import BaseWorkflow
4
+ from vellum.workflows.inputs.base import BaseInputs
5
+ from vellum.workflows.nodes.bases.base import BaseNode
6
+ from vellum.workflows.ports.port import Port
7
+ from vellum.workflows.state.base import BaseState
8
+ from vellum_ee.workflows.display.utils.exceptions import WorkflowValidationError
9
+ from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
10
+
11
+
12
+ def test_workflow_serialization_error__node_points_to_itself():
13
+ """
14
+ Tests that serialization raises an error when a node creates an infinite loop by pointing to itself.
15
+ """
16
+
17
+ # GIVEN a simple workflow where a node points to itself
18
+ class StartNode(BaseNode[BaseState]):
19
+ pass
20
+
21
+ class InfiniteLoopWorkflow(BaseWorkflow[BaseInputs, BaseState]):
22
+ graph = StartNode >> StartNode
23
+
24
+ # WHEN we attempt to serialize the workflow
25
+ workflow_display = get_workflow_display(workflow_class=InfiniteLoopWorkflow)
26
+
27
+ # THEN it should raise a WorkflowValidationError about the self-edge
28
+ with pytest.raises(WorkflowValidationError) as exc_info:
29
+ workflow_display.serialize()
30
+
31
+ # AND the error message should be exact and descriptive
32
+ error_message = str(exc_info.value)
33
+ assert error_message == (
34
+ "Workflow validation error in InfiniteLoopWorkflow: " "Graph contains a self-edge (StartNode >> StartNode)."
35
+ )
36
+
37
+
38
+ def test_workflow_serialization__node_with_conditional_loop_is_valid():
39
+ """
40
+ Tests that a node with conditional ports (one looping back, one going forward) is valid.
41
+ """
42
+
43
+ # GIVEN a workflow where a node has ports with one looping back to itself and one going to another node
44
+ class State(BaseState):
45
+ should_loop: bool = False
46
+
47
+ class StartNode(BaseNode[State]):
48
+ class Ports(BaseNode.Ports):
49
+ loop = Port.on_if(State.should_loop.equals(True))
50
+ end = Port.on_else()
51
+
52
+ class EndNode(BaseNode[State]):
53
+ pass
54
+
55
+ class ConditionalLoopWorkflow(BaseWorkflow[BaseInputs, State]):
56
+ graph = {
57
+ StartNode.Ports.loop >> StartNode,
58
+ StartNode.Ports.end >> EndNode,
59
+ }
60
+
61
+ # WHEN we attempt to serialize the workflow
62
+ workflow_display = get_workflow_display(workflow_class=ConditionalLoopWorkflow)
63
+
64
+ # THEN it should NOT raise a WorkflowValidationError
65
+ result = workflow_display.serialize()
66
+ assert result is not None
@@ -0,0 +1,40 @@
1
+ from vellum.workflows import BaseWorkflow
2
+ from vellum.workflows.inputs import BaseInputs
3
+ from vellum.workflows.nodes import FinalOutputNode
4
+ from vellum.workflows.state import BaseState
5
+ from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
6
+
7
+
8
+ class IntInputs(BaseInputs):
9
+ count: int
10
+
11
+
12
+ class OutputNode(FinalOutputNode):
13
+ class Outputs(FinalOutputNode.Outputs):
14
+ value = IntInputs.count
15
+
16
+
17
+ class IntInputWorkflow(BaseWorkflow[IntInputs, BaseState]):
18
+ graph = OutputNode
19
+
20
+ class Outputs(BaseWorkflow.Outputs):
21
+ value = OutputNode.Outputs.value
22
+
23
+
24
+ def test_serialize_workflow__int_input_schema_preserved():
25
+ """
26
+ Tests that an int input has its schema preserved during serialization.
27
+ """
28
+
29
+ # GIVEN a Workflow that has an int input
30
+ # WHEN we serialize it
31
+ workflow_display = get_workflow_display(workflow_class=IntInputWorkflow)
32
+ serialized_workflow: dict = workflow_display.serialize()
33
+
34
+ # THEN the input variables should include the schema field with type "integer"
35
+ input_variables = serialized_workflow["input_variables"]
36
+ assert len(input_variables) == 1
37
+
38
+ # AND the schema should preserve the int type
39
+ assert input_variables[0]["type"] == "NUMBER"
40
+ assert input_variables[0]["schema"] == {"type": "integer"}
@@ -187,8 +187,9 @@ def test_trigger_module_paths_are_canonical():
187
187
 
188
188
 
189
189
  def test_integration_trigger_no_entrypoint_node():
190
- """IntegrationTrigger workflows now create ENTRYPOINT nodes and route edges through them."""
190
+ """IntegrationTrigger-only workflows should NOT have an ENTRYPOINT node when all branches are trigger-sourced."""
191
191
 
192
+ # GIVEN an IntegrationTrigger workflow where all branches are sourced from the trigger
192
193
  class SlackMessageTrigger(IntegrationTrigger):
193
194
  message: str
194
195
 
@@ -203,9 +204,10 @@ def test_integration_trigger_no_entrypoint_node():
203
204
  class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
204
205
  graph = SlackMessageTrigger >> ProcessNode
205
206
 
207
+ # WHEN we serialize the workflow
206
208
  result = get_workflow_display(workflow_class=TestWorkflow).serialize()
207
209
 
208
- # Get trigger ID
210
+ # THEN the trigger should be serialized
209
211
  triggers = result["triggers"]
210
212
  assert isinstance(triggers, list)
211
213
  assert len(triggers) == 1
@@ -213,29 +215,21 @@ def test_integration_trigger_no_entrypoint_node():
213
215
  assert isinstance(trigger, dict)
214
216
  trigger_id = trigger["id"]
215
217
 
216
- # Verify ENTRYPOINT node exists
218
+ # AND there should be NO ENTRYPOINT node (all branches are trigger-sourced)
217
219
  workflow_raw_data = result["workflow_raw_data"]
218
220
  assert isinstance(workflow_raw_data, dict)
219
221
  nodes = workflow_raw_data["nodes"]
220
222
  assert isinstance(nodes, list)
221
223
  entrypoint_nodes = [n for n in nodes if isinstance(n, dict) and n.get("type") == "ENTRYPOINT"]
222
- assert len(entrypoint_nodes) == 1, "IntegrationTrigger workflows should have an ENTRYPOINT node"
223
-
224
- entrypoint_node = entrypoint_nodes[0]
225
- assert isinstance(entrypoint_node, dict)
226
- entrypoint_node_id = entrypoint_node["id"]
224
+ assert len(entrypoint_nodes) == 0, "IntegrationTrigger-only workflows should NOT have an ENTRYPOINT node"
227
225
 
226
+ # AND edges should use trigger ID as source_node_id
228
227
  edges = workflow_raw_data["edges"]
229
228
  assert isinstance(edges, list)
230
- entrypoint_edges = [e for e in edges if isinstance(e, dict) and e.get("source_node_id") == entrypoint_node_id]
231
- assert len(entrypoint_edges) == 0
232
-
233
- # Verify edges use trigger ID as sourceNodeId (not ENTRYPOINT)
234
229
  trigger_edges = [e for e in edges if isinstance(e, dict) and e.get("source_node_id") == trigger_id]
235
230
  assert len(trigger_edges) > 0, "Should have edges from trigger ID"
236
231
 
237
- # Verify the edge connects trigger to first node
238
- # ProcessNode should be the only non-terminal, non-entrypoint node
232
+ # AND the edge should connect trigger to the process node
239
233
  process_nodes = [n for n in nodes if isinstance(n, dict) and n.get("type") not in ("TERMINAL", "ENTRYPOINT")]
240
234
  assert len(process_nodes) > 0, "Should have at least one process node"
241
235
  process_node = process_nodes[0]