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
@@ -1,8 +1,11 @@
1
- from typing import TYPE_CHECKING, TypeVar, cast
1
+ from functools import cached_property
2
+ from uuid import UUID
3
+ from typing import TYPE_CHECKING, Optional, Tuple, Type, TypeVar, cast
2
4
 
3
5
  from vellum.workflows.descriptors.base import BaseDescriptor
4
6
  from vellum.workflows.errors.types import WorkflowErrorCode
5
7
  from vellum.workflows.exceptions import NodeException
8
+ from vellum.workflows.utils.uuids import get_state_value_id
6
9
 
7
10
  if TYPE_CHECKING:
8
11
  from vellum.workflows.state.base import BaseState
@@ -13,6 +16,26 @@ _T = TypeVar("_T")
13
16
 
14
17
  class StateValueReference(BaseDescriptor[_T]):
15
18
 
19
+ def __init__(
20
+ self,
21
+ *,
22
+ name: str,
23
+ types: Tuple[Type[_T], ...],
24
+ instance: Optional[_T],
25
+ state_class: Type["BaseState"],
26
+ ) -> None:
27
+ super().__init__(name=name, types=types, instance=instance)
28
+ self._state_class = state_class
29
+
30
+ @property
31
+ def state_class(self) -> Type["BaseState"]:
32
+ return self._state_class
33
+
34
+ @cached_property
35
+ def id(self) -> UUID:
36
+ """Generate deterministic UUID from state class and state value name."""
37
+ return get_state_value_id(self._state_class, self.name)
38
+
16
39
  def resolve(self, state: "BaseState") -> _T:
17
40
  if hasattr(state, self._name):
18
41
  return cast(_T, getattr(state, self._name))
@@ -1,9 +1,29 @@
1
1
  import pytest
2
2
 
3
+ from vellum.workflows import BaseWorkflow
3
4
  from vellum.workflows.nodes import BaseNode
4
5
  from vellum.workflows.references.lazy import LazyReference
5
6
 
6
7
 
8
+ class ResponseNode(BaseNode):
9
+ class Outputs(BaseNode.Outputs):
10
+ response: str = "Hello from node!"
11
+
12
+
13
+ class TestWorkflowWithOutput(BaseWorkflow):
14
+ graph = ResponseNode
15
+
16
+ class Outputs(BaseWorkflow.Outputs):
17
+ final_response = ResponseNode.Outputs.response
18
+
19
+
20
+ class TestWorkflowWithLiteralOutput(BaseWorkflow):
21
+ graph = ResponseNode
22
+
23
+ class Outputs(BaseWorkflow.Outputs):
24
+ literal_value = "Hello literal!"
25
+
26
+
7
27
  @pytest.fixture
8
28
  def mock_inspect_getsource(mocker):
9
29
  return mocker.patch("inspect.getsource")
@@ -28,3 +48,41 @@ def test_lazy_reference__inspect_getsource_fails(mock_inspect_getsource, mock_lo
28
48
 
29
49
  # AND sentry is notified
30
50
  assert mock_logger.exception.call_count == 1
51
+
52
+
53
+ def test_lazy_reference__string_resolves_workflow_output():
54
+ """Tests that string-based LazyReference can resolve workflow output references."""
55
+
56
+ # GIVEN a workflow with an output that references a node output (defined at module level)
57
+ # WHEN we run the workflow
58
+ terminal_event = TestWorkflowWithOutput().run()
59
+
60
+ # THEN the workflow completes successfully
61
+ assert terminal_event.name == "workflow.execution.fulfilled"
62
+ assert terminal_event.final_state is not None
63
+
64
+ # AND we can resolve a string-based LazyReference to the workflow output
65
+ lazy_ref = LazyReference[str]("TestWorkflowWithOutput.Outputs.final_response")
66
+ resolved_value = lazy_ref.resolve(terminal_event.final_state)
67
+
68
+ # THEN the resolved value matches the node output
69
+ assert resolved_value == "Hello from node!"
70
+
71
+
72
+ def test_lazy_reference__string_resolves_literal_workflow_output():
73
+ """Tests that string-based LazyReference can resolve literal workflow output values."""
74
+
75
+ # GIVEN a workflow with a literal output value (defined at module level)
76
+ # WHEN we run the workflow
77
+ terminal_event = TestWorkflowWithLiteralOutput().run()
78
+
79
+ # THEN the workflow completes successfully
80
+ assert terminal_event.name == "workflow.execution.fulfilled"
81
+ assert terminal_event.final_state is not None
82
+
83
+ # AND we can resolve a string-based LazyReference to the literal workflow output
84
+ lazy_ref = LazyReference[str]("TestWorkflowWithLiteralOutput.Outputs.literal_value")
85
+ resolved_value = lazy_ref.resolve(terminal_event.final_state)
86
+
87
+ # THEN the resolved value matches the literal output
88
+ assert resolved_value == "Hello literal!"
@@ -11,7 +11,6 @@ from pydantic_core import core_schema
11
11
  from vellum.workflows.descriptors.base import BaseDescriptor
12
12
  from vellum.workflows.errors.types import WorkflowErrorCode
13
13
  from vellum.workflows.exceptions import NodeException
14
- from vellum.workflows.utils.uuids import get_trigger_attribute_id
15
14
 
16
15
  if TYPE_CHECKING:
17
16
  from vellum.workflows.state.base import BaseState
@@ -40,8 +39,14 @@ class TriggerAttributeReference(BaseDescriptor[_T], Generic[_T]):
40
39
 
41
40
  @property
42
41
  def id(self) -> UUID:
43
- """Generate deterministic UUID from trigger class qualname and attribute name."""
44
- return get_trigger_attribute_id(self._trigger_class, self.name)
42
+ """
43
+ Get the trigger attribute ID via the trigger class, which knows how to read
44
+ metadata.json and fall back to hash-based IDs.
45
+
46
+ This ensures trigger attribute IDs remain stable across serialization round-trips
47
+ when metadata.json is present.
48
+ """
49
+ return self._trigger_class.get_attribute_id(self.name)
45
50
 
46
51
  def resolve(self, state: BaseState) -> _T:
47
52
  trigger_attributes = getattr(state.meta, "trigger_attributes", {})
@@ -1,9 +1,12 @@
1
+ from functools import cached_property
2
+ from uuid import UUID
1
3
  from typing import TYPE_CHECKING, Generic, Optional, Tuple, Type, TypeVar, cast
2
4
 
3
5
  from vellum.workflows.descriptors.base import BaseDescriptor
4
6
  from vellum.workflows.errors.types import WorkflowErrorCode
5
7
  from vellum.workflows.exceptions import NodeException
6
8
  from vellum.workflows.types.generics import import_workflow_class
9
+ from vellum.workflows.utils.uuids import get_workflow_input_id
7
10
 
8
11
  if TYPE_CHECKING:
9
12
  from vellum.workflows.inputs.base import BaseInputs
@@ -29,6 +32,11 @@ class WorkflowInputReference(BaseDescriptor[_InputType], Generic[_InputType]):
29
32
  def inputs_class(self) -> Type["BaseInputs"]:
30
33
  return self._inputs_class
31
34
 
35
+ @cached_property
36
+ def id(self) -> UUID:
37
+ """Generate deterministic UUID from inputs class and input name."""
38
+ return get_workflow_input_id(self._inputs_class, self.name)
39
+
32
40
  def resolve(self, state: "BaseState") -> _InputType:
33
41
  if hasattr(state.meta.workflow_inputs, self._name) and (
34
42
  state.meta.workflow_definition == self._inputs_class.__parent_class__
@@ -2,6 +2,7 @@ import logging
2
2
  from uuid import UUID
3
3
  from typing import Iterator, Optional, Type, Union
4
4
 
5
+ from vellum.client.core.api_error import ApiError
5
6
  from vellum.workflows.events.workflow import WorkflowEvent
6
7
  from vellum.workflows.nodes.utils import cast_to_output_type
7
8
  from vellum.workflows.resolvers.base import BaseWorkflowResolver
@@ -45,9 +46,18 @@ class VellumResolver(BaseWorkflowResolver):
45
46
  return None
46
47
 
47
48
  client = self._context.vellum_client
48
- response = client.workflows.retrieve_state(
49
- span_id=previous_execution_id,
50
- )
49
+ try:
50
+ response = client.workflows.retrieve_state(
51
+ span_id=previous_execution_id,
52
+ )
53
+ except ApiError as e:
54
+ if e.status_code == 404:
55
+ logger.debug(
56
+ "No state found for previous execution %s, continuing without previous state",
57
+ previous_execution_id,
58
+ )
59
+ return None
60
+ raise
51
61
 
52
62
  if response.state is None:
53
63
  return None
@@ -4,6 +4,7 @@ from uuid import uuid4
4
4
  from typing import List
5
5
 
6
6
  from vellum import ChatMessage
7
+ from vellum.client.core.api_error import ApiError
7
8
  from vellum.client.types.workflow_resolved_state import WorkflowResolvedState
8
9
  from vellum.workflows import BaseWorkflow
9
10
  from vellum.workflows.inputs.base import BaseInputs
@@ -166,3 +167,33 @@ def test_load_state_with_chat_message_list():
166
167
  assert result.state.chat_history[2].text == "What can you help me with?"
167
168
 
168
169
  mock_client.workflows.retrieve_state.assert_called_once_with(span_id=str(execution_id))
170
+
171
+
172
+ def test_load_state_returns_none_on_404():
173
+ """Test load_state returns None when retrieve_state returns 404 (e.g., rejected execution with no state)."""
174
+ resolver = VellumResolver()
175
+ execution_id = uuid4()
176
+
177
+ class TestState(BaseState):
178
+ test_key: str = "test_value"
179
+
180
+ class TestWorkflow(BaseWorkflow[BaseInputs, TestState]):
181
+ pass
182
+
183
+ # GIVEN a mock client that raises ApiError with 404 status
184
+ mock_client = Mock()
185
+ mock_client.workflows.retrieve_state.side_effect = ApiError(
186
+ status_code=404,
187
+ body={"detail": "No state found for the given span_id"},
188
+ )
189
+
190
+ # AND context with the test workflow class is set up
191
+ context = WorkflowContext(vellum_client=mock_client)
192
+ TestWorkflow(context=context, resolvers=[resolver])
193
+
194
+ # WHEN load_state is called with an execution ID that has no state
195
+ result = resolver.load_state(previous_execution_id=execution_id)
196
+
197
+ # THEN should return None instead of raising an exception
198
+ assert result is None
199
+ mock_client.workflows.retrieve_state.assert_called_once_with(span_id=str(execution_id))
@@ -25,6 +25,7 @@ from typing import (
25
25
  Union,
26
26
  )
27
27
 
28
+ from vellum.client.core.api_error import ApiError
28
29
  from vellum.workflows.constants import undefined
29
30
  from vellum.workflows.context import ExecutionContext, execution_context, get_execution_context
30
31
  from vellum.workflows.descriptors.base import BaseDescriptor
@@ -49,7 +50,14 @@ from vellum.workflows.events.node import (
49
50
  NodeExecutionRejectedBody,
50
51
  NodeExecutionStreamingBody,
51
52
  )
52
- from vellum.workflows.events.types import BaseEvent, NodeParentContext, ParentContext, SpanLink, WorkflowParentContext
53
+ from vellum.workflows.events.types import (
54
+ BaseEvent,
55
+ NodeParentContext,
56
+ ParentContext,
57
+ SpanLink,
58
+ WorkflowParentContext,
59
+ default_serializer,
60
+ )
53
61
  from vellum.workflows.events.workflow import (
54
62
  WorkflowEventStream,
55
63
  WorkflowExecutionFulfilledBody,
@@ -64,6 +72,7 @@ from vellum.workflows.events.workflow import (
64
72
  WorkflowExecutionStreamingBody,
65
73
  )
66
74
  from vellum.workflows.exceptions import NodeException, WorkflowInitializationException
75
+ from vellum.workflows.inputs.base import BaseInputs
67
76
  from vellum.workflows.nodes.bases import BaseNode
68
77
  from vellum.workflows.nodes.bases.base import NodeRunResponse
69
78
  from vellum.workflows.nodes.mocks import MockNodeExecutionArg
@@ -114,6 +123,7 @@ class WorkflowRunner(Generic[StateType]):
114
123
  init_execution_context: Optional[ExecutionContext] = None,
115
124
  trigger: Optional[BaseTrigger] = None,
116
125
  execution_id: Optional[UUID] = None,
126
+ event_max_size: Optional[int] = None,
117
127
  ):
118
128
  if state and external_inputs:
119
129
  raise ValueError("Can only run a Workflow providing one of state or external inputs, not both")
@@ -200,23 +210,28 @@ class WorkflowRunner(Generic[StateType]):
200
210
  continue
201
211
 
202
212
  if resolver_failed:
203
- raise WorkflowInitializationException(
204
- message=f"All resolvers failed to load initial state for execution ID: {previous_execution_id}",
205
- workflow_definition=self.workflow.__class__,
206
- code=WorkflowErrorCode.INVALID_INPUTS,
213
+ logger.warning(
214
+ f"All resolvers failed to load initial state for execution ID: {previous_execution_id}. "
215
+ "Falling back to default state."
207
216
  )
217
+ normalized_inputs = deepcopy(inputs) if inputs else self.workflow.get_default_inputs()
218
+ self._initial_state = self.workflow.get_default_state(normalized_inputs, execution_id)
208
219
 
209
220
  self._entrypoints = self.workflow.get_entrypoints()
210
221
  elif trigger:
211
222
  # When trigger is provided, set up default state and filter entrypoints by trigger type
212
- normalized_inputs = deepcopy(inputs) if inputs else self.workflow.get_default_inputs()
223
+ default_inputs = deepcopy(inputs) if inputs else None
213
224
  if state:
214
225
  self._initial_state = deepcopy(state)
215
- self._initial_state.meta.workflow_inputs = normalized_inputs
226
+ self._initial_state.meta.workflow_inputs = default_inputs
216
227
  self._initial_state.meta.span_id = execution_id or uuid4()
217
228
  self._initial_state.meta.workflow_definition = self.workflow.__class__
218
229
  else:
219
- self._initial_state = self.workflow.get_default_state(normalized_inputs, execution_id)
230
+ self._initial_state = self.workflow.get_default_state(
231
+ default_inputs,
232
+ execution_id,
233
+ trigger_attributes={},
234
+ )
220
235
  self._should_emit_initial_state = False
221
236
 
222
237
  # Validate and bind trigger, then filter entrypoints
@@ -263,6 +278,7 @@ class WorkflowRunner(Generic[StateType]):
263
278
  self._timeout = timeout
264
279
  self._execution_context = init_execution_context or get_execution_context()
265
280
  self._trigger = trigger
281
+ self._event_max_size = event_max_size
266
282
 
267
283
  setattr(
268
284
  self._initial_state,
@@ -271,6 +287,7 @@ class WorkflowRunner(Generic[StateType]):
271
287
  )
272
288
  self.workflow.context._register_event_queue(self._workflow_event_inner_queue)
273
289
  self.workflow.context._register_node_output_mocks(node_output_mocks or [])
290
+ self.workflow.context._register_event_max_size(event_max_size)
274
291
 
275
292
  self._outputs_listening_to_state = [
276
293
  descriptor for descriptor in self.workflow.Outputs if isinstance(descriptor.instance, StateValueReference)
@@ -294,13 +311,17 @@ class WorkflowRunner(Generic[StateType]):
294
311
  Allows subclasses: if trigger_class is a subclass of any declared trigger,
295
312
  returns those entrypoints.
296
313
  """
314
+ seen: Set[Type[BaseNode]] = set()
297
315
  entrypoints: List[Type[BaseNode]] = []
298
316
  for subgraph in self.workflow.get_subgraphs():
299
317
  for trigger in subgraph.triggers:
300
318
  # Check if the provided trigger_class is a subclass of the declared trigger
301
319
  # This allows runtime instances to be subclasses of what's declared in the workflow
302
320
  if issubclass(trigger_class, trigger):
303
- entrypoints.extend(subgraph.entrypoints)
321
+ for entrypoint in subgraph.entrypoints:
322
+ if entrypoint not in seen:
323
+ seen.add(entrypoint)
324
+ entrypoints.append(entrypoint)
304
325
  return entrypoints
305
326
 
306
327
  def _validate_and_bind_trigger(self, trigger: BaseTrigger) -> None:
@@ -461,6 +482,8 @@ class WorkflowRunner(Generic[StateType]):
461
482
  return state
462
483
 
463
484
  def _emit_event(self, event: WorkflowEvent) -> WorkflowEvent:
485
+ if self._event_max_size is not None:
486
+ event._event_max_size = self._event_max_size
464
487
  self.workflow._store.append_event(event)
465
488
  self._background_thread_queue.put(event)
466
489
  return event
@@ -510,6 +533,8 @@ class WorkflowRunner(Generic[StateType]):
510
533
  was_mocked: Optional[bool] = None
511
534
  mock_candidates = node_output_mocks_map.get(node.Outputs) or []
512
535
  for mock_candidate in mock_candidates:
536
+ if mock_candidate.disabled:
537
+ continue
513
538
  if mock_candidate.when_condition.resolve(node.state):
514
539
  node_run_response = mock_candidate.then_outputs
515
540
  was_mocked = True
@@ -565,6 +590,16 @@ class WorkflowRunner(Generic[StateType]):
565
590
 
566
591
  with execution_context(parent_context=updated_parent_context, trace_id=execution.trace_id):
567
592
  for output in node_run_response:
593
+ try:
594
+ default_serializer(output)
595
+ except (TypeError, ValueError) as exc:
596
+ raise NodeException(
597
+ message=(
598
+ f"Node {node.__class__.__name__} produced output: "
599
+ f"'{output.name}' that could not be serialized to JSON: {exc}"
600
+ ),
601
+ code=WorkflowErrorCode.INVALID_OUTPUTS,
602
+ ) from exc
568
603
  invoked_ports = output > ports
569
604
  if output.is_initiated:
570
605
  yield from initiate_node_streaming_output(output)
@@ -599,6 +634,20 @@ class WorkflowRunner(Generic[StateType]):
599
634
  parent=execution.parent_context,
600
635
  )
601
636
 
637
+ for descriptor, output_value in outputs:
638
+ if output_value is undefined:
639
+ continue
640
+ try:
641
+ default_serializer(output_value)
642
+ except (TypeError, ValueError) as exc:
643
+ raise NodeException(
644
+ message=(
645
+ f"Node {node.__class__.__name__} produced output '{descriptor.name}' "
646
+ f"that could not be serialized to JSON: {exc}"
647
+ ),
648
+ code=WorkflowErrorCode.INVALID_OUTPUTS,
649
+ ) from exc
650
+
602
651
  node.state.meta.node_execution_cache.fulfill_node_execution(node.__class__, span_id)
603
652
 
604
653
  with execution_context(parent_context=updated_parent_context, trace_id=execution.trace_id):
@@ -610,7 +659,15 @@ class WorkflowRunner(Generic[StateType]):
610
659
  continue
611
660
  node.state.meta.node_outputs[descriptor] = output_value
612
661
 
613
- invoked_ports = ports(outputs, node.state)
662
+ try:
663
+ invoked_ports = ports(outputs, node.state)
664
+ except NodeException as e:
665
+ raise NodeException(
666
+ message=e.message,
667
+ code=e.code,
668
+ raw_data={**(e.raw_data or {}), "outputs": outputs.__vellum_encode__()},
669
+ stacktrace=e.stacktrace,
670
+ ) from e
614
671
  yield NodeExecutionFulfilledEvent(
615
672
  trace_id=execution.trace_id,
616
673
  span_id=span_id,
@@ -628,6 +685,71 @@ class WorkflowRunner(Generic[StateType]):
628
685
  yield self._handle_run_node_exception(e, "Workflow Initialization Exception", execution, span_id, node)
629
686
  except InvalidExpressionException as e:
630
687
  yield self._handle_run_node_exception(e, "Invalid Expression Exception", execution, span_id, node)
688
+ except ApiError as e:
689
+ captured_stacktrace = traceback.format_exc()
690
+ # Handle structured 403 credential error responses with integration details
691
+ # The Django API returns {"message": "...", "integration": {...}} for unresolvable credentials
692
+ # We detect this by shape (403 + integration field present) rather than a code field
693
+ if e.status_code == 403 and isinstance(e.body, dict) and e.body.get("integration"):
694
+ error_message = e.body.get(
695
+ "message", "You must authenticate with this integration before you can execute this tool."
696
+ )
697
+ raw_data = {"integration": e.body["integration"]}
698
+ yield NodeExecutionRejectedEvent(
699
+ trace_id=execution.trace_id,
700
+ span_id=span_id,
701
+ body=NodeExecutionRejectedBody(
702
+ node_definition=node.__class__,
703
+ error=WorkflowError(
704
+ message=error_message,
705
+ code=WorkflowErrorCode.INTEGRATION_CREDENTIALS_UNAVAILABLE,
706
+ raw_data=raw_data,
707
+ ),
708
+ stacktrace=captured_stacktrace,
709
+ ),
710
+ parent=execution.parent_context,
711
+ )
712
+ # Handle structured 400 error responses with integration details as INVALID_INPUTS
713
+ elif e.status_code == 400 and isinstance(e.body, dict) and e.body.get("integration"):
714
+ error_message = e.body.get("message", "Invalid request to integration.")
715
+ raw_data = {"integration": e.body["integration"]}
716
+ yield NodeExecutionRejectedEvent(
717
+ trace_id=execution.trace_id,
718
+ span_id=span_id,
719
+ body=NodeExecutionRejectedBody(
720
+ node_definition=node.__class__,
721
+ error=WorkflowError(
722
+ message=error_message,
723
+ code=WorkflowErrorCode.INVALID_INPUTS,
724
+ raw_data=raw_data,
725
+ ),
726
+ stacktrace=captured_stacktrace,
727
+ ),
728
+ parent=execution.parent_context,
729
+ )
730
+ else:
731
+ # For all other ApiErrors, use the existing generic exception behavior
732
+ error_message = self._parse_error_message(e)
733
+ if error_message is None:
734
+ logger.exception(f"An unexpected error occurred while running node {node.__class__.__name__}")
735
+ error_code = WorkflowErrorCode.INTERNAL_ERROR
736
+ error_message = "Internal error"
737
+ else:
738
+ error_code = WorkflowErrorCode.NODE_EXECUTION
739
+
740
+ yield NodeExecutionRejectedEvent(
741
+ trace_id=execution.trace_id,
742
+ span_id=span_id,
743
+ body=NodeExecutionRejectedBody(
744
+ node_definition=node.__class__,
745
+ error=WorkflowError(
746
+ message=error_message,
747
+ code=error_code,
748
+ ),
749
+ stacktrace=captured_stacktrace,
750
+ ),
751
+ parent=execution.parent_context,
752
+ )
631
753
  except Exception as e:
632
754
  error_message = self._parse_error_message(e)
633
755
  captured_stacktrace = traceback.format_exc()
@@ -817,7 +939,7 @@ class WorkflowRunner(Generic[StateType]):
817
939
  )
818
940
  )
819
941
  )
820
- return None
942
+ continue
821
943
 
822
944
  node_output_descriptor = workflow_output_descriptor.instance
823
945
  if not isinstance(node_output_descriptor, OutputReference):
@@ -931,14 +1053,19 @@ class WorkflowRunner(Generic[StateType]):
931
1053
  ),
932
1054
  ]
933
1055
 
1056
+ # Get raw inputs from trigger event data if available
1057
+ # This ensures trigger attributes are included in the serialized inputs
1058
+ raw_inputs = self._trigger._event_data if self._trigger else None
1059
+
934
1060
  return WorkflowExecutionInitiatedEvent(
935
1061
  trace_id=self._execution_context.trace_id,
936
1062
  span_id=self._initial_state.meta.span_id,
937
1063
  body=WorkflowExecutionInitiatedBody(
938
1064
  workflow_definition=self.workflow.__class__,
939
- inputs=self._initial_state.meta.workflow_inputs,
1065
+ inputs=self._initial_state.meta.workflow_inputs or BaseInputs(),
940
1066
  initial_state=deepcopy(self._initial_state) if self._should_emit_initial_state else None,
941
1067
  trigger=self._trigger.__class__ if self._trigger else None,
1068
+ raw_inputs=raw_inputs,
942
1069
  ),
943
1070
  parent=self._execution_context.parent_context,
944
1071
  links=links,
@@ -1019,6 +1146,10 @@ class WorkflowRunner(Generic[StateType]):
1019
1146
  for edge in self.workflow.get_edges():
1020
1147
  self._dependencies[edge.to_node].add(edge.from_port.node_class)
1021
1148
 
1149
+ # Call trigger initiated hook so nodes can reference trigger state
1150
+ if self._trigger is not None:
1151
+ self._trigger.__on_workflow_initiated__(self._initial_state)
1152
+
1022
1153
  current_parent = WorkflowParentContext(
1023
1154
  span_id=self._initial_state.meta.span_id,
1024
1155
  workflow_definition=self.workflow.__class__,
@@ -1078,9 +1209,11 @@ class WorkflowRunner(Generic[StateType]):
1078
1209
  self._workflow_event_outer_queue.put(event)
1079
1210
 
1080
1211
  with execution_context(parent_context=current_parent, trace_id=self._execution_context.trace_id):
1081
- rejection_event = self._handle_work_item_event(event)
1212
+ new_rejection_event = self._handle_work_item_event(event)
1082
1213
 
1083
- if rejection_event:
1214
+ if new_rejection_event:
1215
+ if rejection_event is None:
1216
+ rejection_event = new_rejection_event
1084
1217
  break
1085
1218
  except Empty:
1086
1219
  pass
@@ -1106,6 +1239,18 @@ class WorkflowRunner(Generic[StateType]):
1106
1239
  )
1107
1240
  return
1108
1241
 
1242
+ # Call trigger fulfilled hook before output resolution so state changes are reflected in outputs
1243
+ if self._trigger is not None:
1244
+ self._trigger.__on_workflow_fulfilled__(final_state)
1245
+
1246
+ # Drain any events produced by the trigger hook (e.g., snapshot events from state mutations)
1247
+ # and forward them to the outer queue so stream consumers can observe them
1248
+ try:
1249
+ while event := self._workflow_event_inner_queue.get_nowait():
1250
+ self._workflow_event_outer_queue.put(event)
1251
+ except Empty:
1252
+ pass
1253
+
1109
1254
  fulfilled_outputs = self.workflow.Outputs()
1110
1255
  for descriptor, value in fulfilled_outputs:
1111
1256
  if isinstance(value, BaseDescriptor):
File without changes