vellum-ai 0.14.44__py3-none-any.whl → 0.14.45__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 +1 -1
- vellum/client/core/pydantic_utilities.py +7 -1
- vellum/workflows/nodes/bases/base.py +1 -0
- vellum/workflows/nodes/bases/tests/test_base_node.py +20 -0
- vellum/workflows/nodes/core/try_node/node.py +6 -3
- vellum/workflows/nodes/core/try_node/tests/test_node.py +24 -0
- vellum/workflows/ports/port.py +13 -3
- vellum/workflows/types/tests/test_utils.py +3 -3
- vellum/workflows/types/utils.py +31 -10
- {vellum_ai-0.14.44.dist-info → vellum_ai-0.14.45.dist-info}/METADATA +1 -1
- {vellum_ai-0.14.44.dist-info → vellum_ai-0.14.45.dist-info}/RECORD +46 -44
- vellum_ee/workflows/display/nodes/base_node_display.py +4 -173
- vellum_ee/workflows/display/nodes/vellum/conditional_node.py +1 -1
- vellum_ee/workflows/display/nodes/vellum/final_output_node.py +2 -1
- vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py +4 -1
- vellum_ee/workflows/display/nodes/vellum/retry_node.py +3 -3
- vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py +4 -1
- vellum_ee/workflows/display/nodes/vellum/tests/test_prompt_deployment_node.py +106 -0
- vellum_ee/workflows/display/nodes/vellum/tests/test_subworkflow_deployment_node.py +109 -0
- vellum_ee/workflows/display/nodes/vellum/try_node.py +3 -3
- vellum_ee/workflows/display/tests/test_base_workflow_display.py +1 -0
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +73 -111
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_api_node_serialization.py +0 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +0 -3
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +0 -4
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_default_state_serialization.py +0 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py +0 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_generic_node_serialization.py +0 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_guardrail_node_serialization.py +0 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +18 -2
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +10 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +0 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_prompt_deployment_serialization.py +2 -3
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_search_node_serialization.py +0 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_subworkflow_deployment_serialization.py +2 -3
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_node_serialization.py +0 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py +1 -2
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py +0 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py +5 -55
- vellum_ee/workflows/display/utils/expressions.py +221 -1
- vellum_ee/workflows/display/utils/vellum.py +0 -76
- vellum_ee/workflows/display/workflows/base_workflow_display.py +49 -37
- vellum_ee/workflows/display/workflows/tests/test_workflow_display.py +45 -0
- {vellum_ai-0.14.44.dist-info → vellum_ai-0.14.45.dist-info}/LICENSE +0 -0
- {vellum_ai-0.14.44.dist-info → vellum_ai-0.14.45.dist-info}/WHEEL +0 -0
- {vellum_ai-0.14.44.dist-info → vellum_ai-0.14.45.dist-info}/entry_points.txt +0 -0
@@ -1,12 +1,98 @@
|
|
1
|
-
from typing import TYPE_CHECKING
|
1
|
+
from typing import TYPE_CHECKING, Any
|
2
2
|
|
3
|
+
from vellum.client.types.logical_operator import LogicalOperator
|
3
4
|
from vellum.workflows.descriptors.base import BaseDescriptor
|
5
|
+
from vellum.workflows.expressions.accessor import AccessorExpression
|
6
|
+
from vellum.workflows.expressions.and_ import AndExpression
|
7
|
+
from vellum.workflows.expressions.begins_with import BeginsWithExpression
|
8
|
+
from vellum.workflows.expressions.between import BetweenExpression
|
9
|
+
from vellum.workflows.expressions.coalesce_expression import CoalesceExpression
|
10
|
+
from vellum.workflows.expressions.contains import ContainsExpression
|
11
|
+
from vellum.workflows.expressions.does_not_begin_with import DoesNotBeginWithExpression
|
12
|
+
from vellum.workflows.expressions.does_not_contain import DoesNotContainExpression
|
13
|
+
from vellum.workflows.expressions.does_not_end_with import DoesNotEndWithExpression
|
14
|
+
from vellum.workflows.expressions.does_not_equal import DoesNotEqualExpression
|
15
|
+
from vellum.workflows.expressions.ends_with import EndsWithExpression
|
16
|
+
from vellum.workflows.expressions.equals import EqualsExpression
|
17
|
+
from vellum.workflows.expressions.greater_than import GreaterThanExpression
|
18
|
+
from vellum.workflows.expressions.greater_than_or_equal_to import GreaterThanOrEqualToExpression
|
19
|
+
from vellum.workflows.expressions.in_ import InExpression
|
20
|
+
from vellum.workflows.expressions.is_nil import IsNilExpression
|
21
|
+
from vellum.workflows.expressions.is_not_nil import IsNotNilExpression
|
22
|
+
from vellum.workflows.expressions.is_not_null import IsNotNullExpression
|
23
|
+
from vellum.workflows.expressions.is_not_undefined import IsNotUndefinedExpression
|
24
|
+
from vellum.workflows.expressions.is_null import IsNullExpression
|
25
|
+
from vellum.workflows.expressions.is_undefined import IsUndefinedExpression
|
26
|
+
from vellum.workflows.expressions.less_than import LessThanExpression
|
27
|
+
from vellum.workflows.expressions.less_than_or_equal_to import LessThanOrEqualToExpression
|
28
|
+
from vellum.workflows.expressions.not_between import NotBetweenExpression
|
29
|
+
from vellum.workflows.expressions.not_in import NotInExpression
|
30
|
+
from vellum.workflows.expressions.or_ import OrExpression
|
31
|
+
from vellum.workflows.expressions.parse_json import ParseJsonExpression
|
32
|
+
from vellum.workflows.nodes.displayable.bases.utils import primitive_to_vellum_value
|
33
|
+
from vellum.workflows.references.constant import ConstantValueReference
|
34
|
+
from vellum.workflows.references.execution_count import ExecutionCountReference
|
4
35
|
from vellum.workflows.references.lazy import LazyReference
|
36
|
+
from vellum.workflows.references.output import OutputReference
|
37
|
+
from vellum.workflows.references.state_value import StateValueReference
|
38
|
+
from vellum.workflows.references.vellum_secret import VellumSecretReference
|
39
|
+
from vellum.workflows.references.workflow_input import WorkflowInputReference
|
40
|
+
from vellum.workflows.types.core import JsonObject
|
41
|
+
from vellum_ee.workflows.display.utils.exceptions import UnsupportedSerializationException
|
5
42
|
|
6
43
|
if TYPE_CHECKING:
|
7
44
|
from vellum_ee.workflows.display.types import WorkflowDisplayContext
|
8
45
|
|
9
46
|
|
47
|
+
def convert_descriptor_to_operator(descriptor: BaseDescriptor) -> LogicalOperator:
|
48
|
+
if isinstance(descriptor, EqualsExpression):
|
49
|
+
return "="
|
50
|
+
elif isinstance(descriptor, DoesNotEqualExpression):
|
51
|
+
return "!="
|
52
|
+
elif isinstance(descriptor, LessThanExpression):
|
53
|
+
return "<"
|
54
|
+
elif isinstance(descriptor, GreaterThanExpression):
|
55
|
+
return ">"
|
56
|
+
elif isinstance(descriptor, LessThanOrEqualToExpression):
|
57
|
+
return "<="
|
58
|
+
elif isinstance(descriptor, GreaterThanOrEqualToExpression):
|
59
|
+
return ">="
|
60
|
+
elif isinstance(descriptor, ContainsExpression):
|
61
|
+
return "contains"
|
62
|
+
elif isinstance(descriptor, BeginsWithExpression):
|
63
|
+
return "beginsWith"
|
64
|
+
elif isinstance(descriptor, EndsWithExpression):
|
65
|
+
return "endsWith"
|
66
|
+
elif isinstance(descriptor, DoesNotContainExpression):
|
67
|
+
return "doesNotContain"
|
68
|
+
elif isinstance(descriptor, DoesNotBeginWithExpression):
|
69
|
+
return "doesNotBeginWith"
|
70
|
+
elif isinstance(descriptor, DoesNotEndWithExpression):
|
71
|
+
return "doesNotEndWith"
|
72
|
+
elif isinstance(descriptor, (IsNullExpression, IsNilExpression, IsUndefinedExpression)):
|
73
|
+
return "null"
|
74
|
+
elif isinstance(descriptor, (IsNotNullExpression, IsNotNilExpression, IsNotUndefinedExpression)):
|
75
|
+
return "notNull"
|
76
|
+
elif isinstance(descriptor, InExpression):
|
77
|
+
return "in"
|
78
|
+
elif isinstance(descriptor, NotInExpression):
|
79
|
+
return "notIn"
|
80
|
+
elif isinstance(descriptor, BetweenExpression):
|
81
|
+
return "between"
|
82
|
+
elif isinstance(descriptor, NotBetweenExpression):
|
83
|
+
return "notBetween"
|
84
|
+
elif isinstance(descriptor, AndExpression):
|
85
|
+
return "and"
|
86
|
+
elif isinstance(descriptor, OrExpression):
|
87
|
+
return "or"
|
88
|
+
elif isinstance(descriptor, CoalesceExpression):
|
89
|
+
return "coalesce"
|
90
|
+
elif isinstance(descriptor, ParseJsonExpression):
|
91
|
+
return "parseJson"
|
92
|
+
else:
|
93
|
+
raise ValueError(f"Unsupported descriptor type: {descriptor}")
|
94
|
+
|
95
|
+
|
10
96
|
def get_child_descriptor(value: LazyReference, display_context: "WorkflowDisplayContext") -> BaseDescriptor:
|
11
97
|
if isinstance(value._get, str):
|
12
98
|
reference_parts = value._get.split(".")
|
@@ -28,3 +114,137 @@ def get_child_descriptor(value: LazyReference, display_context: "WorkflowDisplay
|
|
28
114
|
raise Exception(f"Failed to parse lazy reference: {value._get}")
|
29
115
|
|
30
116
|
return value._get()
|
117
|
+
|
118
|
+
|
119
|
+
def serialize_condition(display_context: "WorkflowDisplayContext", condition: BaseDescriptor) -> JsonObject:
|
120
|
+
if isinstance(
|
121
|
+
condition,
|
122
|
+
(
|
123
|
+
IsNullExpression,
|
124
|
+
IsNotNullExpression,
|
125
|
+
IsNilExpression,
|
126
|
+
IsNotNilExpression,
|
127
|
+
IsUndefinedExpression,
|
128
|
+
IsNotUndefinedExpression,
|
129
|
+
ParseJsonExpression,
|
130
|
+
),
|
131
|
+
):
|
132
|
+
lhs = serialize_value(display_context, condition._expression)
|
133
|
+
return {
|
134
|
+
"type": "UNARY_EXPRESSION",
|
135
|
+
"lhs": lhs,
|
136
|
+
"operator": convert_descriptor_to_operator(condition),
|
137
|
+
}
|
138
|
+
elif isinstance(condition, (BetweenExpression, NotBetweenExpression)):
|
139
|
+
base = serialize_value(display_context, condition._value)
|
140
|
+
lhs = serialize_value(display_context, condition._start)
|
141
|
+
rhs = serialize_value(display_context, condition._end)
|
142
|
+
|
143
|
+
return {
|
144
|
+
"type": "TERNARY_EXPRESSION",
|
145
|
+
"base": base,
|
146
|
+
"operator": convert_descriptor_to_operator(condition),
|
147
|
+
"lhs": lhs,
|
148
|
+
"rhs": rhs,
|
149
|
+
}
|
150
|
+
elif isinstance(
|
151
|
+
condition,
|
152
|
+
(
|
153
|
+
AndExpression,
|
154
|
+
BeginsWithExpression,
|
155
|
+
CoalesceExpression,
|
156
|
+
ContainsExpression,
|
157
|
+
DoesNotBeginWithExpression,
|
158
|
+
DoesNotContainExpression,
|
159
|
+
DoesNotEndWithExpression,
|
160
|
+
DoesNotEqualExpression,
|
161
|
+
EndsWithExpression,
|
162
|
+
EqualsExpression,
|
163
|
+
GreaterThanExpression,
|
164
|
+
GreaterThanOrEqualToExpression,
|
165
|
+
InExpression,
|
166
|
+
LessThanExpression,
|
167
|
+
LessThanOrEqualToExpression,
|
168
|
+
NotInExpression,
|
169
|
+
OrExpression,
|
170
|
+
),
|
171
|
+
):
|
172
|
+
lhs = serialize_value(display_context, condition._lhs)
|
173
|
+
rhs = serialize_value(display_context, condition._rhs)
|
174
|
+
|
175
|
+
return {
|
176
|
+
"type": "BINARY_EXPRESSION",
|
177
|
+
"lhs": lhs,
|
178
|
+
"operator": convert_descriptor_to_operator(condition),
|
179
|
+
"rhs": rhs,
|
180
|
+
}
|
181
|
+
elif isinstance(condition, AccessorExpression):
|
182
|
+
return {
|
183
|
+
"type": "BINARY_EXPRESSION",
|
184
|
+
"lhs": serialize_value(display_context, condition._base),
|
185
|
+
"operator": "accessField",
|
186
|
+
"rhs": serialize_value(display_context, condition._field),
|
187
|
+
}
|
188
|
+
|
189
|
+
raise UnsupportedSerializationException(f"Unsupported condition type: {condition.__class__.__name__}")
|
190
|
+
|
191
|
+
|
192
|
+
def serialize_value(display_context: "WorkflowDisplayContext", value: Any) -> JsonObject:
|
193
|
+
if isinstance(value, ConstantValueReference):
|
194
|
+
return serialize_value(display_context, value._value)
|
195
|
+
|
196
|
+
if isinstance(value, LazyReference):
|
197
|
+
child_descriptor = get_child_descriptor(value, display_context)
|
198
|
+
return serialize_value(display_context, child_descriptor)
|
199
|
+
|
200
|
+
if isinstance(value, WorkflowInputReference):
|
201
|
+
workflow_input_display = display_context.global_workflow_input_displays[value]
|
202
|
+
return {
|
203
|
+
"type": "WORKFLOW_INPUT",
|
204
|
+
"input_variable_id": str(workflow_input_display.id),
|
205
|
+
}
|
206
|
+
|
207
|
+
if isinstance(value, StateValueReference):
|
208
|
+
state_value_display = display_context.global_state_value_displays[value]
|
209
|
+
return {
|
210
|
+
"type": "STATE_VALUE",
|
211
|
+
"state_variable_id": str(state_value_display.id),
|
212
|
+
}
|
213
|
+
|
214
|
+
if isinstance(value, OutputReference):
|
215
|
+
upstream_node, output_display = display_context.global_node_output_displays[value]
|
216
|
+
upstream_node_display = display_context.global_node_displays[upstream_node]
|
217
|
+
|
218
|
+
return {
|
219
|
+
"type": "NODE_OUTPUT",
|
220
|
+
"node_id": str(upstream_node_display.node_id),
|
221
|
+
"node_output_id": str(output_display.id),
|
222
|
+
}
|
223
|
+
|
224
|
+
if isinstance(value, VellumSecretReference):
|
225
|
+
return {
|
226
|
+
"type": "VELLUM_SECRET",
|
227
|
+
"vellum_secret_name": value.name,
|
228
|
+
}
|
229
|
+
|
230
|
+
if isinstance(value, ExecutionCountReference):
|
231
|
+
node_class_display = display_context.global_node_displays[value.node_class]
|
232
|
+
|
233
|
+
return {
|
234
|
+
"type": "EXECUTION_COUNTER",
|
235
|
+
"node_id": str(node_class_display.node_id),
|
236
|
+
}
|
237
|
+
|
238
|
+
if isinstance(value, dict) and any(isinstance(v, BaseDescriptor) for v in value.values()):
|
239
|
+
raise ValueError("Nested references are not supported.")
|
240
|
+
|
241
|
+
if not isinstance(value, BaseDescriptor):
|
242
|
+
vellum_value = primitive_to_vellum_value(value)
|
243
|
+
return {
|
244
|
+
"type": "CONSTANT_VALUE",
|
245
|
+
"value": vellum_value.dict(),
|
246
|
+
}
|
247
|
+
|
248
|
+
# If it's not any of the references we know about,
|
249
|
+
# then try to serialize it as a nested value
|
250
|
+
return serialize_condition(display_context, value)
|
@@ -2,36 +2,9 @@ from typing import TYPE_CHECKING, Any, Literal, Optional, Union
|
|
2
2
|
|
3
3
|
from vellum.client.core.pydantic_utilities import UniversalBaseModel
|
4
4
|
from vellum.client.types.array_vellum_value import ArrayVellumValue
|
5
|
-
from vellum.client.types.logical_operator import LogicalOperator
|
6
5
|
from vellum.client.types.vellum_value import VellumValue
|
7
6
|
from vellum.client.types.vellum_variable_type import VellumVariableType
|
8
7
|
from vellum.workflows.descriptors.base import BaseDescriptor
|
9
|
-
from vellum.workflows.expressions.and_ import AndExpression
|
10
|
-
from vellum.workflows.expressions.begins_with import BeginsWithExpression
|
11
|
-
from vellum.workflows.expressions.between import BetweenExpression
|
12
|
-
from vellum.workflows.expressions.coalesce_expression import CoalesceExpression
|
13
|
-
from vellum.workflows.expressions.contains import ContainsExpression
|
14
|
-
from vellum.workflows.expressions.does_not_begin_with import DoesNotBeginWithExpression
|
15
|
-
from vellum.workflows.expressions.does_not_contain import DoesNotContainExpression
|
16
|
-
from vellum.workflows.expressions.does_not_end_with import DoesNotEndWithExpression
|
17
|
-
from vellum.workflows.expressions.does_not_equal import DoesNotEqualExpression
|
18
|
-
from vellum.workflows.expressions.ends_with import EndsWithExpression
|
19
|
-
from vellum.workflows.expressions.equals import EqualsExpression
|
20
|
-
from vellum.workflows.expressions.greater_than import GreaterThanExpression
|
21
|
-
from vellum.workflows.expressions.greater_than_or_equal_to import GreaterThanOrEqualToExpression
|
22
|
-
from vellum.workflows.expressions.in_ import InExpression
|
23
|
-
from vellum.workflows.expressions.is_nil import IsNilExpression
|
24
|
-
from vellum.workflows.expressions.is_not_nil import IsNotNilExpression
|
25
|
-
from vellum.workflows.expressions.is_not_null import IsNotNullExpression
|
26
|
-
from vellum.workflows.expressions.is_not_undefined import IsNotUndefinedExpression
|
27
|
-
from vellum.workflows.expressions.is_null import IsNullExpression
|
28
|
-
from vellum.workflows.expressions.is_undefined import IsUndefinedExpression
|
29
|
-
from vellum.workflows.expressions.less_than import LessThanExpression
|
30
|
-
from vellum.workflows.expressions.less_than_or_equal_to import LessThanOrEqualToExpression
|
31
|
-
from vellum.workflows.expressions.not_between import NotBetweenExpression
|
32
|
-
from vellum.workflows.expressions.not_in import NotInExpression
|
33
|
-
from vellum.workflows.expressions.or_ import OrExpression
|
34
|
-
from vellum.workflows.expressions.parse_json import ParseJsonExpression
|
35
8
|
from vellum.workflows.nodes.bases.base import BaseNode
|
36
9
|
from vellum.workflows.nodes.displayable.bases.utils import primitive_to_vellum_value
|
37
10
|
from vellum.workflows.references import OutputReference, WorkflowInputReference
|
@@ -166,52 +139,3 @@ def create_node_input_value_pointer_rule(
|
|
166
139
|
return ConstantValuePointer(type="CONSTANT_VALUE", data=vellum_value)
|
167
140
|
|
168
141
|
raise UnsupportedSerializationException(f"Unsupported descriptor type: {value.__class__.__name__}")
|
169
|
-
|
170
|
-
|
171
|
-
def convert_descriptor_to_operator(descriptor: BaseDescriptor) -> LogicalOperator:
|
172
|
-
if isinstance(descriptor, EqualsExpression):
|
173
|
-
return "="
|
174
|
-
elif isinstance(descriptor, DoesNotEqualExpression):
|
175
|
-
return "!="
|
176
|
-
elif isinstance(descriptor, LessThanExpression):
|
177
|
-
return "<"
|
178
|
-
elif isinstance(descriptor, GreaterThanExpression):
|
179
|
-
return ">"
|
180
|
-
elif isinstance(descriptor, LessThanOrEqualToExpression):
|
181
|
-
return "<="
|
182
|
-
elif isinstance(descriptor, GreaterThanOrEqualToExpression):
|
183
|
-
return ">="
|
184
|
-
elif isinstance(descriptor, ContainsExpression):
|
185
|
-
return "contains"
|
186
|
-
elif isinstance(descriptor, BeginsWithExpression):
|
187
|
-
return "beginsWith"
|
188
|
-
elif isinstance(descriptor, EndsWithExpression):
|
189
|
-
return "endsWith"
|
190
|
-
elif isinstance(descriptor, DoesNotContainExpression):
|
191
|
-
return "doesNotContain"
|
192
|
-
elif isinstance(descriptor, DoesNotBeginWithExpression):
|
193
|
-
return "doesNotBeginWith"
|
194
|
-
elif isinstance(descriptor, DoesNotEndWithExpression):
|
195
|
-
return "doesNotEndWith"
|
196
|
-
elif isinstance(descriptor, (IsNullExpression, IsNilExpression, IsUndefinedExpression)):
|
197
|
-
return "null"
|
198
|
-
elif isinstance(descriptor, (IsNotNullExpression, IsNotNilExpression, IsNotUndefinedExpression)):
|
199
|
-
return "notNull"
|
200
|
-
elif isinstance(descriptor, InExpression):
|
201
|
-
return "in"
|
202
|
-
elif isinstance(descriptor, NotInExpression):
|
203
|
-
return "notIn"
|
204
|
-
elif isinstance(descriptor, BetweenExpression):
|
205
|
-
return "between"
|
206
|
-
elif isinstance(descriptor, NotBetweenExpression):
|
207
|
-
return "notBetween"
|
208
|
-
elif isinstance(descriptor, AndExpression):
|
209
|
-
return "and"
|
210
|
-
elif isinstance(descriptor, OrExpression):
|
211
|
-
return "or"
|
212
|
-
elif isinstance(descriptor, CoalesceExpression):
|
213
|
-
return "coalesce"
|
214
|
-
elif isinstance(descriptor, ParseJsonExpression):
|
215
|
-
return "parseJson"
|
216
|
-
else:
|
217
|
-
raise ValueError(f"Unsupported descriptor type: {descriptor}")
|
@@ -46,6 +46,7 @@ from vellum_ee.workflows.display.types import (
|
|
46
46
|
WorkflowInputsDisplays,
|
47
47
|
WorkflowOutputDisplays,
|
48
48
|
)
|
49
|
+
from vellum_ee.workflows.display.utils.expressions import serialize_value
|
49
50
|
from vellum_ee.workflows.display.utils.registry import register_workflow_display_class
|
50
51
|
from vellum_ee.workflows.display.utils.vellum import infer_vellum_variable_type
|
51
52
|
from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
|
@@ -124,26 +125,24 @@ class BaseWorkflowDisplay(Generic[WorkflowType]):
|
|
124
125
|
}
|
125
126
|
)
|
126
127
|
|
127
|
-
|
128
|
+
serialized_nodes: Dict[UUID, JsonObject] = {}
|
128
129
|
edges: JsonArray = []
|
129
130
|
|
130
131
|
# Add a single synthetic node for the workflow entrypoint
|
131
132
|
entrypoint_node_id = self.display_context.workflow_display.entrypoint_node_id
|
132
133
|
entrypoint_node_source_handle_id = self.display_context.workflow_display.entrypoint_node_source_handle_id
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
"
|
139
|
-
|
140
|
-
"source_handle_id": str(entrypoint_node_source_handle_id),
|
141
|
-
},
|
142
|
-
"display_data": self.display_context.workflow_display.entrypoint_node_display.dict(),
|
143
|
-
"base": None,
|
144
|
-
"definition": None,
|
134
|
+
serialized_nodes[entrypoint_node_id] = {
|
135
|
+
"id": str(entrypoint_node_id),
|
136
|
+
"type": "ENTRYPOINT",
|
137
|
+
"inputs": [],
|
138
|
+
"data": {
|
139
|
+
"label": "Entrypoint Node",
|
140
|
+
"source_handle_id": str(entrypoint_node_source_handle_id),
|
145
141
|
},
|
146
|
-
|
142
|
+
"display_data": self.display_context.workflow_display.entrypoint_node_display.dict(),
|
143
|
+
"base": None,
|
144
|
+
"definition": None,
|
145
|
+
}
|
147
146
|
|
148
147
|
# Add all the nodes in the workflow
|
149
148
|
for node in self._workflow.get_nodes():
|
@@ -155,7 +154,7 @@ class BaseWorkflowDisplay(Generic[WorkflowType]):
|
|
155
154
|
self.add_error(e)
|
156
155
|
continue
|
157
156
|
|
158
|
-
|
157
|
+
serialized_nodes[node_display.node_id] = serialized_node
|
159
158
|
|
160
159
|
# Add all unused nodes in the workflow
|
161
160
|
for node in self._workflow.get_unused_nodes():
|
@@ -167,10 +166,11 @@ class BaseWorkflowDisplay(Generic[WorkflowType]):
|
|
167
166
|
self.add_error(e)
|
168
167
|
continue
|
169
168
|
|
170
|
-
|
169
|
+
serialized_nodes[node_display.node_id] = serialized_node
|
171
170
|
|
172
171
|
synthetic_output_edges: JsonArray = []
|
173
172
|
output_variables: JsonArray = []
|
173
|
+
output_values: JsonArray = []
|
174
174
|
final_output_nodes = [
|
175
175
|
node for node in self.display_context.node_displays.keys() if issubclass(node, FinalOutputNode)
|
176
176
|
]
|
@@ -185,9 +185,9 @@ class BaseWorkflowDisplay(Generic[WorkflowType]):
|
|
185
185
|
for workflow_output, workflow_output_display in self.display_context.workflow_output_displays.items():
|
186
186
|
final_output_node_id = uuid4_from_hash(f"{self.workflow_id}|node_id|{workflow_output.name}")
|
187
187
|
inferred_type = infer_vellum_variable_type(workflow_output)
|
188
|
-
|
189
188
|
# Remove the terminal node output from the unreferenced set
|
190
|
-
|
189
|
+
if isinstance(workflow_output.instance, OutputReference):
|
190
|
+
unreferenced_final_output_node_outputs.discard(workflow_output.instance)
|
191
191
|
|
192
192
|
if workflow_output.instance not in final_output_node_outputs:
|
193
193
|
# Create a synthetic terminal node only if there is no terminal node for this output
|
@@ -220,24 +220,22 @@ class BaseWorkflowDisplay(Generic[WorkflowType]):
|
|
220
220
|
)
|
221
221
|
synthetic_display_data = NodeDisplayData().dict()
|
222
222
|
synthetic_node_label = "Final Output"
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
"
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
}
|
240
|
-
)
|
223
|
+
serialized_nodes[final_output_node_id] = {
|
224
|
+
"id": str(final_output_node_id),
|
225
|
+
"type": "TERMINAL",
|
226
|
+
"data": {
|
227
|
+
"label": synthetic_node_label,
|
228
|
+
"name": workflow_output_display.name,
|
229
|
+
"target_handle_id": synthetic_target_handle_id,
|
230
|
+
"output_id": str(workflow_output_display.id),
|
231
|
+
"output_type": inferred_type,
|
232
|
+
"node_input_id": str(node_input.id),
|
233
|
+
},
|
234
|
+
"inputs": [node_input.dict()],
|
235
|
+
"display_data": synthetic_display_data,
|
236
|
+
"base": final_output_node_base,
|
237
|
+
"definition": None,
|
238
|
+
}
|
241
239
|
|
242
240
|
if source_node_display:
|
243
241
|
source_handle_id = source_node_display.get_source_handle_id(
|
@@ -255,6 +253,19 @@ class BaseWorkflowDisplay(Generic[WorkflowType]):
|
|
255
253
|
}
|
256
254
|
)
|
257
255
|
|
256
|
+
elif isinstance(workflow_output.instance, OutputReference):
|
257
|
+
terminal_node_id = workflow_output.instance.outputs_class._node_class.__id__
|
258
|
+
serialized_terminal_node = serialized_nodes.get(terminal_node_id)
|
259
|
+
if serialized_terminal_node and isinstance(serialized_terminal_node["data"], dict):
|
260
|
+
serialized_terminal_node["data"]["name"] = workflow_output_display.name
|
261
|
+
|
262
|
+
output_values.append(
|
263
|
+
{
|
264
|
+
"output_variable_id": str(workflow_output_display.id),
|
265
|
+
"value": serialize_value(self.display_context, workflow_output.instance),
|
266
|
+
}
|
267
|
+
)
|
268
|
+
|
258
269
|
output_variables.append(
|
259
270
|
{
|
260
271
|
"id": str(workflow_output_display.id),
|
@@ -309,13 +320,14 @@ class BaseWorkflowDisplay(Generic[WorkflowType]):
|
|
309
320
|
|
310
321
|
return {
|
311
322
|
"workflow_raw_data": {
|
312
|
-
"nodes":
|
323
|
+
"nodes": list(serialized_nodes.values()),
|
313
324
|
"edges": edges,
|
314
325
|
"display_data": self.display_context.workflow_display.display_data.dict(),
|
315
326
|
"definition": {
|
316
327
|
"name": self._workflow.__name__,
|
317
328
|
"module": cast(JsonArray, self._workflow.__module__.split(".")),
|
318
329
|
},
|
330
|
+
"output_values": output_values,
|
319
331
|
},
|
320
332
|
"input_variables": input_variables,
|
321
333
|
"state_variables": state_variables,
|
@@ -6,6 +6,7 @@ from vellum.workflows.nodes.core.inline_subworkflow_node.node import InlineSubwo
|
|
6
6
|
from vellum.workflows.nodes.core.retry_node.node import RetryNode
|
7
7
|
from vellum.workflows.nodes.core.templating_node.node import TemplatingNode
|
8
8
|
from vellum.workflows.nodes.core.try_node.node import TryNode
|
9
|
+
from vellum.workflows.nodes.displayable.final_output_node.node import FinalOutputNode
|
9
10
|
from vellum.workflows.workflows.base import BaseWorkflow
|
10
11
|
from vellum_ee.workflows.display.editor.types import NodeDisplayData, NodeDisplayPosition
|
11
12
|
from vellum_ee.workflows.display.nodes import BaseNodeDisplay
|
@@ -327,3 +328,47 @@ def test_serialize_workflow__inherited_workflow_display_class_not_registered():
|
|
327
328
|
|
328
329
|
# THEN it should should succeed
|
329
330
|
assert data is not None
|
331
|
+
|
332
|
+
|
333
|
+
def test_serialize_workflow__terminal_node_mismatches_workflow_output_name():
|
334
|
+
# GIVEN a node
|
335
|
+
class ExitNode(FinalOutputNode):
|
336
|
+
class Outputs(FinalOutputNode.Outputs):
|
337
|
+
value = "hello"
|
338
|
+
|
339
|
+
# AND a workflow that uses the node
|
340
|
+
class MyWorkflow(BaseWorkflow):
|
341
|
+
graph = ExitNode
|
342
|
+
|
343
|
+
class Outputs(BaseWorkflow.Outputs):
|
344
|
+
answer = ExitNode.Outputs.value
|
345
|
+
|
346
|
+
# WHEN we serialize it
|
347
|
+
workflow_display = get_workflow_display(workflow_class=MyWorkflow)
|
348
|
+
data = workflow_display.serialize()
|
349
|
+
|
350
|
+
# THEN it should have an output name that matches the workflow output
|
351
|
+
assert isinstance(data["workflow_raw_data"], dict)
|
352
|
+
assert isinstance(data["workflow_raw_data"]["nodes"], list)
|
353
|
+
terminal_node = [
|
354
|
+
node for node in data["workflow_raw_data"]["nodes"] if isinstance(node, dict) and node["type"] == "TERMINAL"
|
355
|
+
][0]
|
356
|
+
assert isinstance(terminal_node["data"], dict)
|
357
|
+
assert terminal_node["data"]["name"] == "answer"
|
358
|
+
|
359
|
+
# AND the output variable should have the correct name
|
360
|
+
assert isinstance(data["output_variables"], list)
|
361
|
+
assert isinstance(data["output_variables"][0], dict)
|
362
|
+
assert data["output_variables"][0]["key"] == "answer"
|
363
|
+
assert data["output_variables"][0]["type"] == "STRING"
|
364
|
+
|
365
|
+
# AND the output value should have the correct name
|
366
|
+
output_variable_id = data["output_variables"][0]["id"]
|
367
|
+
assert isinstance(data["workflow_raw_data"]["output_values"], list)
|
368
|
+
assert isinstance(data["workflow_raw_data"]["output_values"][0], dict)
|
369
|
+
assert data["workflow_raw_data"]["output_values"][0]["output_variable_id"] == output_variable_id
|
370
|
+
assert data["workflow_raw_data"]["output_values"][0]["value"] == {
|
371
|
+
"type": "NODE_OUTPUT",
|
372
|
+
"node_id": str(ExitNode.__id__),
|
373
|
+
"node_output_id": str(ExitNode.__output_ids__["value"]),
|
374
|
+
}
|
File without changes
|
File without changes
|
File without changes
|