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,912 @@
1
+ import pytest
2
+ import sys
3
+ from uuid import uuid4
4
+
5
+ from vellum.workflows import BaseInputs, BaseNode, BaseState, BaseWorkflow, MockNodeExecution
6
+ from vellum.workflows.exceptions import NodeException
7
+ from vellum.workflows.expressions.accessor import AccessorExpression
8
+ from vellum.workflows.expressions.add import AddExpression
9
+ from vellum.workflows.expressions.and_ import AndExpression
10
+ from vellum.workflows.expressions.begins_with import BeginsWithExpression
11
+ from vellum.workflows.expressions.between import BetweenExpression
12
+ from vellum.workflows.expressions.coalesce_expression import CoalesceExpression
13
+ from vellum.workflows.expressions.concat import ConcatExpression
14
+ from vellum.workflows.expressions.contains import ContainsExpression
15
+ from vellum.workflows.expressions.does_not_begin_with import DoesNotBeginWithExpression
16
+ from vellum.workflows.expressions.does_not_contain import DoesNotContainExpression
17
+ from vellum.workflows.expressions.does_not_end_with import DoesNotEndWithExpression
18
+ from vellum.workflows.expressions.ends_with import EndsWithExpression
19
+ from vellum.workflows.expressions.in_ import InExpression
20
+ from vellum.workflows.expressions.is_blank import IsBlankExpression
21
+ from vellum.workflows.expressions.is_error import IsErrorExpression
22
+ from vellum.workflows.expressions.is_not_blank import IsNotBlankExpression
23
+ from vellum.workflows.expressions.is_not_null import IsNotNullExpression
24
+ from vellum.workflows.expressions.is_null import IsNullExpression
25
+ from vellum.workflows.expressions.length import LengthExpression
26
+ from vellum.workflows.expressions.minus import MinusExpression
27
+ from vellum.workflows.expressions.not_between import NotBetweenExpression
28
+ from vellum.workflows.expressions.not_in import NotInExpression
29
+ from vellum.workflows.expressions.or_ import OrExpression
30
+ from vellum.workflows.expressions.parse_json import ParseJsonExpression
31
+ from vellum.workflows.nodes.core.inline_subworkflow_node.node import InlineSubworkflowNode
32
+ from vellum.workflows.nodes.core.try_node.node import TryNode
33
+ from vellum.workflows.references.constant import ConstantValueReference
34
+ from vellum.workflows.references.environment_variable import EnvironmentVariableReference
35
+ from vellum.workflows.references.vellum_secret import VellumSecretReference
36
+ from vellum_ee.workflows.display.utils.expressions import base_descriptor_validator
37
+ from vellum_ee.workflows.server.virtual_file_loader import VirtualFileFinder
38
+
39
+
40
+ def test_mocks__parse_from_app__descriptors():
41
+ # GIVEN a Base Node
42
+ class StartNode(BaseNode):
43
+ class Outputs(BaseNode.Outputs):
44
+ foo: str
45
+
46
+ # AND workflow inputs
47
+ class Inputs(BaseInputs):
48
+ bar: str
49
+
50
+ # AND workflow state
51
+ class State(BaseState):
52
+ baz: str
53
+
54
+ # AND a workflow class with that Node
55
+ class MyWorkflow(BaseWorkflow[Inputs, State]):
56
+ graph = StartNode
57
+
58
+ class Outputs(BaseWorkflow.Outputs):
59
+ final_value = StartNode.Outputs.foo
60
+
61
+ # AND a mock workflow node execution from the app
62
+ raw_mock_workflow_node_executions = [
63
+ {
64
+ "node_id": str(StartNode.__id__),
65
+ "when_condition": {
66
+ "type": "BINARY_EXPRESSION",
67
+ "operator": ">=",
68
+ "lhs": {
69
+ "type": "EXECUTION_COUNTER",
70
+ "node_id": str(StartNode.__id__),
71
+ },
72
+ "rhs": {
73
+ "type": "CONSTANT_VALUE",
74
+ "value": {
75
+ "type": "NUMBER",
76
+ "value": 1,
77
+ },
78
+ },
79
+ },
80
+ "then_outputs": {
81
+ "foo": "Hello foo",
82
+ },
83
+ },
84
+ {
85
+ "node_id": str(StartNode.__id__),
86
+ "when_condition": {
87
+ "type": "BINARY_EXPRESSION",
88
+ "operator": "==",
89
+ "lhs": {
90
+ "type": "WORKFLOW_INPUT",
91
+ "input_variable_id": str(Inputs.bar.id),
92
+ },
93
+ "rhs": {
94
+ "type": "CONSTANT_VALUE",
95
+ "value": {
96
+ "type": "STRING",
97
+ "value": "bar",
98
+ },
99
+ },
100
+ },
101
+ "then_outputs": {
102
+ "foo": "Hello bar",
103
+ },
104
+ },
105
+ {
106
+ "node_id": str(StartNode.__id__),
107
+ "when_condition": {
108
+ "type": "BINARY_EXPRESSION",
109
+ "operator": "!=",
110
+ "lhs": {
111
+ "type": "WORKFLOW_STATE",
112
+ "state_variable_id": str(State.baz.id),
113
+ },
114
+ "rhs": {
115
+ "type": "CONSTANT_VALUE",
116
+ "value": {
117
+ "type": "STRING",
118
+ "value": "baz",
119
+ },
120
+ },
121
+ },
122
+ "then_outputs": {
123
+ "foo": "Hello baz",
124
+ },
125
+ },
126
+ ]
127
+
128
+ # WHEN we parsed the raw data on `MockNodeExecution`
129
+ node_output_mocks = MockNodeExecution.validate_all(
130
+ raw_mock_workflow_node_executions,
131
+ MyWorkflow,
132
+ descriptor_validator=base_descriptor_validator,
133
+ )
134
+
135
+ # THEN we get the expected list of MockNodeExecution objects
136
+ assert node_output_mocks
137
+ assert len(node_output_mocks) == 3
138
+ assert node_output_mocks[0] == MockNodeExecution(
139
+ when_condition=StartNode.Execution.count.greater_than_or_equal_to(1),
140
+ then_outputs=StartNode.Outputs(
141
+ foo="Hello foo",
142
+ ),
143
+ )
144
+ assert node_output_mocks[1] == MockNodeExecution(
145
+ when_condition=Inputs.bar.equals("bar"),
146
+ then_outputs=StartNode.Outputs(
147
+ foo="Hello bar",
148
+ ),
149
+ )
150
+ assert node_output_mocks[2] == MockNodeExecution(
151
+ when_condition=State.baz.does_not_equal("baz"),
152
+ then_outputs=StartNode.Outputs(
153
+ foo="Hello baz",
154
+ ),
155
+ )
156
+
157
+
158
+ def test_mocks__parse_from_app__when_condition_defaults_to_false_without_descriptor_validator():
159
+ """
160
+ Tests that when_condition defaults to ConstantValueReference(False) when
161
+ descriptor_validator is not provided. This ensures that mocks without a
162
+ descriptor_validator have a valid when_condition that evaluates to False.
163
+ """
164
+
165
+ # GIVEN a Base Node
166
+ class StartNode(BaseNode):
167
+ class Outputs(BaseNode.Outputs):
168
+ foo: str
169
+
170
+ # AND a workflow class with that Node
171
+ class MyWorkflow(BaseWorkflow):
172
+ graph = StartNode
173
+
174
+ class Outputs(BaseWorkflow.Outputs):
175
+ final_value = StartNode.Outputs.foo
176
+
177
+ # AND a mock workflow node execution with a valid when_condition JSON structure
178
+ raw_mock_workflow_node_executions = [
179
+ {
180
+ "node_id": str(StartNode.__id__),
181
+ "when_condition": {
182
+ "type": "BINARY_EXPRESSION",
183
+ "operator": ">=",
184
+ "lhs": {
185
+ "type": "EXECUTION_COUNTER",
186
+ "node_id": str(StartNode.__id__),
187
+ },
188
+ "rhs": {
189
+ "type": "CONSTANT_VALUE",
190
+ "value": {
191
+ "type": "NUMBER",
192
+ "value": 1,
193
+ },
194
+ },
195
+ },
196
+ "then_outputs": {
197
+ "foo": "Hello foo",
198
+ },
199
+ },
200
+ ]
201
+
202
+ # WHEN we parse the raw data on `MockNodeExecution` without a descriptor_validator
203
+ node_output_mocks = MockNodeExecution.validate_all(
204
+ raw_mock_workflow_node_executions,
205
+ MyWorkflow,
206
+ )
207
+
208
+ # THEN we get a list of MockNodeExecution objects
209
+ assert node_output_mocks is not None
210
+ assert len(node_output_mocks) == 1
211
+
212
+ # AND the when_condition defaults to ConstantValueReference(False)
213
+ assert node_output_mocks[0].when_condition == ConstantValueReference(False)
214
+
215
+
216
+ def test_mocks__use_node_id_from_display():
217
+ """
218
+ Tests that validate_all correctly resolves mocks when the node ID is annotated by the display class.
219
+ """
220
+
221
+ # GIVEN a workflow module with a display class that sets a custom node_id
222
+ display_node_id = uuid4()
223
+ files = {
224
+ "__init__.py": "",
225
+ "workflow.py": """\
226
+ from vellum.workflows import BaseWorkflow
227
+ from vellum.workflows.nodes import BaseNode
228
+
229
+
230
+ class StartNode(BaseNode):
231
+ class Outputs(BaseNode.Outputs):
232
+ foo: str
233
+
234
+
235
+ class Workflow(BaseWorkflow):
236
+ graph = StartNode
237
+
238
+ class Outputs(BaseWorkflow.Outputs):
239
+ final_value = StartNode.Outputs.foo
240
+ """,
241
+ "display/__init__.py": """\
242
+ # flake8: noqa: F401, F403
243
+
244
+ from .workflow import *
245
+ from .nodes import *
246
+ """,
247
+ "display/workflow.py": """\
248
+ from vellum_ee.workflows.display.workflows import BaseWorkflowDisplay
249
+ from ..workflow import Workflow
250
+
251
+
252
+ class WorkflowDisplay(BaseWorkflowDisplay[Workflow]):
253
+ pass
254
+ """,
255
+ "display/nodes/__init__.py": """\
256
+ # flake8: noqa: F401, F403
257
+
258
+ from .start_node import *
259
+ """,
260
+ "display/nodes/start_node.py": f"""\
261
+ from uuid import UUID
262
+ from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
263
+ from ...workflow import StartNode
264
+
265
+
266
+ class StartNodeDisplay(BaseNodeDisplay[StartNode]):
267
+ node_id = UUID("{display_node_id}")
268
+ """,
269
+ }
270
+
271
+ namespace = str(uuid4())
272
+
273
+ # AND the virtual file loader is registered
274
+ sys.meta_path.append(VirtualFileFinder(files, namespace))
275
+
276
+ # AND the workflow is loaded from the module
277
+ Workflow = BaseWorkflow.load_from_module(namespace)
278
+ StartNode = list(Workflow.get_nodes())[0]
279
+
280
+ # AND a mock workflow node execution using the display-annotated node ID
281
+ raw_mock_workflow_node_executions = [
282
+ {
283
+ "node_id": str(display_node_id),
284
+ "when_condition": {
285
+ "type": "BINARY_EXPRESSION",
286
+ "operator": ">=",
287
+ "lhs": {
288
+ "type": "EXECUTION_COUNTER",
289
+ "node_id": str(display_node_id),
290
+ },
291
+ "rhs": {
292
+ "type": "CONSTANT_VALUE",
293
+ "value": {
294
+ "type": "NUMBER",
295
+ "value": 0,
296
+ },
297
+ },
298
+ },
299
+ "then_outputs": {
300
+ "foo": "Hello",
301
+ },
302
+ },
303
+ ]
304
+
305
+ # WHEN we parse the mock workflow node execution
306
+ node_output_mocks = MockNodeExecution.validate_all(
307
+ raw_mock_workflow_node_executions,
308
+ Workflow,
309
+ descriptor_validator=base_descriptor_validator,
310
+ )
311
+
312
+ # THEN we get the expected list of MockNodeExecution objects
313
+ assert node_output_mocks
314
+ assert len(node_output_mocks) == 1
315
+ assert node_output_mocks[0] == MockNodeExecution(
316
+ when_condition=StartNode.Execution.count.greater_than_or_equal_to(0),
317
+ then_outputs=StartNode.Outputs( # type: ignore[call-arg]
318
+ foo="Hello",
319
+ ),
320
+ )
321
+
322
+
323
+ def test_mocks__node_not_found_in_workflow_skips_with_warning(caplog):
324
+ """
325
+ Tests that when a mock references a node_id that doesn't exist in the workflow,
326
+ the mock is skipped with a warning instead of raising an error.
327
+ """
328
+
329
+ # GIVEN a Base Node
330
+ class StartNode(BaseNode):
331
+ class Outputs(BaseNode.Outputs):
332
+ foo: str
333
+
334
+ # AND a workflow class with that Node
335
+ class MyWorkflow(BaseWorkflow):
336
+ graph = StartNode
337
+
338
+ class Outputs(BaseWorkflow.Outputs):
339
+ final_value = StartNode.Outputs.foo
340
+
341
+ # AND a mock workflow node execution referencing a non-existent node_id
342
+ non_existent_node_id = uuid4()
343
+ raw_mock_with_missing_node = [
344
+ {
345
+ "node_id": str(non_existent_node_id),
346
+ "when_condition": {
347
+ "type": "BINARY_EXPRESSION",
348
+ "operator": ">=",
349
+ "lhs": {
350
+ "type": "EXECUTION_COUNTER",
351
+ "node_id": str(non_existent_node_id),
352
+ },
353
+ "rhs": {
354
+ "type": "CONSTANT_VALUE",
355
+ "value": {
356
+ "type": "NUMBER",
357
+ "value": 0,
358
+ },
359
+ },
360
+ },
361
+ "then_outputs": {
362
+ "foo": "Hello",
363
+ },
364
+ }
365
+ ]
366
+
367
+ # WHEN we parse the mock workflow node execution
368
+ node_output_mocks = MockNodeExecution.validate_all(
369
+ raw_mock_with_missing_node,
370
+ MyWorkflow,
371
+ descriptor_validator=base_descriptor_validator,
372
+ )
373
+
374
+ # THEN we get an empty list (the mock was skipped)
375
+ assert node_output_mocks == []
376
+
377
+ # AND a warning was logged
378
+ assert any(
379
+ f"Skipping mock for node {non_existent_node_id}" in record.message
380
+ and "node not found in workflow MyWorkflow" in record.message
381
+ for record in caplog.records
382
+ )
383
+
384
+
385
+ def test_base_descriptor_validator__vellum_secret():
386
+ """
387
+ Tests that VELLUM_SECRET descriptor type is correctly validated.
388
+ """
389
+
390
+ # GIVEN a simple workflow
391
+ class StartNode(BaseNode):
392
+ class Outputs(BaseNode.Outputs):
393
+ foo: str
394
+
395
+ class MyWorkflow(BaseWorkflow):
396
+ graph = StartNode
397
+
398
+ # AND a VELLUM_SECRET descriptor
399
+ raw_descriptor = {
400
+ "type": "VELLUM_SECRET",
401
+ "vellum_secret_name": "my_secret",
402
+ }
403
+
404
+ # WHEN we validate the descriptor
405
+ result = base_descriptor_validator(raw_descriptor, MyWorkflow)
406
+
407
+ # THEN we get a VellumSecretReference
408
+ assert isinstance(result, VellumSecretReference)
409
+ assert result.name == "my_secret"
410
+
411
+
412
+ def test_base_descriptor_validator__environment_variable():
413
+ """
414
+ Tests that ENVIRONMENT_VARIABLE descriptor type is correctly validated.
415
+ """
416
+
417
+ # GIVEN a simple workflow
418
+ class StartNode(BaseNode):
419
+ class Outputs(BaseNode.Outputs):
420
+ foo: str
421
+
422
+ class MyWorkflow(BaseWorkflow):
423
+ graph = StartNode
424
+
425
+ # AND an ENVIRONMENT_VARIABLE descriptor
426
+ raw_descriptor = {
427
+ "type": "ENVIRONMENT_VARIABLE",
428
+ "environment_variable": "MY_ENV_VAR",
429
+ }
430
+
431
+ # WHEN we validate the descriptor
432
+ result = base_descriptor_validator(raw_descriptor, MyWorkflow)
433
+
434
+ # THEN we get an EnvironmentVariableReference
435
+ assert isinstance(result, EnvironmentVariableReference)
436
+ assert result.name == "MY_ENV_VAR"
437
+
438
+
439
+ def test_base_descriptor_validator__array_reference():
440
+ """
441
+ Tests that ARRAY_REFERENCE descriptor type is correctly validated.
442
+ """
443
+
444
+ # GIVEN a simple workflow
445
+ class StartNode(BaseNode):
446
+ class Outputs(BaseNode.Outputs):
447
+ foo: str
448
+
449
+ class MyWorkflow(BaseWorkflow):
450
+ graph = StartNode
451
+
452
+ # AND an ARRAY_REFERENCE descriptor with constant values
453
+ raw_descriptor = {
454
+ "type": "ARRAY_REFERENCE",
455
+ "items": [
456
+ {"type": "CONSTANT_VALUE", "value": {"type": "STRING", "value": "item1"}},
457
+ {"type": "CONSTANT_VALUE", "value": {"type": "STRING", "value": "item2"}},
458
+ ],
459
+ }
460
+
461
+ # WHEN we validate the descriptor
462
+ result = base_descriptor_validator(raw_descriptor, MyWorkflow)
463
+
464
+ # THEN we get a ConstantValueReference containing a list
465
+ assert isinstance(result, ConstantValueReference)
466
+ assert isinstance(result._value, list)
467
+ assert len(result._value) == 2
468
+
469
+
470
+ def test_base_descriptor_validator__dictionary_reference():
471
+ """
472
+ Tests that DICTIONARY_REFERENCE descriptor type is correctly validated.
473
+ """
474
+
475
+ # GIVEN a simple workflow
476
+ class StartNode(BaseNode):
477
+ class Outputs(BaseNode.Outputs):
478
+ foo: str
479
+
480
+ class MyWorkflow(BaseWorkflow):
481
+ graph = StartNode
482
+
483
+ # AND a DICTIONARY_REFERENCE descriptor
484
+ raw_descriptor = {
485
+ "type": "DICTIONARY_REFERENCE",
486
+ "entries": [
487
+ {
488
+ "id": "entry1",
489
+ "key": "key1",
490
+ "value": {"type": "CONSTANT_VALUE", "value": {"type": "STRING", "value": "value1"}},
491
+ },
492
+ {
493
+ "id": "entry2",
494
+ "key": "key2",
495
+ "value": {"type": "CONSTANT_VALUE", "value": {"type": "NUMBER", "value": 42}},
496
+ },
497
+ ],
498
+ }
499
+
500
+ # WHEN we validate the descriptor
501
+ result = base_descriptor_validator(raw_descriptor, MyWorkflow)
502
+
503
+ # THEN we get a ConstantValueReference containing a dict
504
+ assert isinstance(result, ConstantValueReference)
505
+ assert isinstance(result._value, dict)
506
+ assert "key1" in result._value
507
+ assert "key2" in result._value
508
+
509
+
510
+ @pytest.mark.parametrize(
511
+ "operator,expected_type",
512
+ [
513
+ ("blank", IsBlankExpression),
514
+ ("notBlank", IsNotBlankExpression),
515
+ ("null", IsNullExpression),
516
+ ("notNull", IsNotNullExpression),
517
+ ("isError", IsErrorExpression),
518
+ ("length", LengthExpression),
519
+ ("parseJson", ParseJsonExpression),
520
+ ],
521
+ )
522
+ def test_base_descriptor_validator__unary_expression(operator, expected_type):
523
+ """
524
+ Tests that UNARY_EXPRESSION descriptor types are correctly validated.
525
+ """
526
+
527
+ # GIVEN a simple workflow
528
+ class StartNode(BaseNode):
529
+ class Outputs(BaseNode.Outputs):
530
+ foo: str
531
+
532
+ class MyWorkflow(BaseWorkflow):
533
+ graph = StartNode
534
+
535
+ # AND a UNARY_EXPRESSION descriptor
536
+ raw_descriptor = {
537
+ "type": "UNARY_EXPRESSION",
538
+ "operator": operator,
539
+ "lhs": {"type": "CONSTANT_VALUE", "value": {"type": "STRING", "value": "test"}},
540
+ }
541
+
542
+ # WHEN we validate the descriptor
543
+ result = base_descriptor_validator(raw_descriptor, MyWorkflow)
544
+
545
+ # THEN we get the expected expression type
546
+ assert isinstance(result, expected_type)
547
+
548
+
549
+ @pytest.mark.parametrize(
550
+ "operator,expected_type",
551
+ [
552
+ ("between", BetweenExpression),
553
+ ("notBetween", NotBetweenExpression),
554
+ ],
555
+ )
556
+ def test_base_descriptor_validator__ternary_expression(operator, expected_type):
557
+ """
558
+ Tests that TERNARY_EXPRESSION descriptor types are correctly validated.
559
+ """
560
+
561
+ # GIVEN a simple workflow
562
+ class StartNode(BaseNode):
563
+ class Outputs(BaseNode.Outputs):
564
+ foo: str
565
+
566
+ class MyWorkflow(BaseWorkflow):
567
+ graph = StartNode
568
+
569
+ # AND a TERNARY_EXPRESSION descriptor
570
+ raw_descriptor = {
571
+ "type": "TERNARY_EXPRESSION",
572
+ "operator": operator,
573
+ "base": {"type": "CONSTANT_VALUE", "value": {"type": "NUMBER", "value": 5}},
574
+ "lhs": {"type": "CONSTANT_VALUE", "value": {"type": "NUMBER", "value": 1}},
575
+ "rhs": {"type": "CONSTANT_VALUE", "value": {"type": "NUMBER", "value": 10}},
576
+ }
577
+
578
+ # WHEN we validate the descriptor
579
+ result = base_descriptor_validator(raw_descriptor, MyWorkflow)
580
+
581
+ # THEN we get the expected expression type
582
+ assert isinstance(result, expected_type)
583
+
584
+
585
+ @pytest.mark.parametrize(
586
+ "operator,expected_type",
587
+ [
588
+ ("=", BeginsWithExpression.__bases__[0].__bases__[0]),
589
+ ("==", BeginsWithExpression.__bases__[0].__bases__[0]),
590
+ ("doesNotContain", DoesNotContainExpression),
591
+ ("doesNotBeginWith", DoesNotBeginWithExpression),
592
+ ("doesNotEndWith", DoesNotEndWithExpression),
593
+ ("in", InExpression),
594
+ ("notIn", NotInExpression),
595
+ ("and", AndExpression),
596
+ ("or", OrExpression),
597
+ ("coalesce", CoalesceExpression),
598
+ ("+", AddExpression),
599
+ ("-", MinusExpression),
600
+ ("concat", ConcatExpression),
601
+ ],
602
+ )
603
+ def test_base_descriptor_validator__binary_expression_additional_operators(operator, expected_type):
604
+ """
605
+ Tests that additional BINARY_EXPRESSION operators are correctly validated.
606
+ """
607
+
608
+ # GIVEN a simple workflow
609
+ class StartNode(BaseNode):
610
+ class Outputs(BaseNode.Outputs):
611
+ foo: str
612
+
613
+ class MyWorkflow(BaseWorkflow):
614
+ graph = StartNode
615
+
616
+ # AND a BINARY_EXPRESSION descriptor with the given operator
617
+ raw_descriptor = {
618
+ "type": "BINARY_EXPRESSION",
619
+ "operator": operator,
620
+ "lhs": {"type": "CONSTANT_VALUE", "value": {"type": "STRING", "value": "left"}},
621
+ "rhs": {"type": "CONSTANT_VALUE", "value": {"type": "STRING", "value": "right"}},
622
+ }
623
+
624
+ # WHEN we validate the descriptor
625
+ result = base_descriptor_validator(raw_descriptor, MyWorkflow)
626
+
627
+ # THEN we get the expected expression type
628
+ assert isinstance(result, expected_type)
629
+
630
+
631
+ def test_base_descriptor_validator__accessor_expression():
632
+ """
633
+ Tests that accessField operator in BINARY_EXPRESSION is correctly validated.
634
+ """
635
+
636
+ # GIVEN a simple workflow
637
+ class StartNode(BaseNode):
638
+ class Outputs(BaseNode.Outputs):
639
+ foo: str
640
+
641
+ class MyWorkflow(BaseWorkflow):
642
+ graph = StartNode
643
+
644
+ # AND a BINARY_EXPRESSION descriptor with accessField operator
645
+ raw_descriptor = {
646
+ "type": "BINARY_EXPRESSION",
647
+ "operator": "accessField",
648
+ "lhs": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": {"field": "value"}}},
649
+ "rhs": {"type": "CONSTANT_VALUE", "value": {"type": "STRING", "value": "field"}},
650
+ }
651
+
652
+ # WHEN we validate the descriptor
653
+ result = base_descriptor_validator(raw_descriptor, MyWorkflow)
654
+
655
+ # THEN we get an AccessorExpression
656
+ assert isinstance(result, AccessorExpression)
657
+
658
+
659
+ def test_mocks__validate_all__node_nested_in_subworkflow():
660
+ """
661
+ Tests that MockNodeExecution.validate_all correctly handles mocks for nodes
662
+ nested within a subworkflow node by validating against the inner subworkflow.
663
+ """
664
+
665
+ # GIVEN a node that will be nested inside a subworkflow
666
+ class NestedNode(BaseNode):
667
+ class Outputs(BaseNode.Outputs):
668
+ result: str
669
+
670
+ def run(self) -> Outputs:
671
+ raise NodeException("This node should be mocked")
672
+
673
+ # AND a subworkflow containing that nested node
674
+ class InnerSubworkflow(BaseWorkflow[BaseInputs, BaseState]):
675
+ graph = NestedNode
676
+
677
+ class Outputs(BaseWorkflow.Outputs):
678
+ inner_result = NestedNode.Outputs.result
679
+
680
+ # AND a subworkflow node that uses the inner subworkflow
681
+ class SubworkflowNode(InlineSubworkflowNode):
682
+ subworkflow = InnerSubworkflow
683
+
684
+ # AND an outer workflow containing the subworkflow node
685
+ class OuterWorkflow(BaseWorkflow):
686
+ graph = SubworkflowNode
687
+
688
+ class Outputs(BaseWorkflow.Outputs):
689
+ final_result = SubworkflowNode.Outputs.inner_result
690
+
691
+ # AND raw mock data for the nested node using the modern data model format
692
+ raw_mock_workflow_node_executions = [
693
+ {
694
+ "node_id": str(NestedNode.__id__),
695
+ "when_condition": {
696
+ "type": "BINARY_EXPRESSION",
697
+ "operator": ">=",
698
+ "lhs": {
699
+ "type": "EXECUTION_COUNTER",
700
+ "node_id": str(NestedNode.__id__),
701
+ },
702
+ "rhs": {
703
+ "type": "CONSTANT_VALUE",
704
+ "value": {
705
+ "type": "NUMBER",
706
+ "value": 0,
707
+ },
708
+ },
709
+ },
710
+ "then_outputs": {
711
+ "result": "mocked_result",
712
+ },
713
+ }
714
+ ]
715
+
716
+ # WHEN we call validate_all on the outer workflow
717
+ node_output_mocks = MockNodeExecution.validate_all(
718
+ raw_mock_workflow_node_executions,
719
+ OuterWorkflow,
720
+ descriptor_validator=base_descriptor_validator,
721
+ )
722
+
723
+ # THEN we get a list of MockNodeExecution objects
724
+ assert node_output_mocks is not None
725
+ assert len(node_output_mocks) == 1
726
+
727
+ # AND the mock is correctly parsed with the nested node's outputs
728
+ assert node_output_mocks[0] == MockNodeExecution(
729
+ when_condition=NestedNode.Execution.count.greater_than_or_equal_to(0),
730
+ then_outputs=NestedNode.Outputs(result="mocked_result"),
731
+ )
732
+
733
+ # AND when we run the outer workflow with the mocks
734
+ workflow = OuterWorkflow()
735
+ terminal_event = workflow.run(node_output_mocks=node_output_mocks)
736
+
737
+ # THEN the workflow completes successfully
738
+ assert terminal_event.name == "workflow.execution.fulfilled", terminal_event
739
+
740
+ # AND the output reflects the mocked value from the nested node
741
+ assert terminal_event.outputs.final_result == "mocked_result"
742
+
743
+
744
+ def test_mocks__serialize__try_node_wrapped_node_has_correct_node_id():
745
+ """
746
+ Tests that when serializing a MockNodeExecution for a node wrapped in a TryNode adornment,
747
+ the serialized node_id matches the inner wrapped node's ID (not the adornment wrapper's ID).
748
+ """
749
+
750
+ # GIVEN a node wrapped in a TryNode adornment
751
+ @TryNode.wrap()
752
+ class WrappedNode(BaseNode):
753
+ class Outputs(BaseNode.Outputs):
754
+ result: str
755
+
756
+ # AND a workflow that uses the wrapped node
757
+ class MyWorkflow(BaseWorkflow):
758
+ graph = WrappedNode
759
+
760
+ class Outputs(BaseWorkflow.Outputs):
761
+ final_result = WrappedNode.Outputs.result
762
+
763
+ # AND a mock for the wrapped node
764
+ mock = MockNodeExecution(
765
+ when_condition=ConstantValueReference(True),
766
+ then_outputs=WrappedNode.Outputs(result="mocked_result"),
767
+ )
768
+
769
+ # WHEN we serialize the mock
770
+ serialized_mock = mock.model_dump()
771
+
772
+ # THEN the serialized node_id should match the inner wrapped node's ID
773
+ inner_node = WrappedNode.__wrapped_node__
774
+ assert inner_node is not None
775
+ assert serialized_mock["node_id"] == str(inner_node.__id__)
776
+
777
+ # AND the serialized mock should have the correct type
778
+ assert serialized_mock["type"] == "NODE_EXECUTION"
779
+
780
+ # AND the then_outputs should be serialized correctly
781
+ assert serialized_mock["then_outputs"]["result"] == "mocked_result"
782
+
783
+
784
+ def test_mocks__validate_all__try_node_wrapped_node_deserializes_correctly():
785
+ """
786
+ Tests that validate_all correctly deserializes a mock for a node wrapped in a TryNode adornment.
787
+ """
788
+
789
+ # GIVEN a node wrapped in a TryNode adornment
790
+ @TryNode.wrap()
791
+ class WrappedNode(BaseNode):
792
+ class Outputs(BaseNode.Outputs):
793
+ result: str
794
+
795
+ # AND a workflow that uses the wrapped node
796
+ class MyWorkflow(BaseWorkflow):
797
+ graph = WrappedNode
798
+
799
+ class Outputs(BaseWorkflow.Outputs):
800
+ final_result = WrappedNode.Outputs.result
801
+
802
+ # AND the inner wrapped node's ID
803
+ inner_node = WrappedNode.__wrapped_node__
804
+ assert inner_node is not None
805
+
806
+ # AND a raw mock workflow node execution using the inner node's ID
807
+ raw_mock_workflow_node_executions = [
808
+ {
809
+ "node_id": str(inner_node.__id__),
810
+ "when_condition": {
811
+ "type": "BINARY_EXPRESSION",
812
+ "operator": ">=",
813
+ "lhs": {
814
+ "type": "EXECUTION_COUNTER",
815
+ "node_id": str(inner_node.__id__),
816
+ },
817
+ "rhs": {
818
+ "type": "CONSTANT_VALUE",
819
+ "value": {
820
+ "type": "NUMBER",
821
+ "value": 1,
822
+ },
823
+ },
824
+ },
825
+ "then_outputs": {
826
+ "result": "mocked_result",
827
+ },
828
+ },
829
+ ]
830
+
831
+ # WHEN we parse the raw data on MockNodeExecution
832
+ node_output_mocks = MockNodeExecution.validate_all(
833
+ raw_mock_workflow_node_executions,
834
+ MyWorkflow,
835
+ descriptor_validator=base_descriptor_validator,
836
+ )
837
+
838
+ # THEN we get a list with one MockNodeExecution object
839
+ assert node_output_mocks is not None
840
+ assert len(node_output_mocks) == 1
841
+
842
+ # AND the MockNodeExecution has the correct when_condition
843
+ assert node_output_mocks[0].when_condition == inner_node.Execution.count.greater_than_or_equal_to(1)
844
+
845
+ # AND the then_outputs is the correct type with the expected value
846
+ assert node_output_mocks[0].then_outputs == inner_node.Outputs(result="mocked_result")
847
+
848
+
849
+ def test_mocks__validate_all__ignores_undefined_outputs():
850
+ """
851
+ Tests that validate_all ignores outputs that are not defined in the node's Outputs class.
852
+ """
853
+
854
+ # GIVEN a node wrapped in a TryNode adornment
855
+ @TryNode.wrap()
856
+ class WrappedNode(BaseNode):
857
+ class Outputs(BaseNode.Outputs):
858
+ result: str
859
+
860
+ # AND a workflow that uses the wrapped node
861
+ class MyWorkflow(BaseWorkflow):
862
+ graph = WrappedNode
863
+
864
+ class Outputs(BaseWorkflow.Outputs):
865
+ final_result = WrappedNode.Outputs.result
866
+
867
+ # AND the inner wrapped node's ID
868
+ inner_node = WrappedNode.__wrapped_node__
869
+ assert inner_node is not None
870
+
871
+ # AND a raw mock workflow node execution with an undefined "error" output
872
+ raw_mock_workflow_node_executions = [
873
+ {
874
+ "node_id": str(inner_node.__id__),
875
+ "when_condition": {
876
+ "type": "BINARY_EXPRESSION",
877
+ "operator": ">=",
878
+ "lhs": {
879
+ "type": "EXECUTION_COUNTER",
880
+ "node_id": str(inner_node.__id__),
881
+ },
882
+ "rhs": {
883
+ "type": "CONSTANT_VALUE",
884
+ "value": {
885
+ "type": "NUMBER",
886
+ "value": 1,
887
+ },
888
+ },
889
+ },
890
+ "then_outputs": {
891
+ "result": "mocked_result",
892
+ "error": "some error value",
893
+ },
894
+ },
895
+ ]
896
+
897
+ # WHEN we parse the raw data on MockNodeExecution
898
+ node_output_mocks = MockNodeExecution.validate_all(
899
+ raw_mock_workflow_node_executions,
900
+ MyWorkflow,
901
+ descriptor_validator=base_descriptor_validator,
902
+ )
903
+
904
+ # THEN we get a list with one MockNodeExecution object
905
+ assert node_output_mocks is not None
906
+ assert len(node_output_mocks) == 1
907
+
908
+ # AND the then_outputs only contains the declared "result" output
909
+ assert node_output_mocks[0].then_outputs == inner_node.Outputs(result="mocked_result")
910
+
911
+ # AND the "error" output was ignored and is not present
912
+ assert not hasattr(node_output_mocks[0].then_outputs, "error")