vellum-ai 1.3.2__py3-none-any.whl → 1.3.3__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.
@@ -27,10 +27,10 @@ class BaseClientWrapper:
27
27
 
28
28
  def get_headers(self) -> typing.Dict[str, str]:
29
29
  headers: typing.Dict[str, str] = {
30
- "User-Agent": "vellum-ai/1.3.2",
30
+ "User-Agent": "vellum-ai/1.3.3",
31
31
  "X-Fern-Language": "Python",
32
32
  "X-Fern-SDK-Name": "vellum-ai",
33
- "X-Fern-SDK-Version": "1.3.2",
33
+ "X-Fern-SDK-Version": "1.3.3",
34
34
  **(self.get_custom_headers() or {}),
35
35
  }
36
36
  if self._api_version is not None:
@@ -1,5 +1,6 @@
1
1
  import logging
2
- from typing import Optional
2
+ import threading
3
+ from typing import List, Optional
3
4
 
4
5
  from vellum.core.request_options import RequestOptions
5
6
  from vellum.workflows.emitters.base import BaseWorkflowEmitter
@@ -29,6 +30,7 @@ class VellumEmitter(BaseWorkflowEmitter):
29
30
  *,
30
31
  timeout: Optional[float] = 30.0,
31
32
  max_retries: int = 3,
33
+ debounce_timeout: float = 0.1,
32
34
  ):
33
35
  """
34
36
  Initialize the VellumEmitter.
@@ -36,14 +38,19 @@ class VellumEmitter(BaseWorkflowEmitter):
36
38
  Args:
37
39
  timeout: Request timeout in seconds.
38
40
  max_retries: Maximum number of retry attempts for failed requests.
41
+ debounce_timeout: Time in seconds to wait before sending batched events.
39
42
  """
40
43
  super().__init__()
41
44
  self._timeout = timeout
42
45
  self._max_retries = max_retries
46
+ self._debounce_timeout = debounce_timeout
47
+ self._event_queue: List[SDKWorkflowEvent] = []
48
+ self._queue_lock = threading.Lock()
49
+ self._debounce_timer: Optional[threading.Timer] = None
43
50
 
44
51
  def emit_event(self, event: SDKWorkflowEvent) -> None:
45
52
  """
46
- Emit a workflow event to Vellum's infrastructure.
53
+ Queue a workflow event for batched emission to Vellum's infrastructure.
47
54
 
48
55
  Args:
49
56
  event: The workflow event to emit.
@@ -55,10 +62,45 @@ class VellumEmitter(BaseWorkflowEmitter):
55
62
  return
56
63
 
57
64
  try:
58
- self._send_event(event)
65
+ with self._queue_lock:
66
+ self._event_queue.append(event)
59
67
 
68
+ if self._debounce_timer:
69
+ self._debounce_timer.cancel()
70
+
71
+ self._debounce_timer = threading.Timer(self._debounce_timeout, self._flush_events)
72
+ self._debounce_timer.start()
73
+
74
+ except Exception as e:
75
+ logger.exception(f"Failed to queue event {event.name}: {e}")
76
+
77
+ def _flush_events(self) -> None:
78
+ """
79
+ Send all queued events as a batch to Vellum's infrastructure.
80
+ """
81
+ with self._queue_lock:
82
+ if not self._event_queue:
83
+ return
84
+
85
+ events_to_send = self._event_queue.copy()
86
+ self._event_queue.clear()
87
+ self._debounce_timer = None
88
+
89
+ try:
90
+ self._send_events(events_to_send)
60
91
  except Exception as e:
61
- logger.exception(f"Failed to emit event {event.name}: {e}")
92
+ logger.exception(f"Failed to send batched events: {e}")
93
+
94
+ def __del__(self) -> None:
95
+ """
96
+ Cleanup: flush any pending events and cancel timer.
97
+ """
98
+ try:
99
+ if self._debounce_timer:
100
+ self._debounce_timer.cancel()
101
+ self._flush_events()
102
+ except Exception:
103
+ pass
62
104
 
63
105
  def snapshot_state(self, state: BaseState) -> None:
64
106
  """
@@ -69,23 +111,27 @@ class VellumEmitter(BaseWorkflowEmitter):
69
111
  """
70
112
  pass
71
113
 
72
- def _send_event(self, event: SDKWorkflowEvent) -> None:
114
+ def _send_events(self, events: List[SDKWorkflowEvent]) -> None:
73
115
  """
74
- Send event to Vellum's events endpoint using client.events.create.
116
+ Send events to Vellum's events endpoint using client.events.create.
75
117
 
76
118
  Args:
77
- event: The WorkflowEvent object to send.
119
+ events: List of WorkflowEvent objects to send.
78
120
  """
79
121
  if not self._context:
80
- logger.warning("Cannot send event: No workflow context registered")
122
+ logger.warning("Cannot send events: No workflow context registered")
123
+ return
124
+
125
+ if not events:
81
126
  return
82
127
 
83
128
  client = self._context.vellum_client
84
129
  request_options = RequestOptions(timeout_in_seconds=self._timeout, max_retries=self._max_retries)
130
+
85
131
  client.events.create(
86
132
  # The API accepts a ClientWorkflowEvent but our SDK emits an SDKWorkflowEvent. These shapes are
87
133
  # meant to be identical, just with different helper methods. We may consolidate the two in the future.
88
134
  # But for now, the type ignore allows us to avoid an additional Model -> json -> Model conversion.
89
- request=event, # type: ignore[arg-type]
135
+ request=events, # type: ignore[arg-type]
90
136
  request_options=request_options,
91
137
  )
@@ -6,7 +6,6 @@ from vellum.workflows.inputs.base import BaseInputs
6
6
  from vellum.workflows.nodes.bases import BaseNode
7
7
  from vellum.workflows.nodes.core.retry_node.node import RetryNode
8
8
  from vellum.workflows.outputs import BaseOutputs
9
- from vellum.workflows.references.lazy import LazyReference
10
9
  from vellum.workflows.state.base import BaseState, StateMeta
11
10
 
12
11
 
@@ -102,7 +101,7 @@ def test_retry_node__condition_arg_successfully_retries():
102
101
  # AND a retry node that retries on a condition
103
102
  @RetryNode.wrap(
104
103
  max_attempts=5,
105
- retry_on_condition=LazyReference(lambda: State.count.less_than(3)),
104
+ retry_on_condition=State.count.less_than(3),
106
105
  )
107
106
  class TestNode(BaseNode[State]):
108
107
  attempt_number = RetryNode.SubworkflowInputs.attempt_number
@@ -27,7 +27,6 @@ from vellum.workflows.nodes.displayable.subworkflow_deployment_node.node import
27
27
  from vellum.workflows.nodes.displayable.tool_calling_node.state import ToolCallingState
28
28
  from vellum.workflows.outputs.base import BaseOutput
29
29
  from vellum.workflows.ports.port import Port
30
- from vellum.workflows.references.lazy import LazyReference
31
30
  from vellum.workflows.state import BaseState
32
31
  from vellum.workflows.state.encoder import DefaultStateEncoder
33
32
  from vellum.workflows.types.core import EntityInputsInterface, MergeBehavior, Tool, ToolBase
@@ -421,19 +420,13 @@ def create_router_node(
421
420
  # and if the function_name is changed, the port_condition will also change.
422
421
  def create_port_condition(fn_name):
423
422
  return Port.on_if(
424
- LazyReference(
425
- lambda: (
426
- ToolCallingState.current_prompt_output_index.less_than(
427
- tool_prompt_node.Outputs.results.length()
428
- )
429
- & tool_prompt_node.Outputs.results[ToolCallingState.current_prompt_output_index]["type"].equals(
430
- "FUNCTION_CALL"
431
- )
432
- & tool_prompt_node.Outputs.results[ToolCallingState.current_prompt_output_index]["value"][
433
- "name"
434
- ].equals(fn_name)
435
- )
423
+ ToolCallingState.current_prompt_output_index.less_than(tool_prompt_node.Outputs.results.length())
424
+ & tool_prompt_node.Outputs.results[ToolCallingState.current_prompt_output_index]["type"].equals(
425
+ "FUNCTION_CALL"
436
426
  )
427
+ & tool_prompt_node.Outputs.results[ToolCallingState.current_prompt_output_index]["value"][
428
+ "name"
429
+ ].equals(fn_name)
437
430
  )
438
431
 
439
432
  for function in functions:
@@ -4,11 +4,13 @@ import json
4
4
  from queue import Queue
5
5
  from typing import Dict, List, cast
6
6
 
7
+ from vellum.workflows.constants import undefined
7
8
  from vellum.workflows.nodes.bases import BaseNode
8
9
  from vellum.workflows.outputs.base import BaseOutputs
9
10
  from vellum.workflows.state.base import BaseState
10
11
  from vellum.workflows.state.delta import SetStateDelta, StateDelta
11
12
  from vellum.workflows.state.encoder import DefaultStateEncoder
13
+ from vellum.workflows.types.code_execution_node_wrappers import DictWrapper
12
14
 
13
15
 
14
16
  @pytest.fixture()
@@ -229,3 +231,15 @@ def test_state_snapshot__deepcopy_fails__logs_error(mock_deepcopy, mock_logger):
229
231
 
230
232
  # AND alert sentry once
231
233
  assert mock_logger.exception.call_count == 1
234
+
235
+
236
+ def test_state_deepcopy_handles_undefined_values():
237
+ # GIVEN a state with undefined values in node outputs
238
+ state = MockState(foo="bar")
239
+ state.meta.node_outputs[MockNode.Outputs.baz] = DictWrapper({"foo": undefined})
240
+
241
+ # WHEN we deepcopy the state
242
+ deepcopied_state = deepcopy(state)
243
+
244
+ # THEN the undefined values are preserved
245
+ assert deepcopied_state.meta.node_outputs[MockNode.Outputs.baz] == {"foo": undefined}
@@ -72,6 +72,9 @@ class DictWrapper(dict):
72
72
  # several values as VellumValue objects, we use the "value" key to return itself
73
73
  return self
74
74
 
75
+ if attr.startswith("__") and attr.endswith("__"):
76
+ return super().__getattribute__(attr)
77
+
75
78
  return undefined
76
79
 
77
80
  item = super().__getitem__(attr)
@@ -22,6 +22,7 @@ from vellum import (
22
22
  VellumVideo,
23
23
  VellumVideoRequest,
24
24
  )
25
+ from vellum.workflows.constants import undefined
25
26
  from vellum.workflows.descriptors.base import BaseDescriptor
26
27
  from vellum.workflows.types.core import Json
27
28
 
@@ -29,8 +30,16 @@ from vellum.workflows.types.core import Json
29
30
  def primitive_type_to_vellum_variable_type(type_: Union[Type, BaseDescriptor]) -> VellumVariableType:
30
31
  """Converts a python primitive to a VellumVariableType"""
31
32
  if isinstance(type_, BaseDescriptor):
32
- # Ignore None because those just make types optional
33
- types = [t for t in type_.types if t is not type(None)]
33
+ # Ignore None and undefined because those just make types optional
34
+ types = []
35
+ for t in type_.types:
36
+ if t is type(None):
37
+ continue
38
+ if t is undefined or t is type(undefined):
39
+ continue
40
+ if get_origin(t) is type and len(get_args(t)) == 1 and get_args(t)[0] is undefined:
41
+ continue
42
+ types.append(t)
34
43
 
35
44
  # default to JSON for typevars where the types is empty tuple
36
45
  if len(types) == 0:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-ai
3
- Version: 1.3.2
3
+ Version: 1.3.3
4
4
  Summary:
5
5
  License: MIT
6
6
  Requires-Python: >=3.9,<4.0
@@ -1,11 +1,12 @@
1
1
  vellum_cli/CONTRIBUTING.md,sha256=FtDC7BGxSeMnwCXAUssFsAIElXtmJE-O5Z7BpolcgvI,2935
2
2
  vellum_cli/README.md,sha256=2NudRoLzWxNKqnuVy1JuQ7DerIaxWGYkrH8kMd-asIE,90
3
- vellum_cli/__init__.py,sha256=rHcUFsfu-nivhX02R-6dmYr9ee6LMA-wy2DrFnOhC4Q,13030
3
+ vellum_cli/__init__.py,sha256=CCTCiCvwxjFHXVSKhzUtZ3GE_h4HrbLEX_XC3qjPuAk,13618
4
4
  vellum_cli/aliased_group.py,sha256=ugW498j0yv4ALJ8vS9MsO7ctDW7Jlir9j6nE_uHAP8c,3363
5
5
  vellum_cli/config.py,sha256=qJrd8W__UZZaUMAG6BO3sxfkgpCoOS4NA3QfqneW-jE,9588
6
6
  vellum_cli/image_push.py,sha256=eeOBqKtKkPu6Kgm_jQCVCivogzAcdlIlkv61-QxH67c,11006
7
7
  vellum_cli/init.py,sha256=WpnMXPItPmh0f0bBGIer3p-e5gu8DUGwSArT_FuoMEw,5093
8
8
  vellum_cli/logger.py,sha256=dcM_OmgqXLo93vDYswO5ylyUQQcTfnA5GTd5tbIt3wM,1446
9
+ vellum_cli/move.py,sha256=lCHQ-U4BspgS512GxFFvUrglitaHkWfuKn1Hpfcn7-Q,2053
9
10
  vellum_cli/ping.py,sha256=p_BCCRjgPhng6JktuECtkDQLbhopt6JpmrtGoLnLJT8,1161
10
11
  vellum_cli/pull.py,sha256=udYyPlJ6VKDdh78rApNJOZgxHl82fcV6iGnRPSdX1LY,14750
11
12
  vellum_cli/push.py,sha256=KpBGq7B-ffwa9QTHsTRSk73l-tfKc3gyiBSn9Pwlsak,11878
@@ -16,6 +17,7 @@ vellum_cli/tests/test_image_push.py,sha256=X0YOPdoaAnWtK9IQTxaN_wWpw08-5G3v76k1W
16
17
  vellum_cli/tests/test_image_push_error_handling.py,sha256=QRH0eYNEEIkD2-EVFQWYexOKlhMB6puh1GouovrE-VU,7647
17
18
  vellum_cli/tests/test_init.py,sha256=C_rV4lu-ab5iFoLRizs1XAUnSPdMf7oFuc1i4N4udOU,18285
18
19
  vellum_cli/tests/test_main.py,sha256=qDZG-aQauPwBwM6A2DIu1494n47v3pL28XakTbLGZ-k,272
20
+ vellum_cli/tests/test_move.py,sha256=FIrL1xlH5oFKGX2MugcTKL8JilpopmUC7hP5OaqF5zw,5213
19
21
  vellum_cli/tests/test_ping.py,sha256=b3aQLd-N59_8w2rRiWqwpB1rlHaKEYVbAj1Y3hi7A-g,2605
20
22
  vellum_cli/tests/test_pull.py,sha256=f7dK61y82LlJm-FAv-IpfmscJijIfZZrR-rzN1TKkU0,49421
21
23
  vellum_cli/tests/test_push.py,sha256=10G-H88tdiYvg9CaC8OXOf25UEzWkWWB01vNhuxzjog,41944
@@ -23,12 +25,12 @@ vellum_ee/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
25
  vellum_ee/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
26
  vellum_ee/workflows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
27
  vellum_ee/workflows/display/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
- vellum_ee/workflows/display/base.py,sha256=po1YIXOupglAFi1-_zsgNH-VwIjHfy5GCvii6Wzntu0,1878
28
+ vellum_ee/workflows/display/base.py,sha256=R3f2T8FlZrXn2FawAmpVuLB3fKFWw11mCUulWAyIKA0,1912
27
29
  vellum_ee/workflows/display/editor/__init__.py,sha256=MSAgY91xCEg2neH5d8jXx5wRdR962ftZVa6vO9BGq9k,167
28
- vellum_ee/workflows/display/editor/types.py,sha256=x-tOOCJ6CF4HmiKDfCmcc3bOVfc1EBlP5o6u5WEfLoY,567
30
+ vellum_ee/workflows/display/editor/types.py,sha256=JXpd8E7dsInbDp5T7ra0-T6XvdQB9RAHhZ1Lc8_0Fk4,601
29
31
  vellum_ee/workflows/display/exceptions.py,sha256=Oys39dHoW-s-1dnlRSZxTntMq8_macj-b2CT_6dqzJs,355
30
32
  vellum_ee/workflows/display/nodes/__init__.py,sha256=jI1aPBQf8DkmrYoZ4O-wR1duqZByOf5mDFmo_wFJPE4,307
31
- vellum_ee/workflows/display/nodes/base_node_display.py,sha256=bxDJxtNqcyy1LwFltggm63W1ZHZPuwK1NyA6KPLHRQQ,18345
33
+ vellum_ee/workflows/display/nodes/base_node_display.py,sha256=98vszulLRvYz0kAh7ZEOeWOqzMtlpnOZM2lAViOUieA,18393
32
34
  vellum_ee/workflows/display/nodes/get_node_display_class.py,sha256=jI_kUi9LnNLDpY63QtlC4TfN8P571VN4LpzH0I1ZtLk,1149
33
35
  vellum_ee/workflows/display/nodes/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
36
  vellum_ee/workflows/display/nodes/tests/test_base_node_display.py,sha256=wBoCqULS4XO3s9Vwhd9v4g10opfBFqeZgRqB8CoFz0c,3015
@@ -86,7 +88,7 @@ vellum_ee/workflows/display/tests/workflow_serialization/test_basic_default_stat
86
88
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py,sha256=03Mk8OE3kWcoZW9lNBe7v4KThCYN-pXg5Rjbkfx-jOQ,6031
87
89
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_generic_node_serialization.py,sha256=-T0cd2jx1bC0ZNtAESF78fnYD_0nOqo8zMMLwRHUTRM,6227
88
90
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_guardrail_node_serialization.py,sha256=LnZp1YXDn-AaaxiYgxrhCQeH-rLgmlu_r_lvM65EQ5w,7517
89
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_prompt_node_serialization.py,sha256=zOv-CzFZs_9NqlINQ7hzJmd4o8ArDBKB7Eyr1Rfd-BU,25767
91
+ vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_prompt_node_serialization.py,sha256=fze2nu8UlH36giAxu60YQzmYwOm8QK3tv_v7MYBm3WY,26306
90
92
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py,sha256=yKmOyunzt5_0cUcqhvCmV2pu81TTkshVi_uN3yt76Wc,21816
91
93
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py,sha256=W4--Ldih7mRMnfyJ9G7kdyeoKkeebSu_5d33TJQzShU,16735
92
94
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py,sha256=UnfWTfKN8DrOLpjCfWMgENJBjgNLWcRVfexbwERKY-c,8501
@@ -151,7 +153,7 @@ vellum/client/README.md,sha256=b6XKeYBBbhQx0v1sHWfM0gIJeJhUFF-aqL2ig7ADa08,5564
151
153
  vellum/client/__init__.py,sha256=T5Ht_w-Mk_9nzGqdadhQB8V20M0vYj7am06ut0A3P1o,73401
152
154
  vellum/client/core/__init__.py,sha256=lTcqUPXcx4112yLDd70RAPeqq6tu3eFMe1pKOqkW9JQ,1562
153
155
  vellum/client/core/api_error.py,sha256=44vPoTyWN59gonCIZMdzw7M1uspygiLnr3GNFOoVL2Q,614
154
- vellum/client/core/client_wrapper.py,sha256=U9SkO9ewIIRiSsS9pT50GwmBH-rMjpV3QS5Htj-ipqs,2840
156
+ vellum/client/core/client_wrapper.py,sha256=8my-Ir86AssDOMCGyePyF0hcUkPe6DsVFwW880WD_c8,2840
155
157
  vellum/client/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
156
158
  vellum/client/core/file.py,sha256=d4NNbX8XvXP32z8KpK2Xovv33nFfruIrpz0QWxlgpZk,2663
157
159
  vellum/client/core/force_multipart.py,sha256=awxh5MtcRYe74ehY8U76jzv6fYM_w_D3Rur7KQQzSDk,429
@@ -1720,7 +1722,7 @@ vellum/workflows/edges/__init__.py,sha256=wSkmAnz9xyi4vZwtDbKxwlplt2skD7n3NsxkvR
1720
1722
  vellum/workflows/edges/edge.py,sha256=N0SnY3gKVuxImPAdCbPMPlHJIXbkQ3fwq_LbJRvVMFc,677
1721
1723
  vellum/workflows/emitters/__init__.py,sha256=d9QFOI3eVg6rzpSFLvrjkDYXWikf1tcp3ruTRa2Boyc,143
1722
1724
  vellum/workflows/emitters/base.py,sha256=Tcp13VMB-GMwEJdl-6XTPckspdOdwpMgBx22-PcQxds,892
1723
- vellum/workflows/emitters/vellum_emitter.py,sha256=hhMX8uXZFH2ZYHJP77uA6U4ECisacwdTBKI7papU79Y,2899
1725
+ vellum/workflows/emitters/vellum_emitter.py,sha256=ECBIRA48WS5rIJd1iWUfye7B5Up7ujL98BTlZwWALKs,4430
1724
1726
  vellum/workflows/environment/__init__.py,sha256=TJz0m9dwIs6YOwCTeuN0HHsU-ecyjc1OJXx4AFy83EQ,121
1725
1727
  vellum/workflows/environment/environment.py,sha256=Ck3RPKXJvtMGx_toqYQQQF-ZwXm5ijVwJpEPTeIJ4_Q,471
1726
1728
  vellum/workflows/errors/__init__.py,sha256=tWGPu5xyAU8gRb8_bl0fL7OfU3wxQ9UH6qVwy4X4P_Q,113
@@ -1814,7 +1816,7 @@ vellum/workflows/nodes/core/map_node/tests/test_node.py,sha256=Xc2xZY5ShSy-bsIQe
1814
1816
  vellum/workflows/nodes/core/retry_node/__init__.py,sha256=lN2bIy5a3Uzhs_FYCrooADyYU6ZGShtvLKFWpelwPvo,60
1815
1817
  vellum/workflows/nodes/core/retry_node/node.py,sha256=EM4ya8Myr7ADllpjt9q-BAhB3hGrsF8MLZhp5eh4lyo,5590
1816
1818
  vellum/workflows/nodes/core/retry_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1817
- vellum/workflows/nodes/core/retry_node/tests/test_node.py,sha256=PCvD_XROP26k4cVYOdSQUfkDdbTljAJxtOTFzOUoS8c,4450
1819
+ vellum/workflows/nodes/core/retry_node/tests/test_node.py,sha256=XyMtL_ZI6zcqCe0mG4DYjeuZGqX9zm35lnpLUZxsNUk,4368
1818
1820
  vellum/workflows/nodes/core/templating_node/__init__.py,sha256=GmyuYo81_A1_Bz6id69ozVFS6FKiuDsZTiA3I6MaL2U,70
1819
1821
  vellum/workflows/nodes/core/templating_node/node.py,sha256=mn0JEbORWaM-mR7fgUZy-BItZCup8CaxZQaY_g3TSEE,3855
1820
1822
  vellum/workflows/nodes/core/templating_node/tests/test_templating_node.py,sha256=gfLi8lJpTU5jGO1Kt6UuzVz1fQN8dcNhHBZG90enP-s,15013
@@ -1895,7 +1897,7 @@ vellum/workflows/nodes/displayable/tool_calling_node/tests/__init__.py,sha256=47
1895
1897
  vellum/workflows/nodes/displayable/tool_calling_node/tests/test_composio_service.py,sha256=in1fbEz5x1tx3uKv9YXdvOncsHucNL8Ro6Go7lBuuOQ,8962
1896
1898
  vellum/workflows/nodes/displayable/tool_calling_node/tests/test_node.py,sha256=GZoeybB9uM7ai8sBLAtUMHrMVgh-WrJDWrKZci6feDs,11892
1897
1899
  vellum/workflows/nodes/displayable/tool_calling_node/tests/test_utils.py,sha256=SIu5GCj4tIE4fz-cAcdULtQfqZIhrcc3Doo6TWLXBws,8804
1898
- vellum/workflows/nodes/displayable/tool_calling_node/utils.py,sha256=ACEfl2pY65sO0DaMYxuecx2Kxg32HpSh7XXpp8U1J84,23459
1900
+ vellum/workflows/nodes/displayable/tool_calling_node/utils.py,sha256=wymqUCytUHiWKJxJDvMc7cLdJ69zfxNrHXAmXkFSUiQ,23189
1899
1901
  vellum/workflows/nodes/displayable/web_search_node/__init__.py,sha256=8FOnEP-n-U68cvxTlJW9wphIAGHq5aqjzLM-DoSSXnU,61
1900
1902
  vellum/workflows/nodes/displayable/web_search_node/node.py,sha256=NQYux2bOtuBF5E4tn-fXi5y3btURPRrNqMSM9MAZYI4,5091
1901
1903
  vellum/workflows/nodes/displayable/web_search_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -1943,12 +1945,12 @@ vellum/workflows/state/delta.py,sha256=7h8wR10lRCm15SykaPj-gSEvvsMjCwYLPsOx3nsvB
1943
1945
  vellum/workflows/state/encoder.py,sha256=HdNlabmBOcFSeY_dgn4LNtQEugyzsw3p4mvn2ChC1Io,3380
1944
1946
  vellum/workflows/state/store.py,sha256=uVe-oN73KwGV6M6YLhwZMMUQhzTQomsVfVnb8V91gVo,1147
1945
1947
  vellum/workflows/state/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1946
- vellum/workflows/state/tests/test_state.py,sha256=SjQZgovETrosPUeFohaPB9egAkSVe8ptJO5O4Fk2E04,6920
1948
+ vellum/workflows/state/tests/test_state.py,sha256=zEVFIY2any41X2BA5Us_qqKpzH5HRqmyrUJ04GTO0pU,7484
1947
1949
  vellum/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1948
1950
  vellum/workflows/tests/test_sandbox.py,sha256=JKwaluI-lODQo7Ek9sjDstjL_WTdSqUlVik6ZVTfVOA,1826
1949
1951
  vellum/workflows/tests/test_undefined.py,sha256=zMCVliCXVNLrlC6hEGyOWDnQADJ2g83yc5FIM33zuo8,353
1950
1952
  vellum/workflows/types/__init__.py,sha256=KxUTMBGzuRCfiMqzzsykOeVvrrkaZmTTo1a7SLu8gRM,68
1951
- vellum/workflows/types/code_execution_node_wrappers.py,sha256=m6C4cNEfPZFuqsS9qaJHOKq7UviOBDIYdAJalMWmabQ,3089
1953
+ vellum/workflows/types/code_execution_node_wrappers.py,sha256=fewX9bqF_4TZuK-gZYIn12s31-k03vHMGRpvFAPm11Y,3206
1952
1954
  vellum/workflows/types/core.py,sha256=TggDVs2lVya33xvu374EDhMC1b7RRlAAs0zWLaF46BA,1385
1953
1955
  vellum/workflows/types/definition.py,sha256=2vq3uGT-m994zRcla0yTUsMiPLKSDuzEZs7H6U9QbiE,4993
1954
1956
  vellum/workflows/types/generics.py,sha256=8jptbEx1fnJV0Lhj0MpCJOT6yNiEWeTOYOwrEAb5CRU,1576
@@ -1968,7 +1970,7 @@ vellum/workflows/utils/tests/test_names.py,sha256=aOqpyvMsOEK_9mg_-yaNxQDW7QQfwq
1968
1970
  vellum/workflows/utils/tests/test_uuids.py,sha256=i77ABQ0M3S-aFLzDXHJq_yr5FPkJEWCMBn1HJ3DObrE,437
1969
1971
  vellum/workflows/utils/tests/test_vellum_variables.py,sha256=vbnKgm41aB5OXlO-ZIPbhQ6xDiZkT8KMxCLqz4zocWY,1791
1970
1972
  vellum/workflows/utils/uuids.py,sha256=IaZQANz7fhF7la0_J1O50Y6D2PIYv_taRDTRzBT9aWw,1284
1971
- vellum/workflows/utils/vellum_variables.py,sha256=-CohqD3AeyCYzqwyrPZ7mt_lt7ibWLyx0MuS484feJk,5503
1973
+ vellum/workflows/utils/vellum_variables.py,sha256=YHLNiQGWDNssGH1FQoG9Z1jUFZ-zYebWqTLBG4cS-Fg,5837
1972
1974
  vellum/workflows/utils/zip.py,sha256=HVg_YZLmBOTXKaDV3Xhaf3V6sYnfqqZXQ8CpuafkbPY,1181
1973
1975
  vellum/workflows/vellum_client.py,sha256=xkfoucodxNK5JR2-lbRqZx3xzDgExWkP6kySrpi_Ubc,1079
1974
1976
  vellum/workflows/workflows/__init__.py,sha256=KY45TqvavCCvXIkyCFMEc0dc6jTMOUci93U2DUrlZYc,66
@@ -1977,8 +1979,8 @@ vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnad
1977
1979
  vellum/workflows/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1978
1980
  vellum/workflows/workflows/tests/test_base_workflow.py,sha256=ptMntHzVyy8ZuzNgeTuk7hREgKQ5UBdgq8VJFSGaW4Y,20832
1979
1981
  vellum/workflows/workflows/tests/test_context.py,sha256=VJBUcyWVtMa_lE5KxdhgMu0WYNYnUQUDvTF7qm89hJ0,2333
1980
- vellum_ai-1.3.2.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1981
- vellum_ai-1.3.2.dist-info/METADATA,sha256=tnrvX2iEEP0vTsPk5wc2h5nOtdqyExs5bW8fBNV1-7U,5547
1982
- vellum_ai-1.3.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1983
- vellum_ai-1.3.2.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1984
- vellum_ai-1.3.2.dist-info/RECORD,,
1982
+ vellum_ai-1.3.3.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1983
+ vellum_ai-1.3.3.dist-info/METADATA,sha256=bNuEdxi3O1ua5f1Q56poP2dIfUnw_dHq58lIVX6pMds,5547
1984
+ vellum_ai-1.3.3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1985
+ vellum_ai-1.3.3.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1986
+ vellum_ai-1.3.3.dist-info/RECORD,,
vellum_cli/__init__.py CHANGED
@@ -5,6 +5,7 @@ import click
5
5
  from vellum_cli.aliased_group import ClickAliasedGroup
6
6
  from vellum_cli.image_push import image_push_command
7
7
  from vellum_cli.init import init_command
8
+ from vellum_cli.move import move_command
8
9
  from vellum_cli.ping import ping_command
9
10
  from vellum_cli.pull import pull_command
10
11
  from vellum_cli.push import push_command
@@ -376,6 +377,26 @@ def image_push(
376
377
  image_push_command(image, tag, workspace, source)
377
378
 
378
379
 
380
+ @workflows.command(name="move")
381
+ @click.argument("old_module", required=True)
382
+ @click.argument("new_module", required=True)
383
+ @click.option("--workspace", type=str, help="The specific Workspace config to use when moving")
384
+ def workflows_move(
385
+ old_module: str,
386
+ new_module: str,
387
+ workspace: Optional[str],
388
+ ) -> None:
389
+ """
390
+ Move/rename a Workflow module. Updates both the filesystem structure and configuration.
391
+ """
392
+
393
+ move_command(
394
+ old_module=old_module,
395
+ new_module=new_module,
396
+ workspace=workspace,
397
+ )
398
+
399
+
379
400
  @workflows.command(name="init")
380
401
  @click.argument("template_name", required=False)
381
402
  @click.option(
vellum_cli/move.py ADDED
@@ -0,0 +1,56 @@
1
+ import os
2
+ import shutil
3
+ from typing import Optional
4
+
5
+ from dotenv import load_dotenv
6
+
7
+ from vellum_cli.config import DEFAULT_WORKSPACE_CONFIG, load_vellum_cli_config
8
+ from vellum_cli.logger import load_cli_logger
9
+ from vellum_cli.push import module_exists
10
+
11
+
12
+ def move_command(
13
+ old_module: str,
14
+ new_module: str,
15
+ workspace: Optional[str] = None,
16
+ ) -> None:
17
+ load_dotenv(dotenv_path=os.path.join(os.getcwd(), ".env"))
18
+ logger = load_cli_logger()
19
+ config = load_vellum_cli_config()
20
+
21
+ if not module_exists(old_module):
22
+ raise ValueError(f"Module '{old_module}' does not exist in the filesystem.")
23
+
24
+ if module_exists(new_module):
25
+ raise ValueError(f"Module '{new_module}' already exists. Cannot move to existing module.")
26
+
27
+ matching_configs = [w for w in config.workflows if w.module == old_module]
28
+
29
+ if workspace:
30
+ matching_configs = [w for w in matching_configs if w.workspace == workspace]
31
+ else:
32
+ matching_configs = [w for w in matching_configs if w.workspace == DEFAULT_WORKSPACE_CONFIG.name]
33
+
34
+ if not matching_configs:
35
+ workspace_msg = (
36
+ f" in workspace '{workspace}'" if workspace else f" in workspace '{DEFAULT_WORKSPACE_CONFIG.name}'"
37
+ )
38
+ raise ValueError(f"No workflow configuration found for module '{old_module}'{workspace_msg}.")
39
+
40
+ logger.info(f"Moving module from '{old_module}' to '{new_module}'...")
41
+
42
+ old_path = os.path.join(os.getcwd(), *old_module.split("."))
43
+ new_path = os.path.join(os.getcwd(), *new_module.split("."))
44
+
45
+ os.makedirs(os.path.dirname(new_path), exist_ok=True)
46
+
47
+ shutil.move(old_path, new_path)
48
+ logger.info(f"Moved filesystem directory from '{old_path}' to '{new_path}'")
49
+
50
+ for workflow_config in matching_configs:
51
+ workflow_config.module = new_module
52
+ logger.info(f"Updated workflow configuration: {workflow_config.workflow_sandbox_id}")
53
+
54
+ config.save()
55
+ logger.info("Updated vellum.lock.json file.")
56
+ logger.info(f"Successfully moved module from '{old_module}' to '{new_module}'")
@@ -0,0 +1,154 @@
1
+ import os
2
+
3
+ from click.testing import CliRunner
4
+
5
+ from vellum_cli import main
6
+ from vellum_cli.config import load_vellum_cli_config
7
+
8
+
9
+ def test_move__happy_path(mock_module):
10
+ """
11
+ Test that the move command successfully moves a module and updates configuration.
12
+ """
13
+
14
+ temp_dir = mock_module.temp_dir
15
+ old_module = mock_module.module
16
+ new_module = "examples.new.workflow"
17
+
18
+ old_module_dir = os.path.join(temp_dir, *old_module.split("."))
19
+ os.makedirs(old_module_dir, exist_ok=True)
20
+ with open(os.path.join(old_module_dir, "workflow.py"), "w") as f:
21
+ f.write("# test workflow")
22
+
23
+ runner = CliRunner()
24
+ result = runner.invoke(main, ["workflows", "move", old_module, new_module])
25
+
26
+ assert result.exit_code == 0
27
+
28
+ assert not os.path.exists(old_module_dir)
29
+
30
+ new_module_dir = os.path.join(temp_dir, *new_module.split("."))
31
+ assert os.path.exists(new_module_dir)
32
+ assert os.path.exists(os.path.join(new_module_dir, "workflow.py"))
33
+
34
+ config = load_vellum_cli_config()
35
+ workflow_config = next((w for w in config.workflows if w.module == new_module), None)
36
+ assert workflow_config is not None
37
+ assert workflow_config.workflow_sandbox_id == mock_module.workflow_sandbox_id
38
+
39
+
40
+ def test_move__old_module_not_exists(mock_module):
41
+ """
42
+ Test that the move command fails when the old module doesn't exist.
43
+ """
44
+
45
+ old_module = "nonexistent.module"
46
+ new_module = "examples.new.workflow"
47
+
48
+ runner = CliRunner()
49
+ result = runner.invoke(main, ["workflows", "move", old_module, new_module])
50
+
51
+ assert result.exit_code != 0
52
+ assert "does not exist in the filesystem" in str(result.exception)
53
+
54
+
55
+ def test_move__new_module_already_exists(mock_module):
56
+ """
57
+ Test that the move command fails when the new module already exists.
58
+ """
59
+
60
+ temp_dir = mock_module.temp_dir
61
+ old_module = mock_module.module
62
+ new_module = "examples.existing.workflow"
63
+
64
+ old_module_dir = os.path.join(temp_dir, *old_module.split("."))
65
+ new_module_dir = os.path.join(temp_dir, *new_module.split("."))
66
+ os.makedirs(old_module_dir, exist_ok=True)
67
+ os.makedirs(new_module_dir, exist_ok=True)
68
+
69
+ runner = CliRunner()
70
+ result = runner.invoke(main, ["workflows", "move", old_module, new_module])
71
+
72
+ assert result.exit_code != 0
73
+ assert "already exists" in str(result.exception)
74
+
75
+
76
+ def test_move__no_workflow_config_found(mock_module):
77
+ """
78
+ Test that the move command fails when no workflow config is found.
79
+ """
80
+
81
+ temp_dir = mock_module.temp_dir
82
+ old_module = "examples.unconfigured.workflow"
83
+ new_module = "examples.new.workflow"
84
+
85
+ old_module_dir = os.path.join(temp_dir, *old_module.split("."))
86
+ os.makedirs(old_module_dir, exist_ok=True)
87
+
88
+ runner = CliRunner()
89
+ result = runner.invoke(main, ["workflows", "move", old_module, new_module])
90
+
91
+ assert result.exit_code != 0
92
+ assert "No workflow configuration found" in str(result.exception)
93
+
94
+
95
+ def test_move__with_workspace_filter(mock_module):
96
+ """
97
+ Test that the move command works with workspace filtering.
98
+ """
99
+
100
+ temp_dir = mock_module.temp_dir
101
+ old_module = mock_module.module
102
+ new_module = "examples.new.workflow"
103
+ workspace = "default"
104
+
105
+ old_module_dir = os.path.join(temp_dir, *old_module.split("."))
106
+ os.makedirs(old_module_dir, exist_ok=True)
107
+ with open(os.path.join(old_module_dir, "workflow.py"), "w") as f:
108
+ f.write("# test workflow")
109
+
110
+ runner = CliRunner()
111
+ result = runner.invoke(main, ["workflows", "move", old_module, new_module, "--workspace", workspace])
112
+
113
+ assert result.exit_code == 0
114
+
115
+ config = load_vellum_cli_config()
116
+ workflow_config = next((w for w in config.workflows if w.module == new_module), None)
117
+ assert workflow_config is not None
118
+ assert workflow_config.workspace == workspace
119
+
120
+
121
+ def test_move__preserves_workflow_metadata(mock_module):
122
+ """
123
+ Test that the move command preserves all workflow metadata except the module name.
124
+ """
125
+
126
+ temp_dir = mock_module.temp_dir
127
+ old_module = mock_module.module
128
+ new_module = "examples.new.workflow"
129
+
130
+ config = load_vellum_cli_config()
131
+ original_config = next((w for w in config.workflows if w.module == old_module), None)
132
+ assert original_config is not None
133
+ original_config.container_image_name = "test-image"
134
+ original_config.container_image_tag = "v1.0"
135
+ original_config.ignore = "sandbox.py"
136
+ config.save()
137
+
138
+ old_module_dir = os.path.join(temp_dir, *old_module.split("."))
139
+ os.makedirs(old_module_dir, exist_ok=True)
140
+ with open(os.path.join(old_module_dir, "workflow.py"), "w") as f:
141
+ f.write("# test workflow")
142
+
143
+ runner = CliRunner()
144
+ result = runner.invoke(main, ["workflows", "move", old_module, new_module])
145
+
146
+ assert result.exit_code == 0
147
+
148
+ config = load_vellum_cli_config()
149
+ workflow_config = next((w for w in config.workflows if w.module == new_module), None)
150
+ assert workflow_config is not None
151
+ assert workflow_config.workflow_sandbox_id == mock_module.workflow_sandbox_id
152
+ assert workflow_config.container_image_name == "test-image"
153
+ assert workflow_config.container_image_tag == "v1.0"
154
+ assert workflow_config.ignore == "sandbox.py"
@@ -56,6 +56,7 @@ class StateValueDisplay:
56
56
  @dataclass
57
57
  class EdgeDisplay:
58
58
  id: UUID
59
+ z_index: Optional[int] = None
59
60
 
60
61
 
61
62
  @dataclass
@@ -17,6 +17,7 @@ class NodeDisplayComment(UniversalBaseModel):
17
17
 
18
18
  class NodeDisplayData(UniversalBaseModel):
19
19
  position: NodeDisplayPosition = Field(default_factory=NodeDisplayPosition)
20
+ z_index: Optional[int] = None
20
21
  width: Optional[int] = None
21
22
  height: Optional[int] = None
22
23
  comment: Optional[NodeDisplayComment] = None
@@ -431,6 +431,7 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
431
431
  )
432
432
  return NodeDisplayData(
433
433
  position=explicit_value.position,
434
+ z_index=explicit_value.z_index,
434
435
  width=explicit_value.width,
435
436
  height=explicit_value.height,
436
437
  comment=comment,
@@ -47,17 +47,20 @@ def test_serialize_workflow():
47
47
 
48
48
  # AND its output variables should be what we expect
49
49
  output_variables = serialized_workflow["output_variables"]
50
- assert len(output_variables) == 1
50
+ assert len(output_variables) == 2
51
51
  assert not DeepDiff(
52
- [{"id": "15a0ab89-8ed4-43b9-afa2-3c0b29d4dc3e", "key": "results", "type": "JSON"}],
52
+ [
53
+ {"id": "15a0ab89-8ed4-43b9-afa2-3c0b29d4dc3e", "key": "results", "type": "JSON"},
54
+ {"id": "0ef1608e-1737-41cc-9b90-a8e124138f70", "key": "json", "type": "JSON"},
55
+ ],
53
56
  output_variables,
54
57
  ignore_order=True,
55
58
  )
56
59
 
57
60
  # AND its raw data should be what we expect
58
61
  workflow_raw_data = serialized_workflow["workflow_raw_data"]
59
- assert len(workflow_raw_data["edges"]) == 2
60
- assert len(workflow_raw_data["nodes"]) == 3
62
+ assert len(workflow_raw_data["edges"]) == 3
63
+ assert len(workflow_raw_data["nodes"]) == 4
61
64
 
62
65
  # AND each node should be serialized correctly
63
66
  entrypoint_node = workflow_raw_data["nodes"][0]
@@ -305,7 +308,7 @@ def test_serialize_workflow():
305
308
  },
306
309
  }
307
310
  ],
308
- "display_data": {"position": {"x": 400.0, "y": -50.0}},
311
+ "display_data": {"position": {"x": 400.0, "y": 75.0}},
309
312
  "base": {
310
313
  "name": "FinalOutputNode",
311
314
  "module": ["vellum", "workflows", "nodes", "displayable", "final_output_node", "node"],
@@ -336,6 +339,14 @@ def test_serialize_workflow():
336
339
  "target_handle_id": "46c99277-2b4b-477d-851c-ea497aef6b16",
337
340
  "type": "DEFAULT",
338
341
  },
342
+ {
343
+ "id": "0b1a2960-4cd5-4045-844f-42b6c87487aa",
344
+ "source_node_id": "8450dd06-975a-41a4-a564-808ee8808fe6",
345
+ "source_handle_id": "d4a097ab-e22d-42f1-b6bc-2ed96856377a",
346
+ "target_node_id": "1f4e3b7b-6af1-42c8-ab33-05b0f01e2b62",
347
+ "target_handle_id": "7d94907f-c840-4ced-b813-ee3b17f2a8a9",
348
+ "type": "DEFAULT",
349
+ },
339
350
  ],
340
351
  serialized_edges,
341
352
  ignore_order=True,