vellum-ai 1.7.10__py3-none-any.whl → 1.7.12__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.
Potentially problematic release.
This version of vellum-ai might be problematic. Click here for more details.
- vellum/__init__.py +2 -0
- vellum/client/core/client_wrapper.py +2 -2
- vellum/client/types/__init__.py +2 -0
- vellum/client/types/auth_type_enum.py +5 -0
- vellum/client/types/integration_name.py +4 -0
- vellum/client/types/slim_integration_auth_config_read.py +2 -0
- vellum/client/types/slim_workflow_execution_read.py +3 -3
- vellum/client/types/vellum_error_code_enum.py +1 -0
- vellum/client/types/vellum_sdk_error_code_enum.py +1 -0
- vellum/client/types/workflow_event_execution_read.py +3 -3
- vellum/client/types/workflow_execution_event_error_code.py +1 -0
- vellum/client/types/workflow_execution_snapshotted_body.py +1 -0
- vellum/types/auth_type_enum.py +3 -0
- vellum/workflows/events/tests/test_event.py +1 -0
- vellum/workflows/events/workflow.py +3 -0
- vellum/workflows/exceptions.py +3 -0
- vellum/workflows/integrations/mcp_service.py +7 -0
- vellum/workflows/integrations/tests/test_mcp_service.py +48 -0
- vellum/workflows/loaders/__init__.py +3 -0
- vellum/workflows/loaders/base.py +21 -0
- vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py +3 -0
- vellum/workflows/nodes/displayable/inline_prompt_node/tests/test_node.py +3 -0
- vellum/workflows/tests/triggers/test_vellum_integration_trigger.py +225 -0
- vellum/workflows/triggers/__init__.py +2 -1
- vellum/workflows/triggers/vellum_integration.py +383 -0
- vellum/workflows/types/__init__.py +3 -0
- vellum/workflows/types/tests/test_utils.py +11 -0
- vellum/workflows/types/trigger_exec_config.py +63 -0
- vellum/workflows/types/utils.py +22 -0
- vellum/workflows/utils/names.py +20 -0
- vellum/workflows/workflows/base.py +13 -1
- {vellum_ai-1.7.10.dist-info → vellum_ai-1.7.12.dist-info}/METADATA +1 -1
- {vellum_ai-1.7.10.dist-info → vellum_ai-1.7.12.dist-info}/RECORD +45 -37
- vellum_cli/pull.py +6 -5
- vellum_cli/push.py +35 -2
- vellum_cli/tests/test_push.py +122 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_list_vellum_document_serialization.py +65 -0
- vellum_ee/workflows/display/utils/events.py +6 -3
- vellum_ee/workflows/display/utils/tests/test_events.py +29 -0
- vellum_ee/workflows/server/virtual_file_loader.py +15 -4
- vellum_ee/workflows/tests/test_serialize_module.py +48 -0
- vellum_ee/workflows/tests/test_server.py +105 -0
- {vellum_ai-1.7.10.dist-info → vellum_ai-1.7.12.dist-info}/LICENSE +0 -0
- {vellum_ai-1.7.10.dist-info → vellum_ai-1.7.12.dist-info}/WHEEL +0 -0
- {vellum_ai-1.7.10.dist-info → vellum_ai-1.7.12.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Any, ClassVar, Dict, Optional, Type, cast
|
|
3
|
+
|
|
4
|
+
from vellum.workflows.constants import VellumIntegrationProviderType
|
|
5
|
+
from vellum.workflows.references.trigger import TriggerAttributeReference
|
|
6
|
+
from vellum.workflows.triggers.base import BaseTriggerMeta
|
|
7
|
+
from vellum.workflows.triggers.integration import IntegrationTrigger
|
|
8
|
+
from vellum.workflows.types import ComposioIntegrationTriggerExecConfig
|
|
9
|
+
from vellum.workflows.utils.uuids import uuid4_from_hash
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class VellumIntegrationTriggerMeta(BaseTriggerMeta):
|
|
13
|
+
"""
|
|
14
|
+
Custom metaclass for VellumIntegrationTrigger that supports dynamic attribute discovery.
|
|
15
|
+
|
|
16
|
+
This metaclass extends BaseTriggerMeta to enable class-level access to attributes
|
|
17
|
+
that aren't statically defined. When accessing an undefined attribute on a
|
|
18
|
+
VellumIntegrationTrigger class, this metaclass will create a TriggerAttributeReference
|
|
19
|
+
dynamically, allowing triggers to work with attributes discovered from integration
|
|
20
|
+
APIs or event payloads.
|
|
21
|
+
|
|
22
|
+
Note: This metaclass intentionally deviates from the standard metaclass pattern used
|
|
23
|
+
in BaseNodeMeta and BaseWorkflowMeta, which generate nested classes (Outputs, Inputs)
|
|
24
|
+
in __new__. Since VellumIntegrationTrigger uses a factory pattern with dynamic
|
|
25
|
+
attributes rather than predefined nested classes, this metaclass focuses on
|
|
26
|
+
attribute discovery via __getattribute__ during workflow definition (when the
|
|
27
|
+
developer references trigger attributes in their workflow code) instead of class
|
|
28
|
+
generation in __new__. This architectural difference is necessary to support the
|
|
29
|
+
dynamic nature of integration triggers where attributes are not known until the
|
|
30
|
+
integration is queried or event data is received.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __getattribute__(cls, name: str) -> Any:
|
|
34
|
+
"""
|
|
35
|
+
Override attribute access to support dynamic attribute discovery.
|
|
36
|
+
|
|
37
|
+
For VellumIntegrationTrigger classes generated by the factory, this method
|
|
38
|
+
allows access to any attribute name, creating TriggerAttributeReference objects
|
|
39
|
+
on demand. This enables usage like:
|
|
40
|
+
|
|
41
|
+
SlackMessage = VellumIntegrationTrigger.for_trigger(
|
|
42
|
+
integration_name="SLACK",
|
|
43
|
+
slug="slack_new_message",
|
|
44
|
+
trigger_nano_id="abc123"
|
|
45
|
+
)
|
|
46
|
+
text = SlackMessage.message # Creates reference even though 'message' isn't pre-defined
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
name: The attribute name being accessed
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
The attribute value or a dynamically created TriggerAttributeReference
|
|
53
|
+
"""
|
|
54
|
+
# Let BaseTriggerMeta handle internal attributes and known attributes
|
|
55
|
+
try:
|
|
56
|
+
return super().__getattribute__(name)
|
|
57
|
+
except AttributeError:
|
|
58
|
+
# For VellumIntegrationTrigger factory-generated classes, create dynamic references
|
|
59
|
+
# Only enable dynamic attribute creation for factory-generated classes, not the base
|
|
60
|
+
# VellumIntegrationTrigger class itself. We check the internal __name__ attribute
|
|
61
|
+
# (e.g., "VellumIntegrationTrigger_COMPOSIO_SLACK_slack_new_message"), not the user-facing
|
|
62
|
+
# variable name (e.g., "SlackNewMessage").
|
|
63
|
+
try:
|
|
64
|
+
is_factory_class = super().__getattribute__("__name__").startswith(
|
|
65
|
+
"VellumIntegrationTrigger_"
|
|
66
|
+
) and not name.startswith("_")
|
|
67
|
+
except AttributeError:
|
|
68
|
+
is_factory_class = False
|
|
69
|
+
|
|
70
|
+
if is_factory_class:
|
|
71
|
+
trigger_cls = cast(Type["VellumIntegrationTrigger"], cls)
|
|
72
|
+
|
|
73
|
+
# Check cache first
|
|
74
|
+
cache = super().__getattribute__("__trigger_attribute_cache__")
|
|
75
|
+
if name in cache:
|
|
76
|
+
return cache[name]
|
|
77
|
+
|
|
78
|
+
# Generate and store deterministic UUID for this attribute if not already present.
|
|
79
|
+
# This ensures consistent IDs across multiple accesses to the same attribute,
|
|
80
|
+
# which is critical for serialization and state resolution.
|
|
81
|
+
# Use semantic identity (provider|integration|slug) instead of __qualname__ for stability.
|
|
82
|
+
attribute_ids = super().__getattribute__("__trigger_attribute_ids__")
|
|
83
|
+
if name not in attribute_ids:
|
|
84
|
+
# Generate stable ID from trigger semantics, not class naming
|
|
85
|
+
provider = super().__getattribute__("provider")
|
|
86
|
+
integration_name = super().__getattribute__("integration_name")
|
|
87
|
+
slug = super().__getattribute__("slug")
|
|
88
|
+
trigger_identity = f"{provider.value}|{integration_name}|{slug}"
|
|
89
|
+
attribute_ids[name] = uuid4_from_hash(f"{trigger_identity}|{name}")
|
|
90
|
+
|
|
91
|
+
# Create a new dynamic reference for this attribute
|
|
92
|
+
types = (object,)
|
|
93
|
+
reference = TriggerAttributeReference(name=name, types=types, instance=None, trigger_class=trigger_cls)
|
|
94
|
+
cache[name] = reference
|
|
95
|
+
return reference
|
|
96
|
+
|
|
97
|
+
# Not a factory class or starts with _, re-raise the AttributeError
|
|
98
|
+
raise
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class VellumIntegrationTrigger(IntegrationTrigger, metaclass=VellumIntegrationTriggerMeta):
|
|
102
|
+
"""
|
|
103
|
+
Factory-based trigger for Vellum-managed integration events.
|
|
104
|
+
|
|
105
|
+
VellumIntegrationTrigger provides a pure factory pattern for creating trigger
|
|
106
|
+
classes dynamically based on integration provider, integration name, slug, and
|
|
107
|
+
trigger nano ID. Unlike predefined trigger classes, these triggers are created
|
|
108
|
+
on-demand and support dynamic attribute discovery from the integration API.
|
|
109
|
+
|
|
110
|
+
This design ensures parity with VellumIntegrationToolDefinition and allows users to
|
|
111
|
+
work with any integration trigger without requiring SDK updates for new integrations.
|
|
112
|
+
|
|
113
|
+
Examples:
|
|
114
|
+
Create triggers dynamically for different integrations:
|
|
115
|
+
>>> SlackNewMessage = VellumIntegrationTrigger.for_trigger(
|
|
116
|
+
... integration_name="SLACK",
|
|
117
|
+
... slug="slack_new_message",
|
|
118
|
+
... trigger_nano_id="abc123def456"
|
|
119
|
+
... )
|
|
120
|
+
>>>
|
|
121
|
+
>>> GithubPush = VellumIntegrationTrigger.for_trigger(
|
|
122
|
+
... integration_name="GITHUB",
|
|
123
|
+
... slug="github_push_event",
|
|
124
|
+
... trigger_nano_id="xyz789ghi012"
|
|
125
|
+
... )
|
|
126
|
+
|
|
127
|
+
Use in workflow graph:
|
|
128
|
+
>>> class MyWorkflow(BaseWorkflow):
|
|
129
|
+
... graph = SlackNewMessage >> ProcessMessageNode
|
|
130
|
+
|
|
131
|
+
Reference trigger attributes in nodes:
|
|
132
|
+
>>> class ProcessNode(BaseNode):
|
|
133
|
+
... class Outputs(BaseNode.Outputs):
|
|
134
|
+
... text = SlackNewMessage.message
|
|
135
|
+
... channel = SlackNewMessage.channel
|
|
136
|
+
|
|
137
|
+
Instantiate for testing:
|
|
138
|
+
>>> trigger = SlackNewMessage(event_data={
|
|
139
|
+
... "message": "Hello world",
|
|
140
|
+
... "channel": "C123456"
|
|
141
|
+
... })
|
|
142
|
+
>>> trigger.message
|
|
143
|
+
'Hello world'
|
|
144
|
+
|
|
145
|
+
Note:
|
|
146
|
+
The factory method generates unique classes with proper __name__ and __module__
|
|
147
|
+
for correct attribute ID generation and serialization. Each factory call with
|
|
148
|
+
the same parameters returns the same class instance (cached).
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
# Class variables that identify this trigger
|
|
152
|
+
provider: ClassVar[VellumIntegrationProviderType]
|
|
153
|
+
integration_name: ClassVar[str]
|
|
154
|
+
slug: ClassVar[str]
|
|
155
|
+
trigger_nano_id: ClassVar[str]
|
|
156
|
+
attributes: ClassVar[Dict[str, Any]]
|
|
157
|
+
|
|
158
|
+
# Cache for generated trigger classes to ensure consistency
|
|
159
|
+
_trigger_class_cache: ClassVar[Dict[tuple, Type["VellumIntegrationTrigger"]]] = {}
|
|
160
|
+
|
|
161
|
+
@classmethod
|
|
162
|
+
def _freeze_attributes(cls, attributes: Dict[str, Any]) -> str:
|
|
163
|
+
"""
|
|
164
|
+
Convert attributes dict to hashable string for caching.
|
|
165
|
+
|
|
166
|
+
Attributes must be JSON-serializable since they're sent to the backend
|
|
167
|
+
via ComposioIntegrationTriggerExecConfig. This method fails fast if
|
|
168
|
+
attributes contain non-JSON-serializable types.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
attributes: Dictionary of trigger attributes
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
JSON string representation with sorted keys for deterministic hashing
|
|
175
|
+
|
|
176
|
+
Raises:
|
|
177
|
+
ValueError: If attributes are not JSON-serializable
|
|
178
|
+
"""
|
|
179
|
+
if not attributes:
|
|
180
|
+
return ""
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
# Use json.dumps with sort_keys for deterministic output
|
|
184
|
+
return json.dumps(attributes, sort_keys=True)
|
|
185
|
+
except (TypeError, ValueError) as e:
|
|
186
|
+
raise ValueError(
|
|
187
|
+
f"Trigger attributes must be JSON-serializable (str, int, float, bool, None, list, dict). "
|
|
188
|
+
f"Got non-serializable value: {e}"
|
|
189
|
+
) from e
|
|
190
|
+
|
|
191
|
+
def __init__(self, event_data: dict):
|
|
192
|
+
"""
|
|
193
|
+
Initialize trigger with event data from the integration.
|
|
194
|
+
|
|
195
|
+
The trigger dynamically populates its attributes based on the event_data
|
|
196
|
+
dictionary keys. Any key in event_data becomes an accessible attribute.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
event_data: Raw event data from the integration. Keys become trigger attributes.
|
|
200
|
+
|
|
201
|
+
Examples:
|
|
202
|
+
>>> SlackMessage = VellumIntegrationTrigger.for_trigger(
|
|
203
|
+
... integration_name="SLACK",
|
|
204
|
+
... slug="slack_new_message",
|
|
205
|
+
... trigger_nano_id="abc123"
|
|
206
|
+
... )
|
|
207
|
+
>>> trigger = SlackMessage(event_data={
|
|
208
|
+
... "message": "Hello",
|
|
209
|
+
... "channel": "C123",
|
|
210
|
+
... "user": "U456"
|
|
211
|
+
... })
|
|
212
|
+
>>> trigger.message
|
|
213
|
+
'Hello'
|
|
214
|
+
>>> trigger.channel
|
|
215
|
+
'C123'
|
|
216
|
+
"""
|
|
217
|
+
super().__init__(event_data)
|
|
218
|
+
|
|
219
|
+
# Dynamically populate instance attributes from event_data.
|
|
220
|
+
# This allows any key in event_data to become an accessible attribute:
|
|
221
|
+
# event_data={"message": "Hi"} → trigger.message == "Hi"
|
|
222
|
+
for key, value in event_data.items():
|
|
223
|
+
setattr(self, key, value)
|
|
224
|
+
|
|
225
|
+
def to_trigger_attribute_values(self) -> Dict["TriggerAttributeReference[Any]", Any]:
|
|
226
|
+
"""
|
|
227
|
+
Materialize attribute descriptor/value pairs for this trigger instance.
|
|
228
|
+
|
|
229
|
+
For VellumIntegrationTrigger, this includes all dynamic attributes from event_data.
|
|
230
|
+
"""
|
|
231
|
+
attribute_values: Dict["TriggerAttributeReference[Any]", Any] = {}
|
|
232
|
+
|
|
233
|
+
# Unlike the base class which iterates over type(self) (predefined annotations),
|
|
234
|
+
# we iterate over event_data keys since our attributes are discovered dynamically
|
|
235
|
+
# from the actual event data received during workflow execution.
|
|
236
|
+
# The base class approach: for reference in type(self)
|
|
237
|
+
# Our approach: for attr_name in self._event_data.keys()
|
|
238
|
+
for attr_name in self._event_data.keys():
|
|
239
|
+
# Get the class-level reference for this attribute
|
|
240
|
+
# This will create it via our custom metaclass if it doesn't exist
|
|
241
|
+
reference = getattr(type(self), attr_name)
|
|
242
|
+
if isinstance(reference, TriggerAttributeReference):
|
|
243
|
+
attribute_values[reference] = getattr(self, attr_name)
|
|
244
|
+
|
|
245
|
+
return attribute_values
|
|
246
|
+
|
|
247
|
+
@classmethod
|
|
248
|
+
def to_exec_config(cls) -> ComposioIntegrationTriggerExecConfig:
|
|
249
|
+
"""
|
|
250
|
+
Generate execution configuration for serialization.
|
|
251
|
+
|
|
252
|
+
This method creates a ComposioIntegrationTriggerExecConfig from the trigger
|
|
253
|
+
class's configuration, which is used during serialization to the backend.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
ComposioIntegrationTriggerExecConfig with all required fields
|
|
257
|
+
|
|
258
|
+
Raises:
|
|
259
|
+
AttributeError: If called on base VellumIntegrationTrigger (not factory class)
|
|
260
|
+
|
|
261
|
+
Examples:
|
|
262
|
+
>>> SlackMessage = VellumIntegrationTrigger.for_trigger(
|
|
263
|
+
... integration_name="SLACK",
|
|
264
|
+
... slug="slack_new_message",
|
|
265
|
+
... trigger_nano_id="abc123",
|
|
266
|
+
... attributes={"channel": "C123456"}
|
|
267
|
+
... )
|
|
268
|
+
>>> exec_config = SlackMessage.to_exec_config()
|
|
269
|
+
>>> exec_config.slug
|
|
270
|
+
'slack_new_message'
|
|
271
|
+
>>> exec_config.attributes
|
|
272
|
+
{'channel': 'C123456'}
|
|
273
|
+
"""
|
|
274
|
+
if not hasattr(cls, "slug"):
|
|
275
|
+
raise AttributeError(
|
|
276
|
+
"to_exec_config() can only be called on factory-generated trigger classes. "
|
|
277
|
+
"Use VellumIntegrationTrigger.for_trigger() to create a trigger class first."
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
return ComposioIntegrationTriggerExecConfig(
|
|
281
|
+
provider=cls.provider,
|
|
282
|
+
integration_name=cls.integration_name,
|
|
283
|
+
slug=cls.slug,
|
|
284
|
+
trigger_nano_id=cls.trigger_nano_id,
|
|
285
|
+
attributes=cls.attributes,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
@classmethod
|
|
289
|
+
def for_trigger(
|
|
290
|
+
cls,
|
|
291
|
+
integration_name: str,
|
|
292
|
+
slug: str,
|
|
293
|
+
trigger_nano_id: str,
|
|
294
|
+
provider: str = "COMPOSIO",
|
|
295
|
+
attributes: Optional[Dict[str, Any]] = None,
|
|
296
|
+
) -> Type["VellumIntegrationTrigger"]:
|
|
297
|
+
"""
|
|
298
|
+
Factory method to create a new trigger class for a specific integration trigger.
|
|
299
|
+
|
|
300
|
+
This method generates a unique trigger class that can be used in workflow graphs
|
|
301
|
+
and node definitions. Each unique combination of provider, integration_name,
|
|
302
|
+
slug, and trigger_nano_id produces the same class instance (cached).
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
integration_name: The integration identifier (e.g., "SLACK", "GITHUB")
|
|
306
|
+
slug: The slug of the integration trigger in Composio (e.g., "slack_new_message")
|
|
307
|
+
trigger_nano_id: Composio's unique trigger identifier used for event matching
|
|
308
|
+
provider: The integration provider (default: "COMPOSIO")
|
|
309
|
+
attributes: Optional dict of trigger-specific configuration attributes
|
|
310
|
+
used for filtering events. For example, {"channel": "C123456"} to only
|
|
311
|
+
match events from a specific Slack channel.
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
A new trigger class configured for the specified integration trigger
|
|
315
|
+
|
|
316
|
+
Examples:
|
|
317
|
+
>>> SlackNewMessage = VellumIntegrationTrigger.for_trigger(
|
|
318
|
+
... integration_name="SLACK",
|
|
319
|
+
... slug="slack_new_message",
|
|
320
|
+
... trigger_nano_id="abc123def456",
|
|
321
|
+
... attributes={"channel": "C123456"}
|
|
322
|
+
... )
|
|
323
|
+
>>> type(SlackNewMessage).__name__
|
|
324
|
+
'VellumIntegrationTrigger_COMPOSIO_SLACK_slack_new_message'
|
|
325
|
+
>>>
|
|
326
|
+
>>> # Use in workflow
|
|
327
|
+
>>> class MyWorkflow(BaseWorkflow):
|
|
328
|
+
... graph = SlackNewMessage >> ProcessNode
|
|
329
|
+
|
|
330
|
+
Note:
|
|
331
|
+
The generated class has proper __name__, __module__, and __qualname__
|
|
332
|
+
for correct serialization and attribute ID generation.
|
|
333
|
+
"""
|
|
334
|
+
# Validate and normalize provider
|
|
335
|
+
provider_enum = VellumIntegrationProviderType(provider)
|
|
336
|
+
|
|
337
|
+
# Normalize attributes
|
|
338
|
+
attrs = attributes or {}
|
|
339
|
+
|
|
340
|
+
# Create cache key - include all identifying parameters including attributes
|
|
341
|
+
# Convert attributes dict to a hashable representation for caching
|
|
342
|
+
frozen_attrs = cls._freeze_attributes(attrs)
|
|
343
|
+
cache_key = (
|
|
344
|
+
provider_enum.value,
|
|
345
|
+
integration_name,
|
|
346
|
+
slug,
|
|
347
|
+
trigger_nano_id,
|
|
348
|
+
frozen_attrs,
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
# Return cached class if it exists
|
|
352
|
+
if cache_key in cls._trigger_class_cache:
|
|
353
|
+
return cls._trigger_class_cache[cache_key]
|
|
354
|
+
|
|
355
|
+
# Generate unique class name including provider to avoid collisions across providers
|
|
356
|
+
class_name = f"VellumIntegrationTrigger_{provider_enum.value}_{integration_name}_{slug}"
|
|
357
|
+
|
|
358
|
+
# Create the new trigger class
|
|
359
|
+
trigger_class = type(
|
|
360
|
+
class_name,
|
|
361
|
+
(cls,),
|
|
362
|
+
{
|
|
363
|
+
"provider": provider_enum,
|
|
364
|
+
"integration_name": integration_name,
|
|
365
|
+
"slug": slug,
|
|
366
|
+
"trigger_nano_id": trigger_nano_id,
|
|
367
|
+
"attributes": attrs,
|
|
368
|
+
"__module__": cls.__module__,
|
|
369
|
+
# Explicitly set __qualname__ to match __name__ for deterministic UUID generation.
|
|
370
|
+
# UUIDs are generated from __qualname__, so this must be consistent and unique
|
|
371
|
+
# across different trigger configurations to prevent ID collisions.
|
|
372
|
+
"__qualname__": class_name,
|
|
373
|
+
# Initialize cache attributes that would normally be set by BaseTriggerMeta.__new__
|
|
374
|
+
# Since we're using type() directly, we need to set these ourselves
|
|
375
|
+
"__trigger_attribute_ids__": {},
|
|
376
|
+
"__trigger_attribute_cache__": {},
|
|
377
|
+
},
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
# Cache the generated class
|
|
381
|
+
cls._trigger_class_cache[cache_key] = trigger_class
|
|
382
|
+
|
|
383
|
+
return trigger_class
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
from .core import CancelSignal, MergeBehavior
|
|
2
|
+
from .trigger_exec_config import BaseIntegrationTriggerExecConfig, ComposioIntegrationTriggerExecConfig
|
|
2
3
|
|
|
3
4
|
__all__ = [
|
|
5
|
+
"BaseIntegrationTriggerExecConfig",
|
|
4
6
|
"CancelSignal",
|
|
7
|
+
"ComposioIntegrationTriggerExecConfig",
|
|
5
8
|
"MergeBehavior",
|
|
6
9
|
]
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import pytest
|
|
2
4
|
from typing import Any, ClassVar, Generic, List, TypeVar, Union
|
|
3
5
|
|
|
@@ -24,6 +26,11 @@ class ExampleClass:
|
|
|
24
26
|
mu: list[str]
|
|
25
27
|
|
|
26
28
|
|
|
29
|
+
class ExamplePEP604Class:
|
|
30
|
+
pep604_union: str | int
|
|
31
|
+
pep604_optional: str | None
|
|
32
|
+
|
|
33
|
+
|
|
27
34
|
T = TypeVar("T")
|
|
28
35
|
|
|
29
36
|
|
|
@@ -58,6 +65,8 @@ class ExampleNode(BaseNode):
|
|
|
58
65
|
(ExampleNode.Outputs, "iota", (str,)),
|
|
59
66
|
(ExampleClass, "kappa", (Any,)),
|
|
60
67
|
(ExampleClass, "mu", (list[str],)),
|
|
68
|
+
(ExamplePEP604Class, "pep604_union", (str, int)),
|
|
69
|
+
(ExamplePEP604Class, "pep604_optional", (str, type(None))),
|
|
61
70
|
],
|
|
62
71
|
ids=[
|
|
63
72
|
"str",
|
|
@@ -74,6 +83,8 @@ class ExampleNode(BaseNode):
|
|
|
74
83
|
"try_node_output",
|
|
75
84
|
"any",
|
|
76
85
|
"list_str_generic",
|
|
86
|
+
"pep604_union",
|
|
87
|
+
"pep604_optional",
|
|
77
88
|
],
|
|
78
89
|
)
|
|
79
90
|
def test_infer_types(cls, attr_name, expected_type):
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Execution configuration dataclasses for integration triggers.
|
|
3
|
+
|
|
4
|
+
These classes define the structure of execution configuration data that is
|
|
5
|
+
sent to/from the backend for integration triggers. They are used during
|
|
6
|
+
serialization and deserialization of trigger configurations.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any, Dict, Literal, Optional
|
|
10
|
+
|
|
11
|
+
from pydantic import Field
|
|
12
|
+
|
|
13
|
+
from vellum.client.core.pydantic_utilities import UniversalBaseModel
|
|
14
|
+
from vellum.workflows.constants import VellumIntegrationProviderType
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BaseIntegrationTriggerExecConfig(UniversalBaseModel):
|
|
18
|
+
"""
|
|
19
|
+
Base class for integration trigger execution configurations.
|
|
20
|
+
|
|
21
|
+
This class defines the common structure for all integration trigger exec configs,
|
|
22
|
+
regardless of the provider. Specific providers should extend this class.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
type: str = Field(..., description="The type of integration trigger exec config")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ComposioIntegrationTriggerExecConfig(BaseIntegrationTriggerExecConfig):
|
|
29
|
+
"""
|
|
30
|
+
Execution configuration for Composio-based integration triggers.
|
|
31
|
+
|
|
32
|
+
This configuration is used to identify and execute triggers through the Composio
|
|
33
|
+
integration provider. It includes the provider type, integration name, slug,
|
|
34
|
+
trigger nano ID, and optional attributes for filtering.
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
>>> config = ComposioIntegrationTriggerExecConfig(
|
|
38
|
+
... provider="COMPOSIO",
|
|
39
|
+
... integration_name="SLACK",
|
|
40
|
+
... slug="slack_new_message",
|
|
41
|
+
... trigger_nano_id="abc123def456",
|
|
42
|
+
... attributes={"channel": "C123456"}
|
|
43
|
+
... )
|
|
44
|
+
>>> config.provider
|
|
45
|
+
<VellumIntegrationProviderType.COMPOSIO: 'COMPOSIO'>
|
|
46
|
+
|
|
47
|
+
Attributes:
|
|
48
|
+
type: Always "COMPOSIO_INTEGRATION_TRIGGER" for this config type
|
|
49
|
+
provider: The integration provider (e.g., COMPOSIO)
|
|
50
|
+
integration_name: The integration identifier (e.g., "SLACK", "GITHUB")
|
|
51
|
+
slug: The slug of the integration trigger in Composio
|
|
52
|
+
trigger_nano_id: Composio's unique trigger identifier used for event matching
|
|
53
|
+
attributes: Optional dictionary of trigger-specific configuration attributes for filtering
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
type: Literal["COMPOSIO_INTEGRATION_TRIGGER"] = "COMPOSIO_INTEGRATION_TRIGGER"
|
|
57
|
+
provider: VellumIntegrationProviderType = Field(..., description="The integration provider (e.g., COMPOSIO)")
|
|
58
|
+
integration_name: str = Field(..., description="The integration name (e.g., 'SLACK', 'GITHUB')")
|
|
59
|
+
slug: str = Field(..., description="The slug of the integration trigger in Composio")
|
|
60
|
+
trigger_nano_id: str = Field(..., description="Composio's unique trigger identifier used for event matching")
|
|
61
|
+
attributes: Optional[Dict[str, Any]] = Field(
|
|
62
|
+
default=None, description="Optional trigger-specific configuration attributes for filtering"
|
|
63
|
+
)
|
vellum/workflows/types/utils.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import builtins
|
|
1
2
|
from copy import deepcopy
|
|
2
3
|
from datetime import datetime
|
|
3
4
|
import importlib
|
|
@@ -68,6 +69,27 @@ def infer_types(object_: Type, attr_name: str, localns: Optional[Dict[str, Any]]
|
|
|
68
69
|
args = get_args(object_)
|
|
69
70
|
type_var_mapping = {t: a for t, a in zip(origin.__parameters__, args)}
|
|
70
71
|
|
|
72
|
+
if hasattr(object_, "__annotations__") and attr_name in object_.__annotations__:
|
|
73
|
+
annotation_str = object_.__annotations__[attr_name]
|
|
74
|
+
if isinstance(annotation_str, str) and "|" in annotation_str:
|
|
75
|
+
parts = [part.strip() for part in annotation_str.split("|")]
|
|
76
|
+
types_list: List[Type] = []
|
|
77
|
+
for part in parts:
|
|
78
|
+
if part == "None":
|
|
79
|
+
types_list.append(type(None))
|
|
80
|
+
else:
|
|
81
|
+
try:
|
|
82
|
+
module = importlib.import_module(object_.__module__)
|
|
83
|
+
resolved_type = getattr(module, part, None)
|
|
84
|
+
if resolved_type is None:
|
|
85
|
+
resolved_type = getattr(builtins, part, None)
|
|
86
|
+
if resolved_type is not None and isinstance(resolved_type, type):
|
|
87
|
+
types_list.append(resolved_type)
|
|
88
|
+
except (ImportError, AttributeError):
|
|
89
|
+
pass
|
|
90
|
+
if len(types_list) == len(parts):
|
|
91
|
+
return tuple(types_list)
|
|
92
|
+
|
|
71
93
|
type_hints = get_type_hints(class_, localns=LOCAL_NS if localns is None else {**LOCAL_NS, **localns})
|
|
72
94
|
if attr_name in type_hints:
|
|
73
95
|
type_hint = type_hints[attr_name]
|
vellum/workflows/utils/names.py
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import re
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from pydash import snake_case
|
|
2
5
|
|
|
3
6
|
|
|
4
7
|
def pascal_to_title_case(pascal_str: str) -> str:
|
|
@@ -19,3 +22,20 @@ def pascal_to_title_case(pascal_str: str) -> str:
|
|
|
19
22
|
|
|
20
23
|
def snake_to_title_case(snake_str: str) -> str:
|
|
21
24
|
return pascal_to_title_case(snake_str.replace("_", " "))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def create_module_name(*, deployment_name: Optional[str] = None, label: Optional[str] = None) -> str:
|
|
28
|
+
"""Create a module name from potential workflow metadata.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
deployment_name: Optional deployment name to convert to snake_case
|
|
32
|
+
label: Optional label to convert to snake_case (fallback if deployment_name not provided)
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Module name in valid python syntax, or empty string if unable to resolve one based on the arguments
|
|
36
|
+
"""
|
|
37
|
+
if deployment_name:
|
|
38
|
+
return snake_case(deployment_name)
|
|
39
|
+
elif label:
|
|
40
|
+
return snake_case(label)
|
|
41
|
+
return ""
|
|
@@ -4,6 +4,7 @@ from functools import lru_cache
|
|
|
4
4
|
import importlib
|
|
5
5
|
import inspect
|
|
6
6
|
import logging
|
|
7
|
+
import sys
|
|
7
8
|
from uuid import UUID, uuid4
|
|
8
9
|
from typing import (
|
|
9
10
|
Any,
|
|
@@ -68,6 +69,7 @@ from vellum.workflows.exceptions import WorkflowInitializationException
|
|
|
68
69
|
from vellum.workflows.executable import BaseExecutable
|
|
69
70
|
from vellum.workflows.graph import Graph
|
|
70
71
|
from vellum.workflows.inputs.base import BaseInputs
|
|
72
|
+
from vellum.workflows.loaders.base import BaseWorkflowFinder
|
|
71
73
|
from vellum.workflows.nodes.bases import BaseNode
|
|
72
74
|
from vellum.workflows.nodes.mocks import MockNodeExecutionArg
|
|
73
75
|
from vellum.workflows.outputs import BaseOutputs
|
|
@@ -701,7 +703,17 @@ class BaseWorkflow(Generic[InputsType, StateType], BaseExecutable, metaclass=_Ba
|
|
|
701
703
|
except SyntaxError as e:
|
|
702
704
|
raise WorkflowInitializationException(message=f"Syntax Error raised while loading Workflow: {e}") from e
|
|
703
705
|
except ModuleNotFoundError as e:
|
|
704
|
-
|
|
706
|
+
error_message = f"Workflow module not found: {e}"
|
|
707
|
+
raw_data = None
|
|
708
|
+
has_namespace_match = False
|
|
709
|
+
for finder in sys.meta_path:
|
|
710
|
+
if isinstance(finder, BaseWorkflowFinder):
|
|
711
|
+
error_message = finder.format_error_message(error_message)
|
|
712
|
+
if hasattr(finder, "namespace") and e.name and finder.namespace in e.name:
|
|
713
|
+
has_namespace_match = True
|
|
714
|
+
if not has_namespace_match:
|
|
715
|
+
raw_data = {"vellum_on_error_action": "CREATE_CUSTOM_IMAGE"}
|
|
716
|
+
raise WorkflowInitializationException(message=error_message, raw_data=raw_data) from e
|
|
705
717
|
except ImportError as e:
|
|
706
718
|
raise WorkflowInitializationException(message=f"Invalid import found while loading Workflow: {e}") from e
|
|
707
719
|
except NameError as e:
|