vellum-ai 0.14.4__py3-none-any.whl → 0.14.5__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 (21) hide show
  1. vellum/client/core/client_wrapper.py +1 -1
  2. vellum/workflows/context.py +8 -3
  3. vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py +81 -1
  4. vellum/workflows/nodes/displayable/code_execution_node/utils.py +44 -20
  5. vellum/workflows/nodes/displayable/prompt_deployment_node/node.py +17 -10
  6. vellum/workflows/workflows/base.py +41 -24
  7. vellum/workflows/workflows/tests/test_base_workflow.py +47 -0
  8. vellum/workflows/workflows/tests/test_context.py +60 -0
  9. {vellum_ai-0.14.4.dist-info → vellum_ai-0.14.5.dist-info}/METADATA +1 -1
  10. {vellum_ai-0.14.4.dist-info → vellum_ai-0.14.5.dist-info}/RECORD +21 -19
  11. vellum_ee/workflows/display/nodes/vellum/__init__.py +2 -0
  12. vellum_ee/workflows/display/nodes/vellum/base_adornment_node.py +39 -0
  13. vellum_ee/workflows/display/nodes/vellum/map_node.py +2 -2
  14. vellum_ee/workflows/display/nodes/vellum/retry_node.py +36 -4
  15. vellum_ee/workflows/display/nodes/vellum/try_node.py +43 -29
  16. vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py +25 -1
  17. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py +14 -0
  18. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py +19 -1
  19. {vellum_ai-0.14.4.dist-info → vellum_ai-0.14.5.dist-info}/LICENSE +0 -0
  20. {vellum_ai-0.14.4.dist-info → vellum_ai-0.14.5.dist-info}/WHEEL +0 -0
  21. {vellum_ai-0.14.4.dist-info → vellum_ai-0.14.5.dist-info}/entry_points.txt +0 -0
@@ -18,7 +18,7 @@ class BaseClientWrapper:
18
18
  headers: typing.Dict[str, str] = {
19
19
  "X-Fern-Language": "Python",
20
20
  "X-Fern-SDK-Name": "vellum-ai",
21
- "X-Fern-SDK-Version": "0.14.4",
21
+ "X-Fern-SDK-Version": "0.14.5",
22
22
  }
23
23
  headers["X_API_KEY"] = self.api_key
24
24
  return headers
@@ -1,5 +1,6 @@
1
1
  from contextlib import contextmanager
2
2
  import threading
3
+ from uuid import UUID
3
4
  from typing import Iterator, Optional, cast
4
5
 
5
6
  from vellum.client.core import UniversalBaseModel
@@ -8,6 +9,7 @@ from vellum.workflows.events.types import ParentContext
8
9
 
9
10
  class ExecutionContext(UniversalBaseModel):
10
11
  parent_context: Optional[ParentContext] = None
12
+ trace_id: Optional[UUID] = None
11
13
 
12
14
 
13
15
  _CONTEXT_KEY = "_execution_context"
@@ -30,11 +32,14 @@ def get_parent_context() -> ParentContext:
30
32
 
31
33
 
32
34
  @contextmanager
33
- def execution_context(parent_context: Optional[ParentContext] = None) -> Iterator[None]:
35
+ def execution_context(
36
+ parent_context: Optional[ParentContext] = None, trace_id: Optional[UUID] = None
37
+ ) -> Iterator[None]:
34
38
  """Context manager for handling execution context."""
35
39
  prev_context = get_execution_context()
36
- set_context = ExecutionContext(parent_context=parent_context) if parent_context else prev_context
37
-
40
+ set_trace_id = prev_context.trace_id or trace_id
41
+ set_parent_context = parent_context or prev_context.parent_context
42
+ set_context = ExecutionContext(parent_context=set_parent_context, trace_id=set_trace_id)
38
43
  try:
39
44
  set_execution_context(set_context)
40
45
  yield
@@ -1,12 +1,16 @@
1
1
  import pytest
2
2
  import os
3
- from typing import Any, Union
3
+ from typing import Any, List, Union
4
+
5
+ from pydantic import BaseModel
4
6
 
5
7
  from vellum import CodeExecutorResponse, NumberVellumValue, StringInput, StringVellumValue
8
+ from vellum.client.types.chat_message import ChatMessage
6
9
  from vellum.client.types.code_execution_package import CodeExecutionPackage
7
10
  from vellum.client.types.code_executor_secret_input import CodeExecutorSecretInput
8
11
  from vellum.client.types.function_call import FunctionCall
9
12
  from vellum.client.types.number_input import NumberInput
13
+ from vellum.client.types.string_chat_message_content import StringChatMessageContent
10
14
  from vellum.workflows.errors import WorkflowErrorCode
11
15
  from vellum.workflows.exceptions import NodeException
12
16
  from vellum.workflows.inputs.base import BaseInputs
@@ -610,3 +614,79 @@ def main(arg1: list[bool]) -> int:
610
614
 
611
615
  # AND the error should contain the execution error details
612
616
  assert outputs == {"result": 3, "log": ""}
617
+
618
+
619
+ def test_run_node__union_output_type__pydantic_children():
620
+ # GIVEN a node that is a union type with a pydantic child
621
+ class OptionOne(BaseModel):
622
+ foo: str
623
+
624
+ class OptionTwo(BaseModel):
625
+ bar: int
626
+
627
+ class ExampleCodeExecutionNode(CodeExecutionNode[BaseState, Union[OptionOne, OptionTwo]]):
628
+ code = """\
629
+ def main():
630
+ return { "foo": "hello" }
631
+ """
632
+ runtime = "PYTHON_3_11_6"
633
+ code_inputs = {}
634
+
635
+ # WHEN we run the node
636
+ node = ExampleCodeExecutionNode()
637
+
638
+ # THEN it should run successfully
639
+ outputs = node.run()
640
+
641
+ # AND the result should be the correct type
642
+ assert outputs == {"result": OptionOne(foo="hello"), "log": ""}
643
+
644
+
645
+ def test_run_node__union_output_type__miss():
646
+ # GIVEN a node that is a union type
647
+ class ExampleCodeExecutionNode(CodeExecutionNode[BaseState, Union[int, float]]):
648
+ code = """\
649
+ def main():
650
+ return "hello"
651
+ """
652
+ runtime = "PYTHON_3_11_6"
653
+ code_inputs = {}
654
+
655
+ # WHEN we run the node
656
+ node = ExampleCodeExecutionNode()
657
+
658
+ # THEN it should raise a NodeException with the execution error
659
+ with pytest.raises(NodeException) as exc_info:
660
+ node.run()
661
+
662
+ # AND the error should contain the execution error details
663
+ assert exc_info.value.message == "Expected an output of type 'int | float', but received 'str'"
664
+
665
+
666
+ def test_run_node__chat_history_output_type():
667
+ # GIVEN a node that that has a chat history return type
668
+ class ExampleCodeExecutionNode(CodeExecutionNode[BaseState, List[ChatMessage]]):
669
+ code = """\
670
+ def main():
671
+ return [
672
+ {
673
+ "role": "USER",
674
+ "content": {
675
+ "type": "STRING",
676
+ "value": "Hello, world!",
677
+ }
678
+ }
679
+ ]
680
+ """
681
+ code_inputs = {}
682
+ runtime = "PYTHON_3_11_6"
683
+
684
+ # WHEN we run the node
685
+ node = ExampleCodeExecutionNode()
686
+ outputs = node.run()
687
+
688
+ # AND the error should contain the execution error details
689
+ assert outputs == {
690
+ "result": [ChatMessage(role="USER", content=StringChatMessageContent(value="Hello, world!"))],
691
+ "log": "",
692
+ }
@@ -67,6 +67,49 @@ def _clean_for_dict_wrapper(obj):
67
67
  return obj
68
68
 
69
69
 
70
+ def _get_type_name(obj: Any) -> str:
71
+ if isinstance(obj, type):
72
+ return obj.__name__
73
+
74
+ if get_origin(obj) is Union:
75
+ children = [_get_type_name(child) for child in get_args(obj)]
76
+ return " | ".join(children)
77
+
78
+ return str(obj)
79
+
80
+
81
+ def _cast_to_output_type(result: Any, output_type: Any) -> Any:
82
+ is_valid_output_type = isinstance(output_type, type)
83
+ if get_origin(output_type) is Union:
84
+ allowed_types = get_args(output_type)
85
+ for allowed_type in allowed_types:
86
+ try:
87
+ return _cast_to_output_type(result, allowed_type)
88
+ except NodeException:
89
+ continue
90
+ elif get_origin(output_type) is list:
91
+ allowed_item_type = get_args(output_type)[0]
92
+ if isinstance(result, list):
93
+ return [_cast_to_output_type(item, allowed_item_type) for item in result]
94
+ elif is_valid_output_type and issubclass(output_type, BaseModel) and not isinstance(result, output_type):
95
+ try:
96
+ return output_type.model_validate(result)
97
+ except ValidationError as e:
98
+ raise NodeException(
99
+ code=WorkflowErrorCode.INVALID_OUTPUTS,
100
+ message=re.sub(r"\s+For further information visit [^\s]+", "", str(e)),
101
+ ) from e
102
+ elif is_valid_output_type and isinstance(result, output_type):
103
+ return result
104
+
105
+ output_type_name = _get_type_name(output_type)
106
+ result_type_name = _get_type_name(type(result))
107
+ raise NodeException(
108
+ code=WorkflowErrorCode.INVALID_OUTPUTS,
109
+ message=f"Expected an output of type '{output_type_name}', but received '{result_type_name}'",
110
+ )
111
+
112
+
70
113
  def run_code_inline(
71
114
  code: str,
72
115
  inputs: EntityInputsInterface,
@@ -112,25 +155,6 @@ __arg__out = main({", ".join(run_args)})
112
155
  result = exec_globals["__arg__out"]
113
156
 
114
157
  if output_type != Any:
115
- if get_origin(output_type) is Union:
116
- allowed_types = get_args(output_type)
117
- if not isinstance(result, allowed_types):
118
- raise NodeException(
119
- code=WorkflowErrorCode.INVALID_OUTPUTS,
120
- message=f"Expected output to be in types {allowed_types}, but received '{type(result).__name__}'",
121
- )
122
- elif issubclass(output_type, BaseModel) and not isinstance(result, output_type):
123
- try:
124
- result = output_type.model_validate(result)
125
- except ValidationError as e:
126
- raise NodeException(
127
- code=WorkflowErrorCode.INVALID_OUTPUTS,
128
- message=re.sub(r"\s+For further information visit [^\s]+", "", str(e)),
129
- ) from e
130
- elif not isinstance(result, output_type):
131
- raise NodeException(
132
- code=WorkflowErrorCode.INVALID_OUTPUTS,
133
- message=f"Expected an output of type '{output_type.__name__}', but received '{type(result).__name__}'",
134
- )
158
+ result = _cast_to_output_type(result, output_type)
135
159
 
136
160
  return logs, result
@@ -1,3 +1,4 @@
1
+ import json
1
2
  from typing import Iterator
2
3
 
3
4
  from vellum.workflows.errors import WorkflowErrorCode
@@ -44,13 +45,19 @@ class PromptDeploymentNode(BasePromptDeploymentNode[StateType]):
44
45
  code=WorkflowErrorCode.INTERNAL_ERROR,
45
46
  )
46
47
 
47
- string_output = next((output for output in outputs if output.type == "STRING"), None)
48
- if not string_output or string_output.value is None:
49
- output_types = {output.type for output in outputs}
50
- is_plural = len(output_types) > 1
51
- raise NodeException(
52
- message=f"Expected to receive a non-null string output from Prompt. Only found outputs of type{'s' if is_plural else ''}: {', '.join(output_types)}", # noqa: E501
53
- code=WorkflowErrorCode.INTERNAL_ERROR,
54
- )
55
-
56
- yield BaseOutput(name="text", value=string_output.value)
48
+ string_outputs = []
49
+ for output in outputs:
50
+ if output.value is None:
51
+ continue
52
+
53
+ if output.type == "STRING":
54
+ string_outputs.append(output.value)
55
+ elif output.type == "JSON":
56
+ string_outputs.append(json.dumps(output.value, indent=4))
57
+ elif output.type == "FUNCTION_CALL":
58
+ string_outputs.append(output.value.model_dump_json(indent=4))
59
+ else:
60
+ string_outputs.append(output.value.message)
61
+
62
+ value = "\n".join(string_outputs)
63
+ yield BaseOutput(name="text", value=value)
@@ -221,6 +221,28 @@ class BaseWorkflow(Generic[InputsType, StateType], metaclass=_BaseWorkflowMeta):
221
221
  nodes.add(node)
222
222
  yield node
223
223
 
224
+ @classmethod
225
+ def get_unused_subgraphs(cls) -> List[Graph]:
226
+ """
227
+ Returns a list of subgraphs that are defined but not used in the graph
228
+ """
229
+ if not hasattr(cls, "unused_graphs"):
230
+ return []
231
+
232
+ graphs = []
233
+ for item in cls.unused_graphs:
234
+ if isinstance(item, Graph):
235
+ graphs.append(item)
236
+ elif isinstance(item, set):
237
+ for subitem in item:
238
+ if isinstance(subitem, Graph):
239
+ graphs.append(subitem)
240
+ elif issubclass(subitem, BaseNode):
241
+ graphs.append(Graph.from_node(subitem))
242
+ elif issubclass(item, BaseNode):
243
+ graphs.append(Graph.from_node(item))
244
+ return graphs
245
+
224
246
  @classmethod
225
247
  def get_unused_nodes(cls) -> Iterator[Type[BaseNode]]:
226
248
  """
@@ -230,30 +252,25 @@ class BaseWorkflow(Generic[InputsType, StateType], metaclass=_BaseWorkflowMeta):
230
252
  yield from ()
231
253
  else:
232
254
  nodes = set()
233
- for item in cls.unused_graphs:
234
- if isinstance(item, Graph):
235
- # Item is a graph
236
- for node in item.nodes:
237
- if node not in nodes:
238
- nodes.add(node)
239
- yield node
240
- elif isinstance(item, set):
241
- # Item is a set of graphs or nodes
242
- for subitem in item:
243
- if isinstance(subitem, Graph):
244
- for node in subitem.nodes:
245
- if node not in nodes:
246
- nodes.add(node)
247
- yield node
248
- elif issubclass(subitem, BaseNode):
249
- if subitem not in nodes:
250
- nodes.add(subitem)
251
- yield subitem
252
- elif issubclass(item, BaseNode):
253
- # Item is a node
254
- if item not in nodes:
255
- nodes.add(item)
256
- yield item
255
+ subgraphs = cls.get_unused_subgraphs()
256
+ for subgraph in subgraphs:
257
+ for node in subgraph.nodes:
258
+ if node not in nodes:
259
+ nodes.add(node)
260
+ yield node
261
+
262
+ @classmethod
263
+ def get_unused_edges(cls) -> Iterator[Edge]:
264
+ """
265
+ Returns an iterator over edges that are defined but not used in the graph.
266
+ """
267
+ edges = set()
268
+ subgraphs = cls.get_unused_subgraphs()
269
+ for subgraph in subgraphs:
270
+ for edge in subgraph.edges:
271
+ if edge not in edges:
272
+ edges.add(edge)
273
+ yield edge
257
274
 
258
275
  @classmethod
259
276
  def get_entrypoints(cls) -> Iterable[Type[BaseNode]]:
@@ -1,5 +1,6 @@
1
1
  import pytest
2
2
 
3
+ from vellum.workflows.edges.edge import Edge
3
4
  from vellum.workflows.inputs.base import BaseInputs
4
5
  from vellum.workflows.nodes.bases.base import BaseNode
5
6
  from vellum.workflows.nodes.core.inline_subworkflow_node.node import InlineSubworkflowNode
@@ -166,3 +167,49 @@ def test_workflow__node_in_both_graph_and_unused():
166
167
 
167
168
  # THEN it should raise an error
168
169
  assert "Node(s) NodeA cannot appear in both graph and unused_graphs" in str(exc_info.value)
170
+
171
+
172
+ def test_workflow__get_unused_edges():
173
+ """
174
+ Test that get_unused_edges correctly identifies edges that are defined but not used in the workflow graph.
175
+ """
176
+
177
+ class NodeA(BaseNode):
178
+ pass
179
+
180
+ class NodeB(BaseNode):
181
+ pass
182
+
183
+ class NodeC(BaseNode):
184
+ pass
185
+
186
+ class NodeD(BaseNode):
187
+ pass
188
+
189
+ class NodeE(BaseNode):
190
+ pass
191
+
192
+ class NodeF(BaseNode):
193
+ pass
194
+
195
+ class NodeG(BaseNode):
196
+ pass
197
+
198
+ class TestWorkflow(BaseWorkflow[BaseInputs, BaseState]):
199
+ graph = NodeA >> NodeB
200
+ unused_graphs = {NodeC >> {NodeD >> NodeE, NodeF} >> NodeG}
201
+
202
+ edge_c_to_d = Edge(from_port=NodeC.Ports.default, to_node=NodeD)
203
+ edge_c_to_f = Edge(from_port=NodeC.Ports.default, to_node=NodeF)
204
+ edge_d_to_e = Edge(from_port=NodeD.Ports.default, to_node=NodeE)
205
+ edge_e_to_g = Edge(from_port=NodeE.Ports.default, to_node=NodeG)
206
+ edge_f_to_g = Edge(from_port=NodeF.Ports.default, to_node=NodeG)
207
+
208
+ # Collect unused edges
209
+ unused_edges = set(TestWorkflow.get_unused_edges())
210
+
211
+ # Expected unused edges
212
+ expected_unused_edges = {edge_c_to_d, edge_c_to_f, edge_d_to_e, edge_e_to_g, edge_f_to_g}
213
+
214
+ # TEST that unused edges are correctly identified
215
+ assert unused_edges == expected_unused_edges, f"Expected {expected_unused_edges}, but got {unused_edges}"
@@ -0,0 +1,60 @@
1
+ from uuid import UUID, uuid4
2
+
3
+ from vellum.workflows import BaseWorkflow
4
+ from vellum.workflows.context import execution_context, get_execution_context
5
+ from vellum.workflows.events.types import NodeParentContext, WorkflowParentContext
6
+ from vellum.workflows.inputs import BaseInputs
7
+ from vellum.workflows.nodes import BaseNode
8
+ from vellum.workflows.references import VellumSecretReference
9
+ from vellum.workflows.state import BaseState
10
+
11
+
12
+ class MockInputs(BaseInputs):
13
+ foo: str
14
+
15
+
16
+ class MockNode(BaseNode):
17
+ node_foo = MockInputs.foo
18
+ node_secret = VellumSecretReference("secret")
19
+
20
+ class Outputs(BaseNode.Outputs):
21
+ example: str
22
+
23
+
24
+ class MockWorkflow(BaseWorkflow[MockInputs, BaseState]):
25
+ graph = MockNode
26
+
27
+
28
+ def test_context_trace_and_parent():
29
+ trace_id = uuid4()
30
+ parent_context = NodeParentContext(
31
+ node_definition=MockNode,
32
+ span_id=UUID("123e4567-e89b-12d3-a456-426614174000"),
33
+ parent=WorkflowParentContext(
34
+ workflow_definition=MockWorkflow,
35
+ span_id=UUID("123e4567-e89b-12d3-a456-426614174000"),
36
+ ),
37
+ )
38
+ second_parent_context = WorkflowParentContext(
39
+ workflow_definition=MockWorkflow, span_id=uuid4(), parent=parent_context
40
+ )
41
+ # When using execution context , if we set trace id within
42
+ with execution_context(parent_context=parent_context, trace_id=trace_id):
43
+ test = get_execution_context()
44
+ assert test.trace_id == trace_id
45
+ assert test.parent_context == parent_context
46
+ with execution_context(parent_context=second_parent_context):
47
+ test1 = get_execution_context()
48
+ assert test1.trace_id == trace_id
49
+ assert test1.parent_context == second_parent_context
50
+ # then we can assume trace id will not change
51
+ with execution_context(trace_id=uuid4()):
52
+ test3 = get_execution_context()
53
+ assert test3.trace_id == trace_id
54
+ with execution_context(parent_context=parent_context, trace_id=uuid4()):
55
+ test3 = get_execution_context()
56
+ assert test3.trace_id == trace_id
57
+ # and if we have a new context, the trace will differ
58
+ with execution_context(parent_context=parent_context, trace_id=uuid4()):
59
+ test = get_execution_context()
60
+ assert test.trace_id != trace_id
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-ai
3
- Version: 0.14.4
3
+ Version: 0.14.5
4
4
  Summary:
5
5
  License: MIT
6
6
  Requires-Python: >=3.9,<4.0
@@ -28,8 +28,9 @@ vellum_ee/workflows/display/nodes/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5J
28
28
  vellum_ee/workflows/display/nodes/tests/test_base_node_display.py,sha256=QqR3Ly0RNrXwOeLdW5nERDFt0gRPf76n1bPES6o5UN4,1093
29
29
  vellum_ee/workflows/display/nodes/types.py,sha256=St1BB6no528OyELGiyRabWao0GGw6mLhstQAvEACbGk,247
30
30
  vellum_ee/workflows/display/nodes/utils.py,sha256=sloya5TpXsnot1HURc9L51INwflRqUzHxRVnCS9Cd-4,973
31
- vellum_ee/workflows/display/nodes/vellum/__init__.py,sha256=VHx6wSs9wuuiFlZpSQSd3mhECz4SUy2wEBuTSv--_As,1578
31
+ vellum_ee/workflows/display/nodes/vellum/__init__.py,sha256=nUIgH2s0-7IbQRNrBhLPyRNe8YIrx3Yo9HeeW-aXXFk,1668
32
32
  vellum_ee/workflows/display/nodes/vellum/api_node.py,sha256=hoV-cUtS6H9kmRQXHd2py95GRWI_dAnnaPwvlNBkDOQ,8571
33
+ vellum_ee/workflows/display/nodes/vellum/base_adornment_node.py,sha256=UlfX8ifleaAaRCmGlj6XNubEMJnHOgCXSBRk6LAZw38,1995
33
34
  vellum_ee/workflows/display/nodes/vellum/code_execution_node.py,sha256=z00Z3L0d4PsUQo4S8FRDTtOFLtjdi17TJbatNVF4nM8,4288
34
35
  vellum_ee/workflows/display/nodes/vellum/conditional_node.py,sha256=ybLIa4uclqVIy3VAQvI1ivg2tnK5Ug_1R5a69DFqL7E,11104
35
36
  vellum_ee/workflows/display/nodes/vellum/error_node.py,sha256=I1Jkp2htRINJATtv1e-zs9BrReFX842djpiVgBPHDYg,2186
@@ -37,11 +38,11 @@ vellum_ee/workflows/display/nodes/vellum/final_output_node.py,sha256=p-PvlnxpBQ7
37
38
  vellum_ee/workflows/display/nodes/vellum/guardrail_node.py,sha256=aYZSJTxknU4LMiQdWk9LcK6CkhdozeDEMiRxfAyUNEc,2202
38
39
  vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py,sha256=aNZhjw5CwpUO8IcLJ2nhYrzn96RJ3FWeJXdfDizuPzw,8491
39
40
  vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py,sha256=MU9I8CB1X1TgL1aa1eT6DHWwNJ-2v79t74xl0oy-fBo,5510
40
- vellum_ee/workflows/display/nodes/vellum/map_node.py,sha256=VlO3UwkspCOdDQ-h3v8k16-7JZwWNSLpOLT4p-eirIs,3740
41
+ vellum_ee/workflows/display/nodes/vellum/map_node.py,sha256=8CPnn06HIBxBOiECevUffeVmQmCpec6WtPQnNl9gj9Y,3748
41
42
  vellum_ee/workflows/display/nodes/vellum/merge_node.py,sha256=HkNMgdQELiON42jdO-xDLmqrEKdGx1RVqrz2DXNTLS8,3239
42
43
  vellum_ee/workflows/display/nodes/vellum/note_node.py,sha256=TMb8txILu2uWjzoxaghjgjlzeBAgzn4vkP_8zSh2qoE,1151
43
44
  vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py,sha256=LFjLUrH6sJ4czPnExdRqFr0PB_yKBMLXLvK5GAzIAgc,3273
44
- vellum_ee/workflows/display/nodes/vellum/retry_node.py,sha256=i0XGbKecLiHtf7mBf2rbqldPgLcs1TitIphzcHRIvkA,341
45
+ vellum_ee/workflows/display/nodes/vellum/retry_node.py,sha256=hVqTtuHjlw_cYJ3ydNAvUHfGEoQi5YocVONZUo4i_Gs,1717
45
46
  vellum_ee/workflows/display/nodes/vellum/search_node.py,sha256=TxcAGZDl_hvJ7Y1hUi9YVEVrj9Ie0hKkASdpfRL4_cs,9227
46
47
  vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py,sha256=62baAElKoRKIoba0lLhnrXGWWx96B73VxKGxh7BaIxc,2612
47
48
  vellum_ee/workflows/display/nodes/vellum/templating_node.py,sha256=JVIMPR3WpveOCWZubHKZkE04mavnTdb_9QY_r3XliRg,3424
@@ -50,20 +51,20 @@ vellum_ee/workflows/display/nodes/vellum/tests/test_error_node.py,sha256=ulrpoYU
50
51
  vellum_ee/workflows/display/nodes/vellum/tests/test_prompt_node.py,sha256=bg9INsXiWfyK047u8TD1oEOFYrqDq8GC7Hvgz69n7BE,1988
51
52
  vellum_ee/workflows/display/nodes/vellum/tests/test_try_node.py,sha256=mtzB8LJlFCHVFM4H5AanLp29gQfaVmnN4A4iaRGJHoI,2427
52
53
  vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py,sha256=3uT7Gbc0f_mQ3u8uZuCWd0mJ4GtWbz2gbUMySYaVlNE,3774
53
- vellum_ee/workflows/display/nodes/vellum/try_node.py,sha256=EoU1J7HfcszAZr7ROy_xsNhaDBRiI95-wTK-OzgBwvg,5861
54
+ vellum_ee/workflows/display/nodes/vellum/try_node.py,sha256=HBfGz4yt9GlmMW9JxzaCacPnHBDNIeXE8Jhqr9DqLLw,6191
54
55
  vellum_ee/workflows/display/nodes/vellum/utils.py,sha256=OEGHjQSbuUgJexXI1aubYW33z2F_YdkhQ8REahfz864,4320
55
56
  vellum_ee/workflows/display/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
57
  vellum_ee/workflows/display/tests/test_vellum_workflow_display.py,sha256=1EEvkKQRfOKlnpLxE9-hKSsVLLaelM39LY7007LM5dg,4983
57
58
  vellum_ee/workflows/display/tests/workflow_serialization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
58
59
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
60
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/conftest.py,sha256=A1-tIpC5KIKG9JA_rkd1nLS8zUG3Kb4QiVdvb3boFxE,2509
60
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py,sha256=KYdohS5pRgHM0DcUaK0tHa40f0gSvDKi2K5On0zNEU8,8305
61
+ vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_adornments_serialization.py,sha256=7Fc-TtPw7hz_pvQ-TWz3G8Vy9h2AztukpyDK0p7REGU,9071
61
62
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attributes_serialization.py,sha256=1cszL6N6FNGVm61MOa7AEiHnF0QjZWqDQuPOp4yiG94,18277
62
63
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_outputs_serialization.py,sha256=-12ZkZb3f5gyoNASV2yeQtMo5HmNsVEo8nXwL6IC-I8,6261
63
64
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_ports_serialization.py,sha256=6th6kCwzql6lddjkTQx4Jbvvs4ChqtJwctW-B4QuBhI,37352
64
65
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_trigger_serialization.py,sha256=EbVgg_3_ipTt3MOop4RARX0fmNjwqZtkhIXzx9nGw7Y,4487
65
66
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_api_node_serialization.py,sha256=IRazH2QR6F8RGqNemEnHueyj5DtEa6rFTYhT16S4jI8,15917
66
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py,sha256=PiHqgj-vLhlaOHoQFVVEW2YydYWLI0mX6nq_sbzZiy4,29233
67
+ vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py,sha256=V__y7uu-dy6TJjPeu4UDvaoO2yYwBRbPiW9uJdzWRx4,29828
67
68
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py,sha256=R8DW1DUb0DOSLtnF2E1HaCTmtpG-ski0LfcM2WeLVNo,47672
68
69
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_default_state_serialization.py,sha256=z9FSufJeV-003R87wi_Lx4mZewdeeOeTPCGNc9vg8vY,8819
69
70
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py,sha256=wgedEa2IVP2ssH_FLghoEmLgpJR41AY-iNIw1SESeqA,6106
@@ -78,7 +79,7 @@ vellum_ee/workflows/display/tests/workflow_serialization/test_basic_search_node_
78
79
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_subworkflow_deployment_serialization.py,sha256=BzFNl9ECeGh0krm-CUjbBQQq0g7krANsp0Sh-j5dAkc,11322
79
80
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_node_serialization.py,sha256=xXtW915v9yxxKlyu5ObzKHyJYMvobvev3ermX61SGx4,7818
80
81
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_node_serialization.py,sha256=NdhE3lm7RMQ8DqkraPSq24IbOxNla9unbs4tsMWRzm4,3781
81
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py,sha256=gHKU8Vg960ooV3uXqM2LMnVS-mGbv3aagGozQuTVTjI,2455
82
+ vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py,sha256=eD5686C9nWC5s6t08vbAnm9qf9t53gYQM-E1FwAa75c,3035
82
83
  vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py,sha256=huKAOeMJ2MKmp6XtbvMJTUadqynoV40Ypoz9jsBEBEQ,7431
83
84
  vellum_ee/workflows/display/types.py,sha256=xDC1zy4rWKNqDtSr-h6MQfWnJ6scZ_Sadxp4t8Q3PY4,2897
84
85
  vellum_ee/workflows/display/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -119,7 +120,7 @@ vellum/client/README.md,sha256=JkCJjmMZl4jrPj46pkmL9dpK4gSzQQmP5I7z4aME4LY,4749
119
120
  vellum/client/__init__.py,sha256=j6zi0NZ4BMC6JrwckvzMWuG5x8KoOvO4KqsLhvVCa68,117624
120
121
  vellum/client/core/__init__.py,sha256=SQ85PF84B9MuKnBwHNHWemSGuy-g_515gFYNFhvEE0I,1438
121
122
  vellum/client/core/api_error.py,sha256=RE8LELok2QCjABadECTvtDp7qejA1VmINCh6TbqPwSE,426
122
- vellum/client/core/client_wrapper.py,sha256=pxEwlQ6QCqqK61MR38NnSRA28GMZgrYhfxlyjX2Sy-Y,1868
123
+ vellum/client/core/client_wrapper.py,sha256=XW9zEBd9XlCTI0wEOgZ6LI4dCsGwvpW4NO7KogWDgao,1868
123
124
  vellum/client/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
124
125
  vellum/client/core/file.py,sha256=X9IbmkZmB2bB_DpmZAO3crWdXagOakAyn6UCOCImCPg,2322
125
126
  vellum/client/core/http_client.py,sha256=R0pQpCppnEtxccGvXl4uJ76s7ro_65Fo_erlNNLp_AI,19228
@@ -1271,7 +1272,7 @@ vellum/version.py,sha256=jq-1PlAYxN9AXuaZqbYk9ak27SgE2lw9Ia5gx1b1gVI,76
1271
1272
  vellum/workflows/README.md,sha256=MLNm-ihc0ao6I8gwwOhXQQBf0jOf-EsA9C519ALYI1o,3610
1272
1273
  vellum/workflows/__init__.py,sha256=CssPsbNvN6rDhoLuqpEv7MMKGa51vE6dvAh6U31Pcio,71
1273
1274
  vellum/workflows/constants.py,sha256=2yg4_uo5gpqViy3ZLSwfC8qTybleYCtOnhA4Rj6bacM,1310
1274
- vellum/workflows/context.py,sha256=R8qdsFbD_0p7B6PWnyvSrZ_aOgMtGw-_uk0P0UAmwLA,1230
1275
+ vellum/workflows/context.py,sha256=DwSf8lO9NHABiqOoD3exgrjUoRuNsKtutaL5TgRbD-A,1441
1275
1276
  vellum/workflows/descriptors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1276
1277
  vellum/workflows/descriptors/base.py,sha256=gSib3vJpcI_UC8y8jhdp-hOK3_TGTF-SuwdhxF6x5iQ,14332
1277
1278
  vellum/workflows/descriptors/exceptions.py,sha256=gUy4UD9JFUKSeQnQpeuDSLiRqWjWiIsxLahB7p_q3JY,54
@@ -1380,8 +1381,8 @@ vellum/workflows/nodes/displayable/code_execution_node/node.py,sha256=_yrQn_uLwy
1380
1381
  vellum/workflows/nodes/displayable/code_execution_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1381
1382
  vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1382
1383
  vellum/workflows/nodes/displayable/code_execution_node/tests/fixtures/main.py,sha256=5QsbmkzSlSbcbWTG_JmIqcP-JNJzOPTKxGzdHos19W4,79
1383
- vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py,sha256=5WHsW_uEKDn1ukM9_O2FsGCYELfONz-2h_kYnNRNYHE,18333
1384
- vellum/workflows/nodes/displayable/code_execution_node/utils.py,sha256=rdWD1wtxPKHbdHRHEdTeRMSJi9sgAm2t0FqeAWuQHlQ,4534
1384
+ vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py,sha256=4ADDKsObtUs0PhcWyAjWyQcAF7PGUYE0CxjYp8d-1NM,20637
1385
+ vellum/workflows/nodes/displayable/code_execution_node/utils.py,sha256=BQraIN4I3DCzXLEuBlRYCyp7ote7hQmnnKHu4jFHCCA,5174
1385
1386
  vellum/workflows/nodes/displayable/conditional_node/__init__.py,sha256=AS_EIqFdU1F9t8aLmbZU-rLh9ry6LCJ0uj0D8F0L5Uw,72
1386
1387
  vellum/workflows/nodes/displayable/conditional_node/node.py,sha256=Qjfl33gZ3JEgxBA1EgzSUebboGvsARthIxxcQyvx5Gg,1152
1387
1388
  vellum/workflows/nodes/displayable/final_output_node/__init__.py,sha256=G7VXM4OWpubvSJtVkGmMNeqgb9GkM7qZT838eL18XU4,72
@@ -1397,7 +1398,7 @@ vellum/workflows/nodes/displayable/merge_node/node.py,sha256=nZtGGVAvY4fvGg8vwV6
1397
1398
  vellum/workflows/nodes/displayable/note_node/__init__.py,sha256=KWA3P4fyYJ-fOTky8qNGlcOotQ-HeHJ9AjZt6mRQmCE,58
1398
1399
  vellum/workflows/nodes/displayable/note_node/node.py,sha256=sIN1VBQ7zeT3GhN0kupXbFfdpvgedWV79k4woJNp5IQ,394
1399
1400
  vellum/workflows/nodes/displayable/prompt_deployment_node/__init__.py,sha256=krX1Hds-TSVYZsx0wJFX4wsAKkEFYOX1ifwRGiIM-EA,82
1400
- vellum/workflows/nodes/displayable/prompt_deployment_node/node.py,sha256=ruOgvpg_9KV_HkovPQeu6TKpur9DT_J4CYQo50tULCY,2680
1401
+ vellum/workflows/nodes/displayable/prompt_deployment_node/node.py,sha256=pb-KbrnfTRL7mmNtVAMmiCiys8raXkl5Od7sIu682xU,2707
1401
1402
  vellum/workflows/nodes/displayable/prompt_deployment_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1402
1403
  vellum/workflows/nodes/displayable/prompt_deployment_node/tests/test_node.py,sha256=ymEwMwrwRuQGyvkTnqeRZvfK7dhnf-kmRJTuwlycNjI,3939
1403
1404
  vellum/workflows/nodes/displayable/search_node/__init__.py,sha256=hpBpvbrDYf43DElRZFLzieSn8weXiwNiiNOJurERQbs,62
@@ -1470,12 +1471,13 @@ vellum/workflows/utils/uuids.py,sha256=DFzPv9RCvsKhvdTEIQyfSek2A31D6S_QcmeLPbgrg
1470
1471
  vellum/workflows/utils/vellum_variables.py,sha256=fC2aSLvlS31D15dOWu43LBRR0QsgUKNXBiCUvvaLXSs,3231
1471
1472
  vellum/workflows/vellum_client.py,sha256=ODrq_TSl-drX2aezXegf7pizpWDVJuTXH-j6528t75s,683
1472
1473
  vellum/workflows/workflows/__init__.py,sha256=KY45TqvavCCvXIkyCFMEc0dc6jTMOUci93U2DUrlZYc,66
1473
- vellum/workflows/workflows/base.py,sha256=eHa5iojvXFl3_vSb3jCoYt24dZdjppoohAqv99z8xRY,22096
1474
+ vellum/workflows/workflows/base.py,sha256=MNwWXt0xosrGAjbkQ_lhmBPlHzsJVX1Cr2qy7guFRK8,22446
1474
1475
  vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnadGsrSZGa7t7LpJA,2008
1475
1476
  vellum/workflows/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1476
- vellum/workflows/workflows/tests/test_base_workflow.py,sha256=dTwC2VIqXNJ5yJ3MPwGNB9QpVzVzycT2kiBIljxhCwM,4836
1477
- vellum_ai-0.14.4.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1478
- vellum_ai-0.14.4.dist-info/METADATA,sha256=cV7wQcYIgzVrVDPeufin7dSQ5sRFUGtzbAhDw9kYKKc,5407
1479
- vellum_ai-0.14.4.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1480
- vellum_ai-0.14.4.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1481
- vellum_ai-0.14.4.dist-info/RECORD,,
1477
+ vellum/workflows/workflows/tests/test_base_workflow.py,sha256=DTfXqHUF0SJELTVSA9PCOWHVoAylqpDNFZTkQM44CdU,6215
1478
+ vellum/workflows/workflows/tests/test_context.py,sha256=VJBUcyWVtMa_lE5KxdhgMu0WYNYnUQUDvTF7qm89hJ0,2333
1479
+ vellum_ai-0.14.5.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1480
+ vellum_ai-0.14.5.dist-info/METADATA,sha256=_53R_GRMep--gP15bdsMgW6SYTC9AD_W9yvPkQp94w0,5407
1481
+ vellum_ai-0.14.5.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1482
+ vellum_ai-0.14.5.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1483
+ vellum_ai-0.14.5.dist-info/RECORD,,
@@ -1,4 +1,5 @@
1
1
  from .api_node import BaseAPINodeDisplay
2
+ from .base_adornment_node import BaseAdornmentNodeDisplay
2
3
  from .code_execution_node import BaseCodeExecutionNodeDisplay
3
4
  from .conditional_node import BaseConditionalNodeDisplay
4
5
  from .error_node import BaseErrorNodeDisplay
@@ -18,6 +19,7 @@ from .try_node import BaseTryNodeDisplay
18
19
 
19
20
  # All node display classes must be imported here to be registered in BaseNodeDisplay's node display registry
20
21
  __all__ = [
22
+ "BaseAdornmentNodeDisplay",
21
23
  "BaseAPINodeDisplay",
22
24
  "BaseCodeExecutionNodeDisplay",
23
25
  "BaseConditionalNodeDisplay",
@@ -0,0 +1,39 @@
1
+ from uuid import UUID
2
+ from typing import Any, Callable, Generic, Optional, TypeVar, cast
3
+
4
+ from vellum.workflows.nodes.bases.base_adornment_node import BaseAdornmentNode
5
+ from vellum.workflows.nodes.utils import get_wrapped_node
6
+ from vellum.workflows.types.core import JsonArray, JsonObject
7
+ from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
8
+ from vellum_ee.workflows.display.nodes.base_node_vellum_display import BaseNodeVellumDisplay
9
+ from vellum_ee.workflows.display.nodes.get_node_display_class import get_node_display_class
10
+ from vellum_ee.workflows.display.types import WorkflowDisplayContext
11
+
12
+ _BaseAdornmentNodeType = TypeVar("_BaseAdornmentNodeType", bound=BaseAdornmentNode)
13
+
14
+
15
+ class BaseAdornmentNodeDisplay(BaseNodeVellumDisplay[_BaseAdornmentNodeType], Generic[_BaseAdornmentNodeType]):
16
+ def serialize(
17
+ self,
18
+ display_context: "WorkflowDisplayContext",
19
+ **kwargs: Any,
20
+ ) -> dict:
21
+ node = self._node
22
+ adornment = cast(Optional[JsonObject], kwargs.get("adornment"))
23
+ get_additional_kwargs = cast(Optional[Callable[[UUID], dict]], kwargs.get("get_additional_kwargs"))
24
+
25
+ wrapped_node = get_wrapped_node(node)
26
+ if not wrapped_node:
27
+ raise NotImplementedError(
28
+ "Unable to serialize standalone adornment nodes. Please use adornment nodes as a decorator."
29
+ )
30
+
31
+ wrapped_node_display_class = get_node_display_class(BaseNodeDisplay, wrapped_node)
32
+ wrapped_node_display = wrapped_node_display_class()
33
+ additional_kwargs = get_additional_kwargs(wrapped_node_display.node_id) if get_additional_kwargs else {}
34
+ serialized_wrapped_node = wrapped_node_display.serialize(display_context, **kwargs, **additional_kwargs)
35
+
36
+ adornments = cast(JsonArray, serialized_wrapped_node.get("adornments")) or []
37
+ serialized_wrapped_node["adornments"] = adornments + [adornment] if adornment else adornments
38
+
39
+ return serialized_wrapped_node
@@ -3,8 +3,8 @@ from typing import Dict, Generic, List, Optional, TypeVar, cast
3
3
 
4
4
  from vellum.workflows.nodes import MapNode
5
5
  from vellum.workflows.types.core import JsonObject
6
- from vellum_ee.workflows.display.nodes.base_node_vellum_display import BaseNodeVellumDisplay
7
6
  from vellum_ee.workflows.display.nodes.utils import raise_if_descriptor
7
+ from vellum_ee.workflows.display.nodes.vellum.base_adornment_node import BaseAdornmentNodeDisplay
8
8
  from vellum_ee.workflows.display.nodes.vellum.utils import create_node_input
9
9
  from vellum_ee.workflows.display.types import WorkflowDisplayContext
10
10
  from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
@@ -12,7 +12,7 @@ from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class imp
12
12
  _MapNodeType = TypeVar("_MapNodeType", bound=MapNode)
13
13
 
14
14
 
15
- class BaseMapNodeDisplay(BaseNodeVellumDisplay[_MapNodeType], Generic[_MapNodeType]):
15
+ class BaseMapNodeDisplay(BaseAdornmentNodeDisplay[_MapNodeType], Generic[_MapNodeType]):
16
16
  def serialize(
17
17
  self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **kwargs
18
18
  ) -> JsonObject:
@@ -1,10 +1,42 @@
1
- from typing import Generic, TypeVar
1
+ import inspect
2
+ from typing import Any, Generic, TypeVar, cast
2
3
 
4
+ from vellum.workflows.descriptors.base import BaseDescriptor
3
5
  from vellum.workflows.nodes.core.retry_node.node import RetryNode
4
- from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
6
+ from vellum.workflows.types.core import JsonArray, JsonObject
7
+ from vellum.workflows.utils.uuids import uuid4_from_hash
8
+ from vellum.workflows.workflows.base import BaseWorkflow
9
+ from vellum_ee.workflows.display.nodes.vellum.base_adornment_node import BaseAdornmentNodeDisplay
10
+ from vellum_ee.workflows.display.types import WorkflowDisplayContext
5
11
 
6
12
  _RetryNodeType = TypeVar("_RetryNodeType", bound=RetryNode)
7
13
 
8
14
 
9
- class BaseRetryNodeDisplay(BaseNodeDisplay[_RetryNodeType], Generic[_RetryNodeType]):
10
- pass
15
+ class BaseRetryNodeDisplay(BaseAdornmentNodeDisplay[_RetryNodeType], Generic[_RetryNodeType]):
16
+ def serialize(self, display_context: WorkflowDisplayContext, **kwargs: Any) -> JsonObject:
17
+ node = self._node
18
+ node_id = self.node_id
19
+
20
+ attributes: JsonArray = []
21
+ for attribute in node:
22
+ if inspect.isclass(attribute.instance) and issubclass(attribute.instance, BaseWorkflow):
23
+ # We don't need to serialize attributes that are workflows
24
+ continue
25
+
26
+ id = str(uuid4_from_hash(f"{node_id}|{attribute.name}"))
27
+ attributes.append(
28
+ {
29
+ "id": id,
30
+ "name": attribute.name,
31
+ "value": self.serialize_value(display_context, cast(BaseDescriptor, attribute.instance)),
32
+ }
33
+ )
34
+
35
+ adornment: JsonObject = {
36
+ "id": str(node_id),
37
+ "label": node.__qualname__,
38
+ "base": self.get_base().dict(),
39
+ "attributes": attributes,
40
+ }
41
+
42
+ return super().serialize(display_context, adornment=adornment)
@@ -1,55 +1,69 @@
1
+ import inspect
1
2
  from uuid import UUID
2
3
  from typing import Any, Callable, ClassVar, Generic, Optional, Tuple, Type, TypeVar, cast
3
4
 
5
+ from vellum.workflows.descriptors.base import BaseDescriptor
4
6
  from vellum.workflows.nodes.bases.base import BaseNode
5
7
  from vellum.workflows.nodes.core.try_node.node import TryNode
6
- from vellum.workflows.nodes.utils import ADORNMENT_MODULE_NAME, get_wrapped_node
8
+ from vellum.workflows.nodes.utils import ADORNMENT_MODULE_NAME
7
9
  from vellum.workflows.references.output import OutputReference
8
- from vellum.workflows.types.core import JsonObject
10
+ from vellum.workflows.types.core import JsonArray, JsonObject
9
11
  from vellum.workflows.types.utils import get_original_base
10
12
  from vellum.workflows.utils.uuids import uuid4_from_hash
13
+ from vellum.workflows.workflows.base import BaseWorkflow
11
14
  from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
12
- from vellum_ee.workflows.display.nodes.base_node_vellum_display import BaseNodeVellumDisplay
13
15
  from vellum_ee.workflows.display.nodes.get_node_display_class import get_node_display_class
14
16
  from vellum_ee.workflows.display.nodes.types import NodeOutputDisplay
15
- from vellum_ee.workflows.display.nodes.utils import raise_if_descriptor
17
+ from vellum_ee.workflows.display.nodes.vellum.base_adornment_node import BaseAdornmentNodeDisplay
16
18
  from vellum_ee.workflows.display.types import WorkflowDisplayContext
17
19
 
18
20
  _TryNodeType = TypeVar("_TryNodeType", bound=TryNode)
19
21
 
20
22
 
21
- class BaseTryNodeDisplay(BaseNodeVellumDisplay[_TryNodeType], Generic[_TryNodeType]):
23
+ class BaseTryNodeDisplay(BaseAdornmentNodeDisplay[_TryNodeType], Generic[_TryNodeType]):
22
24
  error_output_id: ClassVar[Optional[UUID]] = None
23
25
 
24
- def serialize(self, display_context: WorkflowDisplayContext, **kwargs: Any) -> JsonObject:
26
+ def serialize(self, display_context: "WorkflowDisplayContext", **kwargs: Any) -> JsonObject:
25
27
  node = self._node
28
+ node_id = self.node_id
29
+
30
+ # We let the inner node serialize first and then append to it
31
+ attributes: JsonArray = []
32
+ for attribute in node:
33
+ if inspect.isclass(attribute.instance) and issubclass(attribute.instance, BaseWorkflow):
34
+ # We don't need to serialize attributes that are workflows
35
+ continue
36
+
37
+ id = str(uuid4_from_hash(f"{node_id}|{attribute.name}"))
38
+ attributes.append(
39
+ {
40
+ "id": id,
41
+ "name": attribute.name,
42
+ "value": self.serialize_value(display_context, cast(BaseDescriptor, attribute.instance)),
43
+ }
44
+ )
26
45
 
27
- inner_node = get_wrapped_node(node)
28
- if not inner_node:
29
- subworkflow = raise_if_descriptor(node.subworkflow)
30
- if not isinstance(subworkflow.graph, type) or not issubclass(subworkflow.graph, BaseNode):
31
- raise NotImplementedError(
32
- "Unable to serialize Try Nodes that wrap subworkflows containing more than one Node."
33
- )
34
-
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)
42
-
43
- # We need the node display class of the underlying node because
44
- # it contains the logic for serializing the node and potential display overrides
45
- node_display_class = get_node_display_class(BaseNodeDisplay, inner_node)
46
- node_display = node_display_class()
47
-
48
- serialized_node = node_display.serialize(
46
+ adornment: JsonObject = {
47
+ "id": str(node_id),
48
+ "label": node.__qualname__,
49
+ "base": self.get_base().dict(),
50
+ "attributes": attributes,
51
+ }
52
+
53
+ # We need the inner node's ID to generate the error output ID
54
+ # Long term we want to hoist error_output_id append from inner node displays to this display,
55
+ # But that's a lot of work and this allows us to punt a little longer
56
+ serialized_node = super().serialize(
49
57
  display_context,
50
- error_output_id=self.error_output_id or uuid4_from_hash(f"{node_display.node_id}|error_output_id"),
58
+ adornment=adornment,
59
+ get_additional_kwargs=lambda node_id: {
60
+ "error_output_id": self.error_output_id or uuid4_from_hash(f"{node_id}|error_output_id")
61
+ },
51
62
  )
52
63
 
64
+ if serialized_node["type"] == "GENERIC":
65
+ return serialized_node
66
+
53
67
  serialized_node_definition = serialized_node.get("definition")
54
68
  if isinstance(serialized_node_definition, dict):
55
69
  serialized_node_definition_module = serialized_node_definition.get("module")
@@ -1,3 +1,4 @@
1
+ import pytest
1
2
  from uuid import uuid4
2
3
 
3
4
  from deepdiff import DeepDiff
@@ -11,6 +12,7 @@ from vellum.workflows.workflows.base import BaseWorkflow
11
12
  from vellum_ee.workflows.display.base import WorkflowInputsDisplay
12
13
  from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
13
14
  from vellum_ee.workflows.display.nodes.base_node_vellum_display import BaseNodeVellumDisplay
15
+ from vellum_ee.workflows.display.nodes.vellum.retry_node import BaseRetryNodeDisplay
14
16
  from vellum_ee.workflows.display.nodes.vellum.try_node import BaseTryNodeDisplay
15
17
  from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
16
18
  from vellum_ee.workflows.display.workflows.vellum_workflow_display import VellumWorkflowDisplay
@@ -32,7 +34,7 @@ class InnerRetryGenericNodeDisplay(BaseNodeDisplay[InnerRetryGenericNode.__wrapp
32
34
  pass
33
35
 
34
36
 
35
- class OuterRetryNodeDisplay(BaseNodeDisplay[InnerRetryGenericNode]):
37
+ class OuterRetryNodeDisplay(BaseRetryNodeDisplay[InnerRetryGenericNode]): # type: ignore
36
38
  pass
37
39
 
38
40
 
@@ -213,3 +215,25 @@ def test_serialize_node__try(serialize_node):
213
215
  },
214
216
  serialized_node,
215
217
  )
218
+
219
+
220
+ @pytest.mark.skip(reason="Not implemented")
221
+ def test_serialize_node__stacked():
222
+ @TryNode.wrap()
223
+ @RetryNode.wrap(max_attempts=5)
224
+ class InnerStackedGenericNode(BaseNode):
225
+ pass
226
+
227
+ # AND a workflow that uses the adornment node
228
+ class StackedWorkflow(BaseWorkflow):
229
+ graph = InnerStackedGenericNode
230
+
231
+ # WHEN we serialize the workflow
232
+ workflow_display = get_workflow_display(
233
+ base_display_class=VellumWorkflowDisplay,
234
+ workflow_class=StackedWorkflow,
235
+ )
236
+ exec_config = workflow_display.serialize()
237
+
238
+ # THEN the workflow display is created successfully
239
+ assert exec_config is not None
@@ -596,6 +596,20 @@ def test_serialize_workflow__try_wrapped():
596
596
  ],
597
597
  "name": "TryNode",
598
598
  },
599
+ "adornments": [
600
+ {
601
+ "id": "3344083c-a32c-4a32-920b-0fb5093448fa",
602
+ "label": "TryNode",
603
+ "base": {"name": "TryNode", "module": ["vellum", "workflows", "nodes", "core", "try_node", "node"]},
604
+ "attributes": [
605
+ {
606
+ "id": "ab2fbab0-e2a0-419b-b1ef-ce11ecf11e90",
607
+ "name": "on_error_code",
608
+ "value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
609
+ }
610
+ ],
611
+ }
612
+ ],
599
613
  }
600
614
 
601
615
  final_output_nodes = workflow_raw_data["nodes"][2:]
@@ -1,9 +1,11 @@
1
+ import pytest
2
+
1
3
  from deepdiff import DeepDiff
2
4
 
3
5
  from vellum_ee.workflows.display.workflows import VellumWorkflowDisplay
4
6
  from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
5
7
 
6
- from tests.workflows.basic_try_node.workflow import SimpleTryExample
8
+ from tests.workflows.basic_try_node.workflow import SimpleTryExample, StandaloneTryExample
7
9
 
8
10
 
9
11
  def test_serialize_workflow():
@@ -69,3 +71,19 @@ def test_serialize_workflow():
69
71
 
70
72
  try_node = workflow_raw_data["nodes"][1]
71
73
  assert try_node["id"] == "1381c078-efa2-4255-89a1-7b4cb742c7fc"
74
+
75
+
76
+ def test_serialize_workflow__standalone():
77
+ # GIVEN a Workflow with a standalone TryNode
78
+ # WHEN we serialize it
79
+ with pytest.raises(NotImplementedError) as exc:
80
+ workflow_display = get_workflow_display(
81
+ base_display_class=VellumWorkflowDisplay, workflow_class=StandaloneTryExample
82
+ )
83
+ workflow_display.serialize()
84
+
85
+ # THEN we should get an error
86
+ assert (
87
+ exc.value.args[0]
88
+ == "Unable to serialize standalone adornment nodes. Please use adornment nodes as a decorator."
89
+ )