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 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.4",
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.4",
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
@@ -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",
@@ -0,0 +1,7 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ import typing
4
+
5
+ NameEnum = typing.Union[
6
+ typing.Literal["workflow_executions", "prompt_executions", "workflow_runtime_seconds"], typing.Any
7
+ ]
@@ -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: str
11
+ name: NameEnum
11
12
  value: typing.Optional[int] = None
12
13
  period_seconds: typing.Optional[int] = None
13
14
 
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.name_enum import *
@@ -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
- if self.function_call_output and len(self.function_call_output) > 0:
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.chat_history.append(
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(InlineSubworkflowNode[ToolCallingState, BaseInputs, BaseState]):
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
- if self.function_call_output and len(self.function_call_output) > 0:
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.chat_history.append(
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
- if self.function_call_output and len(self.function_call_output) > 0:
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.chat_history.append(
175
- ChatMessage(
176
- role="FUNCTION",
177
- content=StringChatMessageContent(value=json.dumps(result, cls=DefaultStateEncoder)),
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
- # If we have functions, create dynamic ports for each function
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
- (FunctionNode,),
354
+ (ComposioNode,),
301
355
  {
302
- "function_definition": composio_not_implemented,
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]}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-ai
3
- Version: 1.0.4
3
+ Version: 1.0.5
4
4
  Summary:
5
5
  License: MIT
6
6
  Requires-Python: >=3.9,<4.0
@@ -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=-Ou4HwpSHaHMrNOXucTQDBqLeF0Hf3SkNV7ZXbm-9BY,43243
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=im_0U7PdkUrr4begxJ1IwohjLbmzi8c2diHiklFefHE,2383
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=QRZCQZxK7WN5zFYOV0eCdhKfVvYFDfDgkux3CL9S9qc,65478
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=e-Y1YHvd6ZDFDvfJdMs5sA9JQh8pl2Ep7bFrzB5LjNI,635
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=eu6WTyENhGLg9pGp_j69rysZjf_qiQXske1YdZn9PzU,1718
1705
- vellum/workflows/nodes/displayable/tool_calling_node/utils.py,sha256=jMXStMKr3BFfQKdQa9hFFxI7RQp8eAMAknpqkbY0Nk0,13344
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=Gi08SYaTfLF04slbY_YcfP5erIMwtFgtYa59vhWez9k,7195
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=6AQt2Wu2Uj6UAxYzwi1Zd0YL4-ZOL47Yc_X3iE8ol9U,18445
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.4.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1779
- vellum_ai-1.0.4.dist-info/METADATA,sha256=a7IAzLZ3FlACUej3-52w1DORsAgXM5tpzkLJ8XAgnIQ,5649
1780
- vellum_ai-1.0.4.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1781
- vellum_ai-1.0.4.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1782
- vellum_ai-1.0.4.dist-info/RECORD,,
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"