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,173 @@
1
+ """Tests for IntegrationTrigger attribute type validation during serialization."""
2
+
3
+ import pytest
4
+ from unittest.mock import MagicMock, patch
5
+ from typing import Dict
6
+
7
+ from vellum.workflows import BaseWorkflow
8
+ from vellum.workflows.inputs.base import BaseInputs
9
+ from vellum.workflows.nodes.bases.base import BaseNode
10
+ from vellum.workflows.state.base import BaseState
11
+ from vellum.workflows.triggers.integration import IntegrationTrigger
12
+ from vellum_ee.workflows.display.utils.exceptions import TriggerValidationError
13
+ from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
14
+
15
+
16
+ class LinearIssueTrigger(IntegrationTrigger):
17
+ """Custom Linear issue trigger for testing."""
18
+
19
+ action: str
20
+ data: dict
21
+ type: str
22
+ url: str
23
+
24
+ class Config:
25
+ provider = "COMPOSIO"
26
+ integration_name = "LINEAR"
27
+ slug = "LINEAR_ISSUE_CREATED_TRIGGER"
28
+ setup_attributes = {"team_id": "test-team-id"}
29
+
30
+
31
+ class ProcessNode(BaseNode):
32
+ """Node that processes the trigger."""
33
+
34
+ class Outputs(BaseNode.Outputs):
35
+ result = LinearIssueTrigger.action
36
+
37
+ def run(self) -> Outputs:
38
+ return self.Outputs()
39
+
40
+
41
+ class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
42
+ graph = LinearIssueTrigger >> ProcessNode
43
+
44
+
45
+ def _create_mock_tool_definition(properties: Dict[str, dict]) -> MagicMock:
46
+ """Helper to create a mock tool definition with output_parameters as JSON Schema.
47
+
48
+ For triggers, output_parameters contains the webhook payload schema,
49
+ while input_parameters contains setup/config arguments.
50
+ """
51
+ mock_tool_def = MagicMock()
52
+ mock_tool_def.name = "LINEAR_ISSUE_CREATED_TRIGGER"
53
+ # output_parameters is a JSON Schema object containing the payload schema
54
+ mock_tool_def.output_parameters = {
55
+ "type": "object",
56
+ "properties": properties,
57
+ "required": list(properties.keys()),
58
+ }
59
+ return mock_tool_def
60
+
61
+
62
+ def test_integration_trigger_validation__matching_types():
63
+ """
64
+ Tests that serialization succeeds when trigger attribute types match the API definition.
65
+ """
66
+ # GIVEN a tool definition from the API with matching types (JSON Schema format)
67
+ mock_tool_def = _create_mock_tool_definition(
68
+ {
69
+ "action": {"type": "string"},
70
+ "data": {"type": "object"},
71
+ "type": {"type": "string"},
72
+ "url": {"type": "string"},
73
+ }
74
+ )
75
+
76
+ # WHEN we serialize the workflow with mocked API
77
+ workflow_display = get_workflow_display(workflow_class=TestWorkflow)
78
+
79
+ with patch.object(
80
+ workflow_display._client.integrations,
81
+ "retrieve_integration_tool_definition",
82
+ return_value=mock_tool_def,
83
+ ):
84
+ # THEN serialization should succeed without raising an error
85
+ result = workflow_display.serialize()
86
+
87
+ # AND the trigger should be serialized correctly
88
+ assert "triggers" in result
89
+ triggers = result["triggers"]
90
+ assert isinstance(triggers, list)
91
+ assert len(triggers) == 1
92
+
93
+
94
+ def test_integration_trigger_validation__type_mismatch():
95
+ """
96
+ Tests that serialization raises TriggerValidationError when attribute types don't match.
97
+ """
98
+ # GIVEN a tool definition from the API with mismatched types (JSON Schema format)
99
+ # The API says 'action' should be object (JSON), but our trigger defines it as STRING
100
+ mock_tool_def = _create_mock_tool_definition(
101
+ {
102
+ "action": {"type": "object"},
103
+ "data": {"type": "object"},
104
+ "type": {"type": "string"},
105
+ "url": {"type": "string"},
106
+ }
107
+ )
108
+
109
+ # WHEN we serialize the workflow with mocked API
110
+ workflow_display = get_workflow_display(workflow_class=TestWorkflow)
111
+
112
+ with patch.object(
113
+ workflow_display._client.integrations,
114
+ "retrieve_integration_tool_definition",
115
+ return_value=mock_tool_def,
116
+ ):
117
+ # THEN serialization should raise TriggerValidationError
118
+ with pytest.raises(TriggerValidationError) as exc_info:
119
+ workflow_display.serialize()
120
+
121
+ # AND the error message should indicate the type mismatch with expected and actual types
122
+ assert "action" in str(exc_info.value)
123
+ assert "STRING" in str(exc_info.value)
124
+ assert "expected type 'JSON'" in str(exc_info.value)
125
+ assert "The trigger configuration is invalid or contains unsupported values" in str(exc_info.value)
126
+
127
+
128
+ def test_integration_trigger_validation__api_error():
129
+ """
130
+ Tests that serialization continues gracefully when the API returns an error.
131
+ """
132
+ # GIVEN an API that raises an exception
133
+ workflow_display = get_workflow_display(workflow_class=TestWorkflow)
134
+
135
+ with patch.object(
136
+ workflow_display._client.integrations,
137
+ "retrieve_integration_tool_definition",
138
+ side_effect=Exception("API Error"),
139
+ ):
140
+ # WHEN we serialize the workflow
141
+ # THEN serialization should succeed (validation is skipped on API error)
142
+ result = workflow_display.serialize()
143
+
144
+ # AND the trigger should still be serialized
145
+ assert "triggers" in result
146
+ triggers = result["triggers"]
147
+ assert isinstance(triggers, list)
148
+ assert len(triggers) == 1
149
+
150
+
151
+ def test_integration_trigger_validation__no_properties():
152
+ """
153
+ Tests that serialization continues gracefully when the API response has no properties.
154
+ """
155
+ # GIVEN a tool definition with empty properties in JSON Schema
156
+ mock_tool_def = _create_mock_tool_definition({})
157
+
158
+ # WHEN we serialize the workflow with mocked API
159
+ workflow_display = get_workflow_display(workflow_class=TestWorkflow)
160
+
161
+ with patch.object(
162
+ workflow_display._client.integrations,
163
+ "retrieve_integration_tool_definition",
164
+ return_value=mock_tool_def,
165
+ ):
166
+ # THEN serialization should succeed (validation is skipped when no output_parameters)
167
+ result = workflow_display.serialize()
168
+
169
+ # AND the trigger should still be serialized
170
+ assert "triggers" in result
171
+ triggers = result["triggers"]
172
+ assert isinstance(triggers, list)
173
+ assert len(triggers) == 1
@@ -16,9 +16,13 @@ from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class imp
16
16
  def test_integration_trigger_with_explicit_entrypoint_node_id():
17
17
  """
18
18
  Tests that a workflow with an IntegrationTrigger and explicit entrypoint_node_id
19
- creates an ENTRYPOINT node using the explicit ID (not the trigger's ID).
19
+ does NOT create an ENTRYPOINT node when all branches are trigger-sourced.
20
+
21
+ Even with explicit IDs provided, if all graph branches originate from triggers,
22
+ the ENTRYPOINT node should be skipped.
20
23
  """
21
24
 
25
+ # GIVEN an IntegrationTrigger workflow with explicit entrypoint_node_id
22
26
  class SlackMessageTrigger(IntegrationTrigger):
23
27
  message: str
24
28
 
@@ -53,8 +57,10 @@ def test_integration_trigger_with_explicit_entrypoint_node_id():
53
57
  )
54
58
  }
55
59
 
60
+ # WHEN we serialize the workflow
56
61
  result: dict = get_workflow_display(workflow_class=TestWorkflow).serialize()
57
62
 
63
+ # THEN the trigger should be serialized
58
64
  assert "workflow_raw_data" in result
59
65
  workflow_raw_data = result["workflow_raw_data"]
60
66
  assert isinstance(workflow_raw_data, dict)
@@ -67,22 +73,19 @@ def test_integration_trigger_with_explicit_entrypoint_node_id():
67
73
  assert isinstance(trigger, dict)
68
74
  trigger_id = trigger["id"]
69
75
 
76
+ # AND there should be NO ENTRYPOINT node (all branches are trigger-sourced)
70
77
  nodes = workflow_raw_data["nodes"]
71
78
  assert isinstance(nodes, list)
72
79
 
73
80
  entrypoint_nodes = [n for n in nodes if isinstance(n, dict) and n.get("type") == "ENTRYPOINT"]
74
81
 
75
- assert (
76
- len(entrypoint_nodes) == 1
77
- ), "IntegrationTrigger workflows with explicit entrypoint_node_id should create an ENTRYPOINT node"
78
-
79
- entrypoint_node = entrypoint_nodes[0]
80
- assert isinstance(entrypoint_node, dict)
81
- entrypoint_node_id = entrypoint_node["id"]
82
-
83
- assert entrypoint_node_id == str(explicit_entrypoint_node_id), (
84
- f"Entrypoint node ID should be {explicit_entrypoint_node_id} from workflow_display, "
85
- f"not {trigger_id} from trigger"
82
+ assert len(entrypoint_nodes) == 0, (
83
+ "IntegrationTrigger-only workflows should NOT create an ENTRYPOINT node "
84
+ "even with explicit entrypoint_node_id, since all branches are trigger-sourced"
86
85
  )
87
86
 
88
- assert entrypoint_node_id != trigger_id, "Entrypoint node should not use the trigger's ID"
87
+ # AND edges should use trigger ID as source_node_id
88
+ edges = workflow_raw_data["edges"]
89
+ assert isinstance(edges, list)
90
+ trigger_edges = [e for e in edges if isinstance(e, dict) and e.get("source_node_id") == trigger_id]
91
+ assert len(trigger_edges) > 0, "Should have edges from trigger ID"
@@ -49,7 +49,11 @@ def test_serialize_workflow_with_list_vellum_document():
49
49
  input_var = input_variables[0]
50
50
  assert input_var["key"] == "documents"
51
51
  assert input_var["type"] == "JSON"
52
- # NOTE: Once custom type serialization is supported, we will want to represent this using an openapi spec
52
+ # The schema field now includes the OpenAPI spec for the input type
53
+ assert input_var["schema"] == {
54
+ "type": "array",
55
+ "items": {"$ref": "#/$defs/vellum.client.types.vellum_document.VellumDocument"},
56
+ }
53
57
  assert input_var["required"] is True
54
58
  assert input_var["default"] is None
55
59
  assert input_var["extensions"] == {"color": None}
@@ -24,7 +24,12 @@ def test_manual_trigger_serialization():
24
24
  assert isinstance(triggers, list)
25
25
 
26
26
  assert len(triggers) == 1
27
- assert triggers[0] == {"id": "b3c8ab56-001f-4157-bbc2-4a7fe5ebf8c6", "type": "MANUAL", "attributes": []}
27
+ assert triggers[0] == {
28
+ "id": "b3c8ab56-001f-4157-bbc2-4a7fe5ebf8c6",
29
+ "type": "MANUAL",
30
+ "name": "manual",
31
+ "attributes": [],
32
+ }
28
33
 
29
34
 
30
35
  def test_manual_trigger_multiple_entrypoints():
@@ -54,7 +59,12 @@ def test_manual_trigger_multiple_entrypoints():
54
59
  assert isinstance(triggers, list)
55
60
 
56
61
  assert len(triggers) == 1
57
- assert triggers[0] == {"id": "b3c8ab56-001f-4157-bbc2-4a7fe5ebf8c6", "type": "MANUAL", "attributes": []}
62
+ assert triggers[0] == {
63
+ "id": "b3c8ab56-001f-4157-bbc2-4a7fe5ebf8c6",
64
+ "type": "MANUAL",
65
+ "name": "manual",
66
+ "attributes": [],
67
+ }
58
68
 
59
69
 
60
70
  def test_unknown_trigger_type():
@@ -97,3 +97,114 @@ def test_manual_and_slack_trigger_same_node():
97
97
  assert slack_edge is not None, (
98
98
  f"Should have edge from Slack trigger ({slack_trigger_id}) " f"to ProcessNode ({process_node_id})"
99
99
  )
100
+
101
+
102
+ def test_two_integration_triggers_same_node_unique_edge_ids():
103
+ """
104
+ Tests that when two IntegrationTriggers point to the same node,
105
+ each trigger edge gets a unique ID (not duplicates).
106
+ """
107
+
108
+ # GIVEN two different IntegrationTriggers
109
+ class SlackMessageTrigger(IntegrationTrigger):
110
+ message: str
111
+
112
+ class Config(IntegrationTrigger.Config):
113
+ provider = VellumIntegrationProviderType.COMPOSIO
114
+ integration_name = "SLACK"
115
+ slug = "slack_new_message"
116
+
117
+ class GithubPRTrigger(IntegrationTrigger):
118
+ pr_title: str
119
+
120
+ class Config(IntegrationTrigger.Config):
121
+ provider = VellumIntegrationProviderType.COMPOSIO
122
+ integration_name = "GITHUB"
123
+ slug = "github_pr_opened"
124
+
125
+ class ProcessNode(BaseNode):
126
+ pass
127
+
128
+ # AND a workflow where both triggers point to the same node
129
+ class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
130
+ graph = {
131
+ SlackMessageTrigger >> ProcessNode,
132
+ GithubPRTrigger >> ProcessNode,
133
+ }
134
+
135
+ # WHEN we serialize the workflow
136
+ result = get_workflow_display(workflow_class=TestWorkflow).serialize()
137
+
138
+ # THEN we should have two triggers
139
+ triggers = result["triggers"]
140
+ assert isinstance(triggers, list)
141
+ assert len(triggers) == 2
142
+
143
+ def get_integration_name(trigger: dict) -> str:
144
+ exec_config = trigger.get("exec_config")
145
+ if isinstance(exec_config, dict):
146
+ name = exec_config.get("integration_name")
147
+ return str(name) if name else ""
148
+ return ""
149
+
150
+ slack_trigger = next(
151
+ (t for t in triggers if isinstance(t, dict) and get_integration_name(t) == "SLACK"),
152
+ None,
153
+ )
154
+ assert slack_trigger is not None
155
+ assert isinstance(slack_trigger, dict)
156
+ slack_trigger_id = slack_trigger["id"]
157
+
158
+ github_trigger = next(
159
+ (t for t in triggers if isinstance(t, dict) and get_integration_name(t) == "GITHUB"),
160
+ None,
161
+ )
162
+ assert github_trigger is not None
163
+ assert isinstance(github_trigger, dict)
164
+ github_trigger_id = github_trigger["id"]
165
+
166
+ # AND we should have edges from both triggers
167
+ workflow_raw_data = result["workflow_raw_data"]
168
+ assert isinstance(workflow_raw_data, dict)
169
+ edges = workflow_raw_data["edges"]
170
+ assert isinstance(edges, list)
171
+
172
+ nodes = workflow_raw_data["nodes"]
173
+ assert isinstance(nodes, list)
174
+ process_nodes = [n for n in nodes if isinstance(n, dict) and n.get("type") not in ("TERMINAL", "ENTRYPOINT")]
175
+ assert len(process_nodes) > 0
176
+ process_node = process_nodes[0]
177
+ assert isinstance(process_node, dict)
178
+ process_node_id = process_node["id"]
179
+
180
+ slack_edge = next(
181
+ (
182
+ e
183
+ for e in edges
184
+ if isinstance(e, dict)
185
+ and e.get("source_node_id") == slack_trigger_id
186
+ and e.get("target_node_id") == process_node_id
187
+ ),
188
+ None,
189
+ )
190
+ assert slack_edge is not None, "Should have edge from Slack trigger to ProcessNode"
191
+ assert isinstance(slack_edge, dict)
192
+
193
+ github_edge = next(
194
+ (
195
+ e
196
+ for e in edges
197
+ if isinstance(e, dict)
198
+ and e.get("source_node_id") == github_trigger_id
199
+ and e.get("target_node_id") == process_node_id
200
+ ),
201
+ None,
202
+ )
203
+ assert github_edge is not None, "Should have edge from GitHub trigger to ProcessNode"
204
+ assert isinstance(github_edge, dict)
205
+
206
+ # AND the edge IDs should be unique (not duplicates)
207
+ assert slack_edge["id"] != github_edge["id"], (
208
+ f"Edge IDs should be unique but both are {slack_edge['id']}. "
209
+ "Multiple triggers targeting the same node should have distinct edge IDs."
210
+ )
@@ -0,0 +1,64 @@
1
+ """Tests for validation when a workflow has no triggers and no entrypoint node."""
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_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
+ def test_workflow_serialization_error__no_triggers_no_entrypoint():
12
+ """
13
+ Tests that serialization adds an error when a workflow has no triggers and no entrypoint node.
14
+ """
15
+
16
+ # GIVEN a workflow with an empty graph (no triggers and no entrypoint nodes)
17
+ class EmptyWorkflow(BaseWorkflow[BaseInputs, BaseState]):
18
+ graph = set()
19
+
20
+ # WHEN we serialize the workflow
21
+ workflow_display = get_workflow_display(workflow_class=EmptyWorkflow)
22
+ workflow_display.serialize()
23
+
24
+ # THEN the display_context should contain a WorkflowValidationError
25
+ errors = list(workflow_display.display_context.errors)
26
+ validation_errors = [e for e in errors if isinstance(e, WorkflowValidationError)]
27
+ assert len(validation_errors) == 1
28
+
29
+ # AND the error message should be exact and descriptive
30
+ error = validation_errors[0]
31
+ assert str(error) == (
32
+ "Workflow validation error in EmptyWorkflow: "
33
+ "Workflow has no triggers and no entrypoint nodes. "
34
+ "A workflow must have at least one trigger or one node in its graph."
35
+ )
36
+
37
+
38
+ def test_workflow_serialization_error__no_triggers_no_entrypoint_with_unused_graphs():
39
+ """
40
+ Tests that serialization adds an error even when a workflow has nodes in unused_graphs
41
+ but no triggers and no entrypoint nodes in the main graph.
42
+ """
43
+
44
+ # GIVEN a workflow with nodes only in unused_graphs (no triggers and no entrypoint nodes in main graph)
45
+ class UnusedNode(BaseNode):
46
+ class Outputs(BaseNode.Outputs):
47
+ result: str
48
+
49
+ class WorkflowWithOnlyUnusedGraphs(BaseWorkflow[BaseInputs, BaseState]):
50
+ graph = set()
51
+ unused_graphs = {UnusedNode}
52
+
53
+ # WHEN we serialize the workflow
54
+ workflow_display = get_workflow_display(workflow_class=WorkflowWithOnlyUnusedGraphs)
55
+ workflow_display.serialize()
56
+
57
+ # THEN the display_context should contain a WorkflowValidationError
58
+ errors = list(workflow_display.display_context.errors)
59
+ validation_errors = [e for e in errors if isinstance(e, WorkflowValidationError)]
60
+ assert len(validation_errors) == 1
61
+
62
+ # AND the error message should indicate the workflow has no triggers and no entrypoint nodes
63
+ error = validation_errors[0]
64
+ assert "no triggers and no entrypoint nodes" in str(error)
@@ -0,0 +1,55 @@
1
+ """Tests for partial WorkflowMetaDisplay override 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_ee.workflows.display.base import WorkflowDisplayData, WorkflowDisplayDataViewport, WorkflowMetaDisplay
8
+ from vellum_ee.workflows.display.workflows.base_workflow_display import BaseWorkflowDisplay
9
+ from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
10
+
11
+
12
+ def test_triggerless_workflow_with_partial_display_override_creates_entrypoint():
13
+ """
14
+ Tests that a triggerless workflow with partial WorkflowMetaDisplay overrides
15
+ (only display_data set) still creates an ENTRYPOINT node with backfilled IDs.
16
+ """
17
+
18
+ # GIVEN a simple triggerless workflow
19
+ class ProcessNode(BaseNode):
20
+ pass
21
+
22
+ class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
23
+ graph = ProcessNode
24
+
25
+ # AND a display class that only overrides display_data (viewport), not entrypoint IDs
26
+ class TestWorkflowDisplay(BaseWorkflowDisplay[TestWorkflow]):
27
+ workflow_display = WorkflowMetaDisplay(
28
+ display_data=WorkflowDisplayData(viewport=WorkflowDisplayDataViewport(x=100.0, y=200.0, zoom=1.5))
29
+ )
30
+
31
+ # WHEN we serialize the workflow
32
+ result = get_workflow_display(workflow_class=TestWorkflow).serialize()
33
+
34
+ # THEN the workflow should have an ENTRYPOINT node
35
+ workflow_raw_data = result["workflow_raw_data"]
36
+ assert isinstance(workflow_raw_data, dict)
37
+ nodes = workflow_raw_data["nodes"]
38
+ assert isinstance(nodes, list)
39
+ entrypoint_nodes = [n for n in nodes if isinstance(n, dict) and n.get("type") == "ENTRYPOINT"]
40
+ assert len(entrypoint_nodes) == 1, "Triggerless workflow with partial overrides should have an ENTRYPOINT node"
41
+
42
+ # AND the ENTRYPOINT node should have valid IDs (not None)
43
+ entrypoint_node = entrypoint_nodes[0]
44
+ assert isinstance(entrypoint_node, dict)
45
+ assert entrypoint_node["id"] is not None, "ENTRYPOINT node should have a non-None id"
46
+ entrypoint_data = entrypoint_node["data"]
47
+ assert isinstance(entrypoint_data, dict)
48
+ source_handle_id = entrypoint_data["source_handle_id"]
49
+ assert source_handle_id is not None, "ENTRYPOINT node should have a non-None source_handle_id"
50
+
51
+ # AND there should be an edge from ENTRYPOINT to the process node
52
+ edges = workflow_raw_data["edges"]
53
+ assert isinstance(edges, list)
54
+ entrypoint_edges = [e for e in edges if isinstance(e, dict) and e.get("source_node_id") == entrypoint_node["id"]]
55
+ assert len(entrypoint_edges) > 0, "Should have edges from ENTRYPOINT node"