vellum-ai 1.11.2__py3-none-any.whl → 1.13.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of vellum-ai might be problematic. Click here for more details.

Files changed (275) hide show
  1. vellum/__init__.py +18 -0
  2. vellum/client/README.md +1 -1
  3. vellum/client/core/client_wrapper.py +2 -2
  4. vellum/client/core/force_multipart.py +4 -2
  5. vellum/client/core/http_response.py +1 -1
  6. vellum/client/core/pydantic_utilities.py +7 -4
  7. vellum/client/errors/too_many_requests_error.py +1 -2
  8. vellum/client/reference.md +677 -76
  9. vellum/client/resources/container_images/client.py +299 -0
  10. vellum/client/resources/container_images/raw_client.py +286 -0
  11. vellum/client/resources/documents/client.py +20 -10
  12. vellum/client/resources/documents/raw_client.py +20 -10
  13. vellum/client/resources/events/raw_client.py +4 -4
  14. vellum/client/resources/integration_auth_configs/client.py +2 -0
  15. vellum/client/resources/integration_auth_configs/raw_client.py +2 -0
  16. vellum/client/resources/integration_providers/client.py +28 -2
  17. vellum/client/resources/integration_providers/raw_client.py +24 -0
  18. vellum/client/resources/integrations/client.py +52 -4
  19. vellum/client/resources/integrations/raw_client.py +61 -0
  20. vellum/client/resources/workflow_deployments/client.py +156 -0
  21. vellum/client/resources/workflow_deployments/raw_client.py +334 -0
  22. vellum/client/resources/workflows/client.py +212 -8
  23. vellum/client/resources/workflows/raw_client.py +343 -6
  24. vellum/client/types/__init__.py +18 -0
  25. vellum/client/types/api_actor_type_enum.py +1 -1
  26. vellum/client/types/check_workflow_execution_status_error.py +21 -0
  27. vellum/client/types/check_workflow_execution_status_response.py +29 -0
  28. vellum/client/types/code_execution_package_request.py +21 -0
  29. vellum/client/types/composio_execute_tool_request.py +5 -0
  30. vellum/client/types/composio_tool_definition.py +1 -0
  31. vellum/client/types/container_image_build_config.py +1 -0
  32. vellum/client/types/container_image_container_image_tag.py +1 -0
  33. vellum/client/types/dataset_row_push_request.py +3 -0
  34. vellum/client/types/document_document_to_document_index.py +1 -0
  35. vellum/client/types/integration_name.py +24 -0
  36. vellum/client/types/node_execution_fulfilled_body.py +1 -0
  37. vellum/client/types/node_execution_log_body.py +24 -0
  38. vellum/client/types/node_execution_log_event.py +47 -0
  39. vellum/client/types/prompt_deployment_release_prompt_deployment.py +1 -0
  40. vellum/client/types/runner_config_request.py +24 -0
  41. vellum/client/types/severity_enum.py +5 -0
  42. vellum/client/types/slim_composio_tool_definition.py +1 -0
  43. vellum/client/types/slim_document_document_to_document_index.py +2 -0
  44. vellum/client/types/type_checker_enum.py +5 -0
  45. vellum/client/types/vellum_audio.py +5 -1
  46. vellum/client/types/vellum_audio_request.py +5 -1
  47. vellum/client/types/vellum_document.py +5 -1
  48. vellum/client/types/vellum_document_request.py +5 -1
  49. vellum/client/types/vellum_image.py +5 -1
  50. vellum/client/types/vellum_image_request.py +5 -1
  51. vellum/client/types/vellum_node_execution_event.py +2 -0
  52. vellum/client/types/vellum_variable.py +5 -0
  53. vellum/client/types/vellum_variable_extensions.py +1 -0
  54. vellum/client/types/vellum_variable_type.py +1 -0
  55. vellum/client/types/vellum_video.py +5 -1
  56. vellum/client/types/vellum_video_request.py +5 -1
  57. vellum/client/types/workflow_deployment_release_workflow_deployment.py +1 -0
  58. vellum/client/types/workflow_event.py +2 -0
  59. vellum/client/types/workflow_execution_fulfilled_body.py +1 -0
  60. vellum/client/types/workflow_result_event_output_data_array.py +1 -1
  61. vellum/client/types/workflow_result_event_output_data_chat_history.py +1 -1
  62. vellum/client/types/workflow_result_event_output_data_error.py +1 -1
  63. vellum/client/types/workflow_result_event_output_data_function_call.py +1 -1
  64. vellum/client/types/workflow_result_event_output_data_json.py +1 -1
  65. vellum/client/types/workflow_result_event_output_data_number.py +1 -1
  66. vellum/client/types/workflow_result_event_output_data_search_results.py +1 -1
  67. vellum/client/types/workflow_result_event_output_data_string.py +1 -1
  68. vellum/client/types/workflow_sandbox_execute_node_response.py +8 -0
  69. vellum/plugins/vellum_mypy.py +37 -2
  70. vellum/types/check_workflow_execution_status_error.py +3 -0
  71. vellum/types/check_workflow_execution_status_response.py +3 -0
  72. vellum/types/code_execution_package_request.py +3 -0
  73. vellum/types/node_execution_log_body.py +3 -0
  74. vellum/types/node_execution_log_event.py +3 -0
  75. vellum/types/runner_config_request.py +3 -0
  76. vellum/types/severity_enum.py +3 -0
  77. vellum/types/type_checker_enum.py +3 -0
  78. vellum/types/workflow_sandbox_execute_node_response.py +3 -0
  79. vellum/utils/files/mixin.py +26 -0
  80. vellum/utils/files/tests/test_mixin.py +62 -0
  81. vellum/utils/tests/test_vellum_client.py +95 -0
  82. vellum/utils/uuid.py +19 -2
  83. vellum/utils/vellum_client.py +10 -3
  84. vellum/workflows/__init__.py +7 -1
  85. vellum/workflows/descriptors/base.py +86 -0
  86. vellum/workflows/descriptors/tests/test_utils.py +9 -0
  87. vellum/workflows/errors/tests/__init__.py +0 -0
  88. vellum/workflows/errors/tests/test_types.py +52 -0
  89. vellum/workflows/errors/types.py +1 -0
  90. vellum/workflows/events/node.py +24 -0
  91. vellum/workflows/events/tests/test_event.py +123 -0
  92. vellum/workflows/events/types.py +2 -1
  93. vellum/workflows/events/workflow.py +28 -2
  94. vellum/workflows/expressions/add.py +3 -0
  95. vellum/workflows/expressions/tests/test_add.py +24 -0
  96. vellum/workflows/graph/graph.py +26 -5
  97. vellum/workflows/graph/tests/test_graph.py +228 -1
  98. vellum/workflows/inputs/base.py +22 -6
  99. vellum/workflows/inputs/dataset_row.py +121 -16
  100. vellum/workflows/inputs/tests/test_inputs.py +3 -3
  101. vellum/workflows/integrations/tests/test_vellum_integration_service.py +84 -0
  102. vellum/workflows/integrations/vellum_integration_service.py +12 -1
  103. vellum/workflows/loaders/base.py +2 -0
  104. vellum/workflows/nodes/bases/base.py +37 -16
  105. vellum/workflows/nodes/bases/tests/test_base_node.py +104 -1
  106. vellum/workflows/nodes/core/inline_subworkflow_node/node.py +1 -0
  107. vellum/workflows/nodes/core/inline_subworkflow_node/tests/test_node.py +1 -1
  108. vellum/workflows/nodes/core/map_node/node.py +7 -5
  109. vellum/workflows/nodes/core/map_node/tests/test_node.py +33 -0
  110. vellum/workflows/nodes/core/retry_node/node.py +1 -0
  111. vellum/workflows/nodes/core/try_node/node.py +1 -0
  112. vellum/workflows/nodes/displayable/api_node/node.py +3 -2
  113. vellum/workflows/nodes/displayable/api_node/tests/test_api_node.py +38 -0
  114. vellum/workflows/nodes/displayable/bases/api_node/node.py +1 -1
  115. vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py +18 -1
  116. vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +109 -2
  117. vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py +13 -2
  118. vellum/workflows/nodes/displayable/code_execution_node/node.py +9 -15
  119. vellum/workflows/nodes/displayable/code_execution_node/tests/test_node.py +65 -24
  120. vellum/workflows/nodes/displayable/code_execution_node/utils.py +3 -0
  121. vellum/workflows/nodes/displayable/final_output_node/node.py +24 -69
  122. vellum/workflows/nodes/displayable/final_output_node/tests/test_node.py +53 -3
  123. vellum/workflows/nodes/displayable/note_node/node.py +4 -1
  124. vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +16 -5
  125. vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py +47 -0
  126. vellum/workflows/nodes/displayable/tool_calling_node/node.py +74 -34
  127. vellum/workflows/nodes/displayable/tool_calling_node/tests/test_node.py +204 -8
  128. vellum/workflows/nodes/displayable/tool_calling_node/utils.py +92 -71
  129. vellum/workflows/nodes/mocks.py +47 -213
  130. vellum/workflows/nodes/tests/test_mocks.py +0 -177
  131. vellum/workflows/nodes/utils.py +23 -8
  132. vellum/workflows/outputs/base.py +36 -3
  133. vellum/workflows/references/environment_variable.py +1 -11
  134. vellum/workflows/references/lazy.py +8 -0
  135. vellum/workflows/references/state_value.py +24 -1
  136. vellum/workflows/references/tests/test_lazy.py +58 -0
  137. vellum/workflows/references/trigger.py +8 -3
  138. vellum/workflows/references/workflow_input.py +8 -0
  139. vellum/workflows/resolvers/resolver.py +13 -3
  140. vellum/workflows/resolvers/tests/test_resolver.py +31 -0
  141. vellum/workflows/runner/runner.py +159 -14
  142. vellum/workflows/runner/tests/__init__.py +0 -0
  143. vellum/workflows/runner/tests/test_runner.py +170 -0
  144. vellum/workflows/sandbox.py +7 -8
  145. vellum/workflows/state/base.py +89 -30
  146. vellum/workflows/state/context.py +74 -3
  147. vellum/workflows/state/tests/test_state.py +269 -1
  148. vellum/workflows/tests/test_dataset_row.py +8 -7
  149. vellum/workflows/tests/test_sandbox.py +97 -8
  150. vellum/workflows/triggers/__init__.py +2 -1
  151. vellum/workflows/triggers/base.py +160 -28
  152. vellum/workflows/triggers/chat_message.py +141 -0
  153. vellum/workflows/triggers/integration.py +12 -0
  154. vellum/workflows/triggers/manual.py +3 -1
  155. vellum/workflows/triggers/schedule.py +3 -1
  156. vellum/workflows/triggers/tests/test_chat_message.py +257 -0
  157. vellum/workflows/types/core.py +18 -0
  158. vellum/workflows/types/definition.py +6 -13
  159. vellum/workflows/types/generics.py +12 -0
  160. vellum/workflows/types/tests/test_utils.py +12 -0
  161. vellum/workflows/types/utils.py +32 -2
  162. vellum/workflows/types/workflow_metadata.py +124 -0
  163. vellum/workflows/utils/functions.py +152 -16
  164. vellum/workflows/utils/pydantic_schema.py +19 -1
  165. vellum/workflows/utils/tests/test_functions.py +123 -8
  166. vellum/workflows/utils/tests/test_validate.py +79 -0
  167. vellum/workflows/utils/tests/test_vellum_variables.py +62 -2
  168. vellum/workflows/utils/uuids.py +90 -0
  169. vellum/workflows/utils/validate.py +108 -0
  170. vellum/workflows/utils/vellum_variables.py +96 -16
  171. vellum/workflows/workflows/base.py +177 -35
  172. vellum/workflows/workflows/tests/test_base_workflow.py +51 -0
  173. {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/METADATA +6 -1
  174. {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/RECORD +274 -227
  175. vellum_cli/__init__.py +21 -0
  176. vellum_cli/config.py +16 -2
  177. vellum_cli/pull.py +2 -0
  178. vellum_cli/push.py +23 -10
  179. vellum_cli/tests/conftest.py +8 -13
  180. vellum_cli/tests/test_image_push.py +4 -11
  181. vellum_cli/tests/test_pull.py +83 -68
  182. vellum_cli/tests/test_push.py +251 -2
  183. vellum_ee/assets/node-definitions.json +225 -12
  184. vellum_ee/scripts/generate_node_definitions.py +15 -3
  185. vellum_ee/workflows/display/base.py +4 -3
  186. vellum_ee/workflows/display/nodes/base_node_display.py +44 -11
  187. vellum_ee/workflows/display/nodes/tests/test_base_node_display.py +93 -0
  188. vellum_ee/workflows/display/nodes/types.py +1 -0
  189. vellum_ee/workflows/display/nodes/vellum/__init__.py +0 -2
  190. vellum_ee/workflows/display/nodes/vellum/base_adornment_node.py +5 -2
  191. vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +1 -1
  192. vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +10 -2
  193. vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +17 -14
  194. vellum_ee/workflows/display/nodes/vellum/map_node.py +2 -0
  195. vellum_ee/workflows/display/nodes/vellum/note_node.py +18 -3
  196. vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py +37 -14
  197. vellum_ee/workflows/display/nodes/vellum/tests/test_code_execution_node.py +62 -2
  198. vellum_ee/workflows/display/nodes/vellum/tests/test_final_output_node.py +136 -0
  199. vellum_ee/workflows/display/nodes/vellum/tests/test_note_node.py +44 -7
  200. vellum_ee/workflows/display/nodes/vellum/tests/test_prompt_node.py +5 -13
  201. vellum_ee/workflows/display/nodes/vellum/tests/test_subworkflow_deployment_node.py +27 -17
  202. vellum_ee/workflows/display/nodes/vellum/tests/test_tool_calling_node.py +145 -22
  203. vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py +107 -2
  204. vellum_ee/workflows/display/nodes/vellum/utils.py +54 -12
  205. vellum_ee/workflows/display/tests/test_base_workflow_display.py +13 -16
  206. vellum_ee/workflows/display/tests/test_json_schema_validation.py +190 -0
  207. vellum_ee/workflows/display/tests/test_mocks.py +912 -0
  208. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +14 -2
  209. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attributes_serialization.py +109 -0
  210. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_outputs_serialization.py +3 -0
  211. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_ports_serialization.py +187 -1
  212. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_api_node_serialization.py +34 -325
  213. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +42 -393
  214. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +13 -315
  215. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_default_state_serialization.py +2 -122
  216. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py +24 -115
  217. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_generic_node_serialization.py +4 -93
  218. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_guardrail_node_serialization.py +7 -80
  219. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_prompt_node_serialization.py +9 -101
  220. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +77 -308
  221. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +62 -324
  222. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +3 -82
  223. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_prompt_deployment_serialization.py +4 -142
  224. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_search_node_serialization.py +1 -61
  225. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_set_state_node_serialization.py +4 -4
  226. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_subworkflow_deployment_serialization.py +205 -134
  227. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_node_serialization.py +34 -146
  228. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py +2 -0
  229. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_composio_serialization.py +8 -6
  230. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_serialization.py +137 -266
  231. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_tool_wrapper_serialization.py +84 -0
  232. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_mcp_serialization.py +55 -16
  233. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py +15 -1
  234. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_tool_wrapper_serialization.py +71 -0
  235. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_vellum_integration_serialization.py +119 -0
  236. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_workflow_deployment_serialization.py +1 -1
  237. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py +0 -2
  238. vellum_ee/workflows/display/tests/workflow_serialization/test_chat_message_dict_reference_serialization.py +22 -1
  239. vellum_ee/workflows/display/tests/workflow_serialization/test_chat_message_trigger_serialization.py +412 -0
  240. vellum_ee/workflows/display/tests/workflow_serialization/test_code_tool_node_reference_error.py +106 -0
  241. vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +9 -41
  242. vellum_ee/workflows/display/tests/workflow_serialization/test_duplicate_trigger_name_validation.py +208 -0
  243. vellum_ee/workflows/display/tests/workflow_serialization/test_final_output_node_not_referenced_by_workflow_outputs.py +45 -0
  244. vellum_ee/workflows/display/tests/workflow_serialization/test_infinite_loop_validation.py +66 -0
  245. vellum_ee/workflows/display/tests/workflow_serialization/test_int_input_serialization.py +40 -0
  246. vellum_ee/workflows/display/tests/workflow_serialization/test_integration_trigger_serialization.py +8 -14
  247. vellum_ee/workflows/display/tests/workflow_serialization/test_integration_trigger_validation.py +173 -0
  248. vellum_ee/workflows/display/tests/workflow_serialization/test_integration_trigger_with_entrypoint_node_id.py +16 -13
  249. vellum_ee/workflows/display/tests/workflow_serialization/test_list_vellum_document_serialization.py +5 -1
  250. vellum_ee/workflows/display/tests/workflow_serialization/test_manual_trigger_serialization.py +12 -2
  251. vellum_ee/workflows/display/tests/workflow_serialization/test_multi_trigger_same_node_serialization.py +111 -0
  252. vellum_ee/workflows/display/tests/workflow_serialization/test_no_triggers_no_entrypoint_validation.py +64 -0
  253. vellum_ee/workflows/display/tests/workflow_serialization/test_partial_workflow_meta_display_override.py +55 -0
  254. vellum_ee/workflows/display/tests/workflow_serialization/test_sandbox_dataset_mocks_serialization.py +268 -0
  255. vellum_ee/workflows/display/tests/workflow_serialization/test_sandbox_invalid_pdf_data_url.py +49 -0
  256. vellum_ee/workflows/display/tests/workflow_serialization/test_sandbox_validation_errors.py +112 -0
  257. vellum_ee/workflows/display/tests/workflow_serialization/test_scheduled_trigger_serialization.py +25 -16
  258. vellum_ee/workflows/display/tests/workflow_serialization/test_terminal_node_in_unused_graphs_serialization.py +53 -0
  259. vellum_ee/workflows/display/utils/exceptions.py +34 -0
  260. vellum_ee/workflows/display/utils/expressions.py +463 -52
  261. vellum_ee/workflows/display/utils/metadata.py +98 -33
  262. vellum_ee/workflows/display/utils/tests/test_metadata.py +31 -0
  263. vellum_ee/workflows/display/utils/triggers.py +153 -0
  264. vellum_ee/workflows/display/utils/vellum.py +59 -5
  265. vellum_ee/workflows/display/workflows/base_workflow_display.py +656 -254
  266. vellum_ee/workflows/display/workflows/get_vellum_workflow_display_class.py +26 -0
  267. vellum_ee/workflows/display/workflows/tests/test_workflow_display.py +77 -29
  268. vellum_ee/workflows/server/namespaces.py +18 -0
  269. vellum_ee/workflows/tests/test_display_meta.py +2 -0
  270. vellum_ee/workflows/tests/test_serialize_module.py +174 -7
  271. vellum_ee/workflows/tests/test_server.py +0 -3
  272. vellum_ee/workflows/display/nodes/vellum/function_node.py +0 -14
  273. {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/LICENSE +0 -0
  274. {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/WHEEL +0 -0
  275. {vellum_ai-1.11.2.dist-info → vellum_ai-1.13.5.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,257 @@
1
+ """Tests for ChatMessageTrigger."""
2
+
3
+ import pytest
4
+ from typing import List, Optional
5
+
6
+ from pydantic import Field
7
+
8
+ from vellum.client.types import (
9
+ ArrayChatMessageContent,
10
+ ArrayChatMessageContentItem,
11
+ ChatMessage,
12
+ ImageChatMessageContent,
13
+ StringChatMessageContent,
14
+ StringVellumValue,
15
+ VellumImage,
16
+ )
17
+ from vellum.workflows.inputs.base import BaseInputs
18
+ from vellum.workflows.nodes.bases.base import BaseNode
19
+ from vellum.workflows.outputs import BaseOutputs
20
+ from vellum.workflows.state.base import BaseState
21
+ from vellum.workflows.triggers.chat_message import ChatMessageTrigger
22
+ from vellum.workflows.workflows.base import BaseWorkflow
23
+
24
+
25
+ class ChatState(BaseState):
26
+ chat_history: List[ChatMessage] = Field(default_factory=list)
27
+
28
+
29
+ @pytest.mark.parametrize(
30
+ ["message", "expected_content"],
31
+ [
32
+ pytest.param(
33
+ [StringChatMessageContent(value="Hello, world!")],
34
+ ArrayChatMessageContent(value=[StringChatMessageContent(value="Hello, world!")]),
35
+ id="text_string_content",
36
+ ),
37
+ pytest.param(
38
+ [ImageChatMessageContent(value=VellumImage(src="https://example.com/image.jpg"))],
39
+ ArrayChatMessageContent(
40
+ value=[ImageChatMessageContent(value=VellumImage(src="https://example.com/image.jpg"))]
41
+ ),
42
+ id="multimodal_image",
43
+ ),
44
+ pytest.param(
45
+ [
46
+ StringChatMessageContent(value="Look at this image:"),
47
+ ImageChatMessageContent(value=VellumImage(src="https://example.com/image.jpg")),
48
+ ],
49
+ ArrayChatMessageContent(
50
+ value=[
51
+ StringChatMessageContent(value="Look at this image:"),
52
+ ImageChatMessageContent(value=VellumImage(src="https://example.com/image.jpg")),
53
+ ]
54
+ ),
55
+ id="text_and_multimodal_array",
56
+ ),
57
+ ],
58
+ )
59
+ def test_chat_message_trigger__initiated(
60
+ message: List[ArrayChatMessageContentItem],
61
+ expected_content: ArrayChatMessageContent,
62
+ ):
63
+ """Tests that ChatMessageTrigger appends user message on workflow initiation."""
64
+
65
+ # GIVEN a ChatMessageTrigger with a message
66
+ trigger = ChatMessageTrigger(message=message)
67
+
68
+ # AND a state with chat_history
69
+ state = ChatState()
70
+
71
+ # WHEN the initiated lifecycle hook is called
72
+ trigger.__on_workflow_initiated__(state)
73
+
74
+ # THEN the user message is appended to chat_history
75
+ assert len(state.chat_history) == 1
76
+ assert state.chat_history[0].role == "USER"
77
+ assert state.chat_history[0].text is None
78
+ assert state.chat_history[0].content == expected_content
79
+
80
+
81
+ def test_chat_message_trigger__state_without_chat_history():
82
+ """Tests that ChatMessageTrigger handles state without chat_history gracefully."""
83
+
84
+ # GIVEN a ChatMessageTrigger with a message
85
+ trigger = ChatMessageTrigger(message=[StringChatMessageContent(value="Hello")])
86
+
87
+ # AND a state without chat_history attribute
88
+ state = BaseState()
89
+
90
+ # WHEN the initiated lifecycle hook is called
91
+ trigger.__on_workflow_initiated__(state)
92
+
93
+ # THEN no error is raised and state is unchanged
94
+ assert not hasattr(state, "chat_history")
95
+
96
+
97
+ def test_chat_message_trigger__graph_syntax():
98
+ """Tests that ChatMessageTrigger can be used in graph syntax."""
99
+
100
+ # GIVEN a ChatMessageTrigger and a node
101
+ class TestNode(BaseNode):
102
+ pass
103
+
104
+ # WHEN we use trigger >> node syntax
105
+ graph = ChatMessageTrigger >> TestNode
106
+
107
+ # THEN a graph is created with the trigger edge
108
+ assert graph is not None
109
+ assert len(list(graph.trigger_edges)) == 1
110
+ assert list(graph.trigger_edges)[0].trigger_class == ChatMessageTrigger
111
+ assert list(graph.trigger_edges)[0].to_node == TestNode
112
+
113
+
114
+ def test_chat_message_trigger__converts_vellum_value_objects():
115
+ """Tests that ChatMessageTrigger converts VellumValue objects to ChatMessageContent."""
116
+
117
+ # GIVEN a message as VellumValue objects (as received from codegen/API)
118
+ vellum_value_message = [
119
+ StringVellumValue(type="STRING", value="Hello, world!"),
120
+ ]
121
+
122
+ # WHEN a ChatMessageTrigger is created with VellumValue objects
123
+ trigger = ChatMessageTrigger(message=vellum_value_message)
124
+
125
+ # THEN the message is converted to ChatMessageContent
126
+ assert len(trigger.message) == 1
127
+ assert isinstance(trigger.message[0], StringChatMessageContent)
128
+ assert trigger.message[0].value == "Hello, world!"
129
+
130
+ # AND the trigger works correctly with state
131
+ state = ChatState()
132
+ trigger.__on_workflow_initiated__(state)
133
+
134
+ assert len(state.chat_history) == 1
135
+ assert state.chat_history[0].role == "USER"
136
+ assert state.chat_history[0].content == ArrayChatMessageContent(
137
+ value=[StringChatMessageContent(value="Hello, world!")]
138
+ )
139
+
140
+
141
+ def test_chat_message_trigger__converts_dict_format():
142
+ """Tests that ChatMessageTrigger converts dict format to ChatMessageContent."""
143
+
144
+ # GIVEN a message as dict objects (alternative API format)
145
+ dict_message = [
146
+ {"type": "STRING", "value": "Hello from dict!"},
147
+ ]
148
+
149
+ # WHEN a ChatMessageTrigger is created with dict objects
150
+ trigger = ChatMessageTrigger(message=dict_message)
151
+
152
+ # THEN the message is converted to ChatMessageContent
153
+ assert len(trigger.message) == 1
154
+ assert isinstance(trigger.message[0], StringChatMessageContent)
155
+ assert trigger.message[0].value == "Hello from dict!"
156
+
157
+
158
+ def test_chat_message_trigger__converts_string_message():
159
+ """Tests that ChatMessageTrigger converts string messages to ChatMessageContent list."""
160
+
161
+ # GIVEN a message as a string
162
+ string_message = "Hello, world!"
163
+
164
+ # WHEN a ChatMessageTrigger is created with a string message
165
+ trigger = ChatMessageTrigger(message=string_message)
166
+
167
+ # THEN the message is converted to a list with a single StringChatMessageContent
168
+ assert len(trigger.message) == 1
169
+ assert isinstance(trigger.message[0], StringChatMessageContent)
170
+ assert trigger.message[0].value == "Hello, world!"
171
+
172
+ # AND the trigger works correctly with state
173
+ state = ChatState()
174
+ trigger.__on_workflow_initiated__(state)
175
+
176
+ assert len(state.chat_history) == 1
177
+ assert state.chat_history[0].role == "USER"
178
+ assert state.chat_history[0].content == ArrayChatMessageContent(
179
+ value=[StringChatMessageContent(value="Hello, world!")]
180
+ )
181
+
182
+
183
+ def test_chat_message_trigger__raw_string_array__workflow_stream():
184
+ """Tests that workflow.stream() works with raw string array after deserialize_trigger."""
185
+
186
+ # GIVEN a workflow with ChatMessageTrigger
187
+ class TestNode(BaseNode):
188
+ class Outputs(BaseOutputs):
189
+ result: str
190
+
191
+ def run(self) -> Outputs:
192
+ return self.Outputs(result="success")
193
+
194
+ class TestWorkflow(BaseWorkflow):
195
+ graph = ChatMessageTrigger >> TestNode
196
+
197
+ # AND a raw string array input (not wrapped in VellumValue format)
198
+ raw_string_array_input = {"message": ["Hello", "World"]}
199
+
200
+ # WHEN we deserialize the trigger with raw string array
201
+ trigger = TestWorkflow.deserialize_trigger(
202
+ trigger_id=ChatMessageTrigger.__id__,
203
+ inputs=raw_string_array_input,
204
+ )
205
+
206
+ # THEN the trigger is properly deserialized
207
+ assert isinstance(trigger, ChatMessageTrigger)
208
+ assert len(trigger.message) == 2
209
+ assert isinstance(trigger.message[0], StringChatMessageContent)
210
+ assert trigger.message[0].value == "Hello"
211
+ assert isinstance(trigger.message[1], StringChatMessageContent)
212
+ assert trigger.message[1].value == "World"
213
+
214
+ # AND the workflow can be streamed successfully
215
+ workflow = TestWorkflow()
216
+ events = list(workflow.stream(trigger=trigger))
217
+
218
+ # THEN the workflow completes successfully
219
+ assert len(events) > 0
220
+
221
+
222
+ def test_chat_message_trigger__chat_history_defaulted_to_none():
223
+ """Tests that workflow runs successfully when chat_history is defaulted to None."""
224
+
225
+ # GIVEN a state with chat_history defaulted to None
226
+ class NullableChatHistoryState(BaseState):
227
+ chat_history: Optional[List[ChatMessage]] = None
228
+
229
+ # AND a simple node
230
+ class TestNode(BaseNode):
231
+ class Outputs(BaseOutputs):
232
+ result: str
233
+
234
+ def run(self) -> Outputs:
235
+ return self.Outputs(result="success")
236
+
237
+ # AND a workflow with ChatMessageTrigger and the nullable chat_history state
238
+ class TestWorkflow(BaseWorkflow[BaseInputs, NullableChatHistoryState]):
239
+ graph = ChatMessageTrigger >> TestNode
240
+
241
+ # WHEN we run the workflow with a trigger
242
+ trigger = ChatMessageTrigger(message="Hello")
243
+ workflow = TestWorkflow()
244
+ terminal_event = workflow.run(trigger=trigger)
245
+
246
+ # THEN the workflow completes successfully
247
+ assert terminal_event.name == "workflow.execution.fulfilled"
248
+
249
+ # AND the final state has a contentful chat_history
250
+ final_state = terminal_event.final_state
251
+ assert final_state is not None
252
+ assert final_state.chat_history is not None
253
+ assert len(final_state.chat_history) == 1
254
+ assert final_state.chat_history[0].role == "USER"
255
+ assert final_state.chat_history[0].content == ArrayChatMessageContent(
256
+ value=[StringChatMessageContent(value="Hello")]
257
+ )
@@ -4,6 +4,7 @@ from threading import Event as ThreadingEvent
4
4
  from typing import ( # type: ignore[attr-defined]
5
5
  Any,
6
6
  Dict,
7
+ Iterable,
7
8
  List,
8
9
  Union,
9
10
  _GenericAlias,
@@ -17,6 +18,23 @@ JsonArray = List["Json"]
17
18
  JsonObject = Dict[str, "Json"]
18
19
  Json = Union[None, bool, int, float, str, JsonArray, JsonObject]
19
20
 
21
+
22
+ def is_json_type(types: Iterable) -> bool:
23
+ # Check explicitly for our internal JSON type.
24
+ # Matches the type found at vellum.workflows.types.core.Json
25
+ actual_types_with_explicit_ref = [
26
+ bool,
27
+ int,
28
+ float,
29
+ str,
30
+ List[Json],
31
+ Dict[str, Json],
32
+ ]
33
+ with_none = [type(None), *actual_types_with_explicit_ref]
34
+ types_list = list(types)
35
+ return types_list == actual_types_with_explicit_ref or types_list == with_none
36
+
37
+
20
38
  CancelSignal = Union[ThreadingEvent, MultiprocessingEvent]
21
39
 
22
40
  # Unions and Generics inherit from `_GenericAlias` instead of `type`
@@ -181,8 +181,11 @@ class VellumIntegrationToolDefinition(UniversalBaseModel):
181
181
  integration_name: str # "GITHUB", "SLACK", etc.
182
182
  name: str # Specific action like "GITHUB_CREATE_AN_ISSUE"
183
183
 
184
- # Required for tool base consistency
185
- description: str
184
+ # Optional description for tool base consistency
185
+ description: str = ""
186
+
187
+ # Optional toolkit version for pinning
188
+ toolkit_version: Optional[str] = None
186
189
 
187
190
 
188
191
  class VellumIntegrationToolDetails(VellumIntegrationToolDefinition):
@@ -208,16 +211,6 @@ class MCPServer(UniversalBaseModel):
208
211
 
209
212
  model_config = {"arbitrary_types_allowed": True}
210
213
 
211
- def __setattr__(self, name: str, value: Any) -> None:
212
- """Override to automatically set serialization flags for environment variables."""
213
- super().__setattr__(name, value)
214
-
215
- if name == "bearer_token_value" and isinstance(value, EnvironmentVariableReference):
216
- value.serialize_as_constant = True
217
-
218
- if name == "api_key_header_value" and isinstance(value, EnvironmentVariableReference):
219
- value.serialize_as_constant = True
220
-
221
214
 
222
215
  class MCPToolDefinition(UniversalBaseModel):
223
216
  name: str
@@ -234,4 +227,4 @@ ToolBase = Union[
234
227
  ComposioToolDefinition,
235
228
  VellumIntegrationToolDefinition,
236
229
  ]
237
- Tool = Union[ToolBase, MCPServer]
230
+ Tool = Union[ToolBase, MCPServer, MCPToolDefinition]
@@ -6,6 +6,7 @@ if TYPE_CHECKING:
6
6
  from vellum.workflows import BaseWorkflow
7
7
  from vellum.workflows.inputs import BaseInputs
8
8
  from vellum.workflows.nodes import BaseNode
9
+ from vellum.workflows.nodes.bases.base_adornment_node import BaseAdornmentNode
9
10
  from vellum.workflows.outputs import BaseOutputs
10
11
  from vellum.workflows.state import BaseState
11
12
 
@@ -37,6 +38,17 @@ def import_workflow_class() -> Type["BaseWorkflow"]:
37
38
  return BaseWorkflow
38
39
 
39
40
 
41
+ @cache
42
+ def import_base_adornment_node() -> Type["BaseAdornmentNode"]:
43
+ """
44
+ Helper function to help avoid circular imports.
45
+ """
46
+
47
+ from vellum.workflows.nodes.bases.base_adornment_node import BaseAdornmentNode
48
+
49
+ return BaseAdornmentNode
50
+
51
+
40
52
  def is_node_class(obj: Any) -> TypeGuard[Type["BaseNode"]]:
41
53
  base_node_class = _import_node_class()
42
54
  return isinstance(obj, type) and issubclass(obj, base_node_class)
@@ -101,3 +101,15 @@ def test_infer_types(cls, attr_name, expected_type):
101
101
  )
102
102
  def test_class_attr_names(cls, expected_attr_names):
103
103
  assert get_class_attr_names(cls) == expected_attr_names
104
+
105
+
106
+ def test_infer_types__non_existent_attribute__raises_clear_error():
107
+ """Test that infer_types raises a clear error message for non-existent attributes."""
108
+
109
+ # GIVEN a class with some attributes
110
+ # WHEN we try to infer types for a non-existent attribute
111
+ # THEN it should raise an AttributeError with a clear message
112
+ with pytest.raises(AttributeError) as exc_info:
113
+ infer_types(ExampleClass, "non_existent_attribute")
114
+
115
+ assert "'ExampleClass' has no attribute 'non_existent_attribute'" in str(exc_info.value)
@@ -91,7 +91,10 @@ def infer_types(object_: Type, attr_name: str, localns: Optional[Dict[str, Any]]
91
91
  if len(types_list) == len(parts):
92
92
  return tuple(types_list)
93
93
 
94
- type_hints = get_type_hints(class_, localns=LOCAL_NS if localns is None else {**LOCAL_NS, **localns})
94
+ try:
95
+ type_hints = get_type_hints(class_, localns=LOCAL_NS if localns is None else {**LOCAL_NS, **localns})
96
+ except AttributeError:
97
+ type_hints = {}
95
98
  if attr_name in type_hints:
96
99
  type_hint = type_hints[attr_name]
97
100
  if get_origin(type_hint) is ClassVar:
@@ -121,12 +124,14 @@ def infer_types(object_: Type, attr_name: str, localns: Optional[Dict[str, Any]]
121
124
  class_attribute = class_attributes[attr_name]
122
125
  return resolve_types(class_attribute)
123
126
 
124
- raise AttributeError(f"Failed to infer type from attribute {attr_name} on {object_.__name__}")
127
+ raise AttributeError(f"'{object_.__name__}' has no attribute '{attr_name}'")
125
128
  except TypeError:
126
129
  # Python 3.13+: object class doesn't have __annotations__ by default
127
130
  # Use getattr with default to safely access annotations
128
131
  annotations = getattr(object_, "__annotations__", {})
129
132
  annotation_value = annotations.get(attr_name, undefined)
133
+ if annotation_value is undefined:
134
+ raise AttributeError(f"'{object_.__name__}' has no attribute '{attr_name}'")
130
135
  raise AttributeError(
131
136
  f"Found 3.9+ typing syntax for field '{attr_name}' on class '{object_.__name__}' – {annotation_value}. Type annotations must be compatible with python version 3.8. " # noqa: E501
132
137
  )
@@ -219,3 +224,28 @@ def get_original_base(cls: Type) -> Type:
219
224
  # in Python 3.12, there is `from types import get_original_bases`, making this future proof
220
225
  # https://docs.python.org/3/library/types.html#types.get_original_bases
221
226
  return cls.__orig_bases__[0] # type: ignore[attr-defined]
227
+
228
+
229
+ def coerce_to_declared_type(value: Any, declared_type: Type, field_name: str) -> Any:
230
+ """Coerce a value to the declared type if needed.
231
+
232
+ This handles cases where the API returns a float for an int field
233
+ (since NumberVellumValue.value is typed as float).
234
+
235
+ Args:
236
+ value: The value to coerce
237
+ declared_type: The expected type from the field annotation
238
+ field_name: The name of the field (for error messages)
239
+
240
+ Returns:
241
+ The coerced value
242
+
243
+ Raises:
244
+ ValueError: If the value cannot be safely coerced to the declared type
245
+ """
246
+ if declared_type is int and isinstance(value, float):
247
+ if value.is_integer():
248
+ return int(value)
249
+ raise ValueError(f"Expected integer for input '{field_name}', but received non-integer float: {value}")
250
+
251
+ return value
@@ -0,0 +1,124 @@
1
+ """Pydantic models for workflow metadata.json files.
2
+
3
+ The metadata.json file is generated alongside workflow code to ensure stable,
4
+ UI-aligned identifiers without relying on Python display classes. It contains
5
+ mappings between code paths and UI identifiers for nodes, triggers, edges, and
6
+ dataset rows.
7
+ """
8
+
9
+ from typing import Dict, Optional
10
+
11
+ from vellum.client.core.pydantic_utilities import UniversalBaseModel
12
+ from vellum.client.types.code_resource_definition import CodeResourceDefinition
13
+
14
+
15
+ class RunnerConfig(UniversalBaseModel):
16
+ """Configuration for the workflow runner environment.
17
+
18
+ This specifies the container image and version information used to execute
19
+ the workflow.
20
+ """
21
+
22
+ container_image_name: Optional[str] = None
23
+ """The name of the container image to use. If not provided, defaults to the
24
+ standard Python workflow runtime image."""
25
+
26
+ container_image_tag: Optional[str] = None
27
+ """The tag of the container image to use. If not provided, defaults to the
28
+ latest tag."""
29
+
30
+ codegen_version: Optional[str] = None
31
+ """The version of codegen used to generate the workflow artifact.
32
+
33
+ .. deprecated::
34
+ This field is deprecated. Use `sdk_version` instead.
35
+ """
36
+
37
+ sdk_version: Optional[str] = None
38
+ """The SDK version the workflow is intended to run on."""
39
+
40
+ is_deployment_inlining_enabled: bool = False
41
+ """Whether deployment inlining is enabled for this workflow."""
42
+
43
+ server_version: Optional[str] = None
44
+ """The server version the workflow is intended to run on. This gets set when
45
+ deploying a workflow with a hotswappable custom image."""
46
+
47
+
48
+ class WorkflowMetadata(UniversalBaseModel):
49
+ """Metadata persisted alongside generated workflow code.
50
+
51
+ This metadata ensures stable, UI-aligned identifiers without relying on
52
+ Python display classes. It is generated by the codegen system and consumed
53
+ during workflow serialization to maintain consistent IDs across round-trips.
54
+ """
55
+
56
+ runner_config: Optional[RunnerConfig] = None
57
+ """Configuration for the workflow runner environment."""
58
+
59
+ label: Optional[str] = None
60
+ """Human-readable label for the workflow."""
61
+
62
+ codegen_version: Optional[str] = None
63
+ """The version of codegen used to generate this workflow.
64
+
65
+ .. deprecated::
66
+ This field is deprecated. Use `runner_config.codegen_version` instead.
67
+ """
68
+
69
+ server_version: Optional[str] = None
70
+ """The server version this workflow was generated for.
71
+
72
+ .. deprecated::
73
+ This field is deprecated. Use `runner_config.server_version` instead.
74
+ """
75
+
76
+ node_id_to_file_mapping: Dict[str, CodeResourceDefinition] = {}
77
+ """Mapping of node IDs to their code resource definitions.
78
+
79
+ Key: Node UUID string
80
+ Value: CodeResourceDefinition containing the module path and class name
81
+ """
82
+
83
+ trigger_path_to_id_mapping: Dict[str, str] = {}
84
+ """Mapping of trigger class paths to their UI trigger IDs.
85
+
86
+ Key: ".<relative_trigger_module_path>.<TriggerClassName>"
87
+ Value: UI trigger ID (UUID string)
88
+ """
89
+
90
+ trigger_attribute_id_mapping: Dict[str, str] = {}
91
+ """Mapping of trigger attribute keys to their UI attribute IDs.
92
+
93
+ Key: "<trigger_path>|<attribute_key>"
94
+ Value: Attribute ID (UUID string)
95
+
96
+ This ensures trigger attribute IDs remain stable across serialization
97
+ round-trips.
98
+ """
99
+
100
+ entrypoint: Optional[str] = None
101
+ """The UI entrypoint node ID.
102
+
103
+ Kept for compatibility. Edge IDs for entrypoint flows are now included in
104
+ edges_to_id_mapping using the manual trigger path as the source.
105
+ """
106
+
107
+ edges_to_id_mapping: Dict[str, str] = {}
108
+ """Mapping of edge path keys to their UI edge IDs.
109
+
110
+ Key: "<source_path>|<target_node_path>"
111
+ where:
112
+ source_path := "<trigger_module_path>.<TriggerClassName>" for trigger edges
113
+ or "<module_path>.<NodeClassName>.Ports.<source_handle_id>"
114
+ for node->node edges (handle id disambiguates multiple edges)
115
+ target_node_path := "<module_path>.<ClassName>.Trigger" for the target node
116
+ Value: UI edge ID (UUID string)
117
+ """
118
+
119
+ dataset_row_index_to_id_mapping: Dict[str, str] = {}
120
+ """Mapping of dataset row indices to their UI row IDs.
121
+
122
+ Key: Dataset row index (0-based) as a string
123
+ Value: UI dataset row ID (UUID string)
124
+ """