vellum-ai 1.3.4__py3-none-any.whl → 1.3.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 (22) hide show
  1. vellum/client/core/client_wrapper.py +2 -2
  2. vellum/workflows/nodes/bases/base.py +2 -5
  3. vellum/workflows/nodes/core/map_node/node.py +8 -1
  4. vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +2 -2
  5. vellum/workflows/nodes/displayable/guardrail_node/node.py +8 -3
  6. vellum/workflows/nodes/displayable/tool_calling_node/node.py +4 -0
  7. vellum/workflows/nodes/displayable/tool_calling_node/utils.py +2 -0
  8. vellum/workflows/outputs/base.py +11 -11
  9. vellum/workflows/references/output.py +3 -5
  10. vellum/workflows/workflows/base.py +9 -1
  11. {vellum_ai-1.3.4.dist-info → vellum_ai-1.3.5.dist-info}/METADATA +1 -1
  12. {vellum_ai-1.3.4.dist-info → vellum_ai-1.3.5.dist-info}/RECORD +22 -20
  13. vellum_ee/workflows/display/tests/test_base_workflow_display.py +47 -0
  14. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_parent_input.py +85 -0
  15. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py +1 -1
  16. vellum_ee/workflows/display/tests/workflow_serialization/test_final_output_node_map_reference_serialization.py +88 -0
  17. vellum_ee/workflows/display/utils/expressions.py +12 -0
  18. vellum_ee/workflows/display/utils/vellum.py +3 -1
  19. vellum_ee/workflows/display/workflows/base_workflow_display.py +9 -5
  20. {vellum_ai-1.3.4.dist-info → vellum_ai-1.3.5.dist-info}/LICENSE +0 -0
  21. {vellum_ai-1.3.4.dist-info → vellum_ai-1.3.5.dist-info}/WHEEL +0 -0
  22. {vellum_ai-1.3.4.dist-info → vellum_ai-1.3.5.dist-info}/entry_points.txt +0 -0
@@ -27,10 +27,10 @@ class BaseClientWrapper:
27
27
 
28
28
  def get_headers(self) -> typing.Dict[str, str]:
29
29
  headers: typing.Dict[str, str] = {
30
- "User-Agent": "vellum-ai/1.3.4",
30
+ "User-Agent": "vellum-ai/1.3.5",
31
31
  "X-Fern-Language": "Python",
32
32
  "X-Fern-SDK-Name": "vellum-ai",
33
- "X-Fern-SDK-Version": "1.3.4",
33
+ "X-Fern-SDK-Version": "1.3.5",
34
34
  **(self.get_custom_headers() or {}),
35
35
  }
36
36
  if self._api_version is not None:
@@ -120,7 +120,7 @@ class BaseNodeMeta(ABCMeta):
120
120
  cls = super().__new__(mcs, name, bases, dct)
121
121
  node_class = cast(Type["BaseNode"], cls)
122
122
 
123
- node_class.Outputs._node_class = node_class
123
+ node_class.Outputs.__parent_class__ = node_class
124
124
 
125
125
  # Add cls to relevant nested classes, since python should've been doing this by default
126
126
  for port in node_class.Ports:
@@ -270,11 +270,8 @@ class BaseNode(Generic[StateType], ABC, metaclass=BaseNodeMeta):
270
270
  class ExternalInputs(BaseInputs):
271
271
  __descriptor_class__ = ExternalInputReference
272
272
 
273
- # TODO: Consider using metaclasses to prevent the need for users to specify that the
274
- # "Outputs" class inherits from "BaseOutputs" and do so automatically.
275
- # https://app.shortcut.com/vellum/story/4008/auto-inherit-basenodeoutputs-in-outputs-classes
276
273
  class Outputs(BaseOutputs):
277
- _node_class: Type["BaseNode"] = field(init=False)
274
+ __parent_class__: Type["BaseNode"] = field(init=False)
278
275
 
279
276
  class Ports(NodePorts):
280
277
  default = Port(default=True)
@@ -27,6 +27,7 @@ from vellum.workflows.nodes.bases.base_adornment_node import BaseAdornmentNode
27
27
  from vellum.workflows.nodes.utils import create_adornment
28
28
  from vellum.workflows.outputs import BaseOutputs
29
29
  from vellum.workflows.outputs.base import BaseOutput
30
+ from vellum.workflows.references.node import NodeReference
30
31
  from vellum.workflows.references.output import OutputReference
31
32
  from vellum.workflows.state.context import WorkflowContext
32
33
  from vellum.workflows.types.generics import StateType
@@ -217,5 +218,11 @@ class MapNode(BaseAdornmentNode[StateType], Generic[StateType, MapNodeItemType])
217
218
  # value: List[str]
218
219
  outputs_class.__annotations__ = {**previous_annotations, reference.name: annotation}
219
220
 
220
- output_id = uuid4_from_hash(f"{cls.__id__}|{reference.name}")
221
+ subworkflow_class = cls.subworkflow.instance if isinstance(cls.subworkflow, NodeReference) else None
222
+ if subworkflow_class:
223
+ output_id = subworkflow_class.__output_ids__.get(reference.name) or uuid4_from_hash(
224
+ f"{cls.__id__}|{reference.name}"
225
+ )
226
+ else:
227
+ output_id = uuid4_from_hash(f"{cls.__id__}|{reference.name}")
221
228
  cls.__output_ids__[reference.name] = output_id
@@ -100,7 +100,7 @@ class BaseInlinePromptNode(BasePromptNode[StateType], Generic[StateType]):
100
100
  execution_context = get_execution_context()
101
101
  request_options = self.request_options or RequestOptions()
102
102
 
103
- processed_parameters = self._process_parameters(self.parameters)
103
+ processed_parameters = self.process_parameters(self.parameters)
104
104
 
105
105
  request_options["additional_body_parameters"] = {
106
106
  "execution_context": execution_context.model_dump(mode="json"),
@@ -300,7 +300,7 @@ class BaseInlinePromptNode(BasePromptNode[StateType], Generic[StateType]):
300
300
 
301
301
  return input_variables, input_values
302
302
 
303
- def _process_parameters(self, parameters: PromptParameters) -> PromptParameters:
303
+ def process_parameters(self, parameters: PromptParameters) -> PromptParameters:
304
304
  """
305
305
  Process parameters to recursively convert any Pydantic models to JSON schema dictionaries.
306
306
  """
@@ -8,7 +8,6 @@ from vellum.workflows.constants import LATEST_RELEASE_TAG
8
8
  from vellum.workflows.errors.types import WorkflowErrorCode
9
9
  from vellum.workflows.exceptions import NodeException
10
10
  from vellum.workflows.nodes.bases import BaseNode
11
- from vellum.workflows.outputs.base import BaseOutputs
12
11
  from vellum.workflows.types.core import EntityInputsInterface, MergeBehavior
13
12
  from vellum.workflows.types.generics import StateType
14
13
 
@@ -33,7 +32,7 @@ class GuardrailNode(BaseNode[StateType], Generic[StateType]):
33
32
  class Trigger(BaseNode.Trigger):
34
33
  merge_behavior = MergeBehavior.AWAIT_ANY
35
34
 
36
- class Outputs(BaseOutputs):
35
+ class Outputs(BaseNode.Outputs):
37
36
  score: float
38
37
  normalized_score: Optional[float]
39
38
  log: Optional[str]
@@ -98,7 +97,13 @@ class GuardrailNode(BaseNode[StateType], Generic[StateType]):
98
97
  else:
99
98
  reason = None
100
99
 
101
- return self.Outputs(score=score, normalized_score=normalized_score, log=log, reason=reason, **metric_outputs)
100
+ return self.Outputs(
101
+ score=score,
102
+ normalized_score=normalized_score,
103
+ log=log,
104
+ reason=reason,
105
+ **metric_outputs, # type: ignore [arg-type]
106
+ )
102
107
 
103
108
  def _compile_metric_inputs(self) -> List[MetricDefinitionInput]:
104
109
  # TODO: We may want to consolidate with prompt deployment input compilation
@@ -138,6 +138,9 @@ class ToolCallingNode(BaseNode[StateType], Generic[StateType]):
138
138
  )
139
139
 
140
140
  def _build_graph(self) -> None:
141
+ # Get the process_parameters method if it exists on this class
142
+ process_parameters_method = getattr(self.__class__, "process_parameters", None)
143
+
141
144
  self.tool_prompt_node = create_tool_prompt_node(
142
145
  ml_model=self.ml_model,
143
146
  blocks=self.blocks,
@@ -145,6 +148,7 @@ class ToolCallingNode(BaseNode[StateType], Generic[StateType]):
145
148
  prompt_inputs=self.prompt_inputs,
146
149
  parameters=self.parameters,
147
150
  max_prompt_iterations=self.max_prompt_iterations,
151
+ process_parameters_method=process_parameters_method,
148
152
  )
149
153
 
150
154
  # Create the router node (handles routing logic only)
@@ -329,6 +329,7 @@ def create_tool_prompt_node(
329
329
  prompt_inputs: Optional[EntityInputsInterface],
330
330
  parameters: PromptParameters,
331
331
  max_prompt_iterations: Optional[int] = None,
332
+ process_parameters_method: Optional[Callable] = None,
332
333
  ) -> Type[ToolPromptNode]:
333
334
  if functions and len(functions) > 0:
334
335
  prompt_functions: List[Union[Tool, FunctionDefinition]] = []
@@ -398,6 +399,7 @@ def create_tool_prompt_node(
398
399
  "prompt_inputs": node_prompt_inputs,
399
400
  "parameters": parameters,
400
401
  "max_prompt_iterations": max_prompt_iterations,
402
+ **({"process_parameters": process_parameters_method} if process_parameters_method is not None else {}),
401
403
  "__module__": __name__,
402
404
  },
403
405
  ),
@@ -1,5 +1,6 @@
1
+ from dataclasses import field
1
2
  import inspect
2
- from typing import TYPE_CHECKING, Any, Generic, Iterator, Set, Tuple, Type, TypeVar, Union, cast
3
+ from typing import Any, Generic, Iterator, Set, Tuple, Type, TypeVar, Union, cast
3
4
  from typing_extensions import dataclass_transform
4
5
 
5
6
  from pydantic import GetCoreSchemaHandler
@@ -13,9 +14,6 @@ from vellum.workflows.references.output import OutputReference
13
14
  from vellum.workflows.types.generics import is_node_instance
14
15
  from vellum.workflows.types.utils import get_class_attr_names, infer_types
15
16
 
16
- if TYPE_CHECKING:
17
- from vellum.workflows.nodes.bases.base import BaseNode
18
-
19
17
  _Delta = TypeVar("_Delta")
20
18
  _Accumulated = TypeVar("_Accumulated")
21
19
 
@@ -112,18 +110,16 @@ class _BaseOutputsMeta(type):
112
110
  if not cls.__qualname__.endswith(".Outputs") or not other.__qualname__.endswith(".Outputs"):
113
111
  return super().__eq__(other)
114
112
 
115
- self_outputs_class = cast(Type["BaseNode.Outputs"], cls)
116
- other_outputs_class = cast(Type["BaseNode.Outputs"], other)
113
+ self_outputs_class = cast(Type["BaseOutputs"], cls)
114
+ other_outputs_class = cast(Type["BaseOutputs"], other)
117
115
 
118
- if not hasattr(self_outputs_class, "_node_class") or not hasattr(other_outputs_class, "_node_class"):
116
+ if not hasattr(self_outputs_class, "__parent_class__") or not hasattr(other_outputs_class, "__parent_class__"):
119
117
  return super().__eq__(other)
120
118
 
121
- if self_outputs_class._node_class is None or other_outputs_class._node_class is None:
119
+ if self_outputs_class.__parent_class__ is None or other_outputs_class.__parent_class__ is None:
122
120
  return super().__eq__(other)
123
121
 
124
- return getattr(self_outputs_class._node_class, "__qualname__") == getattr(
125
- other_outputs_class._node_class, "__qualname__"
126
- )
122
+ return self_outputs_class.__parent_class__.__qualname__ == other_outputs_class.__parent_class__.__qualname__
127
123
 
128
124
  def __setattr__(cls, name: str, value: Any) -> None:
129
125
  if isinstance(value, OutputReference):
@@ -187,6 +183,10 @@ class _BaseOutputsMeta(type):
187
183
 
188
184
 
189
185
  class BaseOutputs(metaclass=_BaseOutputsMeta):
186
+ # TODO: Uncomment once we figure out why this causes a failure in `infer_types`
187
+ # __parent_class__: Type[Union["BaseNode", "BaseWorkflow"]] = field(init=False)
188
+ __parent_class__: Type = field(init=False)
189
+
190
190
  def __init__(self, **kwargs: Any) -> None:
191
191
  declared_fields = {descriptor.name for descriptor in self.__class__}
192
192
  provided_fields = set(kwargs.keys())
@@ -35,13 +35,11 @@ class OutputReference(BaseDescriptor[_OutputType], Generic[_OutputType]):
35
35
 
36
36
  @cached_property
37
37
  def id(self) -> UUID:
38
- self._outputs_class = self._outputs_class
39
-
40
- node_class = getattr(self._outputs_class, "_node_class", None)
41
- if not node_class:
38
+ parent_class = self._outputs_class.__parent_class__
39
+ if not parent_class:
42
40
  return uuid4()
43
41
 
44
- output_ids = getattr(node_class, "__output_ids__", {})
42
+ output_ids = getattr(parent_class, "__output_ids__", {})
45
43
  if not isinstance(output_ids, dict):
46
44
  return uuid4()
47
45
 
@@ -1,3 +1,4 @@
1
+ from dataclasses import field
1
2
  from datetime import datetime
2
3
  from functools import lru_cache
3
4
  import importlib
@@ -199,6 +200,12 @@ class _BaseWorkflowMeta(type):
199
200
  if inputs_class is not BaseInputs and inputs_class.__parent_class__ is type(None):
200
201
  inputs_class.__parent_class__ = workflow_class
201
202
 
203
+ # TODO: Uncomment this once we figure out why it's causing the ipython reload test to fail
204
+ # workflow_class.Outputs.__parent_class__ = workflow_class
205
+ workflow_class.__output_ids__ = {
206
+ ref.name: uuid4_from_hash(f"{workflow_class.__id__}|id|{ref.name}") for ref in workflow_class.Outputs
207
+ }
208
+
202
209
  return workflow_class
203
210
 
204
211
 
@@ -207,6 +214,7 @@ GraphAttribute = Union[Type[BaseNode], Graph, Set[Type[BaseNode]], Set[Graph]]
207
214
 
208
215
  class BaseWorkflow(Generic[InputsType, StateType], metaclass=_BaseWorkflowMeta):
209
216
  __id__: UUID = uuid4_from_hash(__qualname__)
217
+ __output_ids__: Dict[str, UUID] = {}
210
218
  graph: ClassVar[GraphAttribute]
211
219
  unused_graphs: ClassVar[Set[GraphAttribute]] # nodes or graphs that are defined but not used in the graph
212
220
  emitters: List[BaseWorkflowEmitter]
@@ -214,7 +222,7 @@ class BaseWorkflow(Generic[InputsType, StateType], metaclass=_BaseWorkflowMeta):
214
222
  is_dynamic: ClassVar[bool] = False
215
223
 
216
224
  class Outputs(BaseOutputs):
217
- pass
225
+ __parent_class__: Type["BaseWorkflow"] = field(init=False)
218
226
 
219
227
  WorkflowEvent = Union[ # type: ignore
220
228
  GenericWorkflowEvent,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-ai
3
- Version: 1.3.4
3
+ Version: 1.3.5
4
4
  Summary:
5
5
  License: MIT
6
6
  Requires-Python: >=3.9,<4.0
@@ -72,7 +72,7 @@ vellum_ee/workflows/display/nodes/vellum/tests/test_utils.py,sha256=rHybfUAWwa0L
72
72
  vellum_ee/workflows/display/nodes/vellum/try_node.py,sha256=47fOnSCEFnY8th9m2yTYlgnoUuzgyRZdjg-SXwn--lk,4079
73
73
  vellum_ee/workflows/display/nodes/vellum/utils.py,sha256=oICunzyaXPs0tYnW5zH1r93Bx35MSH7mcD-n0DEWRok,4978
74
74
  vellum_ee/workflows/display/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
75
- vellum_ee/workflows/display/tests/test_base_workflow_display.py,sha256=KUxFl04uZv1xPfnLHsMOjPKygwJkf6fRvPj0JvV8Des,14190
75
+ vellum_ee/workflows/display/tests/test_base_workflow_display.py,sha256=fh-F7TmzOSiWEBwhtd3Nm_Ix9v5W4LhMNhiIxT7G100,15990
76
76
  vellum_ee/workflows/display/tests/workflow_serialization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
77
77
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
78
78
  vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/conftest.py,sha256=Y-ajeT65b5varmrZCw6L3hir4hJCFq-eO0jZfRcrs7g,1886
@@ -100,10 +100,12 @@ vellum_ee/workflows/display/tests/workflow_serialization/test_basic_terminal_nod
100
100
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_composio_serialization.py,sha256=oVXCjkU0G56QJmqnd_xIwF3D9bhJwALFibM2wmRhwUk,3739
101
101
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_serialization.py,sha256=Sg82qV5NCzQDy-RD90hA6QaHgFHOq6ESNkbWXygsnNw,26367
102
102
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_mcp_serialization.py,sha256=QhQbijeCnFeX1i3SMjHJg2WVAEt5JEO3dhFRv-mofdA,2458
103
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py,sha256=cvjHGrgN1LbRvuSKq2U7eLrRFDK4Rb6VJt8L2HIC8HY,10137
103
+ vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_parent_input.py,sha256=HMMa4liBACtL7vU2b-9Ui8Oltyxxb5dbAf-CziLGCes,4284
104
+ vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py,sha256=cKaxme5vUIvKa8aBU7xdeFxXF9wVZ5fW3T5Ie5vToU0,10152
104
105
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_workflow_deployment_serialization.py,sha256=XIZZr5POo2NLn2uEWm9EC3rejeBMoO4X-JtzTH6mvp4,4074
105
106
  vellum_ee/workflows/display/tests/workflow_serialization/test_basic_try_node_serialization.py,sha256=pLCyMScV88DTBXRH7jXaXOEA1GBq8NIipCUFwIAWnwI,2771
106
107
  vellum_ee/workflows/display/tests/workflow_serialization/test_complex_terminal_node_serialization.py,sha256=exT7U-axwtYgFylagScflSQLJEND51qIAx2UATju6JM,6023
108
+ vellum_ee/workflows/display/tests/workflow_serialization/test_final_output_node_map_reference_serialization.py,sha256=vl3pxUJlrYRA8zzFJ-gRm7fe-5fviLNSIsUC7imnMqk,3502
107
109
  vellum_ee/workflows/display/tests/workflow_serialization/test_web_search_node_serialization.py,sha256=vbDFBrWUPeeW7cxjNA6SXrsHlYcbOAhlQ4C45Vdnr1c,3428
108
110
  vellum_ee/workflows/display/tests/workflow_serialization/test_workflow_input_parameterization_error.py,sha256=vAdmn3YTBDpo55znbydQxsgg9ASqHcvsUPwiBR_7wfo,1461
109
111
  vellum_ee/workflows/display/types.py,sha256=cyZruu4sXAdHjwuFc7dydM4DcFNf-pp_CmulXItxac4,3679
@@ -111,15 +113,15 @@ vellum_ee/workflows/display/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeR
111
113
  vellum_ee/workflows/display/utils/auto_layout.py,sha256=f4GiLn_LazweupfqTpubcdtdfE_vrOcmZudSsnYIY9E,3906
112
114
  vellum_ee/workflows/display/utils/events.py,sha256=MEG2BT6GgAzkbv1dMaFpov5OShtaAZeAb1-g3xDFsAM,1826
113
115
  vellum_ee/workflows/display/utils/exceptions.py,sha256=LSwwxCYNxFkf5XMUcFkaZKpQ13OSrI7y_bpEUwbKVk0,169
114
- vellum_ee/workflows/display/utils/expressions.py,sha256=5DwluPppoJSsMf0pfbRafPIisZHZ4VFueXR1YeLcRwY,18593
116
+ vellum_ee/workflows/display/utils/expressions.py,sha256=ci6vOzfCwqVrSRI9A-HR7t4bz_reuS2kzr71SHYIloI,19118
115
117
  vellum_ee/workflows/display/utils/registry.py,sha256=1qXiBTdsnro6FeCX0FGBEK7CIf6wa--Jt50iZ_nEp_M,3460
116
118
  vellum_ee/workflows/display/utils/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
117
119
  vellum_ee/workflows/display/utils/tests/test_auto_layout.py,sha256=vfXI769418s9vda5Gb5NFBH747WMOwSgHRXeLCTLVm8,2356
118
120
  vellum_ee/workflows/display/utils/tests/test_events.py,sha256=Qze6wEmFJx23_sKQhX-i329apWgMeS9zTptWlRca6Ko,4528
119
- vellum_ee/workflows/display/utils/vellum.py,sha256=mtoXmSYwR7rvrq-d6CzCW_auaJXTct0Mi1F0xpRCiNQ,5627
121
+ vellum_ee/workflows/display/utils/vellum.py,sha256=sZwU0KdmZZTKWW62SyxJTl2tC8tN6p_BpZ-lDoinV-U,5670
120
122
  vellum_ee/workflows/display/vellum.py,sha256=J2mdJZ1sdLW535DDUkq_Vm8Z572vhuxHxVZF9deKSdk,391
121
123
  vellum_ee/workflows/display/workflows/__init__.py,sha256=JTB9ObEV3l4gGGdtfBHwVJtTTKC22uj-a-XjTVwXCyA,148
122
- vellum_ee/workflows/display/workflows/base_workflow_display.py,sha256=o1yKDaBv3dECPcJnEtjDZw8Nt9Y5kjijGc7FQi5IhOQ,43745
124
+ vellum_ee/workflows/display/workflows/base_workflow_display.py,sha256=5PCZY37qMA3z1Qpu35Ot0koF5-iH4XVctlLzEMJ26OQ,43991
123
125
  vellum_ee/workflows/display/workflows/get_vellum_workflow_display_class.py,sha256=gxz76AeCqgAZ9D2lZeTiZzxY9eMgn3qOSfVgiqYcOh8,2028
124
126
  vellum_ee/workflows/display/workflows/tests/test_workflow_display.py,sha256=pb7BTH-ivRnya1LQU3j-MApWk_m8POpPNOdD0oEK82A,37847
125
127
  vellum_ee/workflows/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -153,7 +155,7 @@ vellum/client/README.md,sha256=b6XKeYBBbhQx0v1sHWfM0gIJeJhUFF-aqL2ig7ADa08,5564
153
155
  vellum/client/__init__.py,sha256=T5Ht_w-Mk_9nzGqdadhQB8V20M0vYj7am06ut0A3P1o,73401
154
156
  vellum/client/core/__init__.py,sha256=lTcqUPXcx4112yLDd70RAPeqq6tu3eFMe1pKOqkW9JQ,1562
155
157
  vellum/client/core/api_error.py,sha256=44vPoTyWN59gonCIZMdzw7M1uspygiLnr3GNFOoVL2Q,614
156
- vellum/client/core/client_wrapper.py,sha256=7aJQmDvTC5-vD5Qkr7IPOm7wV5Tj_nzD5SlG1cAUg9g,2840
158
+ vellum/client/core/client_wrapper.py,sha256=Mh9pKr9Xxrby4VuKOk1M3W1PdfGhP8jsj32r0WIAdN8,2840
157
159
  vellum/client/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
158
160
  vellum/client/core/file.py,sha256=d4NNbX8XvXP32z8KpK2Xovv33nFfruIrpz0QWxlgpZk,2663
159
161
  vellum/client/core/force_multipart.py,sha256=awxh5MtcRYe74ehY8U76jzv6fYM_w_D3Rur7KQQzSDk,429
@@ -1797,7 +1799,7 @@ vellum/workflows/integrations/tests/test_mcp_service.py,sha256=q_DYrDkIqI4sQBNgI
1797
1799
  vellum/workflows/logging.py,sha256=_a217XogktV4Ncz6xKFz7WfYmZAzkfVRVuC0rWob8ls,437
1798
1800
  vellum/workflows/nodes/__init__.py,sha256=zymtc3_iW2rFmMR-sayTLuN6ZsAw8VnJweWPsjQk2-Q,1197
1799
1801
  vellum/workflows/nodes/bases/__init__.py,sha256=cniHuz_RXdJ4TQgD8CBzoiKDiPxg62ErdVpCbWICX64,58
1800
- vellum/workflows/nodes/bases/base.py,sha256=9vOPHP6K6oK36bEby1KFRXpMevMwq6slwuSRhC-bWFc,20752
1802
+ vellum/workflows/nodes/bases/base.py,sha256=424pCSFZtXqMpAYX01nQCP3Ej2s16_ynWY3VtxUwTdo,20497
1801
1803
  vellum/workflows/nodes/bases/base_adornment_node.py,sha256=hrgzuTetM4ynPd9YGHoK8Vwwn4XITi3aZZ_OCnQrq4Y,3433
1802
1804
  vellum/workflows/nodes/bases/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1803
1805
  vellum/workflows/nodes/bases/tests/test_base_adornment_node.py,sha256=fXZI9KqpS4XMBrBnIEkK3foHaBVvyHwYcQWWDKay7ic,1148
@@ -1810,7 +1812,7 @@ vellum/workflows/nodes/core/inline_subworkflow_node/node.py,sha256=TCmO0wPbt7kc8
1810
1812
  vellum/workflows/nodes/core/inline_subworkflow_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1811
1813
  vellum/workflows/nodes/core/inline_subworkflow_node/tests/test_node.py,sha256=RK2g1h2ib-ruQZ9A2_2L-B9WBdHV44WZj75rkDNL0cE,5766
1812
1814
  vellum/workflows/nodes/core/map_node/__init__.py,sha256=MXpZYmGfhsMJHqqlpd64WiJRtbAtAMQz-_3fCU_cLV0,56
1813
- vellum/workflows/nodes/core/map_node/node.py,sha256=gyOUJw2EhuI5FYT0782moEFlMKG1ajk3ukTiDCtFlxw,9678
1815
+ vellum/workflows/nodes/core/map_node/node.py,sha256=kSyQmIWk4v-KSt4WBf3d-0_QueKYkjtrEmQPGTbMryw,10054
1814
1816
  vellum/workflows/nodes/core/map_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1815
1817
  vellum/workflows/nodes/core/map_node/tests/test_node.py,sha256=Xc2xZY5ShSy-bsIQe41JbvIjq3TE95duS-ygaELRVkk,9320
1816
1818
  vellum/workflows/nodes/core/retry_node/__init__.py,sha256=lN2bIy5a3Uzhs_FYCrooADyYU6ZGShtvLKFWpelwPvo,60
@@ -1837,7 +1839,7 @@ vellum/workflows/nodes/displayable/bases/api_node/tests/test_node.py,sha256=5C59
1837
1839
  vellum/workflows/nodes/displayable/bases/base_prompt_node/__init__.py,sha256=Org3xTvgp1pA0uUXFfnJr29D3HzCey2lEdYF4zbIUgo,70
1838
1840
  vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py,sha256=ea20icDM1HB942wkH-XtXNSNCBDcjeOiN3vowkHL4fs,4477
1839
1841
  vellum/workflows/nodes/displayable/bases/inline_prompt_node/__init__.py,sha256=Hl35IAoepRpE-j4cALaXVJIYTYOF3qszyVbxTj4kS1s,82
1840
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py,sha256=cW0fgmferJVm6LctqC5kCFu4Qg9udMQEb0IISLS7CTo,13795
1842
+ vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py,sha256=wtJjaV0tJ5svO0shxFA-lInDQB25wfv24VUvAOYLOZ8,13793
1841
1843
  vellum/workflows/nodes/displayable/bases/inline_prompt_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1842
1844
  vellum/workflows/nodes/displayable/bases/inline_prompt_node/tests/test_inline_prompt_node.py,sha256=Hk_u2IxLIeeqL_s0RTgoyL5QGYwY9VllKT8z5_JHiDU,24956
1843
1845
  vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py,sha256=0a40fkkZkFMmZN0CsWf6EP_y1H6x36EGa3WcfVNyOsM,9797
@@ -1861,7 +1863,7 @@ vellum/workflows/nodes/displayable/final_output_node/node.py,sha256=6SMaGeBlHQ5r
1861
1863
  vellum/workflows/nodes/displayable/final_output_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1862
1864
  vellum/workflows/nodes/displayable/final_output_node/tests/test_node.py,sha256=E6LQ74qZjY4Xi4avx2qdOCgGhF8pEcNLBh8cqYRkzMI,709
1863
1865
  vellum/workflows/nodes/displayable/guardrail_node/__init__.py,sha256=Ab5eXmOoBhyV4dMWdzh32HLUmnPIBEK_zFCT38C4Fng,68
1864
- vellum/workflows/nodes/displayable/guardrail_node/node.py,sha256=GRWRDCVfPbL03hGbyZP4FAUtAjg_8K3XrU0xJ_lIFeA,5820
1866
+ vellum/workflows/nodes/displayable/guardrail_node/node.py,sha256=axYUojar_kdB3gi4LG3g9euJ8VkOxNtiFxJNI46v-SQ,5869
1865
1867
  vellum/workflows/nodes/displayable/guardrail_node/test_node.py,sha256=SAGv6hSFcBwQkudn1VxtaKNsXSXWWELl3eK05zM6tS0,5410
1866
1868
  vellum/workflows/nodes/displayable/guardrail_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1867
1869
  vellum/workflows/nodes/displayable/guardrail_node/tests/test_node.py,sha256=X2pd6TI8miYxIa7rgvs1pHTEreyWcf77EyR0_Jsa700,2055
@@ -1891,13 +1893,13 @@ vellum/workflows/nodes/displayable/tests/test_search_node_error_handling.py,sha2
1891
1893
  vellum/workflows/nodes/displayable/tests/test_search_node_wth_text_output.py,sha256=VepO5z1277c1y5N6LLIC31nnWD1aak2m5oPFplfJHHs,6935
1892
1894
  vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py,sha256=Bjv-wZyFgNaVZb9KEMMZd9lFoLzbPEPjEMpANizMZw4,2413
1893
1895
  vellum/workflows/nodes/displayable/tool_calling_node/__init__.py,sha256=3n0-ysmFKsr40CVxPthc0rfJgqVJeZuUEsCmYudLVRg,117
1894
- vellum/workflows/nodes/displayable/tool_calling_node/node.py,sha256=diAm1hJ4ZywdRmwZAYhBq_TjmCsUt_qc6ew7tywV1rs,7840
1896
+ vellum/workflows/nodes/displayable/tool_calling_node/node.py,sha256=KxKU8JWFr-XUdwYelEtUTfAh3UaN3FTm2wK-nioXu9U,8065
1895
1897
  vellum/workflows/nodes/displayable/tool_calling_node/state.py,sha256=CcBVb_YtwfSSka4ze678k6-qwmzMSfjfVP8_Y95feSo,302
1896
1898
  vellum/workflows/nodes/displayable/tool_calling_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1897
1899
  vellum/workflows/nodes/displayable/tool_calling_node/tests/test_composio_service.py,sha256=in1fbEz5x1tx3uKv9YXdvOncsHucNL8Ro6Go7lBuuOQ,8962
1898
1900
  vellum/workflows/nodes/displayable/tool_calling_node/tests/test_node.py,sha256=GZoeybB9uM7ai8sBLAtUMHrMVgh-WrJDWrKZci6feDs,11892
1899
1901
  vellum/workflows/nodes/displayable/tool_calling_node/tests/test_utils.py,sha256=SIu5GCj4tIE4fz-cAcdULtQfqZIhrcc3Doo6TWLXBws,8804
1900
- vellum/workflows/nodes/displayable/tool_calling_node/utils.py,sha256=Y2MlODE0lRQ4Fci-G3eYZBpI7CQYe5cIykTLcF4jrbE,23602
1902
+ vellum/workflows/nodes/displayable/tool_calling_node/utils.py,sha256=VC0TenXfktfWHd4ZYQFqiXaZvkhizLJFIK3nQvKoVG0,23780
1901
1903
  vellum/workflows/nodes/displayable/web_search_node/__init__.py,sha256=8FOnEP-n-U68cvxTlJW9wphIAGHq5aqjzLM-DoSSXnU,61
1902
1904
  vellum/workflows/nodes/displayable/web_search_node/node.py,sha256=NQYux2bOtuBF5E4tn-fXi5y3btURPRrNqMSM9MAZYI4,5091
1903
1905
  vellum/workflows/nodes/displayable/web_search_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -1912,7 +1914,7 @@ vellum/workflows/nodes/tests/test_mocks.py,sha256=mfPvrs75PKcsNsbJLQAN6PDFoVqs9T
1912
1914
  vellum/workflows/nodes/tests/test_utils.py,sha256=BUugAHx2C9YuCwTlsTXV1Glxca0kW3St6T9o_QFatSU,5649
1913
1915
  vellum/workflows/nodes/utils.py,sha256=wCvf8K5qruT5GwtvnHcQ-LMllktTD8aaFmAGpKQy--c,10720
1914
1916
  vellum/workflows/outputs/__init__.py,sha256=AyZ4pRh_ACQIGvkf0byJO46EDnSix1ZCAXfvh-ms1QE,94
1915
- vellum/workflows/outputs/base.py,sha256=PUn0zhGzYCSZL34JXtXg9zALlXS_cqxZldLilPxDzb8,9614
1917
+ vellum/workflows/outputs/base.py,sha256=XLt2WnjOhHBC0tT4-1FIUHLVdI1J5NXME6UZxKCKwOI,9741
1916
1918
  vellum/workflows/ports/__init__.py,sha256=bZuMt-R7z5bKwpu4uPW7LlJeePOQWmCcDSXe5frUY5g,101
1917
1919
  vellum/workflows/ports/node_ports.py,sha256=SM9uLAaoaE1HwR-Uqwf2v5zZK5iFnphKs6mE5Ls7ldE,2877
1918
1920
  vellum/workflows/ports/port.py,sha256=PYhmzEHgJyjUjSFkPIQ38cNIKpcXrhYiZlj7nZC5yCk,3989
@@ -1925,7 +1927,7 @@ vellum/workflows/references/external_input.py,sha256=c_4SojTpykCSbGS1Pjmx9FfquyY
1925
1927
  vellum/workflows/references/input.py,sha256=3INu-TLTi4dziWmva6LO3WvgDlPzsjayUx61cVvqLJA,325
1926
1928
  vellum/workflows/references/lazy.py,sha256=jgUYmgt-yAybzPf_R-74MzdU8VuNwMYI8EQqrj9lVR0,2948
1927
1929
  vellum/workflows/references/node.py,sha256=LP854wDVs-9I_aZ7-nkbwXqL2H7W2_3LED2e9FixNS8,1418
1928
- vellum/workflows/references/output.py,sha256=Odpjqnw2uY6lbmt49sUwDclBPZMndYxgtFAGn1iKj8k,3387
1930
+ vellum/workflows/references/output.py,sha256=utYoYPVfsPCTFPXcAc0zTVn2aZcFwL_-y1_WLiKMhHA,3329
1929
1931
  vellum/workflows/references/state_value.py,sha256=bInUF0A3Pt4-zhA0f6LdSuyv8tz7n5QRkHAEn4gsmqI,711
1930
1932
  vellum/workflows/references/tests/test_lazy.py,sha256=0s50-LizMTlSTBQahpK0fg_xqCucA8YTp6QmIMqPvMk,919
1931
1933
  vellum/workflows/references/vellum_secret.py,sha256=Od4d19a5yletWMqNfJR5d_mZQUkVcFzj29mE-T9J7yE,480
@@ -1974,13 +1976,13 @@ vellum/workflows/utils/vellum_variables.py,sha256=YHLNiQGWDNssGH1FQoG9Z1jUFZ-zYe
1974
1976
  vellum/workflows/utils/zip.py,sha256=HVg_YZLmBOTXKaDV3Xhaf3V6sYnfqqZXQ8CpuafkbPY,1181
1975
1977
  vellum/workflows/vellum_client.py,sha256=xkfoucodxNK5JR2-lbRqZx3xzDgExWkP6kySrpi_Ubc,1079
1976
1978
  vellum/workflows/workflows/__init__.py,sha256=KY45TqvavCCvXIkyCFMEc0dc6jTMOUci93U2DUrlZYc,66
1977
- vellum/workflows/workflows/base.py,sha256=PT7Hveate5A4Nt1UQoS6wwFehxgZQ5fuYjS1vLnlkl0,28008
1979
+ vellum/workflows/workflows/base.py,sha256=nEFaKuLQahpEAxi-3VmlIbxEcTaKJ3_1G3WNVATVvzA,28466
1978
1980
  vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnadGsrSZGa7t7LpJA,2008
1979
1981
  vellum/workflows/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1980
1982
  vellum/workflows/workflows/tests/test_base_workflow.py,sha256=ptMntHzVyy8ZuzNgeTuk7hREgKQ5UBdgq8VJFSGaW4Y,20832
1981
1983
  vellum/workflows/workflows/tests/test_context.py,sha256=VJBUcyWVtMa_lE5KxdhgMu0WYNYnUQUDvTF7qm89hJ0,2333
1982
- vellum_ai-1.3.4.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1983
- vellum_ai-1.3.4.dist-info/METADATA,sha256=b4vvPio8YVqk9c6RUtdneolPn9VxgV12Es1eAIxG0qM,5547
1984
- vellum_ai-1.3.4.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1985
- vellum_ai-1.3.4.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1986
- vellum_ai-1.3.4.dist-info/RECORD,,
1984
+ vellum_ai-1.3.5.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
1985
+ vellum_ai-1.3.5.dist-info/METADATA,sha256=cGN1uqwggZ4itH8f0IlQACy6h-uM2e-IspLFVz3DwKU,5547
1986
+ vellum_ai-1.3.5.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
1987
+ vellum_ai-1.3.5.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
1988
+ vellum_ai-1.3.5.dist-info/RECORD,,
@@ -9,6 +9,8 @@ from vellum.workflows.references.lazy import LazyReference
9
9
  from vellum.workflows.state import BaseState
10
10
  from vellum.workflows.workflows.base import BaseWorkflow
11
11
  from vellum_ee.workflows.display.base import EdgeDisplay, WorkflowInputsDisplay
12
+ from vellum_ee.workflows.display.editor.types import NodeDisplayData, NodeDisplayPosition
13
+ from vellum_ee.workflows.display.nodes import BaseNodeDisplay
12
14
  from vellum_ee.workflows.display.workflows.base_workflow_display import BaseWorkflowDisplay
13
15
  from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
14
16
 
@@ -429,3 +431,48 @@ def test_serialize_workflow_with_edge_display_data():
429
431
  assert edge_with_display_data["type"] == "DEFAULT"
430
432
  assert "source_node_id" in edge_with_display_data
431
433
  assert "target_node_id" in edge_with_display_data
434
+
435
+
436
+ def test_serialize_workflow_with_node_display_data():
437
+ """
438
+ Tests that nodes with z_index values serialize display_data correctly.
439
+ """
440
+
441
+ # GIVEN a workflow with a node that has custom display data
442
+ class TestNode(BaseNode):
443
+ class Outputs(BaseNode.Outputs):
444
+ result: str
445
+
446
+ class TestWorkflow(BaseWorkflow):
447
+ graph = TestNode
448
+
449
+ class Outputs(BaseWorkflow.Outputs):
450
+ final_result = TestNode.Outputs.result
451
+
452
+ class TestNodeDisplay(BaseNodeDisplay[TestNode]):
453
+ display_data = NodeDisplayData(position=NodeDisplayPosition(x=100, y=200), z_index=10, width=300, height=150)
454
+
455
+ class TestWorkflowDisplay(BaseWorkflowDisplay[TestWorkflow]):
456
+ pass
457
+
458
+ # WHEN we serialize the workflow with the custom node display
459
+ display = get_workflow_display(
460
+ base_display_class=TestWorkflowDisplay,
461
+ workflow_class=TestWorkflow,
462
+ )
463
+ serialized_workflow = display.serialize()
464
+
465
+ # THEN the node should include display_data with z_index
466
+ workflow_raw_data = cast(Dict[str, Any], serialized_workflow["workflow_raw_data"])
467
+ nodes = cast(List[Dict[str, Any]], workflow_raw_data["nodes"])
468
+
469
+ test_node = None
470
+ for node in nodes:
471
+ if node.get("type") == "GENERIC":
472
+ definition = node.get("definition")
473
+ if isinstance(definition, dict) and definition.get("name") == "TestNode":
474
+ test_node = node
475
+ break
476
+
477
+ assert test_node is not None, "TestNode not found in serialized nodes"
478
+ assert test_node["display_data"] == {"position": {"x": 100, "y": 200}, "z_index": 10, "width": 300, "height": 150}
@@ -0,0 +1,85 @@
1
+ from deepdiff import DeepDiff
2
+
3
+ from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
4
+
5
+ from tests.workflows.basic_tool_calling_node_parent_input.workflow import BasicToolCallingNodeParentInputWorkflow
6
+
7
+
8
+ def test_serialize_workflow():
9
+ # GIVEN a Workflow that uses a generic node
10
+ # WHEN we serialize it
11
+ workflow_display = get_workflow_display(workflow_class=BasicToolCallingNodeParentInputWorkflow)
12
+
13
+ serialized_workflow: dict = workflow_display.serialize()
14
+ # THEN we should get a serialized representation of the Workflow
15
+ assert serialized_workflow.keys() == {
16
+ "workflow_raw_data",
17
+ "input_variables",
18
+ "state_variables",
19
+ "output_variables",
20
+ }
21
+
22
+ # AND its input variables should be what we expect
23
+ input_variables = serialized_workflow["input_variables"]
24
+ assert len(input_variables) == 1
25
+
26
+ # AND its output variables should be what we expect
27
+ output_variables = serialized_workflow["output_variables"]
28
+ assert len(output_variables) == 2
29
+ assert not DeepDiff(
30
+ [
31
+ {"id": "e2e36cfc-cf24-42fd-ba8f-cce39c53d47b", "key": "text", "type": "STRING"},
32
+ {"id": "08ca9519-e421-47e8-a42d-44f49f6aab16", "key": "chat_history", "type": "CHAT_HISTORY"},
33
+ ],
34
+ output_variables,
35
+ ignore_order=True,
36
+ )
37
+
38
+ # AND its raw data should be what we expect
39
+ workflow_raw_data = serialized_workflow["workflow_raw_data"]
40
+ tool_calling_node = workflow_raw_data["nodes"][2]
41
+
42
+ attributes = tool_calling_node["attributes"]
43
+ function_attributes = next(attribute for attribute in attributes if attribute["name"] == "functions")
44
+ assert function_attributes == {
45
+ "id": "cec9f5f2-7bb0-42e4-9c56-f215f07c5569",
46
+ "name": "functions",
47
+ "value": {
48
+ "type": "CONSTANT_VALUE",
49
+ "value": {
50
+ "type": "JSON",
51
+ "value": [
52
+ {
53
+ "type": "CODE_EXECUTION",
54
+ "name": "get_string",
55
+ "description": "\n Get a string with the parent input, dummy input, and the populated input.\n ", # noqa: E501
56
+ "definition": {
57
+ "state": None,
58
+ "cache_config": None,
59
+ "name": "get_string",
60
+ "description": "\n Get a string with the parent input, dummy input, and the populated input.\n ", # noqa: E501
61
+ "parameters": {
62
+ "type": "object",
63
+ "properties": {"populated_input": {"type": "string"}},
64
+ "required": ["populated_input"],
65
+ },
66
+ "inputs": {
67
+ "parent_input": {
68
+ "type": "WORKFLOW_INPUT",
69
+ "input_variable_id": "4bf1f0e7-76c6-4204-9f8c-bd9c3b73a8db",
70
+ },
71
+ "dummy_input": {
72
+ "type": "NODE_OUTPUT",
73
+ "node_id": "8e89ae10-a709-45ec-89f8-242f92e4a83f",
74
+ "node_output_id": "0cd09f0a-c142-4d5d-acc7-b93cd30ca58d",
75
+ },
76
+ },
77
+ "forced": None,
78
+ "strict": None,
79
+ },
80
+ "src": 'from vellum.workflows.utils.functions import use_tool_inputs\n\nfrom .inputs import ParentInputs\nfrom .nodes.dummy_node import DummyNode\n\n\n@use_tool_inputs(\n parent_input=ParentInputs.parent_input,\n dummy_input=DummyNode.Outputs.text,\n)\ndef get_string(parent_input: str, dummy_input: str, populated_input: str) -> str:\n """\n Get a string with the parent input, dummy input, and the populated input.\n """\n return f"This is the parent input: {parent_input}, this is the dummy input: {dummy_input}, and this is the populated input: {populated_input}" # noqa: E501\n', # noqa: E501
81
+ }
82
+ ],
83
+ },
84
+ },
85
+ }
@@ -195,7 +195,7 @@ def test_serialize_workflow():
195
195
  "frequency_penalty": 0.0,
196
196
  "presence_penalty": 0.0,
197
197
  "logit_bias": None,
198
- "custom_parameters": None,
198
+ "custom_parameters": {"mode": "initial"},
199
199
  },
200
200
  },
201
201
  },
@@ -0,0 +1,88 @@
1
+ from typing import List
2
+
3
+ from vellum.workflows import BaseWorkflow
4
+ from vellum.workflows.inputs.base import BaseInputs
5
+ from vellum.workflows.nodes import MapNode
6
+ from vellum.workflows.nodes.bases import BaseNode
7
+ from vellum.workflows.nodes.displayable import FinalOutputNode
8
+ from vellum.workflows.outputs import BaseOutputs
9
+ from vellum.workflows.state import BaseState
10
+ from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
11
+
12
+
13
+ class TestInputs(BaseInputs):
14
+ items: List[str]
15
+
16
+
17
+ class TestIteration(BaseNode):
18
+ item = MapNode.SubworkflowInputs.item
19
+ index = MapNode.SubworkflowInputs.index
20
+
21
+ class Outputs(BaseOutputs):
22
+ processed: str
23
+
24
+ def run(self) -> Outputs:
25
+ return self.Outputs(processed=f"processed_{self.item}_{self.index}")
26
+
27
+
28
+ class TestIterationSubworkflow(BaseWorkflow[MapNode.SubworkflowInputs, BaseState]):
29
+ graph = TestIteration
30
+
31
+ class Outputs(BaseOutputs):
32
+ processed = TestIteration.Outputs.processed
33
+
34
+
35
+ class TestMapNode(MapNode):
36
+ items = TestInputs.items
37
+ subworkflow = TestIterationSubworkflow
38
+
39
+
40
+ class TestFinalOutputNode(FinalOutputNode[BaseState, List[str]]):
41
+ class Outputs(FinalOutputNode.Outputs):
42
+ value = TestMapNode.Outputs.processed
43
+
44
+
45
+ class TestWorkflowWithFinalOutputReferencingMap(BaseWorkflow[TestInputs, BaseState]):
46
+ graph = TestMapNode >> TestFinalOutputNode
47
+
48
+ class Outputs(BaseOutputs):
49
+ final_result = TestFinalOutputNode.Outputs.value
50
+
51
+
52
+ def test_serialize_workflow__final_output_node_referencing_map_node():
53
+ """
54
+ Test that final output nodes referencing map node outputs have correct outputs structure.
55
+
56
+ This test verifies that when a FinalOutputNode references a MapNode output,
57
+ the serialized output contains proper NODE_OUTPUT references instead of None values.
58
+ This addresses the Agent Builder issue where final outputs showed value=None in the UI.
59
+ """
60
+ workflow_display = get_workflow_display(workflow_class=TestWorkflowWithFinalOutputReferencingMap)
61
+
62
+ # WHEN we serialize it
63
+ serialized_workflow: dict = workflow_display.serialize()
64
+
65
+ # THEN the final output node should have the correct outputs structure
66
+ workflow_raw_data = serialized_workflow["workflow_raw_data"]
67
+ map_node = next(node for node in workflow_raw_data["nodes"] if node["type"] == "MAP")
68
+ final_output_node = next(node for node in workflow_raw_data["nodes"] if node["type"] == "TERMINAL")
69
+
70
+ # AND the map node's subworkflow should have the one output variable
71
+ output_variable = next(iter(map_node["data"]["output_variables"]))
72
+ map_node_output_id = output_variable["id"]
73
+
74
+ # AND the final output node should have an outputs array with proper structure
75
+ assert "outputs" in final_output_node
76
+ outputs = final_output_node["outputs"]
77
+ assert len(outputs) == 1
78
+
79
+ output = outputs[0]
80
+ # AND the output should have the correct structure with NODE_OUTPUT reference instead of None
81
+ assert output["name"] == "value"
82
+ assert output["type"] == "JSON"
83
+
84
+ # AND the value should be a NODE_OUTPUT reference, not None
85
+ assert output["value"] is not None, f"Expected NODE_OUTPUT reference but got None. Full output: {output}"
86
+ assert output["value"]["type"] == "NODE_OUTPUT", f"Expected NODE_OUTPUT type but got {output['value']['type']}"
87
+ assert "node_id" in output["value"], f"Missing node_id in output value: {output['value']}"
88
+ assert output["value"]["node_output_id"] == map_node_output_id
@@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, cast
6
6
 
7
7
  from pydantic import BaseModel
8
8
 
9
+ from vellum.client.types.function_definition import FunctionDefinition
9
10
  from vellum.client.types.logical_operator import LogicalOperator
10
11
  from vellum.workflows.descriptors.base import BaseDescriptor
11
12
  from vellum.workflows.expressions.accessor import AccessorExpression
@@ -422,6 +423,17 @@ def serialize_value(display_context: "WorkflowDisplayContext", value: Any) -> Js
422
423
 
423
424
  if callable(value):
424
425
  function_definition = compile_function_definition(value)
426
+ inputs = getattr(value, "__vellum_inputs__", {})
427
+
428
+ if inputs:
429
+ serialized_inputs = {}
430
+ for param_name, input_ref in inputs.items():
431
+ serialized_inputs[param_name] = serialize_value(display_context, input_ref)
432
+
433
+ model_data = function_definition.model_dump()
434
+ model_data["inputs"] = serialized_inputs
435
+ function_definition = FunctionDefinition.model_validate(model_data)
436
+
425
437
  source_path = inspect.getsourcefile(value)
426
438
  if source_path is not None:
427
439
  with virtual_open(source_path) as f:
@@ -102,7 +102,9 @@ def create_node_input_value_pointer_rule(
102
102
  if isinstance(value, OutputReference):
103
103
  if value not in display_context.global_node_output_displays:
104
104
  if issubclass(value.outputs_class, BaseNode.Outputs):
105
- raise ValueError(f"Reference to node '{value.outputs_class._node_class.__name__}' not found in graph.")
105
+ raise ValueError(
106
+ f"Reference to node '{value.outputs_class.__parent_class__.__name__}' not found in graph."
107
+ )
106
108
 
107
109
  raise ValueError(f"Reference to outputs '{value.outputs_class.__qualname__}' is invalid.")
108
110
 
@@ -294,7 +294,7 @@ class BaseWorkflowDisplay(Generic[WorkflowType]):
294
294
  )
295
295
 
296
296
  elif isinstance(workflow_output.instance, OutputReference):
297
- terminal_node_id = workflow_output.instance.outputs_class._node_class.__id__
297
+ terminal_node_id = workflow_output.instance.outputs_class.__parent_class__.__id__
298
298
  serialized_terminal_node = serialized_nodes.get(terminal_node_id)
299
299
  if serialized_terminal_node and isinstance(serialized_terminal_node["data"], dict):
300
300
  serialized_terminal_node["data"]["name"] = workflow_output_display.name
@@ -599,7 +599,7 @@ class BaseWorkflowDisplay(Generic[WorkflowType]):
599
599
 
600
600
  workflow_output_display = self.output_displays.get(workflow_output)
601
601
  workflow_output_displays[workflow_output] = (
602
- workflow_output_display or self._generate_workflow_output_display(workflow_output)
602
+ workflow_output_display or self._generate_workflow_output_display(workflow_output, self._workflow)
603
603
  )
604
604
 
605
605
  return WorkflowDisplayContext(
@@ -688,9 +688,13 @@ class BaseWorkflowDisplay(Generic[WorkflowType]):
688
688
 
689
689
  return EntrypointDisplay(id=entrypoint_id, edge_display=edge_display)
690
690
 
691
- def _generate_workflow_output_display(self, output: BaseDescriptor) -> WorkflowOutputDisplay:
692
- output_id = uuid4_from_hash(f"{self.workflow_id}|id|{output.name}")
693
-
691
+ def _generate_workflow_output_display(
692
+ self, output: OutputReference, workflow_class: Type[BaseWorkflow]
693
+ ) -> WorkflowOutputDisplay:
694
+ # TODO: use the output.id field instead once we add `__parent_class__` to BaseWorkflow.Outputs
695
+ output_id = workflow_class.__output_ids__.get(output.name) or uuid4_from_hash(
696
+ f"{self.workflow_id}|id|{output.name}"
697
+ )
694
698
  return WorkflowOutputDisplay(id=output_id, name=output.name)
695
699
 
696
700
  def __init_subclass__(cls, **kwargs: Any) -> None: