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,71 @@
1
+ from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
2
+
3
+ from tests.workflows.basic_tool_calling_node_tool_wrapper.workflow import BasicToolCallingNodeWrapperWorkflow
4
+
5
+
6
+ def test_serialize_workflow():
7
+ # GIVEN a Workflow that uses a tool calling node with tool wrapper
8
+ # WHEN we serialize it
9
+ workflow_display = get_workflow_display(workflow_class=BasicToolCallingNodeWrapperWorkflow)
10
+
11
+ serialized_workflow: dict = workflow_display.serialize()
12
+ # THEN we should get a serialized representation of the Workflow
13
+ assert serialized_workflow.keys() == {
14
+ "workflow_raw_data",
15
+ "input_variables",
16
+ "state_variables",
17
+ "output_variables",
18
+ }
19
+
20
+ # AND its input variables should be what we expect
21
+ input_variables = serialized_workflow["input_variables"]
22
+ assert len(input_variables) == 1
23
+ assert input_variables[0]["key"] == "date_input"
24
+ assert input_variables[0]["type"] == "STRING"
25
+
26
+ # AND its output variables should be what we expect
27
+ output_variables = serialized_workflow["output_variables"]
28
+ assert len(output_variables) == 2
29
+ output_keys = {output["key"] for output in output_variables}
30
+ assert output_keys == {"text", "chat_history"}
31
+
32
+ # AND its raw data should be what we expect
33
+ workflow_raw_data = serialized_workflow["workflow_raw_data"]
34
+
35
+ nodes = workflow_raw_data["nodes"]
36
+ assert len(nodes) == 2
37
+
38
+ tool_calling_node = next(node for node in nodes if node.get("type") == "GENERIC")
39
+
40
+ functions_attr = next(attr for attr in tool_calling_node["attributes"] if attr["name"] == "functions")
41
+ functions = functions_attr["value"]["value"]["value"]
42
+ assert len(functions) == 1
43
+ assert functions[0] == {
44
+ "type": "CODE_EXECUTION",
45
+ "name": "get_current_weather",
46
+ "description": "",
47
+ "definition": {
48
+ "state": None,
49
+ "cache_config": None,
50
+ "name": "get_current_weather",
51
+ "description": None,
52
+ "parameters": {
53
+ "type": "object",
54
+ "properties": {
55
+ "location": {"type": "string", "description": "The location to get the weather for"},
56
+ "units": {"type": "string", "description": "The unit of temperature", "default": "fahrenheit"},
57
+ },
58
+ "required": ["location"],
59
+ "examples": [{"location": "San Francisco"}, {"location": "New York", "units": "celsius"}],
60
+ },
61
+ "inputs": {
62
+ "date_input": {
63
+ "type": "WORKFLOW_INPUT",
64
+ "input_variable_id": functions[0]["definition"]["inputs"]["date_input"]["input_variable_id"],
65
+ }
66
+ },
67
+ "forced": None,
68
+ "strict": None,
69
+ },
70
+ "src": 'from typing import Annotated\n\n\ndef get_current_weather(\n date_input: str,\n location: Annotated[str, "The location to get the weather for"],\n units: Annotated[str, "The unit of temperature"] = "fahrenheit",\n) -> str:\n return f"The current weather on {date_input} in {location} is sunny with a temperature of 70 degrees {units}."\n', # noqa: E501
71
+ }
@@ -1,3 +1,11 @@
1
+ from vellum.client.types.chat_message_prompt_block import ChatMessagePromptBlock
2
+ from vellum.client.types.rich_text_prompt_block import RichTextPromptBlock
3
+ from vellum.client.types.variable_prompt_block import VariablePromptBlock
4
+ from vellum.workflows.constants import VellumIntegrationProviderType
5
+ from vellum.workflows.nodes.displayable.tool_calling_node import ToolCallingNode
6
+ from vellum.workflows.state.base import BaseState
7
+ from vellum.workflows.types.definition import VellumIntegrationToolDefinition
8
+ from vellum.workflows.workflows.base import BaseInputs, BaseWorkflow
1
9
  from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
2
10
 
3
11
  from tests.workflows.basic_tool_calling_node_with_vellum_integration_tool.workflow import (
@@ -66,3 +74,114 @@ def test_serialize_workflow():
66
74
  assert function["integration_name"] == "GITHUB"
67
75
  assert function["name"] == "create_issue"
68
76
  assert function["description"] == "Create a new issue in a GitHub repository"
77
+
78
+
79
+ def test_serialize_workflow__with_toolkit_version():
80
+ """
81
+ Tests that VellumIntegrationToolDefinition with toolkit_version serializes correctly.
82
+ """
83
+
84
+ # GIVEN a VellumIntegrationToolDefinition with toolkit_version
85
+ github_tool_with_version = VellumIntegrationToolDefinition(
86
+ provider=VellumIntegrationProviderType.COMPOSIO,
87
+ integration_name="GITHUB",
88
+ name="create_issue",
89
+ description="Create a new issue in a GitHub repository",
90
+ toolkit_version="1.2.3",
91
+ )
92
+
93
+ # AND a ToolCallingNode that uses this tool
94
+ class TestInputs(BaseInputs):
95
+ query: str
96
+
97
+ class TestToolCallingNode(ToolCallingNode):
98
+ ml_model = "gpt-4o-mini"
99
+ blocks = [
100
+ ChatMessagePromptBlock(
101
+ chat_role="USER",
102
+ blocks=[
103
+ RichTextPromptBlock(
104
+ blocks=[
105
+ VariablePromptBlock(input_variable="question"),
106
+ ],
107
+ ),
108
+ ],
109
+ ),
110
+ ]
111
+ functions = [github_tool_with_version]
112
+ prompt_inputs = {"question": TestInputs.query}
113
+
114
+ class TestWorkflow(BaseWorkflow[TestInputs, BaseState]):
115
+ graph = TestToolCallingNode
116
+
117
+ class Outputs(BaseWorkflow.Outputs):
118
+ text = TestToolCallingNode.Outputs.text
119
+
120
+ # WHEN we serialize the workflow
121
+ workflow_display = get_workflow_display(workflow_class=TestWorkflow)
122
+ serialized_workflow: dict = workflow_display.serialize()
123
+
124
+ # THEN the function should include the toolkit_version
125
+ workflow_raw_data = serialized_workflow["workflow_raw_data"]
126
+ tool_calling_node = workflow_raw_data["nodes"][1]
127
+ attributes = tool_calling_node["attributes"]
128
+ functions_attr = next(attr for attr in attributes if attr["name"] == "functions")
129
+ functions_value = functions_attr["value"]["value"]["value"]
130
+ function = functions_value[0]
131
+
132
+ assert function["toolkit_version"] == "1.2.3"
133
+
134
+
135
+ def test_serialize_workflow__without_toolkit_version():
136
+ """
137
+ Tests that VellumIntegrationToolDefinition without toolkit_version serializes with null.
138
+ """
139
+
140
+ # GIVEN a VellumIntegrationToolDefinition without toolkit_version
141
+ github_tool_no_version = VellumIntegrationToolDefinition(
142
+ provider=VellumIntegrationProviderType.COMPOSIO,
143
+ integration_name="SLACK",
144
+ name="send_message",
145
+ description="Send a message to a Slack channel",
146
+ )
147
+
148
+ # AND a ToolCallingNode that uses this tool
149
+ class TestInputs(BaseInputs):
150
+ query: str
151
+
152
+ class TestToolCallingNode(ToolCallingNode):
153
+ ml_model = "gpt-4o-mini"
154
+ blocks = [
155
+ ChatMessagePromptBlock(
156
+ chat_role="USER",
157
+ blocks=[
158
+ RichTextPromptBlock(
159
+ blocks=[
160
+ VariablePromptBlock(input_variable="question"),
161
+ ],
162
+ ),
163
+ ],
164
+ ),
165
+ ]
166
+ functions = [github_tool_no_version]
167
+ prompt_inputs = {"question": TestInputs.query}
168
+
169
+ class TestWorkflow(BaseWorkflow[TestInputs, BaseState]):
170
+ graph = TestToolCallingNode
171
+
172
+ class Outputs(BaseWorkflow.Outputs):
173
+ text = TestToolCallingNode.Outputs.text
174
+
175
+ # WHEN we serialize the workflow
176
+ workflow_display = get_workflow_display(workflow_class=TestWorkflow)
177
+ serialized_workflow: dict = workflow_display.serialize()
178
+
179
+ # THEN the function should have toolkit_version as None
180
+ workflow_raw_data = serialized_workflow["workflow_raw_data"]
181
+ tool_calling_node = workflow_raw_data["nodes"][1]
182
+ attributes = tool_calling_node["attributes"]
183
+ functions_attr = next(attr for attr in attributes if attr["name"] == "functions")
184
+ functions_value = functions_attr["value"]["value"]["value"]
185
+ function = functions_value[0]
186
+
187
+ assert function["toolkit_version"] is None
@@ -33,7 +33,7 @@ def test_serialize_workflow(vellum_client):
33
33
  input_variables=[],
34
34
  output_variables=[],
35
35
  ),
36
- deployment=WorkflowDeploymentReleaseWorkflowDeployment(name="test-name"),
36
+ deployment=WorkflowDeploymentReleaseWorkflowDeployment(id="test-deployment-id", name="test-name"),
37
37
  description="test-description", # main mock
38
38
  release_tags=[
39
39
  ReleaseReleaseTag(
@@ -47,8 +47,6 @@ def test_serialize_workflow():
47
47
 
48
48
  # AND its raw data should be what we expect
49
49
  workflow_raw_data = serialized_workflow["workflow_raw_data"]
50
- assert len(workflow_raw_data["edges"]) == 3
51
- assert len(workflow_raw_data["nodes"]) == 4
52
50
 
53
51
  # AND each node should be serialized correctly
54
52
  entrypoint_node = workflow_raw_data["nodes"][0]
@@ -5,6 +5,7 @@ from vellum.workflows import BaseWorkflow
5
5
  from vellum.workflows.inputs.base import BaseInputs
6
6
  from vellum.workflows.nodes.displayable.set_state_node import SetStateNode
7
7
  from vellum.workflows.state.base import BaseState
8
+ from vellum_ee.workflows.display.workflows.base_workflow_display import BaseWorkflowDisplay
8
9
  from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
9
10
 
10
11
 
@@ -67,7 +68,7 @@ def test_serialize_chat_message_dict_reference_with_definition():
67
68
  "key": "chat_history",
68
69
  "value": {
69
70
  "type": "BINARY_EXPRESSION",
70
- "lhs": {"type": "WORKFLOW_STATE", "state_variable_id": "2ae688ad-8690-4765-a5fa-aecc7d6496e5"},
71
+ "lhs": {"type": "WORKFLOW_STATE", "state_variable_id": "fff74a8e-752e-4088-9d3e-493e9162bda5"},
71
72
  "operator": "+",
72
73
  "rhs": {
73
74
  "type": "DICTIONARY_REFERENCE",
@@ -106,3 +107,23 @@ def test_serialize_chat_message_dict_reference_with_definition():
106
107
  ],
107
108
  },
108
109
  }
110
+
111
+
112
+ def test_serialize_chat_message_trigger_with_message_parameter():
113
+ """Test that ChatMessageTrigger with message parameter serializes correctly in dataset."""
114
+ # GIVEN a workflow module with a ChatMessageTrigger that has a message parameter
115
+ module_path = "tests.workflows.test_chat_message_trigger_serialization"
116
+
117
+ # WHEN we serialize the module
118
+ result = BaseWorkflowDisplay.serialize_module(module_path)
119
+
120
+ # THEN the dataset should contain the trigger's message in inputs
121
+ assert result.dataset is not None
122
+ assert isinstance(result.dataset, list)
123
+ assert len(result.dataset) == 1
124
+
125
+ # AND the message should be serialized in the inputs as array format
126
+ dataset_row = result.dataset[0]
127
+ assert dataset_row["label"] == "New conversation"
128
+ assert "inputs" in dataset_row
129
+ assert dataset_row["inputs"]["message"] == [{"type": "STRING", "value": "I want to tweet about AI agents"}]
@@ -0,0 +1,412 @@
1
+ """Tests for ChatMessageTrigger serialization."""
2
+
3
+ from typing import Optional
4
+
5
+ from vellum import ChatMessage, ChatMessagePromptBlock, JinjaPromptBlock
6
+ from vellum.workflows import BaseWorkflow
7
+ from vellum.workflows.inputs import BaseInputs
8
+ from vellum.workflows.nodes import InlinePromptNode
9
+ from vellum.workflows.nodes.bases import BaseNode
10
+ from vellum.workflows.ports import Port
11
+ from vellum.workflows.references import LazyReference
12
+ from vellum.workflows.state.base import BaseState
13
+ from vellum.workflows.triggers.chat_message import ChatMessageTrigger
14
+ from vellum_ee.workflows.display.utils.exceptions import (
15
+ StateValidationError,
16
+ TriggerValidationError,
17
+ WorkflowValidationError,
18
+ )
19
+ from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
20
+
21
+ from tests.workflows.chat_message_trigger_execution.workflows.simple_chat_workflow import SimpleChatWorkflow
22
+
23
+
24
+ def test_simple_chat_workflow_serialization():
25
+ """SimpleChatWorkflow from tests/workflows serializes correctly with ChatMessageTrigger."""
26
+
27
+ # GIVEN a Workflow that uses a ChatMessageTrigger
28
+ # WHEN we serialize it
29
+ workflow_display = get_workflow_display(workflow_class=SimpleChatWorkflow)
30
+ serialized_workflow: dict = workflow_display.serialize()
31
+
32
+ # THEN we should get a serialized representation of the Workflow
33
+ assert serialized_workflow.keys() == {
34
+ "workflow_raw_data",
35
+ "input_variables",
36
+ "state_variables",
37
+ "output_variables",
38
+ "triggers",
39
+ }
40
+
41
+ # AND the triggers should be serialized correctly
42
+ assert serialized_workflow["triggers"] == [
43
+ {
44
+ "id": "9e14c49b-c6d9-4fe5-9ff2-835fd695fe5f",
45
+ "type": "CHAT_MESSAGE",
46
+ "name": "chat",
47
+ "attributes": [
48
+ {
49
+ "id": "5edbfd78-b634-4305-b2ad-d9feecbd5e5f",
50
+ "key": "message",
51
+ "type": "ARRAY",
52
+ "required": True,
53
+ "default": {
54
+ "type": "ARRAY",
55
+ "value": None,
56
+ },
57
+ "extensions": None,
58
+ "schema": {
59
+ "anyOf": [
60
+ {"type": "string"},
61
+ {
62
+ "type": "array",
63
+ "items": {
64
+ "$ref": "#/$defs/vellum.client.types.array_chat_message_content_item.ArrayChatMessageContentItem" # noqa: E501
65
+ },
66
+ },
67
+ ]
68
+ },
69
+ }
70
+ ],
71
+ "exec_config": {
72
+ "output": {
73
+ "type": "WORKFLOW_OUTPUT",
74
+ "output_variable_id": "cc1208e9-c043-47f4-abea-c01ac0dbf04c",
75
+ },
76
+ },
77
+ "display_data": {
78
+ "label": "Chat Message",
79
+ "position": {
80
+ "x": 0.0,
81
+ "y": 0.0,
82
+ },
83
+ "z_index": 0,
84
+ "icon": "vellum:icon:message-dots",
85
+ "color": "blue",
86
+ },
87
+ }
88
+ ]
89
+
90
+
91
+ class ResponseNode(BaseNode):
92
+ """Node that returns a simple response."""
93
+
94
+ class Outputs(BaseNode.Outputs):
95
+ response: str = "Hello!"
96
+
97
+
98
+ class ChatTriggerWithoutOutput(ChatMessageTrigger):
99
+ """Chat trigger without Config.output specified."""
100
+
101
+ pass
102
+
103
+
104
+ class WorkflowWithUnspecifiedChatTriggerOutput(BaseWorkflow[BaseInputs, BaseState]):
105
+ """Workflow using ChatTrigger without output specified."""
106
+
107
+ graph = ChatTriggerWithoutOutput >> ResponseNode
108
+
109
+ class Outputs(BaseWorkflow.Outputs):
110
+ response = ResponseNode.Outputs.response
111
+
112
+
113
+ def test_chat_message_trigger_validation__output_not_specified():
114
+ """
115
+ Tests that serialization adds TriggerValidationError to errors when Chat Trigger output is not specified.
116
+ """
117
+
118
+ # GIVEN a Workflow that uses a ChatMessageTrigger without Config.output specified
119
+ workflow_display = get_workflow_display(workflow_class=WorkflowWithUnspecifiedChatTriggerOutput)
120
+
121
+ # WHEN we serialize the workflow
122
+ workflow_display.serialize()
123
+
124
+ # THEN the display_context should contain a TriggerValidationError
125
+ errors = list(workflow_display.display_context.errors)
126
+ assert len(errors) == 1
127
+
128
+ # AND the error should be a TriggerValidationError with the expected message
129
+ error = errors[0]
130
+ assert isinstance(error, TriggerValidationError)
131
+ assert "Chat Trigger output must be specified" in str(error)
132
+
133
+
134
+ def test_chat_message_trigger_graph_with_duplicate_edges():
135
+ """ChatMessageTrigger graph with duplicate edges should deduplicate and report error."""
136
+
137
+ # GIVEN a ChatMessageTrigger subclass
138
+ class Chat(ChatMessageTrigger):
139
+ pass
140
+
141
+ class Orchestrator(BaseNode):
142
+ pass
143
+
144
+ # AND a workflow with a graph containing duplicate edges from trigger to node
145
+ class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
146
+ graph = {
147
+ Chat >> Orchestrator,
148
+ Chat >> Orchestrator,
149
+ }
150
+
151
+ # WHEN we serialize the workflow
152
+ workflow_display = get_workflow_display(workflow_class=TestWorkflow)
153
+ result: dict = workflow_display.serialize()
154
+
155
+ # THEN the workflow should serialize successfully
156
+ assert "workflow_raw_data" in result
157
+ assert "triggers" in result
158
+
159
+ # AND there should be a CHAT_MESSAGE trigger
160
+ triggers: list = result["triggers"]
161
+ assert len(triggers) == 1
162
+ assert triggers[0]["type"] == "CHAT_MESSAGE"
163
+
164
+ # AND there should NOT be a MANUAL trigger
165
+ manual_triggers = [t for t in triggers if t.get("type") == "MANUAL"]
166
+ assert len(manual_triggers) == 0
167
+
168
+ # AND there should NOT be an ENTRYPOINT node
169
+ workflow_raw_data: dict = result["workflow_raw_data"]
170
+ nodes: list = workflow_raw_data["nodes"]
171
+ entrypoint_nodes = [n for n in nodes if isinstance(n, dict) and n.get("type") == "ENTRYPOINT"]
172
+ assert len(entrypoint_nodes) == 0, "ChatMessageTrigger workflows should NOT have an ENTRYPOINT node"
173
+
174
+ # AND edges should have unique IDs (no duplicates)
175
+ edges: list = workflow_raw_data["edges"]
176
+ edge_ids = [e["id"] for e in edges if isinstance(e, dict)]
177
+ assert len(edge_ids) == len(set(edge_ids)), f"Duplicate edge IDs found: {edge_ids}"
178
+
179
+ # AND there should be an error about the duplicate edge
180
+ errors = list(workflow_display.display_context.errors)
181
+ duplicate_edge_errors = [
182
+ e for e in errors if isinstance(e, WorkflowValidationError) and "duplicate" in str(e).lower()
183
+ ]
184
+ assert len(duplicate_edge_errors) == 1, f"Expected 1 duplicate edge error, got {len(duplicate_edge_errors)}"
185
+
186
+
187
+ def test_graph_with_shared_prefix_different_paths__no_validation_error():
188
+ """Graph with shared prefix but different paths should not produce validation errors."""
189
+
190
+ # GIVEN a ChatMessageTrigger subclass as the entry point
191
+ class A(ChatMessageTrigger):
192
+ pass
193
+
194
+ # AND nodes for a workflow
195
+ class B(BaseNode):
196
+ pass
197
+
198
+ class C(BaseNode):
199
+ pass
200
+
201
+ class D(BaseNode):
202
+ pass
203
+
204
+ # AND a workflow with a graph containing shared prefix but different paths
205
+ class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
206
+ graph = {
207
+ A >> B >> C,
208
+ A >> B >> D,
209
+ }
210
+
211
+ # WHEN we serialize the workflow
212
+ workflow_display = get_workflow_display(workflow_class=TestWorkflow)
213
+ result: dict = workflow_display.serialize()
214
+
215
+ # THEN the workflow should serialize successfully
216
+ assert "workflow_raw_data" in result
217
+
218
+ # AND there should be NO validation errors about duplicates
219
+ errors = list(workflow_display.display_context.errors)
220
+ duplicate_errors = [e for e in errors if isinstance(e, WorkflowValidationError) and "duplicate" in str(e).lower()]
221
+ assert len(duplicate_errors) == 0, f"Expected no duplicate errors, got {len(duplicate_errors)}: {duplicate_errors}"
222
+
223
+
224
+ def test_graph_with_duplicate_paths__validation_error():
225
+ """Graph with duplicate paths should produce a validation error."""
226
+
227
+ # GIVEN nodes for a workflow
228
+ class A(BaseNode):
229
+ pass
230
+
231
+ class B(BaseNode):
232
+ pass
233
+
234
+ class C(BaseNode):
235
+ pass
236
+
237
+ # AND a workflow with a graph containing duplicate paths
238
+ class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
239
+ graph = {
240
+ A >> B >> C,
241
+ A >> B >> C,
242
+ }
243
+
244
+ # WHEN we serialize the workflow
245
+ workflow_display = get_workflow_display(workflow_class=TestWorkflow)
246
+ result: dict = workflow_display.serialize()
247
+
248
+ # THEN the workflow should serialize successfully
249
+ assert "workflow_raw_data" in result
250
+
251
+ # AND there should be a validation error about the duplicate path
252
+ errors = list(workflow_display.display_context.errors)
253
+ duplicate_errors = [e for e in errors if isinstance(e, WorkflowValidationError) and "duplicate" in str(e).lower()]
254
+ assert len(duplicate_errors) == 1, f"Expected 1 duplicate error, got {len(duplicate_errors)}"
255
+
256
+
257
+ def test_graph_with_different_ports_same_target__no_validation_error():
258
+ """Graph with different ports from same node to same target should not produce validation errors."""
259
+
260
+ # GIVEN a node with multiple ports
261
+ class A(BaseNode):
262
+ class Ports(BaseNode.Ports):
263
+ foo = Port()
264
+ bar = Port()
265
+
266
+ # AND a target node
267
+ class B(BaseNode):
268
+ pass
269
+
270
+ # AND a workflow with a graph where different ports connect to the same target
271
+ class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
272
+ graph = {
273
+ A.Ports.foo >> B,
274
+ A.Ports.bar >> B,
275
+ }
276
+
277
+ # WHEN we serialize the workflow
278
+ workflow_display = get_workflow_display(workflow_class=TestWorkflow)
279
+ result: dict = workflow_display.serialize()
280
+
281
+ # THEN the workflow should serialize successfully
282
+ assert "workflow_raw_data" in result
283
+
284
+ # AND there should be NO validation errors about duplicates
285
+ errors = list(workflow_display.display_context.errors)
286
+ duplicate_errors = [e for e in errors if isinstance(e, WorkflowValidationError) and "duplicate" in str(e).lower()]
287
+ assert len(duplicate_errors) == 0, f"Expected no duplicate errors, got {len(duplicate_errors)}: {duplicate_errors}"
288
+
289
+
290
+ def test_chat_message_trigger_message_wired_to_prompt_inputs():
291
+ """Tests that ChatMessageTrigger.message can be wired to InlinePromptNode.prompt_inputs."""
292
+
293
+ # GIVEN a ChatMessageTrigger subclass
294
+ class Chat(ChatMessageTrigger):
295
+ class Display(ChatMessageTrigger.Display):
296
+ label: str = "Chat"
297
+
298
+ # AND an InlinePromptNode that uses the trigger's message as a prompt input
299
+ class PromptNode(InlinePromptNode):
300
+ ml_model = "gpt-4o"
301
+ blocks = [
302
+ ChatMessagePromptBlock(
303
+ chat_role="USER",
304
+ blocks=[JinjaPromptBlock(template="{{ message }}")],
305
+ ),
306
+ ]
307
+ prompt_inputs = {"message": Chat.message}
308
+
309
+ # AND a workflow that wires the trigger to the prompt node
310
+ class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
311
+ graph = Chat >> PromptNode
312
+
313
+ class Outputs(BaseWorkflow.Outputs):
314
+ result = PromptNode.Outputs.text
315
+
316
+ # WHEN we serialize the workflow
317
+ workflow_display = get_workflow_display(workflow_class=TestWorkflow)
318
+ serialized_workflow: dict = workflow_display.serialize()
319
+
320
+ # THEN the workflow should serialize successfully
321
+ assert "workflow_raw_data" in serialized_workflow
322
+
323
+ # AND the prompt node should have the message input wired correctly
324
+ workflow_raw_data = serialized_workflow["workflow_raw_data"]
325
+ nodes = workflow_raw_data["nodes"]
326
+ prompt_nodes = [n for n in nodes if isinstance(n, dict) and n.get("type") == "PROMPT"]
327
+ assert len(prompt_nodes) == 1
328
+
329
+ prompt_node = prompt_nodes[0]
330
+ inputs = prompt_node.get("inputs", [])
331
+ message_input = next((i for i in inputs if i.get("key") == "message"), None)
332
+ assert message_input is not None
333
+
334
+ # AND the input should reference the trigger attribute
335
+ rules = message_input.get("value", {}).get("rules", [])
336
+ trigger_attr_rule = next((r for r in rules if r.get("type") == "TRIGGER_ATTRIBUTE"), None)
337
+ assert trigger_attr_rule is not None
338
+
339
+ # AND the prompt input variable type should be ARRAY
340
+ exec_config = prompt_node.get("data", {}).get("exec_config", {})
341
+ input_variables = exec_config.get("input_variables", [])
342
+ message_var = next((v for v in input_variables if v.get("key") == "message"), None)
343
+ assert message_var is not None
344
+ assert message_var.get("type") == "ARRAY"
345
+
346
+
347
+ def test_chat_message_trigger_validation__chat_history_none_default():
348
+ """Tests that serialization adds StateValidationError when chat_history defaults to None instead of empty array."""
349
+
350
+ # GIVEN a state class with chat_history defaulting to None
351
+ class InvalidChatState(BaseState):
352
+ chat_history: Optional[ChatMessage] = None
353
+
354
+ # AND a ChatMessageTrigger with output specified
355
+ class ChatTrigger(ChatMessageTrigger):
356
+ class Config(ChatMessageTrigger.Config):
357
+ output = LazyReference("ChatHistoryNoneWorkflow.Outputs.response")
358
+
359
+ # AND a workflow using the trigger with the invalid state
360
+ class ChatHistoryNoneWorkflow(BaseWorkflow[BaseInputs, InvalidChatState]):
361
+ graph = ChatTrigger >> ResponseNode
362
+
363
+ class Outputs(BaseWorkflow.Outputs):
364
+ response = ResponseNode.Outputs.response
365
+
366
+ # WHEN we serialize the workflow
367
+ workflow_display = get_workflow_display(workflow_class=ChatHistoryNoneWorkflow)
368
+ workflow_display.serialize()
369
+
370
+ # THEN the display_context should contain a StateValidationError
371
+ errors = list(workflow_display.display_context.errors)
372
+ state_errors = [e for e in errors if isinstance(e, StateValidationError)]
373
+ assert len(state_errors) == 1
374
+
375
+ # AND the error should mention chat_history and empty array
376
+ error = state_errors[0]
377
+ assert "chat_history" in str(error)
378
+ assert "empty array" in str(error)
379
+
380
+
381
+ def test_chat_message_trigger_validation__chat_history_missing():
382
+ """Tests that serialization adds StateValidationError when chat_history is missing from state class."""
383
+
384
+ # GIVEN a state class without chat_history
385
+ class StateMissingChatHistory(BaseState):
386
+ pass
387
+
388
+ # AND a ChatMessageTrigger with output specified
389
+ class ChatTrigger(ChatMessageTrigger):
390
+ class Config(ChatMessageTrigger.Config):
391
+ output = LazyReference("MissingChatHistoryWorkflow.Outputs.response")
392
+
393
+ # AND a workflow using the trigger with the state missing chat_history
394
+ class MissingChatHistoryWorkflow(BaseWorkflow[BaseInputs, StateMissingChatHistory]):
395
+ graph = ChatTrigger >> ResponseNode
396
+
397
+ class Outputs(BaseWorkflow.Outputs):
398
+ response = ResponseNode.Outputs.response
399
+
400
+ # WHEN we serialize the workflow
401
+ workflow_display = get_workflow_display(workflow_class=MissingChatHistoryWorkflow)
402
+ workflow_display.serialize()
403
+
404
+ # THEN the display_context should contain a StateValidationError
405
+ errors = list(workflow_display.display_context.errors)
406
+ state_errors = [e for e in errors if isinstance(e, StateValidationError)]
407
+ assert len(state_errors) == 1
408
+
409
+ # AND the error should mention chat_history is required
410
+ error = state_errors[0]
411
+ assert "chat_history" in str(error)
412
+ assert "require" in str(error)