vellum-ai 1.6.4__py3-none-any.whl → 1.7.1__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/reference.md +81 -0
- vellum/client/resources/container_images/client.py +8 -2
- vellum/client/resources/container_images/raw_client.py +8 -0
- vellum/client/resources/workflows/client.py +81 -0
- vellum/client/resources/workflows/raw_client.py +85 -0
- vellum/client/types/__init__.py +2 -0
- vellum/client/types/workflow_resolved_state.py +31 -0
- vellum/types/workflow_resolved_state.py +3 -0
- vellum/workflows/descriptors/base.py +3 -0
- vellum/workflows/errors/types.py +1 -0
- vellum/workflows/inputs/base.py +4 -1
- vellum/workflows/inputs/tests/test_inputs.py +21 -0
- vellum/workflows/resolvers/resolver.py +13 -46
- vellum/workflows/resolvers/tests/test_resolver.py +27 -112
- vellum/workflows/runner/runner.py +16 -0
- vellum/workflows/workflows/base.py +2 -0
- {vellum_ai-1.6.4.dist-info → vellum_ai-1.7.1.dist-info}/METADATA +1 -1
- {vellum_ai-1.6.4.dist-info → vellum_ai-1.7.1.dist-info}/RECORD +39 -37
- vellum_ee/assets/node-definitions.json +203 -18
- vellum_ee/workflows/display/exceptions.py +2 -6
- vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py +11 -4
- vellum_ee/workflows/display/nodes/vellum/search_node.py +4 -4
- vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py +11 -4
- vellum_ee/workflows/display/nodes/vellum/templating_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/tests/test_code_execution_node.py +1 -1
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +30 -0
- vellum_ee/workflows/display/utils/exceptions.py +19 -0
- vellum_ee/workflows/display/utils/expressions.py +19 -11
- vellum_ee/workflows/display/utils/vellum.py +7 -1
- vellum_ee/workflows/display/workflows/base_workflow_display.py +11 -3
- vellum_ee/workflows/display/workflows/tests/test_workflow_display.py +54 -1
- vellum_ee/workflows/tests/test_server.py +41 -0
- {vellum_ai-1.6.4.dist-info → vellum_ai-1.7.1.dist-info}/LICENSE +0 -0
- {vellum_ai-1.6.4.dist-info → vellum_ai-1.7.1.dist-info}/WHEEL +0 -0
- {vellum_ai-1.6.4.dist-info → vellum_ai-1.7.1.dist-info}/entry_points.txt +0 -0
@@ -542,6 +542,208 @@
|
|
542
542
|
}
|
543
543
|
]
|
544
544
|
},
|
545
|
+
{
|
546
|
+
"id": "3ae7366b-28ce-4d6b-8ec1-c6366b1e1a8b",
|
547
|
+
"display_data": {
|
548
|
+
"position": {
|
549
|
+
"x": 0.0,
|
550
|
+
"y": 0.0
|
551
|
+
},
|
552
|
+
"comment": {
|
553
|
+
"value": "\n Used to execute a Workflow Deployment.\n\n subworkflow_inputs: EntityInputsInterface - The inputs for the Subworkflow\n deployment: Union[UUID, str] - Either the Workflow Deployment's UUID or its name.\n release_tag: str = LATEST_RELEASE_TAG - The release tag to use for the Workflow Execution\n external_id: Optional[str] = OMIT - Optionally include a unique identifier for tracking purposes.\n Must be unique within a given Workflow Deployment.\n expand_meta: Optional[WorkflowExpandMetaRequest] = OMIT - Expandable execution fields to include in the response\n metadata: Optional[Dict[str, Optional[Any]]] = OMIT - The metadata to use for the Workflow Execution\n request_options: Optional[RequestOptions] = None - The request options to use for the Workflow Execution\n ",
|
554
|
+
"expanded": true
|
555
|
+
}
|
556
|
+
},
|
557
|
+
"base": {
|
558
|
+
"name": "BaseNode",
|
559
|
+
"module": [
|
560
|
+
"vellum",
|
561
|
+
"workflows",
|
562
|
+
"nodes",
|
563
|
+
"bases",
|
564
|
+
"base"
|
565
|
+
]
|
566
|
+
},
|
567
|
+
"definition": {
|
568
|
+
"name": "SubworkflowDeploymentNode",
|
569
|
+
"module": [
|
570
|
+
"vellum",
|
571
|
+
"workflows",
|
572
|
+
"nodes",
|
573
|
+
"displayable",
|
574
|
+
"subworkflow_deployment_node",
|
575
|
+
"node"
|
576
|
+
]
|
577
|
+
},
|
578
|
+
"trigger": {
|
579
|
+
"id": "a6eecf28-5c21-4406-80bb-917c6de84050",
|
580
|
+
"merge_behavior": "AWAIT_ANY"
|
581
|
+
},
|
582
|
+
"ports": [
|
583
|
+
{
|
584
|
+
"id": "8fc8d25b-c9b5-4db7-a632-d4eb1eb3e9fd",
|
585
|
+
"name": "default",
|
586
|
+
"type": "DEFAULT"
|
587
|
+
}
|
588
|
+
]
|
589
|
+
},
|
590
|
+
{
|
591
|
+
"id": "d2f9a8ec-3023-46bb-aca3-dd67ffb61e18",
|
592
|
+
"display_data": {
|
593
|
+
"position": {
|
594
|
+
"x": 0.0,
|
595
|
+
"y": 0.0
|
596
|
+
},
|
597
|
+
"comment": {
|
598
|
+
"value": "\n Used to execute a Prompt Deployment and surface a string output and json output if applicable for convenience.\n\n prompt_inputs: EntityInputsInterface - The inputs for the Prompt\n deployment: Union[UUID, str] - Either the Prompt Deployment's UUID or its name.\n release_tag: str - The release tag to use for the Prompt Execution\n external_id: Optional[str] - Optionally include a unique identifier for tracking purposes.\n Must be unique within a given Prompt Deployment.\n expand_meta: Optional[PromptDeploymentExpandMetaRequest] - Expandable execution fields to include in the response\n raw_overrides: Optional[RawPromptExecutionOverridesRequest] - The raw overrides to use for the Prompt Execution\n expand_raw: Optional[Sequence[str]] - Expandable raw fields to include in the response\n metadata: Optional[Dict[str, Optional[Any]]] - The metadata to use for the Prompt Execution\n request_options: Optional[RequestOptions] - The request options to use for the Prompt Execution\n ",
|
599
|
+
"expanded": true
|
600
|
+
}
|
601
|
+
},
|
602
|
+
"base": {
|
603
|
+
"name": "BasePromptDeploymentNode",
|
604
|
+
"module": [
|
605
|
+
"vellum",
|
606
|
+
"workflows",
|
607
|
+
"nodes",
|
608
|
+
"displayable",
|
609
|
+
"bases",
|
610
|
+
"prompt_deployment_node"
|
611
|
+
]
|
612
|
+
},
|
613
|
+
"definition": {
|
614
|
+
"name": "PromptDeploymentNode",
|
615
|
+
"module": [
|
616
|
+
"vellum",
|
617
|
+
"workflows",
|
618
|
+
"nodes",
|
619
|
+
"displayable",
|
620
|
+
"prompt_deployment_node",
|
621
|
+
"node"
|
622
|
+
]
|
623
|
+
},
|
624
|
+
"trigger": {
|
625
|
+
"id": "7d209a9c-14a5-4b74-949c-d3e681750049",
|
626
|
+
"merge_behavior": "AWAIT_ANY"
|
627
|
+
},
|
628
|
+
"ports": [
|
629
|
+
{
|
630
|
+
"id": "d51b7c72-3e1f-400c-ba1c-1125b1047d9a",
|
631
|
+
"name": "default",
|
632
|
+
"type": "DEFAULT"
|
633
|
+
}
|
634
|
+
],
|
635
|
+
"outputs": [
|
636
|
+
{
|
637
|
+
"id": "09c6ba90-4d69-4848-aa0b-74bdfcdd5a97",
|
638
|
+
"name": "json",
|
639
|
+
"type": "JSON",
|
640
|
+
"value": null
|
641
|
+
},
|
642
|
+
{
|
643
|
+
"id": "5ef5e3c6-1578-46da-89f6-6da3dd01fb5f",
|
644
|
+
"name": "text",
|
645
|
+
"type": "STRING",
|
646
|
+
"value": null
|
647
|
+
},
|
648
|
+
{
|
649
|
+
"id": "48513658-a72b-4db4-ad49-75a48a6cd084",
|
650
|
+
"name": "results",
|
651
|
+
"type": "ARRAY",
|
652
|
+
"value": null
|
653
|
+
}
|
654
|
+
]
|
655
|
+
},
|
656
|
+
{
|
657
|
+
"id": "0ac6bdba-dbae-4ce5-b815-3d670aae0572",
|
658
|
+
"display_data": {
|
659
|
+
"position": {
|
660
|
+
"x": 0.0,
|
661
|
+
"y": 0.0
|
662
|
+
},
|
663
|
+
"comment": {
|
664
|
+
"value": "\n Used to perform a hybrid search against a Document Index in Vellum.\n\n document_index: Union[UUID, str] - Either the UUID or name of the Vellum Document Index that you'd like to search\n against\n query: str - The query to search for\n options: Optional[SearchRequestOptionsRequest] = None - Runtime configuration for the search\n request_options: Optional[RequestOptions] = None - The request options to use for the search\n chunk_separator: str = \"\n\n#####\n\n\" - The separator to use when joining the text of each search result\n ",
|
665
|
+
"expanded": true
|
666
|
+
}
|
667
|
+
},
|
668
|
+
"base": {
|
669
|
+
"name": "BaseSearchNode",
|
670
|
+
"module": [
|
671
|
+
"vellum",
|
672
|
+
"workflows",
|
673
|
+
"nodes",
|
674
|
+
"displayable",
|
675
|
+
"bases",
|
676
|
+
"search_node"
|
677
|
+
]
|
678
|
+
},
|
679
|
+
"definition": {
|
680
|
+
"name": "SearchNode",
|
681
|
+
"module": [
|
682
|
+
"vellum",
|
683
|
+
"workflows",
|
684
|
+
"nodes",
|
685
|
+
"displayable",
|
686
|
+
"search_node",
|
687
|
+
"node"
|
688
|
+
]
|
689
|
+
},
|
690
|
+
"trigger": {
|
691
|
+
"id": "37e64cf0-e604-4045-b8a0-d17e68c037e1",
|
692
|
+
"merge_behavior": "AWAIT_ANY"
|
693
|
+
},
|
694
|
+
"ports": [
|
695
|
+
{
|
696
|
+
"id": "8dea61c3-107c-4f17-bcba-dbbdc1185360",
|
697
|
+
"name": "default",
|
698
|
+
"type": "DEFAULT"
|
699
|
+
}
|
700
|
+
]
|
701
|
+
},
|
702
|
+
{
|
703
|
+
"id": "ee9b5234-247b-49d1-bed4-490312f18838",
|
704
|
+
"display_data": {
|
705
|
+
"position": {
|
706
|
+
"x": 0.0,
|
707
|
+
"y": 0.0
|
708
|
+
},
|
709
|
+
"comment": {
|
710
|
+
"value": "Used to render a Jinja template.\n\n Useful for lightweight data transformations and complex string templating.\n ",
|
711
|
+
"expanded": true
|
712
|
+
}
|
713
|
+
},
|
714
|
+
"base": {
|
715
|
+
"name": "BaseNode",
|
716
|
+
"module": [
|
717
|
+
"vellum",
|
718
|
+
"workflows",
|
719
|
+
"nodes",
|
720
|
+
"bases",
|
721
|
+
"base"
|
722
|
+
]
|
723
|
+
},
|
724
|
+
"definition": {
|
725
|
+
"name": "TemplatingNode",
|
726
|
+
"module": [
|
727
|
+
"vellum",
|
728
|
+
"workflows",
|
729
|
+
"nodes",
|
730
|
+
"core",
|
731
|
+
"templating_node",
|
732
|
+
"node"
|
733
|
+
]
|
734
|
+
},
|
735
|
+
"trigger": {
|
736
|
+
"id": "005e1cd2-5452-4e1f-be6a-ac9fe3c02b9b",
|
737
|
+
"merge_behavior": "AWAIT_ATTRIBUTES"
|
738
|
+
},
|
739
|
+
"ports": [
|
740
|
+
{
|
741
|
+
"id": "8f4460f0-717b-4972-a6f4-ac164e5e204e",
|
742
|
+
"name": "default",
|
743
|
+
"type": "DEFAULT"
|
744
|
+
}
|
745
|
+
]
|
746
|
+
},
|
545
747
|
{
|
546
748
|
"id": "035842e0-34ed-43af-97de-a706e40912ae",
|
547
749
|
"label": "Tool Calling Node",
|
@@ -853,22 +1055,5 @@
|
|
853
1055
|
]
|
854
1056
|
}
|
855
1057
|
],
|
856
|
-
"errors": [
|
857
|
-
{
|
858
|
-
"node": "SubworkflowDeploymentNode",
|
859
|
-
"error": "ApiError: headers: {'server': 'gunicorn', 'date': 'Wed, 24 Sep 2025 04:58:53 GMT', 'content-type': 'application/json', 'allow': 'GET, PUT, PATCH, DELETE, HEAD, OPTIONS', 'x-frame-options': 'DENY', 'content-length': '58', 'vary': 'Accept-Language, Origin', 'content-language': 'en', 'strict-transport-security': 'max-age=60; includeSubDomains; preload', 'x-content-type-options': 'nosniff', 'referrer-policy': 'same-origin', 'cross-origin-opener-policy': 'same-origin', 'via': '1.1 google', 'alt-svc': 'h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000'}, status_code: 403, body: {'detail': 'Authentication credentials were not provided.'}"
|
860
|
-
},
|
861
|
-
{
|
862
|
-
"node": "PromptDeploymentNode",
|
863
|
-
"error": "ApiError: headers: {'server': 'gunicorn', 'date': 'Wed, 24 Sep 2025 04:58:53 GMT', 'content-type': 'application/json', 'allow': 'GET, PUT, PATCH, DELETE, HEAD, OPTIONS', 'x-frame-options': 'DENY', 'content-length': '58', 'vary': 'Accept-Language, Origin', 'content-language': 'en', 'strict-transport-security': 'max-age=60; includeSubDomains; preload', 'x-content-type-options': 'nosniff', 'referrer-policy': 'same-origin', 'cross-origin-opener-policy': 'same-origin', 'via': '1.1 google', 'alt-svc': 'h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000'}, status_code: 403, body: {'detail': 'Authentication credentials were not provided.'}"
|
864
|
-
},
|
865
|
-
{
|
866
|
-
"node": "SearchNode",
|
867
|
-
"error": "ValueError: Expected NodeReference query to have an instance"
|
868
|
-
},
|
869
|
-
{
|
870
|
-
"node": "TemplatingNode",
|
871
|
-
"error": "KeyError: TemplatingNode.Outputs.result"
|
872
|
-
}
|
873
|
-
]
|
1058
|
+
"errors": []
|
874
1059
|
}
|
@@ -1,7 +1,3 @@
|
|
1
|
-
|
2
|
-
"""Exception raised when a node fails validation during workflow display serialization."""
|
1
|
+
from vellum_ee.workflows.display.utils.exceptions import NodeValidationError
|
3
2
|
|
4
|
-
|
5
|
-
self.message = message
|
6
|
-
self.node_class_name = node_class_name
|
7
|
-
super().__init__(f"Node validation error in {node_class_name}: {message}")
|
3
|
+
__all__ = ["NodeValidationError"]
|
@@ -6,11 +6,11 @@ from typing import ClassVar, Generic, Optional, TypeVar
|
|
6
6
|
from vellum.workflows.nodes.displayable.code_execution_node import CodeExecutionNode
|
7
7
|
from vellum.workflows.types.core import JsonObject
|
8
8
|
from vellum.workflows.utils.vellum_variables import primitive_type_to_vellum_variable_type
|
9
|
-
from vellum_ee.workflows.display.exceptions import NodeValidationError
|
10
9
|
from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
|
11
10
|
from vellum_ee.workflows.display.nodes.utils import raise_if_descriptor
|
12
11
|
from vellum_ee.workflows.display.nodes.vellum.utils import create_node_input
|
13
12
|
from vellum_ee.workflows.display.types import WorkflowDisplayContext
|
13
|
+
from vellum_ee.workflows.display.utils.exceptions import NodeValidationError
|
14
14
|
from vellum_ee.workflows.display.utils.expressions import virtual_open
|
15
15
|
|
16
16
|
_CodeExecutionNodeType = TypeVar("_CodeExecutionNodeType", bound=CodeExecutionNode)
|
@@ -8,11 +8,11 @@ from vellum.workflows.nodes import InlineSubworkflowNode
|
|
8
8
|
from vellum.workflows.nodes.displayable.bases.utils import primitive_to_vellum_value
|
9
9
|
from vellum.workflows.types.core import JsonObject
|
10
10
|
from vellum.workflows.workflows.base import BaseWorkflow
|
11
|
-
from vellum_ee.workflows.display.exceptions import NodeValidationError
|
12
11
|
from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
|
13
12
|
from vellum_ee.workflows.display.nodes.utils import raise_if_descriptor
|
14
13
|
from vellum_ee.workflows.display.nodes.vellum.utils import create_node_input
|
15
14
|
from vellum_ee.workflows.display.types import WorkflowDisplayContext
|
15
|
+
from vellum_ee.workflows.display.utils.exceptions import NodeValidationError
|
16
16
|
from vellum_ee.workflows.display.utils.vellum import infer_vellum_variable_type
|
17
17
|
from vellum_ee.workflows.display.vellum import NodeInput
|
18
18
|
from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
|
@@ -3,6 +3,7 @@ from typing import Generic, Optional, TypeVar
|
|
3
3
|
|
4
4
|
from vellum.workflows.nodes.displayable.prompt_deployment_node import PromptDeploymentNode
|
5
5
|
from vellum.workflows.types.core import JsonObject
|
6
|
+
from vellum.workflows.utils.uuids import uuid4_from_hash
|
6
7
|
from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
|
7
8
|
from vellum_ee.workflows.display.nodes.utils import raise_if_descriptor
|
8
9
|
from vellum_ee.workflows.display.nodes.vellum.utils import create_node_input
|
@@ -43,9 +44,15 @@ class BasePromptDeploymentNodeDisplay(BaseNodeDisplay[_PromptDeploymentNodeType]
|
|
43
44
|
array_display = self.output_display[node.Outputs.results]
|
44
45
|
json_display = self.output_display[node.Outputs.json]
|
45
46
|
|
46
|
-
|
47
|
-
|
48
|
-
|
47
|
+
deployment_descriptor_id = str(raise_if_descriptor(node.deployment))
|
48
|
+
try:
|
49
|
+
deployment = display_context.client.deployments.retrieve(
|
50
|
+
id=deployment_descriptor_id,
|
51
|
+
)
|
52
|
+
deployment_id = str(deployment.id)
|
53
|
+
except Exception as e:
|
54
|
+
display_context.add_error(e)
|
55
|
+
deployment_id = str(uuid4_from_hash(deployment_descriptor_id))
|
49
56
|
ml_model_fallbacks = raise_if_descriptor(node.ml_model_fallbacks)
|
50
57
|
|
51
58
|
return {
|
@@ -60,7 +67,7 @@ class BasePromptDeploymentNodeDisplay(BaseNodeDisplay[_PromptDeploymentNodeType]
|
|
60
67
|
"source_handle_id": str(self.get_source_handle_id(display_context.port_displays)),
|
61
68
|
"target_handle_id": str(self.get_target_handle_id()),
|
62
69
|
"variant": "DEPLOYMENT",
|
63
|
-
"prompt_deployment_id":
|
70
|
+
"prompt_deployment_id": deployment_id,
|
64
71
|
"release_tag": raise_if_descriptor(node.release_tag),
|
65
72
|
"ml_model_fallbacks": list(ml_model_fallbacks) if ml_model_fallbacks else None,
|
66
73
|
},
|
@@ -48,8 +48,8 @@ class BaseSearchNodeDisplay(BaseNodeDisplay[_SearchNodeType], Generic[_SearchNod
|
|
48
48
|
node_id = self.node_id
|
49
49
|
node_inputs = self._generate_search_node_inputs(node_id, node, display_context)
|
50
50
|
|
51
|
-
results_output_display =
|
52
|
-
text_output_display =
|
51
|
+
results_output_display = self.get_node_output_display(node.Outputs.results)
|
52
|
+
text_output_display = self.get_node_output_display(node.Outputs.text)
|
53
53
|
|
54
54
|
return {
|
55
55
|
"id": str(node_id),
|
@@ -111,8 +111,8 @@ class BaseSearchNodeDisplay(BaseNodeDisplay[_SearchNodeType], Generic[_SearchNod
|
|
111
111
|
limit = raw_limit if raw_limit is not None else options.limit if options is not None else None
|
112
112
|
|
113
113
|
node_input_names_and_values = [
|
114
|
-
("query", node.query),
|
115
|
-
("document_index_id", node.document_index),
|
114
|
+
("query", raise_if_descriptor(node.query)),
|
115
|
+
("document_index_id", raise_if_descriptor(node.document_index)),
|
116
116
|
("weights", weights.dict() if weights else None),
|
117
117
|
("limit", limit),
|
118
118
|
("separator", raise_if_descriptor(node.chunk_separator)),
|
@@ -4,6 +4,7 @@ from typing import Generic, Optional, TypeVar
|
|
4
4
|
from vellum.workflows.inputs.base import BaseInputs
|
5
5
|
from vellum.workflows.nodes import SubworkflowDeploymentNode
|
6
6
|
from vellum.workflows.types.core import JsonObject
|
7
|
+
from vellum.workflows.utils.uuids import uuid4_from_hash
|
7
8
|
from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
|
8
9
|
from vellum_ee.workflows.display.nodes.utils import raise_if_descriptor
|
9
10
|
from vellum_ee.workflows.display.nodes.vellum.utils import create_node_input
|
@@ -44,9 +45,15 @@ class BaseSubworkflowDeploymentNodeDisplay(
|
|
44
45
|
for variable_name, variable_value in input_items
|
45
46
|
]
|
46
47
|
|
47
|
-
|
48
|
-
|
49
|
-
|
48
|
+
deployment_descriptor_id = str(raise_if_descriptor(node.deployment))
|
49
|
+
try:
|
50
|
+
deployment = display_context.client.workflow_deployments.retrieve(
|
51
|
+
id=deployment_descriptor_id,
|
52
|
+
)
|
53
|
+
deployment_id = str(deployment.id)
|
54
|
+
except Exception as e:
|
55
|
+
display_context.add_error(e)
|
56
|
+
deployment_id = str(uuid4_from_hash(deployment_descriptor_id))
|
50
57
|
|
51
58
|
return {
|
52
59
|
"id": str(node_id),
|
@@ -58,7 +65,7 @@ class BaseSubworkflowDeploymentNodeDisplay(
|
|
58
65
|
"source_handle_id": str(self.get_source_handle_id(display_context.port_displays)),
|
59
66
|
"target_handle_id": str(self.get_target_handle_id()),
|
60
67
|
"variant": "DEPLOYMENT",
|
61
|
-
"workflow_deployment_id":
|
68
|
+
"workflow_deployment_id": deployment_id,
|
62
69
|
"release_tag": raise_if_descriptor(node.release_tag),
|
63
70
|
},
|
64
71
|
**self.serialize_generic_fields(display_context),
|
@@ -50,7 +50,7 @@ class BaseTemplatingNodeDisplay(BaseNodeDisplay[_TemplatingNodeType], Generic[_T
|
|
50
50
|
# Misc type ignore is due to `node.Outputs` being generic
|
51
51
|
# https://app.shortcut.com/vellum/story/4784
|
52
52
|
output_descriptor = node.Outputs.result # type: ignore [misc]
|
53
|
-
output_display =
|
53
|
+
output_display = self.get_node_output_display(output_descriptor)
|
54
54
|
inferred_output_type = primitive_type_to_vellum_variable_type(output_descriptor)
|
55
55
|
|
56
56
|
return {
|
@@ -6,8 +6,8 @@ from vellum.client.core.api_error import ApiError
|
|
6
6
|
from vellum.workflows.nodes.displayable.code_execution_node.node import CodeExecutionNode
|
7
7
|
from vellum.workflows.references.vellum_secret import VellumSecretReference
|
8
8
|
from vellum.workflows.workflows.base import BaseWorkflow
|
9
|
-
from vellum_ee.workflows.display.exceptions import NodeValidationError
|
10
9
|
from vellum_ee.workflows.display.nodes.vellum.code_execution_node import BaseCodeExecutionNodeDisplay
|
10
|
+
from vellum_ee.workflows.display.utils.exceptions import NodeValidationError
|
11
11
|
from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
|
12
12
|
|
13
13
|
|
@@ -353,3 +353,33 @@ def test_serialize_node__adornment_order_matches_decorator_order():
|
|
353
353
|
assert len(adornments) == 2
|
354
354
|
assert adornments[0]["label"] == "Try Node"
|
355
355
|
assert adornments[1]["label"] == "Retry Node"
|
356
|
+
|
357
|
+
|
358
|
+
def test_serialize_workflow__retry_node_edges():
|
359
|
+
"""
|
360
|
+
Tests that both retry-adorned nodes are correctly serialized in the nodes array.
|
361
|
+
"""
|
362
|
+
|
363
|
+
@RetryNode.wrap(max_attempts=3, delay=60)
|
364
|
+
class FirstNode(BaseNode):
|
365
|
+
class Outputs(BaseOutputs):
|
366
|
+
value: str
|
367
|
+
|
368
|
+
@RetryNode.wrap(max_attempts=5, delay=120)
|
369
|
+
class SecondNode(BaseNode):
|
370
|
+
class Outputs(BaseOutputs):
|
371
|
+
result: str
|
372
|
+
|
373
|
+
class MyWorkflow(BaseWorkflow):
|
374
|
+
graph = FirstNode >> SecondNode
|
375
|
+
|
376
|
+
workflow_display = get_workflow_display(workflow_class=MyWorkflow)
|
377
|
+
exec_config = cast(Dict[str, Any], workflow_display.serialize())
|
378
|
+
|
379
|
+
assert isinstance(exec_config["workflow_raw_data"], dict)
|
380
|
+
assert isinstance(exec_config["workflow_raw_data"]["nodes"], list)
|
381
|
+
|
382
|
+
nodes = cast(List[Dict[str, Any]], exec_config["workflow_raw_data"]["nodes"])
|
383
|
+
|
384
|
+
generic_nodes = [node for node in nodes if node["type"] == "GENERIC"]
|
385
|
+
assert len(generic_nodes) == 2
|
@@ -5,3 +5,22 @@ class UserFacingException(Exception):
|
|
5
5
|
|
6
6
|
class UnsupportedSerializationException(UserFacingException):
|
7
7
|
pass
|
8
|
+
|
9
|
+
|
10
|
+
class NodeValidationError(UserFacingException):
|
11
|
+
"""Exception raised when a node fails validation during workflow display serialization."""
|
12
|
+
|
13
|
+
def __init__(self, message: str, node_class_name: str):
|
14
|
+
self.message = message
|
15
|
+
self.node_class_name = node_class_name
|
16
|
+
super().__init__(f"Node validation error in {node_class_name}: {message}")
|
17
|
+
|
18
|
+
|
19
|
+
class InvalidInputReferenceError(UserFacingException):
|
20
|
+
"""Exception raised when a node references a non-existent workflow input."""
|
21
|
+
|
22
|
+
def __init__(self, message: str, inputs_class_name: str, attribute_name: str):
|
23
|
+
self.message = message
|
24
|
+
self.inputs_class_name = inputs_class_name
|
25
|
+
self.attribute_name = attribute_name
|
26
|
+
super().__init__(f"Invalid input reference in {inputs_class_name}.{attribute_name}: {message}")
|
@@ -56,7 +56,7 @@ from vellum.workflows.types.core import JsonArray, JsonObject
|
|
56
56
|
from vellum.workflows.types.generics import is_workflow_class
|
57
57
|
from vellum.workflows.utils.functions import compile_function_definition
|
58
58
|
from vellum.workflows.utils.uuids import uuid4_from_hash
|
59
|
-
from vellum_ee.workflows.display.utils.exceptions import UnsupportedSerializationException
|
59
|
+
from vellum_ee.workflows.display.utils.exceptions import InvalidInputReferenceError, UnsupportedSerializationException
|
60
60
|
from vellum_ee.workflows.server.virtual_file_loader import VirtualFileLoader
|
61
61
|
|
62
62
|
if TYPE_CHECKING:
|
@@ -275,16 +275,24 @@ def serialize_value(executable_id: UUID, display_context: "WorkflowDisplayContex
|
|
275
275
|
return serialize_value(executable_id, display_context, child_descriptor)
|
276
276
|
|
277
277
|
if isinstance(value, WorkflowInputReference):
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
278
|
+
if value not in display_context.global_workflow_input_displays:
|
279
|
+
workflow_inputs_class = display_context.workflow_display_class.infer_workflow_class().get_inputs_class()
|
280
|
+
if value.inputs_class != workflow_inputs_class:
|
281
|
+
inputs_class_name = value.inputs_class.__name__
|
282
|
+
workflow_class_name = display_context.workflow_display_class.infer_workflow_class().__name__
|
283
|
+
raise UnsupportedSerializationException(
|
284
|
+
f"Inputs class '{inputs_class_name}' referenced during serialization of "
|
285
|
+
f"'{workflow_class_name}' without parameterizing this Workflow with this Inputs definition. "
|
286
|
+
f"Update your Workflow definition to "
|
287
|
+
f"'{workflow_class_name}(BaseWorkflow[{inputs_class_name}, BaseState])'."
|
288
|
+
)
|
289
|
+
else:
|
290
|
+
raise InvalidInputReferenceError(
|
291
|
+
message=f"Inputs class '{value.inputs_class.__qualname__}' has no attribute '{value.name}'",
|
292
|
+
inputs_class_name=value.inputs_class.__qualname__,
|
293
|
+
attribute_name=value.name,
|
294
|
+
)
|
295
|
+
workflow_input_display = display_context.global_workflow_input_displays[value]
|
288
296
|
return {
|
289
297
|
"type": "WORKFLOW_INPUT",
|
290
298
|
"input_variable_id": str(workflow_input_display.id),
|
@@ -15,7 +15,7 @@ from vellum.workflows.references.lazy import LazyReference
|
|
15
15
|
from vellum.workflows.references.node import NodeReference
|
16
16
|
from vellum.workflows.references.vellum_secret import VellumSecretReference
|
17
17
|
from vellum.workflows.utils.vellum_variables import primitive_type_to_vellum_variable_type
|
18
|
-
from vellum_ee.workflows.display.utils.exceptions import UnsupportedSerializationException
|
18
|
+
from vellum_ee.workflows.display.utils.exceptions import InvalidInputReferenceError, UnsupportedSerializationException
|
19
19
|
from vellum_ee.workflows.display.utils.expressions import get_child_descriptor
|
20
20
|
|
21
21
|
if TYPE_CHECKING:
|
@@ -124,6 +124,12 @@ def create_node_input_value_pointer_rule(
|
|
124
124
|
child_descriptor = get_child_descriptor(value, display_context)
|
125
125
|
return create_node_input_value_pointer_rule(child_descriptor, display_context)
|
126
126
|
if isinstance(value, WorkflowInputReference):
|
127
|
+
if value not in display_context.global_workflow_input_displays:
|
128
|
+
raise InvalidInputReferenceError(
|
129
|
+
message=f"Inputs class '{value.inputs_class.__qualname__}' has no attribute '{value.name}'",
|
130
|
+
inputs_class_name=value.inputs_class.__qualname__,
|
131
|
+
attribute_name=value.name,
|
132
|
+
)
|
127
133
|
workflow_input_display = display_context.global_workflow_input_displays[value]
|
128
134
|
return InputVariablePointer(data=InputVariableData(input_variable_id=str(workflow_input_display.id)))
|
129
135
|
if isinstance(value, VellumSecretReference):
|
@@ -39,7 +39,6 @@ from vellum_ee.workflows.display.base import (
|
|
39
39
|
WorkflowOutputDisplay,
|
40
40
|
)
|
41
41
|
from vellum_ee.workflows.display.editor.types import NodeDisplayData, NodeDisplayPosition
|
42
|
-
from vellum_ee.workflows.display.exceptions import NodeValidationError
|
43
42
|
from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
|
44
43
|
from vellum_ee.workflows.display.nodes.get_node_display_class import get_node_display_class
|
45
44
|
from vellum_ee.workflows.display.nodes.types import NodeOutputDisplay, PortDisplay
|
@@ -57,6 +56,7 @@ from vellum_ee.workflows.display.types import (
|
|
57
56
|
WorkflowOutputDisplays,
|
58
57
|
)
|
59
58
|
from vellum_ee.workflows.display.utils.auto_layout import auto_layout_nodes
|
59
|
+
from vellum_ee.workflows.display.utils.exceptions import UserFacingException
|
60
60
|
from vellum_ee.workflows.display.utils.expressions import serialize_value
|
61
61
|
from vellum_ee.workflows.display.utils.registry import register_workflow_display_class
|
62
62
|
from vellum_ee.workflows.display.utils.vellum import infer_vellum_variable_type
|
@@ -203,12 +203,20 @@ class BaseWorkflowDisplay(Generic[WorkflowType]):
|
|
203
203
|
self.display_context.add_validation_error(validation_error)
|
204
204
|
|
205
205
|
serialized_node = node_display.serialize(self.display_context)
|
206
|
-
except (NotImplementedError,
|
206
|
+
except (NotImplementedError, UserFacingException) as e:
|
207
207
|
self.display_context.add_error(e)
|
208
208
|
self.display_context.add_invalid_node(node)
|
209
209
|
continue
|
210
210
|
|
211
|
-
|
211
|
+
# Use wrapped node's ID as dict key for adornment wrappers to prevent overwrites
|
212
|
+
wrapped_node = get_wrapped_node(node)
|
213
|
+
if wrapped_node:
|
214
|
+
wrapped_node_display = self.display_context.node_displays[wrapped_node]
|
215
|
+
dict_key = wrapped_node_display.node_id
|
216
|
+
else:
|
217
|
+
dict_key = node_display.node_id
|
218
|
+
|
219
|
+
serialized_nodes[dict_key] = serialized_node
|
212
220
|
|
213
221
|
synthetic_output_edges: JsonArray = []
|
214
222
|
output_variables: JsonArray = []
|
@@ -14,7 +14,7 @@ from vellum.workflows.state.base import BaseState
|
|
14
14
|
from vellum.workflows.types.core import JsonObject
|
15
15
|
from vellum.workflows.workflows.base import BaseWorkflow
|
16
16
|
from vellum_ee.workflows.display.editor.types import NodeDisplayData, NodeDisplayPosition
|
17
|
-
from vellum_ee.workflows.display.nodes import BaseNodeDisplay
|
17
|
+
from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
|
18
18
|
from vellum_ee.workflows.display.nodes.vellum.retry_node import BaseRetryNodeDisplay
|
19
19
|
from vellum_ee.workflows.display.nodes.vellum.try_node import BaseTryNodeDisplay
|
20
20
|
from vellum_ee.workflows.display.types import WorkflowDisplayContext
|
@@ -997,3 +997,56 @@ def test_serialize_workflow__with_complete_node_failure_prunes_edges():
|
|
997
997
|
node_types = [node["type"] for node in data["workflow_raw_data"]["nodes"]]
|
998
998
|
assert "ENTRYPOINT" in node_types
|
999
999
|
assert "GENERIC" in node_types # This is the WorkingNode that should still be serialized
|
1000
|
+
|
1001
|
+
|
1002
|
+
def test_serialize_workflow__node_with_invalid_input_reference():
|
1003
|
+
"""Test that serialization captures errors when nodes reference a non-existent input attribute."""
|
1004
|
+
|
1005
|
+
# GIVEN a workflow with defined inputs
|
1006
|
+
class Inputs(BaseInputs):
|
1007
|
+
valid_input: str
|
1008
|
+
|
1009
|
+
# AND a templating node that references a non-existent input
|
1010
|
+
class MyTemplatingNode(TemplatingNode):
|
1011
|
+
class Outputs(TemplatingNode.Outputs):
|
1012
|
+
pass
|
1013
|
+
|
1014
|
+
template = "valid: {{ valid_input }}, invalid: {{ invalid_ref }}"
|
1015
|
+
inputs = {
|
1016
|
+
"valid_input": Inputs.valid_input,
|
1017
|
+
"invalid_ref": Inputs.invalid_ref,
|
1018
|
+
}
|
1019
|
+
|
1020
|
+
# AND a base node that also references the non-existent input
|
1021
|
+
class MyBaseNode(BaseNode):
|
1022
|
+
invalid_ref = Inputs.invalid_ref
|
1023
|
+
|
1024
|
+
class Outputs(BaseNode.Outputs):
|
1025
|
+
result: str
|
1026
|
+
|
1027
|
+
def run(self) -> BaseNode.Outputs:
|
1028
|
+
return self.Outputs(result="done")
|
1029
|
+
|
1030
|
+
class MyBaseNodeDisplay(BaseNodeDisplay[MyBaseNode]):
|
1031
|
+
__serializable_inputs__ = {MyBaseNode.invalid_ref}
|
1032
|
+
|
1033
|
+
# WHEN we create a workflow with both nodes and serialize with dry_run=True
|
1034
|
+
class MyWorkflow(BaseWorkflow[Inputs, BaseState]):
|
1035
|
+
graph = MyTemplatingNode >> MyBaseNode
|
1036
|
+
|
1037
|
+
workflow_display = get_workflow_display(workflow_class=MyWorkflow, dry_run=True)
|
1038
|
+
serialized = workflow_display.serialize()
|
1039
|
+
|
1040
|
+
# THEN the serialization should succeed without raising an exception
|
1041
|
+
assert serialized is not None
|
1042
|
+
assert "workflow_raw_data" in serialized
|
1043
|
+
|
1044
|
+
errors = list(workflow_display.display_context.errors)
|
1045
|
+
assert len(errors) > 0
|
1046
|
+
|
1047
|
+
# AND the error messages should reference the missing attribute
|
1048
|
+
error_messages = [str(e) for e in errors]
|
1049
|
+
assert any("invalid_ref" in msg for msg in error_messages)
|
1050
|
+
|
1051
|
+
invalid_nodes = list(workflow_display.display_context.invalid_nodes)
|
1052
|
+
assert len(invalid_nodes) >= 2
|