vellum-ai 1.0.3__py3-none-any.whl → 1.0.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -25,10 +25,10 @@ class BaseClientWrapper:
25
25
 
26
26
  def get_headers(self) -> typing.Dict[str, str]:
27
27
  headers: typing.Dict[str, str] = {
28
- "User-Agent": "vellum-ai/1.0.3",
28
+ "User-Agent": "vellum-ai/1.0.4",
29
29
  "X-Fern-Language": "Python",
30
30
  "X-Fern-SDK-Name": "vellum-ai",
31
- "X-Fern-SDK-Version": "1.0.3",
31
+ "X-Fern-SDK-Version": "1.0.4",
32
32
  }
33
33
  if self._api_version is not None:
34
34
  headers["X-API-Version"] = self._api_version
@@ -13,6 +13,7 @@ class OrganizationLimitConfig(UniversalBaseModel):
13
13
  prompt_executions_quota: typing.Optional[Quota] = None
14
14
  workflow_executions_quota: typing.Optional[Quota] = None
15
15
  workflow_runtime_seconds_quota: typing.Optional[Quota] = None
16
+ max_workflow_runtime_seconds: typing.Optional[int] = None
16
17
 
17
18
  if IS_PYDANTIC_V2:
18
19
  model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
@@ -105,7 +105,11 @@ def compile_prompt_blocks(
105
105
  cache_config=block.cache_config,
106
106
  )
107
107
  )
108
- elif compiled_input == "JSON":
108
+ elif compiled_input.type == "JSON":
109
+ # Skip empty JSON arrays when there are chat message blocks present
110
+ if compiled_input.value == [] and any(block.block_type == "CHAT_MESSAGE" for block in compiled_blocks):
111
+ continue
112
+
109
113
  compiled_blocks.append(
110
114
  CompiledValuePromptBlock(
111
115
  content=JsonVellumValue(value=compiled_input.value),
@@ -10,7 +10,10 @@ from vellum import (
10
10
  VariablePromptBlock,
11
11
  VellumVariable,
12
12
  )
13
+ from vellum.client.types.json_vellum_value import JsonVellumValue
13
14
  from vellum.client.types.number_input import NumberInput
15
+ from vellum.client.types.prompt_block import PromptBlock
16
+ from vellum.client.types.prompt_request_json_input import PromptRequestJsonInput
14
17
  from vellum.prompts.blocks.compilation import compile_prompt_blocks
15
18
  from vellum.prompts.blocks.types import CompiledChatMessagePromptBlock, CompiledValuePromptBlock
16
19
 
@@ -146,3 +149,64 @@ def test_compile_prompt_blocks__happy(blocks, inputs, input_variables, expected)
146
149
  actual = compile_prompt_blocks(blocks=blocks, inputs=inputs, input_variables=input_variables)
147
150
 
148
151
  assert actual == expected
152
+
153
+
154
+ def test_compile_prompt_blocks__empty_json_variable_with_chat_message_blocks():
155
+ """Test JSON variable handling logic, specifically the empty array skipping behavior."""
156
+
157
+ # GIVEN empty array with chat message blocks
158
+ blocks_with_chat: list[PromptBlock] = [
159
+ ChatMessagePromptBlock(
160
+ chat_role="USER",
161
+ blocks=[RichTextPromptBlock(blocks=[PlainTextPromptBlock(text="User message")])],
162
+ ),
163
+ VariablePromptBlock(input_variable="json_data"),
164
+ ]
165
+
166
+ inputs_with_empty_json = [PromptRequestJsonInput(key="json_data", value=[], type="JSON")]
167
+
168
+ input_variables = [VellumVariable(id="901ec2d6-430c-4341-b963-ca689006f5cc", type="JSON", key="json_data")]
169
+
170
+ # THEN the empty JSON array should be skipped when there are chat message blocks
171
+ expected_with_chat = [
172
+ CompiledChatMessagePromptBlock(
173
+ role="USER",
174
+ blocks=[CompiledValuePromptBlock(content=StringVellumValue(value="User message"))],
175
+ ),
176
+ ]
177
+
178
+ actual = compile_prompt_blocks(
179
+ blocks=blocks_with_chat, inputs=inputs_with_empty_json, input_variables=input_variables
180
+ )
181
+ assert actual == expected_with_chat
182
+
183
+
184
+ def test_compile_prompt_blocks__non_empty_json_variable_with_chat_message_blocks():
185
+ """Test that non-empty JSON variables are included even when there are chat message blocks."""
186
+
187
+ # GIVEN non-empty JSON with chat message blocks
188
+ blocks_with_chat: list[PromptBlock] = [
189
+ ChatMessagePromptBlock(
190
+ chat_role="USER",
191
+ blocks=[RichTextPromptBlock(blocks=[PlainTextPromptBlock(text="User message")])],
192
+ ),
193
+ VariablePromptBlock(input_variable="json_data"),
194
+ ]
195
+
196
+ inputs_with_non_empty_json = [PromptRequestJsonInput(key="json_data", value={"key": "value"}, type="JSON")]
197
+
198
+ input_variables = [VellumVariable(id="901ec2d6-430c-4341-b963-ca689006f5cc", type="JSON", key="json_data")]
199
+
200
+ # THEN the non-empty JSON should be included
201
+ expected_with_non_empty = [
202
+ CompiledChatMessagePromptBlock(
203
+ role="USER",
204
+ blocks=[CompiledValuePromptBlock(content=StringVellumValue(value="User message"))],
205
+ ),
206
+ CompiledValuePromptBlock(content=JsonVellumValue(value={"key": "value"})),
207
+ ]
208
+
209
+ actual = compile_prompt_blocks(
210
+ blocks=blocks_with_chat, inputs=inputs_with_non_empty_json, input_variables=input_variables
211
+ )
212
+ assert actual == expected_with_non_empty
@@ -6,6 +6,7 @@ if TYPE_CHECKING:
6
6
  from vellum.workflows.expressions.begins_with import BeginsWithExpression
7
7
  from vellum.workflows.expressions.between import BetweenExpression
8
8
  from vellum.workflows.expressions.coalesce_expression import CoalesceExpression
9
+ from vellum.workflows.expressions.concat import ConcatExpression
9
10
  from vellum.workflows.expressions.contains import ContainsExpression
10
11
  from vellum.workflows.expressions.does_not_begin_with import DoesNotBeginWithExpression
11
12
  from vellum.workflows.expressions.does_not_contain import DoesNotContainExpression
@@ -364,3 +365,14 @@ class BaseDescriptor(Generic[_T]):
364
365
  from vellum.workflows.expressions.is_error import IsErrorExpression
365
366
 
366
367
  return IsErrorExpression(expression=self)
368
+
369
+ @overload
370
+ def concat(self, other: "BaseDescriptor[_O]") -> "ConcatExpression[_T, _O]": ...
371
+
372
+ @overload
373
+ def concat(self, other: _O) -> "ConcatExpression[_T, _O]": ...
374
+
375
+ def concat(self, other: "Union[BaseDescriptor[_O], _O]") -> "ConcatExpression[_T, _O]":
376
+ from vellum.workflows.expressions.concat import ConcatExpression
377
+
378
+ return ConcatExpression(lhs=self, rhs=other)
@@ -0,0 +1,32 @@
1
+ from typing import Generic, TypeVar, Union
2
+
3
+ from vellum.workflows.descriptors.base import BaseDescriptor
4
+ from vellum.workflows.descriptors.exceptions import InvalidExpressionException
5
+ from vellum.workflows.descriptors.utils import resolve_value
6
+ from vellum.workflows.state.base import BaseState
7
+
8
+ LHS = TypeVar("LHS")
9
+ RHS = TypeVar("RHS")
10
+
11
+
12
+ class ConcatExpression(BaseDescriptor[list], Generic[LHS, RHS]):
13
+ def __init__(
14
+ self,
15
+ *,
16
+ lhs: Union[BaseDescriptor[LHS], LHS],
17
+ rhs: Union[BaseDescriptor[RHS], RHS],
18
+ ) -> None:
19
+ super().__init__(name=f"{lhs} + {rhs}", types=(list,))
20
+ self._lhs = lhs
21
+ self._rhs = rhs
22
+
23
+ def resolve(self, state: "BaseState") -> list:
24
+ lval = resolve_value(self._lhs, state)
25
+ rval = resolve_value(self._rhs, state)
26
+
27
+ if not isinstance(lval, list):
28
+ raise InvalidExpressionException(f"Expected LHS to be a list, got {type(lval)}")
29
+ if not isinstance(rval, list):
30
+ raise InvalidExpressionException(f"Expected RHS to be a list, got {type(rval)}")
31
+
32
+ return lval + rval
@@ -0,0 +1,53 @@
1
+ import pytest
2
+
3
+ from vellum.workflows.descriptors.exceptions import InvalidExpressionException
4
+ from vellum.workflows.references.constant import ConstantValueReference
5
+ from vellum.workflows.state.base import BaseState
6
+
7
+
8
+ class TestState(BaseState):
9
+ pass
10
+
11
+
12
+ def test_concat_expression_happy_path():
13
+ # GIVEN two lists
14
+ state = TestState()
15
+ lhs_ref = ConstantValueReference([1, 2, 3])
16
+ rhs_ref = ConstantValueReference([4, 5, 6])
17
+ concat_expr = lhs_ref.concat(rhs_ref)
18
+
19
+ # WHEN we resolve the expression
20
+ result = concat_expr.resolve(state)
21
+
22
+ # THEN the lists should be concatenated
23
+ assert result == [1, 2, 3, 4, 5, 6]
24
+
25
+
26
+ def test_concat_expression_lhs_fail():
27
+ # GIVEN a non-list lhs and a list rhs
28
+ state = TestState()
29
+ lhs_ref = ConstantValueReference(0)
30
+ rhs_ref = ConstantValueReference([4, 5, 6])
31
+ concat_expr = lhs_ref.concat(rhs_ref)
32
+
33
+ # WHEN we attempt to resolve the expression
34
+ with pytest.raises(InvalidExpressionException) as exc_info:
35
+ concat_expr.resolve(state)
36
+
37
+ # THEN an exception should be raised
38
+ assert "Expected LHS to be a list, got <class 'int'>" in str(exc_info.value)
39
+
40
+
41
+ def test_concat_expression_rhs_fail():
42
+ # GIVEN a list lhs and a non-list rhs
43
+ state = TestState()
44
+ lhs_ref = ConstantValueReference([1, 2, 3])
45
+ rhs_ref = ConstantValueReference(False)
46
+ concat_expr = lhs_ref.concat(rhs_ref)
47
+
48
+ # WHEN we attempt to resolve the expression
49
+ with pytest.raises(InvalidExpressionException) as exc_info:
50
+ concat_expr.resolve(state)
51
+
52
+ # THEN an exception should be raised
53
+ assert "Expected RHS to be a list, got <class 'bool'>" in str(exc_info.value)
@@ -64,8 +64,7 @@ class InlinePromptNode(BaseInlinePromptNode[StateType]):
64
64
  elif output.type == "FUNCTION_CALL":
65
65
  string_outputs.append(output.value.model_dump_json(indent=4))
66
66
  elif output.type == "THINKING":
67
- if output.value.type == "STRING":
68
- string_outputs.append(output.value.value)
67
+ continue
69
68
  else:
70
69
  string_outputs.append(output.value.message)
71
70
 
@@ -66,8 +66,7 @@ class PromptDeploymentNode(BasePromptDeploymentNode[StateType]):
66
66
  elif output.type == "FUNCTION_CALL":
67
67
  string_outputs.append(output.value.model_dump_json(indent=4))
68
68
  elif output.type == "THINKING":
69
- if output.value.type == "STRING":
70
- string_outputs.append(output.value.value)
69
+ continue
71
70
  else:
72
71
  string_outputs.append(output.value.message)
73
72
 
@@ -0,0 +1,83 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any, Dict, List
3
+
4
+ from composio import Action, Composio
5
+ from composio_client import Composio as ComposioClient
6
+
7
+
8
+ @dataclass
9
+ class ConnectionInfo:
10
+ """Information about a user's authorized connection"""
11
+
12
+ connection_id: str
13
+ integration_name: str
14
+ created_at: str
15
+ updated_at: str
16
+ status: str = "ACTIVE" # TODO: Use enum if we end up supporting integrations that the user has not yet connected to
17
+
18
+
19
+ class ComposioAccountService:
20
+ """Manages user authorized connections using composio-client"""
21
+
22
+ def __init__(self, api_key: str):
23
+ self.client = ComposioClient(api_key=api_key)
24
+
25
+ def get_user_connections(self) -> List[ConnectionInfo]:
26
+ """Get all authorized connections for the user"""
27
+ response = self.client.connected_accounts.list()
28
+
29
+ return [
30
+ ConnectionInfo(
31
+ connection_id=item.id,
32
+ integration_name=item.toolkit.slug,
33
+ status=item.status,
34
+ created_at=item.created_at,
35
+ updated_at=item.updated_at,
36
+ )
37
+ for item in response.items
38
+ ]
39
+
40
+
41
+ class ComposioCoreService:
42
+ """Handles tool execution using composio-core"""
43
+
44
+ def __init__(self, api_key: str):
45
+ self.client = Composio(api_key=api_key)
46
+
47
+ def execute_tool(self, tool_name: str, arguments: Dict[str, Any]) -> Any:
48
+ """Execute a tool using composio-core
49
+
50
+ Args:
51
+ tool_name: The name of the tool to execute (e.g., "HACKERNEWS_GET_USER")
52
+ arguments: Dictionary of arguments to pass to the tool
53
+
54
+ Returns:
55
+ The result of the tool execution
56
+ """
57
+ # Convert tool name string to Action enum
58
+ action = getattr(Action, tool_name)
59
+ return self.client.actions.execute(action, params=arguments)
60
+
61
+
62
+ class ComposioService:
63
+ """Unified interface for Composio operations"""
64
+
65
+ def __init__(self, api_key: str):
66
+ self.accounts = ComposioAccountService(api_key)
67
+ self.core = ComposioCoreService(api_key)
68
+
69
+ def get_user_connections(self) -> List[ConnectionInfo]:
70
+ """Get user's authorized connections"""
71
+ return self.accounts.get_user_connections()
72
+
73
+ def execute_tool(self, tool_name: str, arguments: Dict[str, Any]) -> Any:
74
+ """Execute a tool using composio-core
75
+
76
+ Args:
77
+ tool_name: The name of the tool to execute (e.g., "HACKERNEWS_GET_USER")
78
+ arguments: Dictionary of arguments to pass to the tool
79
+
80
+ Returns:
81
+ The result of the tool execution
82
+ """
83
+ return self.core.execute_tool(tool_name, arguments)
@@ -0,0 +1,122 @@
1
+ import pytest
2
+ from unittest.mock import Mock, patch
3
+
4
+ from vellum.workflows.nodes.displayable.tool_calling_node.composio_service import ComposioService, ConnectionInfo
5
+
6
+
7
+ @pytest.fixture
8
+ def mock_composio_client():
9
+ """Mock the Composio client completely"""
10
+ with patch("vellum.workflows.nodes.displayable.tool_calling_node.composio_service.ComposioClient") as mock_composio:
11
+ yield mock_composio.return_value
12
+
13
+
14
+ @pytest.fixture
15
+ def mock_connected_accounts_response():
16
+ """Mock response for connected accounts"""
17
+ mock_item1 = Mock()
18
+ mock_item1.id = "conn-123"
19
+ mock_item1.toolkit.slug = "github"
20
+ mock_item1.status = "ACTIVE"
21
+ mock_item1.created_at = "2023-01-01T00:00:00Z"
22
+ mock_item1.updated_at = "2023-01-15T10:30:00Z"
23
+
24
+ mock_item2 = Mock()
25
+ mock_item2.id = "conn-456"
26
+ mock_item2.toolkit.slug = "slack"
27
+ mock_item2.status = "ACTIVE"
28
+ mock_item2.created_at = "2023-01-01T00:00:00Z"
29
+ mock_item2.updated_at = "2023-01-10T08:00:00Z"
30
+
31
+ mock_response = Mock()
32
+ mock_response.items = [mock_item1, mock_item2]
33
+
34
+ return mock_response
35
+
36
+
37
+ @pytest.fixture
38
+ def mock_composio_core_client():
39
+ """Mock the composio-core Composio client"""
40
+ with patch("vellum.workflows.nodes.displayable.tool_calling_node.composio_service.Composio") as mock_composio:
41
+ yield mock_composio.return_value
42
+
43
+
44
+ @pytest.fixture
45
+ def mock_action():
46
+ """Mock the Action class and specific actions"""
47
+ with patch("vellum.workflows.nodes.displayable.tool_calling_node.composio_service.Action") as mock_action_class:
48
+ # Mock a specific action
49
+ mock_hackernews_action = Mock()
50
+ mock_action_class.HACKERNEWS_GET_USER = mock_hackernews_action
51
+ mock_action_class.GITHUB_GET_USER = Mock()
52
+ yield mock_action_class
53
+
54
+
55
+ @pytest.fixture
56
+ def composio_service(mock_composio_client, mock_composio_core_client):
57
+ """Create ComposioService with mocked clients"""
58
+ return ComposioService(api_key="test-key")
59
+
60
+
61
+ class TestComposioAccountService:
62
+ """Test suite for ComposioAccountService"""
63
+
64
+ def test_get_user_connections_success(
65
+ self, composio_service, mock_composio_client, mock_connected_accounts_response
66
+ ):
67
+ """Test successful retrieval of user connections"""
68
+ # GIVEN the Composio client returns a valid response with two connections
69
+ mock_composio_client.connected_accounts.list.return_value = mock_connected_accounts_response
70
+
71
+ # WHEN we request user connections
72
+ result = composio_service.get_user_connections()
73
+
74
+ # THEN we get two properly formatted ConnectionInfo objects
75
+ assert len(result) == 2
76
+ assert isinstance(result[0], ConnectionInfo)
77
+ assert result[0].connection_id == "conn-123"
78
+ assert result[0].integration_name == "github"
79
+ assert result[0].status == "ACTIVE"
80
+ assert result[0].created_at == "2023-01-01T00:00:00Z"
81
+ assert result[0].updated_at == "2023-01-15T10:30:00Z"
82
+
83
+ assert result[1].connection_id == "conn-456"
84
+ assert result[1].integration_name == "slack"
85
+ assert result[1].status == "ACTIVE"
86
+ assert result[1].created_at == "2023-01-01T00:00:00Z"
87
+ assert result[1].updated_at == "2023-01-10T08:00:00Z"
88
+
89
+ mock_composio_client.connected_accounts.list.assert_called_once()
90
+
91
+ def test_get_user_connections_empty_response(self, composio_service, mock_composio_client):
92
+ """Test handling of empty connections response"""
93
+ # GIVEN the Composio client returns an empty response
94
+ mock_response = Mock()
95
+ mock_response.items = []
96
+ mock_composio_client.connected_accounts.list.return_value = mock_response
97
+
98
+ # WHEN we request user connections
99
+ result = composio_service.get_user_connections()
100
+
101
+ # THEN we get an empty list
102
+ assert result == []
103
+
104
+
105
+ class TestComposioCoreService:
106
+ """Test suite for ComposioCoreService"""
107
+
108
+ def test_execute_tool_success(self, composio_service, mock_composio_core_client, mock_action):
109
+ """Test executing a tool with complex argument structure"""
110
+ # GIVEN complex arguments and a mock response
111
+ complex_args = {"filters": {"status": "active"}, "limit": 10, "sort": "created_at"}
112
+ expected_result = {"items": [], "total": 0}
113
+ mock_composio_core_client.actions.execute.return_value = expected_result
114
+
115
+ # WHEN we execute a tool with complex arguments
116
+ result = composio_service.execute_tool("HACKERNEWS_GET_USER", complex_args)
117
+
118
+ # THEN the arguments are passed through correctly
119
+ mock_composio_core_client.actions.execute.assert_called_once_with(
120
+ mock_action.HACKERNEWS_GET_USER, params=complex_args
121
+ )
122
+ assert result == expected_result
@@ -12,6 +12,7 @@ from vellum.client.types.string_chat_message_content import StringChatMessageCon
12
12
  from vellum.client.types.variable_prompt_block import VariablePromptBlock
13
13
  from vellum.workflows.errors.types import WorkflowErrorCode
14
14
  from vellum.workflows.exceptions import NodeException
15
+ from vellum.workflows.expressions.concat import ConcatExpression
15
16
  from vellum.workflows.inputs import BaseInputs
16
17
  from vellum.workflows.nodes.bases import BaseNode
17
18
  from vellum.workflows.nodes.core.inline_subworkflow_node.node import InlineSubworkflowNode
@@ -24,7 +25,7 @@ from vellum.workflows.references.lazy import LazyReference
24
25
  from vellum.workflows.state import BaseState
25
26
  from vellum.workflows.state.encoder import DefaultStateEncoder
26
27
  from vellum.workflows.types.core import EntityInputsInterface, MergeBehavior, Tool
27
- from vellum.workflows.types.definition import DeploymentDefinition
28
+ from vellum.workflows.types.definition import ComposioToolDefinition, DeploymentDefinition
28
29
  from vellum.workflows.types.generics import is_workflow_class
29
30
 
30
31
  CHAT_HISTORY_VARIABLE = "chat_history"
@@ -41,10 +42,6 @@ class ToolRouterNode(InlinePromptNode[ToolCallingState]):
41
42
  max_iterations_message = f"Maximum number of prompt iterations `{self.max_prompt_iterations}` reached."
42
43
  raise NodeException(message=max_iterations_message, code=WorkflowErrorCode.NODE_EXECUTION)
43
44
 
44
- # Merge user-provided chat history with node's chat history
45
- user_chat_history = self.prompt_inputs.get(CHAT_HISTORY_VARIABLE, []) if self.prompt_inputs else []
46
- merged_chat_history = user_chat_history + self.state.chat_history
47
- self.prompt_inputs = {**self.prompt_inputs, CHAT_HISTORY_VARIABLE: merged_chat_history} # type: ignore
48
45
  generator = super().run()
49
46
  for output in generator:
50
47
  if output.name == "results" and output.value:
@@ -234,6 +231,14 @@ def create_tool_router_node(
234
231
  )
235
232
  )
236
233
 
234
+ node_prompt_inputs = {
235
+ **(prompt_inputs or {}),
236
+ CHAT_HISTORY_VARIABLE: ConcatExpression[List[ChatMessage], List[ChatMessage]](
237
+ lhs=(prompt_inputs or {}).get(CHAT_HISTORY_VARIABLE, []),
238
+ rhs=ToolCallingState.chat_history,
239
+ ),
240
+ }
241
+
237
242
  node = cast(
238
243
  Type[ToolRouterNode],
239
244
  type(
@@ -243,7 +248,7 @@ def create_tool_router_node(
243
248
  "ml_model": ml_model,
244
249
  "blocks": blocks,
245
250
  "functions": functions,
246
- "prompt_inputs": prompt_inputs,
251
+ "prompt_inputs": node_prompt_inputs,
247
252
  "parameters": parameters,
248
253
  "max_prompt_iterations": max_prompt_iterations,
249
254
  "Ports": Ports,
@@ -285,6 +290,21 @@ def create_function_node(
285
290
 
286
291
  return node
287
292
 
293
+ 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
+ node = type(
299
+ f"ComposioNode_{function.name}",
300
+ (FunctionNode,),
301
+ {
302
+ "function_definition": composio_not_implemented,
303
+ "function_call_output": tool_router_node.Outputs.results,
304
+ "__module__": __name__,
305
+ },
306
+ )
307
+
288
308
  elif is_workflow_class(function):
289
309
  node = type(
290
310
  f"DynamicInlineSubworkflowNode_{function.__name__}",
@@ -314,5 +334,7 @@ def get_function_name(function: Tool) -> str:
314
334
  if isinstance(function, DeploymentDefinition):
315
335
  name = str(function.deployment_id or function.deployment_name)
316
336
  return name.replace("-", "")
337
+ elif isinstance(function, ComposioToolDefinition):
338
+ return function.name
317
339
  else:
318
340
  return snake_case(function.__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 DeploymentDefinition
16
+ from vellum.workflows.types.definition import ComposioToolDefinition, DeploymentDefinition
17
17
 
18
18
  if TYPE_CHECKING:
19
19
  from vellum.workflows.workflows.base import BaseWorkflow
@@ -50,4 +50,4 @@ class ConditionType(Enum):
50
50
 
51
51
 
52
52
  # Type alias for functions that can be called in tool calling nodes
53
- Tool = Union[Callable[..., Any], DeploymentDefinition, Type["BaseWorkflow"]]
53
+ Tool = Union[Callable[..., Any], DeploymentDefinition, Type["BaseWorkflow"], ComposioToolDefinition]
@@ -2,7 +2,7 @@ import importlib
2
2
  import inspect
3
3
  from types import FrameType
4
4
  from uuid import UUID
5
- from typing import Annotated, Any, Dict, Optional, Union
5
+ from typing import Annotated, Any, Dict, Literal, Optional, Union
6
6
 
7
7
  from pydantic import BeforeValidator
8
8
 
@@ -97,3 +97,22 @@ class DeploymentDefinition(UniversalBaseModel):
97
97
  if not self._is_uuid():
98
98
  return self.deployment
99
99
  return None
100
+
101
+
102
+ class ComposioToolDefinition(UniversalBaseModel):
103
+ """Represents a specific Composio action that can be used in Tool Calling Node"""
104
+
105
+ type: Literal["COMPOSIO"] = "COMPOSIO"
106
+
107
+ # Core identification
108
+ toolkit: str # "GITHUB", "SLACK", etc.
109
+ action: str # Specific action like "GITHUB_CREATE_AN_ISSUE"
110
+ description: str
111
+
112
+ # Optional cached metadata
113
+ display_name: Optional[str] = None
114
+
115
+ @property
116
+ def name(self) -> str:
117
+ """Generate a function name for this tool"""
118
+ return self.action.lower()
@@ -1,7 +1,7 @@
1
1
  import pytest
2
2
  from uuid import UUID
3
3
 
4
- from vellum.workflows.types.definition import DeploymentDefinition
4
+ from vellum.workflows.types.definition import ComposioToolDefinition, DeploymentDefinition
5
5
 
6
6
 
7
7
  @pytest.mark.parametrize(
@@ -31,3 +31,16 @@ def test_deployment_definition(deployment_value, expected_deployment_id, expecte
31
31
 
32
32
  assert deployment.deployment_id == expected_deployment_id
33
33
  assert deployment.deployment_name == expected_deployment_name
34
+
35
+
36
+ def test_composio_tool_definition_creation():
37
+ """Test that ComposioToolDefinition can be created with required fields."""
38
+ composio_tool = ComposioToolDefinition(
39
+ toolkit="GITHUB", action="GITHUB_CREATE_AN_ISSUE", description="Create a new issue in a GitHub repository"
40
+ )
41
+
42
+ assert composio_tool.toolkit == "GITHUB"
43
+ assert composio_tool.action == "GITHUB_CREATE_AN_ISSUE"
44
+ assert composio_tool.description == "Create a new issue in a GitHub repository"
45
+ assert composio_tool.display_name is None
46
+ assert composio_tool.name == "github_create_an_issue"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-ai
3
- Version: 1.0.3
3
+ Version: 1.0.4
4
4
  Summary:
5
5
  License: MIT
6
6
  Requires-Python: >=3.9,<4.0
@@ -22,6 +22,8 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
22
  Classifier: Typing :: Typed
23
23
  Requires-Dist: Jinja2 (>=3.1.0,<4.0.0)
24
24
  Requires-Dist: click (>=8.1.7,<9.0.0)
25
+ Requires-Dist: composio-client (>=1.5.0,<2.0.0)
26
+ Requires-Dist: composio-core (>=0.7.20,<1.0.0)
25
27
  Requires-Dist: docker (>=7.1.0,<8.0.0)
26
28
  Requires-Dist: httpx (>=0.21.2)
27
29
  Requires-Dist: openai (>=1.0.0,<2.0.0)
@@ -144,7 +144,7 @@ vellum/client/README.md,sha256=Dle5iytCXxP1pNeNd7uZyhFo0rl7tp7vU7s8gmi10OQ,4863
144
144
  vellum/client/__init__.py,sha256=KmkyOgReuTsjmXF3WC_dPQ9QqJgYrB3Sr8_LcSUIQyI,125258
145
145
  vellum/client/core/__init__.py,sha256=SQ85PF84B9MuKnBwHNHWemSGuy-g_515gFYNFhvEE0I,1438
146
146
  vellum/client/core/api_error.py,sha256=RE8LELok2QCjABadECTvtDp7qejA1VmINCh6TbqPwSE,426
147
- vellum/client/core/client_wrapper.py,sha256=hWvEhwi9Wp5EJXEwxdFG2CgGyBpzEihn6uuDg5PNZ6c,2383
147
+ vellum/client/core/client_wrapper.py,sha256=im_0U7PdkUrr4begxJ1IwohjLbmzi8c2diHiklFefHE,2383
148
148
  vellum/client/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
149
149
  vellum/client/core/file.py,sha256=d4NNbX8XvXP32z8KpK2Xovv33nFfruIrpz0QWxlgpZk,2663
150
150
  vellum/client/core/http_client.py,sha256=cKs2w0ybDBk1wHQf-fTALm_MmvaMe3cZKcYJxqmCxkE,19539
@@ -499,7 +499,7 @@ vellum/client/types/open_ai_vectorizer_text_embedding_3_small.py,sha256=T_-P7qGj
499
499
  vellum/client/types/open_ai_vectorizer_text_embedding_3_small_request.py,sha256=-lwNeWj7ExP-JLncUp1Uyd20FxweVIDu-aEnenPB98A,841
500
500
  vellum/client/types/open_ai_vectorizer_text_embedding_ada_002.py,sha256=c4vNlR6lRvUjq-67M06sroDMNMG_qC4JUBqwmKEJQ2I,812
501
501
  vellum/client/types/open_ai_vectorizer_text_embedding_ada_002_request.py,sha256=FdpkkNBGgRwfqFjBwpfH4t2zKIM0pIYminX2iZQUzvY,841
502
- vellum/client/types/organization_limit_config.py,sha256=-d3xOt5EPqPwqIhPi85Ez70eFi-ClwBn6_IPQhD6b_Y,894
502
+ vellum/client/types/organization_limit_config.py,sha256=-RXRY_Rpt8hHbJtNxJZvfKo1cUEnlvvGCRhk401jLU0,956
503
503
  vellum/client/types/organization_read.py,sha256=c5Wl5KY6plC7DuPJq6zAK_UTH2XVhT7H8OdEtxLqN98,854
504
504
  vellum/client/types/paginated_container_image_read_list.py,sha256=7lwIgs1q7Z0xDYPGWPnjSNC1kU_peu79CotzaaQfRdA,801
505
505
  vellum/client/types/paginated_deployment_release_tag_read_list.py,sha256=hp7D74CxPY14dEPRZ-fnTCwp63upxkYquL1e74oYXh4,826
@@ -853,11 +853,11 @@ vellum/plugins/utils.py,sha256=cPmxE9R2CK1bki2jKE8rB-G9zMf2pzHjSPDHFPXwd3Q,878
853
853
  vellum/plugins/vellum_mypy.py,sha256=hfjC2rIxNdQuJa6fIN4PDgODnQ3YaUszyaV2eNbLJlE,27952
854
854
  vellum/prompts/__init__.py,sha256=kn-btvQOMNnnBuyQiUpie48_VBJAk7tFFU-ul5dwK60,107
855
855
  vellum/prompts/blocks/__init__.py,sha256=Zwvncjd8sUCPmT-8pFpgLYsKJl0xB6td1GTQzjV9hYA,108
856
- vellum/prompts/blocks/compilation.py,sha256=qeC_4La5auQkm4EyzCMpN34F5R8mjiGcLV7IxKgVf3k,9973
856
+ vellum/prompts/blocks/compilation.py,sha256=wI9IRj-atd8GYJnOhBnyumhH25e3rUYC5uTcMVEyktw,10212
857
857
  vellum/prompts/blocks/exceptions.py,sha256=vmk5PV6Vyw9nKjZYQDUDW0LH8MfQNIgFvFb_mFWdIRI,50
858
858
  vellum/prompts/blocks/helpers.py,sha256=7G1Qi6N3V1K73iqTypMTcl6Rum7wk449yHStkakLy-U,961
859
859
  vellum/prompts/blocks/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
860
- vellum/prompts/blocks/tests/test_compilation.py,sha256=EOUtdzJDFGbGhoc_y5XTMyO0HOpOM7FYJssPzd_yRVg,5235
860
+ vellum/prompts/blocks/tests/test_compilation.py,sha256=r8Z2x6QUiHhP183MtRTYxNxoxADWx_DN2HM5iStnfAg,7885
861
861
  vellum/prompts/blocks/types.py,sha256=6aSJQco-5kKeadfKVVXF_SrQPlIJgMYVNc-C7so1sY8,975
862
862
  vellum/prompts/constants.py,sha256=fnjiRWLoRlC4Puo5oQcpZD5Hd-EesxsAo9l5tGAkpZQ,270
863
863
  vellum/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -1535,7 +1535,7 @@ vellum/workflows/__init__.py,sha256=CssPsbNvN6rDhoLuqpEv7MMKGa51vE6dvAh6U31Pcio,
1535
1535
  vellum/workflows/constants.py,sha256=2yg4_uo5gpqViy3ZLSwfC8qTybleYCtOnhA4Rj6bacM,1310
1536
1536
  vellum/workflows/context.py,sha256=jvMuyeRluay8BQa7GX1TqUlmoHLCycAVYKkp87sfXSo,1644
1537
1537
  vellum/workflows/descriptors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1538
- vellum/workflows/descriptors/base.py,sha256=X47a4TClHknsnjs53DkiXnop_5uLGVor487oxhHuGo4,14902
1538
+ vellum/workflows/descriptors/base.py,sha256=nZeCuuoJj3eJJjhrqOyfBB4FLUkN6fuO8PDdx6YDkzc,15373
1539
1539
  vellum/workflows/descriptors/exceptions.py,sha256=gUy4UD9JFUKSeQnQpeuDSLiRqWjWiIsxLahB7p_q3JY,54
1540
1540
  vellum/workflows/descriptors/tests/test_utils.py,sha256=HJ5DoRz0sJvViGxyZ_FtytZjxN2J8xTkGtaVwCy6Q90,6928
1541
1541
  vellum/workflows/descriptors/utils.py,sha256=1siECBf6AI54gwwUwkF6mP9rYsRryUGaOYBbMpQaceM,3848
@@ -1561,6 +1561,7 @@ vellum/workflows/expressions/and_.py,sha256=I7lNqrUM3-m_5hmjjiMhaHhJtKcLj39kEFVW
1561
1561
  vellum/workflows/expressions/begins_with.py,sha256=FnWsQXbENm0ZwkfEP7dR8Qx4_MMrzj6C1yqAV2KaNHw,1123
1562
1562
  vellum/workflows/expressions/between.py,sha256=dVeddT6YA91eOAlE1Utg7C7gnCiYE7WP-dg17yXUeAY,1492
1563
1563
  vellum/workflows/expressions/coalesce_expression.py,sha256=s4pcfu8KkUaUlQkB6BoQUKitGmV1FIQfV4agHHZtd98,1194
1564
+ vellum/workflows/expressions/concat.py,sha256=bFiHeCpZWfMMO_nvFmM5RgN-hoh-5ThBtxP0EJ2F7BE,1105
1564
1565
  vellum/workflows/expressions/contains.py,sha256=B1Ub63119QmwPKvBt8vDqQbuiaJWh-_2k5ZUnUNZZak,1392
1565
1566
  vellum/workflows/expressions/does_not_begin_with.py,sha256=qcnIJsxg4Jt82i2L-PW6ZhKP3C-OlEiXbiIgwHQc5RE,1137
1566
1567
  vellum/workflows/expressions/does_not_contain.py,sha256=ZdHVewTe_pbPGB0cGv_gIq_4jKkv_oG2tX3RBdEGWoA,1266
@@ -1588,6 +1589,7 @@ vellum/workflows/expressions/or_.py,sha256=s-8YdMSSCDS2yijR38kguwok3iqmDMMgDYKV9
1588
1589
  vellum/workflows/expressions/parse_json.py,sha256=xsk6j3HF7bU1yF6fwt5P9Ugcyd5D9ZXrdng11FRilUI,1088
1589
1590
  vellum/workflows/expressions/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1590
1591
  vellum/workflows/expressions/tests/test_accessor.py,sha256=g2z0mJjuWwVKeXS0yGoFW-aRmT5n_LrhfuBorSmj9kI,7585
1592
+ vellum/workflows/expressions/tests/test_concat.py,sha256=fDHXlmFvCtqPkdZQD9Qs22i6sJq_MJjbUXCnTlSMvA0,1666
1591
1593
  vellum/workflows/expressions/tests/test_expressions.py,sha256=3b6k8xs-CItBBw95NygFLUNoNPKxI4VA1GyWbkMtqyI,11623
1592
1594
  vellum/workflows/expressions/tests/test_parse_json.py,sha256=zpB_qE5_EwWQL7ULQUJm0o1PRSfWZdAqZNW6Ah13oJE,1059
1593
1595
  vellum/workflows/graph/__init__.py,sha256=3sHlay5d_-uD7j3QJXiGl0WHFZZ_QScRvgyDhN2GhHY,74
@@ -1668,7 +1670,7 @@ vellum/workflows/nodes/displayable/guardrail_node/test_node.py,sha256=SAGv6hSFcB
1668
1670
  vellum/workflows/nodes/displayable/guardrail_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1669
1671
  vellum/workflows/nodes/displayable/guardrail_node/tests/test_node.py,sha256=X2pd6TI8miYxIa7rgvs1pHTEreyWcf77EyR0_Jsa700,2055
1670
1672
  vellum/workflows/nodes/displayable/inline_prompt_node/__init__.py,sha256=gSUOoEZLlrx35-tQhSAd3An8WDwBqyiQh-sIebLU9wU,74
1671
- vellum/workflows/nodes/displayable/inline_prompt_node/node.py,sha256=LkFaS7GDPdhqMjQ3duHPX6pjl0z6xKzGxDueQC4aeA0,2999
1673
+ vellum/workflows/nodes/displayable/inline_prompt_node/node.py,sha256=wgZ9bt9IFe5cqWSghfMlD1NgmFhRnuDLRzXzMhJomV0,2912
1672
1674
  vellum/workflows/nodes/displayable/inline_prompt_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1673
1675
  vellum/workflows/nodes/displayable/inline_prompt_node/tests/test_node.py,sha256=bBHs90mV5SZ3rJPAL0wx4WWyawUA406LgMPOdvpZC_A,10923
1674
1676
  vellum/workflows/nodes/displayable/merge_node/__init__.py,sha256=J8IC08dSH7P76wKlNuxe1sn7toNGtSQdFirUbtPDEs0,60
@@ -1676,7 +1678,7 @@ vellum/workflows/nodes/displayable/merge_node/node.py,sha256=nZtGGVAvY4fvGg8vwV6
1676
1678
  vellum/workflows/nodes/displayable/note_node/__init__.py,sha256=KWA3P4fyYJ-fOTky8qNGlcOotQ-HeHJ9AjZt6mRQmCE,58
1677
1679
  vellum/workflows/nodes/displayable/note_node/node.py,sha256=sIN1VBQ7zeT3GhN0kupXbFfdpvgedWV79k4woJNp5IQ,394
1678
1680
  vellum/workflows/nodes/displayable/prompt_deployment_node/__init__.py,sha256=krX1Hds-TSVYZsx0wJFX4wsAKkEFYOX1ifwRGiIM-EA,82
1679
- vellum/workflows/nodes/displayable/prompt_deployment_node/node.py,sha256=rRUIM-zbVCV_0odyPExEZay0k4VCjsYyZ3OC9ZpHQsc,3399
1681
+ vellum/workflows/nodes/displayable/prompt_deployment_node/node.py,sha256=r-HGevtJ0wXPPW58dNfF3euuDmXO-p1Qm8WuJTJqVtg,3312
1680
1682
  vellum/workflows/nodes/displayable/prompt_deployment_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1681
1683
  vellum/workflows/nodes/displayable/prompt_deployment_node/tests/test_node.py,sha256=c_nuuqrwiIjgj4qIbVypfDuOc-3TlgO6CbXFqQl2Nqw,19725
1682
1684
  vellum/workflows/nodes/displayable/search_node/__init__.py,sha256=hpBpvbrDYf43DElRZFLzieSn8weXiwNiiNOJurERQbs,62
@@ -1693,12 +1695,14 @@ vellum/workflows/nodes/displayable/tests/test_search_node_error_handling.py,sha2
1693
1695
  vellum/workflows/nodes/displayable/tests/test_search_node_wth_text_output.py,sha256=VepO5z1277c1y5N6LLIC31nnWD1aak2m5oPFplfJHHs,6935
1694
1696
  vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py,sha256=dc3EEn1sOICpr3GdS8eyeFtExaGwWWcw9eHSdkRhQJU,2584
1695
1697
  vellum/workflows/nodes/displayable/tool_calling_node/__init__.py,sha256=3n0-ysmFKsr40CVxPthc0rfJgqVJeZuUEsCmYudLVRg,117
1698
+ vellum/workflows/nodes/displayable/tool_calling_node/composio_service.py,sha256=C53hgStA-BHe7EFu4j_N650LqsLBOiMovs_nGW8J7nc,2669
1696
1699
  vellum/workflows/nodes/displayable/tool_calling_node/node.py,sha256=BRA6YRCEOk0Nw3DCIT13WY7WCZ7Gx30s-egJe_md0FA,6504
1697
1700
  vellum/workflows/nodes/displayable/tool_calling_node/state.py,sha256=oQg_GAtc349nPB5BL_oeDYYD7q1qSDPAqjj8iA8OoAw,215
1698
1701
  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=BOb4LTAELS-cX5xPH8fH4E2CKl1IBBjiOBYhQl51PpI,4758
1699
1703
  vellum/workflows/nodes/displayable/tool_calling_node/tests/test_node.py,sha256=raY_E5-EgtYNXEPbO2I-Ythe4YeuFdGsXGZ_BAN98uI,7979
1700
1704
  vellum/workflows/nodes/displayable/tool_calling_node/tests/test_utils.py,sha256=eu6WTyENhGLg9pGp_j69rysZjf_qiQXske1YdZn9PzU,1718
1701
- vellum/workflows/nodes/displayable/tool_calling_node/utils.py,sha256=cWlQpEyeLwJX3peMxuTNG3uy26JoXuaQPG4zTuonB5Y,12671
1705
+ vellum/workflows/nodes/displayable/tool_calling_node/utils.py,sha256=jMXStMKr3BFfQKdQa9hFFxI7RQp8eAMAknpqkbY0Nk0,13344
1702
1706
  vellum/workflows/nodes/experimental/README.md,sha256=eF6DfIL8t-HbF9-mcofOMymKrraiBHDLKTlnBa51ZiE,284
1703
1707
  vellum/workflows/nodes/experimental/__init__.py,sha256=jCQgvZEknXKfuNhGSOou4XPfrPqZ1_XBj5F0n0fgiWM,106
1704
1708
  vellum/workflows/nodes/experimental/openai_chat_completion_node/__init__.py,sha256=lsyD9laR9p7kx5-BXGH2gUTM242UhKy8SMV0SR6S2iE,90
@@ -1745,12 +1749,12 @@ vellum/workflows/tests/test_sandbox.py,sha256=JKwaluI-lODQo7Ek9sjDstjL_WTdSqUlVi
1745
1749
  vellum/workflows/tests/test_undefined.py,sha256=zMCVliCXVNLrlC6hEGyOWDnQADJ2g83yc5FIM33zuo8,353
1746
1750
  vellum/workflows/types/__init__.py,sha256=KxUTMBGzuRCfiMqzzsykOeVvrrkaZmTTo1a7SLu8gRM,68
1747
1751
  vellum/workflows/types/code_execution_node_wrappers.py,sha256=3MNIoFZKzVzNS5qFLVuDwMV17QJw72zo7NRf52yMq5A,3074
1748
- vellum/workflows/types/core.py,sha256=iLJkMKf417kjwRncWdT_qsfJ-qBv5x58um7SfrydJbs,1266
1749
- vellum/workflows/types/definition.py,sha256=WSTi7DfwgIUMaHaNl0jV_9thw_wsLbzt5WRElJebaHw,3172
1752
+ vellum/workflows/types/core.py,sha256=6MW_BRLcx4oEJpItQWQa64xfCrsk76suZSsMKKEsJLg,1314
1753
+ vellum/workflows/types/definition.py,sha256=Oq2E5Ae6AsvqAOLWzP0k6H5M7su00JZP_QFhlV2rz7s,3721
1750
1754
  vellum/workflows/types/generics.py,sha256=8jptbEx1fnJV0Lhj0MpCJOT6yNiEWeTOYOwrEAb5CRU,1576
1751
1755
  vellum/workflows/types/stack.py,sha256=h7NE0vXR7l9DevFBIzIAk1Zh59K-kECQtDTKOUunwMY,1314
1752
1756
  vellum/workflows/types/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1753
- vellum/workflows/types/tests/test_definition.py,sha256=5wh_WEnE51epkoo-4PE-JbPlg8OGJUNlaBVWa9TcNSw,993
1757
+ vellum/workflows/types/tests/test_definition.py,sha256=c3GczPtWxuH3BOULwZacxYTQlP2ryqH7rvT_anDORVo,1604
1754
1758
  vellum/workflows/types/tests/test_utils.py,sha256=UnZog59tR577mVwqZRqqWn2fScoOU1H6up0EzS8zYhw,2536
1755
1759
  vellum/workflows/types/utils.py,sha256=mTctHITBybpt4855x32oCKALBEcMNLn-9cCmfEKgJHQ,6498
1756
1760
  vellum/workflows/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -1771,8 +1775,8 @@ vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnad
1771
1775
  vellum/workflows/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1772
1776
  vellum/workflows/workflows/tests/test_base_workflow.py,sha256=ptMntHzVyy8ZuzNgeTuk7hREgKQ5UBdgq8VJFSGaW4Y,20832
1773
1777
  vellum/workflows/workflows/tests/test_context.py,sha256=VJBUcyWVtMa_lE5KxdhgMu0WYNYnUQUDvTF7qm89hJ0,2333
1774
- vellum_ai-1.0.3.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1775
- vellum_ai-1.0.3.dist-info/METADATA,sha256=BhSR95Ti_OY6NHP8v4qgMd9L3RM-6NxohzOdWRF6nxY,5554
1776
- vellum_ai-1.0.3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1777
- vellum_ai-1.0.3.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1778
- vellum_ai-1.0.3.dist-info/RECORD,,
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,,