vellum-ai 1.2.0__py3-none-any.whl → 1.2.2__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 +18 -1
- vellum/client/__init__.py +3 -0
- vellum/client/core/client_wrapper.py +2 -2
- vellum/client/errors/__init__.py +10 -1
- vellum/client/errors/too_many_requests_error.py +11 -0
- vellum/client/errors/unauthorized_error.py +11 -0
- vellum/client/reference.md +94 -0
- vellum/client/resources/__init__.py +2 -0
- vellum/client/resources/events/__init__.py +4 -0
- vellum/client/resources/events/client.py +165 -0
- vellum/client/resources/events/raw_client.py +207 -0
- vellum/client/types/__init__.py +6 -0
- vellum/client/types/error_detail_response.py +22 -0
- vellum/client/types/event_create_response.py +26 -0
- vellum/client/types/execution_thinking_vellum_value.py +1 -1
- vellum/client/types/thinking_vellum_value.py +1 -1
- vellum/client/types/thinking_vellum_value_request.py +1 -1
- vellum/client/types/workflow_event.py +33 -0
- vellum/errors/too_many_requests_error.py +3 -0
- vellum/errors/unauthorized_error.py +3 -0
- vellum/prompts/blocks/compilation.py +13 -11
- vellum/resources/events/__init__.py +3 -0
- vellum/resources/events/client.py +3 -0
- vellum/resources/events/raw_client.py +3 -0
- vellum/types/error_detail_response.py +3 -0
- vellum/types/event_create_response.py +3 -0
- vellum/types/workflow_event.py +3 -0
- vellum/workflows/emitters/vellum_emitter.py +16 -69
- vellum/workflows/events/tests/test_event.py +1 -0
- vellum/workflows/events/workflow.py +3 -0
- vellum/workflows/nodes/bases/base.py +0 -1
- vellum/workflows/nodes/core/inline_subworkflow_node/tests/test_node.py +35 -0
- vellum/workflows/nodes/displayable/bases/api_node/node.py +4 -0
- vellum/workflows/nodes/displayable/bases/api_node/tests/test_node.py +26 -0
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +6 -1
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/tests/test_inline_prompt_node.py +22 -0
- vellum/workflows/nodes/displayable/bases/utils.py +4 -2
- vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +88 -2
- vellum/workflows/nodes/displayable/tool_calling_node/node.py +1 -0
- vellum/workflows/nodes/displayable/tool_calling_node/tests/test_node.py +85 -1
- vellum/workflows/nodes/displayable/tool_calling_node/tests/test_utils.py +12 -0
- vellum/workflows/nodes/displayable/tool_calling_node/utils.py +5 -2
- vellum/workflows/ports/port.py +1 -11
- vellum/workflows/sandbox.py +6 -3
- vellum/workflows/state/context.py +14 -0
- vellum/workflows/state/encoder.py +19 -1
- vellum/workflows/types/definition.py +4 -4
- vellum/workflows/utils/hmac.py +44 -0
- vellum/workflows/utils/vellum_variables.py +5 -3
- vellum/workflows/workflows/base.py +1 -0
- {vellum_ai-1.2.0.dist-info → vellum_ai-1.2.2.dist-info}/METADATA +1 -1
- {vellum_ai-1.2.0.dist-info → vellum_ai-1.2.2.dist-info}/RECORD +94 -76
- vellum_ee/workflows/display/nodes/base_node_display.py +19 -10
- vellum_ee/workflows/display/nodes/vellum/api_node.py +1 -4
- vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +1 -4
- vellum_ee/workflows/display/nodes/vellum/conditional_node.py +1 -4
- vellum_ee/workflows/display/nodes/vellum/error_node.py +6 -4
- vellum_ee/workflows/display/nodes/vellum/final_output_node.py +6 -4
- vellum_ee/workflows/display/nodes/vellum/guardrail_node.py +1 -4
- vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +34 -15
- vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +1 -4
- vellum_ee/workflows/display/nodes/vellum/map_node.py +1 -4
- vellum_ee/workflows/display/nodes/vellum/merge_node.py +1 -4
- vellum_ee/workflows/display/nodes/vellum/note_node.py +2 -4
- vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py +1 -4
- vellum_ee/workflows/display/nodes/vellum/search_node.py +1 -4
- vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py +1 -4
- vellum_ee/workflows/display/nodes/vellum/templating_node.py +1 -4
- vellum_ee/workflows/display/nodes/vellum/tests/test_code_execution_node.py +1 -0
- vellum_ee/workflows/display/nodes/vellum/tests/test_tool_calling_node.py +239 -1
- vellum_ee/workflows/display/tests/test_base_workflow_display.py +53 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_api_node_serialization.py +4 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +12 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +16 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py +5 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_guardrail_node_serialization.py +4 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +4 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +4 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +4 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_prompt_deployment_serialization.py +12 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_search_node_serialization.py +4 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_subworkflow_deployment_serialization.py +4 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_node_serialization.py +4 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py +5 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_composio_serialization.py +1 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +5 -0
- vellum_ee/workflows/display/utils/expressions.py +4 -0
- vellum_ee/workflows/display/utils/registry.py +46 -0
- vellum_ee/workflows/display/workflows/base_workflow_display.py +1 -1
- vellum_ee/workflows/tests/test_registry.py +169 -0
- vellum_ee/workflows/tests/test_server.py +72 -0
- {vellum_ai-1.2.0.dist-info → vellum_ai-1.2.2.dist-info}/LICENSE +0 -0
- {vellum_ai-1.2.0.dist-info → vellum_ai-1.2.2.dist-info}/WHEEL +0 -0
- {vellum_ai-1.2.0.dist-info → vellum_ai-1.2.2.dist-info}/entry_points.txt +0 -0
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py
CHANGED
@@ -114,6 +114,11 @@ def test_serialize_workflow():
|
|
114
114
|
"name": "FailNode",
|
115
115
|
"module": ["tests", "workflows", "basic_error_node", "workflow"],
|
116
116
|
},
|
117
|
+
"trigger": {
|
118
|
+
"id": "70c19f1c-309c-4a5d-ba65-664c0bb2fedf",
|
119
|
+
"merge_behavior": "AWAIT_ATTRIBUTES",
|
120
|
+
},
|
121
|
+
"ports": [],
|
117
122
|
},
|
118
123
|
error_node,
|
119
124
|
ignore_order=True,
|
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_guardrail_node_serialization.py
CHANGED
@@ -117,6 +117,10 @@ def test_serialize_workflow():
|
|
117
117
|
"module": ["tests", "workflows", "basic_guardrail_node", "workflow"],
|
118
118
|
"name": "ExampleGuardrailNode",
|
119
119
|
},
|
120
|
+
"trigger": {
|
121
|
+
"id": "ce5b85b1-eded-46dd-b4b7-020afcdc67ab",
|
122
|
+
"merge_behavior": "AWAIT_ANY",
|
123
|
+
},
|
120
124
|
"ports": [{"id": "0ed87407-697e-4ae9-ab9b-6c5cc2e57cf7", "name": "default", "type": "DEFAULT"}],
|
121
125
|
}
|
122
126
|
|
@@ -324,6 +324,10 @@ def test_serialize_workflow():
|
|
324
324
|
"name": "ExampleInlineSubworkflowNode",
|
325
325
|
"module": ["tests", "workflows", "basic_inline_subworkflow", "workflow"],
|
326
326
|
},
|
327
|
+
"trigger": {
|
328
|
+
"id": "859a75a6-1bd2-4350-9509-4af66245e8e4",
|
329
|
+
"merge_behavior": "AWAIT_ATTRIBUTES",
|
330
|
+
},
|
327
331
|
"ports": [{"id": "cfd831bc-ee7f-44d0-8d76-0ba0cd0277dc", "name": "default", "type": "DEFAULT"}],
|
328
332
|
},
|
329
333
|
subworkflow_node,
|
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py
CHANGED
@@ -286,6 +286,10 @@ def test_serialize_workflow():
|
|
286
286
|
"name": "MapFruitsNode",
|
287
287
|
"module": ["tests", "workflows", "basic_map_node", "workflow"],
|
288
288
|
},
|
289
|
+
"trigger": {
|
290
|
+
"id": "b5e8182e-20c5-482b-b4c5-4dde48c01472",
|
291
|
+
"merge_behavior": "AWAIT_ATTRIBUTES",
|
292
|
+
},
|
289
293
|
"ports": [{"id": "a2171a61-0657-43ad-b6d9-cf93ce3270d0", "name": "default", "type": "DEFAULT"}],
|
290
294
|
},
|
291
295
|
map_node,
|
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py
CHANGED
@@ -83,6 +83,10 @@ def test_serialize_workflow__await_all():
|
|
83
83
|
"module": ["tests", "workflows", "basic_merge_node", "await_all_workflow"],
|
84
84
|
"name": "AwaitAllMergeNode",
|
85
85
|
},
|
86
|
+
"trigger": {
|
87
|
+
"id": "0efd256f-f5f6-45fe-9adb-651780f5e63d",
|
88
|
+
"merge_behavior": "AWAIT_ALL",
|
89
|
+
},
|
86
90
|
"ports": [{"id": "3bbc469f-0fb0-4b3d-a28b-746fefec2818", "name": "default", "type": "DEFAULT"}],
|
87
91
|
},
|
88
92
|
merge_node,
|
@@ -150,6 +150,10 @@ def test_serialize_workflow(vellum_client):
|
|
150
150
|
"name": "ExamplePromptDeploymentNode",
|
151
151
|
"module": ["tests", "workflows", "basic_text_prompt_deployment", "workflow"],
|
152
152
|
},
|
153
|
+
"trigger": {
|
154
|
+
"id": "b7605c48-0937-4ecc-914e-0d1058130e65",
|
155
|
+
"merge_behavior": "AWAIT_ANY",
|
156
|
+
},
|
153
157
|
"ports": [{"id": "2f26c7e0-283d-4f04-b639-adebb56bc679", "name": "default", "type": "DEFAULT"}],
|
154
158
|
"outputs": [
|
155
159
|
{"id": "180355a8-e67c-4ce6-9ac3-e5dbb75a6629", "name": "json", "type": "JSON", "value": None},
|
@@ -384,6 +388,10 @@ def test_serialize_workflow_with_prompt_and_templating(vellum_client):
|
|
384
388
|
"workflow_with_prompt_deployment_json_reference",
|
385
389
|
],
|
386
390
|
},
|
391
|
+
"trigger": {
|
392
|
+
"id": "b7605c48-0937-4ecc-914e-0d1058130e65",
|
393
|
+
"merge_behavior": "AWAIT_ANY",
|
394
|
+
},
|
387
395
|
"ports": [{"id": "2f26c7e0-283d-4f04-b639-adebb56bc679", "name": "default", "type": "DEFAULT"}],
|
388
396
|
"outputs": [
|
389
397
|
{"id": "180355a8-e67c-4ce6-9ac3-e5dbb75a6629", "name": "json", "type": "JSON", "value": None},
|
@@ -479,6 +487,10 @@ def test_serialize_workflow_with_prompt_and_templating(vellum_client):
|
|
479
487
|
"workflow_with_prompt_deployment_json_reference",
|
480
488
|
],
|
481
489
|
},
|
490
|
+
"trigger": {
|
491
|
+
"id": "58427684-3848-498a-8299-c6b0fc70265d",
|
492
|
+
"merge_behavior": "AWAIT_ATTRIBUTES",
|
493
|
+
},
|
482
494
|
"ports": [{"id": "39317827-df43-4f5a-bfbc-20bffc839748", "name": "default", "type": "DEFAULT"}],
|
483
495
|
}
|
484
496
|
|
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_search_node_serialization.py
CHANGED
@@ -238,6 +238,10 @@ def test_serialize_workflow():
|
|
238
238
|
"name": "SimpleSearchNode",
|
239
239
|
"module": ["tests", "workflows", "basic_search_node", "workflow"],
|
240
240
|
},
|
241
|
+
"trigger": {
|
242
|
+
"id": "6d50305f-588b-469f-a042-b0767d3f99b1",
|
243
|
+
"merge_behavior": "AWAIT_ANY",
|
244
|
+
},
|
241
245
|
"ports": [{"id": "00ae06b3-f8d9-4ae6-9fbf-e4ff4d520e9b", "name": "default", "type": "DEFAULT"}],
|
242
246
|
}
|
243
247
|
|
@@ -153,6 +153,10 @@ def test_serialize_workflow(vellum_client):
|
|
153
153
|
"module": ["tests", "workflows", "basic_subworkflow_deployment", "workflow"],
|
154
154
|
"name": "ExampleSubworkflowDeploymentNode",
|
155
155
|
},
|
156
|
+
"trigger": {
|
157
|
+
"id": "e4d80502-9281-42c8-91e3-10817bcd7d9e",
|
158
|
+
"merge_behavior": "AWAIT_ANY",
|
159
|
+
},
|
156
160
|
"ports": [{"id": "ab0db8a9-7b53-4d88-8667-273b31303273", "name": "default", "type": "DEFAULT"}],
|
157
161
|
}
|
158
162
|
|
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_node_serialization.py
CHANGED
@@ -112,6 +112,10 @@ def test_serialize_workflow():
|
|
112
112
|
"name": "ExampleTemplatingNode",
|
113
113
|
"module": ["tests", "workflows", "basic_templating_node", "workflow_with_json_input"],
|
114
114
|
},
|
115
|
+
"trigger": {
|
116
|
+
"id": "58427684-3848-498a-8299-c6b0fc70265d",
|
117
|
+
"merge_behavior": "AWAIT_ATTRIBUTES",
|
118
|
+
},
|
115
119
|
"ports": [{"id": "39317827-df43-4f5a-bfbc-20bffc839748", "name": "default", "type": "DEFAULT"}],
|
116
120
|
},
|
117
121
|
templating_node,
|
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py
CHANGED
@@ -103,4 +103,9 @@ def test_serialize_workflow():
|
|
103
103
|
"value": {"type": "WORKFLOW_INPUT", "input_variable_id": "e39a7b63-de15-490a-ae9b-8112c767aea0"},
|
104
104
|
}
|
105
105
|
],
|
106
|
+
"trigger": {
|
107
|
+
"id": "0173d3c6-11d1-44b7-b070-ca9ff5119046",
|
108
|
+
"merge_behavior": "AWAIT_ANY",
|
109
|
+
},
|
110
|
+
"ports": [],
|
106
111
|
}
|
vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py
CHANGED
@@ -81,6 +81,11 @@ def test_serialize_workflow__missing_final_output_node():
|
|
81
81
|
},
|
82
82
|
}
|
83
83
|
],
|
84
|
+
"trigger": {
|
85
|
+
"id": "a0c2eb7a-398e-4f28-b63d-f3bae9b563ee",
|
86
|
+
"merge_behavior": "AWAIT_ANY",
|
87
|
+
},
|
88
|
+
"ports": [],
|
84
89
|
},
|
85
90
|
{
|
86
91
|
"id": "bb88768d-472e-4997-b7ea-de09163d1b4c",
|
@@ -11,6 +11,7 @@ from vellum.workflows.expressions.and_ import AndExpression
|
|
11
11
|
from vellum.workflows.expressions.begins_with import BeginsWithExpression
|
12
12
|
from vellum.workflows.expressions.between import BetweenExpression
|
13
13
|
from vellum.workflows.expressions.coalesce_expression import CoalesceExpression
|
14
|
+
from vellum.workflows.expressions.concat import ConcatExpression
|
14
15
|
from vellum.workflows.expressions.contains import ContainsExpression
|
15
16
|
from vellum.workflows.expressions.does_not_begin_with import DoesNotBeginWithExpression
|
16
17
|
from vellum.workflows.expressions.does_not_contain import DoesNotContainExpression
|
@@ -105,6 +106,8 @@ def convert_descriptor_to_operator(descriptor: BaseDescriptor) -> LogicalOperato
|
|
105
106
|
return "+"
|
106
107
|
elif isinstance(descriptor, MinusExpression):
|
107
108
|
return "-"
|
109
|
+
elif isinstance(descriptor, ConcatExpression):
|
110
|
+
return "concat"
|
108
111
|
else:
|
109
112
|
raise ValueError(f"Unsupported descriptor type: {descriptor}")
|
110
113
|
|
@@ -171,6 +174,7 @@ def _serialize_condition(display_context: "WorkflowDisplayContext", condition: B
|
|
171
174
|
AndExpression,
|
172
175
|
BeginsWithExpression,
|
173
176
|
CoalesceExpression,
|
177
|
+
ConcatExpression,
|
174
178
|
ContainsExpression,
|
175
179
|
DoesNotBeginWithExpression,
|
176
180
|
DoesNotContainExpression,
|
@@ -1,10 +1,14 @@
|
|
1
|
+
from uuid import UUID
|
1
2
|
from typing import TYPE_CHECKING, Dict, Optional, Type
|
2
3
|
|
4
|
+
from vellum.workflows.events.types import BaseEvent
|
3
5
|
from vellum.workflows.nodes import BaseNode
|
4
6
|
from vellum.workflows.workflows.base import BaseWorkflow
|
5
7
|
|
6
8
|
if TYPE_CHECKING:
|
9
|
+
from vellum.workflows.events.types import ParentContext
|
7
10
|
from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
|
11
|
+
from vellum_ee.workflows.display.types import WorkflowDisplayContext
|
8
12
|
from vellum_ee.workflows.display.workflows.base_workflow_display import BaseWorkflowDisplay
|
9
13
|
|
10
14
|
|
@@ -14,6 +18,9 @@ _workflow_display_registry: Dict[Type[BaseWorkflow], Type["BaseWorkflowDisplay"]
|
|
14
18
|
# Used to store the mapping between node types and their display classes
|
15
19
|
_node_display_registry: Dict[Type[BaseNode], Type["BaseNodeDisplay"]] = {}
|
16
20
|
|
21
|
+
# Registry to store active workflow display contexts by span ID for nested workflow inheritance
|
22
|
+
_active_workflow_display_contexts: Dict[UUID, "WorkflowDisplayContext"] = {}
|
23
|
+
|
17
24
|
|
18
25
|
def get_from_workflow_display_registry(workflow_class: Type[BaseWorkflow]) -> Optional[Type["BaseWorkflowDisplay"]]:
|
19
26
|
return _workflow_display_registry.get(workflow_class)
|
@@ -35,3 +42,42 @@ def get_from_node_display_registry(node_class: Type[BaseNode]) -> Optional[Type[
|
|
35
42
|
|
36
43
|
def register_node_display_class(node_class: Type[BaseNode], node_display_class: Type["BaseNodeDisplay"]) -> None:
|
37
44
|
_node_display_registry[node_class] = node_display_class
|
45
|
+
|
46
|
+
|
47
|
+
def register_workflow_display_context(span_id: UUID, display_context: "WorkflowDisplayContext") -> None:
|
48
|
+
"""Register a workflow display context by span ID for nested workflow inheritance."""
|
49
|
+
_active_workflow_display_contexts[span_id] = display_context
|
50
|
+
|
51
|
+
|
52
|
+
def _get_parent_display_context_for_span(span_id: UUID) -> Optional["WorkflowDisplayContext"]:
|
53
|
+
"""Get the parent display context for a given span ID."""
|
54
|
+
return _active_workflow_display_contexts.get(span_id)
|
55
|
+
|
56
|
+
|
57
|
+
def get_parent_display_context_from_event(event: BaseEvent) -> Optional["WorkflowDisplayContext"]:
|
58
|
+
"""Extract parent display context from an event by traversing the parent chain.
|
59
|
+
|
60
|
+
This function traverses up the parent chain starting from the event's parent,
|
61
|
+
looking for workflow parents and attempting to get their display context.
|
62
|
+
|
63
|
+
Args:
|
64
|
+
event: The event to extract parent display context from
|
65
|
+
|
66
|
+
Returns:
|
67
|
+
The parent workflow display context if found, None otherwise
|
68
|
+
"""
|
69
|
+
if not event.parent:
|
70
|
+
return None
|
71
|
+
|
72
|
+
current_parent: Optional["ParentContext"] = event.parent
|
73
|
+
while current_parent:
|
74
|
+
if current_parent.type == "WORKFLOW":
|
75
|
+
# Found a parent workflow, try to get its display context
|
76
|
+
parent_span_id = current_parent.span_id
|
77
|
+
parent_display_context = _get_parent_display_context_for_span(parent_span_id)
|
78
|
+
if parent_display_context:
|
79
|
+
return parent_display_context
|
80
|
+
# Move up the parent chain
|
81
|
+
current_parent = current_parent.parent
|
82
|
+
|
83
|
+
return None
|
@@ -528,7 +528,7 @@ class BaseWorkflowDisplay(Generic[WorkflowType]):
|
|
528
528
|
workflow_input_displays: WorkflowInputsDisplays = {}
|
529
529
|
# If we're dealing with a nested workflow, then it should have access to the inputs of its parents.
|
530
530
|
global_workflow_input_displays = (
|
531
|
-
copy(self._parent_display_context.
|
531
|
+
copy(self._parent_display_context.global_workflow_input_displays) if self._parent_display_context else {}
|
532
532
|
)
|
533
533
|
for workflow_input in self._workflow.get_inputs_class():
|
534
534
|
workflow_input_display_overrides = self.inputs_display.get(workflow_input)
|
@@ -0,0 +1,169 @@
|
|
1
|
+
from datetime import datetime, timezone
|
2
|
+
from uuid import uuid4
|
3
|
+
|
4
|
+
from vellum.workflows.events.types import NodeParentContext, WorkflowParentContext
|
5
|
+
from vellum.workflows.events.workflow import WorkflowExecutionInitiatedBody, WorkflowExecutionInitiatedEvent
|
6
|
+
from vellum.workflows.inputs.base import BaseInputs
|
7
|
+
from vellum.workflows.nodes import BaseNode
|
8
|
+
from vellum.workflows.state.base import BaseState
|
9
|
+
from vellum.workflows.workflows.base import BaseWorkflow
|
10
|
+
from vellum_ee.workflows.display.utils.registry import (
|
11
|
+
get_parent_display_context_from_event,
|
12
|
+
register_workflow_display_context,
|
13
|
+
)
|
14
|
+
|
15
|
+
|
16
|
+
class MockInputs(BaseInputs):
|
17
|
+
pass
|
18
|
+
|
19
|
+
|
20
|
+
class MockState(BaseState):
|
21
|
+
pass
|
22
|
+
|
23
|
+
|
24
|
+
class MockNode(BaseNode):
|
25
|
+
pass
|
26
|
+
|
27
|
+
|
28
|
+
class MockWorkflow(BaseWorkflow[MockInputs, MockState]):
|
29
|
+
pass
|
30
|
+
|
31
|
+
|
32
|
+
class MockWorkflowDisplayContext:
|
33
|
+
pass
|
34
|
+
|
35
|
+
|
36
|
+
def test_get_parent_display_context_from_event__no_parent():
|
37
|
+
"""Test event with no parent returns None"""
|
38
|
+
# GIVEN a workflow execution initiated event with no parent
|
39
|
+
event: WorkflowExecutionInitiatedEvent = WorkflowExecutionInitiatedEvent(
|
40
|
+
id=uuid4(),
|
41
|
+
timestamp=datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
|
42
|
+
trace_id=uuid4(),
|
43
|
+
span_id=uuid4(),
|
44
|
+
body=WorkflowExecutionInitiatedBody(
|
45
|
+
workflow_definition=MockWorkflow,
|
46
|
+
inputs=MockInputs(),
|
47
|
+
),
|
48
|
+
parent=None, # No parent
|
49
|
+
)
|
50
|
+
|
51
|
+
# WHEN getting parent display context
|
52
|
+
result = get_parent_display_context_from_event(event)
|
53
|
+
|
54
|
+
# THEN it should return None
|
55
|
+
assert result is None
|
56
|
+
|
57
|
+
|
58
|
+
def test_get_parent_display_context_from_event__non_workflow_parent():
|
59
|
+
"""Test event with non-workflow parent continues traversal"""
|
60
|
+
# GIVEN an event with a non-workflow parent (NodeParentContext)
|
61
|
+
non_workflow_parent = NodeParentContext(node_definition=MockNode, span_id=uuid4(), parent=None)
|
62
|
+
|
63
|
+
event: WorkflowExecutionInitiatedEvent = WorkflowExecutionInitiatedEvent(
|
64
|
+
id=uuid4(),
|
65
|
+
timestamp=datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
|
66
|
+
trace_id=uuid4(),
|
67
|
+
span_id=uuid4(),
|
68
|
+
body=WorkflowExecutionInitiatedBody(
|
69
|
+
workflow_definition=MockWorkflow,
|
70
|
+
inputs=MockInputs(),
|
71
|
+
),
|
72
|
+
parent=non_workflow_parent,
|
73
|
+
)
|
74
|
+
|
75
|
+
# WHEN getting parent display context
|
76
|
+
result = get_parent_display_context_from_event(event)
|
77
|
+
|
78
|
+
# THEN it should return None (no workflow parent found)
|
79
|
+
assert result is None
|
80
|
+
|
81
|
+
|
82
|
+
def test_get_parent_display_context_from_event__nested_workflow_parents():
|
83
|
+
"""Test event with nested workflow parents traverses correctly"""
|
84
|
+
# GIVEN a chain of nested contexts:
|
85
|
+
# Event -> WorkflowParent -> NodeParent -> MiddleWorkflowParent -> NodeParent
|
86
|
+
|
87
|
+
# Top level workflow parent
|
88
|
+
top_workflow_span_id = uuid4()
|
89
|
+
top_context = MockWorkflowDisplayContext()
|
90
|
+
register_workflow_display_context(top_workflow_span_id, top_context) # type: ignore[arg-type]
|
91
|
+
|
92
|
+
top_workflow_parent = WorkflowParentContext(
|
93
|
+
workflow_definition=MockWorkflow, span_id=top_workflow_span_id, parent=None
|
94
|
+
)
|
95
|
+
|
96
|
+
top_node_parent = NodeParentContext(node_definition=MockNode, span_id=uuid4(), parent=top_workflow_parent)
|
97
|
+
|
98
|
+
# AND middle workflow parent (no display context)
|
99
|
+
middle_workflow_span_id = uuid4()
|
100
|
+
middle_workflow_parent = WorkflowParentContext(
|
101
|
+
workflow_definition=MockWorkflow, span_id=middle_workflow_span_id, parent=top_node_parent
|
102
|
+
)
|
103
|
+
|
104
|
+
# AND node parent between middle workflow and event
|
105
|
+
node_parent = NodeParentContext(node_definition=MockNode, span_id=uuid4(), parent=middle_workflow_parent)
|
106
|
+
|
107
|
+
event: WorkflowExecutionInitiatedEvent = WorkflowExecutionInitiatedEvent(
|
108
|
+
id=uuid4(),
|
109
|
+
timestamp=datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
|
110
|
+
trace_id=uuid4(),
|
111
|
+
span_id=uuid4(),
|
112
|
+
body=WorkflowExecutionInitiatedBody(
|
113
|
+
workflow_definition=MockWorkflow,
|
114
|
+
inputs=MockInputs(),
|
115
|
+
),
|
116
|
+
parent=node_parent,
|
117
|
+
)
|
118
|
+
|
119
|
+
# WHEN getting parent display context
|
120
|
+
result = get_parent_display_context_from_event(event)
|
121
|
+
|
122
|
+
# THEN it should find the top-level workflow context
|
123
|
+
assert result == top_context
|
124
|
+
|
125
|
+
|
126
|
+
def test_get_parent_display_context_from_event__middle_workflow_has_context():
|
127
|
+
"""Test event returns middle workflow context when it's the first one with registered context"""
|
128
|
+
# GIVEN a chain of nested contexts:
|
129
|
+
# Event -> WorkflowParent -> NodeParent -> MiddleWorkflowParent -> NodeParent
|
130
|
+
|
131
|
+
top_workflow_span_id = uuid4()
|
132
|
+
top_context = MockWorkflowDisplayContext()
|
133
|
+
register_workflow_display_context(top_workflow_span_id, top_context) # type: ignore[arg-type]
|
134
|
+
|
135
|
+
top_workflow_parent = WorkflowParentContext(
|
136
|
+
workflow_definition=MockWorkflow, span_id=top_workflow_span_id, parent=None
|
137
|
+
)
|
138
|
+
|
139
|
+
# AND node parent between top workflow and middle workflow
|
140
|
+
top_node_parent = NodeParentContext(node_definition=MockNode, span_id=uuid4(), parent=top_workflow_parent)
|
141
|
+
|
142
|
+
# AND middle workflow parent
|
143
|
+
middle_workflow_span_id = uuid4()
|
144
|
+
middle_context = MockWorkflowDisplayContext()
|
145
|
+
register_workflow_display_context(middle_workflow_span_id, middle_context) # type: ignore[arg-type]
|
146
|
+
|
147
|
+
middle_workflow_parent = WorkflowParentContext(
|
148
|
+
workflow_definition=MockWorkflow, span_id=middle_workflow_span_id, parent=top_node_parent
|
149
|
+
)
|
150
|
+
|
151
|
+
node_parent = NodeParentContext(node_definition=MockNode, span_id=uuid4(), parent=middle_workflow_parent)
|
152
|
+
|
153
|
+
event: WorkflowExecutionInitiatedEvent = WorkflowExecutionInitiatedEvent(
|
154
|
+
id=uuid4(),
|
155
|
+
timestamp=datetime(2024, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
|
156
|
+
trace_id=uuid4(),
|
157
|
+
span_id=uuid4(),
|
158
|
+
body=WorkflowExecutionInitiatedBody(
|
159
|
+
workflow_definition=MockWorkflow,
|
160
|
+
inputs=MockInputs(),
|
161
|
+
),
|
162
|
+
parent=node_parent,
|
163
|
+
)
|
164
|
+
|
165
|
+
# WHEN getting parent display context
|
166
|
+
result = get_parent_display_context_from_event(event)
|
167
|
+
|
168
|
+
# THEN it should find the MIDDLE workflow context
|
169
|
+
assert result == middle_context
|
@@ -8,6 +8,7 @@ from vellum.client.types.number_vellum_value import NumberVellumValue
|
|
8
8
|
from vellum.workflows import BaseWorkflow
|
9
9
|
from vellum.workflows.nodes import BaseNode
|
10
10
|
from vellum.workflows.state.context import WorkflowContext
|
11
|
+
from vellum_ee.workflows.display.workflows.base_workflow_display import BaseWorkflowDisplay
|
11
12
|
from vellum_ee.workflows.server.virtual_file_loader import VirtualFileFinder
|
12
13
|
|
13
14
|
|
@@ -494,3 +495,74 @@ class MapNodeWorkflow(BaseWorkflow):
|
|
494
495
|
|
495
496
|
# AND we get the map node results as a list
|
496
497
|
assert event.body.outputs == {"results": [1.0, 1.0, 1.0]}
|
498
|
+
|
499
|
+
|
500
|
+
def test_serialize_module__tool_calling_node_with_single_tool():
|
501
|
+
"""Test that serialize_module works with a tool calling node that has a single tool."""
|
502
|
+
|
503
|
+
# GIVEN a simple tool function
|
504
|
+
tool_function_code = '''def get_weather(location: str) -> str:
|
505
|
+
"""Get the current weather for a location."""
|
506
|
+
return f"The weather in {location} is sunny."
|
507
|
+
'''
|
508
|
+
|
509
|
+
# AND a workflow module with a tool calling node using that single tool
|
510
|
+
files = {
|
511
|
+
"__init__.py": "",
|
512
|
+
"workflow.py": """
|
513
|
+
from vellum.workflows import BaseWorkflow
|
514
|
+
from vellum.workflows.nodes.displayable.tool_calling_node import ToolCallingNode
|
515
|
+
from vellum.workflows.state.base import BaseState
|
516
|
+
from vellum.workflows.workflows.base import BaseInputs
|
517
|
+
from vellum.client.types.chat_message_prompt_block import ChatMessagePromptBlock
|
518
|
+
from vellum.client.types.plain_text_prompt_block import PlainTextPromptBlock
|
519
|
+
from vellum.client.types.rich_text_prompt_block import RichTextPromptBlock
|
520
|
+
from vellum.client.types.variable_prompt_block import VariablePromptBlock
|
521
|
+
|
522
|
+
from .get_weather import get_weather
|
523
|
+
|
524
|
+
|
525
|
+
class Inputs(BaseInputs):
|
526
|
+
location: str
|
527
|
+
|
528
|
+
|
529
|
+
class WeatherNode(ToolCallingNode):
|
530
|
+
ml_model = "gpt-4o-mini"
|
531
|
+
blocks = [
|
532
|
+
ChatMessagePromptBlock(
|
533
|
+
chat_role="USER",
|
534
|
+
blocks=[
|
535
|
+
RichTextPromptBlock(
|
536
|
+
blocks=[
|
537
|
+
VariablePromptBlock(
|
538
|
+
input_variable="location",
|
539
|
+
),
|
540
|
+
],
|
541
|
+
),
|
542
|
+
],
|
543
|
+
),
|
544
|
+
]
|
545
|
+
functions = [get_weather]
|
546
|
+
prompt_inputs = {
|
547
|
+
"location": Inputs.location,
|
548
|
+
}
|
549
|
+
|
550
|
+
|
551
|
+
class Workflow(BaseWorkflow[Inputs, BaseState]):
|
552
|
+
graph = WeatherNode
|
553
|
+
|
554
|
+
class Outputs(BaseWorkflow.Outputs):
|
555
|
+
result = WeatherNode.Outputs.text
|
556
|
+
""",
|
557
|
+
"get_weather.py": tool_function_code,
|
558
|
+
}
|
559
|
+
|
560
|
+
namespace = str(uuid4())
|
561
|
+
|
562
|
+
# AND the virtual file loader is registered
|
563
|
+
sys.meta_path.append(VirtualFileFinder(files, namespace))
|
564
|
+
|
565
|
+
result = BaseWorkflowDisplay.serialize_module(namespace)
|
566
|
+
|
567
|
+
# THEN the serialization should complete successfully
|
568
|
+
assert result is not None
|
File without changes
|
File without changes
|
File without changes
|