vellum-ai 0.13.9__py3-none-any.whl → 0.13.11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. vellum/client/core/client_wrapper.py +1 -1
  2. vellum/workflows/descriptors/utils.py +1 -1
  3. vellum/workflows/errors/types.py +21 -0
  4. vellum/workflows/nodes/bases/base.py +1 -1
  5. vellum/workflows/nodes/displayable/api_node/node.py +4 -1
  6. vellum/workflows/nodes/displayable/bases/api_node/node.py +4 -1
  7. vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py +18 -2
  8. vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +4 -0
  9. vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py +4 -0
  10. vellum/workflows/nodes/displayable/bases/search_node.py +4 -0
  11. vellum/workflows/nodes/displayable/bases/tests/test_utils.py +18 -0
  12. vellum/workflows/nodes/displayable/bases/utils.py +8 -1
  13. vellum/workflows/nodes/displayable/code_execution_node/node.py +4 -1
  14. vellum/workflows/nodes/displayable/conditional_node/node.py +4 -0
  15. vellum/workflows/nodes/displayable/final_output_node/node.py +4 -0
  16. vellum/workflows/nodes/displayable/guardrail_node/node.py +4 -1
  17. vellum/workflows/nodes/displayable/inline_prompt_node/node.py +4 -0
  18. vellum/workflows/nodes/displayable/inline_prompt_node/tests/test_node.py +55 -0
  19. vellum/workflows/nodes/displayable/merge_node/node.py +3 -1
  20. vellum/workflows/nodes/displayable/note_node/node.py +4 -0
  21. vellum/workflows/nodes/displayable/prompt_deployment_node/node.py +4 -0
  22. vellum/workflows/nodes/displayable/search_node/node.py +4 -0
  23. vellum/workflows/nodes/displayable/subworkflow_deployment_node/node.py +4 -1
  24. {vellum_ai-0.13.9.dist-info → vellum_ai-0.13.11.dist-info}/METADATA +1 -1
  25. {vellum_ai-0.13.9.dist-info → vellum_ai-0.13.11.dist-info}/RECORD +59 -59
  26. vellum_cli/__init__.py +9 -1
  27. vellum_cli/config.py +29 -1
  28. vellum_cli/push.py +24 -3
  29. vellum_cli/tests/conftest.py +3 -0
  30. vellum_cli/tests/test_pull.py +6 -0
  31. vellum_cli/tests/test_push.py +88 -1
  32. vellum_ee/workflows/display/nodes/base_node_display.py +207 -3
  33. vellum_ee/workflows/display/nodes/base_node_vellum_display.py +16 -1
  34. vellum_ee/workflows/display/nodes/get_node_display_class.py +6 -4
  35. vellum_ee/workflows/display/nodes/vellum/__init__.py +0 -2
  36. vellum_ee/workflows/display/nodes/vellum/conditional_node.py +2 -1
  37. vellum_ee/workflows/display/nodes/vellum/error_node.py +9 -3
  38. vellum_ee/workflows/display/nodes/vellum/tests/test_error_node.py +44 -0
  39. vellum_ee/workflows/display/nodes/vellum/try_node.py +8 -2
  40. vellum_ee/workflows/display/nodes/vellum/utils.py +0 -69
  41. vellum_ee/workflows/display/tests/test_vellum_workflow_display.py +56 -0
  42. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/conftest.py +4 -3
  43. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +146 -26
  44. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attributes_serialization.py +11 -11
  45. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_outputs_serialization.py +7 -7
  46. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_ports_serialization.py +33 -35
  47. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_trigger_serialization.py +4 -4
  48. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +5 -5
  49. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py +15 -1
  50. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_generic_node_serialization.py +1 -1
  51. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +6 -3
  52. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +6 -3
  53. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +3 -3
  54. vellum_ee/workflows/display/utils/vellum.py +74 -4
  55. vellum_ee/workflows/display/workflows/base_workflow_display.py +6 -4
  56. vellum_ee/workflows/display/workflows/vellum_workflow_display.py +26 -14
  57. vellum_ee/workflows/display/nodes/vellum/base_node.py +0 -192
  58. {vellum_ai-0.13.9.dist-info → vellum_ai-0.13.11.dist-info}/LICENSE +0 -0
  59. {vellum_ai-0.13.9.dist-info → vellum_ai-0.13.11.dist-info}/WHEEL +0 -0
  60. {vellum_ai-0.13.9.dist-info → vellum_ai-0.13.11.dist-info}/entry_points.txt +0 -0
@@ -16,16 +16,34 @@ from typing import (
16
16
  get_origin,
17
17
  )
18
18
 
19
+ from vellum.workflows import BaseWorkflow
20
+ from vellum.workflows.constants import UNDEF
21
+ from vellum.workflows.descriptors.base import BaseDescriptor
22
+ from vellum.workflows.expressions.between import BetweenExpression
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.not_between import NotBetweenExpression
19
30
  from vellum.workflows.nodes.bases.base import BaseNode
31
+ from vellum.workflows.nodes.utils import get_wrapped_node
20
32
  from vellum.workflows.ports import Port
21
33
  from vellum.workflows.references import OutputReference
22
- from vellum.workflows.types.core import JsonObject
34
+ from vellum.workflows.references.execution_count import ExecutionCountReference
35
+ from vellum.workflows.references.vellum_secret import VellumSecretReference
36
+ from vellum.workflows.references.workflow_input import WorkflowInputReference
37
+ from vellum.workflows.types.core import JsonArray, JsonObject
23
38
  from vellum.workflows.types.generics import NodeType
24
39
  from vellum.workflows.types.utils import get_original_base
25
40
  from vellum.workflows.utils.names import pascal_to_title_case
26
41
  from vellum.workflows.utils.uuids import uuid4_from_hash
42
+ from vellum.workflows.utils.vellum_variables import primitive_type_to_vellum_variable_type
43
+ from vellum_ee.workflows.display.nodes.get_node_display_class import get_node_display_class
27
44
  from vellum_ee.workflows.display.nodes.types import NodeOutputDisplay, PortDisplay, PortDisplayOverrides
28
- from vellum_ee.workflows.display.vellum import CodeResourceDefinition
45
+ from vellum_ee.workflows.display.utils.vellum import convert_descriptor_to_operator, primitive_to_vellum_value
46
+ from vellum_ee.workflows.display.vellum import CodeResourceDefinition, GenericNodeDisplayData
29
47
 
30
48
  if TYPE_CHECKING:
31
49
  from vellum_ee.workflows.display.types import WorkflowDisplayContext
@@ -52,7 +70,97 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
52
70
  _node_display_registry: Dict[Type[NodeType], Type["BaseNodeDisplay"]] = {}
53
71
 
54
72
  def serialize(self, display_context: "WorkflowDisplayContext", **kwargs: Any) -> JsonObject:
55
- raise NotImplementedError(f"Serialization for nodes of type {self._node.__name__} is not supported.")
73
+ node = self._node
74
+ node_id = self.node_id
75
+
76
+ attributes: JsonArray = []
77
+ for attribute in node:
78
+ if inspect.isclass(attribute.instance) and issubclass(attribute.instance, BaseWorkflow):
79
+ # We don't need to serialize generic node attributes containing a subworkflow
80
+ continue
81
+
82
+ id = str(uuid4_from_hash(f"{node_id}|{attribute.name}"))
83
+ attributes.append(
84
+ {
85
+ "id": id,
86
+ "name": attribute.name,
87
+ "value": self.serialize_value(display_context, cast(BaseDescriptor, attribute.instance)),
88
+ }
89
+ )
90
+
91
+ adornments = kwargs.get("adornments", None)
92
+ wrapped_node = get_wrapped_node(node)
93
+ if wrapped_node is not None:
94
+ display_class = get_node_display_class(BaseNodeDisplay, wrapped_node)
95
+
96
+ adornment: JsonObject = {
97
+ "id": str(node_id),
98
+ "label": node.__qualname__,
99
+ "base": self.get_base().dict(),
100
+ "attributes": attributes,
101
+ }
102
+
103
+ existing_adornments = adornments if adornments is not None else []
104
+ return display_class().serialize(display_context, adornments=existing_adornments + [adornment])
105
+
106
+ ports: JsonArray = []
107
+ for port in node.Ports:
108
+ id = str(self.get_node_port_display(port).id)
109
+
110
+ if port._condition_type:
111
+ ports.append(
112
+ {
113
+ "id": id,
114
+ "name": port.name,
115
+ "type": port._condition_type.value,
116
+ "expression": (
117
+ self.serialize_condition(display_context, port._condition) if port._condition else None
118
+ ),
119
+ }
120
+ )
121
+ else:
122
+ ports.append(
123
+ {
124
+ "id": id,
125
+ "name": port.name,
126
+ "type": "DEFAULT",
127
+ }
128
+ )
129
+
130
+ outputs: JsonArray = []
131
+ for output in node.Outputs:
132
+ type = primitive_type_to_vellum_variable_type(output)
133
+ value = (
134
+ self.serialize_value(display_context, output.instance)
135
+ if output.instance is not None and output.instance != UNDEF
136
+ else None
137
+ )
138
+
139
+ outputs.append(
140
+ {
141
+ "id": str(uuid4_from_hash(f"{node_id}|{output.name}")),
142
+ "name": output.name,
143
+ "type": type,
144
+ "value": value,
145
+ }
146
+ )
147
+
148
+ return {
149
+ "id": str(node_id),
150
+ "label": node.__qualname__,
151
+ "type": "GENERIC",
152
+ "display_data": self._get_generic_node_display_data().dict(),
153
+ "base": self.get_base().dict(),
154
+ "definition": self.get_definition().dict(),
155
+ "trigger": {
156
+ "id": str(self.get_trigger_id()),
157
+ "merge_behavior": node.Trigger.merge_behavior.value,
158
+ },
159
+ "ports": ports,
160
+ "adornments": adornments,
161
+ "attributes": attributes,
162
+ "outputs": outputs,
163
+ }
56
164
 
57
165
  def get_base(self) -> CodeResourceDefinition:
58
166
  node = self._node
@@ -94,6 +202,9 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
94
202
 
95
203
  return PortDisplay(id=port_id, node_id=self.node_id)
96
204
 
205
+ def get_trigger_id(self) -> UUID:
206
+ return uuid4_from_hash(f"{self.node_id}|trigger")
207
+
97
208
  @classmethod
98
209
  def get_from_node_display_registry(cls, node_class: Type[NodeType]) -> Type["BaseNodeDisplay"]:
99
210
  return cls._node_display_registry[node_class]
@@ -171,6 +282,99 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
171
282
 
172
283
  def __init_subclass__(cls, **kwargs: Any) -> None:
173
284
  super().__init_subclass__(**kwargs)
285
+ if not cls._node_display_registry:
286
+ cls._node_display_registry[BaseNode] = BaseNodeDisplay
174
287
 
175
288
  node_class = cls.infer_node_class()
289
+ if node_class is BaseNode:
290
+ return
291
+
176
292
  cls._node_display_registry[node_class] = cls
293
+
294
+ def _get_generic_node_display_data(self) -> GenericNodeDisplayData:
295
+ explicit_value = self._get_explicit_node_display_attr("display_data", GenericNodeDisplayData)
296
+ return explicit_value if explicit_value else GenericNodeDisplayData()
297
+
298
+ def serialize_condition(self, display_context: "WorkflowDisplayContext", condition: BaseDescriptor) -> JsonObject:
299
+ if isinstance(
300
+ condition,
301
+ (
302
+ IsNullExpression,
303
+ IsNotNullExpression,
304
+ IsNilExpression,
305
+ IsNotNilExpression,
306
+ IsUndefinedExpression,
307
+ IsNotUndefinedExpression,
308
+ ),
309
+ ):
310
+ lhs = self.serialize_value(display_context, condition._expression)
311
+ return {
312
+ "type": "UNARY_EXPRESSION",
313
+ "lhs": lhs,
314
+ "operator": convert_descriptor_to_operator(condition),
315
+ }
316
+ elif isinstance(condition, (BetweenExpression, NotBetweenExpression)):
317
+ base = self.serialize_value(display_context, condition._value)
318
+ lhs = self.serialize_value(display_context, condition._start)
319
+ rhs = self.serialize_value(display_context, condition._end)
320
+
321
+ return {
322
+ "type": "TERNARY_EXPRESSION",
323
+ "base": base,
324
+ "operator": convert_descriptor_to_operator(condition),
325
+ "lhs": lhs,
326
+ "rhs": rhs,
327
+ }
328
+ else:
329
+ lhs = self.serialize_value(display_context, condition._lhs) # type: ignore[attr-defined]
330
+ rhs = self.serialize_value(display_context, condition._rhs) # type: ignore[attr-defined]
331
+
332
+ return {
333
+ "type": "BINARY_EXPRESSION",
334
+ "lhs": lhs,
335
+ "operator": convert_descriptor_to_operator(condition),
336
+ "rhs": rhs,
337
+ }
338
+
339
+ def serialize_value(self, display_context: "WorkflowDisplayContext", value: BaseDescriptor) -> JsonObject:
340
+ if isinstance(value, WorkflowInputReference):
341
+ workflow_input_display = display_context.global_workflow_input_displays[value]
342
+ return {
343
+ "type": "WORKFLOW_INPUT",
344
+ "input_variable_id": str(workflow_input_display.id),
345
+ }
346
+
347
+ if isinstance(value, OutputReference):
348
+ upstream_node, output_display = display_context.global_node_output_displays[value]
349
+ upstream_node_display = display_context.global_node_displays[upstream_node]
350
+
351
+ return {
352
+ "type": "NODE_OUTPUT",
353
+ "node_id": str(upstream_node_display.node_id),
354
+ "node_output_id": str(output_display.id),
355
+ }
356
+
357
+ if isinstance(value, VellumSecretReference):
358
+ return {
359
+ "type": "VELLUM_SECRET",
360
+ "vellum_secret_name": value.name,
361
+ }
362
+
363
+ if isinstance(value, ExecutionCountReference):
364
+ node_class_display = display_context.global_node_displays[value.node_class]
365
+
366
+ return {
367
+ "type": "EXECUTION_COUNTER",
368
+ "node_id": str(node_class_display.node_id),
369
+ }
370
+
371
+ if not isinstance(value, BaseDescriptor):
372
+ vellum_value = primitive_to_vellum_value(value)
373
+ return {
374
+ "type": "CONSTANT_VALUE",
375
+ "value": vellum_value.dict(),
376
+ }
377
+
378
+ # If it's not any of the references we know about,
379
+ # then try to serialize it as a nested value
380
+ return self.serialize_condition(display_context, value)
@@ -7,7 +7,7 @@ from vellum.workflows.types.generics import NodeType
7
7
  from vellum.workflows.utils.uuids import uuid4_from_hash
8
8
  from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
9
9
  from vellum_ee.workflows.display.nodes.types import PortDisplay
10
- from vellum_ee.workflows.display.vellum import NodeDisplayData
10
+ from vellum_ee.workflows.display.vellum import NodeDisplayComment, NodeDisplayData
11
11
 
12
12
 
13
13
  class BaseNodeVellumDisplay(BaseNodeDisplay[NodeType]):
@@ -26,6 +26,21 @@ class BaseNodeVellumDisplay(BaseNodeDisplay[NodeType]):
26
26
 
27
27
  def get_display_data(self) -> NodeDisplayData:
28
28
  explicit_value = self._get_explicit_node_display_attr("display_data", NodeDisplayData)
29
+ docstring = self._node.__doc__
30
+
31
+ if explicit_value and explicit_value.comment and docstring:
32
+ comment = (
33
+ NodeDisplayComment(value=docstring, expanded=explicit_value.comment.expanded)
34
+ if explicit_value.comment.expanded
35
+ else NodeDisplayComment(value=docstring)
36
+ )
37
+ return NodeDisplayData(
38
+ position=explicit_value.position,
39
+ width=explicit_value.width,
40
+ height=explicit_value.height,
41
+ comment=comment,
42
+ )
43
+
29
44
  return explicit_value if explicit_value else NodeDisplayData()
30
45
 
31
46
  def get_target_handle_id(self) -> UUID:
@@ -1,13 +1,15 @@
1
1
  import types
2
- from typing import Optional, Type
2
+ from typing import TYPE_CHECKING, Optional, Type
3
3
 
4
4
  from vellum.workflows.types.generics import NodeType
5
- from vellum_ee.workflows.display.types import NodeDisplayType
5
+
6
+ if TYPE_CHECKING:
7
+ from vellum_ee.workflows.display.types import NodeDisplayType
6
8
 
7
9
 
8
10
  def get_node_display_class(
9
- base_class: Type[NodeDisplayType], node_class: Type[NodeType], root_node_class: Optional[Type[NodeType]] = None
10
- ) -> Type[NodeDisplayType]:
11
+ base_class: Type["NodeDisplayType"], node_class: Type[NodeType], root_node_class: Optional[Type[NodeType]] = None
12
+ ) -> Type["NodeDisplayType"]:
11
13
  try:
12
14
  node_display_class = base_class.get_from_node_display_registry(node_class)
13
15
  except KeyError:
@@ -1,5 +1,4 @@
1
1
  from .api_node import BaseAPINodeDisplay
2
- from .base_node import BaseNodeDisplay
3
2
  from .code_execution_node import BaseCodeExecutionNodeDisplay
4
3
  from .conditional_node import BaseConditionalNodeDisplay
5
4
  from .error_node import BaseErrorNodeDisplay
@@ -28,7 +27,6 @@ __all__ = [
28
27
  "BaseInlineSubworkflowNodeDisplay",
29
28
  "BaseMapNodeDisplay",
30
29
  "BaseMergeNodeDisplay",
31
- "BaseNodeDisplay",
32
30
  "BaseNoteNodeDisplay",
33
31
  "BasePromptDeploymentNodeDisplay",
34
32
  "BaseSearchNodeDisplay",
@@ -17,8 +17,9 @@ from vellum.workflows.nodes.displayable import ConditionalNode
17
17
  from vellum.workflows.types.core import ConditionType, JsonObject
18
18
  from vellum.workflows.utils.uuids import uuid4_from_hash
19
19
  from vellum_ee.workflows.display.nodes.base_node_vellum_display import BaseNodeVellumDisplay
20
- from vellum_ee.workflows.display.nodes.vellum.utils import convert_descriptor_to_operator, create_node_input
20
+ from vellum_ee.workflows.display.nodes.vellum.utils import create_node_input
21
21
  from vellum_ee.workflows.display.types import WorkflowDisplayContext
22
+ from vellum_ee.workflows.display.utils.vellum import convert_descriptor_to_operator
22
23
  from vellum_ee.workflows.display.vellum import NodeInput
23
24
 
24
25
  _ConditionalNodeType = TypeVar("_ConditionalNodeType", bound=ConditionalNode)
@@ -1,9 +1,10 @@
1
1
  from uuid import UUID
2
- from typing import Any, ClassVar, Dict, Generic, Optional, TypeVar
2
+ from typing import ClassVar, Generic, Optional, TypeVar
3
3
 
4
4
  from vellum.workflows.nodes import ErrorNode
5
5
  from vellum.workflows.types.core import JsonObject
6
6
  from vellum_ee.workflows.display.nodes.base_node_vellum_display import BaseNodeVellumDisplay
7
+ from vellum_ee.workflows.display.nodes.utils import raise_if_descriptor
7
8
  from vellum_ee.workflows.display.nodes.vellum.utils import create_node_input
8
9
  from vellum_ee.workflows.display.types import WorkflowDisplayContext
9
10
 
@@ -12,7 +13,7 @@ _ErrorNodeType = TypeVar("_ErrorNodeType", bound=ErrorNode)
12
13
 
13
14
  class BaseErrorNodeDisplay(BaseNodeVellumDisplay[_ErrorNodeType], Generic[_ErrorNodeType]):
14
15
  error_output_id: ClassVar[Optional[UUID]] = None
15
- error_inputs_by_name: ClassVar[Dict[str, Any]] = {}
16
+
16
17
  name: ClassVar[str] = "error-node"
17
18
 
18
19
  def serialize(
@@ -21,6 +22,11 @@ class BaseErrorNodeDisplay(BaseNodeVellumDisplay[_ErrorNodeType], Generic[_Error
21
22
  node_id = self.node_id
22
23
  error_source_input_id = self.node_input_ids_by_name.get("error_source_input_id")
23
24
 
25
+ error_attribute = raise_if_descriptor(self._node.error)
26
+ input_values_by_name = {
27
+ "error_source_input_id": error_attribute,
28
+ }
29
+
24
30
  node_inputs = [
25
31
  create_node_input(
26
32
  node_id=node_id,
@@ -29,7 +35,7 @@ class BaseErrorNodeDisplay(BaseNodeVellumDisplay[_ErrorNodeType], Generic[_Error
29
35
  display_context=display_context,
30
36
  input_id=self.node_input_ids_by_name.get(variable_name),
31
37
  )
32
- for variable_name, variable_value in self.error_inputs_by_name.items()
38
+ for variable_name, variable_value in input_values_by_name.items()
33
39
  ]
34
40
 
35
41
  return {
@@ -0,0 +1,44 @@
1
+ from typing import Any, Dict, cast
2
+
3
+ from vellum.client.types.vellum_error import VellumError
4
+ from vellum.workflows import BaseWorkflow
5
+ from vellum.workflows.nodes.core.error_node.node import ErrorNode
6
+ from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
7
+ from vellum_ee.workflows.display.workflows.vellum_workflow_display import VellumWorkflowDisplay
8
+
9
+
10
+ def test_error_node_display__serialize_with_vellum_error() -> None:
11
+ # GIVEN an Error Node with a VellumError
12
+ class MyNode(ErrorNode):
13
+ error = VellumError(
14
+ message="A bad thing happened",
15
+ code="USER_DEFINED_ERROR",
16
+ )
17
+
18
+ # AND a workflow referencing the two node
19
+ class MyWorkflow(BaseWorkflow):
20
+ graph = MyNode
21
+
22
+ # WHEN we serialize the workflow
23
+ workflow_display = get_workflow_display(base_display_class=VellumWorkflowDisplay, workflow_class=MyWorkflow)
24
+ serialized_workflow = cast(Dict[str, Any], workflow_display.serialize())
25
+
26
+ # THEN the correct inputs should be serialized on the node
27
+ serialized_node = next(
28
+ node for node in serialized_workflow["workflow_raw_data"]["nodes"] if node["id"] == str(MyNode.__id__)
29
+ )
30
+ assert serialized_node["inputs"][0]["value"] == {
31
+ "combinator": "OR",
32
+ "rules": [
33
+ {
34
+ "data": {
35
+ "type": "ERROR",
36
+ "value": {
37
+ "message": "A bad thing happened",
38
+ "code": "USER_DEFINED_ERROR",
39
+ },
40
+ },
41
+ "type": "CONSTANT_VALUE",
42
+ }
43
+ ],
44
+ }
@@ -33,10 +33,16 @@ class BaseTryNodeDisplay(BaseNodeVellumDisplay[_TryNodeType], Generic[_TryNodeTy
33
33
  )
34
34
 
35
35
  inner_node = subworkflow.graph
36
+ elif inner_node.__bases__[0] is BaseNode:
37
+ # If the wrapped node is a generic node, we let generic node do adornment handling
38
+ class TryBaseNodeDisplay(BaseNodeDisplay[node]): # type: ignore[valid-type]
39
+ pass
40
+
41
+ return TryBaseNodeDisplay().serialize(display_context)
36
42
 
37
43
  # We need the node display class of the underlying node because
38
44
  # it contains the logic for serializing the node and potential display overrides
39
- node_display_class = get_node_display_class(BaseNodeVellumDisplay, inner_node)
45
+ node_display_class = get_node_display_class(BaseNodeDisplay, inner_node)
40
46
  node_display = node_display_class()
41
47
 
42
48
  serialized_node = node_display.serialize(
@@ -63,7 +69,7 @@ class BaseTryNodeDisplay(BaseNodeVellumDisplay[_TryNodeType], Generic[_TryNodeTy
63
69
  if not inner_node:
64
70
  return super().get_node_output_display(output)
65
71
 
66
- node_display_class = get_node_display_class(BaseNodeVellumDisplay, inner_node)
72
+ node_display_class = get_node_display_class(BaseNodeDisplay, inner_node)
67
73
  node_display = node_display_class()
68
74
  if output.name == "error":
69
75
  return inner_node, NodeOutputDisplay(
@@ -2,31 +2,7 @@ from uuid import UUID
2
2
  from typing import Any, List, Optional, Type, Union, cast
3
3
 
4
4
  from vellum.workflows.descriptors.base import BaseDescriptor
5
- from vellum.workflows.expressions.and_ import AndExpression
6
- from vellum.workflows.expressions.begins_with import BeginsWithExpression
7
- from vellum.workflows.expressions.between import BetweenExpression
8
5
  from vellum.workflows.expressions.coalesce_expression import CoalesceExpression
9
- from vellum.workflows.expressions.contains import ContainsExpression
10
- from vellum.workflows.expressions.does_not_begin_with import DoesNotBeginWithExpression
11
- from vellum.workflows.expressions.does_not_contain import DoesNotContainExpression
12
- from vellum.workflows.expressions.does_not_end_with import DoesNotEndWithExpression
13
- from vellum.workflows.expressions.does_not_equal import DoesNotEqualExpression
14
- from vellum.workflows.expressions.ends_with import EndsWithExpression
15
- from vellum.workflows.expressions.equals import EqualsExpression
16
- from vellum.workflows.expressions.greater_than import GreaterThanExpression
17
- from vellum.workflows.expressions.greater_than_or_equal_to import GreaterThanOrEqualToExpression
18
- from vellum.workflows.expressions.in_ import InExpression
19
- from vellum.workflows.expressions.is_nil import IsNilExpression
20
- from vellum.workflows.expressions.is_not_nil import IsNotNilExpression
21
- from vellum.workflows.expressions.is_not_null import IsNotNullExpression
22
- from vellum.workflows.expressions.is_not_undefined import IsNotUndefinedExpression
23
- from vellum.workflows.expressions.is_null import IsNullExpression
24
- from vellum.workflows.expressions.is_undefined import IsUndefinedExpression
25
- from vellum.workflows.expressions.less_than import LessThanExpression
26
- from vellum.workflows.expressions.less_than_or_equal_to import LessThanOrEqualToExpression
27
- from vellum.workflows.expressions.not_between import NotBetweenExpression
28
- from vellum.workflows.expressions.not_in import NotInExpression
29
- from vellum.workflows.expressions.or_ import OrExpression
30
6
  from vellum.workflows.nodes.displayable.bases.utils import primitive_to_vellum_value
31
7
  from vellum.workflows.references import NodeReference
32
8
  from vellum.workflows.utils.uuids import uuid4_from_hash
@@ -124,48 +100,3 @@ def create_pointer(
124
100
  return ConstantValuePointer(type="CONSTANT_VALUE", data=vellum_variable_value)
125
101
  else:
126
102
  raise ValueError(f"Pointer type {pointer_type} not supported")
127
-
128
-
129
- def convert_descriptor_to_operator(descriptor: BaseDescriptor) -> str:
130
- if isinstance(descriptor, EqualsExpression):
131
- return "="
132
- elif isinstance(descriptor, DoesNotEqualExpression):
133
- return "!="
134
- elif isinstance(descriptor, LessThanExpression):
135
- return "<"
136
- elif isinstance(descriptor, GreaterThanExpression):
137
- return ">"
138
- elif isinstance(descriptor, LessThanOrEqualToExpression):
139
- return "<="
140
- elif isinstance(descriptor, GreaterThanOrEqualToExpression):
141
- return ">="
142
- elif isinstance(descriptor, ContainsExpression):
143
- return "contains"
144
- elif isinstance(descriptor, BeginsWithExpression):
145
- return "beginsWith"
146
- elif isinstance(descriptor, EndsWithExpression):
147
- return "endsWith"
148
- elif isinstance(descriptor, DoesNotContainExpression):
149
- return "doesNotContain"
150
- elif isinstance(descriptor, DoesNotBeginWithExpression):
151
- return "doesNotBeginWith"
152
- elif isinstance(descriptor, DoesNotEndWithExpression):
153
- return "doesNotEndWith"
154
- elif isinstance(descriptor, (IsNullExpression, IsNilExpression, IsUndefinedExpression)):
155
- return "null"
156
- elif isinstance(descriptor, (IsNotNullExpression, IsNotNilExpression, IsNotUndefinedExpression)):
157
- return "notNull"
158
- elif isinstance(descriptor, InExpression):
159
- return "in"
160
- elif isinstance(descriptor, NotInExpression):
161
- return "notIn"
162
- elif isinstance(descriptor, BetweenExpression):
163
- return "between"
164
- elif isinstance(descriptor, NotBetweenExpression):
165
- return "notBetween"
166
- elif isinstance(descriptor, AndExpression):
167
- return "and"
168
- elif isinstance(descriptor, OrExpression):
169
- return "or"
170
- else:
171
- raise ValueError(f"Unsupported descriptor type: {descriptor}")
@@ -88,3 +88,59 @@ def test_vellum_workflow_display__serialize_input_variables_with_capitalized_var
88
88
  "extensions": {"color": None},
89
89
  }
90
90
  ]
91
+
92
+
93
+ def test_vellum_workflow_display_serialize_valid_handle_ids_for_base_nodes():
94
+ # GIVEN a workflow between two base nodes
95
+ class StartNode(BaseNode):
96
+ pass
97
+
98
+ class EndNode(BaseNode):
99
+ class Outputs(BaseNode.Outputs):
100
+ hello = "world"
101
+
102
+ class Workflow(BaseWorkflow):
103
+ graph = StartNode >> EndNode
104
+
105
+ class Outputs(BaseWorkflow.Outputs):
106
+ final_value = EndNode.Outputs.hello
107
+
108
+ # AND a display class for this workflow
109
+ workflow_display = get_workflow_display(
110
+ base_display_class=VellumWorkflowDisplay,
111
+ workflow_class=Workflow,
112
+ )
113
+
114
+ # WHEN we serialize the workflow
115
+ exec_config = workflow_display.serialize()
116
+
117
+ # THEN the serialized workflow handle ids are valid
118
+ raw_data = exec_config.get("workflow_raw_data")
119
+ assert isinstance(raw_data, dict)
120
+ nodes = raw_data.get("nodes")
121
+ edges = raw_data.get("edges")
122
+
123
+ assert isinstance(nodes, list)
124
+ assert isinstance(edges, list)
125
+
126
+ edge_source_handle_ids = {edge.get("source_handle_id") for edge in edges if isinstance(edge, dict)}
127
+ edge_target_handle_ids = {edge.get("target_handle_id") for edge in edges if isinstance(edge, dict)}
128
+
129
+ for node in nodes:
130
+ assert isinstance(node, dict)
131
+
132
+ if node["type"] in {"ENTRYPOINT", "TERMINAL"}:
133
+ continue
134
+
135
+ ports = node.get("ports")
136
+ assert isinstance(ports, list)
137
+ for port in ports:
138
+ assert isinstance(port, dict)
139
+ assert (
140
+ port["id"] in edge_source_handle_ids
141
+ ), f"Port {port['id']} from node {node['label']} not found in edge source handle ids"
142
+
143
+ assert isinstance(node["trigger"], dict)
144
+ assert (
145
+ node["trigger"]["id"] in edge_target_handle_ids
146
+ ), f"Trigger {node['trigger']['id']} from node {node['label']} not found in edge target handle ids"
@@ -1,6 +1,6 @@
1
1
  import pytest
2
2
  from uuid import uuid4
3
- from typing import Dict, Tuple, Type
3
+ from typing import Any, Dict, Tuple, Type
4
4
 
5
5
  from vellum.workflows.nodes.bases.base import BaseNode
6
6
  from vellum.workflows.references.output import OutputReference
@@ -8,9 +8,9 @@ from vellum.workflows.references.workflow_input import WorkflowInputReference
8
8
  from vellum.workflows.types.core import JsonObject
9
9
  from vellum.workflows.types.generics import NodeType
10
10
  from vellum_ee.workflows.display.base import WorkflowInputsDisplayType
11
+ from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
11
12
  from vellum_ee.workflows.display.nodes.get_node_display_class import get_node_display_class
12
13
  from vellum_ee.workflows.display.nodes.types import NodeOutputDisplay
13
- from vellum_ee.workflows.display.nodes.vellum.base_node import BaseNodeDisplay
14
14
  from vellum_ee.workflows.display.types import NodeDisplayType, WorkflowDisplayContext
15
15
  from vellum_ee.workflows.display.vellum import NodeDisplayData, WorkflowMetaVellumDisplay
16
16
  from vellum_ee.workflows.display.workflows.vellum_workflow_display import VellumWorkflowDisplay
@@ -20,11 +20,12 @@ from vellum_ee.workflows.display.workflows.vellum_workflow_display import Vellum
20
20
  def serialize_node():
21
21
  def _serialize_node(
22
22
  node_class: Type[NodeType],
23
+ base_class: type[BaseNodeDisplay[Any]] = BaseNodeDisplay,
23
24
  global_workflow_input_displays: Dict[WorkflowInputReference, WorkflowInputsDisplayType] = {},
24
25
  global_node_displays: Dict[Type[BaseNode], NodeDisplayType] = {},
25
26
  global_node_output_displays: Dict[OutputReference, Tuple[Type[BaseNode], NodeOutputDisplay]] = {},
26
27
  ) -> JsonObject:
27
- node_display_class = get_node_display_class(BaseNodeDisplay, node_class)
28
+ node_display_class = get_node_display_class(base_class, node_class)
28
29
  node_display = node_display_class()
29
30
 
30
31
  context: WorkflowDisplayContext = WorkflowDisplayContext(