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
@@ -69,23 +69,24 @@ class MapNode(BaseAdornmentNode[StateType], Generic[StateType, MapNodeItemType])
69
69
 
70
70
  def run(self) -> Iterator[BaseOutput]:
71
71
  mapped_items: Dict[str, List] = defaultdict(list)
72
+ items = self.items or []
72
73
  for output_descripter in self.subworkflow.Outputs:
73
- mapped_items[output_descripter.name] = [None] * len(self.items)
74
+ mapped_items[output_descripter.name] = [None] * len(items)
74
75
 
75
- if not self.items:
76
+ if not items:
76
77
  for output_name, output_list in mapped_items.items():
77
78
  yield BaseOutput(name=output_name, value=output_list)
78
79
  return
79
80
 
80
81
  self._event_queue: Queue[Tuple[int, WorkflowEvent]] = Queue()
81
- fulfilled_iterations: List[bool] = [False] * len(self.items)
82
+ fulfilled_iterations: List[bool] = [False] * len(items)
82
83
 
83
- max_workers = self.max_concurrency if self.max_concurrency is not None else len(self.items)
84
+ max_workers = self.max_concurrency if self.max_concurrency is not None else len(items)
84
85
 
85
86
  with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
86
87
  futures = []
87
88
  current_execution_context = get_execution_context()
88
- for index, item in enumerate(self.items):
89
+ for index, item in enumerate(items):
89
90
  future = executor.submit(
90
91
  self._context_run_subworkflow,
91
92
  item=item,
@@ -180,6 +181,7 @@ class MapNode(BaseAdornmentNode[StateType], Generic[StateType, MapNodeItemType])
180
181
  inputs=SubworkflowInputsClass(index=index, item=item, items=self.items),
181
182
  node_output_mocks=self._context._get_all_node_output_mocks(),
182
183
  event_filter=all_workflow_event_filter,
184
+ event_max_size=self._context.event_max_size,
183
185
  )
184
186
 
185
187
  for event in events:
@@ -71,6 +71,8 @@ def test_map_node__use_parallelism():
71
71
 
72
72
 
73
73
  def test_map_node__empty_list():
74
+ """Tests that a map node with an empty list returns an empty output."""
75
+
74
76
  # GIVEN a map node that is configured to use the parent's inputs and state
75
77
  @MapNode.wrap(items=[])
76
78
  class TestNode(BaseNode):
@@ -92,6 +94,37 @@ def test_map_node__empty_list():
92
94
  assert fulfilled_output == BaseOutput(name="value", value=[])
93
95
 
94
96
 
97
+ def test_map_node__none_items():
98
+ """Tests that a map node with None items treats it as an empty array and fulfills successfully."""
99
+
100
+ # GIVEN a map node with items set to None
101
+ class TestNode(BaseNode):
102
+ item = MapNode.SubworkflowInputs.item
103
+
104
+ class Outputs(BaseOutputs):
105
+ value: str
106
+
107
+ def run(self) -> Outputs:
108
+ return self.Outputs(value=str(self.item))
109
+
110
+ class TestSubworkflow(BaseWorkflow[MapNode.SubworkflowInputs, BaseState]):
111
+ graph = TestNode
112
+
113
+ class Outputs(BaseOutputs):
114
+ value = TestNode.Outputs.value
115
+
116
+ class TestMapNode(MapNode):
117
+ subworkflow = TestSubworkflow
118
+
119
+ # WHEN the node is run
120
+ node = TestMapNode()
121
+ outputs = list(node.run())
122
+
123
+ # THEN the node should return an empty output without error
124
+ fulfilled_output = outputs[-1]
125
+ assert fulfilled_output == BaseOutput(name="value", value=[])
126
+
127
+
95
128
  def test_map_node__inner_try():
96
129
  # GIVEN a try wrapped node
97
130
  @TryNode.wrap()
@@ -52,6 +52,7 @@ class RetryNode(BaseAdornmentNode[StateType], Generic[StateType]):
52
52
  inputs=inputs_class(attempt_number=attempt_number),
53
53
  event_filter=all_workflow_event_filter,
54
54
  node_output_mocks=self._context._get_all_node_output_mocks(),
55
+ event_max_size=self._context.event_max_size,
55
56
  )
56
57
 
57
58
  node_outputs: Optional[BaseNode.Outputs] = None
@@ -36,6 +36,7 @@ class TryNode(BaseAdornmentNode[StateType], Generic[StateType]):
36
36
  subworkflow_stream = subworkflow.stream(
37
37
  event_filter=all_workflow_event_filter,
38
38
  node_output_mocks=self._context._get_all_node_output_mocks(),
39
+ event_max_size=self._context.event_max_size,
39
40
  )
40
41
 
41
42
  outputs: Optional[BaseOutputs] = None
@@ -1,11 +1,12 @@
1
- from typing import Optional, Union
1
+ from typing import Generic, Optional, Union
2
2
 
3
3
  from vellum.workflows.constants import AuthorizationType
4
4
  from vellum.workflows.nodes.displayable.bases.api_node import BaseAPINode
5
5
  from vellum.workflows.types.core import MergeBehavior, VellumSecret
6
+ from vellum.workflows.types.generics import StateType
6
7
 
7
8
 
8
- class APINode(BaseAPINode):
9
+ class APINode(BaseAPINode[StateType], Generic[StateType]):
9
10
  """
10
11
  Used to execute an API call. This node exists to be backwards compatible with Vellum's API Node, and for most cases,
11
12
  you should extend from `BaseAPINode` directly.
@@ -269,3 +269,41 @@ def test_api_node_passes_timeout_to_vellum_client(vellum_client):
269
269
  # AND the call should include RequestOptions with the correct timeout
270
270
  assert request_options is not None
271
271
  assert request_options["timeout_in_seconds"] == 25
272
+
273
+
274
+ def test_api_node_supports_state_type_parameter(vellum_client):
275
+ """Test that APINode supports StateType generic parameter."""
276
+
277
+ # GIVEN a state class
278
+ from vellum.workflows.state.base import BaseState
279
+
280
+ class TestState(BaseState):
281
+ counter: int = 0
282
+
283
+ # AND an API node that uses the StateType parameter
284
+ class TestAPINode(APINode[TestState]):
285
+ method = APIRequestMethod.GET
286
+ authorization_type = AuthorizationType.BEARER_TOKEN
287
+ url = "https://example.com"
288
+ bearer_token_value = VellumSecret(name="secret")
289
+
290
+ # AND a mock response from the API
291
+ vellum_client.execute_api.return_value = ExecuteApiResponse(
292
+ status_code=200,
293
+ text='{"result": "success"}',
294
+ json_={"result": "success"},
295
+ headers={"content-type": "application/json"},
296
+ )
297
+
298
+ # WHEN we create and run the node with a state instance
299
+ state = TestState()
300
+ node = TestAPINode(state=state)
301
+ outputs = node.run()
302
+
303
+ # THEN the node should run successfully
304
+ assert vellum_client.execute_api.call_count == 1
305
+ assert outputs.status_code == 200
306
+ assert outputs.json == {"result": "success"}
307
+
308
+ # AND the state should be accessible (though not modified by the API node)
309
+ assert state.counter == 0
@@ -40,7 +40,7 @@ class BaseAPINode(BaseNode, Generic[StateType]):
40
40
  timeout: Optional[int] = None
41
41
 
42
42
  class Outputs(BaseOutputs):
43
- json: Optional[Json]
43
+ json: Json
44
44
  headers: Dict[str, str]
45
45
  status_code: int
46
46
  text: str
@@ -85,6 +85,8 @@ class BasePromptNode(BaseNode[StateType], Generic[StateType]):
85
85
  continue
86
86
  elif event.state == "STREAMING":
87
87
  yield BaseOutput(name="results", delta=event.output.value)
88
+ if event.output.type == "STRING":
89
+ yield BaseOutput(name="text", delta=event.output.value)
88
90
  elif event.state == "FULFILLED":
89
91
  outputs = event.outputs
90
92
  yield BaseOutput(name="results", value=event.outputs)
@@ -123,12 +125,24 @@ class BasePromptNode(BaseNode[StateType], Generic[StateType]):
123
125
  event: NodeExecutionStreamingEvent,
124
126
  workflow_output_descriptor: OutputReference,
125
127
  ) -> bool:
128
+ # Check if workflow output directly references this node's text output
129
+ text_output = getattr(event.node_definition.Outputs, "text", None)
130
+
131
+ if (
132
+ text_output is not None
133
+ and event.output.name == "text"
134
+ and isinstance(workflow_output_descriptor.instance, BaseDescriptor)
135
+ and _contains_reference_to_output(workflow_output_descriptor.instance, text_output)
136
+ ):
137
+ return True
138
+
126
139
  if event.output.name != "results":
127
140
  return False
128
141
 
129
142
  if not isinstance(event.output.delta, str) and not event.output.is_initiated:
130
143
  return False
131
144
 
145
+ # Check if workflow output references this node's text output through a FinalOutputNode
132
146
  target_nodes = [e.to_node for port in self.Ports for e in port.edges if e.to_node.__simulates_workflow_output__]
133
147
  target_node_output = next(
134
148
  (
@@ -145,4 +159,7 @@ class BasePromptNode(BaseNode[StateType], Generic[StateType]):
145
159
  if not isinstance(target_node_output.instance, BaseDescriptor):
146
160
  return False
147
161
 
148
- return _contains_reference_to_output(target_node_output.instance, event.node_definition.Outputs.text)
162
+ if text_output is None:
163
+ return False
164
+
165
+ return _contains_reference_to_output(target_node_output.instance, text_output)
@@ -1,9 +1,23 @@
1
1
  from itertools import chain
2
2
  import json
3
3
  from uuid import uuid4
4
- from typing import Callable, ClassVar, Generator, Generic, Iterator, List, Optional, Set, Tuple, Union
4
+ from typing import (
5
+ TYPE_CHECKING,
6
+ Callable,
7
+ ClassVar,
8
+ Generator,
9
+ Generic,
10
+ Iterator,
11
+ List,
12
+ Optional,
13
+ Set,
14
+ Tuple,
15
+ Type,
16
+ Union,
17
+ )
5
18
 
6
19
  import httpx
20
+ import jsonschema
7
21
 
8
22
  from vellum import (
9
23
  AdHocExecutePromptEvent,
@@ -53,6 +67,7 @@ from vellum.workflows.types.definition import (
53
67
  ComposioToolDefinition,
54
68
  DeploymentDefinition,
55
69
  MCPServer,
70
+ MCPToolDefinition,
56
71
  VellumIntegrationToolDefinition,
57
72
  )
58
73
  from vellum.workflows.types.generics import StateType, is_workflow_class
@@ -67,6 +82,60 @@ from vellum.workflows.utils.functions import (
67
82
  )
68
83
  from vellum.workflows.utils.pydantic_schema import normalize_json
69
84
 
85
+ if TYPE_CHECKING:
86
+ from vellum.workflows.workflows.base import BaseWorkflow
87
+
88
+
89
+ def _get_json_schema_to_validate(parameters_ref: object) -> Optional[dict]:
90
+ """
91
+ Extracts the JSON schema to validate from a parameters reference.
92
+
93
+ Also normalizes Pydantic models to JSON schema dicts so they can be validated.
94
+
95
+ Args:
96
+ parameters_ref: The parameters reference (NodeReference wrapping PromptParameters)
97
+
98
+ Returns:
99
+ The JSON schema dict to validate, or None if no schema found.
100
+ """
101
+ parameters_instance = getattr(parameters_ref, "instance", None)
102
+ if not parameters_instance:
103
+ return None
104
+
105
+ custom_params = getattr(parameters_instance, "custom_parameters", None)
106
+ if not isinstance(custom_params, dict):
107
+ return None
108
+
109
+ json_schema = custom_params.get("json_schema")
110
+ if json_schema is None:
111
+ return None
112
+
113
+ # Normalize Pydantic models to JSON schema dicts before validation
114
+ # This handles cases like {"name": "...", "schema": SomePydanticModel}
115
+ json_schema = normalize_json(json_schema)
116
+
117
+ # After normalization, we expect a dict; anything else we ignore
118
+ if not isinstance(json_schema, dict):
119
+ return None
120
+
121
+ return json_schema
122
+
123
+
124
+ def _validate_json_schema_structure(schema: dict) -> None:
125
+ """
126
+ Validates the structure of a JSON schema using the jsonschema library.
127
+
128
+ This uses the JSON Schema meta-schema to validate that the provided schema
129
+ is a valid JSON Schema according to the specification.
130
+
131
+ Args:
132
+ schema: The JSON schema dictionary to validate
133
+
134
+ Raises:
135
+ jsonschema.exceptions.SchemaError: If the schema structure is invalid
136
+ """
137
+ jsonschema.Draft7Validator.check_schema(schema)
138
+
70
139
 
71
140
  class BaseInlinePromptNode(BasePromptNode[StateType], Generic[StateType]):
72
141
  """
@@ -87,7 +156,19 @@ class BaseInlinePromptNode(BasePromptNode[StateType], Generic[StateType]):
87
156
  blocks: ClassVar[List[PromptBlock]]
88
157
 
89
158
  # The functions/tools that a Prompt has access to
90
- functions: Optional[List[Union[FunctionDefinition, Callable]]] = None
159
+ functions: Optional[
160
+ List[
161
+ Union[
162
+ FunctionDefinition,
163
+ Callable,
164
+ DeploymentDefinition,
165
+ Type["BaseWorkflow"],
166
+ VellumIntegrationToolDefinition,
167
+ MCPServer,
168
+ MCPToolDefinition,
169
+ ]
170
+ ]
171
+ ] = None
91
172
 
92
173
  parameters: PromptParameters = DEFAULT_PROMPT_PARAMETERS
93
174
  expand_meta: Optional[AdHocExpandMeta] = AdHocExpandMeta(finish_reason=True)
@@ -159,6 +240,14 @@ class BaseInlinePromptNode(BasePromptNode[StateType], Generic[StateType]):
159
240
  normalized_functions.append(
160
241
  compile_vellum_integration_tool_definition(function, self._context.vellum_client)
161
242
  )
243
+ elif isinstance(function, MCPToolDefinition):
244
+ normalized_functions.append(
245
+ FunctionDefinition(
246
+ name=get_mcp_tool_name(function),
247
+ description=function.description,
248
+ parameters=function.parameters,
249
+ )
250
+ )
162
251
  elif isinstance(function, MCPServer):
163
252
  tool_definitions = compile_mcp_tool_definition(function)
164
253
  for tool_def in tool_definitions:
@@ -255,6 +344,8 @@ class BaseInlinePromptNode(BasePromptNode[StateType], Generic[StateType]):
255
344
  continue
256
345
  elif event.state == "STREAMING":
257
346
  yield BaseOutput(name="results", delta=event.output.value)
347
+ if event.output.type == "STRING":
348
+ yield BaseOutput(name="text", delta=event.output.value)
258
349
  elif event.state == "FULFILLED":
259
350
  if event.meta and event.meta.finish_reason == "LENGTH":
260
351
  text_value, json_value = process_additional_prompt_outputs(event.outputs)
@@ -465,3 +556,19 @@ class BaseInlinePromptNode(BasePromptNode[StateType], Generic[StateType]):
465
556
  Override this method to process the blocks before they are executed.
466
557
  """
467
558
  return blocks
559
+
560
+ @classmethod
561
+ def __validate__(cls) -> None:
562
+ """
563
+ Validates the node configuration, including JSON schema structure in parameters.
564
+
565
+ Raises:
566
+ jsonschema.exceptions.SchemaError: If the JSON schema structure is invalid
567
+ """
568
+ parameters_ref = getattr(cls, "parameters", None)
569
+ if parameters_ref is None:
570
+ return
571
+
572
+ schema = _get_json_schema_to_validate(parameters_ref)
573
+ if schema is not None:
574
+ _validate_json_schema_structure(schema)
@@ -1,3 +1,4 @@
1
+ from itertools import chain
1
2
  import json
2
3
  from uuid import UUID
3
4
  from typing import Any, ClassVar, Dict, Generator, Generic, Iterator, List, Optional, Sequence, Set, Union
@@ -113,12 +114,18 @@ class BasePromptDeploymentNode(BasePromptNode, Generic[StateType]):
113
114
  if prompt_event_stream is None:
114
115
  try:
115
116
  prompt_event_stream = self._get_prompt_event_stream()
116
- next(prompt_event_stream)
117
+ first_event = next(prompt_event_stream)
117
118
  except ApiError as e:
118
119
  if e.status_code and e.status_code < 500 and self.ml_model_fallbacks is not None:
119
120
  prompt_event_stream = self._retry_prompt_stream_with_fallbacks(tried_fallbacks)
120
121
  else:
121
122
  self._handle_api_error(e)
123
+ else:
124
+ if first_event.state == "REJECTED":
125
+ workflow_error = vellum_error_to_workflow_error(first_event.error)
126
+ raise NodeException.of(workflow_error)
127
+ if first_event.state != "INITIATED":
128
+ prompt_event_stream = chain([first_event], prompt_event_stream)
122
129
 
123
130
  outputs: Optional[List[PromptOutput]] = None
124
131
  if prompt_event_stream is not None:
@@ -127,6 +134,8 @@ class BasePromptDeploymentNode(BasePromptNode, Generic[StateType]):
127
134
  continue
128
135
  elif event.state == "STREAMING":
129
136
  yield BaseOutput(name="results", delta=event.output.value)
137
+ if event.output.type == "STRING":
138
+ yield BaseOutput(name="text", delta=event.output.value)
130
139
  elif event.state == "FULFILLED":
131
140
  outputs = event.outputs
132
141
  yield BaseOutput(name="results", value=event.outputs)
@@ -159,7 +168,9 @@ class BasePromptDeploymentNode(BasePromptNode, Generic[StateType]):
159
168
  try:
160
169
  tried_fallbacks.add(ml_model_fallback)
161
170
  prompt_event_stream = self._get_prompt_event_stream(ml_model_fallback=ml_model_fallback)
162
- next(prompt_event_stream)
171
+ first_event = next(prompt_event_stream)
172
+ if first_event.state != "INITIATED":
173
+ prompt_event_stream = chain([first_event], prompt_event_stream)
163
174
  return prompt_event_stream
164
175
  except ApiError:
165
176
  continue
@@ -104,7 +104,7 @@ class CodeExecutionNode(BaseNode[StateType], Generic[StateType, _OutputType], me
104
104
  output_type = self.__class__.get_output_type()
105
105
  code, filepath = self._resolve_code()
106
106
  if not self.packages and self.runtime == "PYTHON_3_11_6" and not self._has_secrets_in_code_inputs():
107
- logs, result = run_code_inline(code, self.code_inputs, output_type, filepath)
107
+ logs, result = run_code_inline(code, self.code_inputs, output_type, filepath, self._context.vellum_client)
108
108
  return self.Outputs(result=result, log=logs)
109
109
 
110
110
  else:
@@ -133,22 +133,16 @@ class CodeExecutionNode(BaseNode[StateType], Generic[StateType, _OutputType], me
133
133
  return self.Outputs(result=code_execution_result.output.value, log=code_execution_result.log)
134
134
 
135
135
  def _handle_api_error(self, e: ApiError) -> None:
136
- if e.status_code and e.status_code == 403 and isinstance(e.body, dict):
137
- raise NodeException(
138
- message=e.body.get("detail", "Provider credentials is missing or unavailable"),
139
- code=WorkflowErrorCode.PROVIDER_CREDENTIALS_UNAVAILABLE,
140
- ) from e
136
+ body = e.body if isinstance(e.body, dict) else {}
137
+ message = body.get("detail") or body.get("message") or "Failed to execute code"
141
138
 
142
- if e.status_code and e.status_code >= 400 and e.status_code < 500 and isinstance(e.body, dict):
143
- raise NodeException(
144
- message=e.body.get("message", e.body.get("detail", "Failed to execute code")),
145
- code=WorkflowErrorCode.INVALID_INPUTS,
146
- ) from e
139
+ if e.status_code == 400:
140
+ raise NodeException(message=message, code=WorkflowErrorCode.INVALID_INPUTS) from e
141
+
142
+ if e.status_code and e.status_code >= 500:
143
+ raise NodeException(message=message, code=WorkflowErrorCode.INTERNAL_ERROR) from e
147
144
 
148
- raise NodeException(
149
- message="Failed to execute code",
150
- code=WorkflowErrorCode.INTERNAL_ERROR,
151
- ) from e
145
+ raise NodeException(message=message, code=WorkflowErrorCode.NODE_EXECUTION) from e
152
146
 
153
147
  def _has_secrets_in_code_inputs(self) -> bool:
154
148
  """Check if any code_inputs contain VellumSecret instances that require API execution."""
@@ -1,12 +1,12 @@
1
1
  import pytest
2
+ from datetime import datetime
2
3
  import os
3
4
  import re
4
5
  from typing import Any, List, Union
5
6
 
6
7
  from pydantic import BaseModel
7
8
 
8
- from vellum import ArrayInput, CodeExecutorResponse, NumberVellumValue, StringInput, StringVellumValue
9
- from vellum.client.core.api_error import ApiError
9
+ from vellum import ArrayInput, CodeExecutorResponse, MlModelRead, NumberVellumValue, StringInput, StringVellumValue
10
10
  from vellum.client.errors.bad_request_error import BadRequestError
11
11
  from vellum.client.errors.forbidden_error import ForbiddenError
12
12
  from vellum.client.errors.internal_server_error import InternalServerError
@@ -24,6 +24,7 @@ from vellum.workflows.inputs.base import BaseInputs
24
24
  from vellum.workflows.nodes.displayable.code_execution_node import CodeExecutionNode
25
25
  from vellum.workflows.references.vellum_secret import VellumSecretReference
26
26
  from vellum.workflows.state.base import BaseState, StateMeta
27
+ from vellum.workflows.state.context import WorkflowContext
27
28
  from vellum.workflows.types.core import Json
28
29
 
29
30
 
@@ -551,6 +552,51 @@ def main(word: str) -> dict:
551
552
  }
552
553
 
553
554
 
555
+ def test_run_node__run_inline__vellum_client(vellum_client):
556
+ """Confirm that CodeExecutionNodes can convert a dict to a Pydantic model during inline execution."""
557
+
558
+ # GIVEN a node that subclasses CodeExecutionNode that returns a dict matching Any
559
+ vellum_client.ml_models.retrieve.return_value = MlModelRead(
560
+ id="test-ml-model-id",
561
+ name="Test ML Model",
562
+ description="Test ML Model Description",
563
+ introduced_on=datetime(2025, 1, 2),
564
+ )
565
+
566
+ class ExampleCodeExecutionNode(CodeExecutionNode[BaseState, Any]):
567
+ code = """\
568
+ def main(model_id: str) -> dict:
569
+ ml_model = vellum_client.ml_models.retrieve(model_id)
570
+ return {
571
+ "model_name": ml_model.name,
572
+ "model_id": ml_model.id,
573
+ }
574
+ """
575
+ runtime = "PYTHON_3_11_6"
576
+
577
+ code_inputs = {
578
+ "model_id": "test-ml-model-id",
579
+ }
580
+
581
+ # WHEN we run the node
582
+ node = ExampleCodeExecutionNode(context=WorkflowContext(vellum_client=vellum_client))
583
+ outputs = node.run()
584
+
585
+ # THEN the node should have produced the outputs we expect
586
+ assert outputs == {
587
+ "result": {
588
+ "model_name": "Test ML Model",
589
+ "model_id": "test-ml-model-id",
590
+ },
591
+ "log": "",
592
+ }
593
+
594
+ # AND
595
+ vellum_client.ml_models.retrieve.assert_called_once_with(
596
+ "test-ml-model-id",
597
+ )
598
+
599
+
554
600
  def test_run_node__array_input_with_vellum_values(vellum_client):
555
601
  """Confirm that CodeExecutionNodes can handle arrays containing VellumValue objects."""
556
602
 
@@ -796,34 +842,34 @@ Node.js v21.7.3
796
842
  assert exc_info.value.message == message
797
843
 
798
844
 
799
- def test_run_node__execute_code_api_fails_403__provider_credentials_unavailable(vellum_client):
800
- """
801
- Tests that a 403 error from the API is handled with PROVIDER_CREDENTIALS_UNAVAILABLE error code.
802
- """
845
+ def test_run_node__execute_code_api_fails_403__node_execution(vellum_client):
846
+ """Tests that a 403 error from the code execution API is handled with NODE_EXECUTION error code."""
803
847
 
848
+ # GIVEN a code execution node
804
849
  class ExampleCodeExecutionNode(CodeExecutionNode[BaseState, str]):
805
850
  code = "def main(): return 'test'"
806
851
  runtime = "PYTHON_3_11_6"
807
852
  packages = [CodeExecutionPackage(name="requests", version="2.28.0")]
808
853
 
854
+ # AND the API returns a 403 error
809
855
  vellum_client.execute_code.side_effect = ForbiddenError(
810
856
  body={
811
- "detail": "Provider credentials is missing or unavailable",
857
+ "detail": "Access denied to this resource",
812
858
  }
813
859
  )
814
860
 
861
+ # WHEN we run the node
815
862
  node = ExampleCodeExecutionNode()
816
863
  with pytest.raises(NodeException) as exc_info:
817
864
  node.run()
818
865
 
819
- assert exc_info.value.code == WorkflowErrorCode.PROVIDER_CREDENTIALS_UNAVAILABLE
820
- assert "Provider credentials is missing or unavailable" in exc_info.value.message
866
+ # THEN it should raise NODE_EXECUTION (not PROVIDER_CREDENTIALS_UNAVAILABLE)
867
+ assert exc_info.value.code == WorkflowErrorCode.NODE_EXECUTION
868
+ assert exc_info.value.message == "Access denied to this resource"
821
869
 
822
870
 
823
- def test_run_node__execute_code_api_fails_404__invalid_inputs(vellum_client):
824
- """
825
- Tests that a 404 error from the API is handled with INVALID_INPUTS error code.
826
- """
871
+ def test_run_node__execute_code_api_fails_404__node_execution(vellum_client):
872
+ """Tests that a 404 error from the API is handled with NODE_EXECUTION error code."""
827
873
 
828
874
  class ExampleCodeExecutionNode(CodeExecutionNode[BaseState, str]):
829
875
  code = "def main(): return 'test'"
@@ -840,14 +886,12 @@ def test_run_node__execute_code_api_fails_404__invalid_inputs(vellum_client):
840
886
  with pytest.raises(NodeException) as exc_info:
841
887
  node.run()
842
888
 
843
- assert exc_info.value.code == WorkflowErrorCode.INVALID_INPUTS
889
+ assert exc_info.value.code == WorkflowErrorCode.NODE_EXECUTION
844
890
  assert "Resource not found" in exc_info.value.message
845
891
 
846
892
 
847
893
  def test_run_node__execute_code_api_fails_500__internal_error(vellum_client):
848
- """
849
- Tests that a 500 error from the API is handled with INTERNAL_ERROR error code.
850
- """
894
+ """Tests that a 500 error from the API is handled with INTERNAL_ERROR error code."""
851
895
 
852
896
  class ExampleCodeExecutionNode(CodeExecutionNode[BaseState, str]):
853
897
  code = "def main(): return 'test'"
@@ -865,21 +909,18 @@ def test_run_node__execute_code_api_fails_500__internal_error(vellum_client):
865
909
  node.run()
866
910
 
867
911
  assert exc_info.value.code == WorkflowErrorCode.INTERNAL_ERROR
868
- assert exc_info.value.message == "Failed to execute code"
912
+ assert exc_info.value.message == "Internal server error occurred"
869
913
 
870
914
 
871
- def test_run_node__execute_code_api_fails_4xx_no_message__uses_detail(vellum_client):
872
- """
873
- Tests that a 4xx error without a 'message' field falls back to 'detail' field.
874
- """
915
+ def test_run_node__execute_code_api_fails_400__invalid_inputs(vellum_client):
916
+ """Tests that a 400 error from the API is handled with INVALID_INPUTS error code."""
875
917
 
876
918
  class ExampleCodeExecutionNode(CodeExecutionNode[BaseState, str]):
877
919
  code = "def main(): return 'test'"
878
920
  runtime = "PYTHON_3_11_6"
879
921
  packages = [CodeExecutionPackage(name="requests", version="2.28.0")]
880
922
 
881
- vellum_client.execute_code.side_effect = ApiError(
882
- status_code=422,
923
+ vellum_client.execute_code.side_effect = BadRequestError(
883
924
  body={
884
925
  "detail": "Invalid request parameters",
885
926
  },
@@ -4,6 +4,7 @@ import sys
4
4
  import traceback
5
5
  from typing import Any, Optional, Tuple, Union
6
6
 
7
+ from vellum import Vellum
7
8
  from vellum.workflows.constants import undefined
8
9
  from vellum.workflows.errors.types import WorkflowErrorCode
9
10
  from vellum.workflows.exceptions import NodeException
@@ -41,6 +42,7 @@ def run_code_inline(
41
42
  inputs: EntityInputsInterface,
42
43
  output_type: Any,
43
44
  filepath: str,
45
+ vellum_client: Vellum,
44
46
  ) -> Tuple[str, Any]:
45
47
  log_buffer = io.StringIO()
46
48
 
@@ -54,6 +56,7 @@ def run_code_inline(
54
56
  "__arg__inputs": wrapped_inputs,
55
57
  "__arg__out": None,
56
58
  "print": _inline_print,
59
+ "vellum_client": vellum_client,
57
60
  }
58
61
  run_args = [f"{name}=__arg__inputs['{name}']" for name, value in inputs.items() if value is not undefined]
59
62
  execution_code = f"""\