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.
Files changed (42) hide show
  1. vellum/__init__.py +2 -0
  2. vellum/client/core/client_wrapper.py +2 -2
  3. vellum/client/core/pydantic_utilities.py +8 -1
  4. vellum/client/reference.md +95 -0
  5. vellum/client/resources/workflow_deployments/client.py +111 -0
  6. vellum/client/resources/workflow_deployments/raw_client.py +121 -0
  7. vellum/client/types/__init__.py +2 -0
  8. vellum/client/types/paginated_workflow_deployment_release_list.py +30 -0
  9. vellum/client/types/vellum_error_code_enum.py +2 -0
  10. vellum/client/types/vellum_sdk_error_code_enum.py +2 -0
  11. vellum/client/types/workflow_execution_event_error_code.py +2 -0
  12. vellum/plugins/pydantic.py +1 -0
  13. vellum/types/paginated_workflow_deployment_release_list.py +3 -0
  14. vellum/workflows/edges/__init__.py +2 -0
  15. vellum/workflows/edges/trigger_edge.py +67 -0
  16. vellum/workflows/errors/types.py +3 -0
  17. vellum/workflows/events/tests/test_event.py +41 -0
  18. vellum/workflows/events/workflow.py +15 -3
  19. vellum/workflows/exceptions.py +9 -1
  20. vellum/workflows/graph/graph.py +93 -0
  21. vellum/workflows/graph/tests/test_graph.py +167 -0
  22. vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +11 -1
  23. vellum/workflows/nodes/displayable/tool_calling_node/node.py +1 -1
  24. vellum/workflows/nodes/displayable/tool_calling_node/utils.py +1 -1
  25. vellum/workflows/ports/port.py +11 -0
  26. vellum/workflows/runner/runner.py +44 -6
  27. vellum/workflows/triggers/__init__.py +4 -0
  28. vellum/workflows/triggers/base.py +125 -0
  29. vellum/workflows/triggers/manual.py +37 -0
  30. vellum/workflows/types/core.py +2 -1
  31. vellum/workflows/workflows/base.py +9 -9
  32. {vellum_ai-1.7.3.dist-info → vellum_ai-1.7.5.dist-info}/METADATA +1 -1
  33. {vellum_ai-1.7.3.dist-info → vellum_ai-1.7.5.dist-info}/RECORD +42 -35
  34. vellum_ee/assets/node-definitions.json +1 -1
  35. vellum_ee/workflows/display/base.py +26 -1
  36. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_serialization.py +1 -1
  37. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py +1 -1
  38. vellum_ee/workflows/display/tests/workflow_serialization/test_manual_trigger_serialization.py +113 -0
  39. vellum_ee/workflows/display/workflows/base_workflow_display.py +63 -10
  40. {vellum_ai-1.7.3.dist-info → vellum_ai-1.7.5.dist-info}/LICENSE +0 -0
  41. {vellum_ai-1.7.3.dist-info → vellum_ai-1.7.5.dist-info}/WHEEL +0 -0
  42. {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
- return {
412
- "workflow_raw_data": {
413
- "nodes": cast(JsonArray, nodes_dict_list),
414
- "edges": edges,
415
- "display_data": self.display_context.workflow_display.display_data.dict(),
416
- "definition": {
417
- "name": self._workflow.__name__,
418
- "module": cast(JsonArray, self._workflow.__module__.split(".")),
419
- },
420
- "output_values": output_values,
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: