vellum-ai 1.0.6__py3-none-any.whl → 1.0.7__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.
@@ -25,10 +25,10 @@ class BaseClientWrapper:
25
25
 
26
26
  def get_headers(self) -> typing.Dict[str, str]:
27
27
  headers: typing.Dict[str, str] = {
28
- "User-Agent": "vellum-ai/1.0.6",
28
+ "User-Agent": "vellum-ai/1.0.7",
29
29
  "X-Fern-Language": "Python",
30
30
  "X-Fern-SDK-Name": "vellum-ai",
31
- "X-Fern-SDK-Version": "1.0.6",
31
+ "X-Fern-SDK-Version": "1.0.7",
32
32
  }
33
33
  if self._api_version is not None:
34
34
  headers["X-API-Version"] = self._api_version
@@ -16,6 +16,7 @@ def test_run_workflow__secrets(vellum_client):
16
16
  json_={"data": [1, 2, 3]},
17
17
  headers={"X-Response-Header": "bar"},
18
18
  )
19
+ vellum_client._client_wrapper.get_headers.return_value = {"User-Agent": "vellum-ai/1.0.6"}
19
20
 
20
21
  class SimpleBaseAPINode(APINode):
21
22
  method = APIRequestMethod.POST
@@ -35,7 +36,9 @@ def test_run_workflow__secrets(vellum_client):
35
36
  assert vellum_client.execute_api.call_count == 1
36
37
  assert vellum_client.execute_api.call_args.kwargs["url"] == "https://example.vellum.ai"
37
38
  assert vellum_client.execute_api.call_args.kwargs["body"] == {"key": "value"}
38
- assert vellum_client.execute_api.call_args.kwargs["headers"] == {"X-Test-Header": "foo"}
39
+ headers = vellum_client.execute_api.call_args.kwargs["headers"]
40
+ assert headers["X-Test-Header"] == "foo"
41
+ assert "vellum-ai" in headers.get("User-Agent", "")
39
42
  bearer_token = vellum_client.execute_api.call_args.kwargs["bearer_token"]
40
43
  assert bearer_token == ClientVellumSecret(name="secret")
41
44
  assert terminal.headers == {"X-Response-Header": "bar"}
@@ -44,6 +47,7 @@ def test_run_workflow__secrets(vellum_client):
44
47
  def test_api_node_raises_error_when_api_call_fails(vellum_client):
45
48
  # GIVEN an API call that fails
46
49
  vellum_client.execute_api.side_effect = ApiError(status_code=400, body="API Error")
50
+ vellum_client._client_wrapper.get_headers.return_value = {"User-Agent": "vellum-ai/1.0.6"}
47
51
 
48
52
  class SimpleAPINode(APINode):
49
53
  method = APIRequestMethod.GET
@@ -70,7 +74,9 @@ def test_api_node_raises_error_when_api_call_fails(vellum_client):
70
74
  assert vellum_client.execute_api.call_count == 1
71
75
  assert vellum_client.execute_api.call_args.kwargs["url"] == "https://example.vellum.ai"
72
76
  assert vellum_client.execute_api.call_args.kwargs["body"] == {"key": "value"}
73
- assert vellum_client.execute_api.call_args.kwargs["headers"] == {"X-Test-Header": "foo"}
77
+ headers = vellum_client.execute_api.call_args.kwargs["headers"]
78
+ assert headers["X-Test-Header"] == "foo"
79
+ assert "vellum-ai" in headers.get("User-Agent", "")
74
80
 
75
81
 
76
82
  def test_api_node_defaults_to_get_method(vellum_client):
@@ -92,6 +92,10 @@ class BaseAPINode(BaseNode, Generic[StateType]):
92
92
  return self._local_execute_api(data, headers, json, normalized_method, url, timeout)
93
93
 
94
94
  def _local_execute_api(self, data, headers, json, method, url, timeout):
95
+ headers = headers or {}
96
+ if "User-Agent" not in headers:
97
+ client_headers = self._context.vellum_client._client_wrapper.get_headers()
98
+ headers["User-Agent"] = client_headers.get("User-Agent")
95
99
  try:
96
100
  if data is not None:
97
101
  prepped = Request(method=method, url=url, data=data, headers=headers).prepare()
@@ -120,6 +124,11 @@ class BaseAPINode(BaseNode, Generic[StateType]):
120
124
  def _vellum_execute_api(self, bearer_token, data, headers, method, url, timeout):
121
125
  client_vellum_secret = ClientVellumSecret(name=bearer_token.name) if bearer_token else None
122
126
 
127
+ headers = headers or {}
128
+ if "User-Agent" not in headers:
129
+ client_headers = self._context.vellum_client._client_wrapper.get_headers()
130
+ headers["User-Agent"] = client_headers.get("User-Agent")
131
+
123
132
  # Create request_options if timeout is specified
124
133
  request_options = None
125
134
  if timeout is not None:
@@ -45,3 +45,80 @@ def test_api_node_with_invalid_method():
45
45
 
46
46
  assert exc_info.value.code == WorkflowErrorCode.INVALID_INPUTS
47
47
  assert "Invalid HTTP method 'INVALID_METHOD'" == str(exc_info.value)
48
+
49
+
50
+ def test_api_node_adds_user_agent_header_when_none_provided(requests_mock):
51
+ """
52
+ Tests that the API node adds User-Agent header when no headers are provided.
53
+ """
54
+
55
+ class TestAPINode(BaseAPINode):
56
+ method = APIRequestMethod.GET
57
+ url = "https://example.com/test"
58
+
59
+ response_mock = requests_mock.get(
60
+ "https://example.com/test",
61
+ json={"result": "success"},
62
+ status_code=200,
63
+ )
64
+
65
+ node = TestAPINode()
66
+ result = node.run()
67
+
68
+ assert response_mock.last_request
69
+ assert "vellum-ai" in response_mock.last_request.headers.get("User-Agent", "")
70
+
71
+ assert result.status_code == 200
72
+
73
+
74
+ def test_api_node_adds_user_agent_header_when_headers_provided_without_user_agent(requests_mock):
75
+ """
76
+ Tests that the API node adds User-Agent header when headers are provided but don't include User-Agent.
77
+ """
78
+
79
+ class TestAPINode(BaseAPINode):
80
+ method = APIRequestMethod.POST
81
+ url = "https://example.com/test"
82
+ headers = {"Content-Type": "application/json", "Custom-Header": "value"}
83
+ json = {"test": "data"}
84
+
85
+ response_mock = requests_mock.post(
86
+ "https://example.com/test",
87
+ json={"result": "success"},
88
+ status_code=200,
89
+ )
90
+
91
+ node = TestAPINode()
92
+ result = node.run()
93
+
94
+ assert response_mock.last_request
95
+ assert "vellum-ai" in response_mock.last_request.headers.get("User-Agent", "")
96
+ assert response_mock.last_request.headers.get("Content-Type") == "application/json"
97
+ assert response_mock.last_request.headers.get("Custom-Header") == "value"
98
+
99
+ assert result.status_code == 200
100
+
101
+
102
+ def test_api_node_preserves_custom_user_agent_header(requests_mock):
103
+ """
104
+ Tests that the API node preserves a custom User-Agent header if provided.
105
+ """
106
+
107
+ class TestAPINode(BaseAPINode):
108
+ method = APIRequestMethod.GET
109
+ url = "https://example.com/test"
110
+ headers = {"User-Agent": "Custom-Agent/1.0"}
111
+
112
+ response_mock = requests_mock.get(
113
+ "https://example.com/test",
114
+ json={"result": "success"},
115
+ status_code=200,
116
+ )
117
+
118
+ node = TestAPINode()
119
+ result = node.run()
120
+
121
+ assert response_mock.last_request
122
+ assert response_mock.last_request.headers.get("User-Agent") == "Custom-Agent/1.0"
123
+
124
+ assert result.status_code == 200
@@ -1,4 +1,4 @@
1
- from typing import ClassVar, Iterator, List, Optional, Set
1
+ from typing import Any, ClassVar, Dict, Iterator, List, Optional, Set, Union
2
2
 
3
3
  from vellum import ChatMessage, PromptBlock
4
4
  from vellum.client.types.prompt_parameters import PromptParameters
@@ -36,7 +36,7 @@ class ToolCallingNode(BaseNode):
36
36
  """
37
37
 
38
38
  ml_model: ClassVar[str] = "gpt-4o-mini"
39
- blocks: ClassVar[List[PromptBlock]] = []
39
+ blocks: ClassVar[List[Union[PromptBlock, Dict[str, Any]]]] = []
40
40
  functions: ClassVar[List[Tool]] = []
41
41
  prompt_inputs: ClassVar[Optional[EntityInputsInterface]] = None
42
42
  parameters: PromptParameters = DEFAULT_PROMPT_PARAMETERS
@@ -1,9 +1,18 @@
1
1
  import pytest
2
-
2
+ from uuid import uuid4
3
+
4
+ from vellum.client.types.chat_message_prompt_block import ChatMessagePromptBlock
5
+ from vellum.client.types.fulfilled_execute_prompt_event import FulfilledExecutePromptEvent
6
+ from vellum.client.types.initiated_execute_prompt_event import InitiatedExecutePromptEvent
7
+ from vellum.client.types.plain_text_prompt_block import PlainTextPromptBlock
8
+ from vellum.client.types.rich_text_prompt_block import RichTextPromptBlock
9
+ from vellum.client.types.string_vellum_value import StringVellumValue
10
+ from vellum.client.types.variable_prompt_block import VariablePromptBlock
11
+ from vellum.prompts.constants import DEFAULT_PROMPT_PARAMETERS
3
12
  from vellum.workflows import BaseWorkflow
4
13
  from vellum.workflows.inputs.base import BaseInputs
5
14
  from vellum.workflows.nodes.bases import BaseNode
6
- from vellum.workflows.nodes.displayable.tool_calling_node.utils import get_function_name
15
+ from vellum.workflows.nodes.displayable.tool_calling_node.utils import create_tool_router_node, get_function_name
7
16
  from vellum.workflows.outputs.base import BaseOutputs
8
17
  from vellum.workflows.state.base import BaseState
9
18
  from vellum.workflows.types.definition import ComposioToolDefinition, DeploymentDefinition
@@ -76,3 +85,139 @@ def test_get_function_name_composio_tool_definition_various_toolkits(
76
85
  result = get_function_name(composio_tool)
77
86
 
78
87
  assert result == expected_result
88
+
89
+
90
+ def test_create_tool_router_node_max_prompt_iterations(vellum_adhoc_prompt_client):
91
+ # GIVEN a tool router node with max_prompt_iterations set to None
92
+ tool_router_node = create_tool_router_node(
93
+ ml_model="gpt-4o-mini",
94
+ blocks=[],
95
+ functions=[],
96
+ prompt_inputs=None,
97
+ parameters=DEFAULT_PROMPT_PARAMETERS,
98
+ max_prompt_iterations=None,
99
+ )
100
+
101
+ def generate_prompt_events(*args, **kwargs):
102
+ execution_id = str(uuid4())
103
+ events = [
104
+ InitiatedExecutePromptEvent(execution_id=execution_id),
105
+ FulfilledExecutePromptEvent(
106
+ execution_id=execution_id,
107
+ outputs=[StringVellumValue(value="test output")],
108
+ ),
109
+ ]
110
+ yield from events
111
+
112
+ vellum_adhoc_prompt_client.adhoc_execute_prompt_stream.side_effect = generate_prompt_events
113
+
114
+ # WHEN we run the tool router node
115
+ node_instance = tool_router_node()
116
+ outputs = list(node_instance.run())
117
+ assert outputs[0].name == "results"
118
+ assert outputs[0].value == [StringVellumValue(type="STRING", value="test output")]
119
+ assert outputs[1].name == "text"
120
+ assert outputs[1].value == "test output"
121
+
122
+
123
+ def test_create_tool_router_node_chat_history_block_dict(vellum_adhoc_prompt_client):
124
+ # GIVEN a list of blocks with a chat history block
125
+ blocks = [
126
+ {
127
+ "block_type": "CHAT_MESSAGE",
128
+ "chat_role": "SYSTEM",
129
+ "blocks": [
130
+ {
131
+ "block_type": "RICH_TEXT",
132
+ "blocks": [{"block_type": "PLAIN_TEXT", "cache_config": None, "text": "first message"}],
133
+ }
134
+ ],
135
+ },
136
+ {
137
+ "block_type": "CHAT_MESSAGE",
138
+ "chat_role": "USER",
139
+ "blocks": [
140
+ {
141
+ "block_type": "RICH_TEXT",
142
+ "blocks": [
143
+ {"block_type": "PLAIN_TEXT", "text": "second message"},
144
+ {"block_type": "PLAIN_TEXT", "text": "third message"},
145
+ ],
146
+ }
147
+ ],
148
+ },
149
+ ]
150
+
151
+ tool_router_node = create_tool_router_node(
152
+ ml_model="gpt-4o-mini",
153
+ blocks=blocks, # type: ignore
154
+ functions=[],
155
+ prompt_inputs=None,
156
+ parameters=DEFAULT_PROMPT_PARAMETERS,
157
+ )
158
+
159
+ def generate_prompt_events(*args, **kwargs):
160
+ execution_id = str(uuid4())
161
+ events = [
162
+ InitiatedExecutePromptEvent(execution_id=execution_id),
163
+ FulfilledExecutePromptEvent(
164
+ execution_id=execution_id,
165
+ outputs=[StringVellumValue(value="test output")],
166
+ ),
167
+ ]
168
+ yield from events
169
+
170
+ vellum_adhoc_prompt_client.adhoc_execute_prompt_stream.side_effect = generate_prompt_events
171
+
172
+ # WHEN we run the tool router node
173
+ node_instance = tool_router_node()
174
+ list(node_instance.run())
175
+
176
+ # THEN the API was called with compiled blocks
177
+ blocks = vellum_adhoc_prompt_client.adhoc_execute_prompt_stream.call_args[1]["blocks"]
178
+ assert blocks == [
179
+ ChatMessagePromptBlock(
180
+ block_type="CHAT_MESSAGE",
181
+ state=None,
182
+ cache_config=None,
183
+ chat_role="SYSTEM",
184
+ chat_source=None,
185
+ chat_message_unterminated=None,
186
+ blocks=[
187
+ RichTextPromptBlock(
188
+ block_type="RICH_TEXT",
189
+ state=None,
190
+ cache_config=None,
191
+ blocks=[
192
+ PlainTextPromptBlock(
193
+ block_type="PLAIN_TEXT", state=None, cache_config=None, text="first message"
194
+ )
195
+ ],
196
+ )
197
+ ],
198
+ ),
199
+ ChatMessagePromptBlock(
200
+ block_type="CHAT_MESSAGE",
201
+ state=None,
202
+ cache_config=None,
203
+ chat_role="USER",
204
+ chat_source=None,
205
+ chat_message_unterminated=None,
206
+ blocks=[
207
+ RichTextPromptBlock(
208
+ block_type="RICH_TEXT",
209
+ state=None,
210
+ cache_config=None,
211
+ blocks=[
212
+ PlainTextPromptBlock(
213
+ block_type="PLAIN_TEXT", state=None, cache_config=None, text="second message"
214
+ ),
215
+ PlainTextPromptBlock(
216
+ block_type="PLAIN_TEXT", state=None, cache_config=None, text="third message"
217
+ ),
218
+ ],
219
+ )
220
+ ],
221
+ ),
222
+ VariablePromptBlock(block_type="VARIABLE", state=None, cache_config=None, input_variable="chat_history"),
223
+ ]
@@ -1,6 +1,6 @@
1
1
  import json
2
2
  import logging
3
- from typing import Any, Callable, Iterator, List, Optional, Type, Union, cast
3
+ from typing import Any, Callable, Dict, Iterator, List, Optional, Type, Union, cast
4
4
 
5
5
  from pydash import snake_case
6
6
 
@@ -67,7 +67,7 @@ class ToolRouterNode(InlinePromptNode[ToolCallingState]):
67
67
  merge_behavior = MergeBehavior.AWAIT_ATTRIBUTES
68
68
 
69
69
  def run(self) -> Iterator[BaseOutput]:
70
- if self.state.prompt_iterations >= self.max_prompt_iterations:
70
+ if self.max_prompt_iterations is not None and self.state.prompt_iterations >= self.max_prompt_iterations:
71
71
  max_iterations_message = f"Maximum number of prompt iterations `{self.max_prompt_iterations}` reached."
72
72
  raise NodeException(message=max_iterations_message, code=WorkflowErrorCode.NODE_EXECUTION)
73
73
 
@@ -235,7 +235,7 @@ def _hydrate_composio_tool_definition(tool_def: ComposioToolDefinition) -> Compo
235
235
 
236
236
  def create_tool_router_node(
237
237
  ml_model: str,
238
- blocks: List[PromptBlock],
238
+ blocks: List[Union[PromptBlock, Dict[str, Any]]],
239
239
  functions: List[Tool],
240
240
  prompt_inputs: Optional[EntityInputsInterface],
241
241
  parameters: PromptParameters,
@@ -287,7 +287,16 @@ def create_tool_router_node(
287
287
 
288
288
  # Add a chat history block to blocks only if one doesn't already exist
289
289
  has_chat_history_block = any(
290
- block.block_type == "VARIABLE" and block.input_variable == CHAT_HISTORY_VARIABLE for block in blocks
290
+ (
291
+ (block["block_type"] if isinstance(block, dict) else block.block_type) == "VARIABLE"
292
+ and (
293
+ block["input_variable"]
294
+ if isinstance(block, dict)
295
+ else block.input_variable if isinstance(block, VariablePromptBlock) else None
296
+ )
297
+ == CHAT_HISTORY_VARIABLE
298
+ )
299
+ for block in blocks
291
300
  )
292
301
 
293
302
  if not has_chat_history_block:
@@ -1,6 +1,6 @@
1
1
  import dataclasses
2
2
  import inspect
3
- from typing import TYPE_CHECKING, Any, Callable, Dict, Literal, Optional, Type, Union, get_args, get_origin
3
+ from typing import TYPE_CHECKING, Annotated, Any, Callable, Dict, Literal, Optional, Type, Union, get_args, get_origin
4
4
 
5
5
  from pydantic import BaseModel
6
6
  from pydantic_core import PydanticUndefined
@@ -53,6 +53,21 @@ def compile_annotation(annotation: Optional[Any], defs: dict[str, Any]) -> dict:
53
53
  item_type = get_args(annotation)[0]
54
54
  return {"type": "array", "items": compile_annotation(item_type, defs)}
55
55
 
56
+ if get_origin(annotation) is tuple:
57
+ args = get_args(annotation)
58
+ if len(args) == 2 and args[1] is Ellipsis:
59
+ # Tuple[int, ...] with homogeneous items
60
+ return {"type": "array", "items": compile_annotation(args[0], defs)}
61
+ else:
62
+ # Tuple[int, str] with fixed length items
63
+ result = {
64
+ "type": "array",
65
+ "prefixItems": [compile_annotation(arg, defs) for arg in args],
66
+ "minItems": len(args),
67
+ "maxItems": len(args),
68
+ }
69
+ return result
70
+
56
71
  if dataclasses.is_dataclass(annotation):
57
72
  if annotation.__name__ not in defs:
58
73
  properties = {}
@@ -127,7 +142,19 @@ def compile_function_definition(function: Callable) -> FunctionDefinition:
127
142
  required = []
128
143
  defs: dict[str, Any] = {}
129
144
  for param in signature.parameters.values():
130
- properties[param.name] = compile_annotation(param.annotation, defs)
145
+ # Check if parameter uses Annotated type hint
146
+ if get_origin(param.annotation) is Annotated:
147
+ args = get_args(param.annotation)
148
+ actual_type = args[0]
149
+ # Extract description from metadata
150
+ description = args[1] if len(args) > 1 and isinstance(args[1], str) else None
151
+
152
+ properties[param.name] = compile_annotation(actual_type, defs)
153
+ if description:
154
+ properties[param.name]["description"] = description
155
+ else:
156
+ properties[param.name] = compile_annotation(param.annotation, defs)
157
+
131
158
  if param.default is inspect.Parameter.empty:
132
159
  required.append(param.name)
133
160
  else:
@@ -2,7 +2,7 @@ import pytest
2
2
  from dataclasses import dataclass
3
3
  from enum import Enum
4
4
  from unittest.mock import Mock
5
- from typing import Dict, List, Literal, Optional, Union
5
+ from typing import Annotated, Dict, List, Literal, Optional, Tuple, Union
6
6
 
7
7
  from pydantic import BaseModel
8
8
 
@@ -612,3 +612,117 @@ def test_compile_function_definition__literal_type_not_in_map():
612
612
  compiled_function = compile_function_definition(my_function)
613
613
  assert isinstance(compiled_function.parameters, dict)
614
614
  assert compiled_function.parameters["properties"]["a"] == {"enum": [MyEnum.FOO, MyEnum.BAR]}
615
+
616
+
617
+ def test_compile_function_definition__annotated_descriptions():
618
+ # GIVEN a function with annotated parameters that include descriptions
619
+ def my_function(
620
+ bar: Annotated[str, "My bar parameter"],
621
+ other: Annotated[int, "My other parameter"],
622
+ regular_param: str,
623
+ optional_param: Annotated[bool, "Optional boolean parameter"] = True,
624
+ ):
625
+ """Test function with annotated parameters."""
626
+ pass
627
+
628
+ # WHEN compiling the function
629
+ compiled_function = compile_function_definition(my_function)
630
+
631
+ # THEN it should return the compiled function definition with descriptions
632
+ assert compiled_function == FunctionDefinition(
633
+ name="my_function",
634
+ description="Test function with annotated parameters.",
635
+ parameters={
636
+ "type": "object",
637
+ "properties": {
638
+ "bar": {"type": "string", "description": "My bar parameter"},
639
+ "other": {"type": "integer", "description": "My other parameter"},
640
+ "regular_param": {"type": "string"},
641
+ "optional_param": {"type": "boolean", "description": "Optional boolean parameter", "default": True},
642
+ },
643
+ "required": ["bar", "other", "regular_param"],
644
+ },
645
+ )
646
+
647
+
648
+ def test_compile_function_definition__annotated_without_description():
649
+ # GIVEN a function with annotated parameters but no description metadata
650
+ def my_function(param: Annotated[str, None]):
651
+ pass
652
+
653
+ # WHEN compiling the function
654
+ compiled_function = compile_function_definition(my_function)
655
+
656
+ # THEN it should return the compiled function definition without description
657
+ assert compiled_function == FunctionDefinition(
658
+ name="my_function",
659
+ parameters={
660
+ "type": "object",
661
+ "properties": {
662
+ "param": {"type": "string"},
663
+ },
664
+ "required": ["param"],
665
+ },
666
+ )
667
+
668
+
669
+ def test_compile_function_definition__annotated_complex_types():
670
+ # GIVEN a function with annotated types
671
+ def my_function(
672
+ location: Annotated[Literal["New York", "Portland"], "The location you found"],
673
+ items: Annotated[List[str], "List of string items"],
674
+ config: Annotated[Dict[str, int], "Configuration mapping"],
675
+ ):
676
+ pass
677
+
678
+ # WHEN compiling the function
679
+ compiled_function = compile_function_definition(my_function)
680
+
681
+ # THEN it should return the compiled function definition with descriptions for complex types
682
+ assert compiled_function == FunctionDefinition(
683
+ name="my_function",
684
+ parameters={
685
+ "type": "object",
686
+ "properties": {
687
+ "location": {
688
+ "type": "string",
689
+ "enum": ["New York", "Portland"],
690
+ "description": "The location you found",
691
+ },
692
+ "items": {"type": "array", "items": {"type": "string"}, "description": "List of string items"},
693
+ "config": {
694
+ "type": "object",
695
+ "additionalProperties": {"type": "integer"},
696
+ "description": "Configuration mapping",
697
+ },
698
+ },
699
+ "required": ["location", "items", "config"],
700
+ },
701
+ )
702
+
703
+
704
+ @pytest.mark.parametrize(
705
+ "annotation,expected_schema",
706
+ [
707
+ (
708
+ Tuple[int, ...],
709
+ {"type": "array", "items": {"type": "integer"}},
710
+ ),
711
+ (
712
+ Tuple[int, str],
713
+ {
714
+ "type": "array",
715
+ "prefixItems": [{"type": "integer"}, {"type": "string"}],
716
+ "minItems": 2,
717
+ "maxItems": 2,
718
+ },
719
+ ),
720
+ ],
721
+ )
722
+ def test_compile_function_definition__tuples(annotation, expected_schema):
723
+ def my_function(a: annotation): # type: ignore
724
+ pass
725
+
726
+ compiled_function = compile_function_definition(my_function)
727
+ assert isinstance(compiled_function.parameters, dict)
728
+ assert compiled_function.parameters["properties"]["a"] == expected_schema
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-ai
3
- Version: 1.0.6
3
+ Version: 1.0.7
4
4
  Summary:
5
5
  License: MIT
6
6
  Requires-Python: >=3.9,<4.0
@@ -96,7 +96,7 @@ vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_n
96
96
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py,sha256=hDWtKXmGI1CKhTwTNqpu_d5RkE5n7SolMLtgd87KqTI,3856
97
97
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_composio_serialization.py,sha256=gonapBCyDDt3qc7U02PCuKyPS8f3YiSAZ7QD86CH1Fw,3794
98
98
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_serialization.py,sha256=4t1lkN2nsZF6lFqP6QnskUQWJlhasF8C2_f6atzk8ZY,26298
99
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py,sha256=1hoakUkh5kHZYIfY1moJBZYzXgAafkgWsIf4lmZ12vg,9521
99
+ vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py,sha256=B0rDsCvO24qPp0gkmj8SdTDY5CxZYkvKwknsKBuAPyA,10017
100
100
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_workflow_deployment_serialization.py,sha256=mova0sPD3evHiHIN1O0VynxlCp-uOcEIKve5Pd_oCDg,4069
101
101
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py,sha256=pLCyMScV88DTBXRH7jXaXOEA1GBq8NIipCUFwIAWnwI,2771
102
102
  vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py,sha256=J4ouI8KxbMfxQP2Zq_9cWMGYgbjCWmKzjCJEtnSJb0I,5829
@@ -145,7 +145,7 @@ vellum/client/README.md,sha256=Dle5iytCXxP1pNeNd7uZyhFo0rl7tp7vU7s8gmi10OQ,4863
145
145
  vellum/client/__init__.py,sha256=KmkyOgReuTsjmXF3WC_dPQ9QqJgYrB3Sr8_LcSUIQyI,125258
146
146
  vellum/client/core/__init__.py,sha256=SQ85PF84B9MuKnBwHNHWemSGuy-g_515gFYNFhvEE0I,1438
147
147
  vellum/client/core/api_error.py,sha256=RE8LELok2QCjABadECTvtDp7qejA1VmINCh6TbqPwSE,426
148
- vellum/client/core/client_wrapper.py,sha256=QsKeKLYWV6cj1EDrHG6sVVmh4h2ol3aw7P3Lckn25Wk,2383
148
+ vellum/client/core/client_wrapper.py,sha256=4HIoFVgVHLgCMAKnGSlXru4ZVIHjc4LSqpYHg9GPlAI,2383
149
149
  vellum/client/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
150
150
  vellum/client/core/file.py,sha256=d4NNbX8XvXP32z8KpK2Xovv33nFfruIrpz0QWxlgpZk,2663
151
151
  vellum/client/core/http_client.py,sha256=cKs2w0ybDBk1wHQf-fTALm_MmvaMe3cZKcYJxqmCxkE,19539
@@ -1632,12 +1632,12 @@ vellum/workflows/nodes/displayable/__init__.py,sha256=zH7SFotr4i8sO-r5_k53yPipQw
1632
1632
  vellum/workflows/nodes/displayable/api_node/__init__.py,sha256=MoxdQSnidIj1Nf_d-hTxlOxcZXaZnsWFDbE-PkTK24o,56
1633
1633
  vellum/workflows/nodes/displayable/api_node/node.py,sha256=F7ucsuEmrVYlTKMIoi60fFJ_ELYgGuc7jEmJCEyQczw,2956
1634
1634
  vellum/workflows/nodes/displayable/api_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1635
- vellum/workflows/nodes/displayable/api_node/tests/test_api_node.py,sha256=qp6v-MrgpNVDt16Kp0EOyeQx3LpzflTlaNSL1EYZ1nE,9185
1635
+ vellum/workflows/nodes/displayable/api_node/tests/test_api_node.py,sha256=DZQGyq-iI9P9qvM5qtIUzb6fubyLnlJ3WbHwMUFsRs8,9527
1636
1636
  vellum/workflows/nodes/displayable/bases/__init__.py,sha256=0mWIx3qUrzllV7jqt7wN03vWGMuI1WrrLZeMLT2Cl2c,304
1637
1637
  vellum/workflows/nodes/displayable/bases/api_node/__init__.py,sha256=1jwx4WC358CLA1jgzl_UD-rZmdMm2v9Mps39ndwCD7U,64
1638
- vellum/workflows/nodes/displayable/bases/api_node/node.py,sha256=jkv803kEPlK4lrzk2oWU0J3t6vyFeQkI9ap9RCb1qt0,6183
1638
+ vellum/workflows/nodes/displayable/bases/api_node/node.py,sha256=iUtdPsbJs1jwo3V5bA6qGab56z3K44_VOpLR5MDXzBQ,6640
1639
1639
  vellum/workflows/nodes/displayable/bases/api_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1640
- vellum/workflows/nodes/displayable/bases/api_node/tests/test_node.py,sha256=Z39BZn-QgGNCKcwbOwhMpAopwgX6J3v7ulCA6TKyphE,1587
1640
+ vellum/workflows/nodes/displayable/bases/api_node/tests/test_node.py,sha256=Pf51DIyhtUxx-pCu0zJYDB4Z5_IW5mRwkJIoPT53_9I,3894
1641
1641
  vellum/workflows/nodes/displayable/bases/base_prompt_node/__init__.py,sha256=Org3xTvgp1pA0uUXFfnJr29D3HzCey2lEdYF4zbIUgo,70
1642
1642
  vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py,sha256=EJsGaz8Umss6-JWGGYbJp93ZHx3IlZQWAySlHAdNYtY,4466
1643
1643
  vellum/workflows/nodes/displayable/bases/inline_prompt_node/__init__.py,sha256=Hl35IAoepRpE-j4cALaXVJIYTYOF3qszyVbxTj4kS1s,82
@@ -1695,13 +1695,13 @@ vellum/workflows/nodes/displayable/tests/test_search_node_error_handling.py,sha2
1695
1695
  vellum/workflows/nodes/displayable/tests/test_search_node_wth_text_output.py,sha256=VepO5z1277c1y5N6LLIC31nnWD1aak2m5oPFplfJHHs,6935
1696
1696
  vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py,sha256=dc3EEn1sOICpr3GdS8eyeFtExaGwWWcw9eHSdkRhQJU,2584
1697
1697
  vellum/workflows/nodes/displayable/tool_calling_node/__init__.py,sha256=3n0-ysmFKsr40CVxPthc0rfJgqVJeZuUEsCmYudLVRg,117
1698
- vellum/workflows/nodes/displayable/tool_calling_node/node.py,sha256=BRA6YRCEOk0Nw3DCIT13WY7WCZ7Gx30s-egJe_md0FA,6504
1698
+ vellum/workflows/nodes/displayable/tool_calling_node/node.py,sha256=KRI1NMgXZTUgQqq9uOA9W_D8k8sy7ZAq6v53-YVno1k,6545
1699
1699
  vellum/workflows/nodes/displayable/tool_calling_node/state.py,sha256=oQg_GAtc349nPB5BL_oeDYYD7q1qSDPAqjj8iA8OoAw,215
1700
1700
  vellum/workflows/nodes/displayable/tool_calling_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1701
1701
  vellum/workflows/nodes/displayable/tool_calling_node/tests/test_composio_service.py,sha256=UV0vZpU7-_tHcwnIq36WKwHrJXNurU4bdC3rfaw8eoU,4804
1702
1702
  vellum/workflows/nodes/displayable/tool_calling_node/tests/test_node.py,sha256=raY_E5-EgtYNXEPbO2I-Ythe4YeuFdGsXGZ_BAN98uI,7979
1703
- vellum/workflows/nodes/displayable/tool_calling_node/tests/test_utils.py,sha256=aCK4TDcD4TcgoYbOs712qFiNCVRVffCb5HZCZQGDiUc,2449
1704
- vellum/workflows/nodes/displayable/tool_calling_node/utils.py,sha256=fOoAr_TTZ5Sjuc2GSyKz87vnfx-RZ6B7BxtQmPdxqfE,16191
1703
+ vellum/workflows/nodes/displayable/tool_calling_node/tests/test_utils.py,sha256=Ku_fUUoqQFeKLZ6o1DPCi7ax9PdkbaxkEEj6rAwjytM,7858
1704
+ vellum/workflows/nodes/displayable/tool_calling_node/utils.py,sha256=uRZGCJA3FBRt1ZPQWOF2R8D49aajrW4Yjxvn3kIcrAQ,16545
1705
1705
  vellum/workflows/nodes/experimental/README.md,sha256=eF6DfIL8t-HbF9-mcofOMymKrraiBHDLKTlnBa51ZiE,284
1706
1706
  vellum/workflows/nodes/experimental/__init__.py,sha256=jCQgvZEknXKfuNhGSOou4XPfrPqZ1_XBj5F0n0fgiWM,106
1707
1707
  vellum/workflows/nodes/experimental/openai_chat_completion_node/__init__.py,sha256=lsyD9laR9p7kx5-BXGH2gUTM242UhKy8SMV0SR6S2iE,90
@@ -1757,11 +1757,11 @@ vellum/workflows/types/tests/test_definition.py,sha256=c3GczPtWxuH3BOULwZacxYTQl
1757
1757
  vellum/workflows/types/tests/test_utils.py,sha256=UnZog59tR577mVwqZRqqWn2fScoOU1H6up0EzS8zYhw,2536
1758
1758
  vellum/workflows/types/utils.py,sha256=mTctHITBybpt4855x32oCKALBEcMNLn-9cCmfEKgJHQ,6498
1759
1759
  vellum/workflows/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1760
- vellum/workflows/utils/functions.py,sha256=4pHaaaOXwvYTLVThTkLYpESBkzNKuZdiyRBGRpcWxqY,7623
1760
+ vellum/workflows/utils/functions.py,sha256=ksvyxPWTbsldlXSlqh20e_1hl9GHipWwggYLfp3NRiE,8735
1761
1761
  vellum/workflows/utils/names.py,sha256=QLUqfJ1tmSEeUwBKTTiv_Qk3QGbInC2RSmlXfGXc8Wo,380
1762
1762
  vellum/workflows/utils/pydantic_schema.py,sha256=eR_bBtY-T0pttJP-ARwagSdCOnwPUtiT3cegm2lzDTQ,1310
1763
1763
  vellum/workflows/utils/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1764
- vellum/workflows/utils/tests/test_functions.py,sha256=xIk1r0XwbPcbQLHwN5NFUrztfKa3fGtUnfjvhOrYNNg,19449
1764
+ vellum/workflows/utils/tests/test_functions.py,sha256=uaxjsIdtTQdz3r0ACbBegDx-5LMAYyVsI83VgxUlE8o,23520
1765
1765
  vellum/workflows/utils/tests/test_names.py,sha256=aOqpyvMsOEK_9mg_-yaNxQDW7QQfwqsYs37PseyLhxw,402
1766
1766
  vellum/workflows/utils/tests/test_uuids.py,sha256=i77ABQ0M3S-aFLzDXHJq_yr5FPkJEWCMBn1HJ3DObrE,437
1767
1767
  vellum/workflows/utils/tests/test_vellum_variables.py,sha256=vbnKgm41aB5OXlO-ZIPbhQ6xDiZkT8KMxCLqz4zocWY,1791
@@ -1774,8 +1774,8 @@ vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnad
1774
1774
  vellum/workflows/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1775
1775
  vellum/workflows/workflows/tests/test_base_workflow.py,sha256=ptMntHzVyy8ZuzNgeTuk7hREgKQ5UBdgq8VJFSGaW4Y,20832
1776
1776
  vellum/workflows/workflows/tests/test_context.py,sha256=VJBUcyWVtMa_lE5KxdhgMu0WYNYnUQUDvTF7qm89hJ0,2333
1777
- vellum_ai-1.0.6.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1778
- vellum_ai-1.0.6.dist-info/METADATA,sha256=EmLX2EVjbQ5g5W-PpqXN-5TPUhFu6L5KXLRLSB0st_s,5554
1779
- vellum_ai-1.0.6.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1780
- vellum_ai-1.0.6.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1781
- vellum_ai-1.0.6.dist-info/RECORD,,
1777
+ vellum_ai-1.0.7.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1778
+ vellum_ai-1.0.7.dist-info/METADATA,sha256=-x_ul0guh3O6FHWJQn5INiN0RWn4W4IBRFk_JZgcUGQ,5554
1779
+ vellum_ai-1.0.7.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1780
+ vellum_ai-1.0.7.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1781
+ vellum_ai-1.0.7.dist-info/RECORD,,
@@ -140,13 +140,19 @@ def test_serialize_workflow():
140
140
  "description": "\n Get the current weather in a given location.\n ",
141
141
  "parameters": {
142
142
  "type": "object",
143
- "properties": {"location": {"type": "string"}, "unit": {"type": "string"}},
143
+ "properties": {
144
+ "location": {
145
+ "type": "string",
146
+ "description": "The location to get the weather for",
147
+ },
148
+ "unit": {"type": "string", "description": "The unit of temperature"},
149
+ },
144
150
  "required": ["location", "unit"],
145
151
  },
146
152
  "forced": None,
147
153
  "strict": None,
148
154
  },
149
- "src": 'import math\n\n\ndef get_current_weather(location: str, unit: str) -> str:\n """\n Get the current weather in a given location.\n """\n return f"The current weather in {location} is sunny with a temperature of {get_temperature(70.1)} degrees {unit}."\n\n\ndef get_temperature(temperature: float) -> int:\n """\n Get the temperature in a given location.\n """\n return math.floor(temperature)\n', # noqa: E501
155
+ "src": 'import math\nfrom typing import Annotated\n\n\ndef get_current_weather(\n location: Annotated[str, "The location to get the weather for"], unit: Annotated[str, "The unit of temperature"]\n) -> str:\n """\n Get the current weather in a given location.\n """\n return f"The current weather in {location} is sunny with a temperature of {get_temperature(70.1)} degrees {unit}."\n\n\ndef get_temperature(temperature: float) -> int:\n """\n Get the temperature in a given location.\n """\n return math.floor(temperature)\n', # noqa: E501
150
156
  }
151
157
  ],
152
158
  },