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
@@ -19,6 +19,7 @@ from typing import (
19
19
  get_origin,
20
20
  )
21
21
 
22
+ from vellum.client import Vellum as VellumClient
22
23
  from vellum.client.types.code_resource_definition import CodeResourceDefinition
23
24
  from vellum.workflows import BaseWorkflow
24
25
  from vellum.workflows.constants import undefined
@@ -30,12 +31,14 @@ from vellum.workflows.references.node import NodeReference
30
31
  from vellum.workflows.types.core import JsonArray, JsonObject
31
32
  from vellum.workflows.types.generics import NodeType
32
33
  from vellum.workflows.types.utils import get_original_base
34
+ from vellum.workflows.utils.functions import compile_annotation
33
35
  from vellum.workflows.utils.names import pascal_to_title_case
34
36
  from vellum.workflows.utils.uuids import uuid4_from_hash
35
37
  from vellum.workflows.utils.vellum_variables import primitive_type_to_vellum_variable_type
36
- from vellum_ee.workflows.display.editor.types import NodeDisplayComment, NodeDisplayData
38
+ from vellum_ee.workflows.display.editor.types import NodeDisplayComment, NodeDisplayData, NodeDisplayPosition
37
39
  from vellum_ee.workflows.display.nodes.get_node_display_class import get_node_display_class
38
40
  from vellum_ee.workflows.display.nodes.types import NodeOutputDisplay, PortDisplay, PortDisplayOverrides
41
+ from vellum_ee.workflows.display.utils.exceptions import NodeValidationError, UnsupportedSerializationException
39
42
  from vellum_ee.workflows.display.utils.expressions import serialize_value
40
43
  from vellum_ee.workflows.display.utils.registry import register_node_display_class
41
44
 
@@ -60,10 +63,9 @@ class BaseNodeDisplayMeta(type):
60
63
  cls = cast(Type["BaseNodeDisplay"], super().__new__(mcs, name, bases, dct))
61
64
  # This cast shouldn't be necessary, but it's a workaround for a mypy bug
62
65
  node_class = cast(Type[BaseNode], cls.infer_node_class() if name != "BaseNodeDisplay" else BaseNode)
63
-
64
66
  if not dct.get("output_display"):
65
67
  cls.output_display = {
66
- ref: NodeOutputDisplay(id=node_class.__output_ids__[ref.name], name=ref.name)
68
+ ref: NodeOutputDisplay(id=node_class.__output_ids__[ref.name], name=ref.name, _is_implicit=True)
67
69
  for ref in node_class.Outputs
68
70
  if ref.name in node_class.__output_ids__
69
71
  }
@@ -140,6 +142,11 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
140
142
  __unserializable_attributes__: Set[NodeReference] = set()
141
143
  # END: Attributes for backwards compatible serialization
142
144
 
145
+ def build(self, client: VellumClient) -> None:
146
+ # Individual display classes can override this method to perform any async logic
147
+ # needed to perform serialization across all nodes.
148
+ pass
149
+
143
150
  def serialize(self, display_context: "WorkflowDisplayContext", **kwargs: Any) -> JsonObject:
144
151
  node = self._node
145
152
  node_id = self.node_id
@@ -202,14 +209,18 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
202
209
  for port in node.Ports:
203
210
  id = str(self.get_node_port_display(port).id)
204
211
  if port._condition_type:
212
+ expression = None
213
+ if port._condition:
214
+ try:
215
+ expression = serialize_value(node_id, display_context, port._condition)
216
+ except UnsupportedSerializationException as e:
217
+ display_context.add_error(e)
205
218
  ports.append(
206
219
  {
207
220
  "id": id,
208
221
  "name": port.name,
209
222
  "type": port._condition_type.value,
210
- "expression": (
211
- serialize_value(node_id, display_context, port._condition) if port._condition else None
212
- ),
223
+ "expression": expression,
213
224
  }
214
225
  )
215
226
  else:
@@ -275,6 +286,16 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
275
286
  if output in self.output_display
276
287
  else str(uuid4_from_hash(f"{self.node_id}|{output.name}"))
277
288
  )
289
+ try:
290
+ schema = compile_annotation(output.normalized_type, {})
291
+ except Exception as e:
292
+ display_context.add_error(
293
+ NodeValidationError(
294
+ message=f"Failed to compile output schema for output '{output.name}': {e}",
295
+ node_class_name=self._node.__name__,
296
+ )
297
+ )
298
+ schema = None
278
299
 
279
300
  outputs.append(
280
301
  {
@@ -282,6 +303,7 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
282
303
  "name": output.name,
283
304
  "type": output_type,
284
305
  "value": value,
306
+ "schema": schema,
285
307
  }
286
308
  )
287
309
 
@@ -480,6 +502,17 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
480
502
  if self._node.Display.color is not None and self._node.Display.color != base_node.Display.color:
481
503
  base_kwargs["color"] = self._node.Display.color
482
504
 
505
+ # Add position from x, y if they exist
506
+ if self._node.Display.x is not None or self._node.Display.y is not None:
507
+ base_kwargs["position"] = NodeDisplayPosition(
508
+ x=self._node.Display.x if self._node.Display.x is not None else 0.0,
509
+ y=self._node.Display.y if self._node.Display.y is not None else 0.0,
510
+ )
511
+
512
+ # Add z_index if it exists
513
+ if self._node.Display.z_index is not None and self._node.Display.z_index != base_node.Display.z_index:
514
+ base_kwargs["z_index"] = self._node.Display.z_index
515
+
483
516
  # Add docstring as comment if present
484
517
  if docstring:
485
518
  base_kwargs["comment"] = NodeDisplayComment(value=docstring, expanded=True)
@@ -492,16 +525,16 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
492
525
  # Get fields that were explicitly set for z_index handling
493
526
  fields_set = explicit_value.model_fields_set
494
527
 
528
+ # Override position only if explicitly set (since it has a default value)
529
+ if "position" in fields_set:
530
+ base_kwargs["position"] = explicit_value.position
531
+
495
532
  # Override simple attributes (only if not None)
496
- for attr in ("position", "width", "height", "icon", "color"):
533
+ for attr in ("width", "height", "icon", "color", "z_index"):
497
534
  value = getattr(explicit_value, attr, None)
498
535
  if value is not None:
499
536
  base_kwargs[attr] = value
500
537
 
501
- # Include z_index if explicitly set (even if None)
502
- if "z_index" in fields_set:
503
- base_kwargs["z_index"] = explicit_value.z_index
504
-
505
538
  # Special handling for comment: merge docstring with explicit comment's expanded state
506
539
  if explicit_value.comment:
507
540
  base_kwargs["comment"] = (
@@ -246,3 +246,96 @@ def test_serialize_display_class_used_as_fallback():
246
246
  assert display_data["icon"] == "vellum:icon:star" # Falls back to Display class
247
247
  assert display_data["color"] == "green" # Falls back to Display class
248
248
  assert display_data["position"] == {"x": 50, "y": 75} # From BaseNodeDisplay
249
+
250
+
251
+ def test_serialize_basenode_display_with_xyz():
252
+ """Tests that BaseNode.Display x, y, z_index attributes serialize correctly."""
253
+
254
+ # GIVEN a node with x, y, z_index in Display class
255
+ class MyNode(BaseNode):
256
+ class Display:
257
+ x = 100.0
258
+ y = 200.0
259
+ z_index = 5
260
+
261
+ # WHEN we serialize the node
262
+ node_display_class = get_node_display_class(MyNode)
263
+ data = node_display_class().serialize(WorkflowDisplayContext())
264
+
265
+ # THEN the display_data should include position and z_index from Display class
266
+ display_data = data["display_data"]
267
+ assert isinstance(display_data, dict)
268
+ assert display_data["position"] == {"x": 100.0, "y": 200.0}
269
+ assert display_data["z_index"] == 5
270
+
271
+
272
+ def test_serialize_basenode_display_with_partial_xy():
273
+ """Tests that BaseNode.Display with only x or y still creates position."""
274
+
275
+ # GIVEN a node with only x in Display class
276
+ class MyNodeX(BaseNode):
277
+ class Display:
278
+ x = 150.0
279
+
280
+ # WHEN we serialize the node
281
+ node_display_class = get_node_display_class(MyNodeX)
282
+ data = node_display_class().serialize(WorkflowDisplayContext())
283
+
284
+ # THEN the display_data should include position with x and default y
285
+ display_data = data["display_data"]
286
+ assert isinstance(display_data, dict)
287
+ assert display_data["position"] == {"x": 150.0, "y": 0.0}
288
+
289
+
290
+ def test_serialize_explicit_display_data_overrides_display_class_xyz():
291
+ """Tests that BaseNodeDisplay's explicit display_data takes precedence over Display class x, y, z_index."""
292
+
293
+ # GIVEN a node with x, y, z_index in Display class
294
+ class MyNode(BaseNode):
295
+ class Display:
296
+ x = 100.0
297
+ y = 200.0
298
+ z_index = 5
299
+
300
+ # AND a BaseNodeDisplay with explicit display_data
301
+ class MyNodeDisplay(BaseNodeDisplay[MyNode]):
302
+ display_data = NodeDisplayData(
303
+ position=NodeDisplayPosition(x=300, y=400),
304
+ z_index=10,
305
+ )
306
+
307
+ # WHEN we serialize the node
308
+ data = MyNodeDisplay().serialize(WorkflowDisplayContext())
309
+
310
+ # THEN the display_data should use values from BaseNodeDisplay
311
+ display_data = data["display_data"]
312
+ assert isinstance(display_data, dict)
313
+ assert display_data["position"] == {"x": 300, "y": 400} # BaseNodeDisplay overrides
314
+ assert display_data["z_index"] == 10 # BaseNodeDisplay overrides
315
+
316
+
317
+ def test_serialize_display_class_xyz_used_as_fallback():
318
+ """Tests that Display class x, y, z_index are used when BaseNodeDisplay doesn't specify them."""
319
+
320
+ # GIVEN a node with x, y, z_index in Display class
321
+ class MyNode(BaseNode):
322
+ class Display:
323
+ x = 50.0
324
+ y = 75.0
325
+ z_index = 3
326
+
327
+ # AND a BaseNodeDisplay with only icon specified
328
+ class MyNodeDisplay(BaseNodeDisplay[MyNode]):
329
+ display_data = NodeDisplayData(
330
+ icon="vellum:icon:star",
331
+ )
332
+
333
+ # WHEN we serialize the node
334
+ data = MyNodeDisplay().serialize(WorkflowDisplayContext())
335
+
336
+ # THEN the display_data should use x, y, z from Display class as fallback
337
+ display_data = data["display_data"]
338
+ assert isinstance(display_data, dict)
339
+ assert display_data["position"] == {"x": 50.0, "y": 75.0} # Falls back to Display class
340
+ assert display_data["z_index"] == 3 # Falls back to Display class
341
+ assert display_data["icon"] == "vellum:icon:star" # From BaseNodeDisplay
@@ -6,6 +6,7 @@ from uuid import UUID
6
6
  class NodeOutputDisplay:
7
7
  id: UUID
8
8
  name: str
9
+ _is_implicit: bool = False
9
10
 
10
11
 
11
12
  @dataclass
@@ -4,7 +4,6 @@ from .code_execution_node import BaseCodeExecutionNodeDisplay
4
4
  from .conditional_node import BaseConditionalNodeDisplay
5
5
  from .error_node import BaseErrorNodeDisplay
6
6
  from .final_output_node import BaseFinalOutputNodeDisplay
7
- from .function_node import FunctionNodeDisplay
8
7
  from .guardrail_node import BaseGuardrailNodeDisplay
9
8
  from .inline_prompt_node import BaseInlinePromptNodeDisplay
10
9
  from .inline_subworkflow_node import BaseInlineSubworkflowNodeDisplay
@@ -26,7 +25,6 @@ __all__ = [
26
25
  "BaseConditionalNodeDisplay",
27
26
  "BaseErrorNodeDisplay",
28
27
  "BaseFinalOutputNodeDisplay",
29
- "FunctionNodeDisplay",
30
28
  "BaseGuardrailNodeDisplay",
31
29
  "BaseInlinePromptNodeDisplay",
32
30
  "BaseInlineSubworkflowNodeDisplay",
@@ -89,8 +89,11 @@ class BaseAdornmentNodeDisplay(BaseNodeDisplay[_BaseAdornmentNodeType], Generic[
89
89
  "Unable to serialize standalone adornment nodes. Please use adornment nodes as a decorator."
90
90
  )
91
91
 
92
- wrapped_node_display_class = get_node_display_class(wrapped_node)
93
- wrapped_node_display = wrapped_node_display_class()
92
+ # Use existing node display instance from display_context if available to preserve build() data
93
+ wrapped_node_display = display_context.node_displays.get(wrapped_node)
94
+ if not wrapped_node_display:
95
+ wrapped_node_display_class = get_node_display_class(wrapped_node)
96
+ wrapped_node_display = wrapped_node_display_class()
94
97
  additional_kwargs = get_additional_kwargs(wrapped_node_display.node_id) if get_additional_kwargs else {}
95
98
  serialized_wrapped_node = wrapped_node_display.serialize(display_context, **kwargs, **additional_kwargs)
96
99
 
@@ -119,5 +119,5 @@ class BaseCodeExecutionNodeDisplay(BaseNodeDisplay[_CodeExecutionNodeType], Gene
119
119
  "output_id": str(self.output_id) if self.output_id else str(output_display.id),
120
120
  "log_output_id": str(self.log_output_id) if self.log_output_id else str(log_output_display.id),
121
121
  },
122
- **self.serialize_generic_fields(display_context, exclude=["outputs"]),
122
+ **self.serialize_generic_fields(display_context),
123
123
  }
@@ -2,10 +2,11 @@ from uuid import UUID
2
2
  from typing import TYPE_CHECKING, Callable, Dict, Generic, List, Optional, Tuple, Type, TypeVar, Union
3
3
 
4
4
  from vellum import FunctionDefinition, PromptBlock, RichTextChildBlock, VellumVariable
5
+ from vellum.workflows import MCPServer
5
6
  from vellum.workflows.descriptors.base import BaseDescriptor
6
7
  from vellum.workflows.nodes import InlinePromptNode
7
8
  from vellum.workflows.types.core import JsonObject
8
- from vellum.workflows.types.definition import DeploymentDefinition, VellumIntegrationToolDefinition
9
+ from vellum.workflows.types.definition import DeploymentDefinition, MCPToolDefinition, VellumIntegrationToolDefinition
9
10
  from vellum.workflows.types.generics import is_workflow_class
10
11
  from vellum.workflows.utils.functions import (
11
12
  compile_function_definition,
@@ -82,6 +83,7 @@ class BaseInlinePromptNodeDisplay(BaseNodeDisplay[_InlinePromptNodeType], Generi
82
83
  [
83
84
  self._generate_function_tools(function, i, display_context)
84
85
  for i, function in enumerate(function_definitions)
86
+ if not isinstance(function, (MCPServer, MCPToolDefinition)) # we don't need to serialize MCP types
85
87
  ]
86
88
  if isinstance(function_definitions, list)
87
89
  else []
@@ -147,7 +149,13 @@ class BaseInlinePromptNodeDisplay(BaseNodeDisplay[_InlinePromptNodeType], Generi
147
149
  )
148
150
  vellum_variable_type = infer_vellum_variable_type(variable_value)
149
151
  node_inputs.append(node_input)
150
- prompt_inputs.append(VellumVariable(id=str(node_input.id), key=variable_name, type=vellum_variable_type))
152
+ prompt_inputs.append(
153
+ VellumVariable(
154
+ id=str(node_input.id),
155
+ key=variable_name,
156
+ type=vellum_variable_type,
157
+ )
158
+ )
151
159
 
152
160
  return node_inputs, prompt_inputs
153
161
 
@@ -14,7 +14,7 @@ from vellum_ee.workflows.display.nodes.utils import raise_if_descriptor
14
14
  from vellum_ee.workflows.display.nodes.vellum.utils import create_node_input
15
15
  from vellum_ee.workflows.display.types import WorkflowDisplayContext
16
16
  from vellum_ee.workflows.display.utils.exceptions import NodeValidationError
17
- from vellum_ee.workflows.display.utils.vellum import infer_vellum_variable_type
17
+ from vellum_ee.workflows.display.utils.vellum import compile_descriptor_annotation, infer_vellum_variable_type
18
18
  from vellum_ee.workflows.display.vellum import NodeInput
19
19
  from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
20
20
 
@@ -111,20 +111,23 @@ class BaseInlineSubworkflowNodeDisplay(
111
111
  for variable_name, variable_value in subworkflow_entries
112
112
  ]
113
113
  node_inputs_by_key = {node_input.key: node_input for node_input in node_inputs}
114
- workflow_inputs = [
115
- VellumVariable(
116
- id=node_inputs_by_key[descriptor.name].id,
117
- key=descriptor.name,
118
- type=infer_vellum_variable_type(descriptor),
119
- required=descriptor.instance is undefined,
120
- default=(
121
- primitive_to_vellum_value(descriptor.instance).dict()
122
- if descriptor.instance is not undefined
123
- else None
124
- ),
114
+ workflow_inputs = []
115
+ for descriptor in subworkflow_inputs_class:
116
+ schema = compile_descriptor_annotation(descriptor)
117
+ workflow_inputs.append(
118
+ VellumVariable(
119
+ id=node_inputs_by_key[descriptor.name].id,
120
+ key=descriptor.name,
121
+ type=infer_vellum_variable_type(descriptor),
122
+ required=descriptor.instance is undefined,
123
+ default=(
124
+ primitive_to_vellum_value(descriptor.instance).dict()
125
+ if descriptor.instance is not undefined
126
+ else None
127
+ ),
128
+ schema=schema,
129
+ )
125
130
  )
126
- for descriptor in subworkflow_inputs_class
127
- ]
128
131
 
129
132
  return node_inputs, workflow_inputs
130
133
 
@@ -17,6 +17,7 @@ _MapNodeType = TypeVar("_MapNodeType", bound=MapNode)
17
17
 
18
18
  class BaseMapNodeDisplay(BaseAdornmentNodeDisplay[_MapNodeType], Generic[_MapNodeType]):
19
19
  __serializable_inputs__ = {MapNode.items} # type: ignore[misc]
20
+ __unserializable_attributes__ = {MapNode.subworkflow, MapNode.max_concurrency} # type: ignore[misc]
20
21
 
21
22
  def serialize(
22
23
  self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **_kwargs
@@ -89,6 +90,7 @@ class BaseMapNodeDisplay(BaseAdornmentNodeDisplay[_MapNodeType], Generic[_MapNod
89
90
  "item_input_id": item_workflow_input_id,
90
91
  "index_input_id": index_workflow_input_id,
91
92
  },
93
+ "attributes": self._serialize_attributes(display_context),
92
94
  **self.serialize_generic_fields(display_context, exclude=["outputs"]),
93
95
  }
94
96
 
@@ -1,20 +1,35 @@
1
- from typing import Any, ClassVar, Dict, Generic, TypeVar, Union
1
+ import warnings
2
+ from typing import Any, ClassVar, Dict, Generic, Optional, TypeVar, Union, cast
2
3
 
3
4
  from vellum.workflows.nodes import NoteNode
4
5
  from vellum.workflows.types.core import JsonObject
5
6
  from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
7
+ from vellum_ee.workflows.display.nodes.utils import raise_if_descriptor
6
8
  from vellum_ee.workflows.display.types import WorkflowDisplayContext
7
9
 
8
10
  _NoteNodeType = TypeVar("_NoteNodeType", bound=NoteNode)
9
11
 
10
12
 
11
13
  class BaseNoteNodeDisplay(BaseNodeDisplay[_NoteNodeType], Generic[_NoteNodeType]):
14
+ # Deprecated: Define text and style on the node class instead. Will be removed in v2.0.0.
12
15
  text: ClassVar[str] = ""
13
16
  style: ClassVar[Union[Dict[str, Any], None]] = None
14
17
 
15
18
  def serialize(self, display_context: WorkflowDisplayContext, **kwargs: Any) -> JsonObject:
16
19
  del kwargs # Unused parameters
17
20
  node_id = self.node_id
21
+ node = self._node
22
+
23
+ text = raise_if_descriptor(node.text) or ""
24
+ style = cast(Optional[Dict[str, Any]], raise_if_descriptor(node.style)) or None
25
+
26
+ if "text" in self.__class__.__dict__ or "style" in self.__class__.__dict__:
27
+ warnings.warn(
28
+ "Defining 'text' and 'style' on the display class is deprecated. "
29
+ "Define them on the node class instead. Will be removed in v2.0.0.",
30
+ DeprecationWarning,
31
+ stacklevel=2,
32
+ )
18
33
 
19
34
  return {
20
35
  "id": str(node_id),
@@ -22,8 +37,8 @@ class BaseNoteNodeDisplay(BaseNodeDisplay[_NoteNodeType], Generic[_NoteNodeType]
22
37
  "inputs": [],
23
38
  "data": {
24
39
  "label": self.label,
25
- "text": self.text,
26
- "style": self.style,
40
+ "text": text,
41
+ "style": style,
27
42
  },
28
43
  **self.serialize_generic_fields(display_context, exclude=["outputs"]),
29
44
  }
@@ -1,11 +1,13 @@
1
1
  from uuid import UUID
2
2
  from typing import Generic, Optional, TypeVar
3
3
 
4
+ from vellum.client import Vellum as VellumClient
5
+ from vellum.utils.uuid import is_valid_uuid
4
6
  from vellum.workflows.inputs.base import BaseInputs
5
7
  from vellum.workflows.nodes import SubworkflowDeploymentNode
6
8
  from vellum.workflows.types.core import JsonObject
7
- from vellum.workflows.utils.uuids import uuid4_from_hash
8
9
  from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
10
+ from vellum_ee.workflows.display.nodes.types import NodeOutputDisplay
9
11
  from vellum_ee.workflows.display.nodes.utils import raise_if_descriptor
10
12
  from vellum_ee.workflows.display.nodes.vellum.utils import create_node_input
11
13
  from vellum_ee.workflows.display.types import WorkflowDisplayContext
@@ -18,6 +20,37 @@ class BaseSubworkflowDeploymentNodeDisplay(
18
20
  ):
19
21
  __serializable_inputs__ = {SubworkflowDeploymentNode.subworkflow_inputs}
20
22
 
23
+ _deployment_id: Optional[str] = None
24
+ _release_tag: Optional[str] = None
25
+
26
+ def build(self, client: VellumClient) -> None:
27
+ node = self._node
28
+ deployment_descriptor_id = str(raise_if_descriptor(node.deployment))
29
+ self._release_tag = raise_if_descriptor(node.release_tag)
30
+
31
+ deployment_release = client.workflow_deployments.retrieve_workflow_deployment_release(
32
+ id=deployment_descriptor_id,
33
+ release_id_or_release_tag=self._release_tag,
34
+ )
35
+ self._deployment_id = str(deployment_release.deployment.id)
36
+ output_variables_by_key = {var.key: var for var in deployment_release.workflow_version.output_variables}
37
+
38
+ for output in node.Outputs:
39
+ original_output_display = self.output_display.get(output)
40
+ if original_output_display and not original_output_display._is_implicit:
41
+ continue
42
+
43
+ output_variable = output_variables_by_key.get(output.name)
44
+ if not output_variable or not is_valid_uuid(output_variable.id):
45
+ continue
46
+
47
+ output_id = UUID(output_variable.id)
48
+ self.output_display[output] = NodeOutputDisplay(
49
+ id=output_id,
50
+ name=output.name,
51
+ )
52
+ self._node.__output_ids__[output.name] = output_id
53
+
21
54
  def serialize(
22
55
  self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **_kwargs
23
56
  ) -> JsonObject:
@@ -45,16 +78,6 @@ class BaseSubworkflowDeploymentNodeDisplay(
45
78
  for variable_name, variable_value in input_items
46
79
  ]
47
80
 
48
- deployment_descriptor_id = str(raise_if_descriptor(node.deployment))
49
- try:
50
- deployment = display_context.client.workflow_deployments.retrieve(
51
- id=deployment_descriptor_id,
52
- )
53
- deployment_id = str(deployment.id)
54
- except Exception as e:
55
- display_context.add_error(e)
56
- deployment_id = str(uuid4_from_hash(deployment_descriptor_id))
57
-
58
81
  return {
59
82
  "id": str(node_id),
60
83
  "type": "SUBWORKFLOW",
@@ -65,8 +88,8 @@ class BaseSubworkflowDeploymentNodeDisplay(
65
88
  "source_handle_id": str(self.get_source_handle_id(display_context.port_displays)),
66
89
  "target_handle_id": str(self.get_target_handle_id()),
67
90
  "variant": "DEPLOYMENT",
68
- "workflow_deployment_id": deployment_id,
69
- "release_tag": raise_if_descriptor(node.release_tag),
91
+ "workflow_deployment_id": self._deployment_id,
92
+ "release_tag": self._release_tag,
70
93
  },
71
- **self.serialize_generic_fields(display_context, exclude=["outputs"]),
94
+ **self.serialize_generic_fields(display_context),
72
95
  }
@@ -6,6 +6,7 @@ from vellum.client.core.api_error import ApiError
6
6
  from vellum.workflows.environment import EnvironmentVariables
7
7
  from vellum.workflows.nodes.displayable.code_execution_node.node import CodeExecutionNode
8
8
  from vellum.workflows.references.vellum_secret import VellumSecretReference
9
+ from vellum.workflows.state.base import BaseState
9
10
  from vellum.workflows.workflows.base import BaseWorkflow
10
11
  from vellum_ee.workflows.display.nodes.vellum.code_execution_node import BaseCodeExecutionNodeDisplay
11
12
  from vellum_ee.workflows.display.utils.exceptions import NodeValidationError
@@ -217,10 +218,13 @@ def test_serialize_node__with_environment_variable_references():
217
218
  assert other_config_input["value"]["combinator"] == "OR"
218
219
  assert len(other_config_input["value"]["rules"]) == 1
219
220
  assert other_config_input["value"]["rules"][0]["type"] == "CONSTANT_VALUE"
220
- # The nested dict should contain the environment variable reference
221
221
  nested_data = other_config_input["value"]["rules"][0]["data"]
222
222
  assert nested_data["type"] == "JSON"
223
- assert "nested_key" in nested_data["value"]
223
+ assert nested_data["value"]["type"] == "DICTIONARY_REFERENCE"
224
+ assert len(nested_data["value"]["entries"]) == 1
225
+ assert nested_data["value"]["entries"][0]["key"] == "nested_key"
226
+ assert nested_data["value"]["entries"][0]["value"]["type"] == "ENVIRONMENT_VARIABLE"
227
+ assert nested_data["value"]["entries"][0]["value"]["environment_variable"] == "NESTED_KEY"
224
228
 
225
229
 
226
230
  def test_serialize_node__with_non_exist_code_input_path_with_dry_run():
@@ -303,6 +307,22 @@ def test_serialize_node__with_non_exist_code_input_path_with_dry_run():
303
307
  },
304
308
  "ports": [{"id": "dc9edb2e-4392-4a2c-ab92-cc1b9c0cbd53", "name": "default", "type": "DEFAULT"}],
305
309
  "trigger": {"id": "66e7ef63-518b-40e7-911a-e38e8bcaec81", "merge_behavior": "AWAIT_ANY"},
310
+ "outputs": [
311
+ {
312
+ "id": "98cae9b9-45cc-4897-a0f5-df250b56c00d",
313
+ "name": "result",
314
+ "schema": {"type": "string"},
315
+ "type": "STRING",
316
+ "value": None,
317
+ },
318
+ {
319
+ "id": "66c06c97-a9d1-4abf-840f-3f6c29709612",
320
+ "name": "log",
321
+ "schema": {"type": "string"},
322
+ "type": "STRING",
323
+ "value": None,
324
+ },
325
+ ],
306
326
  },
307
327
  ],
308
328
  "edges": [
@@ -326,3 +346,43 @@ def test_serialize_node__with_non_exist_code_input_path_with_dry_run():
326
346
  "state_variables": [],
327
347
  "output_variables": [],
328
348
  }
349
+
350
+
351
+ def test_serialize_node__with_custom_output_type():
352
+ # GIVEN a code node with a custom output type
353
+ class MyNode(CodeExecutionNode[BaseState, dict[str, int]]):
354
+ code = """\
355
+ return {
356
+ "hello": 1,
357
+ }
358
+ """
359
+
360
+ # AND a workflow with the code node
361
+ class Workflow(BaseWorkflow):
362
+ graph = MyNode
363
+
364
+ # WHEN we serialize the workflow
365
+ workflow_display = get_workflow_display(workflow_class=Workflow)
366
+ serialized_workflow: dict = workflow_display.serialize()
367
+
368
+ # THEN the node's outputs should serialize correctly
369
+ my_code_execution_node = next(
370
+ node for node in serialized_workflow["workflow_raw_data"]["nodes"] if node["type"] == "CODE_EXECUTION"
371
+ )
372
+ assert my_code_execution_node["outputs"] == [
373
+ {
374
+ "id": "01de8e8b-5e0e-4344-93b0-e002bbaed840",
375
+ "name": "result",
376
+ "value": None,
377
+ "type": "JSON",
378
+ "schema": {"type": "object", "additionalProperties": {"type": "integer"}},
379
+ },
380
+ {
381
+ "id": "64bf62e0-adc7-48cc-b689-8bf9b7e4eeef",
382
+ "name": "log",
383
+ "value": None,
384
+ "type": "STRING",
385
+ "schema": {"type": "string"},
386
+ },
387
+ ]
388
+ assert my_code_execution_node["data"]["output_type"] == "JSON"