vellum-ai 1.0.6__py3-none-any.whl → 1.0.8__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.
- vellum/client/core/client_wrapper.py +2 -2
- vellum/workflows/nodes/displayable/api_node/tests/test_api_node.py +8 -2
- vellum/workflows/nodes/displayable/bases/api_node/node.py +9 -0
- vellum/workflows/nodes/displayable/bases/api_node/tests/test_node.py +77 -0
- vellum/workflows/nodes/displayable/tool_calling_node/node.py +2 -2
- vellum/workflows/nodes/displayable/tool_calling_node/tests/test_utils.py +161 -3
- vellum/workflows/nodes/displayable/tool_calling_node/utils.py +18 -5
- vellum/workflows/types/core.py +2 -2
- vellum/workflows/types/definition.py +21 -0
- vellum/workflows/types/tests/test_definition.py +79 -1
- vellum/workflows/utils/functions.py +29 -2
- vellum/workflows/utils/tests/test_functions.py +115 -1
- {vellum_ai-1.0.6.dist-info → vellum_ai-1.0.8.dist-info}/METADATA +1 -1
- {vellum_ai-1.0.6.dist-info → vellum_ai-1.0.8.dist-info}/RECORD +19 -19
- vellum_cli/push.py +3 -2
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py +8 -2
- {vellum_ai-1.0.6.dist-info → vellum_ai-1.0.8.dist-info}/LICENSE +0 -0
- {vellum_ai-1.0.6.dist-info → vellum_ai-1.0.8.dist-info}/WHEEL +0 -0
- {vellum_ai-1.0.6.dist-info → vellum_ai-1.0.8.dist-info}/entry_points.txt +0 -0
@@ -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.
|
28
|
+
"User-Agent": "vellum-ai/1.0.8",
|
29
29
|
"X-Fern-Language": "Python",
|
30
30
|
"X-Fern-SDK-Name": "vellum-ai",
|
31
|
-
"X-Fern-SDK-Version": "1.0.
|
31
|
+
"X-Fern-SDK-Version": "1.0.8",
|
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
|
-
|
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
|
-
|
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,12 +1,21 @@
|
|
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
|
-
from vellum.workflows.types.definition import ComposioToolDefinition, DeploymentDefinition
|
18
|
+
from vellum.workflows.types.definition import ComposioToolDefinition, DeploymentDefinition, MCPServer, MCPToolDefinition
|
10
19
|
|
11
20
|
|
12
21
|
def test_get_function_name_callable():
|
@@ -60,6 +69,19 @@ def test_get_function_name_subworkflow_deployment_uuid():
|
|
60
69
|
assert result == "57f09bebb46340e0bf9ec972e664352f"
|
61
70
|
|
62
71
|
|
72
|
+
def test_get_function_name_mcp_tool_definition():
|
73
|
+
"""Test MCPToolDefinition function name generation."""
|
74
|
+
mcp_tool = MCPToolDefinition(
|
75
|
+
name="create_repository",
|
76
|
+
server=MCPServer(name="github", url="https://api.github.com"),
|
77
|
+
parameters={"repository_name": "string", "description": "string"},
|
78
|
+
)
|
79
|
+
|
80
|
+
result = get_function_name(mcp_tool)
|
81
|
+
|
82
|
+
assert result == "create_repository"
|
83
|
+
|
84
|
+
|
63
85
|
@pytest.mark.parametrize(
|
64
86
|
"toolkit,action,description,expected_result",
|
65
87
|
[
|
@@ -76,3 +98,139 @@ def test_get_function_name_composio_tool_definition_various_toolkits(
|
|
76
98
|
result = get_function_name(composio_tool)
|
77
99
|
|
78
100
|
assert result == expected_result
|
101
|
+
|
102
|
+
|
103
|
+
def test_create_tool_router_node_max_prompt_iterations(vellum_adhoc_prompt_client):
|
104
|
+
# GIVEN a tool router node with max_prompt_iterations set to None
|
105
|
+
tool_router_node = create_tool_router_node(
|
106
|
+
ml_model="gpt-4o-mini",
|
107
|
+
blocks=[],
|
108
|
+
functions=[],
|
109
|
+
prompt_inputs=None,
|
110
|
+
parameters=DEFAULT_PROMPT_PARAMETERS,
|
111
|
+
max_prompt_iterations=None,
|
112
|
+
)
|
113
|
+
|
114
|
+
def generate_prompt_events(*args, **kwargs):
|
115
|
+
execution_id = str(uuid4())
|
116
|
+
events = [
|
117
|
+
InitiatedExecutePromptEvent(execution_id=execution_id),
|
118
|
+
FulfilledExecutePromptEvent(
|
119
|
+
execution_id=execution_id,
|
120
|
+
outputs=[StringVellumValue(value="test output")],
|
121
|
+
),
|
122
|
+
]
|
123
|
+
yield from events
|
124
|
+
|
125
|
+
vellum_adhoc_prompt_client.adhoc_execute_prompt_stream.side_effect = generate_prompt_events
|
126
|
+
|
127
|
+
# WHEN we run the tool router node
|
128
|
+
node_instance = tool_router_node()
|
129
|
+
outputs = list(node_instance.run())
|
130
|
+
assert outputs[0].name == "results"
|
131
|
+
assert outputs[0].value == [StringVellumValue(type="STRING", value="test output")]
|
132
|
+
assert outputs[1].name == "text"
|
133
|
+
assert outputs[1].value == "test output"
|
134
|
+
|
135
|
+
|
136
|
+
def test_create_tool_router_node_chat_history_block_dict(vellum_adhoc_prompt_client):
|
137
|
+
# GIVEN a list of blocks with a chat history block
|
138
|
+
blocks = [
|
139
|
+
{
|
140
|
+
"block_type": "CHAT_MESSAGE",
|
141
|
+
"chat_role": "SYSTEM",
|
142
|
+
"blocks": [
|
143
|
+
{
|
144
|
+
"block_type": "RICH_TEXT",
|
145
|
+
"blocks": [{"block_type": "PLAIN_TEXT", "cache_config": None, "text": "first message"}],
|
146
|
+
}
|
147
|
+
],
|
148
|
+
},
|
149
|
+
{
|
150
|
+
"block_type": "CHAT_MESSAGE",
|
151
|
+
"chat_role": "USER",
|
152
|
+
"blocks": [
|
153
|
+
{
|
154
|
+
"block_type": "RICH_TEXT",
|
155
|
+
"blocks": [
|
156
|
+
{"block_type": "PLAIN_TEXT", "text": "second message"},
|
157
|
+
{"block_type": "PLAIN_TEXT", "text": "third message"},
|
158
|
+
],
|
159
|
+
}
|
160
|
+
],
|
161
|
+
},
|
162
|
+
]
|
163
|
+
|
164
|
+
tool_router_node = create_tool_router_node(
|
165
|
+
ml_model="gpt-4o-mini",
|
166
|
+
blocks=blocks, # type: ignore
|
167
|
+
functions=[],
|
168
|
+
prompt_inputs=None,
|
169
|
+
parameters=DEFAULT_PROMPT_PARAMETERS,
|
170
|
+
)
|
171
|
+
|
172
|
+
def generate_prompt_events(*args, **kwargs):
|
173
|
+
execution_id = str(uuid4())
|
174
|
+
events = [
|
175
|
+
InitiatedExecutePromptEvent(execution_id=execution_id),
|
176
|
+
FulfilledExecutePromptEvent(
|
177
|
+
execution_id=execution_id,
|
178
|
+
outputs=[StringVellumValue(value="test output")],
|
179
|
+
),
|
180
|
+
]
|
181
|
+
yield from events
|
182
|
+
|
183
|
+
vellum_adhoc_prompt_client.adhoc_execute_prompt_stream.side_effect = generate_prompt_events
|
184
|
+
|
185
|
+
# WHEN we run the tool router node
|
186
|
+
node_instance = tool_router_node()
|
187
|
+
list(node_instance.run())
|
188
|
+
|
189
|
+
# THEN the API was called with compiled blocks
|
190
|
+
blocks = vellum_adhoc_prompt_client.adhoc_execute_prompt_stream.call_args[1]["blocks"]
|
191
|
+
assert blocks == [
|
192
|
+
ChatMessagePromptBlock(
|
193
|
+
block_type="CHAT_MESSAGE",
|
194
|
+
state=None,
|
195
|
+
cache_config=None,
|
196
|
+
chat_role="SYSTEM",
|
197
|
+
chat_source=None,
|
198
|
+
chat_message_unterminated=None,
|
199
|
+
blocks=[
|
200
|
+
RichTextPromptBlock(
|
201
|
+
block_type="RICH_TEXT",
|
202
|
+
state=None,
|
203
|
+
cache_config=None,
|
204
|
+
blocks=[
|
205
|
+
PlainTextPromptBlock(
|
206
|
+
block_type="PLAIN_TEXT", state=None, cache_config=None, text="first message"
|
207
|
+
)
|
208
|
+
],
|
209
|
+
)
|
210
|
+
],
|
211
|
+
),
|
212
|
+
ChatMessagePromptBlock(
|
213
|
+
block_type="CHAT_MESSAGE",
|
214
|
+
state=None,
|
215
|
+
cache_config=None,
|
216
|
+
chat_role="USER",
|
217
|
+
chat_source=None,
|
218
|
+
chat_message_unterminated=None,
|
219
|
+
blocks=[
|
220
|
+
RichTextPromptBlock(
|
221
|
+
block_type="RICH_TEXT",
|
222
|
+
state=None,
|
223
|
+
cache_config=None,
|
224
|
+
blocks=[
|
225
|
+
PlainTextPromptBlock(
|
226
|
+
block_type="PLAIN_TEXT", state=None, cache_config=None, text="second message"
|
227
|
+
),
|
228
|
+
PlainTextPromptBlock(
|
229
|
+
block_type="PLAIN_TEXT", state=None, cache_config=None, text="third message"
|
230
|
+
),
|
231
|
+
],
|
232
|
+
)
|
233
|
+
],
|
234
|
+
),
|
235
|
+
VariablePromptBlock(block_type="VARIABLE", state=None, cache_config=None, input_variable="chat_history"),
|
236
|
+
]
|
@@ -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
|
|
@@ -28,7 +28,7 @@ from vellum.workflows.references.lazy import LazyReference
|
|
28
28
|
from vellum.workflows.state import BaseState
|
29
29
|
from vellum.workflows.state.encoder import DefaultStateEncoder
|
30
30
|
from vellum.workflows.types.core import EntityInputsInterface, MergeBehavior, Tool
|
31
|
-
from vellum.workflows.types.definition import ComposioToolDefinition, DeploymentDefinition
|
31
|
+
from vellum.workflows.types.definition import ComposioToolDefinition, DeploymentDefinition, MCPToolDefinition
|
32
32
|
from vellum.workflows.types.generics import is_workflow_class
|
33
33
|
|
34
34
|
CHAT_HISTORY_VARIABLE = "chat_history"
|
@@ -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
|
-
|
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:
|
@@ -370,6 +379,8 @@ def create_function_node(
|
|
370
379
|
},
|
371
380
|
)
|
372
381
|
return node
|
382
|
+
elif isinstance(function, MCPToolDefinition):
|
383
|
+
pass
|
373
384
|
elif is_workflow_class(function):
|
374
385
|
node = type(
|
375
386
|
f"DynamicInlineSubworkflowNode_{function.__name__}",
|
@@ -401,5 +412,7 @@ def get_function_name(function: Tool) -> str:
|
|
401
412
|
return name.replace("-", "")
|
402
413
|
elif isinstance(function, ComposioToolDefinition):
|
403
414
|
return function.name
|
415
|
+
elif isinstance(function, MCPToolDefinition):
|
416
|
+
return function.name
|
404
417
|
else:
|
405
418
|
return snake_case(function.__name__)
|
vellum/workflows/types/core.py
CHANGED
@@ -13,7 +13,7 @@ from typing import ( # type: ignore[attr-defined]
|
|
13
13
|
)
|
14
14
|
|
15
15
|
from vellum.client.core.pydantic_utilities import UniversalBaseModel
|
16
|
-
from vellum.workflows.types.definition import ComposioToolDefinition, DeploymentDefinition
|
16
|
+
from vellum.workflows.types.definition import ComposioToolDefinition, DeploymentDefinition, MCPToolDefinition
|
17
17
|
|
18
18
|
if TYPE_CHECKING:
|
19
19
|
from vellum.workflows.workflows.base import BaseWorkflow
|
@@ -50,4 +50,4 @@ class ConditionType(Enum):
|
|
50
50
|
|
51
51
|
|
52
52
|
# Type alias for functions that can be called in tool calling nodes
|
53
|
-
Tool = Union[Callable[..., Any], DeploymentDefinition, Type["BaseWorkflow"], ComposioToolDefinition]
|
53
|
+
Tool = Union[Callable[..., Any], DeploymentDefinition, Type["BaseWorkflow"], ComposioToolDefinition, MCPToolDefinition]
|
@@ -8,6 +8,9 @@ from pydantic import BeforeValidator
|
|
8
8
|
|
9
9
|
from vellum.client.core.pydantic_utilities import UniversalBaseModel
|
10
10
|
from vellum.client.types.code_resource_definition import CodeResourceDefinition as ClientCodeResourceDefinition
|
11
|
+
from vellum.client.types.vellum_secret import VellumSecret
|
12
|
+
from vellum.workflows.constants import AuthorizationType
|
13
|
+
from vellum.workflows.references.environment_variable import EnvironmentVariableReference
|
11
14
|
|
12
15
|
|
13
16
|
def serialize_type_encoder(obj: type) -> Dict[str, Any]:
|
@@ -118,3 +121,21 @@ class ComposioToolDefinition(UniversalBaseModel):
|
|
118
121
|
def name(self) -> str:
|
119
122
|
"""Generate a function name for this tool"""
|
120
123
|
return self.action.lower()
|
124
|
+
|
125
|
+
|
126
|
+
class MCPServer(UniversalBaseModel):
|
127
|
+
name: str
|
128
|
+
url: str
|
129
|
+
authorization_type: AuthorizationType = AuthorizationType.BEARER_TOKEN
|
130
|
+
bearer_token_value: Optional[Union[str, EnvironmentVariableReference]] = None
|
131
|
+
api_key_header_key: Optional[str] = None
|
132
|
+
api_key_header_value: Optional[Union[str, VellumSecret]] = None
|
133
|
+
|
134
|
+
model_config = {"arbitrary_types_allowed": True}
|
135
|
+
|
136
|
+
|
137
|
+
class MCPToolDefinition(UniversalBaseModel):
|
138
|
+
name: str
|
139
|
+
server: MCPServer
|
140
|
+
description: Optional[str] = None
|
141
|
+
parameters: Dict[str, Any] = {}
|
@@ -1,7 +1,10 @@
|
|
1
1
|
import pytest
|
2
2
|
from uuid import UUID
|
3
3
|
|
4
|
-
from vellum.
|
4
|
+
from vellum.client.types.vellum_secret import VellumSecret
|
5
|
+
from vellum.workflows.constants import AuthorizationType
|
6
|
+
from vellum.workflows.references.environment_variable import EnvironmentVariableReference
|
7
|
+
from vellum.workflows.types.definition import ComposioToolDefinition, DeploymentDefinition, MCPServer, MCPToolDefinition
|
5
8
|
|
6
9
|
|
7
10
|
@pytest.mark.parametrize(
|
@@ -44,3 +47,78 @@ def test_composio_tool_definition_creation():
|
|
44
47
|
assert composio_tool.description == "Create a new issue in a GitHub repository"
|
45
48
|
assert composio_tool.display_name is None
|
46
49
|
assert composio_tool.name == "github_create_an_issue"
|
50
|
+
|
51
|
+
|
52
|
+
def test_mcp_tool_definition_creation_bearer_token():
|
53
|
+
"""Test that MCPToolDefinition can be created with required fields."""
|
54
|
+
mcp_tool = MCPToolDefinition(
|
55
|
+
name="create_repository",
|
56
|
+
server=MCPServer(
|
57
|
+
name="github",
|
58
|
+
url="https://api.githubcopilot.com/mcp/",
|
59
|
+
authorization_type=AuthorizationType.BEARER_TOKEN,
|
60
|
+
bearer_token_value=EnvironmentVariableReference(name="GITHUB_PERSONAL_ACCESS_TOKEN"),
|
61
|
+
),
|
62
|
+
parameters={
|
63
|
+
"type": "object",
|
64
|
+
"properties": {
|
65
|
+
"repository_name": {"type": "string", "description": "Repository name"},
|
66
|
+
"description": {"type": "string", "description": "Repository description"},
|
67
|
+
},
|
68
|
+
"required": ["repository_name"],
|
69
|
+
},
|
70
|
+
)
|
71
|
+
|
72
|
+
assert mcp_tool.name == "create_repository"
|
73
|
+
assert mcp_tool.server.name == "github"
|
74
|
+
assert mcp_tool.server.url == "https://api.githubcopilot.com/mcp/"
|
75
|
+
assert mcp_tool.server.authorization_type == AuthorizationType.BEARER_TOKEN
|
76
|
+
assert mcp_tool.parameters == {
|
77
|
+
"type": "object",
|
78
|
+
"properties": {
|
79
|
+
"repository_name": {"type": "string", "description": "Repository name"},
|
80
|
+
"description": {"type": "string", "description": "Repository description"},
|
81
|
+
},
|
82
|
+
"required": ["repository_name"],
|
83
|
+
}
|
84
|
+
|
85
|
+
assert isinstance(mcp_tool.server.bearer_token_value, EnvironmentVariableReference)
|
86
|
+
assert mcp_tool.server.bearer_token_value.name == "GITHUB_PERSONAL_ACCESS_TOKEN"
|
87
|
+
|
88
|
+
|
89
|
+
def test_mcp_tool_definition_creation_api_key():
|
90
|
+
"""Test that MCPToolDefinition can be created with required fields."""
|
91
|
+
mcp_tool = MCPToolDefinition(
|
92
|
+
name="create_repository",
|
93
|
+
server=MCPServer(
|
94
|
+
name="github",
|
95
|
+
url="https://api.githubcopilot.com/mcp/",
|
96
|
+
authorization_type=AuthorizationType.API_KEY,
|
97
|
+
api_key_header_key="Authorization",
|
98
|
+
api_key_header_value=VellumSecret(name="GITHUB_PERSONAL_ACCESS_TOKEN"),
|
99
|
+
),
|
100
|
+
parameters={
|
101
|
+
"type": "object",
|
102
|
+
"properties": {
|
103
|
+
"repository_name": {"type": "string", "description": "Repository name"},
|
104
|
+
"description": {"type": "string", "description": "Repository description"},
|
105
|
+
},
|
106
|
+
"required": ["repository_name"],
|
107
|
+
},
|
108
|
+
)
|
109
|
+
|
110
|
+
assert mcp_tool.name == "create_repository"
|
111
|
+
assert mcp_tool.server.name == "github"
|
112
|
+
assert mcp_tool.server.url == "https://api.githubcopilot.com/mcp/"
|
113
|
+
assert mcp_tool.server.authorization_type == AuthorizationType.API_KEY
|
114
|
+
assert mcp_tool.server.api_key_header_key == "Authorization"
|
115
|
+
assert isinstance(mcp_tool.server.api_key_header_value, VellumSecret)
|
116
|
+
assert mcp_tool.server.api_key_header_value.name == "GITHUB_PERSONAL_ACCESS_TOKEN"
|
117
|
+
assert mcp_tool.parameters == {
|
118
|
+
"type": "object",
|
119
|
+
"properties": {
|
120
|
+
"repository_name": {"type": "string", "description": "Repository name"},
|
121
|
+
"description": {"type": "string", "description": "Repository description"},
|
122
|
+
},
|
123
|
+
"required": ["repository_name"],
|
124
|
+
}
|
@@ -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
|
-
|
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
|
@@ -8,7 +8,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=
|
11
|
+
vellum_cli/push.py,sha256=hzBBD7Rc-11Dyu6_JfHeLI03c4XKAYQZZoq1SCNyXpM,11547
|
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
|
@@ -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=
|
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
|
148
|
+
vellum/client/core/client_wrapper.py,sha256=-AFplaIvuzgdfFXfodBM9oR_i0p48jofC_DkscIsYRE,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=
|
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=
|
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=
|
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=
|
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=
|
1704
|
-
vellum/workflows/nodes/displayable/tool_calling_node/utils.py,sha256=
|
1703
|
+
vellum/workflows/nodes/displayable/tool_calling_node/tests/test_utils.py,sha256=prl8GRwSBOgIorjBJkaYrp6XJjXNuaoedg3Lxt269j0,8303
|
1704
|
+
vellum/workflows/nodes/displayable/tool_calling_node/utils.py,sha256=rj9UTNDOi3sx_Rbc4DfBOtBpMTHut9piwlALN9n0zE0,16706
|
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
|
@@ -1748,20 +1748,20 @@ vellum/workflows/tests/test_sandbox.py,sha256=JKwaluI-lODQo7Ek9sjDstjL_WTdSqUlVi
|
|
1748
1748
|
vellum/workflows/tests/test_undefined.py,sha256=zMCVliCXVNLrlC6hEGyOWDnQADJ2g83yc5FIM33zuo8,353
|
1749
1749
|
vellum/workflows/types/__init__.py,sha256=KxUTMBGzuRCfiMqzzsykOeVvrrkaZmTTo1a7SLu8gRM,68
|
1750
1750
|
vellum/workflows/types/code_execution_node_wrappers.py,sha256=3MNIoFZKzVzNS5qFLVuDwMV17QJw72zo7NRf52yMq5A,3074
|
1751
|
-
vellum/workflows/types/core.py,sha256=
|
1752
|
-
vellum/workflows/types/definition.py,sha256=
|
1751
|
+
vellum/workflows/types/core.py,sha256=Vykj9o6fEnS13M1LwJDh9FVgua03acqigBqyYOiJiq8,1352
|
1752
|
+
vellum/workflows/types/definition.py,sha256=pK0fAXHw7C0AFpCoM4WGe1_MD-usupF4-m6ldo5AQXY,4568
|
1753
1753
|
vellum/workflows/types/generics.py,sha256=8jptbEx1fnJV0Lhj0MpCJOT6yNiEWeTOYOwrEAb5CRU,1576
|
1754
1754
|
vellum/workflows/types/stack.py,sha256=h7NE0vXR7l9DevFBIzIAk1Zh59K-kECQtDTKOUunwMY,1314
|
1755
1755
|
vellum/workflows/types/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1756
|
-
vellum/workflows/types/tests/test_definition.py,sha256=
|
1756
|
+
vellum/workflows/types/tests/test_definition.py,sha256=RsDoicu8A1dqJOGa-Ok866K8lnzn5L0Hez3lQijYD4c,5011
|
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=
|
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=
|
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.
|
1778
|
-
vellum_ai-1.0.
|
1779
|
-
vellum_ai-1.0.
|
1780
|
-
vellum_ai-1.0.
|
1781
|
-
vellum_ai-1.0.
|
1777
|
+
vellum_ai-1.0.8.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
|
1778
|
+
vellum_ai-1.0.8.dist-info/METADATA,sha256=qMK12xNOQpaw7B4-wciOlrjn7mmS4zzPS9ALMXFIb_c,5554
|
1779
|
+
vellum_ai-1.0.8.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
1780
|
+
vellum_ai-1.0.8.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
|
1781
|
+
vellum_ai-1.0.8.dist-info/RECORD,,
|
vellum_cli/push.py
CHANGED
@@ -95,8 +95,9 @@ def push_command(
|
|
95
95
|
and workflow_config.workflow_sandbox_id
|
96
96
|
and workflow_config.workflow_sandbox_id != workflow_sandbox_id
|
97
97
|
):
|
98
|
-
|
99
|
-
f"Workflow sandbox id '{workflow_sandbox_id}' is already associated with '{workflow_config.module}'."
|
98
|
+
logger.warning(
|
99
|
+
f"Workflow sandbox id '{workflow_sandbox_id}' is already associated with '{workflow_config.module}'. "
|
100
|
+
f"Continuing with the provided workflow sandbox id '{workflow_sandbox_id}'."
|
100
101
|
)
|
101
102
|
|
102
103
|
client = create_vellum_client(
|
@@ -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": {
|
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
|
},
|
File without changes
|
File without changes
|
File without changes
|