vellum-ai 0.14.18__py3-none-any.whl → 0.14.20__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 (44) hide show
  1. vellum/__init__.py +2 -0
  2. vellum/client/core/client_wrapper.py +1 -1
  3. vellum/client/types/__init__.py +2 -0
  4. vellum/client/types/node_input_compiled_secret_value.py +23 -0
  5. vellum/client/types/node_input_variable_compiled_value.py +2 -0
  6. vellum/types/node_input_compiled_secret_value.py +3 -0
  7. vellum/workflows/nodes/core/inline_subworkflow_node/node.py +16 -14
  8. vellum/workflows/nodes/core/inline_subworkflow_node/tests/test_node.py +29 -0
  9. vellum/workflows/nodes/displayable/code_execution_node/node.py +1 -1
  10. vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py +41 -0
  11. vellum/workflows/nodes/displayable/code_execution_node/utils.py +6 -1
  12. vellum/workflows/references/lazy.py +9 -1
  13. vellum/workflows/references/tests/test_lazy.py +30 -0
  14. {vellum_ai-0.14.18.dist-info → vellum_ai-0.14.20.dist-info}/METADATA +1 -1
  15. {vellum_ai-0.14.18.dist-info → vellum_ai-0.14.20.dist-info}/RECORD +44 -40
  16. vellum_ee/workflows/display/base.py +6 -2
  17. vellum_ee/workflows/display/nodes/base_node_display.py +41 -10
  18. vellum_ee/workflows/display/nodes/base_node_vellum_display.py +0 -20
  19. vellum_ee/workflows/display/nodes/get_node_display_class.py +3 -3
  20. vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +14 -10
  21. vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +2 -6
  22. vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py +2 -6
  23. vellum_ee/workflows/display/nodes/vellum/tests/test_code_execution_node.py +113 -0
  24. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/conftest.py +2 -2
  25. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +4 -4
  26. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attributes_serialization.py +39 -8
  27. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_outputs_serialization.py +3 -3
  28. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_ports_serialization.py +14 -14
  29. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_trigger_serialization.py +3 -3
  30. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py +5 -5
  31. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_default_state_serialization.py +1 -1
  32. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_generic_node_serialization.py +1 -1
  33. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_subworkflow_serialization.py +2 -2
  34. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_map_node_serialization.py +2 -2
  35. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_merge_node_serialization.py +3 -3
  36. vellum_ee/workflows/display/types.py +4 -7
  37. vellum_ee/workflows/display/vellum.py +10 -2
  38. vellum_ee/workflows/display/workflows/base_workflow_display.py +60 -32
  39. vellum_ee/workflows/display/workflows/vellum_workflow_display.py +33 -78
  40. vellum_ee/workflows/server/virtual_file_loader.py +52 -22
  41. vellum_ee/workflows/tests/test_server.py +61 -0
  42. {vellum_ai-0.14.18.dist-info → vellum_ai-0.14.20.dist-info}/LICENSE +0 -0
  43. {vellum_ai-0.14.18.dist-info → vellum_ai-0.14.20.dist-info}/WHEEL +0 -0
  44. {vellum_ai-0.14.18.dist-info → vellum_ai-0.14.20.dist-info}/entry_points.txt +0 -0
@@ -115,6 +115,10 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
115
115
  port_displays: Dict[Port, PortDisplayOverrides] = {}
116
116
  node_input_ids_by_name: ClassVar[Dict[str, UUID]] = {}
117
117
 
118
+ # Used to explicitly set the target handle id for a node
119
+ # Once all nodes are Generic Nodes, we may replace this with a trigger_id or trigger attribute
120
+ target_handle_id: ClassVar[Optional[UUID]] = None
121
+
118
122
  # Used to store the mapping between node types and their display classes
119
123
  _node_display_registry: Dict[Type[NodeType], Type["BaseNodeDisplay"]] = {}
120
124
 
@@ -129,13 +133,16 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
129
133
  continue
130
134
 
131
135
  id = str(uuid4_from_hash(f"{node_id}|{attribute.name}"))
132
- attributes.append(
133
- {
134
- "id": id,
135
- "name": attribute.name,
136
- "value": self.serialize_value(display_context, cast(BaseDescriptor, attribute.instance)),
137
- }
138
- )
136
+ try:
137
+ attributes.append(
138
+ {
139
+ "id": id,
140
+ "name": attribute.name,
141
+ "value": self.serialize_value(display_context, attribute.instance),
142
+ }
143
+ )
144
+ except ValueError as e:
145
+ raise ValueError(f"Failed to serialize attribute '{attribute.name}': {e}")
139
146
 
140
147
  adornments = kwargs.get("adornments", None)
141
148
  wrapped_node = get_wrapped_node(node)
@@ -202,7 +209,7 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
202
209
  "base": self.get_base().dict(),
203
210
  "definition": self.get_definition().dict(),
204
211
  "trigger": {
205
- "id": str(self.get_trigger_id()),
212
+ "id": str(self.get_target_handle_id()),
206
213
  "merge_behavior": node.Trigger.merge_behavior.value,
207
214
  },
208
215
  "ports": ports,
@@ -252,7 +259,24 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
252
259
  return PortDisplay(id=port_id, node_id=self.node_id)
253
260
 
254
261
  def get_trigger_id(self) -> UUID:
255
- return uuid4_from_hash(f"{self.node_id}|trigger")
262
+ return self.get_target_handle_id()
263
+
264
+ def get_target_handle_id(self) -> UUID:
265
+ """
266
+ Is the same as `get_trigger_id()` but kept for legacy workflows. Once all workflows have been updated to
267
+ become Generic Nodes, we should be able to drive off of only `get_trigger_id()` going forward
268
+ """
269
+
270
+ return self._get_node_display_uuid("target_handle_id")
271
+
272
+ def get_target_handle_id_by_source_node_id(self, source_node_id: UUID) -> UUID:
273
+ """
274
+ In the vast majority of cases, nodes will only have a single target handle and can be retrieved independently
275
+ of the source node. However, in rare cases (such as legacy Merge nodes), this method can be overridden to
276
+ account for the case of retrieving one amongst multiple target handles on a node.
277
+ """
278
+
279
+ return self.get_target_handle_id()
256
280
 
257
281
  @classmethod
258
282
  def get_from_node_display_registry(cls, node_class: Type[NodeType]) -> Optional[Type["BaseNodeDisplay"]]:
@@ -312,6 +336,10 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
312
336
 
313
337
  raise ValueError(f"Node {cls.__name__} must define an explicit {attribute} of type {attribute_type.__name__}.")
314
338
 
339
+ def _get_node_display_uuid(self, attribute: str) -> UUID:
340
+ explicit_value = self._get_explicit_node_display_attr(attribute, UUID)
341
+ return explicit_value if explicit_value else uuid4_from_hash(f"{self.node_id}|{attribute}")
342
+
315
343
  def __init_subclass__(cls, **kwargs: Any) -> None:
316
344
  super().__init_subclass__(**kwargs)
317
345
  if not cls._node_display_registry:
@@ -369,7 +397,7 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
369
397
  "rhs": rhs,
370
398
  }
371
399
 
372
- def serialize_value(self, display_context: "WorkflowDisplayContext", value: BaseDescriptor) -> JsonObject:
400
+ def serialize_value(self, display_context: "WorkflowDisplayContext", value: Any) -> JsonObject:
373
401
  if isinstance(value, ConstantValueReference):
374
402
  return self.serialize_value(display_context, value._value)
375
403
 
@@ -415,6 +443,9 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
415
443
  "node_id": str(node_class_display.node_id),
416
444
  }
417
445
 
446
+ if isinstance(value, dict) and any(isinstance(v, BaseDescriptor) for v in value.values()):
447
+ raise ValueError("Nested references are not supported.")
448
+
418
449
  if not isinstance(value, BaseDescriptor):
419
450
  vellum_value = primitive_to_vellum_value(value)
420
451
  return {
@@ -4,7 +4,6 @@ from typing import ClassVar, Dict, Optional
4
4
  from vellum.workflows.nodes.utils import get_unadorned_node
5
5
  from vellum.workflows.ports import Port
6
6
  from vellum.workflows.types.generics import NodeType
7
- from vellum.workflows.utils.uuids import uuid4_from_hash
8
7
  from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
9
8
  from vellum_ee.workflows.display.nodes.types import PortDisplay
10
9
  from vellum_ee.workflows.display.vellum import NodeDisplayComment, NodeDisplayData
@@ -14,13 +13,6 @@ class BaseNodeVellumDisplay(BaseNodeDisplay[NodeType]):
14
13
  # Used to explicitly set display data for a node
15
14
  display_data: ClassVar[Optional[NodeDisplayData]] = None
16
15
 
17
- # Used to explicitly set the target handle id for a node
18
- target_handle_id: ClassVar[Optional[UUID]] = None
19
-
20
- def _get_node_display_uuid(self, attribute: str) -> UUID:
21
- explicit_value = self._get_explicit_node_display_attr(attribute, UUID)
22
- return explicit_value if explicit_value else uuid4_from_hash(f"{self.node_id}|{attribute}")
23
-
24
16
  def get_display_data(self) -> NodeDisplayData:
25
17
  explicit_value = self._get_explicit_node_display_attr("display_data", NodeDisplayData)
26
18
  docstring = self._node.__doc__
@@ -40,18 +32,6 @@ class BaseNodeVellumDisplay(BaseNodeDisplay[NodeType]):
40
32
 
41
33
  return explicit_value if explicit_value else NodeDisplayData()
42
34
 
43
- def get_target_handle_id(self) -> UUID:
44
- return self._get_node_display_uuid("target_handle_id")
45
-
46
- def get_target_handle_id_by_source_node_id(self, source_node_id: UUID) -> UUID:
47
- """
48
- In the vast majority of cases, nodes will only have a single target handle and can be retrieved independently
49
- of the source node. However, in rare cases (such as legacy Merge nodes), this method can be overridden to
50
- account for the case of retrieving one amongst multiple target handles on a node.
51
- """
52
-
53
- return self.get_target_handle_id()
54
-
55
35
  def get_source_handle_id(self, port_displays: Dict[Port, PortDisplay]) -> UUID:
56
36
  unadorned_node = get_unadorned_node(self._node)
57
37
  default_port = unadorned_node.Ports.default
@@ -7,12 +7,12 @@ from vellum.workflows.types.generics import NodeType
7
7
  from vellum.workflows.utils.uuids import uuid4_from_hash
8
8
 
9
9
  if TYPE_CHECKING:
10
- from vellum_ee.workflows.display.types import NodeDisplayType
10
+ from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
11
11
 
12
12
 
13
13
  def get_node_display_class(
14
- base_class: Type["NodeDisplayType"], node_class: Type[NodeType], root_node_class: Optional[Type[NodeType]] = None
15
- ) -> Type["NodeDisplayType"]:
14
+ base_class: Type["BaseNodeDisplay"], node_class: Type[NodeType], root_node_class: Optional[Type[NodeType]] = None
15
+ ) -> Type["BaseNodeDisplay"]:
16
16
  node_display_class = base_class.get_from_node_display_registry(node_class)
17
17
  if node_display_class:
18
18
  if not issubclass(node_display_class, base_class):
@@ -15,9 +15,6 @@ _CodeExecutionNodeType = TypeVar("_CodeExecutionNodeType", bound=CodeExecutionNo
15
15
 
16
16
 
17
17
  class BaseCodeExecutionNodeDisplay(BaseNodeVellumDisplay[_CodeExecutionNodeType], Generic[_CodeExecutionNodeType]):
18
- code_input_id: ClassVar[Optional[UUID]] = None
19
- runtime_input_id: ClassVar[Optional[UUID]] = None
20
-
21
18
  output_id: ClassVar[Optional[UUID]] = None
22
19
  log_output_id: ClassVar[Optional[UUID]] = None
23
20
 
@@ -27,17 +24,20 @@ class BaseCodeExecutionNodeDisplay(BaseNodeVellumDisplay[_CodeExecutionNodeType]
27
24
  node = self._node
28
25
  node_id = self.node_id
29
26
  raw_code = raise_if_descriptor(node.code)
27
+ filepath = raise_if_descriptor(node.filepath)
30
28
 
31
29
  code_value: Optional[str]
32
30
  if raw_code:
33
31
  code_value = raw_code
34
- else:
32
+ elif filepath:
35
33
  node_file_path = inspect.getfile(node)
36
34
  file_code = read_file_from_path(
37
35
  node_filepath=node_file_path,
38
- script_filepath=(raise_if_descriptor(node.filepath)), # type: ignore
36
+ script_filepath=filepath,
39
37
  )
40
38
  code_value = file_code
39
+ else:
40
+ code_value = ""
41
41
 
42
42
  code_inputs = raise_if_descriptor(node.code_inputs)
43
43
 
@@ -47,24 +47,28 @@ class BaseCodeExecutionNodeDisplay(BaseNodeVellumDisplay[_CodeExecutionNodeType]
47
47
  input_name=variable_name,
48
48
  value=variable_value,
49
49
  display_context=display_context,
50
- input_id=self.node_input_ids_by_name.get(variable_name),
50
+ input_id=self.node_input_ids_by_name.get(f"{CodeExecutionNode.code_inputs.name}.{variable_name}")
51
+ or self.node_input_ids_by_name.get(variable_name),
51
52
  )
52
53
  for variable_name, variable_value in code_inputs.items()
53
54
  ]
54
55
 
56
+ code_input_id = self.node_input_ids_by_name.get(CodeExecutionNode.code.name)
55
57
  code_node_input = create_node_input(
56
58
  node_id=node_id,
57
59
  input_name="code",
58
60
  value=code_value,
59
61
  display_context=display_context,
60
- input_id=self.code_input_id,
62
+ input_id=code_input_id,
61
63
  )
64
+
65
+ runtime_input_id = self.node_input_ids_by_name.get(CodeExecutionNode.runtime.name)
62
66
  runtime_node_input = create_node_input(
63
67
  node_id=node_id,
64
68
  input_name="runtime",
65
69
  value=node.runtime,
66
70
  display_context=display_context,
67
- input_id=self.runtime_input_id,
71
+ input_id=runtime_input_id,
68
72
  )
69
73
  inputs.extend([code_node_input, runtime_node_input])
70
74
 
@@ -84,8 +88,8 @@ class BaseCodeExecutionNodeDisplay(BaseNodeVellumDisplay[_CodeExecutionNodeType]
84
88
  "error_output_id": str(error_output_id) if error_output_id else None,
85
89
  "source_handle_id": str(self.get_source_handle_id(display_context.port_displays)),
86
90
  "target_handle_id": str(self.get_target_handle_id()),
87
- "code_input_id": str(self.code_input_id) if self.code_input_id else code_node_input.id,
88
- "runtime_input_id": str(self.runtime_input_id) if self.runtime_input_id else runtime_node_input.id,
91
+ "code_input_id": code_node_input.id,
92
+ "runtime_input_id": runtime_node_input.id,
89
93
  "output_type": output_type,
90
94
  "packages": [package.dict() for package in packages] if packages is not None else [],
91
95
  "output_id": str(self.output_id) if self.output_id else str(output_display.id),
@@ -1,5 +1,5 @@
1
1
  from uuid import UUID
2
- from typing import Callable, ClassVar, Dict, Generic, List, Optional, Tuple, Type, TypeVar, Union
2
+ from typing import Callable, Dict, Generic, List, Optional, Tuple, Type, TypeVar, Union
3
3
 
4
4
  from vellum import FunctionDefinition, PromptBlock, RichTextChildBlock, VellumVariable
5
5
  from vellum.workflows.nodes import InlinePromptNode
@@ -17,10 +17,6 @@ _InlinePromptNodeType = TypeVar("_InlinePromptNodeType", bound=InlinePromptNode)
17
17
 
18
18
 
19
19
  class BaseInlinePromptNodeDisplay(BaseNodeVellumDisplay[_InlinePromptNodeType], Generic[_InlinePromptNodeType]):
20
- output_id: ClassVar[Optional[UUID]] = None
21
- array_output_id: ClassVar[Optional[UUID]] = None
22
- prompt_input_ids_by_name: ClassVar[Dict[str, UUID]] = {}
23
-
24
20
  def serialize(
25
21
  self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **kwargs
26
22
  ) -> JsonObject:
@@ -98,7 +94,7 @@ class BaseInlinePromptNodeDisplay(BaseNodeVellumDisplay[_InlinePromptNodeType],
98
94
  input_name=variable_name,
99
95
  value=variable_value,
100
96
  display_context=display_context,
101
- input_id=self.prompt_input_ids_by_name.get(variable_name),
97
+ input_id=self.node_input_ids_by_name.get(variable_name),
102
98
  )
103
99
  vellum_variable_type = infer_vellum_variable_type(variable_value)
104
100
  node_inputs.append(node_input)
@@ -1,5 +1,5 @@
1
1
  from uuid import UUID
2
- from typing import ClassVar, Dict, Generic, Optional, TypeVar, cast
2
+ from typing import Generic, Optional, TypeVar, cast
3
3
 
4
4
  from vellum.workflows.nodes.displayable.prompt_deployment_node import PromptDeploymentNode
5
5
  from vellum.workflows.references import OutputReference
@@ -16,10 +16,6 @@ _PromptDeploymentNodeType = TypeVar("_PromptDeploymentNodeType", bound=PromptDep
16
16
  class BasePromptDeploymentNodeDisplay(
17
17
  BaseNodeVellumDisplay[_PromptDeploymentNodeType], Generic[_PromptDeploymentNodeType]
18
18
  ):
19
- output_id: ClassVar[Optional[UUID]] = None
20
- array_output_id: ClassVar[Optional[UUID]] = None
21
- prompt_input_ids_by_name: ClassVar[Dict[str, UUID]] = {}
22
-
23
19
  def serialize(
24
20
  self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **kwargs
25
21
  ) -> JsonObject:
@@ -34,7 +30,7 @@ class BasePromptDeploymentNodeDisplay(
34
30
  input_name=variable_name,
35
31
  value=variable_value,
36
32
  display_context=display_context,
37
- input_id=self.prompt_input_ids_by_name.get(variable_name),
33
+ input_id=self.node_input_ids_by_name.get(variable_name),
38
34
  )
39
35
  for variable_name, variable_value in prompt_inputs.items()
40
36
  ]
@@ -0,0 +1,113 @@
1
+ import pytest
2
+ from uuid import UUID
3
+ from typing import Type
4
+
5
+ from vellum.workflows.nodes.displayable.code_execution_node.node import CodeExecutionNode
6
+ from vellum.workflows.workflows.base import BaseWorkflow
7
+ from vellum_ee.workflows.display.nodes.vellum.code_execution_node import BaseCodeExecutionNodeDisplay
8
+ from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
9
+ from vellum_ee.workflows.display.workflows.vellum_workflow_display import VellumWorkflowDisplay
10
+
11
+
12
+ def _no_display_class(Node: Type[CodeExecutionNode]):
13
+ return None
14
+
15
+
16
+ def _display_class_with_node_input_ids_by_name(Node: Type[CodeExecutionNode]):
17
+ class CodeExecutionNodeDisplay(BaseCodeExecutionNodeDisplay[Node]): # type: ignore[valid-type]
18
+ node_input_ids_by_name = {"foo": UUID("fba6a4d5-835a-4e99-afb7-f6a4aed15110")}
19
+
20
+ return CodeExecutionNodeDisplay
21
+
22
+
23
+ def _display_class_with_node_input_ids_by_name_with_inputs_prefix(Node: Type[CodeExecutionNode]):
24
+ class CodeExecutionNodeDisplay(BaseCodeExecutionNodeDisplay[Node]): # type: ignore[valid-type]
25
+ node_input_ids_by_name = {"code_inputs.foo": UUID("fba6a4d5-835a-4e99-afb7-f6a4aed15110")}
26
+
27
+ return CodeExecutionNodeDisplay
28
+
29
+
30
+ @pytest.mark.parametrize(
31
+ ["GetDisplayClass", "expected_input_id"],
32
+ [
33
+ (_no_display_class, "e3cdb222-324e-4ad1-abb2-bdd7881b3a0e"),
34
+ (_display_class_with_node_input_ids_by_name, "fba6a4d5-835a-4e99-afb7-f6a4aed15110"),
35
+ (_display_class_with_node_input_ids_by_name_with_inputs_prefix, "fba6a4d5-835a-4e99-afb7-f6a4aed15110"),
36
+ ],
37
+ ids=[
38
+ "no_display_class",
39
+ "display_class_with_node_input_ids_by_name",
40
+ "display_class_with_node_input_ids_by_name_with_inputs_prefix",
41
+ ],
42
+ )
43
+ def test_serialize_node__code_node_inputs(GetDisplayClass, expected_input_id):
44
+ # GIVEN a code node with inputs
45
+ class MyCodeExecutionNode(CodeExecutionNode):
46
+ code_inputs = {"foo": "bar"}
47
+
48
+ # AND a workflow with the code node
49
+ class Workflow(BaseWorkflow):
50
+ graph = MyCodeExecutionNode
51
+
52
+ # AND a display class
53
+ GetDisplayClass(MyCodeExecutionNode)
54
+
55
+ # WHEN the workflow is serialized
56
+ workflow_display = get_workflow_display(base_display_class=VellumWorkflowDisplay, workflow_class=Workflow)
57
+ serialized_workflow: dict = workflow_display.serialize()
58
+
59
+ # THEN the node should properly serialize the inputs
60
+ my_code_execution_node = next(
61
+ node for node in serialized_workflow["workflow_raw_data"]["nodes"] if node["type"] == "CODE_EXECUTION"
62
+ )
63
+
64
+ assert my_code_execution_node["inputs"] == [
65
+ {
66
+ "id": expected_input_id,
67
+ "key": "foo",
68
+ "value": {
69
+ "combinator": "OR",
70
+ "rules": [
71
+ {
72
+ "type": "CONSTANT_VALUE",
73
+ "data": {
74
+ "type": "STRING",
75
+ "value": "bar",
76
+ },
77
+ }
78
+ ],
79
+ },
80
+ },
81
+ {
82
+ "id": "9774d864-c76d-4a1a-8181-b632ed3ab87c",
83
+ "key": "code",
84
+ "value": {
85
+ "combinator": "OR",
86
+ "rules": [
87
+ {
88
+ "type": "CONSTANT_VALUE",
89
+ "data": {
90
+ "type": "STRING",
91
+ "value": "",
92
+ },
93
+ }
94
+ ],
95
+ },
96
+ },
97
+ {
98
+ "id": "34742235-5699-45cd-9d34-bce3745e743d",
99
+ "key": "runtime",
100
+ "value": {
101
+ "combinator": "OR",
102
+ "rules": [
103
+ {
104
+ "type": "CONSTANT_VALUE",
105
+ "data": {
106
+ "type": "STRING",
107
+ "value": "PYTHON_3_11_6",
108
+ },
109
+ }
110
+ ],
111
+ },
112
+ },
113
+ ]
@@ -12,7 +12,7 @@ from vellum_ee.workflows.display.base import StateValueDisplayType, WorkflowInpu
12
12
  from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
13
13
  from vellum_ee.workflows.display.nodes.get_node_display_class import get_node_display_class
14
14
  from vellum_ee.workflows.display.nodes.types import NodeOutputDisplay
15
- from vellum_ee.workflows.display.types import NodeDisplayType, WorkflowDisplayContext
15
+ from vellum_ee.workflows.display.types import WorkflowDisplayContext
16
16
  from vellum_ee.workflows.display.vellum import NodeDisplayData, WorkflowMetaVellumDisplay
17
17
  from vellum_ee.workflows.display.workflows.vellum_workflow_display import VellumWorkflowDisplay
18
18
 
@@ -24,7 +24,7 @@ def serialize_node():
24
24
  base_class: type[BaseNodeDisplay[Any]] = BaseNodeDisplay,
25
25
  global_workflow_input_displays: Dict[WorkflowInputReference, WorkflowInputsDisplayType] = {},
26
26
  global_state_value_displays: Dict[StateValueReference, StateValueDisplayType] = {},
27
- global_node_displays: Dict[Type[BaseNode], NodeDisplayType] = {},
27
+ global_node_displays: Dict[Type[BaseNode], BaseNodeDisplay] = {},
28
28
  global_node_output_displays: Dict[OutputReference, Tuple[Type[BaseNode], NodeOutputDisplay]] = {},
29
29
  ) -> JsonObject:
30
30
  node_display_class = get_node_display_class(base_class, node_class)
@@ -64,7 +64,7 @@ def test_serialize_node__retry(serialize_node):
64
64
  "test_adornments_serialization",
65
65
  ],
66
66
  },
67
- "trigger": {"id": "d38a83bf-23d1-4f9d-a875-a08dc27cf397", "merge_behavior": "AWAIT_ATTRIBUTES"},
67
+ "trigger": {"id": "75fbe874-c00b-4fc2-9ade-52f4fe9209fa", "merge_behavior": "AWAIT_ATTRIBUTES"},
68
68
  "ports": [{"id": "078650c9-f775-4cd0-a08c-23af9983a361", "name": "default", "type": "DEFAULT"}],
69
69
  "adornments": [
70
70
  {
@@ -175,7 +175,7 @@ def test_serialize_node__try(serialize_node):
175
175
  "test_adornments_serialization",
176
176
  ],
177
177
  },
178
- "trigger": {"id": "16bc1522-c408-47ad-9a22-0ef136384abf", "merge_behavior": "AWAIT_ATTRIBUTES"},
178
+ "trigger": {"id": "bbb343ff-2b7a-4793-a8cf-fb05132ca46a", "merge_behavior": "AWAIT_ATTRIBUTES"},
179
179
  "ports": [{"id": "8d25f244-4b12-4f8b-b202-8948698679a0", "name": "default", "type": "DEFAULT"}],
180
180
  "adornments": [
181
181
  {
@@ -283,7 +283,7 @@ def test_serialize_node__stacked():
283
283
  "test_adornments_serialization",
284
284
  ],
285
285
  },
286
- "trigger": {"id": "f206358d-04a5-41c9-beee-0871a074fa48", "merge_behavior": "AWAIT_ATTRIBUTES"},
286
+ "trigger": {"id": "6e4af17f-bbee-4777-b10d-af042cd6e16a", "merge_behavior": "AWAIT_ATTRIBUTES"},
287
287
  "ports": [{"id": "408cd5fb-3a3e-4eb2-9889-61111bd6a129", "name": "default", "type": "DEFAULT"}],
288
288
  "adornments": [
289
289
  {
@@ -342,7 +342,7 @@ def test_serialize_node__stacked():
342
342
  "source_node_id": "c14c1c9b-a7a4-4d2c-84fb-c940cfb09525",
343
343
  "source_handle_id": "51a5eb25-af14-4bee-9ced-d2aa534ea8e9",
344
344
  "target_node_id": "074833b0-e142-4bbc-8dec-209a35e178a3",
345
- "target_handle_id": "f206358d-04a5-41c9-beee-0871a074fa48",
345
+ "target_handle_id": "6e4af17f-bbee-4777-b10d-af042cd6e16a",
346
346
  "type": "DEFAULT",
347
347
  }
348
348
  ],
@@ -1,12 +1,16 @@
1
+ import pytest
1
2
  from uuid import uuid4
3
+ from typing import List
2
4
 
3
5
  from deepdiff import DeepDiff
4
6
 
7
+ from vellum.client.types.chat_message import ChatMessage
5
8
  from vellum.workflows.inputs.base import BaseInputs
6
9
  from vellum.workflows.nodes.bases.base import BaseNode
7
10
  from vellum.workflows.references.constant import ConstantValueReference
8
11
  from vellum.workflows.references.lazy import LazyReference
9
12
  from vellum.workflows.references.vellum_secret import VellumSecretReference
13
+ from vellum.workflows.state.base import BaseState
10
14
  from vellum.workflows.workflows.base import BaseWorkflow
11
15
  from vellum_ee.workflows.display.base import WorkflowInputsDisplay
12
16
  from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
@@ -44,7 +48,7 @@ def test_serialize_node__constant_value(serialize_node):
44
48
  "test_attributes_serialization",
45
49
  ],
46
50
  },
47
- "trigger": {"id": "5d41f6fc-fc1a-4a19-9a06-6a0ea9d38557", "merge_behavior": "AWAIT_ATTRIBUTES"},
51
+ "trigger": {"id": "e2cde904-de60-4755-87cf-55052ea23a51", "merge_behavior": "AWAIT_ATTRIBUTES"},
48
52
  "ports": [{"id": "96ac6512-0128-4cf7-ba51-2725b4807c8f", "type": "DEFAULT", "name": "default"}],
49
53
  "adornments": None,
50
54
  "attributes": [
@@ -92,7 +96,7 @@ def test_serialize_node__constant_value_reference(serialize_node):
92
96
  "test_attributes_serialization",
93
97
  ],
94
98
  },
95
- "trigger": {"id": "174f3a8e-99c2-4045-8327-ad2dc658889e", "merge_behavior": "AWAIT_ATTRIBUTES"},
99
+ "trigger": {"id": "dc2f90b9-14a1-457a-a9f9-dec7a04f74eb", "merge_behavior": "AWAIT_ATTRIBUTES"},
96
100
  "ports": [{"id": "61adfacf-c3a9-4aea-a3da-bcdbc03273c6", "name": "default", "type": "DEFAULT"}],
97
101
  "adornments": None,
98
102
  "attributes": [
@@ -134,7 +138,7 @@ def test_serialize_node__lazy_reference(serialize_node):
134
138
  "test_attributes_serialization",
135
139
  ],
136
140
  },
137
- "trigger": {"id": "a3598540-7464-4965-8a2f-f022a011007d", "merge_behavior": "AWAIT_ATTRIBUTES"},
141
+ "trigger": {"id": "14ec4d19-13e5-4db3-94fa-4e15274bffc7", "merge_behavior": "AWAIT_ATTRIBUTES"},
138
142
  "ports": [{"id": "2dba7224-a376-4780-8414-2b50601f9283", "name": "default", "type": "DEFAULT"}],
139
143
  "adornments": None,
140
144
  "attributes": [
@@ -217,7 +221,7 @@ def test_serialize_node__workflow_input(serialize_node):
217
221
  "test_attributes_serialization",
218
222
  ],
219
223
  },
220
- "trigger": {"id": "dcb92d51-1fbd-4d41-ab89-c8f490d2bb38", "merge_behavior": "AWAIT_ATTRIBUTES"},
224
+ "trigger": {"id": "debf37b9-720d-48dd-9699-69283966f927", "merge_behavior": "AWAIT_ATTRIBUTES"},
221
225
  "ports": [{"id": "20d91130-ca86-4420-b2e7-a962c0f1a509", "type": "DEFAULT", "name": "default"}],
222
226
  "adornments": None,
223
227
  "attributes": [
@@ -237,6 +241,33 @@ def test_serialize_node__workflow_input(serialize_node):
237
241
  )
238
242
 
239
243
 
244
+ def test_serialize_node__workflow_input_as_nested_chat_history():
245
+ # GIVEN workflow inputs as chat history
246
+ class Inputs(BaseInputs):
247
+ chat_history: List[ChatMessage]
248
+
249
+ # AND a node referencing the workflow input
250
+ class GenericNode(BaseNode):
251
+ attr = {
252
+ "hello": Inputs.chat_history,
253
+ }
254
+
255
+ # AND a workflow with the node
256
+ class Workflow(BaseWorkflow[Inputs, BaseState]):
257
+ graph = GenericNode
258
+
259
+ # WHEN the workflow is serialized
260
+ workflow_display = get_workflow_display(
261
+ base_display_class=VellumWorkflowDisplay,
262
+ workflow_class=Workflow,
263
+ )
264
+ with pytest.raises(Exception) as exc_info:
265
+ workflow_display.serialize()
266
+
267
+ # THEN we should raise a user facing error
268
+ assert str(exc_info.value) == "Failed to serialize attribute 'attr': Nested references are not supported."
269
+
270
+
240
271
  def test_serialize_node__node_output(serialize_node):
241
272
  class NodeWithOutput(BaseNode):
242
273
  class Outputs(BaseNode.Outputs):
@@ -278,7 +309,7 @@ def test_serialize_node__node_output(serialize_node):
278
309
  "test_attributes_serialization",
279
310
  ],
280
311
  },
281
- "trigger": {"id": "aa7f0dce-0413-4802-b1dd-f96a2d2eb8e5", "merge_behavior": "AWAIT_ATTRIBUTES"},
312
+ "trigger": {"id": "d4b08664-bb78-4fdd-83a2-877c4ca4175a", "merge_behavior": "AWAIT_ATTRIBUTES"},
282
313
  "ports": [{"id": "a345665a-decd-4f6b-af38-387bd41c2643", "type": "DEFAULT", "name": "default"}],
283
314
  "adornments": None,
284
315
  "attributes": [
@@ -328,7 +359,7 @@ def test_serialize_node__vellum_secret(serialize_node):
328
359
  "test_attributes_serialization",
329
360
  ],
330
361
  },
331
- "trigger": {"id": "c5006d90-90cc-4e97-9092-f75785fa61ec", "merge_behavior": "AWAIT_ATTRIBUTES"},
362
+ "trigger": {"id": "70a3d4c0-83e3-428d-ac84-bf9e5644a84d", "merge_behavior": "AWAIT_ATTRIBUTES"},
332
363
  "ports": [{"id": "6d1c2139-64bd-4433-84d7-3fe08850134b", "type": "DEFAULT", "name": "default"}],
333
364
  "adornments": None,
334
365
  "attributes": [
@@ -381,7 +412,7 @@ def test_serialize_node__node_execution(serialize_node):
381
412
  "test_attributes_serialization",
382
413
  ],
383
414
  },
384
- "trigger": {"id": "2fc95236-b5bc-4574-bade-2c9f0933b18c", "merge_behavior": "AWAIT_ATTRIBUTES"},
415
+ "trigger": {"id": "0c06baa5-55b6-494a-a89d-9535dfa5f24b", "merge_behavior": "AWAIT_ATTRIBUTES"},
385
416
  "ports": [{"id": "59844b72-ac5e-43c5-b3a7-9c57ba73ec8c", "type": "DEFAULT", "name": "default"}],
386
417
  "adornments": None,
387
418
  "attributes": [
@@ -462,7 +493,7 @@ def test_serialize_node__coalesce(serialize_node):
462
493
  "test_attributes_serialization",
463
494
  ],
464
495
  },
465
- "trigger": {"id": "0302231d-73f2-4587-8a62-8ed3640f0f91", "merge_behavior": "AWAIT_ATTRIBUTES"},
496
+ "trigger": {"id": "b9894d9a-1887-416d-895d-a4129aac37b8", "merge_behavior": "AWAIT_ATTRIBUTES"},
466
497
  "ports": [{"id": "9d97a0c9-6a79-433a-bcdf-e07aa10c0f3c", "name": "default", "type": "DEFAULT"}],
467
498
  "adornments": None,
468
499
  "attributes": [
@@ -39,7 +39,7 @@ def test_serialize_node__annotated_output(serialize_node):
39
39
  "test_outputs_serialization",
40
40
  ],
41
41
  },
42
- "trigger": {"id": "753f7ef1-8724-4af2-939a-794f74ffc21b", "merge_behavior": "AWAIT_ATTRIBUTES"},
42
+ "trigger": {"id": "e66c7dde-02c9-4f6d-84a6-16117b54cd88", "merge_behavior": "AWAIT_ATTRIBUTES"},
43
43
  "ports": [{"id": "d83b7a5d-bbac-47ee-9277-1fbed71e83e8", "type": "DEFAULT", "name": "default"}],
44
44
  "adornments": None,
45
45
  "attributes": [],
@@ -87,7 +87,7 @@ def test_serialize_node__workflow_input(serialize_node):
87
87
  "test_outputs_serialization",
88
88
  ],
89
89
  },
90
- "trigger": {"id": "dcb92d51-1fbd-4d41-ab89-c8f490d2bb38", "merge_behavior": "AWAIT_ATTRIBUTES"},
90
+ "trigger": {"id": "debf37b9-720d-48dd-9699-69283966f927", "merge_behavior": "AWAIT_ATTRIBUTES"},
91
91
  "ports": [{"id": "20d91130-ca86-4420-b2e7-a962c0f1a509", "type": "DEFAULT", "name": "default"}],
92
92
  "adornments": None,
93
93
  "attributes": [],
@@ -150,7 +150,7 @@ def test_serialize_node__node_output_reference(serialize_node):
150
150
  "test_outputs_serialization",
151
151
  ],
152
152
  },
153
- "trigger": {"id": "e949426f-9f3c-425e-a4de-8c0c5f6a8945", "merge_behavior": "AWAIT_ATTRIBUTES"},
153
+ "trigger": {"id": "c8804b97-9f84-41b6-ade8-aa74544d6846", "merge_behavior": "AWAIT_ATTRIBUTES"},
154
154
  "ports": [{"id": "383dc10a-d8f3-4bac-b995-8b95bc6deb21", "type": "DEFAULT", "name": "default"}],
155
155
  "adornments": None,
156
156
  "attributes": [],