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
@@ -5,6 +5,10 @@ from uuid import UUID
5
5
  from deepdiff import DeepDiff
6
6
 
7
7
  from vellum.client.core.pydantic_utilities import UniversalBaseModel
8
+ from vellum.client.types import (
9
+ NodeExecutionFulfilledEvent as ClientNodeExecutionFulfilledEvent,
10
+ WorkflowExecutionFulfilledEvent as ClientWorkflowExecutionFulfilledEvent,
11
+ )
8
12
  from vellum.workflows.constants import undefined
9
13
  from vellum.workflows.errors.types import WorkflowError, WorkflowErrorCode
10
14
  from vellum.workflows.events.exception_handling import stream_initialization_exception
@@ -13,6 +17,8 @@ from vellum.workflows.events.node import (
13
17
  NodeExecutionFulfilledEvent,
14
18
  NodeExecutionInitiatedBody,
15
19
  NodeExecutionInitiatedEvent,
20
+ NodeExecutionLogBody,
21
+ NodeExecutionLogEvent,
16
22
  NodeExecutionStreamingBody,
17
23
  NodeExecutionStreamingEvent,
18
24
  )
@@ -37,6 +43,7 @@ from vellum.workflows.state.base import BaseState
37
43
  from vellum.workflows.types.core import VellumSecret
38
44
  from vellum.workflows.utils.uuids import uuid4_from_hash
39
45
  from vellum.workflows.workflows.base import BaseWorkflow
46
+ from vellum.workflows.workflows.event_filters import all_workflow_event_filter
40
47
 
41
48
 
42
49
  class MockInputs(BaseInputs):
@@ -471,6 +478,41 @@ mock_node_uuid = str(MockNode.__id__)
471
478
  "links": None,
472
479
  },
473
480
  ),
481
+ (
482
+ NodeExecutionLogEvent(
483
+ id=UUID("123e4567-e89b-12d3-a456-426614174000"),
484
+ timestamp=datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
485
+ trace_id=UUID("123e4567-e89b-12d3-a456-426614174000"),
486
+ span_id=UUID("123e4567-e89b-12d3-a456-426614174000"),
487
+ body=NodeExecutionLogBody(
488
+ node_definition=MockNode,
489
+ attributes={"foo": "bar"},
490
+ severity="INFO",
491
+ message="Test log message",
492
+ ),
493
+ ),
494
+ {
495
+ "id": "123e4567-e89b-12d3-a456-426614174000",
496
+ "api_version": "2024-10-25",
497
+ "timestamp": "2024-01-01T12:00:00Z",
498
+ "trace_id": "123e4567-e89b-12d3-a456-426614174000",
499
+ "span_id": "123e4567-e89b-12d3-a456-426614174000",
500
+ "name": "node.execution.log",
501
+ "body": {
502
+ "node_definition": {
503
+ "id": mock_node_uuid,
504
+ "name": "MockNode",
505
+ "module": module_root + ["events", "tests", "test_event"],
506
+ "exclude_from_monitoring": False,
507
+ },
508
+ "attributes": {"foo": "bar"},
509
+ "severity": "INFO",
510
+ "message": "Test log message",
511
+ },
512
+ "parent": None,
513
+ "links": None,
514
+ },
515
+ ),
474
516
  ],
475
517
  ids=[
476
518
  "workflow.execution.initiated",
@@ -483,6 +525,7 @@ mock_node_uuid = str(MockNode.__id__)
483
525
  "node.execution.fulfilled",
484
526
  "fulfilled_node_with_undefined_outputs",
485
527
  "mocked_node",
528
+ "node.execution.log",
486
529
  ],
487
530
  )
488
531
  def test_event_serialization(event, expected_json):
@@ -573,3 +616,83 @@ def test_node_execution_initiated_event_includes_exclude_from_monitoring():
573
616
  serialized = else_node_event.model_dump(mode="json")
574
617
  assert "exclude_from_monitoring" in serialized["body"]["node_definition"]
575
618
  assert serialized["body"]["node_definition"]["exclude_from_monitoring"] is True
619
+
620
+
621
+ def test_event_max_size__outputs_redacted_when_exceeds_limit():
622
+ """
623
+ Tests that event outputs are redacted when serialized size exceeds event_max_size.
624
+ """
625
+
626
+ # GIVEN a workflow with a node
627
+ workflow = MockWorkflow()
628
+
629
+ # WHEN the workflow is streamed with a very small event_max_size
630
+ events = list(
631
+ workflow.stream(inputs=MockInputs(foo="bar"), event_filter=all_workflow_event_filter, event_max_size=10)
632
+ )
633
+
634
+ # THEN both node and workflow fulfilled events should have redacted outputs
635
+ node_fulfilled_events = [e for e in events if e.name == "node.execution.fulfilled"]
636
+ workflow_fulfilled_events = [e for e in events if e.name == "workflow.execution.fulfilled"]
637
+
638
+ assert len(node_fulfilled_events) > 0, "Expected at least one node fulfilled event"
639
+ assert len(workflow_fulfilled_events) == 1, "Expected exactly one workflow fulfilled event"
640
+
641
+ # AND the node fulfilled event outputs should be empty
642
+ node_serialized = node_fulfilled_events[0].model_dump(mode="json")
643
+ assert node_serialized["body"]["outputs"] == {}
644
+
645
+ # AND the serialized node event should be parseable by the client library
646
+ ClientNodeExecutionFulfilledEvent.model_validate(node_serialized)
647
+
648
+ # AND the workflow fulfilled event outputs should be empty
649
+ workflow_serialized = workflow_fulfilled_events[0].model_dump(mode="json")
650
+ assert workflow_serialized["body"]["outputs"] == {}
651
+
652
+ # AND the serialized workflow event should be parseable by the client library
653
+ ClientWorkflowExecutionFulfilledEvent.model_validate(workflow_serialized)
654
+
655
+
656
+ def test_event_max_size__outputs_preserved_when_under_limit():
657
+ """
658
+ Tests that event outputs are preserved when serialized size is under event_max_size.
659
+ """
660
+
661
+ # GIVEN a workflow with a node that produces outputs
662
+ class OutputNode(BaseNode):
663
+ class Outputs(BaseNode.Outputs):
664
+ result: str = "hello world"
665
+
666
+ class OutputWorkflow(BaseWorkflow[MockInputs, BaseState]):
667
+ graph = OutputNode
668
+
669
+ class Outputs(BaseWorkflow.Outputs):
670
+ final = OutputNode.Outputs.result
671
+
672
+ workflow = OutputWorkflow()
673
+
674
+ # WHEN the workflow is streamed with a large event_max_size
675
+ events = list(
676
+ workflow.stream(inputs=MockInputs(foo="bar"), event_filter=all_workflow_event_filter, event_max_size=100000)
677
+ )
678
+
679
+ # THEN both node and workflow fulfilled events should have their outputs preserved
680
+ node_fulfilled_events = [e for e in events if e.name == "node.execution.fulfilled"]
681
+ workflow_fulfilled_events = [e for e in events if e.name == "workflow.execution.fulfilled"]
682
+
683
+ assert len(node_fulfilled_events) > 0, "Expected at least one node fulfilled event"
684
+ assert len(workflow_fulfilled_events) == 1, "Expected exactly one workflow fulfilled event"
685
+
686
+ # AND the node fulfilled event outputs should contain the result
687
+ node_serialized = node_fulfilled_events[0].model_dump(mode="json")
688
+ assert node_serialized["body"]["outputs"]["result"] == "hello world"
689
+
690
+ # AND the serialized node event should be parseable by the client library
691
+ ClientNodeExecutionFulfilledEvent.model_validate(node_serialized)
692
+
693
+ # AND the workflow fulfilled event outputs should contain the final output
694
+ workflow_serialized = workflow_fulfilled_events[0].model_dump(mode="json")
695
+ assert workflow_serialized["body"]["outputs"]["final"] == "hello world"
696
+
697
+ # AND the serialized workflow event should be parseable by the client library
698
+ ClientWorkflowExecutionFulfilledEvent.model_validate(workflow_serialized)
@@ -3,7 +3,7 @@ import json
3
3
  from uuid import UUID, uuid4
4
4
  from typing import Annotated, Any, List, Literal, Optional, Union, get_args
5
5
 
6
- from pydantic import Field, GetCoreSchemaHandler, Tag, ValidationInfo
6
+ from pydantic import Field, GetCoreSchemaHandler, PrivateAttr, Tag, ValidationInfo
7
7
  from pydantic_core import CoreSchema, core_schema
8
8
 
9
9
  from vellum.client.core.pydantic_utilities import UniversalBaseModel
@@ -186,3 +186,4 @@ class BaseEvent(UniversalBaseModel):
186
186
  span_id: UUID
187
187
  parent: Optional[ParentContext] = None
188
188
  links: Optional[List[SpanLink]] = None
189
+ _event_max_size: Optional[int] = PrivateAttr(default=None)
@@ -1,11 +1,13 @@
1
+ import json
1
2
  import logging
2
3
  from uuid import UUID
3
4
  from typing import TYPE_CHECKING, Any, Dict, Generic, Iterable, Literal, Optional, Type, Union, cast
4
5
  from typing_extensions import TypeGuard
5
6
 
6
- from pydantic import SerializationInfo, field_serializer, field_validator
7
+ from pydantic import Field, SerializationInfo, field_serializer, field_validator, model_serializer
7
8
 
8
9
  from vellum.client.core.pydantic_utilities import UniversalBaseModel
10
+ from vellum.utils.json_encoder import VellumJsonEncoder
9
11
  from vellum.workflows.errors import WorkflowError
10
12
  from vellum.workflows.outputs.base import BaseOutput
11
13
  from vellum.workflows.references import ExternalInputReference
@@ -16,6 +18,7 @@ from ..triggers import BaseTrigger
16
18
  from .node import (
17
19
  NodeExecutionFulfilledEvent,
18
20
  NodeExecutionInitiatedEvent,
21
+ NodeExecutionLogEvent,
19
22
  NodeExecutionPausedEvent,
20
23
  NodeExecutionRejectedEvent,
21
24
  NodeExecutionResumedEvent,
@@ -111,9 +114,17 @@ class WorkflowExecutionInitiatedBody(_BaseWorkflowExecutionBody, Generic[InputsT
111
114
 
112
115
  trigger: Optional[Type[BaseTrigger]] = None
113
116
 
117
+ # Raw inputs from trigger event data, used to include trigger attributes in serialized inputs.
118
+ # This field is excluded from serialization and only used to merge into the inputs field.
119
+ raw_inputs: Optional[Dict[str, Any]] = Field(default=None, exclude=True)
120
+
114
121
  @field_serializer("inputs")
115
122
  def serialize_inputs(self, inputs: InputsType, _info: Any) -> Dict[str, Any]:
116
- return default_serializer(inputs)
123
+ serialized = default_serializer(inputs)
124
+ # Merge raw_inputs (trigger event data) with serialized inputs
125
+ if self.raw_inputs:
126
+ return {**self.raw_inputs, **serialized}
127
+ return serialized
117
128
 
118
129
  @field_serializer("initial_state")
119
130
  def serialize_initial_state(self, initial_state: Optional[StateType], _info: Any) -> Optional[Dict[str, Any]]:
@@ -203,6 +214,20 @@ class WorkflowExecutionFulfilledEvent(_BaseWorkflowEvent, Generic[OutputsType, S
203
214
  WorkflowExecutionFulfilledBody[OutputsType, StateType], _serialize_body_with_enricher(self, body, info)
204
215
  )
205
216
 
217
+ @model_serializer(mode="plain", when_used="json")
218
+ def serialize_model(self, info: SerializationInfo) -> Dict[str, Any]:
219
+ serialized = super().serialize_model(info)
220
+
221
+ if (
222
+ self._event_max_size is not None
223
+ and len(json.dumps(serialized, cls=VellumJsonEncoder)) > self._event_max_size
224
+ and "body" in serialized
225
+ and isinstance(serialized["body"], dict)
226
+ ):
227
+ serialized["body"]["outputs"] = {}
228
+
229
+ return serialized
230
+
206
231
 
207
232
  class WorkflowExecutionRejectedBody(_BaseWorkflowExecutionBody):
208
233
  error: WorkflowError
@@ -287,6 +312,7 @@ GenericWorkflowEvent = Union[
287
312
  NodeExecutionRejectedEvent,
288
313
  NodeExecutionPausedEvent,
289
314
  NodeExecutionResumedEvent,
315
+ NodeExecutionLogEvent,
290
316
  ]
291
317
 
292
318
  WorkflowEvent = Union[
@@ -38,4 +38,7 @@ class AddExpression(BaseDescriptor[Any], Generic[LHS, RHS]):
38
38
  if not has_add(lhs):
39
39
  raise InvalidExpressionException(f"'{lhs.__class__.__name__}' must support the '+' operator")
40
40
 
41
+ if isinstance(lhs, list) and not isinstance(rhs, list):
42
+ return lhs + [rhs]
43
+
41
44
  return lhs + rhs
@@ -70,3 +70,27 @@ def test_add_expression_types():
70
70
  expression = AddExpression(lhs=5, rhs=3)
71
71
 
72
72
  assert expression.types == (object,)
73
+
74
+
75
+ def test_add_expression_list_plus_non_list():
76
+ """
77
+ Tests that AddExpression correctly handles list + non-list by wrapping non-list in a list.
78
+ This allows State.chat_history + ChatMessage(...) to work.
79
+ """
80
+
81
+ state = TestState()
82
+
83
+ # GIVEN a list and a non-list value
84
+ my_list = [1, 2, 3]
85
+ single_item = 4
86
+
87
+ # WHEN we create an AddExpression and resolve it
88
+ expression = AddExpression(lhs=my_list, rhs=single_item)
89
+ result = expression.resolve(state)
90
+
91
+ # THEN the result should be the list with the item appended
92
+ assert result == [1, 2, 3, 4]
93
+ assert isinstance(result, list)
94
+
95
+ # AND the original list should be unchanged
96
+ assert my_list == [1, 2, 3]
@@ -81,12 +81,16 @@ class Graph:
81
81
  entrypoints = set()
82
82
  edges = OrderedSet[Edge]()
83
83
  terminals = set()
84
+ trigger_edges: List[TriggerEdge] = []
84
85
 
85
86
  for target in targets:
86
87
  if isinstance(target, Graph):
87
88
  entrypoints.update(target._entrypoints)
88
89
  edges.update(target._edges)
89
90
  terminals.update(target._terminals)
91
+ for trigger_edge in target._trigger_edges:
92
+ if trigger_edge not in trigger_edges:
93
+ trigger_edges.append(trigger_edge)
90
94
  elif hasattr(target, "Ports"):
91
95
  entrypoints.update({port for port in target.Ports})
92
96
  terminals.update({port for port in target.Ports})
@@ -95,11 +99,14 @@ class Graph:
95
99
  entrypoints.update({target})
96
100
  terminals.update({target})
97
101
 
98
- return Graph(entrypoints=entrypoints, edges=list(edges), terminals=terminals)
102
+ return Graph(entrypoints=entrypoints, edges=list(edges), terminals=terminals, trigger_edges=trigger_edges)
99
103
 
100
104
  @staticmethod
101
105
  def from_edge(edge: Edge) -> "Graph":
102
- return Graph(entrypoints={edge.from_port}, edges=[edge], terminals={port for port in edge.to_node.Ports})
106
+ terminals: Set[Union["Port", "NoPortsNode"]] = {port for port in edge.to_node.Ports}
107
+ if not terminals:
108
+ terminals = {NoPortsNode(edge.to_node)}
109
+ return Graph(entrypoints={edge.from_port}, edges=[edge], terminals=terminals)
103
110
 
104
111
  @staticmethod
105
112
  def from_trigger_edge(edge: TriggerEdge) -> "Graph":
@@ -166,6 +173,7 @@ class Graph:
166
173
 
167
174
  def __rshift__(self, other: GraphTarget) -> "Graph":
168
175
  # Check for trigger target (class-level only)
176
+ from vellum.workflows.nodes.bases.base import BaseNode
169
177
  from vellum.workflows.triggers.base import BaseTrigger
170
178
 
171
179
  if isinstance(other, type) and issubclass(other, BaseTrigger):
@@ -197,13 +205,16 @@ class Graph:
197
205
  midgraph = final_output_node >> set(elem.entrypoints)
198
206
  self._extend_edges(midgraph.edges)
199
207
  self._extend_edges(elem.edges)
208
+ self._extend_trigger_edges(elem._trigger_edges)
200
209
  for other_terminal in elem._terminals:
201
210
  new_terminals.add(other_terminal)
202
211
  elif hasattr(elem, "Ports"):
203
212
  midgraph = final_output_node >> elem
204
213
  self._extend_edges(midgraph.edges)
205
- for other_terminal in elem.Ports:
206
- new_terminals.add(other_terminal)
214
+ other_ports = {port for port in elem.Ports}
215
+ if isinstance(elem, type) and issubclass(elem, BaseNode) and not other_ports:
216
+ other_ports = {NoPortsNode(elem)}
217
+ new_terminals.update(other_ports)
207
218
  else:
208
219
  # elem is a Port
209
220
  midgraph = final_output_node >> elem
@@ -219,6 +230,7 @@ class Graph:
219
230
  midgraph = final_output_node >> set(other.entrypoints)
220
231
  self._extend_edges(midgraph.edges)
221
232
  self._extend_edges(other.edges)
233
+ self._extend_trigger_edges(other._trigger_edges)
222
234
  self._terminals = other._terminals
223
235
  return self
224
236
 
@@ -228,7 +240,11 @@ class Graph:
228
240
  continue
229
241
  subgraph = final_output_node >> other
230
242
  self._extend_edges(subgraph.edges)
231
- self._terminals = {port for port in other.Ports}
243
+
244
+ other_ports = {port for port in other.Ports}
245
+ if isinstance(other, type) and issubclass(other, BaseNode) and not other_ports:
246
+ other_ports = {NoPortsNode(other)}
247
+ self._terminals = other_ports
232
248
  return self
233
249
 
234
250
  # other is a Port
@@ -298,6 +314,11 @@ class Graph:
298
314
  if edge not in self._edges:
299
315
  self._edges.append(edge)
300
316
 
317
+ def _extend_trigger_edges(self, trigger_edges: List[TriggerEdge]) -> None:
318
+ for trigger_edge in trigger_edges:
319
+ if trigger_edge not in self._trigger_edges:
320
+ self._trigger_edges.append(trigger_edge)
321
+
301
322
  def __str__(self) -> str:
302
323
  """
303
324
  Return a visual ASCII representation of the graph showing the flow structure.
@@ -1,10 +1,14 @@
1
1
  import pytest
2
+ from typing import Type
2
3
 
3
4
  from vellum.workflows.edges.edge import Edge
4
- from vellum.workflows.graph.graph import Graph
5
+ from vellum.workflows.graph.graph import Graph, NoPortsNode
5
6
  from vellum.workflows.nodes.bases.base import BaseNode
7
+ from vellum.workflows.nodes.displayable.final_output_node import FinalOutputNode
6
8
  from vellum.workflows.ports.port import Port
9
+ from vellum.workflows.state.base import BaseState
7
10
  from vellum.workflows.triggers import ManualTrigger
11
+ from vellum.workflows.triggers.schedule import ScheduleTrigger
8
12
 
9
13
 
10
14
  def test_graph__empty():
@@ -784,3 +788,226 @@ def test_graph__trigger_then_graph_then_node():
784
788
  # AND the graph has both nodes
785
789
  nodes = list(graph.nodes)
786
790
  assert len(nodes) == 2
791
+
792
+
793
+ def test_graph__set_of_trigger_graphs_preserves_trigger_edges():
794
+ """Test that combining graphs with triggers via a set preserves trigger edges.
795
+
796
+ This tests the fix for Graph.from_set() not propagating _trigger_edges.
797
+ """
798
+
799
+ # GIVEN a custom scheduled trigger
800
+ class MyScheduledTrigger(ScheduleTrigger):
801
+ class Config:
802
+ cron = "0 9 * * *"
803
+
804
+ # AND two trigger-initiated graphs
805
+ class MyFirstNode(BaseNode):
806
+ pass
807
+
808
+ class MySecondNode(BaseNode):
809
+ pass
810
+
811
+ trigger_graph_a = MyScheduledTrigger >> MyFirstNode
812
+ trigger_graph_b = MyScheduledTrigger >> MySecondNode
813
+
814
+ # WHEN we combine them in a set
815
+ combined_graph = Graph.from_set({trigger_graph_a, trigger_graph_b})
816
+
817
+ # THEN the combined graph has both trigger edges
818
+ trigger_edges = list(combined_graph.trigger_edges)
819
+ assert len(trigger_edges) == 2
820
+
821
+ # AND both edges point to the correct nodes
822
+ target_nodes = {edge.to_node for edge in trigger_edges}
823
+ assert target_nodes == {MyFirstNode, MySecondNode}
824
+
825
+ # AND the graph exposes the trigger
826
+ triggers = list(combined_graph.triggers)
827
+ assert len(triggers) == 1
828
+ assert triggers[0] == MyScheduledTrigger
829
+
830
+
831
+ def test_graph__set_of_trigger_graphs_to_node_preserves_trigger_edges():
832
+ # GIVEN a custom scheduled trigger
833
+ class MyScheduledTrigger(ScheduleTrigger):
834
+ class Config:
835
+ cron = "0 9 * * *"
836
+
837
+ # AND two trigger-initiated graphs
838
+ class MyFirstNode(BaseNode):
839
+ pass
840
+
841
+ class MySecondNode(BaseNode):
842
+ pass
843
+
844
+ class MyThirdNode(BaseNode):
845
+ pass
846
+
847
+ trigger_graph_a = MyScheduledTrigger >> MyFirstNode
848
+ trigger_graph_b = MyScheduledTrigger >> MySecondNode
849
+
850
+ # WHEN we combine them in a set and connect to another node
851
+ combined_graph = {trigger_graph_a, trigger_graph_b} >> MyThirdNode
852
+
853
+ # THEN the combined graph has both trigger edges
854
+ trigger_edges = list(combined_graph.trigger_edges)
855
+ assert len(trigger_edges) == 2
856
+
857
+ # AND both trigger edges point to the correct initial nodes
858
+ trigger_target_nodes = {edge.to_node for edge in trigger_edges}
859
+ assert trigger_target_nodes == {MyFirstNode, MySecondNode}
860
+
861
+ # AND the graph has regular edges to the third node
862
+ regular_edges = list(combined_graph.edges)
863
+ assert len(regular_edges) == 2
864
+ assert all(edge.to_node == MyThirdNode for edge in regular_edges)
865
+
866
+ # AND the graph has all three nodes
867
+ nodes = list(combined_graph.nodes)
868
+ assert len(nodes) == 3
869
+ assert set(nodes) == {MyFirstNode, MySecondNode, MyThirdNode}
870
+
871
+
872
+ def test_graph__graph_rshift_graph_preserves_trigger_edges():
873
+ """Test that Graph >> Graph preserves trigger edges from the right-hand graph."""
874
+
875
+ # GIVEN a regular graph and a trigger-initiated graph
876
+ class StartNode(BaseNode):
877
+ pass
878
+
879
+ class TriggerNode(BaseNode):
880
+ pass
881
+
882
+ class EndNode(BaseNode):
883
+ pass
884
+
885
+ regular_graph = StartNode >> TriggerNode
886
+ trigger_graph = ManualTrigger >> EndNode
887
+
888
+ # WHEN we combine them with >>
889
+ combined = regular_graph >> trigger_graph
890
+
891
+ # THEN the combined graph has the trigger edge
892
+ trigger_edges = list(combined.trigger_edges)
893
+ assert len(trigger_edges) == 1
894
+ assert trigger_edges[0].to_node == EndNode
895
+
896
+ # AND the graph exposes the trigger
897
+ triggers = list(combined.triggers)
898
+ assert len(triggers) == 1
899
+
900
+
901
+ def test_graph__graph_rshift_set_of_trigger_graphs_preserves_trigger_edges():
902
+ """Test that Graph >> {TriggerGraph1, TriggerGraph2} preserves trigger edges."""
903
+
904
+ # GIVEN a regular graph and two trigger-initiated graphs
905
+ class StartNode(BaseNode):
906
+ pass
907
+
908
+ class TriggerNodeA(BaseNode):
909
+ pass
910
+
911
+ class TriggerNodeB(BaseNode):
912
+ pass
913
+
914
+ regular_graph = Graph.from_node(StartNode)
915
+ trigger_graph_a = ManualTrigger >> TriggerNodeA
916
+ trigger_graph_b = ManualTrigger >> TriggerNodeB
917
+
918
+ # WHEN we combine them with >> and a set
919
+ combined = regular_graph >> {trigger_graph_a, trigger_graph_b}
920
+
921
+ # THEN the combined graph has both trigger edges
922
+ trigger_edges = list(combined.trigger_edges)
923
+ assert len(trigger_edges) == 2
924
+
925
+ # AND both trigger edges point to the correct nodes
926
+ trigger_target_nodes = {edge.to_node for edge in trigger_edges}
927
+ assert trigger_target_nodes == {TriggerNodeA, TriggerNodeB}
928
+
929
+
930
+ class TestFinalOutputNoPortsNode:
931
+ """Test that we preserve final output nodes as NoPortsNode when constructing a Graph."""
932
+
933
+ def validate_graph(self, graph: Graph, final_output_node: Type[FinalOutputNode]):
934
+ assert len(list(graph._terminals)) == 1
935
+ node = list(graph._terminals)[0]
936
+ assert isinstance(node, NoPortsNode)
937
+ assert node.node_class == final_output_node
938
+
939
+ def test_from_edge(self):
940
+ # GIVEN
941
+ class StartNode(BaseNode[BaseState]):
942
+ pass
943
+
944
+ class MyFinalOutput(FinalOutputNode[BaseState, str]):
945
+ pass
946
+
947
+ # WHEN
948
+ edge = Edge(from_port=StartNode.Ports.default, to_node=MyFinalOutput)
949
+ graph = Graph.from_edge(edge)
950
+
951
+ # THEN
952
+ self.validate_graph(graph, MyFinalOutput)
953
+
954
+ def test_rshift__node(self):
955
+ # GIVEN
956
+ class StartNode(BaseNode[BaseState]):
957
+ pass
958
+
959
+ class MyFinalOutput(FinalOutputNode[BaseState, str]):
960
+ pass
961
+
962
+ # WHEN
963
+ initial_graph = Graph.from_node(StartNode)
964
+ graph = initial_graph >> MyFinalOutput
965
+
966
+ # THEN
967
+ self.validate_graph(graph, MyFinalOutput)
968
+
969
+ def test_rshift__graph(self):
970
+ # GIVEN
971
+ class StartNode(BaseNode[BaseState]):
972
+ pass
973
+
974
+ class MyFinalOutput(FinalOutputNode[BaseState, str]):
975
+ pass
976
+
977
+ # WHEN
978
+ subgraph = Graph.from_node(MyFinalOutput)
979
+ graph = Graph.from_node(StartNode) >> subgraph
980
+
981
+ # THEN
982
+ self.validate_graph(subgraph, MyFinalOutput)
983
+ self.validate_graph(graph, MyFinalOutput)
984
+
985
+ def test_rshift__set_with_node(self):
986
+ # GIVEN
987
+ class StartNode(BaseNode[BaseState]):
988
+ pass
989
+
990
+ class MyFinalOutput(FinalOutputNode[BaseState, str]):
991
+ pass
992
+
993
+ # WHEN
994
+ graph = Graph.from_node(StartNode) >> {MyFinalOutput}
995
+
996
+ # THEN
997
+ self.validate_graph(graph, MyFinalOutput)
998
+
999
+ def test_rshift__set_with_graph(self):
1000
+ # GIVEN
1001
+ class StartNode(BaseNode[BaseState]):
1002
+ pass
1003
+
1004
+ class MyFinalOutput(FinalOutputNode[BaseState, str]):
1005
+ pass
1006
+
1007
+ # WHEN
1008
+ subgraph = Graph.from_node(MyFinalOutput)
1009
+ graph = Graph.from_node(StartNode) >> {subgraph}
1010
+
1011
+ # THEN
1012
+ self.validate_graph(subgraph, MyFinalOutput)
1013
+ self.validate_graph(graph, MyFinalOutput)
@@ -1,4 +1,4 @@
1
- from typing import Any, Dict, Iterator, Set, Tuple, Type, Union, get_args, get_origin
1
+ from typing import Any, Callable, Dict, Iterator, Set, Tuple, Type, Union, cast, get_args, get_origin
2
2
  from typing_extensions import dataclass_transform
3
3
 
4
4
  from pydantic import GetCoreSchemaHandler
@@ -10,7 +10,7 @@ from vellum.workflows.errors.types import WorkflowErrorCode
10
10
  from vellum.workflows.exceptions import WorkflowInitializationException
11
11
  from vellum.workflows.references import ExternalInputReference, WorkflowInputReference
12
12
  from vellum.workflows.references.input import InputReference
13
- from vellum.workflows.types.utils import get_class_attr_names, infer_types
13
+ from vellum.workflows.types.utils import coerce_to_declared_type, get_class_attr_names, infer_types
14
14
 
15
15
 
16
16
  @dataclass_transform(kw_only_default=True)
@@ -97,8 +97,7 @@ class BaseInputs(metaclass=_BaseInputsMeta):
97
97
  # Check if default is a FieldInfo with default_factory
98
98
  if isinstance(default_value, FieldInfo):
99
99
  if default_value.default_factory is not None:
100
- # Call the factory function to create a new instance
101
- value = default_value.default_factory() # type: ignore[call-arg]
100
+ value = cast(Callable[[], Any], default_value.default_factory)()
102
101
  elif hasattr(default_value, "default") and default_value.default is not ...:
103
102
  # Use the default value directly
104
103
  value = default_value.default
@@ -114,7 +113,7 @@ class BaseInputs(metaclass=_BaseInputsMeta):
114
113
  if value is undefined and not has_default:
115
114
  # All fields without defaults must be provided, even if Optional
116
115
  raise WorkflowInitializationException(
117
- message=f"Required input variables {name} should have defined value",
116
+ message=f"Required input variables '{name}' should have defined value",
118
117
  code=WorkflowErrorCode.INVALID_INPUTS,
119
118
  workflow_definition=self.__class__.__parent_class__,
120
119
  )
@@ -122,13 +121,30 @@ class BaseInputs(metaclass=_BaseInputsMeta):
122
121
  # Validate that None is not provided for non-Optional fields
123
122
  if value is None and not is_optional:
124
123
  raise WorkflowInitializationException(
125
- message=f"Required input variables {name} should have defined value",
124
+ message=f"Required input variables '{name}' should have defined value",
126
125
  code=WorkflowErrorCode.INVALID_INPUTS,
127
126
  workflow_definition=self.__class__.__parent_class__,
128
127
  )
129
128
 
130
129
  # Set the value on the instance (either from kwargs or default)
131
130
  if value is not undefined:
131
+ # Coerce the value to the declared type if needed
132
+ # For Optional types, extract the non-None type for coercion
133
+ coercion_type = field_type
134
+ if is_optional:
135
+ non_none_args = [arg for arg in args if arg is not type(None)]
136
+ if len(non_none_args) == 1:
137
+ coercion_type = non_none_args[0]
138
+
139
+ try:
140
+ value = coerce_to_declared_type(value, coercion_type, name)
141
+ except ValueError as e:
142
+ raise WorkflowInitializationException(
143
+ message=str(e),
144
+ code=WorkflowErrorCode.INVALID_INPUTS,
145
+ workflow_definition=self.__class__.__parent_class__,
146
+ )
147
+
132
148
  setattr(self, name, value)
133
149
 
134
150
  def __iter__(self) -> Iterator[Tuple[InputReference, Any]]: