vellum-ai 1.2.3__py3-none-any.whl → 1.2.4__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.
Files changed (28) hide show
  1. vellum/client/core/client_wrapper.py +2 -2
  2. vellum/workflows/events/tests/test_event.py +9 -0
  3. vellum/workflows/events/types.py +3 -1
  4. vellum/workflows/integrations/tests/test_mcp_service.py +40 -1
  5. vellum/workflows/nodes/core/templating_node/node.py +3 -2
  6. vellum/workflows/nodes/core/templating_node/tests/test_templating_node.py +129 -0
  7. vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +12 -0
  8. vellum/workflows/nodes/displayable/bases/inline_prompt_node/tests/test_inline_prompt_node.py +41 -0
  9. vellum/workflows/nodes/displayable/bases/utils.py +38 -1
  10. vellum/workflows/nodes/displayable/code_execution_node/utils.py +3 -20
  11. vellum/workflows/nodes/displayable/inline_prompt_node/node.py +3 -26
  12. vellum/workflows/nodes/displayable/prompt_deployment_node/node.py +3 -25
  13. vellum/workflows/nodes/utils.py +26 -1
  14. vellum/workflows/types/definition.py +1 -0
  15. vellum/workflows/utils/functions.py +4 -0
  16. vellum/workflows/utils/tests/test_functions.py +6 -3
  17. {vellum_ai-1.2.3.dist-info → vellum_ai-1.2.4.dist-info}/METADATA +1 -1
  18. {vellum_ai-1.2.3.dist-info → vellum_ai-1.2.4.dist-info}/RECORD +28 -28
  19. vellum_cli/__init__.py +6 -0
  20. vellum_cli/config.py +2 -0
  21. vellum_cli/push.py +3 -0
  22. vellum_cli/tests/test_pull.py +2 -0
  23. vellum_cli/tests/test_push.py +39 -0
  24. vellum_ee/workflows/display/nodes/vellum/tests/test_tool_calling_node.py +2 -0
  25. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_mcp_serialization.py +1 -0
  26. {vellum_ai-1.2.3.dist-info → vellum_ai-1.2.4.dist-info}/LICENSE +0 -0
  27. {vellum_ai-1.2.3.dist-info → vellum_ai-1.2.4.dist-info}/WHEEL +0 -0
  28. {vellum_ai-1.2.3.dist-info → vellum_ai-1.2.4.dist-info}/entry_points.txt +0 -0
@@ -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.2.3",
30
+ "User-Agent": "vellum-ai/1.2.4",
31
31
  "X-Fern-Language": "Python",
32
32
  "X-Fern-SDK-Name": "vellum-ai",
33
- "X-Fern-SDK-Version": "1.2.3",
33
+ "X-Fern-SDK-Version": "1.2.4",
34
34
  **(self.get_custom_headers() or {}),
35
35
  }
36
36
  if self._api_version is not None:
@@ -93,6 +93,7 @@ mock_node_uuid = str(uuid4_from_hash(MockNode.__qualname__))
93
93
  "workflow_version_exec_config": None,
94
94
  },
95
95
  "parent": None,
96
+ "links": None,
96
97
  },
97
98
  ),
98
99
  (
@@ -156,6 +157,7 @@ mock_node_uuid = str(uuid4_from_hash(MockNode.__qualname__))
156
157
  "type": "WORKFLOW_NODE",
157
158
  "span_id": "123e4567-e89b-12d3-a456-426614174000",
158
159
  },
160
+ "links": None,
159
161
  },
160
162
  ),
161
163
  (
@@ -191,6 +193,7 @@ mock_node_uuid = str(uuid4_from_hash(MockNode.__qualname__))
191
193
  },
192
194
  },
193
195
  "parent": None,
196
+ "links": None,
194
197
  },
195
198
  ),
196
199
  (
@@ -224,6 +227,7 @@ mock_node_uuid = str(uuid4_from_hash(MockNode.__qualname__))
224
227
  },
225
228
  },
226
229
  "parent": None,
230
+ "links": None,
227
231
  },
228
232
  ),
229
233
  (
@@ -259,6 +263,7 @@ mock_node_uuid = str(uuid4_from_hash(MockNode.__qualname__))
259
263
  },
260
264
  },
261
265
  "parent": None,
266
+ "links": None,
262
267
  },
263
268
  ),
264
269
  (
@@ -294,6 +299,7 @@ mock_node_uuid = str(uuid4_from_hash(MockNode.__qualname__))
294
299
  },
295
300
  },
296
301
  "parent": None,
302
+ "links": None,
297
303
  },
298
304
  ),
299
305
  (
@@ -334,6 +340,7 @@ mock_node_uuid = str(uuid4_from_hash(MockNode.__qualname__))
334
340
  "mocked": None,
335
341
  },
336
342
  "parent": None,
343
+ "links": None,
337
344
  },
338
345
  ),
339
346
  (
@@ -372,6 +379,7 @@ mock_node_uuid = str(uuid4_from_hash(MockNode.__qualname__))
372
379
  "mocked": None,
373
380
  },
374
381
  "parent": None,
382
+ "links": None,
375
383
  },
376
384
  ),
377
385
  (
@@ -405,6 +413,7 @@ mock_node_uuid = str(uuid4_from_hash(MockNode.__qualname__))
405
413
  "mocked": True,
406
414
  },
407
415
  "parent": None,
416
+ "links": None,
408
417
  },
409
418
  ),
410
419
  ],
@@ -1,12 +1,13 @@
1
1
  from datetime import datetime
2
2
  import json
3
3
  from uuid import UUID, uuid4
4
- from typing import Annotated, Any, Literal, Optional, Union, get_args
4
+ from typing import Annotated, Any, List, Literal, Optional, Union, get_args
5
5
 
6
6
  from pydantic import Field, GetCoreSchemaHandler, Tag, ValidationInfo
7
7
  from pydantic_core import CoreSchema, core_schema
8
8
 
9
9
  from vellum.client.core.pydantic_utilities import UniversalBaseModel
10
+ from vellum.client.types.span_link import SpanLink
10
11
  from vellum.workflows.state.encoder import DefaultStateEncoder
11
12
  from vellum.workflows.types.definition import VellumCodeResourceDefinition
12
13
  from vellum.workflows.types.utils import datetime_now
@@ -164,3 +165,4 @@ class BaseEvent(UniversalBaseModel):
164
165
  trace_id: UUID
165
166
  span_id: UUID
166
167
  parent: Optional[ParentContext] = None
168
+ links: Optional[List[SpanLink]] = None
@@ -2,7 +2,9 @@ import asyncio
2
2
  import json
3
3
  from unittest import mock
4
4
 
5
- from vellum.workflows.integrations.mcp_service import MCPHttpClient
5
+ from vellum.workflows.constants import AuthorizationType
6
+ from vellum.workflows.integrations.mcp_service import MCPHttpClient, MCPService
7
+ from vellum.workflows.types.definition import MCPServer
6
8
 
7
9
 
8
10
  def test_mcp_http_client_sse_response():
@@ -79,3 +81,40 @@ def test_mcp_http_client_json_response():
79
81
 
80
82
  # THEN the JSON response should be returned as expected
81
83
  assert result == sample_json_response
84
+
85
+
86
+ def test_mcp_service_bearer_token_auth():
87
+ """Test that bearer token auth headers are set correctly"""
88
+ # GIVEN an MCP server with bearer token auth
89
+ server = MCPServer(
90
+ name="test-server",
91
+ url="https://test.server.com",
92
+ authorization_type=AuthorizationType.BEARER_TOKEN,
93
+ bearer_token_value="test-token-123",
94
+ )
95
+
96
+ # WHEN we get auth headers
97
+ service = MCPService()
98
+ headers = service._get_auth_headers(server)
99
+
100
+ # THEN the Authorization header should be set correctly
101
+ assert headers == {"Authorization": "Bearer test-token-123"}
102
+
103
+
104
+ def test_mcp_service_api_key_auth():
105
+ """Test that API key auth headers are set correctly"""
106
+ # GIVEN an MCP server with API key auth
107
+ server = MCPServer(
108
+ name="test-server",
109
+ url="https://test.server.com",
110
+ authorization_type=AuthorizationType.API_KEY,
111
+ api_key_header_key="X-API-Key",
112
+ api_key_header_value="api-key-123",
113
+ )
114
+
115
+ # WHEN we get auth headers
116
+ service = MCPService()
117
+ headers = service._get_auth_headers(server)
118
+
119
+ # THEN the custom API key header should be set correctly
120
+ assert headers == {"X-API-Key": "api-key-123"}
@@ -7,7 +7,7 @@ from vellum.workflows.errors import WorkflowErrorCode
7
7
  from vellum.workflows.exceptions import NodeException
8
8
  from vellum.workflows.nodes.bases import BaseNode
9
9
  from vellum.workflows.nodes.bases.base import BaseNodeMeta
10
- from vellum.workflows.nodes.utils import parse_type_from_str
10
+ from vellum.workflows.nodes.utils import parse_type_from_str, wrap_inputs_for_backward_compatibility
11
11
  from vellum.workflows.types.core import EntityInputsInterface
12
12
  from vellum.workflows.types.generics import StateType
13
13
  from vellum.workflows.types.utils import get_original_base
@@ -86,9 +86,10 @@ class TemplatingNode(BaseNode[StateType], Generic[StateType, _OutputType], metac
86
86
 
87
87
  def _render_template(self) -> str:
88
88
  try:
89
+ wrapped_inputs = wrap_inputs_for_backward_compatibility(self.inputs)
89
90
  return render_sandboxed_jinja_template(
90
91
  template=self.template,
91
- input_values=self.inputs,
92
+ input_values=wrapped_inputs,
92
93
  jinja_custom_filters={**self.jinja_custom_filters},
93
94
  jinja_globals=self.jinja_globals,
94
95
  )
@@ -317,3 +317,132 @@ def test_api_error_templating_node():
317
317
 
318
318
  # THEN the output should be empty string
319
319
  assert outputs.result == ""
320
+
321
+
322
+ @pytest.mark.parametrize(
323
+ "template,inputs,expected_result",
324
+ [
325
+ # String value access
326
+ ("{{ text_input.value }}", {"text_input": "hello world"}, "hello world"),
327
+ # Function call value access
328
+ (
329
+ "{{ func.value.name }}",
330
+ {"func": FunctionCall(name="test_function", arguments={"key": "value"})},
331
+ "test_function",
332
+ ),
333
+ # Array item value access
334
+ ("{{ items[0].value }}", {"items": ["apple"]}, "apple"),
335
+ ],
336
+ ids=["string_value", "function_call_value", "array_item_value"],
337
+ )
338
+ def test_templating_node__value_access_patterns_str(template, inputs, expected_result):
339
+ # GIVEN a templating node that accesses wrapper value properties
340
+ class TemplateNode(TemplatingNode[BaseState, str]):
341
+ pass
342
+
343
+ # Set template and inputs dynamically
344
+ TemplateNode.template = template
345
+ TemplateNode.inputs = inputs
346
+
347
+ # WHEN the node is run
348
+ node = TemplateNode()
349
+ outputs = node.run()
350
+
351
+ # THEN the value is accessible
352
+ assert outputs.result == expected_result
353
+
354
+
355
+ @pytest.mark.parametrize(
356
+ "template,inputs,expected_result",
357
+ [
358
+ # Dict value access
359
+ ("{{ data.value }}", {"data": {"name": "test", "score": 42}}, {"name": "test", "score": 42}),
360
+ # List value access
361
+ ("{{ items.value }}", {"items": ["item1", "item2", "item3"]}, ["item1", "item2", "item3"]),
362
+ ],
363
+ ids=["dict_value", "list_value"],
364
+ )
365
+ def test_templating_node__value_access_patterns_json(template, inputs, expected_result):
366
+ # GIVEN a templating node that accesses wrapper value properties
367
+ class TemplateNode(TemplatingNode[BaseState, Json]):
368
+ pass
369
+
370
+ # Set template and inputs dynamically
371
+ TemplateNode.template = template
372
+ TemplateNode.inputs = inputs
373
+
374
+ # WHEN the node is run
375
+ node = TemplateNode()
376
+ outputs = node.run()
377
+
378
+ # THEN the value is accessible
379
+ assert outputs.result == expected_result
380
+
381
+
382
+ @pytest.mark.parametrize(
383
+ "template,inputs,expected_result",
384
+ [
385
+ # String type access
386
+ ("{{ text_input.type }}", {"text_input": "hello world"}, "STRING"),
387
+ # Function call type access
388
+ ("{{ func.type }}", {"func": FunctionCall(name="test_function", arguments={"key": "value"})}, "FUNCTION_CALL"),
389
+ ],
390
+ ids=["string_type", "function_call_type"],
391
+ )
392
+ def test_templating_node__type_access_patterns(template, inputs, expected_result):
393
+ # GIVEN a templating node that accesses wrapper type properties
394
+ class TemplateNode(TemplatingNode[BaseState, str]):
395
+ pass
396
+
397
+ # Set template and inputs dynamically
398
+ TemplateNode.template = template
399
+ TemplateNode.inputs = inputs
400
+
401
+ # WHEN the node is run
402
+ node = TemplateNode()
403
+ outputs = node.run()
404
+
405
+ # THEN the type is accessible
406
+ assert outputs.result == expected_result
407
+
408
+
409
+ def test_templating_node__nested_dict_access():
410
+ # GIVEN a templating node with nested dict access
411
+ class TemplateNode(TemplatingNode[BaseState, str]):
412
+ template = "{{ data.user.name }}"
413
+ inputs = {"data": {"user": {"name": "John Doe", "age": 30}, "status": "active"}}
414
+
415
+ # WHEN the node is run
416
+ node = TemplateNode()
417
+ outputs = node.run()
418
+
419
+ # THEN nested properties are accessible
420
+ assert outputs.result == "John Doe"
421
+
422
+
423
+ def test_templating_node__list_iteration_wrapper_access():
424
+ # GIVEN a templating node that iterates over list with wrapper access
425
+ class TemplateNode(TemplatingNode[BaseState, str]):
426
+ template = "{% for item in items %}{{ item.value }}{% if not loop.last %},{% endif %}{% endfor %}"
427
+ inputs = {"items": ["apple", "banana", "cherry"]}
428
+
429
+ # WHEN the node is run
430
+ node = TemplateNode()
431
+ outputs = node.run()
432
+
433
+ # THEN list iteration with wrapper access works
434
+ assert outputs.result == "apple,banana,cherry"
435
+
436
+
437
+ def test_templating_node__conditional_type_checking():
438
+ # GIVEN a templating node with conditional type checking
439
+ class TemplateNode(TemplatingNode[BaseState, str]):
440
+ template = "{% if input.type == 'STRING' %}{{ input.value }}{% else %}unknown{% endif %}"
441
+ inputs = {"input": "test string"}
442
+
443
+ # WHEN the node is run
444
+ node = TemplateNode()
445
+ outputs = node.run()
446
+
447
+ # THEN conditional type checking works
448
+ assert outputs.result == "test string"
@@ -28,6 +28,7 @@ from vellum.workflows.errors.types import vellum_error_to_workflow_error
28
28
  from vellum.workflows.events.types import default_serializer
29
29
  from vellum.workflows.exceptions import NodeException
30
30
  from vellum.workflows.nodes.displayable.bases.base_prompt_node import BasePromptNode
31
+ from vellum.workflows.nodes.displayable.bases.utils import process_additional_prompt_outputs
31
32
  from vellum.workflows.outputs import BaseOutput
32
33
  from vellum.workflows.types import MergeBehavior
33
34
  from vellum.workflows.types.definition import DeploymentDefinition
@@ -197,6 +198,17 @@ class BaseInlinePromptNode(BasePromptNode[StateType], Generic[StateType]):
197
198
  elif event.state == "STREAMING":
198
199
  yield BaseOutput(name="results", delta=event.output.value)
199
200
  elif event.state == "FULFILLED":
201
+ if event.meta and event.meta.finish_reason == "LENGTH":
202
+ text_value, json_value = process_additional_prompt_outputs(event.outputs)
203
+ if text_value == "":
204
+ raise NodeException(
205
+ message=(
206
+ "Maximum tokens reached before model could output any content. "
207
+ "Consider increasing the max_tokens Prompt Parameter."
208
+ ),
209
+ code=WorkflowErrorCode.INVALID_OUTPUTS,
210
+ )
211
+
200
212
  outputs = event.outputs
201
213
  yield BaseOutput(name="results", value=event.outputs)
202
214
  elif event.state == "REJECTED":
@@ -20,6 +20,7 @@ from vellum import (
20
20
  )
21
21
  from vellum.client.types.execute_prompt_event import ExecutePromptEvent
22
22
  from vellum.client.types.fulfilled_execute_prompt_event import FulfilledExecutePromptEvent
23
+ from vellum.client.types.fulfilled_prompt_execution_meta import FulfilledPromptExecutionMeta
23
24
  from vellum.client.types.initiated_execute_prompt_event import InitiatedExecutePromptEvent
24
25
  from vellum.client.types.prompt_output import PromptOutput
25
26
  from vellum.client.types.prompt_request_string_input import PromptRequestStringInput
@@ -684,3 +685,43 @@ def test_inline_prompt_node__invalid_function_type():
684
685
  # AND the error should have the correct code and message
685
686
  assert excinfo.value.code == WorkflowErrorCode.INVALID_INPUTS
686
687
  assert "`not_a_function` is not a valid function definition" == str(excinfo.value)
688
+
689
+
690
+ def test_inline_prompt_node__empty_string_output_with_length_finish_reason(vellum_adhoc_prompt_client):
691
+ """
692
+ Tests that InlinePromptNode raises NodeException for empty string output with LENGTH finish_reason.
693
+ """
694
+
695
+ # GIVEN an InlinePromptNode with basic configuration
696
+ class TestNode(InlinePromptNode):
697
+ ml_model = "test-model"
698
+ blocks = []
699
+ prompt_inputs = {}
700
+
701
+ expected_outputs: List[PromptOutput] = [
702
+ StringVellumValue(value=""),
703
+ ]
704
+
705
+ def generate_prompt_events(*args: Any, **kwargs: Any) -> Iterator[ExecutePromptEvent]:
706
+ execution_id = str(uuid4())
707
+ events: List[ExecutePromptEvent] = [
708
+ InitiatedExecutePromptEvent(execution_id=execution_id),
709
+ FulfilledExecutePromptEvent(
710
+ execution_id=execution_id,
711
+ outputs=expected_outputs,
712
+ meta=FulfilledPromptExecutionMeta(finish_reason="LENGTH"),
713
+ ),
714
+ ]
715
+ yield from events
716
+
717
+ vellum_adhoc_prompt_client.adhoc_execute_prompt_stream.side_effect = generate_prompt_events
718
+
719
+ # WHEN the node is run
720
+ node = TestNode()
721
+
722
+ # THEN it should raise a NodeException with INVALID_OUTPUTS error code
723
+ with pytest.raises(NodeException) as excinfo:
724
+ list(node.run())
725
+
726
+ # AND the exception should have the correct error code
727
+ assert excinfo.value.code == WorkflowErrorCode.INVALID_OUTPUTS
@@ -1,7 +1,8 @@
1
1
  import enum
2
2
  import json
3
- from typing import Any, List, Union, cast
3
+ from typing import Any, List, Optional, Tuple, Union, cast
4
4
 
5
+ from vellum import PromptOutput
5
6
  from vellum.client.types.array_vellum_value import ArrayVellumValue
6
7
  from vellum.client.types.array_vellum_value_request import ArrayVellumValueRequest
7
8
  from vellum.client.types.audio_vellum_value import AudioVellumValue
@@ -123,3 +124,39 @@ def primitive_to_vellum_value_request(value: Any) -> VellumValueRequest:
123
124
  raise ValueError(f"Unsupported variable type: {vellum_value.__class__.__name__}")
124
125
 
125
126
  return vellum_value_request_class.model_validate(vellum_value.model_dump())
127
+
128
+
129
+ def process_additional_prompt_outputs(outputs: List[PromptOutput]) -> Tuple[str, Optional[Any]]:
130
+ """
131
+ Process prompt outputs using the same logic as prompt nodes to determine text output.
132
+
133
+ Args:
134
+ outputs: List of prompt outputs to process
135
+
136
+ Returns:
137
+ The text representation of the outputs joined with newlines
138
+ The JSON representation of the outputs
139
+ """
140
+ string_outputs = []
141
+ json_output = None
142
+ for output in outputs:
143
+ if output.value is None:
144
+ continue
145
+
146
+ if output.type == "STRING":
147
+ string_outputs.append(output.value)
148
+ try:
149
+ json_output = json.loads(output.value)
150
+ except (json.JSONDecodeError, TypeError):
151
+ pass
152
+ elif output.type == "JSON":
153
+ string_outputs.append(json.dumps(output.value, indent=4))
154
+ json_output = output.value
155
+ elif output.type == "FUNCTION_CALL":
156
+ string_outputs.append(output.value.model_dump_json(indent=4))
157
+ elif output.type == "THINKING":
158
+ continue
159
+ else:
160
+ string_outputs.append(output.value.message)
161
+
162
+ return "\n".join(string_outputs), json_output
@@ -4,13 +4,10 @@ import sys
4
4
  import traceback
5
5
  from typing import Any, Optional, Tuple, Union
6
6
 
7
- from pydantic import BaseModel
8
-
9
7
  from vellum.workflows.errors.types import WorkflowErrorCode
10
8
  from vellum.workflows.exceptions import NodeException
11
- from vellum.workflows.nodes.utils import cast_to_output_type
9
+ from vellum.workflows.nodes.utils import cast_to_output_type, wrap_inputs_for_backward_compatibility
12
10
  from vellum.workflows.state.context import WorkflowContext
13
- from vellum.workflows.types.code_execution_node_wrappers import ListWrapper, clean_for_dict_wrapper
14
11
  from vellum.workflows.types.core import EntityInputsInterface
15
12
 
16
13
 
@@ -51,23 +48,9 @@ def run_code_inline(
51
48
  print_line = f"{' '.join(str_args)}\n"
52
49
  log_buffer.write(print_line)
53
50
 
54
- def wrap_value(value):
55
- if isinstance(value, list):
56
- return ListWrapper(
57
- [
58
- # Convert VellumValue to dict with its fields
59
- (
60
- item.model_dump()
61
- if isinstance(item, BaseModel)
62
- else clean_for_dict_wrapper(item) if isinstance(item, (dict, list, str)) else item
63
- )
64
- for item in value
65
- ]
66
- )
67
- return clean_for_dict_wrapper(value)
68
-
51
+ wrapped_inputs = wrap_inputs_for_backward_compatibility(inputs)
69
52
  exec_globals = {
70
- "__arg__inputs": {name: wrap_value(value) for name, value in inputs.items()},
53
+ "__arg__inputs": wrapped_inputs,
71
54
  "__arg__out": None,
72
55
  "print": _inline_print,
73
56
  }
@@ -1,10 +1,10 @@
1
- import json
2
1
  from typing import Any, Dict, Generic, Iterator, Type, Union
3
2
 
4
3
  from vellum.workflows.constants import undefined
5
4
  from vellum.workflows.errors import WorkflowErrorCode
6
5
  from vellum.workflows.exceptions import NodeException
7
6
  from vellum.workflows.nodes.displayable.bases import BaseInlinePromptNode as BaseInlinePromptNode
7
+ from vellum.workflows.nodes.displayable.bases.utils import process_additional_prompt_outputs
8
8
  from vellum.workflows.outputs import BaseOutput
9
9
  from vellum.workflows.types import MergeBehavior
10
10
  from vellum.workflows.types.generics import StateType
@@ -45,31 +45,8 @@ class InlinePromptNode(BaseInlinePromptNode[StateType], Generic[StateType]):
45
45
  code=WorkflowErrorCode.INTERNAL_ERROR,
46
46
  )
47
47
 
48
- string_outputs = []
49
- json_output = None
50
-
51
- for output in outputs:
52
- if output.value is None:
53
- continue
54
-
55
- if output.type == "STRING":
56
- string_outputs.append(output.value)
57
- try:
58
- json_output = json.loads(output.value)
59
- except (json.JSONDecodeError, TypeError):
60
- pass
61
- elif output.type == "JSON":
62
- string_outputs.append(json.dumps(output.value, indent=4))
63
- json_output = output.value
64
- elif output.type == "FUNCTION_CALL":
65
- string_outputs.append(output.value.model_dump_json(indent=4))
66
- elif output.type == "THINKING":
67
- continue
68
- else:
69
- string_outputs.append(output.value.message)
70
-
71
- value = "\n".join(string_outputs)
72
- yield BaseOutput(name="text", value=value)
48
+ text_output, json_output = process_additional_prompt_outputs(outputs)
49
+ yield BaseOutput(name="text", value=text_output)
73
50
 
74
51
  if json_output:
75
52
  yield BaseOutput(name="json", value=json_output)
@@ -1,10 +1,10 @@
1
- import json
2
1
  from typing import Any, Dict, Iterator, Type, Union
3
2
 
4
3
  from vellum.workflows.constants import undefined
5
4
  from vellum.workflows.errors import WorkflowErrorCode
6
5
  from vellum.workflows.exceptions import NodeException
7
6
  from vellum.workflows.nodes.displayable.bases import BasePromptDeploymentNode as BasePromptDeploymentNode
7
+ from vellum.workflows.nodes.displayable.bases.utils import process_additional_prompt_outputs
8
8
  from vellum.workflows.outputs import BaseOutput
9
9
  from vellum.workflows.types import MergeBehavior
10
10
  from vellum.workflows.types.generics import StateType
@@ -48,30 +48,8 @@ class PromptDeploymentNode(BasePromptDeploymentNode[StateType]):
48
48
  code=WorkflowErrorCode.INTERNAL_ERROR,
49
49
  )
50
50
 
51
- string_outputs = []
52
- json_output = None
53
-
54
- for output in outputs:
55
- if output.value is None:
56
- continue
57
-
58
- if output.type == "STRING":
59
- string_outputs.append(output.value)
60
- try:
61
- json_output = json.loads(output.value)
62
- except (json.JSONDecodeError, TypeError):
63
- pass
64
- elif output.type == "JSON":
65
- string_outputs.append(json.dumps(output.value, indent=4))
66
- elif output.type == "FUNCTION_CALL":
67
- string_outputs.append(output.value.model_dump_json(indent=4))
68
- elif output.type == "THINKING":
69
- continue
70
- else:
71
- string_outputs.append(output.value.message)
72
-
73
- value = "\n".join(string_outputs)
74
- yield BaseOutput(name="text", value=value)
51
+ text_output, json_output = process_additional_prompt_outputs(outputs)
52
+ yield BaseOutput(name="text", value=text_output)
75
53
 
76
54
  if json_output:
77
55
  yield BaseOutput(name="json", value=json_output)
@@ -15,7 +15,12 @@ from vellum.workflows.nodes import BaseNode
15
15
  from vellum.workflows.nodes.bases.base_adornment_node import BaseAdornmentNode
16
16
  from vellum.workflows.ports.port import Port
17
17
  from vellum.workflows.state.base import BaseState
18
- from vellum.workflows.types.code_execution_node_wrappers import DictWrapper, ListWrapper, StringValueWrapper
18
+ from vellum.workflows.types.code_execution_node_wrappers import (
19
+ DictWrapper,
20
+ ListWrapper,
21
+ StringValueWrapper,
22
+ clean_for_dict_wrapper,
23
+ )
19
24
  from vellum.workflows.types.core import Json
20
25
  from vellum.workflows.types.generics import NodeType
21
26
 
@@ -261,3 +266,23 @@ def cast_to_output_type(result: Any, output_type: Any) -> Any:
261
266
  code=WorkflowErrorCode.INVALID_OUTPUTS,
262
267
  message=f"Expected an output of type '{output_type_name}', but received '{result_type_name}'",
263
268
  )
269
+
270
+
271
+ def wrap_inputs_for_backward_compatibility(inputs: Dict[str, Any]) -> Dict[str, Any]:
272
+ """Wrap inputs with backward-compatible wrapper classes for legacy .value and .type support."""
273
+
274
+ def _wrap_single_value(value: Any) -> Any:
275
+ if isinstance(value, list):
276
+ return ListWrapper(
277
+ [
278
+ (
279
+ item.model_dump()
280
+ if isinstance(item, BaseModel)
281
+ else clean_for_dict_wrapper(item) if isinstance(item, (dict, list, str)) else item
282
+ )
283
+ for item in value
284
+ ]
285
+ )
286
+ return clean_for_dict_wrapper(value)
287
+
288
+ return {name: _wrap_single_value(value) for name, value in inputs.items()}
@@ -121,6 +121,7 @@ class ComposioToolDefinition(UniversalBaseModel):
121
121
  class MCPServer(UniversalBaseModel):
122
122
  type: Literal["MCP_SERVER"] = "MCP_SERVER"
123
123
  name: str
124
+ description: str = "" # We don't use this field, its for compatibility with UI
124
125
  url: str
125
126
  authorization_type: Optional[AuthorizationType] = None
126
127
  bearer_token_value: Optional[Union[str, EnvironmentVariableReference]] = None
@@ -89,6 +89,10 @@ def compile_annotation(annotation: Optional[Any], defs: dict[str, Any]) -> dict:
89
89
  # Mypy is incorrect here, the `annotation` attribute is defined on `FieldInfo`
90
90
  field_annotation = field.annotation # type: ignore[attr-defined]
91
91
  properties[field_name] = compile_annotation(field_annotation, defs)
92
+
93
+ if hasattr(field, "description") and field.description is not None:
94
+ properties[field_name]["description"] = field.description # type: ignore[attr-defined]
95
+
92
96
  if field.default is PydanticUndefined:
93
97
  required.append(field_name)
94
98
  else:
@@ -4,7 +4,7 @@ from enum import Enum
4
4
  from unittest.mock import Mock
5
5
  from typing import Annotated, Dict, List, Literal, Optional, Tuple, Union
6
6
 
7
- from pydantic import BaseModel
7
+ from pydantic import BaseModel, Field
8
8
 
9
9
  from vellum.client.types.function_definition import FunctionDefinition
10
10
  from vellum.client.types.string_vellum_value import StringVellumValue
@@ -206,7 +206,7 @@ def test_compile_function_definition__dataclasses():
206
206
  def test_compile_function_definition__pydantic():
207
207
  # GIVEN a function with a pydantic model
208
208
  class MyPydanticModel(BaseModel):
209
- a: int
209
+ a: int = Field(description="The first number")
210
210
  b: str
211
211
 
212
212
  def my_function(c: MyPydanticModel):
@@ -225,7 +225,10 @@ def test_compile_function_definition__pydantic():
225
225
  "$defs": {
226
226
  "MyPydanticModel": {
227
227
  "type": "object",
228
- "properties": {"a": {"type": "integer"}, "b": {"type": "string"}},
228
+ "properties": {
229
+ "a": {"type": "integer", "description": "The first number"},
230
+ "b": {"type": "string"},
231
+ },
229
232
  "required": ["a", "b"],
230
233
  }
231
234
  },
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-ai
3
- Version: 1.2.3
3
+ Version: 1.2.4
4
4
  Summary:
5
5
  License: MIT
6
6
  Requires-Python: >=3.9,<4.0
@@ -1,14 +1,14 @@
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=50dYVkBzGUS1i-UKf_9zv1JMif1lLClriNUSs-Z7i4E,12678
3
+ vellum_cli/__init__.py,sha256=rHcUFsfu-nivhX02R-6dmYr9ee6LMA-wy2DrFnOhC4Q,13030
4
4
  vellum_cli/aliased_group.py,sha256=ugW498j0yv4ALJ8vS9MsO7ctDW7Jlir9j6nE_uHAP8c,3363
5
- vellum_cli/config.py,sha256=r92zfnBRbf7xrRSYv0XbV55wAKbgaiablWB8p47dcOE,9455
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
9
  vellum_cli/ping.py,sha256=p_BCCRjgPhng6JktuECtkDQLbhopt6JpmrtGoLnLJT8,1161
10
10
  vellum_cli/pull.py,sha256=udYyPlJ6VKDdh78rApNJOZgxHl82fcV6iGnRPSdX1LY,14750
11
- vellum_cli/push.py,sha256=5sGkWmwnuPI9mYlBFZO4Wc0WcErZAnFNXZYJByiAJGw,11620
11
+ vellum_cli/push.py,sha256=KpBGq7B-ffwa9QTHsTRSk73l-tfKc3gyiBSn9Pwlsak,11878
12
12
  vellum_cli/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  vellum_cli/tests/conftest.py,sha256=wx3PlJjVB0HRf5dr2b_idOIw27WPPl0J0FNbhIJJaVk,1689
14
14
  vellum_cli/tests/test_config.py,sha256=uvKGDc8BoVyT9_H0Z-g8469zVxomn6Oi3Zj-vK7O_wU,2631
@@ -17,8 +17,8 @@ vellum_cli/tests/test_image_push_error_handling.py,sha256=QRH0eYNEEIkD2-EVFQWYex
17
17
  vellum_cli/tests/test_init.py,sha256=8UOc_ThfouR4ja5cCl_URuLk7ohr9JXfCnG4yka1OUQ,18754
18
18
  vellum_cli/tests/test_main.py,sha256=qDZG-aQauPwBwM6A2DIu1494n47v3pL28XakTbLGZ-k,272
19
19
  vellum_cli/tests/test_ping.py,sha256=b3aQLd-N59_8w2rRiWqwpB1rlHaKEYVbAj1Y3hi7A-g,2605
20
- vellum_cli/tests/test_pull.py,sha256=hxMbW_j0weDDrkzVGpvLpFcwNQdn-fxTv4wBHeYizzc,49904
21
- vellum_cli/tests/test_push.py,sha256=9whdm-i4d3scJeJ7snGrkCVKcDlZsq8WqH_Nnv9KAYk,40395
20
+ vellum_cli/tests/test_pull.py,sha256=WcRtp5N27CbfBv7vx2wNhLTsSacYw7TiBVVXcI7ZMW4,50014
21
+ vellum_cli/tests/test_push.py,sha256=10G-H88tdiYvg9CaC8OXOf25UEzWkWWB01vNhuxzjog,41944
22
22
  vellum_ee/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  vellum_ee/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  vellum_ee/workflows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -64,7 +64,7 @@ vellum_ee/workflows/display/nodes/vellum/tests/test_retry_node.py,sha256=WyqX_g-
64
64
  vellum_ee/workflows/display/nodes/vellum/tests/test_search_node.py,sha256=KvByxgbUkVyfPIVxTUBUk6a92JiJMi8eReZWxzfPExU,3864
65
65
  vellum_ee/workflows/display/nodes/vellum/tests/test_subworkflow_deployment_node.py,sha256=BUzHJgjdWnPeZxjFjHfDBKnbFjYjnbXPjc-1hne1B2Y,3965
66
66
  vellum_ee/workflows/display/nodes/vellum/tests/test_templating_node.py,sha256=LSk2gx9TpGXbAqKe8dggQW8yJZqj-Cf0EGJFeGGlEcw,3321
67
- vellum_ee/workflows/display/nodes/vellum/tests/test_tool_calling_node.py,sha256=o-EpLEtb02X9VKEVpRQ-qzQwmCwuD_ykavP0vmquDaM,24733
67
+ vellum_ee/workflows/display/nodes/vellum/tests/test_tool_calling_node.py,sha256=0D1q5yyt5fr1sC2UgBC-1OWXvGcwRx40xvpM7qXyDIA,24819
68
68
  vellum_ee/workflows/display/nodes/vellum/tests/test_try_node.py,sha256=Khjsb53PKpZuyhKoRMgKAL45eGp5hZqXvHmVeQWRw4w,2289
69
69
  vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py,sha256=rHybfUAWwa0LlstQTNthGb-zYXrUCLADFtn_4SGsbw8,4807
70
70
  vellum_ee/workflows/display/nodes/vellum/try_node.py,sha256=47fOnSCEFnY8th9m2yTYlgnoUuzgyRZdjg-SXwn--lk,4079
@@ -97,7 +97,7 @@ vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_n
97
97
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py,sha256=A7Ef8P1-Nyvsb97bumKT9W2R1LuZaY9IKFV-7iRueog,4010
98
98
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_composio_serialization.py,sha256=oVXCjkU0G56QJmqnd_xIwF3D9bhJwALFibM2wmRhwUk,3739
99
99
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_serialization.py,sha256=Sg82qV5NCzQDy-RD90hA6QaHgFHOq6ESNkbWXygsnNw,26367
100
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_mcp_serialization.py,sha256=9ORex-okcpwbbkxEDJyyRlbPid6zLSDduK0fBfrp8kk,2415
100
+ vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_mcp_serialization.py,sha256=QhQbijeCnFeX1i3SMjHJg2WVAEt5JEO3dhFRv-mofdA,2458
101
101
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py,sha256=-7NvpYUZEl2T15AS9MPt_scyhKmUmS3wEPa320sgWOI,10085
102
102
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_workflow_deployment_serialization.py,sha256=XIZZr5POo2NLn2uEWm9EC3rejeBMoO4X-JtzTH6mvp4,4074
103
103
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py,sha256=pLCyMScV88DTBXRH7jXaXOEA1GBq8NIipCUFwIAWnwI,2771
@@ -150,7 +150,7 @@ vellum/client/README.md,sha256=gxc7JlJRBrBZpN5LHa2ORxYTRHFLPnWmnIugN8pmQh4,5600
150
150
  vellum/client/__init__.py,sha256=LOu_TklkJG01SgXIpPWDhOX2QvKDyd2ZbQyr5H0m31I,72349
151
151
  vellum/client/core/__init__.py,sha256=lTcqUPXcx4112yLDd70RAPeqq6tu3eFMe1pKOqkW9JQ,1562
152
152
  vellum/client/core/api_error.py,sha256=44vPoTyWN59gonCIZMdzw7M1uspygiLnr3GNFOoVL2Q,614
153
- vellum/client/core/client_wrapper.py,sha256=tuUywjHx5snyYLC-aRHmN6s0mGzPCp3z0yJV55IMeGw,2840
153
+ vellum/client/core/client_wrapper.py,sha256=UTXev7Ggseqsagx6lWuso8TzaZN7pWxlZdlinovd_p0,2840
154
154
  vellum/client/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
155
155
  vellum/client/core/file.py,sha256=d4NNbX8XvXP32z8KpK2Xovv33nFfruIrpz0QWxlgpZk,2663
156
156
  vellum/client/core/force_multipart.py,sha256=awxh5MtcRYe74ehY8U76jzv6fYM_w_D3Rur7KQQzSDk,429
@@ -1669,8 +1669,8 @@ vellum/workflows/events/relational_threads.py,sha256=zmLrBCBYpdpQV0snKH3HleST-_h
1669
1669
  vellum/workflows/events/stream.py,sha256=xhXJTZirFi0xad5neAQNogrIQ4h47fpnKbVC3vCM5Js,889
1670
1670
  vellum/workflows/events/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1671
1671
  vellum/workflows/events/tests/test_basic_workflow.py,sha256=Pj6orHsXz37jWC5FARi0Sx2Gjf99Owri2Cvr6Chb79k,1765
1672
- vellum/workflows/events/tests/test_event.py,sha256=355RjK5F4qzDEGa4OlO5sZlCwY8Y1Z313nSAIVJpt4k,18132
1673
- vellum/workflows/events/types.py,sha256=3cv2r3e57yto7M4Nlobtyz9GO4bXubj4TMHSFvdECP8,4840
1672
+ vellum/workflows/events/tests/test_event.py,sha256=c2pM8ZOuES_8XjLYP4cU4cChrZUZ1ZZ3HUNIaaPAxXk,18411
1673
+ vellum/workflows/events/types.py,sha256=5iU8KmS_fl0l42XBQshiNLPl2yLdPMEQKQ8nYP--0L0,4940
1674
1674
  vellum/workflows/events/workflow.py,sha256=gjg4Dt5i40jaTMK_-AaGtW9lzX1JGn3FUAu1XqhduJ8,8753
1675
1675
  vellum/workflows/exceptions.py,sha256=NiBiR3ggfmPxBVqD-H1SqmjI-7mIn0EStSN1BqApvCM,1213
1676
1676
  vellum/workflows/expressions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -1728,7 +1728,7 @@ vellum/workflows/inputs/tests/test_inputs.py,sha256=lioA8917mFLYq7Ml69UNkqUjcWbb
1728
1728
  vellum/workflows/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1729
1729
  vellum/workflows/integrations/composio_service.py,sha256=rSliaZtNiBcDSvDxz9k5i1KkyUIrbxyegu0yU9cDByU,6023
1730
1730
  vellum/workflows/integrations/mcp_service.py,sha256=9DYb8dg2_kgc1UOu830kxhaFlt9yTbhKPhK3L6kb1t4,9831
1731
- vellum/workflows/integrations/tests/test_mcp_service.py,sha256=ff2-f7piLdL_6u6E4ejANs3WQIHch54-O6Dw3tFJs6E,3282
1731
+ vellum/workflows/integrations/tests/test_mcp_service.py,sha256=xQVtcqRbgq6Bx4pyxLdgUtZFNGISPfISvpXcyd0U6Hc,4595
1732
1732
  vellum/workflows/logging.py,sha256=_a217XogktV4Ncz6xKFz7WfYmZAzkfVRVuC0rWob8ls,437
1733
1733
  vellum/workflows/nodes/__init__.py,sha256=aVdQVv7Y3Ro3JlqXGpxwaU2zrI06plDHD2aumH5WUIs,1157
1734
1734
  vellum/workflows/nodes/bases/__init__.py,sha256=cniHuz_RXdJ4TQgD8CBzoiKDiPxg62ErdVpCbWICX64,58
@@ -1753,8 +1753,8 @@ vellum/workflows/nodes/core/retry_node/node.py,sha256=EM4ya8Myr7ADllpjt9q-BAhB3h
1753
1753
  vellum/workflows/nodes/core/retry_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1754
1754
  vellum/workflows/nodes/core/retry_node/tests/test_node.py,sha256=PCvD_XROP26k4cVYOdSQUfkDdbTljAJxtOTFzOUoS8c,4450
1755
1755
  vellum/workflows/nodes/core/templating_node/__init__.py,sha256=GmyuYo81_A1_Bz6id69ozVFS6FKiuDsZTiA3I6MaL2U,70
1756
- vellum/workflows/nodes/core/templating_node/node.py,sha256=iqBmr2i-f-BqhisNQJiDfewjol0ur7-XpupLStyMJsg,3731
1757
- vellum/workflows/nodes/core/templating_node/tests/test_templating_node.py,sha256=nXkgGDBg4wP36NwykdMEVWwx_xjv8oGT2rYkwuCB_VU,10075
1756
+ vellum/workflows/nodes/core/templating_node/node.py,sha256=mn0JEbORWaM-mR7fgUZy-BItZCup8CaxZQaY_g3TSEE,3855
1757
+ vellum/workflows/nodes/core/templating_node/tests/test_templating_node.py,sha256=RzX00apXes3Kz7AY0a_Ewx65byRwp2FFc1ceh0AWUOc,14381
1758
1758
  vellum/workflows/nodes/core/try_node/__init__.py,sha256=JVD4DrldTIqFQQFrubs9KtWCCc0YCAc7Fzol5ZWIWeM,56
1759
1759
  vellum/workflows/nodes/core/try_node/node.py,sha256=XdyOvlwQ3m4h0-_WNtaBl2t_CdlzPXclulkLOtUcX3E,4388
1760
1760
  vellum/workflows/nodes/core/try_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -1772,22 +1772,22 @@ vellum/workflows/nodes/displayable/bases/api_node/tests/test_node.py,sha256=5C59
1772
1772
  vellum/workflows/nodes/displayable/bases/base_prompt_node/__init__.py,sha256=Org3xTvgp1pA0uUXFfnJr29D3HzCey2lEdYF4zbIUgo,70
1773
1773
  vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py,sha256=ea20icDM1HB942wkH-XtXNSNCBDcjeOiN3vowkHL4fs,4477
1774
1774
  vellum/workflows/nodes/displayable/bases/inline_prompt_node/__init__.py,sha256=Hl35IAoepRpE-j4cALaXVJIYTYOF3qszyVbxTj4kS1s,82
1775
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py,sha256=KjLVJtonJ_3GT9RtjIuK56Kf4DpoybjxOrAxGhUp-J8,13102
1775
+ vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py,sha256=cW0fgmferJVm6LctqC5kCFu4Qg9udMQEb0IISLS7CTo,13795
1776
1776
  vellum/workflows/nodes/displayable/bases/inline_prompt_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1777
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/tests/test_inline_prompt_node.py,sha256=51JjPgWPNOOuEKOk5VQ7qchWbQ_zW-lILy1Yl59Q7A8,23455
1777
+ vellum/workflows/nodes/displayable/bases/inline_prompt_node/tests/test_inline_prompt_node.py,sha256=Hk_u2IxLIeeqL_s0RTgoyL5QGYwY9VllKT8z5_JHiDU,24956
1778
1778
  vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py,sha256=0a40fkkZkFMmZN0CsWf6EP_y1H6x36EGa3WcfVNyOsM,9797
1779
1779
  vellum/workflows/nodes/displayable/bases/search_node.py,sha256=9TtFn6oNpEkpCL59QdBViUe4WPjcITajbiS7EOjOGag,6114
1780
1780
  vellum/workflows/nodes/displayable/bases/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1781
1781
  vellum/workflows/nodes/displayable/bases/tests/test_utils.py,sha256=eqdqbKNRWVMDPevgwLg1i6YK0g4L4bCy-7xCBN5yYZI,3156
1782
1782
  vellum/workflows/nodes/displayable/bases/types.py,sha256=C37B2Qh2YP7s7pUjd-EYKc2Zl1TbnCgI_mENuUSb8bo,1706
1783
- vellum/workflows/nodes/displayable/bases/utils.py,sha256=lHlb_76bzC3FI6C3ZS3pq6LFhX8AuP6I5pIma_4FEfc,5700
1783
+ vellum/workflows/nodes/displayable/bases/utils.py,sha256=X1YSPmbBlxDrTAw6dy2-9HrIG8Vb_J-2k1lP3i-SOsk,6951
1784
1784
  vellum/workflows/nodes/displayable/code_execution_node/__init__.py,sha256=0FLWMMktpzSnmBMizQglBpcPrP80fzVsoJwJgf822Cg,76
1785
1785
  vellum/workflows/nodes/displayable/code_execution_node/node.py,sha256=cCpZPna1Exw060KlI8j3oMCfru1GCzUFJz9ucWdcZVM,10311
1786
1786
  vellum/workflows/nodes/displayable/code_execution_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1787
1787
  vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1788
1788
  vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/main.py,sha256=5QsbmkzSlSbcbWTG_JmIqcP-JNJzOPTKxGzdHos19W4,79
1789
1789
  vellum/workflows/nodes/displayable/code_execution_node/tests/test_node.py,sha256=0lxmU-XXie6qu35qAEfZJ3vKZ1XGvWV4eq9y2RMelOE,38521
1790
- vellum/workflows/nodes/displayable/code_execution_node/utils.py,sha256=VRTKms59vrSR9mDk99cojParZVAP4lzjEeDwDNXU1tk,3837
1790
+ vellum/workflows/nodes/displayable/code_execution_node/utils.py,sha256=GxSjRBiH8kNg2dXUVTtnX47fqPIFoGHxRB4ySU6uVPk,3225
1791
1791
  vellum/workflows/nodes/displayable/conditional_node/__init__.py,sha256=AS_EIqFdU1F9t8aLmbZU-rLh9ry6LCJ0uj0D8F0L5Uw,72
1792
1792
  vellum/workflows/nodes/displayable/conditional_node/node.py,sha256=71ZUNfTiD7t2Kai2ypw0tmv1lSf1brQaHAQD-SeUrGE,1101
1793
1793
  vellum/workflows/nodes/displayable/conftest.py,sha256=K2kLM2JGAfcrmmd92u8DXInUO5klFdggPWblg5RVcx4,5729
@@ -1801,7 +1801,7 @@ vellum/workflows/nodes/displayable/guardrail_node/test_node.py,sha256=SAGv6hSFcB
1801
1801
  vellum/workflows/nodes/displayable/guardrail_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1802
1802
  vellum/workflows/nodes/displayable/guardrail_node/tests/test_node.py,sha256=X2pd6TI8miYxIa7rgvs1pHTEreyWcf77EyR0_Jsa700,2055
1803
1803
  vellum/workflows/nodes/displayable/inline_prompt_node/__init__.py,sha256=gSUOoEZLlrx35-tQhSAd3An8WDwBqyiQh-sIebLU9wU,74
1804
- vellum/workflows/nodes/displayable/inline_prompt_node/node.py,sha256=3jY-2UJarVJSz2eupt7c9Mp-Mk56U2meXU0d7c0KVNk,2941
1804
+ vellum/workflows/nodes/displayable/inline_prompt_node/node.py,sha256=0O1dksk0h6-V-Mp5_yTuU3PFrUpLIgsqVMTP8mxTryU,2227
1805
1805
  vellum/workflows/nodes/displayable/inline_prompt_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1806
1806
  vellum/workflows/nodes/displayable/inline_prompt_node/tests/test_node.py,sha256=bBHs90mV5SZ3rJPAL0wx4WWyawUA406LgMPOdvpZC_A,10923
1807
1807
  vellum/workflows/nodes/displayable/merge_node/__init__.py,sha256=J8IC08dSH7P76wKlNuxe1sn7toNGtSQdFirUbtPDEs0,60
@@ -1809,7 +1809,7 @@ vellum/workflows/nodes/displayable/merge_node/node.py,sha256=nZtGGVAvY4fvGg8vwV6
1809
1809
  vellum/workflows/nodes/displayable/note_node/__init__.py,sha256=KWA3P4fyYJ-fOTky8qNGlcOotQ-HeHJ9AjZt6mRQmCE,58
1810
1810
  vellum/workflows/nodes/displayable/note_node/node.py,sha256=sIN1VBQ7zeT3GhN0kupXbFfdpvgedWV79k4woJNp5IQ,394
1811
1811
  vellum/workflows/nodes/displayable/prompt_deployment_node/__init__.py,sha256=krX1Hds-TSVYZsx0wJFX4wsAKkEFYOX1ifwRGiIM-EA,82
1812
- vellum/workflows/nodes/displayable/prompt_deployment_node/node.py,sha256=r-HGevtJ0wXPPW58dNfF3euuDmXO-p1Qm8WuJTJqVtg,3312
1812
+ vellum/workflows/nodes/displayable/prompt_deployment_node/node.py,sha256=XeBn6d3ce-RJuUiTwpDKUKUJvcYLQw61YufpIGVWPHY,2641
1813
1813
  vellum/workflows/nodes/displayable/prompt_deployment_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1814
1814
  vellum/workflows/nodes/displayable/prompt_deployment_node/tests/test_node.py,sha256=c_nuuqrwiIjgj4qIbVypfDuOc-3TlgO6CbXFqQl2Nqw,19725
1815
1815
  vellum/workflows/nodes/displayable/search_node/__init__.py,sha256=hpBpvbrDYf43DElRZFLzieSn8weXiwNiiNOJurERQbs,62
@@ -1841,7 +1841,7 @@ vellum/workflows/nodes/mocks.py,sha256=a1FjWEIocseMfjzM-i8DNozpUsaW0IONRpZmXBoWl
1841
1841
  vellum/workflows/nodes/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1842
1842
  vellum/workflows/nodes/tests/test_mocks.py,sha256=mfPvrs75PKcsNsbJLQAN6PDFoVqs9TmQxpdyFKDdO60,7837
1843
1843
  vellum/workflows/nodes/tests/test_utils.py,sha256=OPVZo9yi8qt0rVqayKhfgh4Hk-dVdIzqfbS89fDhRiE,4913
1844
- vellum/workflows/nodes/utils.py,sha256=K2gf05eM-EKkKHf2SPpvEly8cBL4RftWSMvIZJIMlso,9455
1844
+ vellum/workflows/nodes/utils.py,sha256=CwDobM6T4Whm1B0cekPAIj7pdSxbFGRxx_BqgxZrimg,10265
1845
1845
  vellum/workflows/outputs/__init__.py,sha256=AyZ4pRh_ACQIGvkf0byJO46EDnSix1ZCAXfvh-ms1QE,94
1846
1846
  vellum/workflows/outputs/base.py,sha256=PUn0zhGzYCSZL34JXtXg9zALlXS_cqxZldLilPxDzb8,9614
1847
1847
  vellum/workflows/ports/__init__.py,sha256=bZuMt-R7z5bKwpu4uPW7LlJeePOQWmCcDSXe5frUY5g,101
@@ -1880,7 +1880,7 @@ vellum/workflows/tests/test_undefined.py,sha256=zMCVliCXVNLrlC6hEGyOWDnQADJ2g83y
1880
1880
  vellum/workflows/types/__init__.py,sha256=KxUTMBGzuRCfiMqzzsykOeVvrrkaZmTTo1a7SLu8gRM,68
1881
1881
  vellum/workflows/types/code_execution_node_wrappers.py,sha256=3MNIoFZKzVzNS5qFLVuDwMV17QJw72zo7NRf52yMq5A,3074
1882
1882
  vellum/workflows/types/core.py,sha256=TggDVs2lVya33xvu374EDhMC1b7RRlAAs0zWLaF46BA,1385
1883
- vellum/workflows/types/definition.py,sha256=AiiA1tg7RUpcX3d4xy6GhOET65ZPDq7ushM8T1B06Fo,4909
1883
+ vellum/workflows/types/definition.py,sha256=2vq3uGT-m994zRcla0yTUsMiPLKSDuzEZs7H6U9QbiE,4993
1884
1884
  vellum/workflows/types/generics.py,sha256=8jptbEx1fnJV0Lhj0MpCJOT6yNiEWeTOYOwrEAb5CRU,1576
1885
1885
  vellum/workflows/types/stack.py,sha256=h7NE0vXR7l9DevFBIzIAk1Zh59K-kECQtDTKOUunwMY,1314
1886
1886
  vellum/workflows/types/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -1888,12 +1888,12 @@ vellum/workflows/types/tests/test_definition.py,sha256=rvDYjdJ1rvAv0qHBN7i7s-_WA
1888
1888
  vellum/workflows/types/tests/test_utils.py,sha256=UnZog59tR577mVwqZRqqWn2fScoOU1H6up0EzS8zYhw,2536
1889
1889
  vellum/workflows/types/utils.py,sha256=mTctHITBybpt4855x32oCKALBEcMNLn-9cCmfEKgJHQ,6498
1890
1890
  vellum/workflows/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1891
- vellum/workflows/utils/functions.py,sha256=whJkecRj71V10LM64f0g0q02LBq5nqARnNBxuJYvDHc,8773
1891
+ vellum/workflows/utils/functions.py,sha256=_mJ7vGxHLIByPbfMpDVKsWVEUGlCof1Bc2aZefF56Ns,8967
1892
1892
  vellum/workflows/utils/hmac.py,sha256=JJCczc6pyV6DuE1Oa0QVfYPUN_of3zEYmGFib3OZnrE,1135
1893
1893
  vellum/workflows/utils/names.py,sha256=QLUqfJ1tmSEeUwBKTTiv_Qk3QGbInC2RSmlXfGXc8Wo,380
1894
1894
  vellum/workflows/utils/pydantic_schema.py,sha256=eR_bBtY-T0pttJP-ARwagSdCOnwPUtiT3cegm2lzDTQ,1310
1895
1895
  vellum/workflows/utils/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1896
- vellum/workflows/utils/tests/test_functions.py,sha256=U-znUtUz3RFfW3SKYzpBqj2wlv5fsuMZ6RXImrLGAgE,23535
1896
+ vellum/workflows/utils/tests/test_functions.py,sha256=TlNhD0OBJ-aeYn4qUqmApuajkx9sJx0lIQRMfT8Xf0w,23688
1897
1897
  vellum/workflows/utils/tests/test_names.py,sha256=aOqpyvMsOEK_9mg_-yaNxQDW7QQfwqsYs37PseyLhxw,402
1898
1898
  vellum/workflows/utils/tests/test_uuids.py,sha256=i77ABQ0M3S-aFLzDXHJq_yr5FPkJEWCMBn1HJ3DObrE,437
1899
1899
  vellum/workflows/utils/tests/test_vellum_variables.py,sha256=vbnKgm41aB5OXlO-ZIPbhQ6xDiZkT8KMxCLqz4zocWY,1791
@@ -1906,8 +1906,8 @@ vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnad
1906
1906
  vellum/workflows/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1907
1907
  vellum/workflows/workflows/tests/test_base_workflow.py,sha256=ptMntHzVyy8ZuzNgeTuk7hREgKQ5UBdgq8VJFSGaW4Y,20832
1908
1908
  vellum/workflows/workflows/tests/test_context.py,sha256=VJBUcyWVtMa_lE5KxdhgMu0WYNYnUQUDvTF7qm89hJ0,2333
1909
- vellum_ai-1.2.3.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1910
- vellum_ai-1.2.3.dist-info/METADATA,sha256=x3lBa_prpTnylmEjbsWCn01d3-owo5mW2q6zS-xCMvA,5547
1911
- vellum_ai-1.2.3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1912
- vellum_ai-1.2.3.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1913
- vellum_ai-1.2.3.dist-info/RECORD,,
1909
+ vellum_ai-1.2.4.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1910
+ vellum_ai-1.2.4.dist-info/METADATA,sha256=5gsVuDhKzbBpbW7awdKuaGhzXGg2IBxDeY66boLv0ns,5547
1911
+ vellum_ai-1.2.4.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1912
+ vellum_ai-1.2.4.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1913
+ vellum_ai-1.2.4.dist-info/RECORD,,
vellum_cli/__init__.py CHANGED
@@ -68,6 +68,7 @@ with the provided module or be available for use. The Workflow Sandbox must also
68
68
  @click.option("--deployment-name", type=str, help="Unique name for the Deployment")
69
69
  @click.option("--deployment-description", type=str, help="Description for the Deployment")
70
70
  @click.option("--release-tag", help="Release Tag for the Deployment", multiple=True)
71
+ @click.option("--release-description", type=str, help="Description for the Release")
71
72
  @click.option(
72
73
  "--dry-run",
73
74
  is_flag=True,
@@ -87,6 +88,7 @@ def workflows_push(
87
88
  deployment_name: Optional[str],
88
89
  deployment_description: Optional[str],
89
90
  release_tag: Optional[List[str]],
91
+ release_description: Optional[str],
90
92
  dry_run: Optional[bool],
91
93
  strict: Optional[bool],
92
94
  workspace: Optional[str],
@@ -104,6 +106,7 @@ def workflows_push(
104
106
  deployment_name=deployment_name,
105
107
  deployment_description=deployment_description,
106
108
  release_tags=release_tag,
109
+ release_description=release_description,
107
110
  dry_run=dry_run,
108
111
  strict=strict,
109
112
  workspace=workspace,
@@ -118,6 +121,7 @@ def workflows_push(
118
121
  @click.option("--deployment-name", type=str, help="Unique name for the Deployment")
119
122
  @click.option("--deployment-description", type=str, help="Description for the Deployment")
120
123
  @click.option("--release-tag", help="Release Tag for the Deployment", multiple=True)
124
+ @click.option("--release-description", type=str, help="Description for the Release")
121
125
  @click.option(
122
126
  "--dry-run",
123
127
  is_flag=True,
@@ -137,6 +141,7 @@ def push_module(
137
141
  deployment_name: Optional[str],
138
142
  deployment_description: Optional[str],
139
143
  release_tag: Optional[List[str]],
144
+ release_description: Optional[str],
140
145
  dry_run: Optional[bool],
141
146
  strict: Optional[bool],
142
147
  workspace: Optional[str],
@@ -152,6 +157,7 @@ def push_module(
152
157
  deployment_name=deployment_name,
153
158
  deployment_description=deployment_description,
154
159
  release_tags=release_tag,
160
+ release_description=release_description,
155
161
  dry_run=dry_run,
156
162
  strict=strict,
157
163
  workspace=workspace,
vellum_cli/config.py CHANGED
@@ -36,6 +36,7 @@ class WorkflowDeploymentConfig(UniversalBaseModel):
36
36
  name: Optional[str] = None
37
37
  description: Optional[str] = None
38
38
  release_tags: Optional[List[str]] = None
39
+ release_description: Optional[str] = None
39
40
 
40
41
  def merge(self, other: "WorkflowDeploymentConfig") -> "WorkflowDeploymentConfig":
41
42
  return WorkflowDeploymentConfig(
@@ -44,6 +45,7 @@ class WorkflowDeploymentConfig(UniversalBaseModel):
44
45
  name=self.name or other.name,
45
46
  description=self.description or other.description,
46
47
  release_tags=self.release_tags or other.release_tags,
48
+ release_description=self.release_description or other.release_description,
47
49
  )
48
50
 
49
51
 
vellum_cli/push.py CHANGED
@@ -28,6 +28,7 @@ def push_command(
28
28
  deployment_name: Optional[str] = None,
29
29
  deployment_description: Optional[str] = None,
30
30
  release_tags: Optional[List[str]] = None,
31
+ release_description: Optional[str] = None,
31
32
  dry_run: Optional[bool] = None,
32
33
  strict: Optional[bool] = None,
33
34
  workspace: Optional[str] = None,
@@ -142,6 +143,7 @@ def push_command(
142
143
  name=deployment_name or cli_deployment_config.name or to_kebab_case(module_name),
143
144
  description=deployment_description or cli_deployment_config.description,
144
145
  release_tags=release_tags or cli_deployment_config.release_tags,
146
+ release_description=release_description or cli_deployment_config.release_description,
145
147
  )
146
148
  except ValidationError as e:
147
149
  for error in e.errors():
@@ -286,6 +288,7 @@ Visit at: {base_url}/workflow-sandboxes/{response.workflow_sandbox_id}"""
286
288
  name=deployment_config.name if deploy else None,
287
289
  description=deployment_config.description if deploy else None,
288
290
  release_tags=deployment_config.release_tags if deploy else None,
291
+ release_description=getattr(deployment_config, "release_description", None) if deploy else None,
289
292
  )
290
293
  workflow_config.deployments.append(stored_deployment_config)
291
294
 
@@ -393,6 +393,7 @@ def test_pull__workflow_deployment_with_no_config(vellum_client):
393
393
  "name": "my-deployment",
394
394
  "description": None,
395
395
  "release_tags": None,
396
+ "release_description": None,
396
397
  }
397
398
  ],
398
399
  "container_image_tag": None,
@@ -641,6 +642,7 @@ def test_pull__sandbox_id_with_other_workflow_deployment_in_lock(vellum_client,
641
642
  "name": None,
642
643
  "description": None,
643
644
  "release_tags": None,
645
+ "release_description": None,
644
646
  },
645
647
  ],
646
648
  "container_image_name": None,
@@ -1042,6 +1042,45 @@ def test_push__deploy_with_release_tags_success(mock_module, vellum_client):
1042
1042
  assert "Updated vellum.lock.json file." in result.output
1043
1043
 
1044
1044
 
1045
+ @pytest.mark.usefixtures("info_log_level")
1046
+ def test_push__deploy_with_release_description_success(mock_module, vellum_client):
1047
+ # GIVEN a single workflow configured
1048
+ temp_dir = mock_module.temp_dir
1049
+ module = mock_module.module
1050
+
1051
+ # AND a workflow exists in the module successfully
1052
+ _ensure_workflow_py(temp_dir, module)
1053
+
1054
+ # AND the push API call returns successfully
1055
+ workflow_deployment_id = str(uuid4())
1056
+ vellum_client.workflows.push.return_value = WorkflowPushResponse(
1057
+ workflow_sandbox_id=str(uuid4()),
1058
+ workflow_deployment_id=workflow_deployment_id,
1059
+ )
1060
+
1061
+ # WHEN calling `vellum workflows push` with --deploy and --release-description
1062
+ runner = CliRunner()
1063
+ result = runner.invoke(
1064
+ cli_main, ["workflows", "push", module, "--deploy", "--release-description", "This is a test release"]
1065
+ )
1066
+
1067
+ # THEN it should succeed
1068
+ assert result.exit_code == 0, result.output
1069
+
1070
+ # AND we should have called the push API with the correct deployment config
1071
+ vellum_client.workflows.push.assert_called_once()
1072
+ call_args = vellum_client.workflows.push.call_args.kwargs
1073
+
1074
+ # AND the deployment_config should contain the release description
1075
+ deployment_config_str = call_args["deployment_config"]
1076
+ deployment_config = json.loads(deployment_config_str)
1077
+ assert deployment_config["release_description"] == "This is a test release"
1078
+
1079
+ # AND should show success message
1080
+ assert "Successfully pushed" in result.output
1081
+ assert "Updated vellum.lock.json file." in result.output
1082
+
1083
+
1045
1084
  def test_push__deploy_stores_deployment_config_in_lock_file(mock_module, vellum_client):
1046
1085
  # GIVEN a single workflow
1047
1086
  temp_dir = mock_module.temp_dir
@@ -191,6 +191,7 @@ def test_serialize_node__tool_calling_node__mcp_server_api_key():
191
191
  {
192
192
  "type": "MCP_SERVER",
193
193
  "name": "my-mcp-server",
194
+ "description": "",
194
195
  "url": "https://my-mcp-server.com",
195
196
  "authorization_type": "API_KEY",
196
197
  "bearer_token_value": None,
@@ -243,6 +244,7 @@ def test_serialize_node__tool_calling_node__mcp_server_no_authorization():
243
244
  {
244
245
  "type": "MCP_SERVER",
245
246
  "name": "my-mcp-server",
247
+ "description": "",
246
248
  "url": "https://my-mcp-server.com",
247
249
  "authorization_type": None,
248
250
  "bearer_token_value": None,
@@ -52,6 +52,7 @@ def test_serialize_workflow():
52
52
  {
53
53
  "type": "MCP_SERVER",
54
54
  "name": "github",
55
+ "description": "",
55
56
  "url": "https://api.githubcopilot.com/mcp/",
56
57
  "authorization_type": "BEARER_TOKEN",
57
58
  "bearer_token_value": "GITHUB_PERSONAL_ACCESS_TOKEN",