vellum-ai 0.14.46__py3-none-any.whl → 0.14.47__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 (148) hide show
  1. vellum/client/README.md +2 -2
  2. vellum/client/__init__.py +72 -6
  3. vellum/client/core/client_wrapper.py +1 -1
  4. vellum/client/core/file.py +13 -8
  5. vellum/client/core/http_client.py +26 -14
  6. vellum/client/core/pydantic_utilities.py +2 -2
  7. vellum/client/core/request_options.py +3 -0
  8. vellum/client/resources/ad_hoc/client.py +14 -2
  9. vellum/client/resources/container_images/client.py +6 -0
  10. vellum/client/resources/deployments/client.py +12 -0
  11. vellum/client/resources/document_indexes/client.py +18 -0
  12. vellum/client/resources/documents/client.py +6 -0
  13. vellum/client/resources/folder_entities/client.py +6 -0
  14. vellum/client/resources/metric_definitions/client.py +6 -0
  15. vellum/client/resources/prompts/client.py +6 -0
  16. vellum/client/resources/sandboxes/client.py +12 -0
  17. vellum/client/resources/test_suite_runs/client.py +6 -0
  18. vellum/client/resources/test_suites/client.py +2 -2
  19. vellum/client/resources/workflow_deployments/client.py +6 -0
  20. vellum/client/resources/workflow_sandboxes/client.py +6 -0
  21. vellum/client/resources/workflows/client.py +6 -4
  22. vellum/client/resources/workspace_secrets/client.py +6 -0
  23. vellum/client/types/api_request_parent_context.py +0 -6
  24. vellum/client/types/array_input.py +0 -5
  25. vellum/client/types/code_execution_node_array_result.py +0 -5
  26. vellum/client/types/code_execution_node_result.py +0 -5
  27. vellum/client/types/code_execution_node_result_data.py +0 -5
  28. vellum/client/types/code_executor_response.py +0 -5
  29. vellum/client/types/create_test_suite_test_case_request.py +0 -5
  30. vellum/client/types/deployment_history_item.py +0 -5
  31. vellum/client/types/deployment_read.py +0 -5
  32. vellum/client/types/execute_workflow_response.py +0 -5
  33. vellum/client/types/execution_array_vellum_value.py +0 -5
  34. vellum/client/types/external_test_case_execution.py +0 -5
  35. vellum/client/types/external_test_case_execution_request.py +0 -5
  36. vellum/client/types/fulfilled_execute_workflow_workflow_result_event.py +0 -7
  37. vellum/client/types/fulfilled_workflow_node_result_event.py +0 -5
  38. vellum/client/types/initiated_workflow_node_result_event.py +0 -5
  39. vellum/client/types/metadata_filter_config_request.py +0 -5
  40. vellum/client/types/metric_definition_execution.py +0 -5
  41. vellum/client/types/metric_definition_history_item.py +0 -5
  42. vellum/client/types/named_test_case_array_variable_value.py +0 -5
  43. vellum/client/types/named_test_case_array_variable_value_request.py +0 -7
  44. vellum/client/types/node_execution_fulfilled_event.py +0 -11
  45. vellum/client/types/node_execution_initiated_event.py +0 -11
  46. vellum/client/types/node_execution_paused_event.py +0 -11
  47. vellum/client/types/node_execution_rejected_event.py +0 -11
  48. vellum/client/types/node_execution_resumed_event.py +0 -11
  49. vellum/client/types/node_execution_span.py +0 -11
  50. vellum/client/types/node_execution_streaming_event.py +0 -11
  51. vellum/client/types/node_input_compiled_array_value.py +0 -5
  52. vellum/client/types/node_output_compiled_array_value.py +0 -5
  53. vellum/client/types/node_parent_context.py +0 -6
  54. vellum/client/types/paginated_slim_deployment_read_list.py +0 -5
  55. vellum/client/types/paginated_slim_workflow_deployment_list.py +0 -5
  56. vellum/client/types/paginated_test_suite_run_execution_list.py +0 -5
  57. vellum/client/types/paginated_test_suite_test_case_list.py +0 -5
  58. vellum/client/types/prompt_deployment_parent_context.py +0 -6
  59. vellum/client/types/prompt_exec_config.py +0 -6
  60. vellum/client/types/rejected_workflow_node_result_event.py +0 -5
  61. vellum/client/types/replace_test_suite_test_case_request.py +0 -5
  62. vellum/client/types/search_filters_request.py +0 -7
  63. vellum/client/types/search_request_options_request.py +0 -7
  64. vellum/client/types/slim_deployment_read.py +0 -5
  65. vellum/client/types/slim_workflow_deployment.py +0 -5
  66. vellum/client/types/slim_workflow_execution_read.py +0 -12
  67. vellum/client/types/span_link.py +0 -6
  68. vellum/client/types/streaming_workflow_node_result_event.py +0 -5
  69. vellum/client/types/templating_node_array_result.py +0 -5
  70. vellum/client/types/templating_node_result.py +0 -5
  71. vellum/client/types/templating_node_result_data.py +0 -5
  72. vellum/client/types/terminal_node_array_result.py +0 -5
  73. vellum/client/types/terminal_node_result.py +0 -5
  74. vellum/client/types/terminal_node_result_data.py +0 -5
  75. vellum/client/types/test_case_array_variable_value.py +0 -5
  76. vellum/client/types/test_suite_run_execution.py +0 -5
  77. vellum/client/types/test_suite_run_execution_array_output.py +0 -5
  78. vellum/client/types/test_suite_run_execution_metric_result.py +0 -5
  79. vellum/client/types/test_suite_run_external_exec_config.py +0 -5
  80. vellum/client/types/test_suite_run_external_exec_config_data.py +0 -5
  81. vellum/client/types/test_suite_run_external_exec_config_data_request.py +0 -7
  82. vellum/client/types/test_suite_run_external_exec_config_request.py +0 -7
  83. vellum/client/types/test_suite_run_metric_array_output.py +0 -5
  84. vellum/client/types/test_suite_run_read.py +0 -5
  85. vellum/client/types/test_suite_test_case.py +0 -5
  86. vellum/client/types/test_suite_test_case_create_bulk_operation_request.py +0 -7
  87. vellum/client/types/test_suite_test_case_replace_bulk_operation_request.py +0 -7
  88. vellum/client/types/test_suite_test_case_upsert_bulk_operation_request.py +0 -7
  89. vellum/client/types/upsert_test_suite_test_case_request.py +0 -5
  90. vellum/client/types/vellum_value_logical_condition_group_request.py +0 -3
  91. vellum/client/types/vellum_value_logical_condition_request.py +0 -5
  92. vellum/client/types/vellum_variable.py +0 -5
  93. vellum/client/types/workflow_deployment_event_executions_response.py +0 -26
  94. vellum/client/types/workflow_deployment_history_item.py +0 -5
  95. vellum/client/types/workflow_deployment_parent_context.py +0 -6
  96. vellum/client/types/workflow_deployment_read.py +0 -5
  97. vellum/client/types/workflow_deployment_release.py +0 -5
  98. vellum/client/types/workflow_deployment_release_workflow_version.py +0 -5
  99. vellum/client/types/workflow_event_execution_read.py +0 -12
  100. vellum/client/types/workflow_execution_actual.py +0 -5
  101. vellum/client/types/workflow_execution_fulfilled_event.py +0 -11
  102. vellum/client/types/workflow_execution_initiated_event.py +0 -11
  103. vellum/client/types/workflow_execution_node_result_event.py +0 -5
  104. vellum/client/types/workflow_execution_paused_event.py +0 -11
  105. vellum/client/types/workflow_execution_rejected_event.py +0 -11
  106. vellum/client/types/workflow_execution_resumed_event.py +0 -11
  107. vellum/client/types/workflow_execution_snapshotted_event.py +0 -13
  108. vellum/client/types/workflow_execution_span.py +0 -11
  109. vellum/client/types/workflow_execution_streaming_event.py +0 -11
  110. vellum/client/types/workflow_execution_view_online_eval_metric_result.py +0 -7
  111. vellum/client/types/workflow_execution_workflow_result_event.py +0 -5
  112. vellum/client/types/workflow_output_array.py +0 -5
  113. vellum/client/types/workflow_parent_context.py +0 -6
  114. vellum/client/types/workflow_result_event.py +0 -5
  115. vellum/client/types/workflow_result_event_output_data_array.py +0 -5
  116. vellum/client/types/workflow_sandbox_parent_context.py +0 -6
  117. vellum/workflows/nodes/bases/base.py +26 -6
  118. vellum/workflows/nodes/bases/tests/test_base_node.py +30 -0
  119. vellum/workflows/nodes/displayable/code_execution_node/tests/test_code_execution_node.py +50 -0
  120. vellum/workflows/nodes/utils.py +5 -1
  121. vellum/workflows/types/code_execution_node_wrappers.py +5 -0
  122. {vellum_ai-0.14.46.dist-info → vellum_ai-0.14.47.dist-info}/METADATA +1 -1
  123. {vellum_ai-0.14.46.dist-info → vellum_ai-0.14.47.dist-info}/RECORD +148 -148
  124. vellum_ee/workflows/display/nodes/base_node_display.py +17 -6
  125. vellum_ee/workflows/display/nodes/get_node_display_class.py +4 -5
  126. vellum_ee/workflows/display/nodes/vellum/api_node.py +11 -0
  127. vellum_ee/workflows/display/nodes/vellum/code_execution_node.py +5 -0
  128. vellum_ee/workflows/display/nodes/vellum/error_node.py +22 -16
  129. vellum_ee/workflows/display/nodes/vellum/guardrail_node.py +2 -0
  130. vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +46 -12
  131. vellum_ee/workflows/display/nodes/vellum/inline_subworkflow_node.py +2 -0
  132. vellum_ee/workflows/display/nodes/vellum/map_node.py +2 -0
  133. vellum_ee/workflows/display/nodes/vellum/prompt_deployment_node.py +2 -0
  134. vellum_ee/workflows/display/nodes/vellum/search_node.py +8 -0
  135. vellum_ee/workflows/display/nodes/vellum/subworkflow_deployment_node.py +1 -0
  136. vellum_ee/workflows/display/nodes/vellum/templating_node.py +2 -0
  137. vellum_ee/workflows/display/nodes/vellum/tests/test_code_execution_node.py +1 -1
  138. vellum_ee/workflows/display/nodes/vellum/tests/test_error_node.py +4 -0
  139. vellum_ee/workflows/display/nodes/vellum/tests/test_prompt_deployment_node.py +4 -3
  140. vellum_ee/workflows/display/nodes/vellum/tests/test_prompt_node.py +36 -2
  141. vellum_ee/workflows/display/nodes/vellum/tests/test_subworkflow_deployment_node.py +5 -4
  142. vellum_ee/workflows/display/nodes/vellum/tests/test_templating_node.py +1 -1
  143. vellum_ee/workflows/display/tests/test_base_workflow_display.py +44 -0
  144. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_error_node_serialization.py +2 -4
  145. vellum_ee/workflows/display/utils/expressions.py +2 -2
  146. {vellum_ai-0.14.46.dist-info → vellum_ai-0.14.47.dist-info}/LICENSE +0 -0
  147. {vellum_ai-0.14.46.dist-info → vellum_ai-0.14.47.dist-info}/WHEEL +0 -0
  148. {vellum_ai-0.14.46.dist-info → vellum_ai-0.14.47.dist-info}/entry_points.txt +0 -0
@@ -9,6 +9,7 @@ from typing import (
9
9
  ForwardRef,
10
10
  Generic,
11
11
  Optional,
12
+ Set,
12
13
  Tuple,
13
14
  Type,
14
15
  TypeVar,
@@ -24,6 +25,7 @@ from vellum.workflows.nodes.bases.base import BaseNode
24
25
  from vellum.workflows.nodes.utils import get_unadorned_node, get_wrapped_node
25
26
  from vellum.workflows.ports import Port
26
27
  from vellum.workflows.references import OutputReference
28
+ from vellum.workflows.references.node import NodeReference
27
29
  from vellum.workflows.types.core import JsonArray, JsonObject
28
30
  from vellum.workflows.types.generics import NodeType
29
31
  from vellum.workflows.types.utils import get_original_base
@@ -33,7 +35,7 @@ from vellum.workflows.utils.vellum_variables import primitive_type_to_vellum_var
33
35
  from vellum_ee.workflows.display.editor.types import NodeDisplayComment, NodeDisplayData
34
36
  from vellum_ee.workflows.display.nodes.get_node_display_class import get_node_display_class
35
37
  from vellum_ee.workflows.display.nodes.types import NodeOutputDisplay, PortDisplay, PortDisplayOverrides
36
- from vellum_ee.workflows.display.utils.expressions import serialize_condition, serialize_value
38
+ from vellum_ee.workflows.display.utils.expressions import serialize_value
37
39
  from vellum_ee.workflows.display.utils.registry import register_node_display_class
38
40
 
39
41
  if TYPE_CHECKING:
@@ -98,11 +100,18 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
98
100
  # Default values set by the metaclass
99
101
  output_display: Dict[OutputReference, NodeOutputDisplay]
100
102
  port_displays: Dict[Port, PortDisplayOverrides] = {}
101
- node_input_ids_by_name: ClassVar[Dict[str, UUID]] = {}
103
+ attribute_ids_by_name: ClassVar[Dict[str, UUID]] = {}
102
104
 
105
+ # START: Attributes for backwards compatible serialization
103
106
  # Used to explicitly set the target handle id for a node
104
107
  # Once all nodes are Generic Nodes, we may replace this with a trigger_id or trigger attribute
105
108
  target_handle_id: ClassVar[Optional[UUID]] = None
109
+ # Used to explicitly set the input ids for each node input
110
+ node_input_ids_by_name: ClassVar[Dict[str, UUID]] = {}
111
+ # Used by each class extending BaseNodeDisplay to specify which attributes are meant to be serialized
112
+ # as the former `"inputs"` field
113
+ __serializable_inputs__: Set[NodeReference] = set()
114
+ # END: Attributes for backwards compatible serialization
106
115
 
107
116
  def serialize(self, display_context: "WorkflowDisplayContext", **kwargs: Any) -> JsonObject:
108
117
  node = self._node
@@ -114,7 +123,11 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
114
123
  # We don't need to serialize generic node attributes containing a subworkflow
115
124
  continue
116
125
 
117
- id = str(uuid4_from_hash(f"{node_id}|{attribute.name}"))
126
+ id = (
127
+ str(self.attribute_ids_by_name[attribute.name])
128
+ if self.attribute_ids_by_name
129
+ else str(uuid4_from_hash(f"{node_id}|{attribute.name}"))
130
+ )
118
131
  try:
119
132
  attributes.append(
120
133
  {
@@ -191,9 +204,7 @@ class BaseNodeDisplay(Generic[NodeType], metaclass=BaseNodeDisplayMeta):
191
204
  "id": id,
192
205
  "name": port.name,
193
206
  "type": port._condition_type.value,
194
- "expression": (
195
- serialize_condition(display_context, port._condition) if port._condition else None
196
- ),
207
+ "expression": (serialize_value(display_context, port._condition) if port._condition else None),
197
208
  }
198
209
  )
199
210
  else:
@@ -2,7 +2,6 @@ import types
2
2
  from uuid import UUID
3
3
  from typing import TYPE_CHECKING, Any, Dict, Generic, Type, TypeVar
4
4
 
5
- from vellum.workflows.descriptors.base import BaseDescriptor
6
5
  from vellum.workflows.types.generics import NodeType
7
6
  from vellum.workflows.utils.uuids import uuid4_from_hash
8
7
  from vellum_ee.workflows.display.utils.registry import get_from_node_display_registry
@@ -30,14 +29,14 @@ def get_node_display_class(node_class: Type[NodeType]) -> Type["BaseNodeDisplay"
30
29
  node_input_ids_by_name.update(_get_node_input_ids_by_ref(f"{path}.{key}", value))
31
30
  return node_input_ids_by_name
32
31
 
33
- if isinstance(inst, BaseDescriptor):
34
- return {path: uuid4_from_hash(f"{node_class.__id__}|{path}")}
35
-
36
- return {}
32
+ return {path: uuid4_from_hash(f"{node_class.__id__}|{path}")}
37
33
 
38
34
  def exec_body(ns: Dict):
39
35
  node_input_ids_by_name: Dict[str, UUID] = {}
40
36
  for ref in node_class:
37
+ if ref not in base_node_display_class.__serializable_inputs__:
38
+ continue
39
+
41
40
  node_input_ids_by_name.update(_get_node_input_ids_by_ref(ref.name, ref.instance))
42
41
 
43
42
  if node_input_ids_by_name:
@@ -20,6 +20,17 @@ class BaseAPINodeDisplay(BaseNodeDisplay[_APINodeType], Generic[_APINodeType]):
20
20
  # A mapping between node input keys and their ids for inputs representing additional header values
21
21
  additional_header_value_input_ids: ClassVar[Optional[Dict[str, UUID]]] = None
22
22
 
23
+ __serializable_inputs__ = {
24
+ APINode.url,
25
+ APINode.method,
26
+ APINode.json,
27
+ APINode.headers,
28
+ APINode.api_key_header_key,
29
+ APINode.api_key_header_value,
30
+ APINode.bearer_token_value,
31
+ APINode.authorization_type,
32
+ }
33
+
23
34
  def serialize(
24
35
  self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **kwargs: Any
25
36
  ) -> JsonObject:
@@ -18,6 +18,11 @@ class BaseCodeExecutionNodeDisplay(BaseNodeDisplay[_CodeExecutionNodeType], Gene
18
18
  output_id: ClassVar[Optional[UUID]] = None
19
19
  log_output_id: ClassVar[Optional[UUID]] = None
20
20
 
21
+ __serializable_inputs__ = {
22
+ CodeExecutionNode.code,
23
+ CodeExecutionNode.code_inputs,
24
+ }
25
+
21
26
  def serialize(
22
27
  self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **kwargs
23
28
  ) -> JsonObject:
@@ -1,5 +1,5 @@
1
1
  from uuid import UUID
2
- from typing import ClassVar, Generic, Optional, TypeVar
2
+ from typing import Any, ClassVar, Generic, Optional, TypeVar
3
3
 
4
4
  from vellum.workflows.nodes import ErrorNode
5
5
  from vellum.workflows.types.core import JsonObject
@@ -9,47 +9,53 @@ from vellum_ee.workflows.display.nodes.vellum.utils import create_node_input
9
9
  from vellum_ee.workflows.display.types import WorkflowDisplayContext
10
10
 
11
11
  _ErrorNodeType = TypeVar("_ErrorNodeType", bound=ErrorNode)
12
+ LEGACY_INPUT_NAME = "error_source_input_id"
12
13
 
13
14
 
14
15
  class BaseErrorNodeDisplay(BaseNodeDisplay[_ErrorNodeType], Generic[_ErrorNodeType]):
16
+ # DEPRECATED: Remove in 0.15.0 once removed from the vellum-side
15
17
  error_output_id: ClassVar[Optional[UUID]] = None
18
+ # DEPRECATED: Remove in 0.15.0 once removed from the vellum-side
19
+ name: ClassVar[Optional[str]] = None
16
20
 
17
- name: ClassVar[str] = "error-node"
21
+ __serializable_inputs__ = {ErrorNode.error}
18
22
 
19
- def serialize(
20
- self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **kwargs
21
- ) -> JsonObject:
23
+ def serialize(self, display_context: WorkflowDisplayContext, **kwargs) -> JsonObject:
22
24
  node_id = self.node_id
23
- error_source_input_id = self.node_input_ids_by_name.get("error_source_input_id")
25
+ error_source_input_id = self.node_input_ids_by_name.get(
26
+ ErrorNode.error.name,
27
+ ) or self.node_input_ids_by_name.get(LEGACY_INPUT_NAME)
24
28
 
25
29
  error_attribute = raise_if_descriptor(self._node.error)
26
- input_values_by_name = {
27
- "error_source_input_id": error_attribute,
28
- }
29
30
 
30
31
  node_inputs = [
31
32
  create_node_input(
32
33
  node_id=node_id,
33
- input_name=variable_name,
34
- value=variable_value,
34
+ input_name=LEGACY_INPUT_NAME,
35
+ value=error_attribute,
35
36
  display_context=display_context,
36
- input_id=self.node_input_ids_by_name.get(variable_name),
37
+ input_id=error_source_input_id,
37
38
  )
38
- for variable_name, variable_value in input_values_by_name.items()
39
39
  ]
40
40
 
41
- return {
41
+ node_data: dict[str, Any] = {
42
42
  "id": str(node_id),
43
43
  "type": "ERROR",
44
44
  "inputs": [node_input.dict() for node_input in node_inputs],
45
45
  "data": {
46
- "name": self.name,
47
46
  "label": self.label,
48
47
  "target_handle_id": str(self.get_target_handle_id()),
49
48
  "error_source_input_id": str(error_source_input_id),
50
- "error_output_id": str(self.error_output_id),
51
49
  },
52
50
  "display_data": self.get_display_data().dict(),
53
51
  "base": self.get_base().dict(),
54
52
  "definition": self.get_definition().dict(),
55
53
  }
54
+
55
+ if self.name:
56
+ node_data["data"]["name"] = self.name
57
+
58
+ if self.error_output_id:
59
+ node_data["data"]["error_output_id"] = str(self.error_output_id)
60
+
61
+ return node_data
@@ -12,6 +12,8 @@ _GuardrailNodeType = TypeVar("_GuardrailNodeType", bound=GuardrailNode)
12
12
 
13
13
 
14
14
  class BaseGuardrailNodeDisplay(BaseNodeDisplay[_GuardrailNodeType], Generic[_GuardrailNodeType]):
15
+ __serializable_inputs__ = {GuardrailNode.metric_inputs}
16
+
15
17
  def serialize(
16
18
  self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **kwargs
17
19
  ) -> JsonObject:
@@ -10,6 +10,7 @@ from vellum_ee.workflows.display.nodes.base_node_display import BaseNodeDisplay
10
10
  from vellum_ee.workflows.display.nodes.utils import raise_if_descriptor
11
11
  from vellum_ee.workflows.display.nodes.vellum.utils import create_node_input
12
12
  from vellum_ee.workflows.display.types import WorkflowDisplayContext
13
+ from vellum_ee.workflows.display.utils.expressions import serialize_value
13
14
  from vellum_ee.workflows.display.utils.vellum import infer_vellum_variable_type
14
15
  from vellum_ee.workflows.display.vellum import NodeInput
15
16
 
@@ -17,6 +18,8 @@ _InlinePromptNodeType = TypeVar("_InlinePromptNodeType", bound=InlinePromptNode)
17
18
 
18
19
 
19
20
  class BaseInlinePromptNodeDisplay(BaseNodeDisplay[_InlinePromptNodeType], Generic[_InlinePromptNodeType]):
21
+ __serializable_inputs__ = {InlinePromptNode.prompt_inputs}
22
+
20
23
  def serialize(
21
24
  self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **kwargs
22
25
  ) -> JsonObject:
@@ -26,12 +29,14 @@ class BaseInlinePromptNodeDisplay(BaseNodeDisplay[_InlinePromptNodeType], Generi
26
29
  node_inputs, prompt_inputs = self._generate_node_and_prompt_inputs(node_id, node, display_context)
27
30
  input_variable_id_by_name = {prompt_input.key: prompt_input.id for prompt_input in prompt_inputs}
28
31
 
29
- _, output_display = display_context.global_node_output_displays[node.Outputs.text]
30
- _, array_display = display_context.global_node_output_displays[node.Outputs.results]
31
- _, json_display = display_context.global_node_output_displays[node.Outputs.json]
32
+ output_display = self.output_display[node.Outputs.text]
33
+ array_display = self.output_display[node.Outputs.results]
34
+ json_display = self.output_display[node.Outputs.json]
32
35
  node_blocks = raise_if_descriptor(node.blocks)
33
36
  function_definitions = raise_if_descriptor(node.functions)
34
37
 
38
+ ml_model = str(raise_if_descriptor(node.ml_model))
39
+
35
40
  blocks: list = [
36
41
  self._generate_prompt_block(block, input_variable_id_by_name, [i]) for i, block in enumerate(node_blocks)
37
42
  ]
@@ -42,7 +47,7 @@ class BaseInlinePromptNodeDisplay(BaseNodeDisplay[_InlinePromptNodeType], Generi
42
47
  )
43
48
  blocks.extend(functions)
44
49
 
45
- return {
50
+ serialized_node: JsonObject = {
46
51
  "id": str(node_id),
47
52
  "type": "PROMPT",
48
53
  "inputs": [node_input.dict() for node_input in node_inputs],
@@ -62,7 +67,7 @@ class BaseInlinePromptNodeDisplay(BaseNodeDisplay[_InlinePromptNodeType], Generi
62
67
  "blocks": blocks,
63
68
  },
64
69
  },
65
- "ml_model_name": raise_if_descriptor(node.ml_model),
70
+ "ml_model_name": ml_model,
66
71
  },
67
72
  "display_data": self.get_display_data().dict(),
68
73
  "base": self.get_base().dict(),
@@ -74,6 +79,10 @@ class BaseInlinePromptNodeDisplay(BaseNodeDisplay[_InlinePromptNodeType], Generi
74
79
  ],
75
80
  "ports": self.serialize_ports(display_context),
76
81
  }
82
+ attributes = self._serialize_attributes(display_context)
83
+ if attributes:
84
+ serialized_node["attributes"] = attributes
85
+ return serialized_node
77
86
 
78
87
  def _generate_node_and_prompt_inputs(
79
88
  self,
@@ -127,6 +136,7 @@ class BaseInlinePromptNodeDisplay(BaseNodeDisplay[_InlinePromptNodeType], Generi
127
136
  path: List[int],
128
137
  ) -> JsonObject:
129
138
  block: JsonObject
139
+ block_id = uuid4_from_hash(f"{self.node_id}-{prompt_block.block_type}-{'-'.join([str(i) for i in path])}")
130
140
  if prompt_block.block_type == "JINJA":
131
141
  block = {
132
142
  "block_type": "JINJA",
@@ -162,10 +172,21 @@ class BaseInlinePromptNodeDisplay(BaseNodeDisplay[_InlinePromptNodeType], Generi
162
172
  }
163
173
 
164
174
  elif prompt_block.block_type == "VARIABLE":
165
- block = {
166
- "block_type": "VARIABLE",
167
- "input_variable_id": input_variable_id_by_name[prompt_block.input_variable],
168
- }
175
+ input_variable_id = input_variable_id_by_name.get(prompt_block.input_variable)
176
+ if input_variable_id:
177
+ block = {
178
+ "block_type": "VARIABLE",
179
+ "input_variable_id": input_variable_id,
180
+ }
181
+ else:
182
+ # Even though this will likely fail in runtime, we want to allow serialization to succeed
183
+ # in case the block is work in progress or the node is not yet part of the graph
184
+ block = {
185
+ "block_type": "VARIABLE",
186
+ "input_variable_id": str(
187
+ uuid4_from_hash(f"{block_id}-input_variable-{prompt_block.input_variable}")
188
+ ),
189
+ }
169
190
 
170
191
  elif prompt_block.block_type == "PLAIN_TEXT":
171
192
  block = {
@@ -184,9 +205,7 @@ class BaseInlinePromptNodeDisplay(BaseNodeDisplay[_InlinePromptNodeType], Generi
184
205
  else:
185
206
  raise NotImplementedError(f"Serialization for prompt block type {prompt_block.block_type} not implemented")
186
207
 
187
- block["id"] = str(
188
- uuid4_from_hash(f"{self.node_id}-{prompt_block.block_type}-{'-'.join([str(i) for i in path])}")
189
- )
208
+ block["id"] = str(block_id)
190
209
  if prompt_block.cache_config:
191
210
  block["cache_config"] = prompt_block.cache_config.dict()
192
211
  else:
@@ -198,3 +217,18 @@ class BaseInlinePromptNodeDisplay(BaseNodeDisplay[_InlinePromptNodeType], Generi
198
217
  block["state"] = "ENABLED"
199
218
 
200
219
  return block
220
+
221
+ def _serialize_attributes(self, display_context: "WorkflowDisplayContext"):
222
+ attribute_instances_by_name = {}
223
+ for attribute in self._node:
224
+ if attribute.name in self.attribute_ids_by_name:
225
+ attribute_instances_by_name[attribute.name] = attribute.instance
226
+
227
+ return [
228
+ {
229
+ "id": str(attr_id),
230
+ "name": attr_name,
231
+ "value": serialize_value(display_context, attribute_instances_by_name[attr_name]),
232
+ }
233
+ for attr_name, attr_id in self.attribute_ids_by_name.items()
234
+ ]
@@ -21,6 +21,8 @@ class BaseInlineSubworkflowNodeDisplay(
21
21
  ):
22
22
  workflow_input_ids_by_name: ClassVar[Dict[str, UUID]] = {}
23
23
 
24
+ __serializable_inputs__ = {InlineSubworkflowNode.subworkflow_inputs}
25
+
24
26
  def serialize(
25
27
  self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **kwargs
26
28
  ) -> JsonObject:
@@ -13,6 +13,8 @@ _MapNodeType = TypeVar("_MapNodeType", bound=MapNode)
13
13
 
14
14
 
15
15
  class BaseMapNodeDisplay(BaseAdornmentNodeDisplay[_MapNodeType], Generic[_MapNodeType]):
16
+ __serializable_inputs__ = {MapNode.items} # type: ignore[misc]
17
+
16
18
  def serialize(
17
19
  self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **kwargs
18
20
  ) -> JsonObject:
@@ -13,6 +13,8 @@ _PromptDeploymentNodeType = TypeVar("_PromptDeploymentNodeType", bound=PromptDep
13
13
 
14
14
 
15
15
  class BasePromptDeploymentNodeDisplay(BaseNodeDisplay[_PromptDeploymentNodeType], Generic[_PromptDeploymentNodeType]):
16
+ __serializable_inputs__ = {PromptDeploymentNode.prompt_inputs}
17
+
16
18
  def serialize(
17
19
  self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **kwargs
18
20
  ) -> JsonObject:
@@ -32,6 +32,14 @@ class BaseSearchNodeDisplay(BaseNodeDisplay[_SearchNodeType], Generic[_SearchNod
32
32
  # A mapping between the id of the operand (e.g. "lhs_variable_id" or "rhs_variable_id") and the id of the node input
33
33
  # that the operand is pointing to.
34
34
  metadata_filter_input_id_by_operand_id: Dict[UUID, UUID] = {}
35
+ __serializable_inputs__ = {
36
+ SearchNode.query,
37
+ SearchNode.document_index,
38
+ SearchNode.weights,
39
+ SearchNode.chunk_separator,
40
+ SearchNode.limit,
41
+ SearchNode.result_merging,
42
+ }
35
43
 
36
44
  def serialize(
37
45
  self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **kwargs
@@ -14,6 +14,7 @@ _SubworkflowDeploymentNodeType = TypeVar("_SubworkflowDeploymentNodeType", bound
14
14
  class BaseSubworkflowDeploymentNodeDisplay(
15
15
  BaseNodeDisplay[_SubworkflowDeploymentNodeType], Generic[_SubworkflowDeploymentNodeType]
16
16
  ):
17
+ __serializable_inputs__ = {SubworkflowDeploymentNode.subworkflow_inputs}
17
18
 
18
19
  def serialize(
19
20
  self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **kwargs
@@ -15,6 +15,8 @@ TEMPLATE_INPUT_NAME = TemplatingNode.template.name
15
15
 
16
16
 
17
17
  class BaseTemplatingNodeDisplay(BaseNodeDisplay[_TemplatingNodeType], Generic[_TemplatingNodeType]):
18
+ __serializable_inputs__ = {TemplatingNode.inputs}
19
+
18
20
  def serialize(
19
21
  self, display_context: WorkflowDisplayContext, error_output_id: Optional[UUID] = None, **kwargs
20
22
  ) -> JsonObject:
@@ -29,7 +29,7 @@ def _display_class_with_node_input_ids_by_name_with_inputs_prefix(Node: Type[Cod
29
29
  @pytest.mark.parametrize(
30
30
  ["GetDisplayClass", "expected_input_id"],
31
31
  [
32
- (_no_display_class, "e3cdb222-324e-4ad1-abb2-bdd7881b3a0e"),
32
+ (_no_display_class, "a5dbe403-0b00-4df6-b8f7-ed5f7794b003"),
33
33
  (_display_class_with_node_input_ids_by_name, "fba6a4d5-835a-4e99-afb7-f6a4aed15110"),
34
34
  (_display_class_with_node_input_ids_by_name_with_inputs_prefix, "fba6a4d5-835a-4e99-afb7-f6a4aed15110"),
35
35
  ],
@@ -41,3 +41,7 @@ def test_error_node_display__serialize_with_vellum_error() -> None:
41
41
  }
42
42
  ],
43
43
  }
44
+
45
+ # AND we serialize the DEPRECATED fields
46
+ assert "error_output_id" not in serialized_node["data"]
47
+ assert "name" not in serialized_node["data"]
@@ -5,6 +5,7 @@ from typing import Type
5
5
 
6
6
  from vellum.workflows import BaseWorkflow
7
7
  from vellum.workflows.nodes import PromptDeploymentNode
8
+ from vellum_ee.workflows.display.nodes.vellum.prompt_deployment_node import BasePromptDeploymentNodeDisplay
8
9
  from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
9
10
 
10
11
 
@@ -13,14 +14,14 @@ def _no_display_class(Node: Type[PromptDeploymentNode]): # type: ignore
13
14
 
14
15
 
15
16
  def _display_class_with_node_input_ids_by_name(Node: Type[PromptDeploymentNode]):
16
- class PromptDeploymentNodeDisplay(PromptDeploymentNode[Node]): # type: ignore[valid-type]
17
+ class PromptDeploymentNodeDisplay(BasePromptDeploymentNodeDisplay[Node]): # type: ignore[valid-type]
17
18
  node_input_ids_by_name = {"foo": UUID("6037747a-1d35-4094-b363-4369fc92c5d4")}
18
19
 
19
20
  return PromptDeploymentNodeDisplay
20
21
 
21
22
 
22
23
  def _display_class_with_node_input_ids_by_name_with_inputs_prefix(Node: Type[PromptDeploymentNode]):
23
- class PromptDeploymentNodeDisplay(PromptDeploymentNode[Node]): # type: ignore[valid-type]
24
+ class PromptDeploymentNodeDisplay(BasePromptDeploymentNodeDisplay[Node]): # type: ignore[valid-type]
24
25
  node_input_ids_by_name = {"prompt_inputs.foo": UUID("6037747a-1d35-4094-b363-4369fc92c5d4")}
25
26
 
26
27
  return PromptDeploymentNodeDisplay
@@ -51,7 +52,7 @@ def mock_fetch_deployment(mocker):
51
52
  @pytest.mark.parametrize(
52
53
  ["GetDisplayClass", "expected_input_id"],
53
54
  [
54
- (_no_display_class, "6037747a-1d35-4094-b363-4369fc92c5d4"),
55
+ (_no_display_class, "016187d6-2830-4256-a61d-e52f9bf6355e"),
55
56
  (_display_class_with_node_input_ids_by_name, "6037747a-1d35-4094-b363-4369fc92c5d4"),
56
57
  (_display_class_with_node_input_ids_by_name_with_inputs_prefix, "6037747a-1d35-4094-b363-4369fc92c5d4"),
57
58
  ],
@@ -2,6 +2,7 @@ import pytest
2
2
  from uuid import UUID
3
3
  from typing import Type
4
4
 
5
+ from vellum.client.types.variable_prompt_block import VariablePromptBlock
5
6
  from vellum.workflows import BaseWorkflow
6
7
  from vellum.workflows.nodes import BaseNode
7
8
  from vellum.workflows.nodes.displayable.inline_prompt_node.node import InlinePromptNode
@@ -78,7 +79,7 @@ def _display_class_with_node_input_ids_by_name_with_inputs_prefix(Node: Type[Inl
78
79
  @pytest.mark.parametrize(
79
80
  ["GetDisplayClass", "expected_input_id"],
80
81
  [
81
- (_no_display_class, "8aa4ce7f-5eb8-41b7-abd0-ea2b40c8fb88"),
82
+ (_no_display_class, "9b036991-67ff-4cd0-a4d7-b4ed581e8b6d"),
82
83
  (_display_class_with_node_input_ids_by_name, "fba6a4d5-835a-4e99-afb7-f6a4aed15110"),
83
84
  (_display_class_with_node_input_ids_by_name_with_inputs_prefix, "fba6a4d5-835a-4e99-afb7-f6a4aed15110"),
84
85
  ],
@@ -165,7 +166,7 @@ def test_serialize_node__prompt_inputs__state_reference():
165
166
  },
166
167
  },
167
168
  {
168
- "id": "3750feb9-5d5c-4150-b62d-a9924f466888",
169
+ "id": "b83c40f7-0159-442f-af03-e80870363c52",
169
170
  "key": "bar",
170
171
  "value": {
171
172
  "rules": [
@@ -181,3 +182,36 @@ def test_serialize_node__prompt_inputs__state_reference():
181
182
  },
182
183
  },
183
184
  ]
185
+
186
+
187
+ def test_serialize_node__unreferenced_variable_block__still_serializes():
188
+ # GIVEN a prompt node with an unreferenced variable block
189
+ class MyPromptNode(InlinePromptNode):
190
+ blocks = [VariablePromptBlock(input_variable="foo")]
191
+
192
+ # AND a workflow with the prompt node
193
+ class MyWorkflow(BaseWorkflow):
194
+ graph = MyPromptNode
195
+
196
+ # WHEN the prompt node is serialized
197
+ workflow_display = get_workflow_display(workflow_class=MyWorkflow)
198
+ serialized_workflow: dict = workflow_display.serialize()
199
+
200
+ # THEN the node should skip the state reference input rule
201
+ assert serialized_workflow["workflow_raw_data"]["nodes"][1]["data"]["exec_config"]["prompt_template_block_data"][
202
+ "blocks"
203
+ ] == [
204
+ {
205
+ "id": "fecbb5f3-e0a3-42ed-9774-6c68fd5db50c",
206
+ "block_type": "VARIABLE",
207
+ "input_variable_id": "ea3f6348-8553-4375-bd27-527df4e4f3c2",
208
+ "state": "ENABLED",
209
+ "cache_config": None,
210
+ }
211
+ ]
212
+
213
+ # AND we should have a warning of the invalid reference
214
+ # TODO: Come up with a proposal for how nodes should propagate warnings
215
+ # warnings = list(workflow_display.errors)
216
+ # assert len(warnings) == 1
217
+ # assert "Missing input variable 'foo' for prompt block 0" in str(warnings[0])
@@ -4,7 +4,8 @@ from uuid import UUID, uuid4
4
4
  from typing import Type
5
5
 
6
6
  from vellum.workflows import BaseWorkflow
7
- from vellum.workflows.nodes import SubworkflowDeploymentNode
7
+ from vellum.workflows.nodes.displayable.subworkflow_deployment_node.node import SubworkflowDeploymentNode
8
+ from vellum_ee.workflows.display.nodes.vellum.subworkflow_deployment_node import BaseSubworkflowDeploymentNodeDisplay
8
9
  from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
9
10
 
10
11
 
@@ -13,14 +14,14 @@ def _no_display_class(Node: Type[SubworkflowDeploymentNode]): # type: ignore
13
14
 
14
15
 
15
16
  def _display_class_with_node_input_ids_by_name(Node: Type[SubworkflowDeploymentNode]):
16
- class SubworkflowNodeDisplay(SubworkflowDeploymentNode[Node]): # type: ignore[valid-type]
17
+ class SubworkflowNodeDisplay(BaseSubworkflowDeploymentNodeDisplay[Node]): # type: ignore[valid-type]
17
18
  node_input_ids_by_name = {"foo": UUID("aff4f838-577e-44b9-ac5c-6d8213abbb9c")}
18
19
 
19
20
  return SubworkflowNodeDisplay
20
21
 
21
22
 
22
23
  def _display_class_with_node_input_ids_by_name_with_inputs_prefix(Node: Type[SubworkflowDeploymentNode]):
23
- class SubworkflowNodeDisplay(SubworkflowDeploymentNode[Node]): # type: ignore[valid-type]
24
+ class SubworkflowNodeDisplay(BaseSubworkflowDeploymentNodeDisplay[Node]): # type: ignore[valid-type]
24
25
  node_input_ids_by_name = {"subworkflow_inputs.foo": UUID("aff4f838-577e-44b9-ac5c-6d8213abbb9c")}
25
26
 
26
27
  return SubworkflowNodeDisplay
@@ -55,7 +56,7 @@ def mock_fetch_deployment(mocker):
55
56
  @pytest.mark.parametrize(
56
57
  ["GetDisplayClass", "expected_input_id"],
57
58
  [
58
- (_no_display_class, "aff4f838-577e-44b9-ac5c-6d8213abbb9c"),
59
+ (_no_display_class, "394132c2-1817-455e-9f3f-b7073eb63a2b"),
59
60
  (_display_class_with_node_input_ids_by_name, "aff4f838-577e-44b9-ac5c-6d8213abbb9c"),
60
61
  (_display_class_with_node_input_ids_by_name_with_inputs_prefix, "aff4f838-577e-44b9-ac5c-6d8213abbb9c"),
61
62
  ],
@@ -29,7 +29,7 @@ def _display_class_with_node_input_ids_by_name_with_inputs_prefix(Node: Type[Tem
29
29
  @pytest.mark.parametrize(
30
30
  ["GetDisplayClass", "expected_input_id"],
31
31
  [
32
- (_no_display_class, "d3519cec-590c-416d-8eb1-96051aed5ddd"),
32
+ (_no_display_class, "91d982a9-6c41-42ac-aff9-7b623c450a55"),
33
33
  (_display_class_with_node_input_ids_by_name, "fba6a4d5-835a-4e99-afb7-f6a4aed15110"),
34
34
  (_display_class_with_node_input_ids_by_name_with_inputs_prefix, "fba6a4d5-835a-4e99-afb7-f6a4aed15110"),
35
35
  ],
@@ -3,6 +3,8 @@ from typing import Dict
3
3
 
4
4
  from vellum.workflows.inputs import BaseInputs
5
5
  from vellum.workflows.nodes import BaseNode
6
+ from vellum.workflows.ports.port import Port
7
+ from vellum.workflows.references.lazy import LazyReference
6
8
  from vellum.workflows.state import BaseState
7
9
  from vellum.workflows.workflows.base import BaseWorkflow
8
10
  from vellum_ee.workflows.display.vellum import WorkflowInputsVellumDisplayOverrides
@@ -285,3 +287,45 @@ def test_vellum_workflow_display__serialize_with_parse_json_expression():
285
287
  "operator": "parseJson",
286
288
  },
287
289
  }
290
+
291
+
292
+ def test_serialize__port_with_lazy_reference():
293
+ # GIVEN a node with a lazy reference in a Port
294
+ class MyNode(BaseNode):
295
+ class Ports(BaseNode.Ports):
296
+ foo = Port.on_if(LazyReference(lambda: MyNode.Outputs.bar))
297
+
298
+ class Outputs(BaseNode.Outputs):
299
+ bar: bool
300
+
301
+ # AND a workflow that uses the node
302
+ class Workflow(BaseWorkflow):
303
+ graph = MyNode
304
+
305
+ # WHEN we serialize the workflow
306
+ workflow_display = get_workflow_display(workflow_class=Workflow)
307
+ exec_config = workflow_display.serialize()
308
+
309
+ # THEN the lazy reference should be serialized correctly
310
+ raw_data = exec_config["workflow_raw_data"]
311
+ assert isinstance(raw_data, dict)
312
+
313
+ nodes = raw_data["nodes"]
314
+ assert isinstance(nodes, list)
315
+
316
+ my_node = nodes[1]
317
+ assert isinstance(my_node, dict)
318
+ ports = my_node.get("ports")
319
+ assert isinstance(ports, list)
320
+ assert ports == [
321
+ {
322
+ "id": "6c26bc2b-6469-47c1-b858-d63f0d311ea6",
323
+ "name": "foo",
324
+ "type": "IF",
325
+ "expression": {
326
+ "type": "NODE_OUTPUT",
327
+ "node_id": str(MyNode.__id__),
328
+ "node_output_id": str(MyNode.__output_ids__["bar"]),
329
+ },
330
+ }
331
+ ]
@@ -87,7 +87,7 @@ def test_serialize_workflow():
87
87
  "type": "ERROR",
88
88
  "inputs": [
89
89
  {
90
- "id": "690d825f-6ffd-493e-8141-c86d384e6150",
90
+ "id": "8e4c8d76-2e02-4d7e-a7bf-d71af392dc49",
91
91
  "key": "error_source_input_id",
92
92
  "value": {
93
93
  "rules": [
@@ -101,11 +101,9 @@ def test_serialize_workflow():
101
101
  }
102
102
  ],
103
103
  "data": {
104
- "name": "error-node",
105
104
  "label": "Fail Node",
106
105
  "target_handle_id": "70c19f1c-309c-4a5d-ba65-664c0bb2fedf",
107
- "error_source_input_id": "None",
108
- "error_output_id": "None",
106
+ "error_source_input_id": "8e4c8d76-2e02-4d7e-a7bf-d71af392dc49",
109
107
  },
110
108
  "display_data": {"position": {"x": 0.0, "y": 0.0}},
111
109
  "base": {