vellum-ai 1.0.4__py3-none-any.whl → 1.0.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vellum/__init__.py +2 -0
- vellum/client/core/client_wrapper.py +2 -2
- vellum/client/types/__init__.py +2 -0
- vellum/client/types/name_enum.py +7 -0
- vellum/client/types/quota.py +2 -1
- vellum/types/name_enum.py +3 -0
- vellum/workflows/nodes/displayable/tool_calling_node/tests/test_utils.py +21 -1
- vellum/workflows/nodes/displayable/tool_calling_node/utils.py +112 -58
- vellum/workflows/utils/functions.py +13 -1
- vellum/workflows/utils/tests/test_functions.py +32 -1
- {vellum_ai-1.0.4.dist-info → vellum_ai-1.0.5.dist-info}/METADATA +1 -1
- {vellum_ai-1.0.4.dist-info → vellum_ai-1.0.5.dist-info}/RECORD +16 -13
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_composio_serialization.py +86 -0
- {vellum_ai-1.0.4.dist-info → vellum_ai-1.0.5.dist-info}/LICENSE +0 -0
- {vellum_ai-1.0.4.dist-info → vellum_ai-1.0.5.dist-info}/WHEEL +0 -0
- {vellum_ai-1.0.4.dist-info → vellum_ai-1.0.5.dist-info}/entry_points.txt +0 -0
vellum/__init__.py
CHANGED
@@ -217,6 +217,7 @@ from .client.types import (
|
|
217
217
|
MlModelRead,
|
218
218
|
MlModelUsage,
|
219
219
|
MlModelUsageWrapper,
|
220
|
+
NameEnum,
|
220
221
|
NamedScenarioInputChatHistoryVariableValueRequest,
|
221
222
|
NamedScenarioInputJsonVariableValueRequest,
|
222
223
|
NamedScenarioInputRequest,
|
@@ -867,6 +868,7 @@ __all__ = [
|
|
867
868
|
"MlModelRead",
|
868
869
|
"MlModelUsage",
|
869
870
|
"MlModelUsageWrapper",
|
871
|
+
"NameEnum",
|
870
872
|
"NamedScenarioInputChatHistoryVariableValueRequest",
|
871
873
|
"NamedScenarioInputJsonVariableValueRequest",
|
872
874
|
"NamedScenarioInputRequest",
|
@@ -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.5",
|
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.5",
|
32
32
|
}
|
33
33
|
if self._api_version is not None:
|
34
34
|
headers["X-API-Version"] = self._api_version
|
vellum/client/types/__init__.py
CHANGED
@@ -225,6 +225,7 @@ from .metric_node_result import MetricNodeResult
|
|
225
225
|
from .ml_model_read import MlModelRead
|
226
226
|
from .ml_model_usage import MlModelUsage
|
227
227
|
from .ml_model_usage_wrapper import MlModelUsageWrapper
|
228
|
+
from .name_enum import NameEnum
|
228
229
|
from .named_scenario_input_chat_history_variable_value_request import NamedScenarioInputChatHistoryVariableValueRequest
|
229
230
|
from .named_scenario_input_json_variable_value_request import NamedScenarioInputJsonVariableValueRequest
|
230
231
|
from .named_scenario_input_request import NamedScenarioInputRequest
|
@@ -847,6 +848,7 @@ __all__ = [
|
|
847
848
|
"MlModelRead",
|
848
849
|
"MlModelUsage",
|
849
850
|
"MlModelUsageWrapper",
|
851
|
+
"NameEnum",
|
850
852
|
"NamedScenarioInputChatHistoryVariableValueRequest",
|
851
853
|
"NamedScenarioInputJsonVariableValueRequest",
|
852
854
|
"NamedScenarioInputRequest",
|
vellum/client/types/quota.py
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
# This file was auto-generated by Fern from our API Definition.
|
2
2
|
|
3
3
|
from ..core.pydantic_utilities import UniversalBaseModel
|
4
|
+
from .name_enum import NameEnum
|
4
5
|
import typing
|
5
6
|
from ..core.pydantic_utilities import IS_PYDANTIC_V2
|
6
7
|
import pydantic
|
7
8
|
|
8
9
|
|
9
10
|
class Quota(UniversalBaseModel):
|
10
|
-
name:
|
11
|
+
name: NameEnum
|
11
12
|
value: typing.Optional[int] = None
|
12
13
|
period_seconds: typing.Optional[int] = None
|
13
14
|
|
@@ -1,10 +1,12 @@
|
|
1
|
+
import pytest
|
2
|
+
|
1
3
|
from vellum.workflows import BaseWorkflow
|
2
4
|
from vellum.workflows.inputs.base import BaseInputs
|
3
5
|
from vellum.workflows.nodes.bases import BaseNode
|
4
6
|
from vellum.workflows.nodes.displayable.tool_calling_node.utils import get_function_name
|
5
7
|
from vellum.workflows.outputs.base import BaseOutputs
|
6
8
|
from vellum.workflows.state.base import BaseState
|
7
|
-
from vellum.workflows.types.definition import DeploymentDefinition
|
9
|
+
from vellum.workflows.types.definition import ComposioToolDefinition, DeploymentDefinition
|
8
10
|
|
9
11
|
|
10
12
|
def test_get_function_name_callable():
|
@@ -56,3 +58,21 @@ def test_get_function_name_subworkflow_deployment_uuid():
|
|
56
58
|
result = get_function_name(deployment_config)
|
57
59
|
|
58
60
|
assert result == "57f09bebb46340e0bf9ec972e664352f"
|
61
|
+
|
62
|
+
|
63
|
+
@pytest.mark.parametrize(
|
64
|
+
"toolkit,action,description,expected_result",
|
65
|
+
[
|
66
|
+
("SLACK", "SLACK_SEND_MESSAGE", "Send message to Slack", "slack_send_message"),
|
67
|
+
("GMAIL", "GMAIL_CREATE_EMAIL_DRAFT", "Create Gmail draft", "gmail_create_email_draft"),
|
68
|
+
],
|
69
|
+
)
|
70
|
+
def test_get_function_name_composio_tool_definition_various_toolkits(
|
71
|
+
toolkit: str, action: str, description: str, expected_result: str
|
72
|
+
):
|
73
|
+
"""Test ComposioToolDefinition function name generation with various toolkits."""
|
74
|
+
composio_tool = ComposioToolDefinition(toolkit=toolkit, action=action, description=description)
|
75
|
+
|
76
|
+
result = get_function_name(composio_tool)
|
77
|
+
|
78
|
+
assert result == expected_result
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import json
|
2
|
+
import os
|
2
3
|
from typing import Any, Callable, Iterator, List, Optional, Type, cast
|
3
4
|
|
4
5
|
from pydash import snake_case
|
@@ -18,6 +19,7 @@ from vellum.workflows.nodes.bases import BaseNode
|
|
18
19
|
from vellum.workflows.nodes.core.inline_subworkflow_node.node import InlineSubworkflowNode
|
19
20
|
from vellum.workflows.nodes.displayable.inline_prompt_node.node import InlinePromptNode
|
20
21
|
from vellum.workflows.nodes.displayable.subworkflow_deployment_node.node import SubworkflowDeploymentNode
|
22
|
+
from vellum.workflows.nodes.displayable.tool_calling_node.composio_service import ComposioService
|
21
23
|
from vellum.workflows.nodes.displayable.tool_calling_node.state import ToolCallingState
|
22
24
|
from vellum.workflows.outputs.base import BaseOutput
|
23
25
|
from vellum.workflows.ports.port import Port
|
@@ -31,6 +33,29 @@ from vellum.workflows.types.generics import is_workflow_class
|
|
31
33
|
CHAT_HISTORY_VARIABLE = "chat_history"
|
32
34
|
|
33
35
|
|
36
|
+
class FunctionCallNodeMixin:
|
37
|
+
"""Mixin providing common functionality for nodes that handle function calls."""
|
38
|
+
|
39
|
+
function_call_output: List[PromptOutput]
|
40
|
+
|
41
|
+
def _extract_function_arguments(self) -> dict:
|
42
|
+
"""Extract arguments from function call output."""
|
43
|
+
if self.function_call_output and len(self.function_call_output) > 0:
|
44
|
+
function_call = self.function_call_output[0]
|
45
|
+
if function_call.type == "FUNCTION_CALL" and function_call.value is not None:
|
46
|
+
return function_call.value.arguments or {}
|
47
|
+
return {}
|
48
|
+
|
49
|
+
def _add_function_result_to_chat_history(self, result: Any, state: ToolCallingState) -> None:
|
50
|
+
"""Add function execution result to chat history."""
|
51
|
+
state.chat_history.append(
|
52
|
+
ChatMessage(
|
53
|
+
role="FUNCTION",
|
54
|
+
content=StringChatMessageContent(value=json.dumps(result, cls=DefaultStateEncoder)),
|
55
|
+
)
|
56
|
+
)
|
57
|
+
|
58
|
+
|
34
59
|
class ToolRouterNode(InlinePromptNode[ToolCallingState]):
|
35
60
|
max_prompt_iterations: Optional[int] = 5
|
36
61
|
|
@@ -69,20 +94,11 @@ class ToolRouterNode(InlinePromptNode[ToolCallingState]):
|
|
69
94
|
yield output
|
70
95
|
|
71
96
|
|
72
|
-
class DynamicSubworkflowDeploymentNode(SubworkflowDeploymentNode[ToolCallingState]):
|
97
|
+
class DynamicSubworkflowDeploymentNode(SubworkflowDeploymentNode[ToolCallingState], FunctionCallNodeMixin):
|
73
98
|
"""Node that executes a deployment definition with function call output."""
|
74
99
|
|
75
|
-
function_call_output: List[PromptOutput]
|
76
|
-
|
77
100
|
def run(self) -> Iterator[BaseOutput]:
|
78
|
-
|
79
|
-
function_call = self.function_call_output[0]
|
80
|
-
if function_call.type == "FUNCTION_CALL" and function_call.value is not None:
|
81
|
-
arguments = function_call.value.arguments
|
82
|
-
else:
|
83
|
-
arguments = {}
|
84
|
-
else:
|
85
|
-
arguments = {}
|
101
|
+
arguments = self._extract_function_arguments()
|
86
102
|
|
87
103
|
# Mypy doesn't like instance assignments of class attributes. It's safe in our case tho bc it's what
|
88
104
|
# we do in the `__init__` method. Long term, instead of the function_call_output attribute above, we
|
@@ -103,28 +119,16 @@ class DynamicSubworkflowDeploymentNode(SubworkflowDeploymentNode[ToolCallingStat
|
|
103
119
|
yield output
|
104
120
|
|
105
121
|
# Add the result to the chat history
|
106
|
-
self.state
|
107
|
-
ChatMessage(
|
108
|
-
role="FUNCTION",
|
109
|
-
content=StringChatMessageContent(value=json.dumps(outputs, cls=DefaultStateEncoder)),
|
110
|
-
)
|
111
|
-
)
|
122
|
+
self._add_function_result_to_chat_history(outputs, self.state)
|
112
123
|
|
113
124
|
|
114
|
-
class DynamicInlineSubworkflowNode(
|
125
|
+
class DynamicInlineSubworkflowNode(
|
126
|
+
InlineSubworkflowNode[ToolCallingState, BaseInputs, BaseState], FunctionCallNodeMixin
|
127
|
+
):
|
115
128
|
"""Node that executes an inline subworkflow with function call output."""
|
116
129
|
|
117
|
-
function_call_output: List[PromptOutput]
|
118
|
-
|
119
130
|
def run(self) -> Iterator[BaseOutput]:
|
120
|
-
|
121
|
-
function_call = self.function_call_output[0]
|
122
|
-
if function_call.type == "FUNCTION_CALL" and function_call.value is not None:
|
123
|
-
arguments = function_call.value.arguments
|
124
|
-
else:
|
125
|
-
arguments = {}
|
126
|
-
else:
|
127
|
-
arguments = {}
|
131
|
+
arguments = self._extract_function_arguments()
|
128
132
|
|
129
133
|
self.subworkflow_inputs = arguments # type: ignore[misc]
|
130
134
|
|
@@ -137,29 +141,16 @@ class DynamicInlineSubworkflowNode(InlineSubworkflowNode[ToolCallingState, BaseI
|
|
137
141
|
yield output
|
138
142
|
|
139
143
|
# Add the result to the chat history
|
140
|
-
self.state
|
141
|
-
ChatMessage(
|
142
|
-
role="FUNCTION",
|
143
|
-
content=StringChatMessageContent(value=json.dumps(outputs, cls=DefaultStateEncoder)),
|
144
|
-
)
|
145
|
-
)
|
144
|
+
self._add_function_result_to_chat_history(outputs, self.state)
|
146
145
|
|
147
146
|
|
148
|
-
class FunctionNode(BaseNode[ToolCallingState]):
|
147
|
+
class FunctionNode(BaseNode[ToolCallingState], FunctionCallNodeMixin):
|
149
148
|
"""Node that executes a regular Python function with function call output."""
|
150
149
|
|
151
|
-
function_call_output: List[PromptOutput]
|
152
150
|
function_definition: Callable[..., Any]
|
153
151
|
|
154
152
|
def run(self) -> Iterator[BaseOutput]:
|
155
|
-
|
156
|
-
function_call = self.function_call_output[0]
|
157
|
-
if function_call.type == "FUNCTION_CALL" and function_call.value is not None:
|
158
|
-
arguments = function_call.value.arguments
|
159
|
-
else:
|
160
|
-
arguments = {}
|
161
|
-
else:
|
162
|
-
arguments = {}
|
153
|
+
arguments = self._extract_function_arguments()
|
163
154
|
|
164
155
|
try:
|
165
156
|
result = self.function_definition(**arguments)
|
@@ -171,16 +162,73 @@ class FunctionNode(BaseNode[ToolCallingState]):
|
|
171
162
|
)
|
172
163
|
|
173
164
|
# Add the result to the chat history
|
174
|
-
self.state
|
175
|
-
|
176
|
-
|
177
|
-
|
165
|
+
self._add_function_result_to_chat_history(result, self.state)
|
166
|
+
|
167
|
+
yield from []
|
168
|
+
|
169
|
+
|
170
|
+
class ComposioNode(BaseNode[ToolCallingState], FunctionCallNodeMixin):
|
171
|
+
"""Node that executes a Composio tool with function call output."""
|
172
|
+
|
173
|
+
composio_tool: ComposioToolDefinition
|
174
|
+
|
175
|
+
def run(self) -> Iterator[BaseOutput]:
|
176
|
+
# Extract arguments from function call
|
177
|
+
arguments = self._extract_function_arguments()
|
178
|
+
|
179
|
+
# HACK: Use first Composio API key found in environment variables
|
180
|
+
composio_api_key = None
|
181
|
+
common_env_var_names = ["COMPOSIO_API_KEY", "COMPOSIO_KEY"]
|
182
|
+
|
183
|
+
for env_var_name in common_env_var_names:
|
184
|
+
value = os.environ.get(env_var_name)
|
185
|
+
if value:
|
186
|
+
composio_api_key = value
|
187
|
+
break
|
188
|
+
|
189
|
+
if not composio_api_key:
|
190
|
+
raise NodeException(
|
191
|
+
message=(
|
192
|
+
"No Composio API key found in environment variables. "
|
193
|
+
"Please ensure one of these environment variables is set: "
|
194
|
+
)
|
195
|
+
+ ", ".join(common_env_var_names),
|
196
|
+
code=WorkflowErrorCode.NODE_EXECUTION,
|
197
|
+
)
|
198
|
+
|
199
|
+
try:
|
200
|
+
# Execute using ComposioService
|
201
|
+
composio_service = ComposioService(api_key=composio_api_key)
|
202
|
+
result = composio_service.execute_tool(tool_name=self.composio_tool.action, arguments=arguments)
|
203
|
+
except Exception as e:
|
204
|
+
raise NodeException(
|
205
|
+
message=f"Error executing Composio tool '{self.composio_tool.action}': {str(e)}",
|
206
|
+
code=WorkflowErrorCode.NODE_EXECUTION,
|
178
207
|
)
|
179
|
-
|
208
|
+
|
209
|
+
# Add result to chat history
|
210
|
+
self._add_function_result_to_chat_history(result, self.state)
|
180
211
|
|
181
212
|
yield from []
|
182
213
|
|
183
214
|
|
215
|
+
def create_composio_wrapper_function(tool_def: ComposioToolDefinition):
|
216
|
+
"""Create a real Python function that wraps the Composio tool for prompt layer compatibility."""
|
217
|
+
|
218
|
+
def wrapper_function(**kwargs):
|
219
|
+
# This should never be called due to routing, but satisfies introspection
|
220
|
+
raise RuntimeError(
|
221
|
+
f"ComposioToolDefinition wrapper for '{tool_def.action}' should not be called directly. "
|
222
|
+
f"Execution should go through ComposioNode. This suggests a routing issue."
|
223
|
+
)
|
224
|
+
|
225
|
+
# Set proper function attributes for prompt layer introspection
|
226
|
+
wrapper_function.__name__ = tool_def.name
|
227
|
+
wrapper_function.__doc__ = tool_def.description
|
228
|
+
|
229
|
+
return wrapper_function
|
230
|
+
|
231
|
+
|
184
232
|
def create_tool_router_node(
|
185
233
|
ml_model: str,
|
186
234
|
blocks: List[PromptBlock],
|
@@ -190,9 +238,18 @@ def create_tool_router_node(
|
|
190
238
|
max_prompt_iterations: Optional[int] = None,
|
191
239
|
) -> Type[ToolRouterNode]:
|
192
240
|
if functions and len(functions) > 0:
|
193
|
-
#
|
241
|
+
# Create dynamic ports and convert functions in a single loop
|
194
242
|
Ports = type("Ports", (), {})
|
243
|
+
prompt_functions = []
|
244
|
+
|
195
245
|
for function in functions:
|
246
|
+
# Convert ComposioToolDefinition to wrapper function for prompt layer
|
247
|
+
if isinstance(function, ComposioToolDefinition):
|
248
|
+
prompt_functions.append(create_composio_wrapper_function(function))
|
249
|
+
else:
|
250
|
+
prompt_functions.append(function)
|
251
|
+
|
252
|
+
# Create port for this function (using original function for get_function_name)
|
196
253
|
function_name = get_function_name(function)
|
197
254
|
|
198
255
|
# Avoid using lambda to capture function_name
|
@@ -215,6 +272,7 @@ def create_tool_router_node(
|
|
215
272
|
else:
|
216
273
|
# If no functions exist, create a simple Ports class with just a default port
|
217
274
|
Ports = type("Ports", (), {"default": Port(default=True)})
|
275
|
+
prompt_functions = []
|
218
276
|
|
219
277
|
# Add a chat history block to blocks only if one doesn't already exist
|
220
278
|
has_chat_history_block = any(
|
@@ -247,7 +305,7 @@ def create_tool_router_node(
|
|
247
305
|
{
|
248
306
|
"ml_model": ml_model,
|
249
307
|
"blocks": blocks,
|
250
|
-
"functions": functions
|
308
|
+
"functions": prompt_functions, # Use converted functions for prompt layer
|
251
309
|
"prompt_inputs": node_prompt_inputs,
|
252
310
|
"parameters": parameters,
|
253
311
|
"max_prompt_iterations": max_prompt_iterations,
|
@@ -291,20 +349,16 @@ def create_function_node(
|
|
291
349
|
return node
|
292
350
|
|
293
351
|
elif isinstance(function, ComposioToolDefinition):
|
294
|
-
# ComposioToolDefinition execution not yet implemented
|
295
|
-
def composio_not_implemented(**kwargs):
|
296
|
-
raise NotImplementedError("ComposioToolDefinition execution not yet implemented")
|
297
|
-
|
298
352
|
node = type(
|
299
353
|
f"ComposioNode_{function.name}",
|
300
|
-
(
|
354
|
+
(ComposioNode,),
|
301
355
|
{
|
302
|
-
"
|
356
|
+
"composio_tool": function,
|
303
357
|
"function_call_output": tool_router_node.Outputs.results,
|
304
358
|
"__module__": __name__,
|
305
359
|
},
|
306
360
|
)
|
307
|
-
|
361
|
+
return node
|
308
362
|
elif is_workflow_class(function):
|
309
363
|
node = type(
|
310
364
|
f"DynamicInlineSubworkflowNode_{function.__name__}",
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import dataclasses
|
2
2
|
import inspect
|
3
|
-
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Type, Union, get_args, get_origin
|
3
|
+
from typing import TYPE_CHECKING, 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
|
@@ -33,6 +33,18 @@ def compile_annotation(annotation: Optional[Any], defs: dict[str, Any]) -> dict:
|
|
33
33
|
if get_origin(annotation) is Union:
|
34
34
|
return {"anyOf": [compile_annotation(a, defs) for a in get_args(annotation)]}
|
35
35
|
|
36
|
+
if get_origin(annotation) is Literal:
|
37
|
+
values = list(get_args(annotation))
|
38
|
+
types = {type(value) for value in values}
|
39
|
+
if len(types) == 1:
|
40
|
+
value_type = types.pop()
|
41
|
+
if value_type in type_map:
|
42
|
+
return {"type": type_map[value_type], "enum": values}
|
43
|
+
else:
|
44
|
+
return {"enum": values}
|
45
|
+
else:
|
46
|
+
return {"enum": values}
|
47
|
+
|
36
48
|
if get_origin(annotation) is dict:
|
37
49
|
_, value_type = get_args(annotation)
|
38
50
|
return {"type": "object", "additionalProperties": compile_annotation(value_type, defs)}
|
@@ -1,6 +1,8 @@
|
|
1
|
+
import pytest
|
1
2
|
from dataclasses import dataclass
|
3
|
+
from enum import Enum
|
2
4
|
from unittest.mock import Mock
|
3
|
-
from typing import Dict, List, Optional, Union
|
5
|
+
from typing import Dict, List, Literal, Optional, Union
|
4
6
|
|
5
7
|
from pydantic import BaseModel
|
6
8
|
|
@@ -581,3 +583,32 @@ def test_compile_workflow_deployment_function_definition__defaults():
|
|
581
583
|
"required": ["no_default"],
|
582
584
|
},
|
583
585
|
)
|
586
|
+
|
587
|
+
|
588
|
+
@pytest.mark.parametrize(
|
589
|
+
"annotation,expected_schema",
|
590
|
+
[
|
591
|
+
(Literal["a", "b"], {"type": "string", "enum": ["a", "b"]}),
|
592
|
+
(Literal["a", 1], {"enum": ["a", 1]}),
|
593
|
+
],
|
594
|
+
)
|
595
|
+
def test_compile_function_definition__literal(annotation, expected_schema):
|
596
|
+
def my_function(a: annotation): # type: ignore
|
597
|
+
pass
|
598
|
+
|
599
|
+
compiled_function = compile_function_definition(my_function)
|
600
|
+
assert isinstance(compiled_function.parameters, dict)
|
601
|
+
assert compiled_function.parameters["properties"]["a"] == expected_schema
|
602
|
+
|
603
|
+
|
604
|
+
def test_compile_function_definition__literal_type_not_in_map():
|
605
|
+
class MyEnum(Enum):
|
606
|
+
FOO = "foo"
|
607
|
+
BAR = "bar"
|
608
|
+
|
609
|
+
def my_function(a: Literal[MyEnum.FOO, MyEnum.BAR]):
|
610
|
+
pass
|
611
|
+
|
612
|
+
compiled_function = compile_function_definition(my_function)
|
613
|
+
assert isinstance(compiled_function.parameters, dict)
|
614
|
+
assert compiled_function.parameters["properties"]["a"] == {"enum": [MyEnum.FOO, MyEnum.BAR]}
|
@@ -94,6 +94,7 @@ vellum_ee/workflows/display/tests/workflow_serialization/test_basic_search_node_
|
|
94
94
|
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_subworkflow_deployment_serialization.py,sha256=XWrhHg_acLsRHwjstBAii9Pmes9oXFtAUWSAVF1oSBc,11225
|
95
95
|
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_node_serialization.py,sha256=V8b6gKghLlO7PJI8xeNdnfn8aII0W_IFQvSQBQM62UQ,7721
|
96
96
|
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py,sha256=hDWtKXmGI1CKhTwTNqpu_d5RkE5n7SolMLtgd87KqTI,3856
|
97
|
+
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_composio_serialization.py,sha256=esCCiOXM7Syb9tFyVdZTu95lnP8yiADfBPXw-oUy4zk,3719
|
97
98
|
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_serialization.py,sha256=4t1lkN2nsZF6lFqP6QnskUQWJlhasF8C2_f6atzk8ZY,26298
|
98
99
|
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py,sha256=1hoakUkh5kHZYIfY1moJBZYzXgAafkgWsIf4lmZ12vg,9521
|
99
100
|
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_workflow_deployment_serialization.py,sha256=mova0sPD3evHiHIN1O0VynxlCp-uOcEIKve5Pd_oCDg,4069
|
@@ -139,12 +140,12 @@ vellum_ee/workflows/tests/test_display_meta.py,sha256=PkXJVnMZs9GNooDkd59n4YTBAX
|
|
139
140
|
vellum_ee/workflows/tests/test_serialize_module.py,sha256=EVrCRAP0lpvd0GIDlg2tnGfJzDNooNDXPfGFPLAqmbI,1870
|
140
141
|
vellum_ee/workflows/tests/test_server.py,sha256=SsOkS6sGO7uGC4mxvk4iv8AtcXs058P9hgFHzTWmpII,14519
|
141
142
|
vellum_ee/workflows/tests/test_virtual_files.py,sha256=TJEcMR0v2S8CkloXNmCHA0QW0K6pYNGaIjraJz7sFvY,2762
|
142
|
-
vellum/__init__.py,sha256
|
143
|
+
vellum/__init__.py,sha256=v-QZCDKBW8F_J5j4iE7eSjcrhHQA2JeBDB6Hxn8mnEI,43273
|
143
144
|
vellum/client/README.md,sha256=Dle5iytCXxP1pNeNd7uZyhFo0rl7tp7vU7s8gmi10OQ,4863
|
144
145
|
vellum/client/__init__.py,sha256=KmkyOgReuTsjmXF3WC_dPQ9QqJgYrB3Sr8_LcSUIQyI,125258
|
145
146
|
vellum/client/core/__init__.py,sha256=SQ85PF84B9MuKnBwHNHWemSGuy-g_515gFYNFhvEE0I,1438
|
146
147
|
vellum/client/core/api_error.py,sha256=RE8LELok2QCjABadECTvtDp7qejA1VmINCh6TbqPwSE,426
|
147
|
-
vellum/client/core/client_wrapper.py,sha256=
|
148
|
+
vellum/client/core/client_wrapper.py,sha256=70UredlOjsMiKycnRj65YT1Wa5H3gtf4_h3_2Ip7LOs,2383
|
148
149
|
vellum/client/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
|
149
150
|
vellum/client/core/file.py,sha256=d4NNbX8XvXP32z8KpK2Xovv33nFfruIrpz0QWxlgpZk,2663
|
150
151
|
vellum/client/core/http_client.py,sha256=cKs2w0ybDBk1wHQf-fTALm_MmvaMe3cZKcYJxqmCxkE,19539
|
@@ -214,7 +215,7 @@ vellum/client/resources/workspace_secrets/__init__.py,sha256=FTtvy8EDg9nNNg9WCat
|
|
214
215
|
vellum/client/resources/workspace_secrets/client.py,sha256=l1FOj0f-IB5_oQ7iWiHopFK3lDXBqiaIc9g10W9PHFU,8381
|
215
216
|
vellum/client/resources/workspaces/__init__.py,sha256=FTtvy8EDg9nNNg9WCatVgKTRYV8-_v1roeGPAKoa_pw,65
|
216
217
|
vellum/client/resources/workspaces/client.py,sha256=61eFS8USOtHf4cFoT3dZmAMs6KGAVPbXjAolws2ftsQ,3683
|
217
|
-
vellum/client/types/__init__.py,sha256=
|
218
|
+
vellum/client/types/__init__.py,sha256=zNkFXLVQBQSKVO_yBZ8PGzmAwuwGEOqZ1apSQAuly8E,65526
|
218
219
|
vellum/client/types/ad_hoc_execute_prompt_event.py,sha256=bCjujA2XsOgyF3bRZbcEqV2rOIymRgsLoIRtZpB14xg,607
|
219
220
|
vellum/client/types/ad_hoc_expand_meta.py,sha256=1gv-NCsy_6xBYupLvZH979yf2VMdxAU-l0y0ynMKZaw,1331
|
220
221
|
vellum/client/types/ad_hoc_fulfilled_prompt_execution_meta.py,sha256=oDG60TpwK1YNSKhRsBbiP2O3ZF9PKR-M9chGIfKw4R4,1004
|
@@ -428,6 +429,7 @@ vellum/client/types/metric_node_result.py,sha256=YdKq1DZiBD1RBtjyMejImylv3BqrwY8
|
|
428
429
|
vellum/client/types/ml_model_read.py,sha256=Vr5KjaS2Tca0GXsltfSYQpuyGYpgIahPEFfS6HfFGSo,706
|
429
430
|
vellum/client/types/ml_model_usage.py,sha256=WcZ2F1hfxyTwe-spOVwv-qJYDjs4hf9sn7BF2abawPo,910
|
430
431
|
vellum/client/types/ml_model_usage_wrapper.py,sha256=Vi7urVmTn1E_aZV6TxnW-qjDayRv7A_6JDk84KqAIa0,645
|
432
|
+
vellum/client/types/name_enum.py,sha256=5y6CNh1LNmWC_CTje_rD-N53Z57PckS9avbCxmqjAMk,210
|
431
433
|
vellum/client/types/named_scenario_input_chat_history_variable_value_request.py,sha256=aVZmAxu-47c34NyhSkfi9tQqIPy29cdJ7Pb4MIgKeNw,862
|
432
434
|
vellum/client/types/named_scenario_input_json_variable_value_request.py,sha256=UgnKv70zFviv1kl4nM7aM7IFA-7xyDOtglW4Y3GBZ28,757
|
433
435
|
vellum/client/types/named_scenario_input_request.py,sha256=Pi8l377OHvKBwvPu9slZ1omf_NJ9S1mCQ5Wr-Ux5KVg,611
|
@@ -540,7 +542,7 @@ vellum/client/types/prompt_request_json_input.py,sha256=vLhwvCWL_yjVfDzT4921xK4Q
|
|
540
542
|
vellum/client/types/prompt_request_string_input.py,sha256=8GSFhtN3HeYssbDRY7B5SCh5Qrp67340D9c3oINpCmw,714
|
541
543
|
vellum/client/types/prompt_settings.py,sha256=gITevU-SWiStXFKLfpwG5dQJ-bic5CxnM0OHsT9KR0s,635
|
542
544
|
vellum/client/types/prompt_version_build_config_sandbox.py,sha256=SXU62bAueVpoWo178bLIMYi8aNVpsBGTtOQxHcg6Dmo,678
|
543
|
-
vellum/client/types/quota.py,sha256=
|
545
|
+
vellum/client/types/quota.py,sha256=3NXzWkznGopK9AdoltadaF-BzfGFbC_WYbvqulweilc,672
|
544
546
|
vellum/client/types/raw_prompt_execution_overrides_request.py,sha256=x4Chkm_NxXySOEyA6s6J_mhhiM91KCcQbu6pQETB8RI,927
|
545
547
|
vellum/client/types/reducto_chunker_config.py,sha256=by_Dj0hZPkLQAf7l1KAudRB8X2XnlfHiRTsyiR-DTRY,654
|
546
548
|
vellum/client/types/reducto_chunker_config_request.py,sha256=RnulU2a_PUtvRE2qhARQhsCkWI--K_MYkobzLNRGEz4,661
|
@@ -1128,6 +1130,7 @@ vellum/types/metric_node_result.py,sha256=Q_bUgbdRnSP26nEcJ-vZD7k2oLIcThN3JjW9hX
|
|
1128
1130
|
vellum/types/ml_model_read.py,sha256=d_CPwZ3bhXtC8c5jwXkuNVvobDqPI-I_byZ6WnVla1Q,151
|
1129
1131
|
vellum/types/ml_model_usage.py,sha256=Q-7_W6GfL8rMnqjhSiZirw8oB60GFc0p_mNYdZdlMjY,152
|
1130
1132
|
vellum/types/ml_model_usage_wrapper.py,sha256=anoup7KWug4Mrt-JhsB_S1zuKcdq9ncXsz3y8t_I52g,160
|
1133
|
+
vellum/types/name_enum.py,sha256=YrE8Nx14GVcIUZgM_ackw2n9qYXTX0wQywYGKUdUOSY,147
|
1131
1134
|
vellum/types/named_scenario_input_chat_history_variable_value_request.py,sha256=Sfwba1cvocP8UR6CCDEjE7HsI5Xs6Dopk1W88Zf1ng8,194
|
1132
1135
|
vellum/types/named_scenario_input_json_variable_value_request.py,sha256=1kZ4Y7TttH8O897rmtkEIMraql5dTIyEVvFZn0I5Py8,186
|
1133
1136
|
vellum/types/named_scenario_input_request.py,sha256=E7TcL4YRw7dcEyMEws3AsiCw7GSHnrXpktBKOoVSytA,166
|
@@ -1701,8 +1704,8 @@ vellum/workflows/nodes/displayable/tool_calling_node/state.py,sha256=oQg_GAtc349
|
|
1701
1704
|
vellum/workflows/nodes/displayable/tool_calling_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1702
1705
|
vellum/workflows/nodes/displayable/tool_calling_node/tests/test_composio_service.py,sha256=BOb4LTAELS-cX5xPH8fH4E2CKl1IBBjiOBYhQl51PpI,4758
|
1703
1706
|
vellum/workflows/nodes/displayable/tool_calling_node/tests/test_node.py,sha256=raY_E5-EgtYNXEPbO2I-Ythe4YeuFdGsXGZ_BAN98uI,7979
|
1704
|
-
vellum/workflows/nodes/displayable/tool_calling_node/tests/test_utils.py,sha256=
|
1705
|
-
vellum/workflows/nodes/displayable/tool_calling_node/utils.py,sha256=
|
1707
|
+
vellum/workflows/nodes/displayable/tool_calling_node/tests/test_utils.py,sha256=aCK4TDcD4TcgoYbOs712qFiNCVRVffCb5HZCZQGDiUc,2449
|
1708
|
+
vellum/workflows/nodes/displayable/tool_calling_node/utils.py,sha256=Vbdt6wsQozK6PWJq5chtda9pwasjNpx7IE0IMkWPSak,15622
|
1706
1709
|
vellum/workflows/nodes/experimental/README.md,sha256=eF6DfIL8t-HbF9-mcofOMymKrraiBHDLKTlnBa51ZiE,284
|
1707
1710
|
vellum/workflows/nodes/experimental/__init__.py,sha256=jCQgvZEknXKfuNhGSOou4XPfrPqZ1_XBj5F0n0fgiWM,106
|
1708
1711
|
vellum/workflows/nodes/experimental/openai_chat_completion_node/__init__.py,sha256=lsyD9laR9p7kx5-BXGH2gUTM242UhKy8SMV0SR6S2iE,90
|
@@ -1758,11 +1761,11 @@ vellum/workflows/types/tests/test_definition.py,sha256=c3GczPtWxuH3BOULwZacxYTQl
|
|
1758
1761
|
vellum/workflows/types/tests/test_utils.py,sha256=UnZog59tR577mVwqZRqqWn2fScoOU1H6up0EzS8zYhw,2536
|
1759
1762
|
vellum/workflows/types/utils.py,sha256=mTctHITBybpt4855x32oCKALBEcMNLn-9cCmfEKgJHQ,6498
|
1760
1763
|
vellum/workflows/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1761
|
-
vellum/workflows/utils/functions.py,sha256=
|
1764
|
+
vellum/workflows/utils/functions.py,sha256=4pHaaaOXwvYTLVThTkLYpESBkzNKuZdiyRBGRpcWxqY,7623
|
1762
1765
|
vellum/workflows/utils/names.py,sha256=QLUqfJ1tmSEeUwBKTTiv_Qk3QGbInC2RSmlXfGXc8Wo,380
|
1763
1766
|
vellum/workflows/utils/pydantic_schema.py,sha256=eR_bBtY-T0pttJP-ARwagSdCOnwPUtiT3cegm2lzDTQ,1310
|
1764
1767
|
vellum/workflows/utils/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1765
|
-
vellum/workflows/utils/tests/test_functions.py,sha256=
|
1768
|
+
vellum/workflows/utils/tests/test_functions.py,sha256=xIk1r0XwbPcbQLHwN5NFUrztfKa3fGtUnfjvhOrYNNg,19449
|
1766
1769
|
vellum/workflows/utils/tests/test_names.py,sha256=aOqpyvMsOEK_9mg_-yaNxQDW7QQfwqsYs37PseyLhxw,402
|
1767
1770
|
vellum/workflows/utils/tests/test_uuids.py,sha256=i77ABQ0M3S-aFLzDXHJq_yr5FPkJEWCMBn1HJ3DObrE,437
|
1768
1771
|
vellum/workflows/utils/tests/test_vellum_variables.py,sha256=vbnKgm41aB5OXlO-ZIPbhQ6xDiZkT8KMxCLqz4zocWY,1791
|
@@ -1775,8 +1778,8 @@ vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnad
|
|
1775
1778
|
vellum/workflows/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1776
1779
|
vellum/workflows/workflows/tests/test_base_workflow.py,sha256=ptMntHzVyy8ZuzNgeTuk7hREgKQ5UBdgq8VJFSGaW4Y,20832
|
1777
1780
|
vellum/workflows/workflows/tests/test_context.py,sha256=VJBUcyWVtMa_lE5KxdhgMu0WYNYnUQUDvTF7qm89hJ0,2333
|
1778
|
-
vellum_ai-1.0.
|
1779
|
-
vellum_ai-1.0.
|
1780
|
-
vellum_ai-1.0.
|
1781
|
-
vellum_ai-1.0.
|
1782
|
-
vellum_ai-1.0.
|
1781
|
+
vellum_ai-1.0.5.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
|
1782
|
+
vellum_ai-1.0.5.dist-info/METADATA,sha256=FIbEOXz7OKYnan91KlabwU9Z_BqNdMXNgKp11ZA8olI,5649
|
1783
|
+
vellum_ai-1.0.5.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
1784
|
+
vellum_ai-1.0.5.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
|
1785
|
+
vellum_ai-1.0.5.dist-info/RECORD,,
|
@@ -0,0 +1,86 @@
|
|
1
|
+
from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
|
2
|
+
|
3
|
+
from tests.workflows.basic_tool_calling_node_with_composio_tool.workflow import (
|
4
|
+
BasicToolCallingNodeWithComposioToolWorkflow,
|
5
|
+
)
|
6
|
+
|
7
|
+
|
8
|
+
def test_serialize_workflow():
|
9
|
+
# GIVEN a Workflow that uses a tool calling node with a composio tool
|
10
|
+
# WHEN we serialize it
|
11
|
+
workflow_display = get_workflow_display(workflow_class=BasicToolCallingNodeWithComposioToolWorkflow)
|
12
|
+
|
13
|
+
serialized_workflow: dict = workflow_display.serialize()
|
14
|
+
|
15
|
+
# THEN we should get a serialized representation of the Workflow
|
16
|
+
assert serialized_workflow.keys() == {
|
17
|
+
"workflow_raw_data",
|
18
|
+
"input_variables",
|
19
|
+
"state_variables",
|
20
|
+
"output_variables",
|
21
|
+
}
|
22
|
+
|
23
|
+
# AND its input variables should be what we expect
|
24
|
+
input_variables = serialized_workflow["input_variables"]
|
25
|
+
assert len(input_variables) == 1
|
26
|
+
|
27
|
+
# AND its output variables should be what we expect
|
28
|
+
output_variables = serialized_workflow["output_variables"]
|
29
|
+
assert len(output_variables) == 2
|
30
|
+
|
31
|
+
# Find the text and chat_history outputs
|
32
|
+
text_output = next(var for var in output_variables if var["key"] == "text")
|
33
|
+
chat_history_output = next(var for var in output_variables if var["key"] == "chat_history")
|
34
|
+
|
35
|
+
assert text_output["type"] == "STRING"
|
36
|
+
assert chat_history_output["type"] == "CHAT_HISTORY"
|
37
|
+
|
38
|
+
# AND its raw data should be what we expect
|
39
|
+
workflow_raw_data = serialized_workflow["workflow_raw_data"]
|
40
|
+
tool_calling_node = workflow_raw_data["nodes"][1]
|
41
|
+
|
42
|
+
# AND the tool calling node should have the composio tool properly serialized
|
43
|
+
functions_attribute = next(attr for attr in tool_calling_node["attributes"] if attr["name"] == "functions")
|
44
|
+
assert functions_attribute["value"]["type"] == "CONSTANT_VALUE"
|
45
|
+
assert functions_attribute["value"]["value"]["type"] == "JSON"
|
46
|
+
|
47
|
+
functions_list = functions_attribute["value"]["value"]["value"]
|
48
|
+
assert len(functions_list) == 1
|
49
|
+
|
50
|
+
composio_function = functions_list[0]
|
51
|
+
assert composio_function == {
|
52
|
+
"type": "COMPOSIO",
|
53
|
+
"toolkit": "GITHUB",
|
54
|
+
"action": "GITHUB_CREATE_AN_ISSUE",
|
55
|
+
"description": "Create a new issue in a GitHub repository",
|
56
|
+
"display_name": "Create GitHub Issue",
|
57
|
+
}
|
58
|
+
|
59
|
+
# AND the rest of the node structure should be correct
|
60
|
+
assert tool_calling_node["type"] == "GENERIC"
|
61
|
+
assert tool_calling_node["base"]["name"] == "ToolCallingNode"
|
62
|
+
assert tool_calling_node["definition"]["name"] == "ComposioToolCallingNode"
|
63
|
+
|
64
|
+
# AND the blocks should be properly serialized
|
65
|
+
blocks_attribute = next(attr for attr in tool_calling_node["attributes"] if attr["name"] == "blocks")
|
66
|
+
assert blocks_attribute["value"]["type"] == "CONSTANT_VALUE"
|
67
|
+
blocks_list = blocks_attribute["value"]["value"]["value"]
|
68
|
+
assert len(blocks_list) == 2
|
69
|
+
assert blocks_list[0]["chat_role"] == "SYSTEM"
|
70
|
+
assert blocks_list[1]["chat_role"] == "USER"
|
71
|
+
|
72
|
+
# AND the prompt inputs should be properly serialized
|
73
|
+
prompt_inputs_attribute = next(attr for attr in tool_calling_node["attributes"] if attr["name"] == "prompt_inputs")
|
74
|
+
assert prompt_inputs_attribute["value"]["type"] == "DICTIONARY_REFERENCE"
|
75
|
+
entries = prompt_inputs_attribute["value"]["entries"]
|
76
|
+
assert len(entries) == 1
|
77
|
+
assert entries[0]["key"] == "question"
|
78
|
+
assert entries[0]["value"]["type"] == "WORKFLOW_INPUT"
|
79
|
+
|
80
|
+
# AND the outputs should be correct
|
81
|
+
outputs = tool_calling_node["outputs"]
|
82
|
+
assert len(outputs) == 2
|
83
|
+
assert outputs[0]["name"] == "text"
|
84
|
+
assert outputs[0]["type"] == "STRING"
|
85
|
+
assert outputs[1]["name"] == "chat_history"
|
86
|
+
assert outputs[1]["type"] == "CHAT_HISTORY"
|
File without changes
|
File without changes
|
File without changes
|