vellum-ai 1.7.3__py3-none-any.whl → 1.7.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vellum/__init__.py +2 -0
- vellum/client/core/client_wrapper.py +2 -2
- vellum/client/core/pydantic_utilities.py +8 -1
- vellum/client/reference.md +95 -0
- vellum/client/resources/workflow_deployments/client.py +111 -0
- vellum/client/resources/workflow_deployments/raw_client.py +121 -0
- vellum/client/types/__init__.py +2 -0
- vellum/client/types/paginated_workflow_deployment_release_list.py +30 -0
- vellum/client/types/vellum_error_code_enum.py +2 -0
- vellum/client/types/vellum_sdk_error_code_enum.py +2 -0
- vellum/client/types/workflow_execution_event_error_code.py +2 -0
- vellum/plugins/pydantic.py +1 -0
- vellum/types/paginated_workflow_deployment_release_list.py +3 -0
- vellum/workflows/edges/__init__.py +2 -0
- vellum/workflows/edges/trigger_edge.py +67 -0
- vellum/workflows/errors/types.py +3 -0
- vellum/workflows/events/tests/test_event.py +41 -0
- vellum/workflows/events/workflow.py +15 -3
- vellum/workflows/exceptions.py +9 -1
- vellum/workflows/graph/graph.py +93 -0
- vellum/workflows/graph/tests/test_graph.py +167 -0
- vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +11 -1
- vellum/workflows/nodes/displayable/tool_calling_node/node.py +1 -1
- vellum/workflows/nodes/displayable/tool_calling_node/utils.py +1 -1
- vellum/workflows/ports/port.py +11 -0
- vellum/workflows/runner/runner.py +44 -6
- vellum/workflows/triggers/__init__.py +4 -0
- vellum/workflows/triggers/base.py +125 -0
- vellum/workflows/triggers/manual.py +37 -0
- vellum/workflows/types/core.py +2 -1
- vellum/workflows/workflows/base.py +9 -9
- {vellum_ai-1.7.3.dist-info → vellum_ai-1.7.5.dist-info}/METADATA +1 -1
- {vellum_ai-1.7.3.dist-info → vellum_ai-1.7.5.dist-info}/RECORD +42 -35
- vellum_ee/assets/node-definitions.json +1 -1
- vellum_ee/workflows/display/base.py +26 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_serialization.py +1 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py +1 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_manual_trigger_serialization.py +113 -0
- vellum_ee/workflows/display/workflows/base_workflow_display.py +63 -10
- {vellum_ai-1.7.3.dist-info → vellum_ai-1.7.5.dist-info}/LICENSE +0 -0
- {vellum_ai-1.7.3.dist-info → vellum_ai-1.7.5.dist-info}/WHEEL +0 -0
- {vellum_ai-1.7.3.dist-info → vellum_ai-1.7.5.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,113 @@
|
|
1
|
+
"""Tests for serialization of workflows with ManualTrigger."""
|
2
|
+
|
3
|
+
import pytest
|
4
|
+
from typing import cast
|
5
|
+
|
6
|
+
from deepdiff import DeepDiff
|
7
|
+
|
8
|
+
from vellum.workflows import BaseWorkflow
|
9
|
+
from vellum.workflows.inputs.base import BaseInputs
|
10
|
+
from vellum.workflows.nodes.bases.base import BaseNode
|
11
|
+
from vellum.workflows.state.base import BaseState
|
12
|
+
from vellum.workflows.triggers.base import BaseTrigger
|
13
|
+
from vellum.workflows.triggers.manual import ManualTrigger
|
14
|
+
from vellum.workflows.types.core import JsonArray, JsonObject
|
15
|
+
from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
|
16
|
+
|
17
|
+
|
18
|
+
class Inputs(BaseInputs):
|
19
|
+
input: str
|
20
|
+
|
21
|
+
|
22
|
+
class SimpleNode(BaseNode):
|
23
|
+
class Outputs(BaseNode.Outputs):
|
24
|
+
output = Inputs.input
|
25
|
+
|
26
|
+
|
27
|
+
def create_workflow(trigger=None):
|
28
|
+
"""Factory for creating test workflows."""
|
29
|
+
|
30
|
+
class TestWorkflow(BaseWorkflow[Inputs, BaseState]):
|
31
|
+
graph = trigger >> SimpleNode if trigger else SimpleNode
|
32
|
+
|
33
|
+
class Outputs(BaseWorkflow.Outputs):
|
34
|
+
output = SimpleNode.Outputs.output
|
35
|
+
|
36
|
+
return TestWorkflow
|
37
|
+
|
38
|
+
|
39
|
+
def serialize(workflow_class) -> JsonObject:
|
40
|
+
"""Helper to serialize a workflow."""
|
41
|
+
return get_workflow_display(workflow_class=workflow_class).serialize()
|
42
|
+
|
43
|
+
|
44
|
+
def test_manual_trigger_serialization():
|
45
|
+
"""Workflow with ManualTrigger serializes with trigger field."""
|
46
|
+
result = serialize(create_workflow(ManualTrigger))
|
47
|
+
workflow_raw_data = cast(JsonObject, result["workflow_raw_data"])
|
48
|
+
trigger = cast(JsonObject, workflow_raw_data["trigger"])
|
49
|
+
|
50
|
+
assert trigger["type"] == "MANUAL"
|
51
|
+
assert not DeepDiff(
|
52
|
+
{
|
53
|
+
"type": "MANUAL",
|
54
|
+
"definition": {"name": "ManualTrigger", "module": ["vellum", "workflows", "triggers", "manual"]},
|
55
|
+
},
|
56
|
+
trigger,
|
57
|
+
ignore_order=True,
|
58
|
+
)
|
59
|
+
|
60
|
+
|
61
|
+
def test_no_trigger_serialization():
|
62
|
+
"""Workflow without trigger has no trigger field."""
|
63
|
+
result = serialize(create_workflow())
|
64
|
+
workflow_raw_data = cast(JsonObject, result["workflow_raw_data"])
|
65
|
+
assert "trigger" not in workflow_raw_data
|
66
|
+
|
67
|
+
|
68
|
+
def test_manual_trigger_multiple_entrypoints():
|
69
|
+
"""ManualTrigger with multiple entrypoints."""
|
70
|
+
|
71
|
+
class NodeA(BaseNode):
|
72
|
+
class Outputs(BaseNode.Outputs):
|
73
|
+
output = Inputs.input
|
74
|
+
|
75
|
+
class NodeB(BaseNode):
|
76
|
+
class Outputs(BaseNode.Outputs):
|
77
|
+
output = Inputs.input
|
78
|
+
|
79
|
+
class MultiWorkflow(BaseWorkflow[Inputs, BaseState]):
|
80
|
+
graph = ManualTrigger >> {NodeA, NodeB}
|
81
|
+
|
82
|
+
class Outputs(BaseWorkflow.Outputs):
|
83
|
+
output_a = NodeA.Outputs.output
|
84
|
+
output_b = NodeB.Outputs.output
|
85
|
+
|
86
|
+
result = serialize(MultiWorkflow)
|
87
|
+
workflow_data = cast(JsonObject, result["workflow_raw_data"])
|
88
|
+
trigger = cast(JsonObject, workflow_data["trigger"])
|
89
|
+
nodes = cast(JsonArray, workflow_data["nodes"])
|
90
|
+
|
91
|
+
assert trigger["type"] == "MANUAL"
|
92
|
+
assert len([n for n in nodes if cast(JsonObject, n)["type"] == "GENERIC"]) >= 2
|
93
|
+
|
94
|
+
|
95
|
+
def test_serialized_workflow_structure():
|
96
|
+
"""Verify complete structure of serialized workflow."""
|
97
|
+
result = serialize(create_workflow(ManualTrigger))
|
98
|
+
workflow_raw_data = cast(JsonObject, result["workflow_raw_data"])
|
99
|
+
definition = cast(JsonObject, workflow_raw_data["definition"])
|
100
|
+
|
101
|
+
assert result.keys() == {"workflow_raw_data", "input_variables", "state_variables", "output_variables"}
|
102
|
+
assert workflow_raw_data.keys() == {"nodes", "edges", "display_data", "definition", "output_values", "trigger"}
|
103
|
+
assert definition["name"] == "TestWorkflow"
|
104
|
+
|
105
|
+
|
106
|
+
def test_unknown_trigger_type():
|
107
|
+
"""Unknown trigger types raise ValueError."""
|
108
|
+
|
109
|
+
class UnknownTrigger(BaseTrigger):
|
110
|
+
pass
|
111
|
+
|
112
|
+
with pytest.raises(ValueError, match="Unknown trigger type: UnknownTrigger"):
|
113
|
+
serialize(create_workflow(UnknownTrigger))
|
@@ -37,6 +37,7 @@ from vellum_ee.workflows.display.base import (
|
|
37
37
|
WorkflowInputsDisplay,
|
38
38
|
WorkflowMetaDisplay,
|
39
39
|
WorkflowOutputDisplay,
|
40
|
+
get_trigger_type_mapping,
|
40
41
|
)
|
41
42
|
from vellum_ee.workflows.display.editor.types import NodeDisplayData, NodeDisplayPosition
|
42
43
|
from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
|
@@ -408,22 +409,74 @@ class BaseWorkflowDisplay(Generic[WorkflowType]):
|
|
408
409
|
except Exception as e:
|
409
410
|
self.display_context.add_error(e)
|
410
411
|
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
"
|
412
|
+
# Serialize workflow-level trigger if present
|
413
|
+
trigger_data: Optional[JsonObject] = self._serialize_workflow_trigger()
|
414
|
+
|
415
|
+
workflow_raw_data: JsonObject = {
|
416
|
+
"nodes": cast(JsonArray, nodes_dict_list),
|
417
|
+
"edges": edges,
|
418
|
+
"display_data": self.display_context.workflow_display.display_data.dict(),
|
419
|
+
"definition": {
|
420
|
+
"name": self._workflow.__name__,
|
421
|
+
"module": cast(JsonArray, self._workflow.__module__.split(".")),
|
421
422
|
},
|
423
|
+
"output_values": output_values,
|
424
|
+
}
|
425
|
+
|
426
|
+
if trigger_data is not None:
|
427
|
+
workflow_raw_data["trigger"] = trigger_data
|
428
|
+
|
429
|
+
return {
|
430
|
+
"workflow_raw_data": workflow_raw_data,
|
422
431
|
"input_variables": input_variables,
|
423
432
|
"state_variables": state_variables,
|
424
433
|
"output_variables": output_variables,
|
425
434
|
}
|
426
435
|
|
436
|
+
def _serialize_workflow_trigger(self) -> Optional[JsonObject]:
|
437
|
+
"""
|
438
|
+
Serialize workflow-level trigger information.
|
439
|
+
|
440
|
+
Returns:
|
441
|
+
JsonObject with trigger data if a trigger is present, None otherwise
|
442
|
+
"""
|
443
|
+
# Get all trigger edges from the workflow's subgraphs
|
444
|
+
trigger_edges = []
|
445
|
+
for subgraph in self._workflow.get_subgraphs():
|
446
|
+
trigger_edges.extend(list(subgraph.trigger_edges))
|
447
|
+
|
448
|
+
if not trigger_edges:
|
449
|
+
# No workflow-level trigger defined
|
450
|
+
return None
|
451
|
+
|
452
|
+
# Get the trigger class from the first edge
|
453
|
+
trigger_class = trigger_edges[0].trigger_class
|
454
|
+
|
455
|
+
# Validate that all trigger edges use the same trigger type
|
456
|
+
trigger_type_mapping = get_trigger_type_mapping()
|
457
|
+
for edge in trigger_edges:
|
458
|
+
if edge.trigger_class != trigger_class:
|
459
|
+
raise ValueError(
|
460
|
+
f"Mixed trigger types not supported. Found {trigger_class.__name__} and "
|
461
|
+
f"{edge.trigger_class.__name__} in the same workflow."
|
462
|
+
)
|
463
|
+
|
464
|
+
# Get the trigger type from the mapping
|
465
|
+
trigger_type = trigger_type_mapping.get(trigger_class)
|
466
|
+
if trigger_type is None:
|
467
|
+
raise ValueError(
|
468
|
+
f"Unknown trigger type: {trigger_class.__name__}. "
|
469
|
+
f"Please add it to the trigger type mapping in get_trigger_type_mapping()."
|
470
|
+
)
|
471
|
+
|
472
|
+
return {
|
473
|
+
"type": trigger_type.value,
|
474
|
+
"definition": {
|
475
|
+
"name": trigger_class.__name__,
|
476
|
+
"module": cast(JsonArray, trigger_class.__module__.split(".")),
|
477
|
+
},
|
478
|
+
}
|
479
|
+
|
427
480
|
def _serialize_edge_display_data(self, edge_display: EdgeDisplay) -> Optional[JsonObject]:
|
428
481
|
"""Serialize edge display data, returning None if no display data is present."""
|
429
482
|
if edge_display.z_index is not None:
|
File without changes
|
File without changes
|
File without changes
|