vellum-ai 1.0.9__py3-none-any.whl → 1.0.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (21) hide show
  1. vellum/client/core/client_wrapper.py +2 -2
  2. vellum/workflows/emitters/__init__.py +2 -0
  3. vellum/workflows/emitters/base.py +17 -0
  4. vellum/workflows/emitters/vellum_emitter.py +138 -0
  5. vellum/workflows/integrations/composio_service.py +6 -2
  6. vellum/workflows/nodes/displayable/tool_calling_node/node.py +18 -19
  7. vellum/workflows/nodes/displayable/tool_calling_node/tests/test_composio_service.py +43 -0
  8. vellum/workflows/nodes/displayable/tool_calling_node/tests/test_node.py +22 -2
  9. vellum/workflows/nodes/displayable/tool_calling_node/tests/test_utils.py +7 -5
  10. vellum/workflows/nodes/displayable/tool_calling_node/utils.py +51 -38
  11. vellum/workflows/types/core.py +3 -5
  12. vellum/workflows/types/definition.py +1 -0
  13. vellum/workflows/types/tests/test_definition.py +4 -1
  14. {vellum_ai-1.0.9.dist-info → vellum_ai-1.0.10.dist-info}/METADATA +1 -1
  15. {vellum_ai-1.0.9.dist-info → vellum_ai-1.0.10.dist-info}/RECORD +21 -20
  16. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_composio_serialization.py +1 -0
  17. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_serialization.py +0 -5
  18. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py +0 -5
  19. {vellum_ai-1.0.9.dist-info → vellum_ai-1.0.10.dist-info}/LICENSE +0 -0
  20. {vellum_ai-1.0.9.dist-info → vellum_ai-1.0.10.dist-info}/WHEEL +0 -0
  21. {vellum_ai-1.0.9.dist-info → vellum_ai-1.0.10.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.9",
28
+ "User-Agent": "vellum-ai/1.0.10",
29
29
  "X-Fern-Language": "Python",
30
30
  "X-Fern-SDK-Name": "vellum-ai",
31
- "X-Fern-SDK-Version": "1.0.9",
31
+ "X-Fern-SDK-Version": "1.0.10",
32
32
  }
33
33
  if self._api_version is not None:
34
34
  headers["X-API-Version"] = self._api_version
@@ -1,5 +1,7 @@
1
1
  from .base import BaseWorkflowEmitter
2
+ from .vellum_emitter import VellumEmitter
2
3
 
3
4
  __all__ = [
4
5
  "BaseWorkflowEmitter",
6
+ "VellumEmitter",
5
7
  ]
@@ -1,10 +1,27 @@
1
1
  from abc import ABC, abstractmethod
2
+ from typing import TYPE_CHECKING, Optional
2
3
 
3
4
  from vellum.workflows.events.workflow import WorkflowEvent
4
5
  from vellum.workflows.state.base import BaseState
5
6
 
7
+ # To protect against circular imports
8
+ if TYPE_CHECKING:
9
+ from vellum.workflows.state.context import WorkflowContext
10
+
6
11
 
7
12
  class BaseWorkflowEmitter(ABC):
13
+ def __init__(self):
14
+ self._context: Optional["WorkflowContext"] = None
15
+
16
+ def register_context(self, context: "WorkflowContext") -> None:
17
+ """
18
+ Register the workflow context with this emitter.
19
+
20
+ Args:
21
+ context: The workflow context containing shared resources like vellum_client.
22
+ """
23
+ self._context = context
24
+
8
25
  @abstractmethod
9
26
  def emit_event(self, event: WorkflowEvent) -> None:
10
27
  pass
@@ -0,0 +1,138 @@
1
+ import logging
2
+ import time
3
+ from typing import Any, Dict, Optional
4
+
5
+ import httpx
6
+
7
+ from vellum.workflows.emitters.base import BaseWorkflowEmitter
8
+ from vellum.workflows.events.types import default_serializer
9
+ from vellum.workflows.events.workflow import WorkflowEvent
10
+ from vellum.workflows.state.base import BaseState
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class VellumEmitter(BaseWorkflowEmitter):
16
+ """
17
+ Emitter that sends workflow events to Vellum's infrastructure for monitoring
18
+ externally hosted SDK-powered workflows.
19
+
20
+ Usage:
21
+ class MyWorkflow(BaseWorkflow):
22
+ emitters = [VellumEmitter]
23
+
24
+ The emitter will automatically use the same Vellum client configuration
25
+ as the workflow it's attached to.
26
+ """
27
+
28
+ def __init__(
29
+ self,
30
+ *,
31
+ timeout: Optional[float] = 30.0,
32
+ max_retries: int = 3,
33
+ ):
34
+ """
35
+ Initialize the VellumEmitter.
36
+
37
+ Args:
38
+ timeout: Request timeout in seconds.
39
+ max_retries: Maximum number of retry attempts for failed requests.
40
+ """
41
+ super().__init__()
42
+ self._timeout = timeout
43
+ self._max_retries = max_retries
44
+ self._events_endpoint = "events" # TODO: make this configurable with the correct url
45
+
46
+ def emit_event(self, event: WorkflowEvent) -> None:
47
+ """
48
+ Emit a workflow event to Vellum's infrastructure.
49
+
50
+ Args:
51
+ event: The workflow event to emit.
52
+ """
53
+ if not self._context:
54
+ return
55
+
56
+ try:
57
+ event_data = default_serializer(event)
58
+
59
+ self._send_event(event_data)
60
+
61
+ except Exception as e:
62
+ logger.exception(f"Failed to emit event {event.name}: {e}")
63
+
64
+ def snapshot_state(self, state: BaseState) -> None:
65
+ """
66
+ Send a state snapshot to Vellum's infrastructure.
67
+
68
+ Args:
69
+ state: The workflow state to snapshot.
70
+ """
71
+ pass
72
+
73
+ def _send_event(self, event_data: Dict[str, Any]) -> None:
74
+ """
75
+ Send event data to Vellum's events endpoint with retry logic.
76
+
77
+ Args:
78
+ event_data: The serialized event data to send.
79
+ """
80
+ if not self._context:
81
+ logger.warning("Cannot send event: No workflow context registered")
82
+ return
83
+
84
+ client = self._context.vellum_client
85
+
86
+ for attempt in range(self._max_retries + 1):
87
+ try:
88
+ # Use the Vellum client's underlying HTTP client to make the request
89
+ # For proper authentication headers and configuration
90
+ base_url = client._client_wrapper.get_environment().default
91
+ response = client._client_wrapper.httpx_client.request(
92
+ method="POST",
93
+ path=f"{base_url}/{self._events_endpoint}", # TODO: will be replaced with the correct url
94
+ json=event_data,
95
+ headers=client._client_wrapper.get_headers(),
96
+ request_options={"timeout_in_seconds": self._timeout},
97
+ )
98
+
99
+ response.raise_for_status()
100
+
101
+ if attempt > 0:
102
+ logger.info(f"Event sent successfully after {attempt + 1} attempts")
103
+ return
104
+
105
+ except httpx.HTTPStatusError as e:
106
+ if e.response.status_code >= 500:
107
+ # Server errors might be transient, retry
108
+ if attempt < self._max_retries:
109
+ wait_time = min(2**attempt, 60) # Exponential backoff, max 60s
110
+ logger.warning(
111
+ f"Server error emitting event (attempt {attempt + 1}/{self._max_retries + 1}): "
112
+ f"{e.response.status_code}. Retrying in {wait_time}s..."
113
+ )
114
+ time.sleep(wait_time)
115
+ continue
116
+ else:
117
+ logger.exception(
118
+ f"Server error emitting event after {self._max_retries + 1} attempts: "
119
+ f"{e.response.status_code} {e.response.text}"
120
+ )
121
+ return
122
+ else:
123
+ # Client errors (4xx) are not retriable
124
+ logger.exception(f"Client error emitting event: {e.response.status_code} {e.response.text}")
125
+ return
126
+
127
+ except httpx.RequestError as e:
128
+ if attempt < self._max_retries:
129
+ wait_time = min(2**attempt, 60) # Exponential backoff, max 60s
130
+ logger.warning(
131
+ f"Network error emitting event (attempt {attempt + 1}/{self._max_retries + 1}): "
132
+ f"{e}. Retrying in {wait_time}s..."
133
+ )
134
+ time.sleep(wait_time)
135
+ continue
136
+ else:
137
+ logger.exception(f"Network error emitting event after {self._max_retries + 1} attempts: {e}")
138
+ return
@@ -135,16 +135,20 @@ class ComposioService:
135
135
  else:
136
136
  raise NodeException(f"Failed to retrieve tool details for '{tool_slug}': {error_message}")
137
137
 
138
- def execute_tool(self, tool_name: str, arguments: Dict[str, Any]) -> Any:
138
+ def execute_tool(self, tool_name: str, arguments: Dict[str, Any], user_id: Optional[str] = None) -> Any:
139
139
  """Execute a tool using direct API request
140
140
 
141
141
  Args:
142
142
  tool_name: The name of the tool to execute (e.g., "HACKERNEWS_GET_USER")
143
143
  arguments: Dictionary of arguments to pass to the tool
144
+ user_id: Optional user ID to identify which user's Composio connection to use
144
145
 
145
146
  Returns:
146
147
  The result of the tool execution
147
148
  """
148
149
  endpoint = f"/tools/execute/{tool_name}"
149
- response = self._make_request(endpoint, method="POST", json_data={"arguments": arguments})
150
+ json_data: Dict[str, Any] = {"arguments": arguments}
151
+ if user_id is not None:
152
+ json_data["user_id"] = user_id
153
+ response = self._make_request(endpoint, method="POST", json_data=json_data)
150
154
  return response.get("data", response)
@@ -1,4 +1,4 @@
1
- from typing import Any, ClassVar, Dict, Iterator, List, Optional, Set, Union
1
+ from typing import Any, ClassVar, Dict, Generic, Iterator, List, Optional, Set, Union
2
2
 
3
3
  from vellum import ChatMessage, PromptBlock
4
4
  from vellum.client.types.prompt_parameters import PromptParameters
@@ -13,18 +13,21 @@ from vellum.workflows.nodes.bases import BaseNode
13
13
  from vellum.workflows.nodes.displayable.tool_calling_node.state import ToolCallingState
14
14
  from vellum.workflows.nodes.displayable.tool_calling_node.utils import (
15
15
  create_function_node,
16
+ create_mcp_tool_node,
16
17
  create_tool_router_node,
17
18
  get_function_name,
19
+ get_mcp_tool_name,
18
20
  hydrate_mcp_tool_definitions,
19
21
  )
20
22
  from vellum.workflows.outputs.base import BaseOutput, BaseOutputs
21
23
  from vellum.workflows.state.context import WorkflowContext
22
- from vellum.workflows.types.core import EntityInputsInterface, Tool, ToolSource
24
+ from vellum.workflows.types.core import EntityInputsInterface, Tool
23
25
  from vellum.workflows.types.definition import MCPServer
26
+ from vellum.workflows.types.generics import StateType
24
27
  from vellum.workflows.workflows.event_filters import all_workflow_event_filter
25
28
 
26
29
 
27
- class ToolCallingNode(BaseNode):
30
+ class ToolCallingNode(BaseNode[StateType], Generic[StateType]):
28
31
  """
29
32
  A Node that dynamically invokes the provided functions to the underlying Prompt
30
33
 
@@ -32,7 +35,6 @@ class ToolCallingNode(BaseNode):
32
35
  ml_model: str - The model to use for tool calling (e.g., "gpt-4o-mini")
33
36
  blocks: List[PromptBlock] - The prompt blocks to use (same format as InlinePromptNode)
34
37
  functions: List[Tool] - The functions that can be called
35
- tool_sources: List[ToolSource] - The tool sources that can be called
36
38
  prompt_inputs: Optional[EntityInputsInterface] - Mapping of input variable names to values
37
39
  parameters: PromptParameters - The parameters for the Prompt
38
40
  max_prompt_iterations: Optional[int] - Maximum number of prompt iterations before stopping
@@ -41,7 +43,6 @@ class ToolCallingNode(BaseNode):
41
43
  ml_model: ClassVar[str] = "gpt-4o-mini"
42
44
  blocks: ClassVar[List[Union[PromptBlock, Dict[str, Any]]]] = []
43
45
  functions: ClassVar[List[Tool]] = []
44
- tool_sources: ClassVar[List[ToolSource]] = []
45
46
  prompt_inputs: ClassVar[Optional[EntityInputsInterface]] = None
46
47
  parameters: PromptParameters = DEFAULT_PROMPT_PARAMETERS
47
48
  max_prompt_iterations: ClassVar[Optional[int]] = 5
@@ -138,7 +139,6 @@ class ToolCallingNode(BaseNode):
138
139
  ml_model=self.ml_model,
139
140
  blocks=self.blocks,
140
141
  functions=self.functions,
141
- tool_sources=self.tool_sources,
142
142
  prompt_inputs=self.prompt_inputs,
143
143
  parameters=self.parameters,
144
144
  max_prompt_iterations=self.max_prompt_iterations,
@@ -146,23 +146,22 @@ class ToolCallingNode(BaseNode):
146
146
 
147
147
  self._function_nodes = {}
148
148
  for function in self.functions:
149
- function_name = get_function_name(function)
150
-
151
- self._function_nodes[function_name] = create_function_node(
152
- function=function,
153
- tool_router_node=self.tool_router_node,
154
- )
155
-
156
- for tool_source in self.tool_sources:
157
- if isinstance(tool_source, MCPServer):
158
- tool_definitions = hydrate_mcp_tool_definitions(tool_source)
149
+ if isinstance(function, MCPServer):
150
+ tool_definitions = hydrate_mcp_tool_definitions(function)
159
151
  for tool_definition in tool_definitions:
160
- function_name = get_function_name(tool_definition)
152
+ function_name = get_mcp_tool_name(tool_definition)
161
153
 
162
- self._function_nodes[function_name] = create_function_node(
163
- function=tool_definition,
154
+ self._function_nodes[function_name] = create_mcp_tool_node(
155
+ tool_def=tool_definition,
164
156
  tool_router_node=self.tool_router_node,
165
157
  )
158
+ else:
159
+ function_name = get_function_name(function)
160
+
161
+ self._function_nodes[function_name] = create_function_node(
162
+ function=function,
163
+ tool_router_node=self.tool_router_node,
164
+ )
166
165
 
167
166
  graph_set = set()
168
167
 
@@ -125,3 +125,46 @@ class TestComposioCoreService:
125
125
  timeout=30,
126
126
  )
127
127
  assert result == {"items": [], "total": 0}
128
+
129
+ def test_execute_tool_with_user_id(self, composio_service, mock_requests, mock_tool_execution_response):
130
+ """Test executing a tool with user_id parameter"""
131
+ # GIVEN a user_id and tool arguments
132
+ user_id = "test_user_123"
133
+ tool_args = {"param1": "value1"}
134
+ mock_response = Mock()
135
+ mock_response.json.return_value = mock_tool_execution_response
136
+ mock_response.raise_for_status.return_value = None
137
+ mock_requests.post.return_value = mock_response
138
+
139
+ # WHEN we execute a tool with user_id
140
+ result = composio_service.execute_tool("TEST_TOOL", tool_args, user_id=user_id)
141
+
142
+ # THEN the user_id should be included in the request payload
143
+ mock_requests.post.assert_called_once_with(
144
+ "https://backend.composio.dev/api/v3/tools/execute/TEST_TOOL",
145
+ headers={"x-api-key": "test-key", "Content-Type": "application/json"},
146
+ json={"arguments": tool_args, "user_id": user_id},
147
+ timeout=30,
148
+ )
149
+ assert result == {"items": [], "total": 0}
150
+
151
+ def test_execute_tool_without_user_id(self, composio_service, mock_requests, mock_tool_execution_response):
152
+ """Test executing a tool without user_id parameter maintains backward compatibility"""
153
+ # GIVEN tool arguments without user_id
154
+ tool_args = {"param1": "value1"}
155
+ mock_response = Mock()
156
+ mock_response.json.return_value = mock_tool_execution_response
157
+ mock_response.raise_for_status.return_value = None
158
+ mock_requests.post.return_value = mock_response
159
+
160
+ # WHEN we execute a tool without user_id
161
+ result = composio_service.execute_tool("TEST_TOOL", tool_args)
162
+
163
+ # THEN the user_id should NOT be included in the request payload
164
+ mock_requests.post.assert_called_once_with(
165
+ "https://backend.composio.dev/api/v3/tools/execute/TEST_TOOL",
166
+ headers={"x-api-key": "test-key", "Content-Type": "application/json"},
167
+ json={"arguments": tool_args},
168
+ timeout=30,
169
+ )
170
+ assert result == {"items": [], "total": 0}
@@ -39,7 +39,6 @@ def test_port_condition_match_function_name():
39
39
  ml_model="test-model",
40
40
  blocks=[],
41
41
  functions=[first_function, second_function],
42
- tool_sources=[],
43
42
  prompt_inputs=None,
44
43
  parameters=DEFAULT_PROMPT_PARAMETERS,
45
44
  )
@@ -98,7 +97,6 @@ def test_tool_calling_node_inline_workflow_context():
98
97
  ml_model="test-model",
99
98
  blocks=[],
100
99
  functions=[MyWorkflow],
101
- tool_sources=[],
102
100
  prompt_inputs=None,
103
101
  parameters=DEFAULT_PROMPT_PARAMETERS,
104
102
  )
@@ -213,3 +211,25 @@ def test_tool_calling_node_with_user_provided_chat_history_block(vellum_adhoc_pr
213
211
  ]
214
212
  assert len(chat_history_inputs) == 1
215
213
  assert chat_history_inputs[0].value == [ChatMessage(role="USER", text="Hello from user")]
214
+
215
+
216
+ def test_tool_calling_node_with_generic_type_parameter():
217
+ # GIVEN a custom state class
218
+ class State(BaseState):
219
+ pass
220
+
221
+ # AND a ToolCallingNode that uses the generic type parameter
222
+ class TestToolCallingNode(ToolCallingNode[State]):
223
+ ml_model = "gpt-4o-mini"
224
+ blocks = []
225
+ functions = [first_function]
226
+ max_prompt_iterations = 1
227
+
228
+ # WHEN we create an instance of the node
229
+ state = State()
230
+ node = TestToolCallingNode(state=state)
231
+
232
+ # THEN the node should be created successfully
233
+ assert node is not None
234
+ assert isinstance(node, TestToolCallingNode)
235
+ assert node.state == state
@@ -12,7 +12,11 @@ from vellum.prompts.constants import DEFAULT_PROMPT_PARAMETERS
12
12
  from vellum.workflows import BaseWorkflow
13
13
  from vellum.workflows.inputs.base import BaseInputs
14
14
  from vellum.workflows.nodes.bases import BaseNode
15
- from vellum.workflows.nodes.displayable.tool_calling_node.utils import create_tool_router_node, get_function_name
15
+ from vellum.workflows.nodes.displayable.tool_calling_node.utils import (
16
+ create_tool_router_node,
17
+ get_function_name,
18
+ get_mcp_tool_name,
19
+ )
16
20
  from vellum.workflows.outputs.base import BaseOutputs
17
21
  from vellum.workflows.state.base import BaseState
18
22
  from vellum.workflows.types.definition import ComposioToolDefinition, DeploymentDefinition, MCPServer, MCPToolDefinition
@@ -77,7 +81,7 @@ def test_get_function_name_mcp_tool_definition():
77
81
  parameters={"repository_name": "string", "description": "string"},
78
82
  )
79
83
 
80
- result = get_function_name(mcp_tool)
84
+ result = get_mcp_tool_name(mcp_tool)
81
85
 
82
86
  assert result == "github__create_repository"
83
87
 
@@ -93,7 +97,7 @@ def test_get_function_name_composio_tool_definition_various_toolkits(
93
97
  toolkit: str, action: str, description: str, expected_result: str
94
98
  ):
95
99
  """Test ComposioToolDefinition function name generation with various toolkits."""
96
- composio_tool = ComposioToolDefinition(toolkit=toolkit, action=action, description=description)
100
+ composio_tool = ComposioToolDefinition(toolkit=toolkit, action=action, description=description, user_id=None)
97
101
 
98
102
  result = get_function_name(composio_tool)
99
103
 
@@ -106,7 +110,6 @@ def test_create_tool_router_node_max_prompt_iterations(vellum_adhoc_prompt_clien
106
110
  ml_model="gpt-4o-mini",
107
111
  blocks=[],
108
112
  functions=[],
109
- tool_sources=[],
110
113
  prompt_inputs=None,
111
114
  parameters=DEFAULT_PROMPT_PARAMETERS,
112
115
  max_prompt_iterations=None,
@@ -166,7 +169,6 @@ def test_create_tool_router_node_chat_history_block_dict(vellum_adhoc_prompt_cli
166
169
  ml_model="gpt-4o-mini",
167
170
  blocks=blocks, # type: ignore
168
171
  functions=[],
169
- tool_sources=[],
170
172
  prompt_inputs=None,
171
173
  parameters=DEFAULT_PROMPT_PARAMETERS,
172
174
  )
@@ -28,7 +28,7 @@ from vellum.workflows.ports.port import Port
28
28
  from vellum.workflows.references.lazy import LazyReference
29
29
  from vellum.workflows.state import BaseState
30
30
  from vellum.workflows.state.encoder import DefaultStateEncoder
31
- from vellum.workflows.types.core import EntityInputsInterface, MergeBehavior, Tool, ToolSource
31
+ from vellum.workflows.types.core import EntityInputsInterface, MergeBehavior, Tool, ToolBase
32
32
  from vellum.workflows.types.definition import ComposioToolDefinition, DeploymentDefinition, MCPServer, MCPToolDefinition
33
33
  from vellum.workflows.types.generics import is_workflow_class
34
34
 
@@ -184,7 +184,12 @@ class ComposioNode(BaseNode[ToolCallingState], FunctionCallNodeMixin):
184
184
  try:
185
185
  # Execute using ComposioService
186
186
  composio_service = ComposioService()
187
- result = composio_service.execute_tool(tool_name=self.composio_tool.action, arguments=arguments)
187
+ if self.composio_tool.user_id is not None:
188
+ result = composio_service.execute_tool(
189
+ tool_name=self.composio_tool.action, arguments=arguments, user_id=self.composio_tool.user_id
190
+ )
191
+ else:
192
+ result = composio_service.execute_tool(tool_name=self.composio_tool.action, arguments=arguments)
188
193
  except Exception as e:
189
194
  raise NodeException(
190
195
  message=f"Error executing Composio tool '{self.composio_tool.action}': {str(e)}",
@@ -249,6 +254,7 @@ def _hydrate_composio_tool_definition(tool_def: ComposioToolDefinition) -> Compo
249
254
  parameters=tool_details.get("input_parameters", tool_def.parameters),
250
255
  version=tool_details.get("version", tool_def.version),
251
256
  tags=tool_details.get("tags", tool_def.tags),
257
+ user_id=tool_def.user_id,
252
258
  )
253
259
 
254
260
  except Exception as e:
@@ -281,12 +287,11 @@ def create_tool_router_node(
281
287
  ml_model: str,
282
288
  blocks: List[Union[PromptBlock, Dict[str, Any]]],
283
289
  functions: List[Tool],
284
- tool_sources: List[ToolSource],
285
290
  prompt_inputs: Optional[EntityInputsInterface],
286
291
  parameters: PromptParameters,
287
292
  max_prompt_iterations: Optional[int] = None,
288
293
  ) -> Type[ToolRouterNode]:
289
- if functions and len(functions) > 0 or tool_sources and len(tool_sources) > 0:
294
+ if functions and len(functions) > 0:
290
295
  # Create dynamic ports and convert functions in a single loop
291
296
  Ports = type("Ports", (), {})
292
297
  prompt_functions: List[Union[Tool, FunctionDefinition]] = []
@@ -315,27 +320,28 @@ def create_tool_router_node(
315
320
  parameters=enhanced_function.parameters,
316
321
  )
317
322
  )
323
+ # Create port for this function (using original function for get_function_name)
324
+ function_name = get_function_name(function)
325
+ port = create_port_condition(function_name)
326
+ setattr(Ports, function_name, port)
327
+ elif isinstance(function, MCPServer):
328
+ tool_functions: List[MCPToolDefinition] = hydrate_mcp_tool_definitions(function)
329
+ for tool_function in tool_functions:
330
+ name = get_mcp_tool_name(tool_function)
331
+ prompt_functions.append(
332
+ FunctionDefinition(
333
+ name=name,
334
+ description=tool_function.description,
335
+ parameters=tool_function.parameters,
336
+ )
337
+ )
338
+ port = create_port_condition(name)
339
+ setattr(Ports, name, port)
318
340
  else:
319
341
  prompt_functions.append(function)
320
-
321
- # Create port for this function (using original function for get_function_name)
322
- function_name = get_function_name(function)
323
- port = create_port_condition(function_name)
324
- setattr(Ports, function_name, port)
325
-
326
- for server in tool_sources:
327
- tool_functions = hydrate_mcp_tool_definitions(server)
328
- for tool_function in tool_functions:
329
- name = get_function_name(tool_function)
330
- prompt_functions.append(
331
- FunctionDefinition(
332
- name=name,
333
- description=tool_function.description,
334
- parameters=tool_function.parameters,
335
- )
336
- )
337
- port = create_port_condition(name)
338
- setattr(Ports, name, port)
342
+ function_name = get_function_name(function)
343
+ port = create_port_condition(function_name)
344
+ setattr(Ports, function_name, port)
339
345
 
340
346
  # Add the else port for when no function conditions match
341
347
  setattr(Ports, "default", Port.on_else())
@@ -397,7 +403,7 @@ def create_tool_router_node(
397
403
 
398
404
 
399
405
  def create_function_node(
400
- function: Tool,
406
+ function: ToolBase,
401
407
  tool_router_node: Type[ToolRouterNode],
402
408
  ) -> Type[BaseNode]:
403
409
  """
@@ -438,17 +444,6 @@ def create_function_node(
438
444
  },
439
445
  )
440
446
  return node
441
- elif isinstance(function, MCPToolDefinition):
442
- node = type(
443
- f"MCPNode_{function.name}",
444
- (MCPNode,),
445
- {
446
- "mcp_tool": function,
447
- "function_call_output": tool_router_node.Outputs.results,
448
- "__module__": __name__,
449
- },
450
- )
451
- return node
452
447
  elif is_workflow_class(function):
453
448
  node = type(
454
449
  f"DynamicInlineSubworkflowNode_{function.__name__}",
@@ -474,13 +469,31 @@ def create_function_node(
474
469
  return node
475
470
 
476
471
 
477
- def get_function_name(function: Tool) -> str:
472
+ def create_mcp_tool_node(
473
+ tool_def: MCPToolDefinition,
474
+ tool_router_node: Type[ToolRouterNode],
475
+ ) -> Type[BaseNode]:
476
+ node = type(
477
+ f"MCPNode_{tool_def.name}",
478
+ (MCPNode,),
479
+ {
480
+ "mcp_tool": tool_def,
481
+ "function_call_output": tool_router_node.Outputs.results,
482
+ "__module__": __name__,
483
+ },
484
+ )
485
+ return node
486
+
487
+
488
+ def get_function_name(function: ToolBase) -> str:
478
489
  if isinstance(function, DeploymentDefinition):
479
490
  name = str(function.deployment_id or function.deployment_name)
480
491
  return name.replace("-", "")
481
492
  elif isinstance(function, ComposioToolDefinition):
482
493
  return function.name
483
- elif isinstance(function, MCPToolDefinition):
484
- return f"{function.server.name}__{function.name}"
485
494
  else:
486
495
  return snake_case(function.__name__)
496
+
497
+
498
+ def get_mcp_tool_name(tool_def: MCPToolDefinition) -> str:
499
+ return f"{tool_def.server.name}__{tool_def.name}"
@@ -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, MCPServer, MCPToolDefinition
16
+ from vellum.workflows.types.definition import ComposioToolDefinition, DeploymentDefinition, MCPServer
17
17
 
18
18
  if TYPE_CHECKING:
19
19
  from vellum.workflows.workflows.base import BaseWorkflow
@@ -51,7 +51,5 @@ class ConditionType(Enum):
51
51
 
52
52
 
53
53
  # Type alias for functions that can be called in tool calling nodes
54
- Tool = Union[Callable[..., Any], DeploymentDefinition, Type["BaseWorkflow"], ComposioToolDefinition, MCPToolDefinition]
55
-
56
- # Type alias for sources that provide tools to tool calling nodes
57
- ToolSource = Union[MCPServer]
54
+ ToolBase = Union[Callable[..., Any], DeploymentDefinition, Type["BaseWorkflow"], ComposioToolDefinition]
55
+ Tool = Union[ToolBase, MCPServer]
@@ -116,6 +116,7 @@ class ComposioToolDefinition(UniversalBaseModel):
116
116
  parameters: Optional[Dict[str, Any]] = None
117
117
  version: Optional[str] = None
118
118
  tags: Optional[List[str]] = None
119
+ user_id: Optional[str] = None
119
120
 
120
121
  @property
121
122
  def name(self) -> str:
@@ -39,7 +39,10 @@ def test_deployment_definition(deployment_value, expected_deployment_id, expecte
39
39
  def test_composio_tool_definition_creation():
40
40
  """Test that ComposioToolDefinition can be created with required fields."""
41
41
  composio_tool = ComposioToolDefinition(
42
- toolkit="GITHUB", action="GITHUB_CREATE_AN_ISSUE", description="Create a new issue in a GitHub repository"
42
+ toolkit="GITHUB",
43
+ action="GITHUB_CREATE_AN_ISSUE",
44
+ description="Create a new issue in a GitHub repository",
45
+ user_id=None,
43
46
  )
44
47
 
45
48
  assert composio_tool.toolkit == "GITHUB"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-ai
3
- Version: 1.0.9
3
+ Version: 1.0.10
4
4
  Summary:
5
5
  License: MIT
6
6
  Requires-Python: >=3.9,<4.0
@@ -94,9 +94,9 @@ 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=gonapBCyDDt3qc7U02PCuKyPS8f3YiSAZ7QD86CH1Fw,3794
98
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_serialization.py,sha256=pZJVn7oXyTuKvyLOM6pj00ulSJR2QtVd9h2VwBv4_mg,26522
99
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py,sha256=wKJlecy8kxlOslTlPHRFUb-zFBGpNq6S7eeOrswB9-I,10241
97
+ vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_composio_serialization.py,sha256=sWKSqw1B4iAEamIzbRJBfjtMCy_D54gxEu3aPSDrS_o,3819
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=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=K6kF1J_NBmrtjJs0H4OqVoVsJgY3ivrRe4FzhceFTis,2383
148
+ vellum/client/core/client_wrapper.py,sha256=PYKT4GSWVfnUCdamESx8hYo2KF9SVuaYQAHwwHxyYSI,2385
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
@@ -1536,8 +1536,9 @@ vellum/workflows/descriptors/tests/test_utils.py,sha256=HJ5DoRz0sJvViGxyZ_FtytZj
1536
1536
  vellum/workflows/descriptors/utils.py,sha256=1siECBf6AI54gwwUwkF6mP9rYsRryUGaOYBbMpQaceM,3848
1537
1537
  vellum/workflows/edges/__init__.py,sha256=wSkmAnz9xyi4vZwtDbKxwlplt2skD7n3NsxkvR_pUus,50
1538
1538
  vellum/workflows/edges/edge.py,sha256=N0SnY3gKVuxImPAdCbPMPlHJIXbkQ3fwq_LbJRvVMFc,677
1539
- vellum/workflows/emitters/__init__.py,sha256=YyOgaoLtVW8eFNEWODzCYb0HzL0PoSeNRf4diJ1Y0dk,80
1540
- vellum/workflows/emitters/base.py,sha256=D5SADKIvnbgKwIBgYm77jaqvpo1o0rz4MmuX_muRqQU,359
1539
+ vellum/workflows/emitters/__init__.py,sha256=d9QFOI3eVg6rzpSFLvrjkDYXWikf1tcp3ruTRa2Boyc,143
1540
+ vellum/workflows/emitters/base.py,sha256=Tcp13VMB-GMwEJdl-6XTPckspdOdwpMgBx22-PcQxds,892
1541
+ vellum/workflows/emitters/vellum_emitter.py,sha256=VRJgyEs6RnikwlPBUu1s7BD8flVeuM3QgTeQLUnaDuE,5051
1541
1542
  vellum/workflows/environment/__init__.py,sha256=TJz0m9dwIs6YOwCTeuN0HHsU-ecyjc1OJXx4AFy83EQ,121
1542
1543
  vellum/workflows/environment/environment.py,sha256=Ck3RPKXJvtMGx_toqYQQQF-ZwXm5ijVwJpEPTeIJ4_Q,471
1543
1544
  vellum/workflows/errors/__init__.py,sha256=tWGPu5xyAU8gRb8_bl0fL7OfU3wxQ9UH6qVwy4X4P_Q,113
@@ -1597,7 +1598,7 @@ vellum/workflows/inputs/base.py,sha256=w3owT5B3rLBmIj-v-jL2l-HD4yd3hXK9RmHVd557B
1597
1598
  vellum/workflows/inputs/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1598
1599
  vellum/workflows/inputs/tests/test_inputs.py,sha256=lioA8917mFLYq7Ml69UNkqUjcWbbxkxnpIEJ4FBaYBk,2206
1599
1600
  vellum/workflows/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1600
- vellum/workflows/integrations/composio_service.py,sha256=Wo1IKHr_CzrHFQHR_hjVcKap3JLwlV_TEbKsZcuR6Sw,5664
1601
+ vellum/workflows/integrations/composio_service.py,sha256=v1rVQXTh1rnupguj8oIM20V7bSKaJiAoJ5yjz2NeKA8,5906
1601
1602
  vellum/workflows/integrations/mcp_service.py,sha256=SaOLg76JBAiBDAMUn04mxVWmf2Btobd1kDjc8B1atng,8712
1602
1603
  vellum/workflows/logging.py,sha256=_a217XogktV4Ncz6xKFz7WfYmZAzkfVRVuC0rWob8ls,437
1603
1604
  vellum/workflows/nodes/__init__.py,sha256=aVdQVv7Y3Ro3JlqXGpxwaU2zrI06plDHD2aumH5WUIs,1157
@@ -1696,13 +1697,13 @@ vellum/workflows/nodes/displayable/tests/test_search_node_error_handling.py,sha2
1696
1697
  vellum/workflows/nodes/displayable/tests/test_search_node_wth_text_output.py,sha256=VepO5z1277c1y5N6LLIC31nnWD1aak2m5oPFplfJHHs,6935
1697
1698
  vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py,sha256=dc3EEn1sOICpr3GdS8eyeFtExaGwWWcw9eHSdkRhQJU,2584
1698
1699
  vellum/workflows/nodes/displayable/tool_calling_node/__init__.py,sha256=3n0-ysmFKsr40CVxPthc0rfJgqVJeZuUEsCmYudLVRg,117
1699
- vellum/workflows/nodes/displayable/tool_calling_node/node.py,sha256=6uFt-_woayqyvbVGNJeWoiBQs2_VBtgTTdMX-Ztwx1I,7338
1700
+ vellum/workflows/nodes/displayable/tool_calling_node/node.py,sha256=ftPf7hmPvk_rJeIoxnJGkTLex-kDW1CRuvVDUwUdMxg,7283
1700
1701
  vellum/workflows/nodes/displayable/tool_calling_node/state.py,sha256=oQg_GAtc349nPB5BL_oeDYYD7q1qSDPAqjj8iA8OoAw,215
1701
1702
  vellum/workflows/nodes/displayable/tool_calling_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1702
- vellum/workflows/nodes/displayable/tool_calling_node/tests/test_composio_service.py,sha256=UV0vZpU7-_tHcwnIq36WKwHrJXNurU4bdC3rfaw8eoU,4804
1703
- vellum/workflows/nodes/displayable/tool_calling_node/tests/test_node.py,sha256=U14GS6YwJGpIA13nq4Qvo1YBEku5ZRL627ZWnybp4hg,8029
1704
- vellum/workflows/nodes/displayable/tool_calling_node/tests/test_utils.py,sha256=3TQeLD-HlB0OHEtUcADe4jl0XGi9YTv5FL4ZupDaSJA,8361
1705
- vellum/workflows/nodes/displayable/tool_calling_node/utils.py,sha256=shFC4Vm0vyqAILWsUTDOo_Ggn0_7GHKQlgZ0TPHohWQ,19180
1703
+ vellum/workflows/nodes/displayable/tool_calling_node/tests/test_composio_service.py,sha256=y7KAqbiJHoya6N5EWv1qgz0htM_Yzz7zjAHVp78IMFo,6919
1704
+ vellum/workflows/nodes/displayable/tool_calling_node/tests/test_node.py,sha256=TPafJhCAV6oLg5kTttQw0hL56ct3a2Xatvnld6dK8CY,8628
1705
+ vellum/workflows/nodes/displayable/tool_calling_node/tests/test_utils.py,sha256=om4FztVQ33jFZK_lbusi6khOM7zgzNCHlUcEb5-r6pU,8361
1706
+ vellum/workflows/nodes/displayable/tool_calling_node/utils.py,sha256=fvy0O3YpibWUpw4aLKnk8PdwlRCJC7Z2acjryOTiuxY,19728
1706
1707
  vellum/workflows/nodes/experimental/README.md,sha256=eF6DfIL8t-HbF9-mcofOMymKrraiBHDLKTlnBa51ZiE,284
1707
1708
  vellum/workflows/nodes/experimental/__init__.py,sha256=jCQgvZEknXKfuNhGSOou4XPfrPqZ1_XBj5F0n0fgiWM,106
1708
1709
  vellum/workflows/nodes/experimental/openai_chat_completion_node/__init__.py,sha256=lsyD9laR9p7kx5-BXGH2gUTM242UhKy8SMV0SR6S2iE,90
@@ -1749,12 +1750,12 @@ vellum/workflows/tests/test_sandbox.py,sha256=JKwaluI-lODQo7Ek9sjDstjL_WTdSqUlVi
1749
1750
  vellum/workflows/tests/test_undefined.py,sha256=zMCVliCXVNLrlC6hEGyOWDnQADJ2g83yc5FIM33zuo8,353
1750
1751
  vellum/workflows/types/__init__.py,sha256=KxUTMBGzuRCfiMqzzsykOeVvrrkaZmTTo1a7SLu8gRM,68
1751
1752
  vellum/workflows/types/code_execution_node_wrappers.py,sha256=3MNIoFZKzVzNS5qFLVuDwMV17QJw72zo7NRf52yMq5A,3074
1752
- vellum/workflows/types/core.py,sha256=ybQj70QjlVNSEXsdIO8Mug9EBIDEkyFPxYv4cnn_hM0,1482
1753
- vellum/workflows/types/definition.py,sha256=pK0fAXHw7C0AFpCoM4WGe1_MD-usupF4-m6ldo5AQXY,4568
1753
+ vellum/workflows/types/core.py,sha256=TggDVs2lVya33xvu374EDhMC1b7RRlAAs0zWLaF46BA,1385
1754
+ vellum/workflows/types/definition.py,sha256=fzWfsfbXLS4sZvjOQMSDoiuSaFo4Ii2kC8AOiPade9o,4602
1754
1755
  vellum/workflows/types/generics.py,sha256=8jptbEx1fnJV0Lhj0MpCJOT6yNiEWeTOYOwrEAb5CRU,1576
1755
1756
  vellum/workflows/types/stack.py,sha256=h7NE0vXR7l9DevFBIzIAk1Zh59K-kECQtDTKOUunwMY,1314
1756
1757
  vellum/workflows/types/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1757
- vellum/workflows/types/tests/test_definition.py,sha256=RsDoicu8A1dqJOGa-Ok866K8lnzn5L0Hez3lQijYD4c,5011
1758
+ vellum/workflows/types/tests/test_definition.py,sha256=4Qqlf7GpoG9MrLuMCkcRzEZMgwrr7du4DROcB1xfv0E,5050
1758
1759
  vellum/workflows/types/tests/test_utils.py,sha256=UnZog59tR577mVwqZRqqWn2fScoOU1H6up0EzS8zYhw,2536
1759
1760
  vellum/workflows/types/utils.py,sha256=mTctHITBybpt4855x32oCKALBEcMNLn-9cCmfEKgJHQ,6498
1760
1761
  vellum/workflows/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -1775,8 +1776,8 @@ vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnad
1775
1776
  vellum/workflows/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1776
1777
  vellum/workflows/workflows/tests/test_base_workflow.py,sha256=ptMntHzVyy8ZuzNgeTuk7hREgKQ5UBdgq8VJFSGaW4Y,20832
1777
1778
  vellum/workflows/workflows/tests/test_context.py,sha256=VJBUcyWVtMa_lE5KxdhgMu0WYNYnUQUDvTF7qm89hJ0,2333
1778
- vellum_ai-1.0.9.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1779
- vellum_ai-1.0.9.dist-info/METADATA,sha256=b1ciqHMubElgtkn0rm_nW7vFBfZ2KqdOVuogZ9ICOcE,5554
1780
- vellum_ai-1.0.9.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1781
- vellum_ai-1.0.9.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1782
- vellum_ai-1.0.9.dist-info/RECORD,,
1779
+ vellum_ai-1.0.10.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1780
+ vellum_ai-1.0.10.dist-info/METADATA,sha256=j-b34bQSbPMhl5YG7dLuwC4_EZYCtRrdhoTxjXYJ3-U,5555
1781
+ vellum_ai-1.0.10.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1782
+ vellum_ai-1.0.10.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1783
+ vellum_ai-1.0.10.dist-info/RECORD,,
@@ -57,6 +57,7 @@ def test_serialize_workflow():
57
57
  "parameters": None,
58
58
  "version": None,
59
59
  "tags": None,
60
+ "user_id": None,
60
61
  }
61
62
 
62
63
  # AND the rest of the node structure should be correct
@@ -407,11 +407,6 @@ def test_serialize_workflow():
407
407
  ],
408
408
  },
409
409
  },
410
- {
411
- "id": "89084be2-0853-4483-9031-c24bdf872c32",
412
- "name": "tool_sources",
413
- "value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": []}},
414
- },
415
410
  {
416
411
  "id": "229cd1ca-dc2f-4586-b933-c4d4966f7bd1",
417
412
  "name": "parameters",
@@ -175,11 +175,6 @@ def test_serialize_workflow():
175
175
  ],
176
176
  },
177
177
  },
178
- {
179
- "id": "89084be2-0853-4483-9031-c24bdf872c32",
180
- "name": "tool_sources",
181
- "value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": []}},
182
- },
183
178
  {
184
179
  "id": "229cd1ca-dc2f-4586-b933-c4d4966f7bd1",
185
180
  "name": "parameters",