vellum-ai 1.3.7__py3-none-any.whl → 1.3.9__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/client/core/client_wrapper.py +2 -2
- vellum/client/reference.md +71 -0
- vellum/client/resources/workflows/client.py +80 -0
- vellum/client/resources/workflows/raw_client.py +98 -0
- vellum/client/types/node_execution_rejected_body.py +1 -0
- vellum/client/types/vellum_error.py +2 -1
- vellum/client/types/vellum_error_request.py +2 -1
- vellum/client/types/workflow_event_error.py +1 -0
- vellum/client/types/workflow_execution_rejected_body.py +1 -0
- vellum/workflows/descriptors/exceptions.py +18 -1
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +86 -0
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/tests/test_inline_prompt_node.py +87 -7
- vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py +60 -0
- vellum/workflows/runner/runner.py +16 -0
- vellum/workflows/utils/tests/test_vellum_variables.py +7 -1
- vellum/workflows/utils/vellum_variables.py +42 -3
- {vellum_ai-1.3.7.dist-info → vellum_ai-1.3.9.dist-info}/METADATA +1 -1
- {vellum_ai-1.3.7.dist-info → vellum_ai-1.3.9.dist-info}/RECORD +42 -42
- vellum_ee/workflows/display/editor/types.py +2 -0
- vellum_ee/workflows/display/nodes/base_node_display.py +42 -14
- vellum_ee/workflows/display/nodes/tests/test_base_node_display.py +64 -0
- vellum_ee/workflows/display/nodes/vellum/final_output_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/retry_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/tests/test_prompt_deployment_node.py +70 -0
- vellum_ee/workflows/display/nodes/vellum/tests/test_prompt_node.py +12 -12
- vellum_ee/workflows/display/nodes/vellum/tests/test_tool_calling_node.py +4 -4
- vellum_ee/workflows/display/nodes/vellum/try_node.py +1 -1
- vellum_ee/workflows/display/tests/test_base_workflow_display.py +46 -0
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attributes_serialization.py +1 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_prompt_node_serialization.py +8 -8
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +1 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +1 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_serialization.py +2 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py +2 -1
- vellum_ee/workflows/display/utils/events.py +7 -1
- vellum_ee/workflows/display/utils/expressions.py +33 -19
- vellum_ee/workflows/display/utils/tests/test_events.py +4 -4
- vellum_ee/workflows/display/workflows/base_workflow_display.py +1 -1
- vellum_ee/workflows/display/workflows/tests/test_workflow_display.py +10 -10
- {vellum_ai-1.3.7.dist-info → vellum_ai-1.3.9.dist-info}/LICENSE +0 -0
- {vellum_ai-1.3.7.dist-info → vellum_ai-1.3.9.dist-info}/WHEEL +0 -0
- {vellum_ai-1.3.7.dist-info → vellum_ai-1.3.9.dist-info}/entry_points.txt +0 -0
@@ -3,6 +3,20 @@ from datetime import datetime
|
|
3
3
|
from uuid import UUID, uuid4
|
4
4
|
from typing import Type
|
5
5
|
|
6
|
+
from vellum import (
|
7
|
+
AudioInputRequest,
|
8
|
+
DocumentInputRequest,
|
9
|
+
ImageInputRequest,
|
10
|
+
VellumAudio,
|
11
|
+
VellumAudioRequest,
|
12
|
+
VellumDocument,
|
13
|
+
VellumDocumentRequest,
|
14
|
+
VellumImage,
|
15
|
+
VellumImageRequest,
|
16
|
+
VellumVideo,
|
17
|
+
VellumVideoRequest,
|
18
|
+
VideoInputRequest,
|
19
|
+
)
|
6
20
|
from vellum.workflows import BaseWorkflow
|
7
21
|
from vellum.workflows.nodes import PromptDeploymentNode
|
8
22
|
from vellum_ee.workflows.display.nodes.vellum.prompt_deployment_node import BasePromptDeploymentNodeDisplay
|
@@ -105,3 +119,59 @@ def test_serialize_node__prompt_inputs(GetDisplayClass, expected_input_id, mock_
|
|
105
119
|
},
|
106
120
|
}
|
107
121
|
]
|
122
|
+
|
123
|
+
|
124
|
+
@pytest.mark.parametrize(
|
125
|
+
[
|
126
|
+
"raw_input",
|
127
|
+
"expected_compiled_inputs",
|
128
|
+
],
|
129
|
+
[
|
130
|
+
# Cast VellumX -> VellumXRequest
|
131
|
+
(
|
132
|
+
VellumAudio(src="data:audio/wav;base64,mockaudio"),
|
133
|
+
[AudioInputRequest(name="file_input", value=VellumAudioRequest(src="data:audio/wav;base64,mockaudio"))],
|
134
|
+
),
|
135
|
+
(
|
136
|
+
VellumImage(src="data:image/png;base64,mockimage"),
|
137
|
+
[ImageInputRequest(name="file_input", value=VellumImageRequest(src="data:image/png;base64,mockimage"))],
|
138
|
+
),
|
139
|
+
(
|
140
|
+
VellumVideo(src="data:video/mp4;base64,mockvideo"),
|
141
|
+
[VideoInputRequest(name="file_input", value=VellumVideoRequest(src="data:video/mp4;base64,mockvideo"))],
|
142
|
+
),
|
143
|
+
(
|
144
|
+
VellumDocument(src="mockdocument"),
|
145
|
+
[DocumentInputRequest(name="file_input", value=VellumDocumentRequest(src="mockdocument"))],
|
146
|
+
),
|
147
|
+
# No casting required
|
148
|
+
(
|
149
|
+
VellumAudioRequest(src="data:audio/wav;base64,mockaudio"),
|
150
|
+
[AudioInputRequest(name="file_input", value=VellumAudioRequest(src="data:audio/wav;base64,mockaudio"))],
|
151
|
+
),
|
152
|
+
(
|
153
|
+
VellumImageRequest(src="data:image/png;base64,mockimage"),
|
154
|
+
[ImageInputRequest(name="file_input", value=VellumImageRequest(src="data:image/png;base64,mockimage"))],
|
155
|
+
),
|
156
|
+
(
|
157
|
+
VellumVideoRequest(src="data:video/mp4;base64,mockvideo"),
|
158
|
+
[VideoInputRequest(name="file_input", value=VellumVideoRequest(src="data:video/mp4;base64,mockvideo"))],
|
159
|
+
),
|
160
|
+
(
|
161
|
+
VellumDocumentRequest(src="mockdocument"),
|
162
|
+
[DocumentInputRequest(name="file_input", value=VellumDocumentRequest(src="mockdocument"))],
|
163
|
+
),
|
164
|
+
],
|
165
|
+
)
|
166
|
+
def test_file_input_compilation(raw_input, expected_compiled_inputs):
|
167
|
+
# GIVEN a prompt node with file input
|
168
|
+
class MyPromptDeploymentNode(PromptDeploymentNode):
|
169
|
+
deployment = "DEPLOYMENT"
|
170
|
+
prompt_inputs = {"file_input": raw_input}
|
171
|
+
ml_model_fallbacks = None
|
172
|
+
|
173
|
+
# WHEN we compile the inputs
|
174
|
+
compiled_inputs = MyPromptDeploymentNode()._compile_prompt_inputs()
|
175
|
+
|
176
|
+
# THEN we should get the correct input type
|
177
|
+
assert compiled_inputs == expected_compiled_inputs
|
@@ -197,7 +197,7 @@ def test_serialize_node__prompt_inputs__state_reference():
|
|
197
197
|
"type": "DICTIONARY_REFERENCE",
|
198
198
|
"entries": [
|
199
199
|
{
|
200
|
-
"id": "
|
200
|
+
"id": "52559b9e-4e8e-438a-8246-cfa30c98d5d1",
|
201
201
|
"key": "foo",
|
202
202
|
"value": {
|
203
203
|
"type": "WORKFLOW_STATE",
|
@@ -205,7 +205,7 @@ def test_serialize_node__prompt_inputs__state_reference():
|
|
205
205
|
},
|
206
206
|
},
|
207
207
|
{
|
208
|
-
"id": "
|
208
|
+
"id": "3750feb9-5d5c-4150-b62d-a9924f466888",
|
209
209
|
"key": "bar",
|
210
210
|
"value": {
|
211
211
|
"type": "CONSTANT_VALUE",
|
@@ -325,52 +325,52 @@ def test_serialize_node__prompt_parameters__dynamic_references():
|
|
325
325
|
assert parameters_attribute["value"]["type"] == "DICTIONARY_REFERENCE"
|
326
326
|
assert parameters_attribute["value"]["entries"] == [
|
327
327
|
{
|
328
|
-
"id": "
|
328
|
+
"id": "24703d3a-ee6c-4b1b-80f8-6c19ef16723a",
|
329
329
|
"key": "stop",
|
330
330
|
"value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
|
331
331
|
},
|
332
332
|
{
|
333
|
-
"id": "
|
333
|
+
"id": "88a3bf5d-f42b-4895-850e-ad843945a003",
|
334
334
|
"key": "temperature",
|
335
335
|
"value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
|
336
336
|
},
|
337
337
|
{
|
338
|
-
"id": "
|
338
|
+
"id": "ede3e0c2-3033-4d0a-bd72-e52595bdc916",
|
339
339
|
"key": "max_tokens",
|
340
340
|
"value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
|
341
341
|
},
|
342
342
|
{
|
343
|
-
"id": "
|
343
|
+
"id": "0013cd8f-7658-4908-80fc-b8995d8ca4cc",
|
344
344
|
"key": "top_p",
|
345
345
|
"value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
|
346
346
|
},
|
347
347
|
{
|
348
|
-
"id": "
|
348
|
+
"id": "98eb2e57-d4ec-4c27-b39b-0b8086918a0f",
|
349
349
|
"key": "top_k",
|
350
350
|
"value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
|
351
351
|
},
|
352
352
|
{
|
353
|
-
"id": "
|
353
|
+
"id": "04accc66-888c-4145-8b4f-d8ff99e38172",
|
354
354
|
"key": "frequency_penalty",
|
355
355
|
"value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
|
356
356
|
},
|
357
357
|
{
|
358
|
-
"id": "
|
358
|
+
"id": "9236d564-0637-48de-8423-cdf3617dd6b4",
|
359
359
|
"key": "presence_penalty",
|
360
360
|
"value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
|
361
361
|
},
|
362
362
|
{
|
363
|
-
"id": "
|
363
|
+
"id": "74f3e80a-3935-45af-a9b3-d49e310a4c03",
|
364
364
|
"key": "logit_bias",
|
365
365
|
"value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
|
366
366
|
},
|
367
367
|
{
|
368
|
-
"id": "
|
368
|
+
"id": "69a7ebf7-d21a-44e9-a0fa-43eb9b2815df",
|
369
369
|
"key": "custom_parameters",
|
370
370
|
"value": {
|
371
371
|
"entries": [
|
372
372
|
{
|
373
|
-
"id": "
|
373
|
+
"id": "e709dc4d-f2db-4dc9-b912-401b52fbb7b4",
|
374
374
|
"key": "json_schema",
|
375
375
|
"value": {
|
376
376
|
"input_variable_id": "c02d1201-86d1-4364-b3b3-4fc6824db8a4",
|
@@ -92,7 +92,7 @@ def test_serialize_node__prompt_inputs__input_reference():
|
|
92
92
|
"type": "DICTIONARY_REFERENCE",
|
93
93
|
"entries": [
|
94
94
|
{
|
95
|
-
"id": "
|
95
|
+
"id": "845009c8-03f8-4de4-b956-841309457d37",
|
96
96
|
"key": "foo",
|
97
97
|
"value": {"type": "WORKFLOW_INPUT", "input_variable_id": "e3657390-fd3c-4fea-8cdd-fc5ea79f3278"},
|
98
98
|
}
|
@@ -134,12 +134,12 @@ def test_serialize_node__prompt_inputs__mixed_values():
|
|
134
134
|
"type": "DICTIONARY_REFERENCE",
|
135
135
|
"entries": [
|
136
136
|
{
|
137
|
-
"id": "
|
137
|
+
"id": "a4016385-3cab-4c01-b9d2-7865cd54bdb0",
|
138
138
|
"key": "foo",
|
139
139
|
"value": {"type": "CONSTANT_VALUE", "value": {"type": "STRING", "value": "bar"}},
|
140
140
|
},
|
141
141
|
{
|
142
|
-
"id": "
|
142
|
+
"id": "828928b1-24e3-4457-9d6f-4f0692dfa355",
|
143
143
|
"key": "baz",
|
144
144
|
"value": {"type": "WORKFLOW_INPUT", "input_variable_id": "8d57cf1d-147c-427b-9a5e-e5f6ab76e2eb"},
|
145
145
|
},
|
@@ -540,7 +540,7 @@ def test_serialize_tool_prompt_node_with_inline_workflow():
|
|
540
540
|
"type": "DICTIONARY_REFERENCE",
|
541
541
|
"entries": [
|
542
542
|
{
|
543
|
-
"id": "
|
543
|
+
"id": "b1dfaf2b-b9fb-4fea-ad04-a988e5223d06",
|
544
544
|
"key": "chat_history",
|
545
545
|
"value": {
|
546
546
|
"type": "BINARY_EXPRESSION",
|
@@ -37,7 +37,7 @@ class BaseTryNodeDisplay(BaseAdornmentNodeDisplay[_TryNodeType], Generic[_TryNod
|
|
37
37
|
{
|
38
38
|
"id": id,
|
39
39
|
"name": attribute.name,
|
40
|
-
"value": serialize_value(display_context, attribute.instance),
|
40
|
+
"value": serialize_value(node_id, display_context, attribute.instance),
|
41
41
|
}
|
42
42
|
)
|
43
43
|
|
@@ -476,3 +476,49 @@ def test_serialize_workflow_with_node_display_data():
|
|
476
476
|
|
477
477
|
assert test_node is not None, "TestNode not found in serialized nodes"
|
478
478
|
assert test_node["display_data"] == {"position": {"x": 100, "y": 200}, "z_index": 10, "width": 300, "height": 150}
|
479
|
+
|
480
|
+
|
481
|
+
def test_serialize_workflow_with_node_icon_and_color():
|
482
|
+
"""
|
483
|
+
Tests that nodes with icon and color serialize correctly in workflow context.
|
484
|
+
"""
|
485
|
+
|
486
|
+
# GIVEN a workflow with a node that has icon and color
|
487
|
+
class TestNode(BaseNode):
|
488
|
+
class Outputs(BaseNode.Outputs):
|
489
|
+
result: str
|
490
|
+
|
491
|
+
class TestWorkflow(BaseWorkflow):
|
492
|
+
graph = TestNode
|
493
|
+
|
494
|
+
class Outputs(BaseWorkflow.Outputs):
|
495
|
+
final_result = TestNode.Outputs.result
|
496
|
+
|
497
|
+
class TestNodeDisplay(BaseNodeDisplay[TestNode]):
|
498
|
+
display_data = NodeDisplayData(position=NodeDisplayPosition(x=100, y=200), icon="vellum:icon:cog", color="navy")
|
499
|
+
|
500
|
+
class TestWorkflowDisplay(BaseWorkflowDisplay[TestWorkflow]):
|
501
|
+
pass
|
502
|
+
|
503
|
+
# WHEN we serialize the workflow
|
504
|
+
display = get_workflow_display(
|
505
|
+
base_display_class=TestWorkflowDisplay,
|
506
|
+
workflow_class=TestWorkflow,
|
507
|
+
)
|
508
|
+
serialized_workflow = display.serialize()
|
509
|
+
|
510
|
+
# THEN the node should include icon and color in display_data
|
511
|
+
workflow_raw_data = cast(Dict[str, Any], serialized_workflow["workflow_raw_data"])
|
512
|
+
nodes = cast(List[Dict[str, Any]], workflow_raw_data["nodes"])
|
513
|
+
|
514
|
+
test_node = None
|
515
|
+
for node in nodes:
|
516
|
+
if node.get("type") == "GENERIC":
|
517
|
+
definition = node.get("definition")
|
518
|
+
if isinstance(definition, dict) and definition.get("name") == "TestNode":
|
519
|
+
test_node = node
|
520
|
+
break
|
521
|
+
|
522
|
+
assert test_node is not None, "TestNode not found in serialized nodes"
|
523
|
+
assert test_node["display_data"]["icon"] == "vellum:icon:cog"
|
524
|
+
assert test_node["display_data"]["color"] == "navy"
|
@@ -293,7 +293,7 @@ def test_serialize_node__workflow_input_as_nested_chat_history():
|
|
293
293
|
"type": "DICTIONARY_REFERENCE",
|
294
294
|
"entries": [
|
295
295
|
{
|
296
|
-
"id": "
|
296
|
+
"id": "07513ab1-cf47-490e-8b43-5da226332a00",
|
297
297
|
"key": "hello",
|
298
298
|
"value": {
|
299
299
|
"type": "WORKFLOW_INPUT",
|
@@ -219,7 +219,7 @@ def test_serialize_workflow():
|
|
219
219
|
"type": "DICTIONARY_REFERENCE",
|
220
220
|
"entries": [
|
221
221
|
{
|
222
|
-
"id": "
|
222
|
+
"id": "6eb6687c-f894-4398-8e62-7dc89e96a0a4",
|
223
223
|
"key": "noun",
|
224
224
|
"value": {
|
225
225
|
"type": "WORKFLOW_INPUT",
|
@@ -521,37 +521,37 @@ def test_serialize_workflow_with_nested_descriptor_blocks():
|
|
521
521
|
{
|
522
522
|
"entries": [
|
523
523
|
{
|
524
|
-
"id": "
|
524
|
+
"id": "4e61fbcf-13b3-4d5f-b5fb-2bf919a92045",
|
525
525
|
"key": "block_type",
|
526
526
|
"value": {"type": "CONSTANT_VALUE", "value": {"type": "STRING", "value": "CHAT_MESSAGE"}},
|
527
527
|
},
|
528
528
|
{
|
529
|
-
"id": "
|
529
|
+
"id": "79dd757e-46db-4c36-9ffc-ddb763d14f27",
|
530
530
|
"key": "state",
|
531
531
|
"value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
|
532
532
|
},
|
533
533
|
{
|
534
|
-
"id": "
|
534
|
+
"id": "2f8164e8-5495-4b9c-8268-d75618cd0842",
|
535
535
|
"key": "cache_config",
|
536
536
|
"value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
|
537
537
|
},
|
538
538
|
{
|
539
|
-
"id": "
|
539
|
+
"id": "0e8dc132-de9a-40dc-9845-336bc957df5a",
|
540
540
|
"key": "chat_role",
|
541
541
|
"value": {"type": "CONSTANT_VALUE", "value": {"type": "STRING", "value": "SYSTEM"}},
|
542
542
|
},
|
543
543
|
{
|
544
|
-
"id": "
|
544
|
+
"id": "755a45d2-2420-4414-b318-5790880f84ec",
|
545
545
|
"key": "chat_source",
|
546
546
|
"value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
|
547
547
|
},
|
548
548
|
{
|
549
|
-
"id": "
|
549
|
+
"id": "3a563cdb-d130-497f-bac6-c324a4349a3c",
|
550
550
|
"key": "chat_message_unterminated",
|
551
551
|
"value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
|
552
552
|
},
|
553
553
|
{
|
554
|
-
"id": "
|
554
|
+
"id": "2d0c084e-c54f-48f5-9444-a17f8aeb8f76",
|
555
555
|
"key": "blocks",
|
556
556
|
"value": {
|
557
557
|
"items": [
|
@@ -131,6 +131,7 @@ def test_serialize_workflow():
|
|
131
131
|
"id": "1381c078-efa2-4255-89a1-7b4cb742c7fc",
|
132
132
|
"label": "Start Node",
|
133
133
|
"type": "GENERIC",
|
134
|
+
"should_file_merge": True,
|
134
135
|
"display_data": {"position": {"x": 200.0, "y": -50.0}},
|
135
136
|
"base": {"name": "BaseNode", "module": ["vellum", "workflows", "nodes", "bases", "base"]},
|
136
137
|
"definition": {
|
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py
CHANGED
@@ -118,6 +118,7 @@ def test_serialize_workflow():
|
|
118
118
|
"id": "baf6d316-dc75-41e8-96c0-015aede96309",
|
119
119
|
"label": "Iteration",
|
120
120
|
"type": "GENERIC",
|
121
|
+
"should_file_merge": True,
|
121
122
|
"display_data": {"position": {"x": 200.0, "y": -50.0}},
|
122
123
|
"base": {
|
123
124
|
"name": "BaseNode",
|
@@ -155,6 +155,7 @@ def test_serialize_workflow():
|
|
155
155
|
"id": "1381c078-efa2-4255-89a1-7b4cb742c7fc",
|
156
156
|
"label": "Start Node",
|
157
157
|
"type": "GENERIC",
|
158
|
+
"should_file_merge": True,
|
158
159
|
"display_data": {"position": {"x": 200.0, "y": -50.0}},
|
159
160
|
"base": {
|
160
161
|
"name": "BaseNode",
|
@@ -400,7 +401,7 @@ def test_serialize_workflow():
|
|
400
401
|
"type": "DICTIONARY_REFERENCE",
|
401
402
|
"entries": [
|
402
403
|
{
|
403
|
-
"id": "
|
404
|
+
"id": "8eb8b551-9b48-43b3-861f-52adb5c585a8",
|
404
405
|
"key": "question",
|
405
406
|
"value": {
|
406
407
|
"type": "WORKFLOW_INPUT",
|
@@ -42,6 +42,7 @@ def test_serialize_workflow():
|
|
42
42
|
"id": "21f29cac-da87-495f-bba1-093d423f4e46",
|
43
43
|
"label": "Get Current Weather Node",
|
44
44
|
"type": "GENERIC",
|
45
|
+
"should_file_merge": True,
|
45
46
|
"display_data": {
|
46
47
|
"position": {"x": 200.0, "y": -50.0},
|
47
48
|
"comment": {
|
@@ -169,7 +170,7 @@ def test_serialize_workflow():
|
|
169
170
|
"type": "DICTIONARY_REFERENCE",
|
170
171
|
"entries": [
|
171
172
|
{
|
172
|
-
"id": "
|
173
|
+
"id": "8eb8b551-9b48-43b3-861f-52adb5c585a8",
|
173
174
|
"key": "question",
|
174
175
|
"value": {
|
175
176
|
"type": "WORKFLOW_INPUT",
|
@@ -1,3 +1,6 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
|
3
|
+
from vellum import Vellum
|
1
4
|
from vellum.workflows.events.workflow import WorkflowExecutionInitiatedEvent
|
2
5
|
from vellum_ee.workflows.display.utils.registry import (
|
3
6
|
get_parent_display_context_from_event,
|
@@ -25,7 +28,9 @@ def _should_mark_workflow_dynamic(event: WorkflowExecutionInitiatedEvent) -> boo
|
|
25
28
|
return True
|
26
29
|
|
27
30
|
|
28
|
-
def event_enricher(
|
31
|
+
def event_enricher(
|
32
|
+
event: WorkflowExecutionInitiatedEvent, client: Optional[Vellum] = None
|
33
|
+
) -> WorkflowExecutionInitiatedEvent:
|
29
34
|
if event.name != "workflow.execution.initiated":
|
30
35
|
return event
|
31
36
|
|
@@ -33,6 +38,7 @@ def event_enricher(event: WorkflowExecutionInitiatedEvent) -> WorkflowExecutionI
|
|
33
38
|
workflow_display = get_workflow_display(
|
34
39
|
workflow_class=workflow_definition,
|
35
40
|
parent_display_context=get_parent_display_context_from_event(event),
|
41
|
+
client=client,
|
36
42
|
dry_run=True,
|
37
43
|
)
|
38
44
|
register_workflow_display_context(event.span_id, workflow_display.display_context)
|
@@ -2,6 +2,7 @@ from dataclasses import asdict, is_dataclass
|
|
2
2
|
import inspect
|
3
3
|
from io import StringIO
|
4
4
|
import sys
|
5
|
+
from uuid import UUID
|
5
6
|
from typing import TYPE_CHECKING, Any, Dict, List, cast
|
6
7
|
|
7
8
|
from pydantic import BaseModel
|
@@ -157,7 +158,9 @@ def get_child_descriptor(value: LazyReference, display_context: "WorkflowDisplay
|
|
157
158
|
return value._get()
|
158
159
|
|
159
160
|
|
160
|
-
def _serialize_condition(
|
161
|
+
def _serialize_condition(
|
162
|
+
executable_id: UUID, display_context: "WorkflowDisplayContext", condition: BaseDescriptor
|
163
|
+
) -> JsonObject:
|
161
164
|
if isinstance(
|
162
165
|
condition,
|
163
166
|
(
|
@@ -171,16 +174,16 @@ def _serialize_condition(display_context: "WorkflowDisplayContext", condition: B
|
|
171
174
|
ParseJsonExpression,
|
172
175
|
),
|
173
176
|
):
|
174
|
-
lhs = serialize_value(display_context, condition._expression)
|
177
|
+
lhs = serialize_value(executable_id, display_context, condition._expression)
|
175
178
|
return {
|
176
179
|
"type": "UNARY_EXPRESSION",
|
177
180
|
"lhs": lhs,
|
178
181
|
"operator": convert_descriptor_to_operator(condition),
|
179
182
|
}
|
180
183
|
elif isinstance(condition, (BetweenExpression, NotBetweenExpression)):
|
181
|
-
base = serialize_value(display_context, condition._value)
|
182
|
-
lhs = serialize_value(display_context, condition._start)
|
183
|
-
rhs = serialize_value(display_context, condition._end)
|
184
|
+
base = serialize_value(executable_id, display_context, condition._value)
|
185
|
+
lhs = serialize_value(executable_id, display_context, condition._start)
|
186
|
+
rhs = serialize_value(executable_id, display_context, condition._end)
|
184
187
|
|
185
188
|
return {
|
186
189
|
"type": "TERNARY_EXPRESSION",
|
@@ -214,8 +217,8 @@ def _serialize_condition(display_context: "WorkflowDisplayContext", condition: B
|
|
214
217
|
OrExpression,
|
215
218
|
),
|
216
219
|
):
|
217
|
-
lhs = serialize_value(display_context, condition._lhs)
|
218
|
-
rhs = serialize_value(display_context, condition._rhs)
|
220
|
+
lhs = serialize_value(executable_id, display_context, condition._lhs)
|
221
|
+
rhs = serialize_value(executable_id, display_context, condition._rhs)
|
219
222
|
|
220
223
|
return {
|
221
224
|
"type": "BINARY_EXPRESSION",
|
@@ -226,9 +229,9 @@ def _serialize_condition(display_context: "WorkflowDisplayContext", condition: B
|
|
226
229
|
elif isinstance(condition, AccessorExpression):
|
227
230
|
return {
|
228
231
|
"type": "BINARY_EXPRESSION",
|
229
|
-
"lhs": serialize_value(display_context, condition._base),
|
232
|
+
"lhs": serialize_value(executable_id, display_context, condition._base),
|
230
233
|
"operator": "accessField",
|
231
|
-
"rhs": serialize_value(display_context, condition._field),
|
234
|
+
"rhs": serialize_value(executable_id, display_context, condition._field),
|
232
235
|
}
|
233
236
|
|
234
237
|
raise UnsupportedSerializationException(f"Unsupported condition type: {condition.__class__.__name__}")
|
@@ -248,16 +251,27 @@ def serialize_key(key: Any) -> str:
|
|
248
251
|
_UNDEFINED_SENTINEL: JsonObject = {"__undefined__": True}
|
249
252
|
|
250
253
|
|
251
|
-
def serialize_value(display_context: "WorkflowDisplayContext", value: Any) -> JsonObject:
|
254
|
+
def serialize_value(executable_id: UUID, display_context: "WorkflowDisplayContext", value: Any) -> JsonObject:
|
255
|
+
"""
|
256
|
+
Serialize a value to a JSON object.
|
257
|
+
|
258
|
+
Args:
|
259
|
+
executable_id: node id or workflow id
|
260
|
+
display_context: workflow display context
|
261
|
+
value: value to serialize
|
262
|
+
|
263
|
+
Returns:
|
264
|
+
serialized value
|
265
|
+
"""
|
252
266
|
if value is undefined:
|
253
267
|
return _UNDEFINED_SENTINEL
|
254
268
|
|
255
269
|
if isinstance(value, ConstantValueReference):
|
256
|
-
return serialize_value(display_context, value._value)
|
270
|
+
return serialize_value(executable_id, display_context, value._value)
|
257
271
|
|
258
272
|
if isinstance(value, LazyReference):
|
259
273
|
child_descriptor = get_child_descriptor(value, display_context)
|
260
|
-
return serialize_value(display_context, child_descriptor)
|
274
|
+
return serialize_value(executable_id, display_context, child_descriptor)
|
261
275
|
|
262
276
|
if isinstance(value, WorkflowInputReference):
|
263
277
|
try:
|
@@ -324,7 +338,7 @@ def serialize_value(display_context: "WorkflowDisplayContext", value: Any) -> Js
|
|
324
338
|
if isinstance(value, list):
|
325
339
|
serialized_items = []
|
326
340
|
for item in value:
|
327
|
-
serialized_item = serialize_value(display_context, item)
|
341
|
+
serialized_item = serialize_value(executable_id, display_context, item)
|
328
342
|
if serialized_item != _UNDEFINED_SENTINEL:
|
329
343
|
serialized_items.append(serialized_item)
|
330
344
|
|
@@ -355,16 +369,16 @@ def serialize_value(display_context: "WorkflowDisplayContext", value: Any) -> Js
|
|
355
369
|
|
356
370
|
if is_dataclass(value) and not isinstance(value, type):
|
357
371
|
dict_value = asdict(value)
|
358
|
-
return serialize_value(display_context, dict_value)
|
372
|
+
return serialize_value(executable_id, display_context, dict_value)
|
359
373
|
|
360
374
|
if isinstance(value, dict):
|
361
375
|
serialized_entries: List[Dict[str, Any]] = []
|
362
376
|
for key, val in value.items():
|
363
|
-
serialized_val = serialize_value(display_context, val)
|
377
|
+
serialized_val = serialize_value(executable_id, display_context, val)
|
364
378
|
if serialized_val != _UNDEFINED_SENTINEL:
|
365
379
|
serialized_entries.append(
|
366
380
|
{
|
367
|
-
"id": str(uuid4_from_hash(f"{
|
381
|
+
"id": str(uuid4_from_hash(f"{executable_id}|{key}")),
|
368
382
|
"key": serialize_key(key),
|
369
383
|
"value": serialized_val,
|
370
384
|
}
|
@@ -434,7 +448,7 @@ def serialize_value(display_context: "WorkflowDisplayContext", value: Any) -> Js
|
|
434
448
|
|
435
449
|
if isinstance(value, BaseModel):
|
436
450
|
dict_value = value.model_dump()
|
437
|
-
return serialize_value(display_context, dict_value)
|
451
|
+
return serialize_value(executable_id, display_context, dict_value)
|
438
452
|
|
439
453
|
if callable(value):
|
440
454
|
function_definition = compile_function_definition(value)
|
@@ -447,7 +461,7 @@ def serialize_value(display_context: "WorkflowDisplayContext", value: Any) -> Js
|
|
447
461
|
if inputs:
|
448
462
|
serialized_inputs = {}
|
449
463
|
for param_name, input_ref in inputs.items():
|
450
|
-
serialized_inputs[param_name] = serialize_value(display_context, input_ref)
|
464
|
+
serialized_inputs[param_name] = serialize_value(executable_id, display_context, input_ref)
|
451
465
|
|
452
466
|
model_data = function_definition.model_dump()
|
453
467
|
model_data["inputs"] = serialized_inputs
|
@@ -485,4 +499,4 @@ def serialize_value(display_context: "WorkflowDisplayContext", value: Any) -> Js
|
|
485
499
|
|
486
500
|
# If it's not any of the references we know about,
|
487
501
|
# then try to serialize it as a nested value
|
488
|
-
return _serialize_condition(display_context, value)
|
502
|
+
return _serialize_condition(executable_id, display_context, value)
|
@@ -47,7 +47,7 @@ from vellum_ee.workflows.display.utils.events import event_enricher
|
|
47
47
|
),
|
48
48
|
],
|
49
49
|
)
|
50
|
-
def test_event_enricher_static_workflow(is_dynamic: bool, expected_config: Optional[dict]):
|
50
|
+
def test_event_enricher_static_workflow(vellum_client, is_dynamic: bool, expected_config: Optional[dict]):
|
51
51
|
"""Test event_enricher with a static workflow (is_dynamic=False)."""
|
52
52
|
# GIVEN a workflow class with the specified is_dynamic value
|
53
53
|
_is_dynamic = is_dynamic
|
@@ -65,7 +65,7 @@ def test_event_enricher_static_workflow(is_dynamic: bool, expected_config: Optio
|
|
65
65
|
)
|
66
66
|
|
67
67
|
# WHEN the event_enricher is called with mocked dependencies
|
68
|
-
event_enricher(event)
|
68
|
+
event_enricher(event, vellum_client)
|
69
69
|
|
70
70
|
# THEN workflow_version_exec_config is set to the expected config
|
71
71
|
assert event.body.workflow_version_exec_config == expected_config
|
@@ -76,7 +76,7 @@ def test_event_enricher_static_workflow(is_dynamic: bool, expected_config: Optio
|
|
76
76
|
assert hasattr(event.body.display_context, "workflow_outputs")
|
77
77
|
|
78
78
|
|
79
|
-
def test_event_enricher_marks_subworkflow_deployment_as_dynamic():
|
79
|
+
def test_event_enricher_marks_subworkflow_deployment_as_dynamic(vellum_client):
|
80
80
|
"""Test that event_enricher treats subworkflow deployments as dynamic."""
|
81
81
|
|
82
82
|
class TestWorkflow(BaseWorkflow):
|
@@ -110,7 +110,7 @@ def test_event_enricher_marks_subworkflow_deployment_as_dynamic():
|
|
110
110
|
),
|
111
111
|
)
|
112
112
|
|
113
|
-
enriched_event = event_enricher(event)
|
113
|
+
enriched_event = event_enricher(event, vellum_client)
|
114
114
|
|
115
115
|
assert hasattr(enriched_event.body, "workflow_version_exec_config")
|
116
116
|
assert enriched_event.body.workflow_version_exec_config is not None
|
@@ -302,7 +302,7 @@ class BaseWorkflowDisplay(Generic[WorkflowType]):
|
|
302
302
|
output_values.append(
|
303
303
|
{
|
304
304
|
"output_variable_id": str(workflow_output_display.id),
|
305
|
-
"value": serialize_value(self.display_context, workflow_output.instance),
|
305
|
+
"value": serialize_value(self.workflow_id, self.display_context, workflow_output.instance),
|
306
306
|
}
|
307
307
|
)
|
308
308
|
|