vellum-ai 1.3.4__py3-none-any.whl → 1.3.6__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.
- vellum/client/README.md +16 -0
- vellum/client/core/client_wrapper.py +2 -2
- vellum/plugins/vellum_mypy.py +12 -9
- vellum/workflows/executable.py +9 -0
- vellum/workflows/graph/graph.py +5 -0
- vellum/workflows/graph/tests/test_graph.py +17 -0
- vellum/workflows/nodes/bases/base.py +4 -8
- vellum/workflows/nodes/core/map_node/node.py +8 -1
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +11 -4
- vellum/workflows/nodes/displayable/guardrail_node/node.py +8 -3
- vellum/workflows/nodes/displayable/tool_calling_node/node.py +6 -0
- vellum/workflows/nodes/displayable/tool_calling_node/utils.py +9 -1
- vellum/workflows/outputs/base.py +19 -10
- vellum/workflows/references/output.py +4 -6
- vellum/workflows/utils/names.py +12 -4
- vellum/workflows/utils/tests/test_names.py +9 -0
- vellum/workflows/workflows/base.py +9 -3
- {vellum_ai-1.3.4.dist-info → vellum_ai-1.3.6.dist-info}/METADATA +1 -1
- {vellum_ai-1.3.4.dist-info → vellum_ai-1.3.6.dist-info}/RECORD +31 -28
- vellum_ee/workflows/display/nodes/tests/test_base_node_display.py +17 -0
- vellum_ee/workflows/display/tests/test_base_workflow_display.py +47 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_api_node_serialization.py +1 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_parent_input.py +89 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py +1 -1
- vellum_ee/workflows/display/tests/workflow_serialization/test_final_output_node_map_reference_serialization.py +88 -0
- vellum_ee/workflows/display/utils/expressions.py +45 -12
- vellum_ee/workflows/display/utils/vellum.py +3 -1
- vellum_ee/workflows/display/workflows/base_workflow_display.py +3 -5
- {vellum_ai-1.3.4.dist-info → vellum_ai-1.3.6.dist-info}/LICENSE +0 -0
- {vellum_ai-1.3.4.dist-info → vellum_ai-1.3.6.dist-info}/WHEEL +0 -0
- {vellum_ai-1.3.4.dist-info → vellum_ai-1.3.6.dist-info}/entry_points.txt +0 -0
vellum/client/README.md
CHANGED
@@ -232,3 +232,19 @@ client = Vellum(
|
|
232
232
|
)
|
233
233
|
```
|
234
234
|
|
235
|
+
## Contributing
|
236
|
+
|
237
|
+
While we value open-source contributions to this SDK, this library is generated programmatically.
|
238
|
+
Additions made directly to this library would have to be moved over to our generation code,
|
239
|
+
otherwise they would be overwritten upon the next generated release. Feel free to open a PR as
|
240
|
+
a proof of concept, but know that we will not be able to merge it as-is. We suggest opening
|
241
|
+
an issue first to discuss with us!
|
242
|
+
|
243
|
+
On the other hand, contributions to the README are always very welcome!
|
244
|
+
## Open-Source vs. Paid
|
245
|
+
|
246
|
+
This repo is available under the [MIT expat license](https://github.com/vellum-ai/vellum-python-sdks/blob/main/LICENSE), except
|
247
|
+
for the `ee` directory (which has its [license here](https://github.com/vellum-ai/vellum-python-sdks/blob/main/ee/LICENSE)) if applicable.
|
248
|
+
|
249
|
+
To learn more, [book a demo](https://www.vellum.ai/landing-pages/request-demo) or see our [pricing page](https://www.vellum.ai/pricing).
|
250
|
+
|
@@ -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.
|
30
|
+
"User-Agent": "vellum-ai/1.3.6",
|
31
31
|
"X-Fern-Language": "Python",
|
32
32
|
"X-Fern-SDK-Name": "vellum-ai",
|
33
|
-
"X-Fern-SDK-Version": "1.3.
|
33
|
+
"X-Fern-SDK-Version": "1.3.6",
|
34
34
|
**(self.get_custom_headers() or {}),
|
35
35
|
}
|
36
36
|
if self._api_version is not None:
|
vellum/plugins/vellum_mypy.py
CHANGED
@@ -20,6 +20,7 @@ from mypy.types import AnyType, CallableType, FunctionLike, Instance, Type as My
|
|
20
20
|
|
21
21
|
TypeResolver = Callable[[str, List[MypyType]], MypyType]
|
22
22
|
NODE_ATTRIBUTE_REGEX = r"^[a-z].*$"
|
23
|
+
BASE_NODE_FULLNAME = "vellum.workflows.nodes.bases.base.BaseNode"
|
23
24
|
|
24
25
|
DESCRIPTOR_PATHS: list[tuple[str, str, str]] = [
|
25
26
|
(
|
@@ -28,12 +29,12 @@ DESCRIPTOR_PATHS: list[tuple[str, str, str]] = [
|
|
28
29
|
r"^[^_].*$",
|
29
30
|
),
|
30
31
|
(
|
31
|
-
"
|
32
|
+
f"{BASE_NODE_FULLNAME}.ExternalInputs",
|
32
33
|
"vellum.workflows.references.external_input.ExternalInputReference",
|
33
34
|
r"^[^_].*$",
|
34
35
|
),
|
35
36
|
(
|
36
|
-
"
|
37
|
+
f"{BASE_NODE_FULLNAME}.Execution",
|
37
38
|
"vellum.workflows.references.execution_count.ExecutionCountReference",
|
38
39
|
r"^count$",
|
39
40
|
),
|
@@ -48,7 +49,7 @@ DESCRIPTOR_PATHS: list[tuple[str, str, str]] = [
|
|
48
49
|
r"^[^_].*$",
|
49
50
|
),
|
50
51
|
(
|
51
|
-
|
52
|
+
BASE_NODE_FULLNAME,
|
52
53
|
"vellum.workflows.references.node.NodeReference",
|
53
54
|
NODE_ATTRIBUTE_REGEX,
|
54
55
|
),
|
@@ -163,7 +164,7 @@ class VellumMypyPlugin(Plugin):
|
|
163
164
|
elif _is_subclass(ctx.cls.info, "vellum.workflows.nodes.displayable.final_output_node.node.FinalOutputNode"):
|
164
165
|
self._dynamic_output_node_class_hook(ctx, "value")
|
165
166
|
|
166
|
-
if _is_subclass(ctx.cls.info,
|
167
|
+
if _is_subclass(ctx.cls.info, BASE_NODE_FULLNAME):
|
167
168
|
return self._base_node_class_hook(ctx)
|
168
169
|
|
169
170
|
if _is_subclass(ctx.cls.info, "vellum.workflows.workflows.base.BaseWorkflow"):
|
@@ -241,6 +242,9 @@ class VellumMypyPlugin(Plugin):
|
|
241
242
|
"""
|
242
243
|
|
243
244
|
for key, sym in ctx.cls.info.names.items():
|
245
|
+
if ctx.cls.info.fullname == BASE_NODE_FULLNAME:
|
246
|
+
continue
|
247
|
+
|
244
248
|
if not isinstance(sym.node, Var):
|
245
249
|
continue
|
246
250
|
|
@@ -250,6 +254,7 @@ class VellumMypyPlugin(Plugin):
|
|
250
254
|
type_ = sym.node.type
|
251
255
|
if not type_:
|
252
256
|
continue
|
257
|
+
|
253
258
|
sym.node.type = self._get_resolvable_type(
|
254
259
|
lambda fullname, types: ctx.api.named_type(fullname, types), type_
|
255
260
|
)
|
@@ -301,7 +306,7 @@ class VellumMypyPlugin(Plugin):
|
|
301
306
|
and isinstance(arg.expr, MemberExpr)
|
302
307
|
and isinstance(arg.expr.expr, NameExpr)
|
303
308
|
and isinstance(arg.expr.expr.node, TypeInfo)
|
304
|
-
and _is_subclass(arg.expr.expr.node,
|
309
|
+
and _is_subclass(arg.expr.expr.node, BASE_NODE_FULLNAME)
|
305
310
|
and arg.expr.name == "Outputs"
|
306
311
|
):
|
307
312
|
self._calls_with_nested_descriptor_expressions.add(call_expr)
|
@@ -548,7 +553,7 @@ class VellumMypyPlugin(Plugin):
|
|
548
553
|
if not isinstance(ctx.default_return_type, Instance):
|
549
554
|
return ctx.default_return_type
|
550
555
|
|
551
|
-
if not _is_subclass(ctx.default_return_type.type, "
|
556
|
+
if not _is_subclass(ctx.default_return_type.type, f"{BASE_NODE_FULLNAME}.Outputs"):
|
552
557
|
return ctx.default_return_type
|
553
558
|
|
554
559
|
outputs_node = self._get_node_outputs_type_info(ctx)
|
@@ -677,9 +682,7 @@ class VellumMypyPlugin(Plugin):
|
|
677
682
|
|
678
683
|
expr = ctx.context.callee.expr
|
679
684
|
instance = ctx.api.get_expression_type(expr)
|
680
|
-
if not isinstance(instance, Instance) or not _is_subclass(
|
681
|
-
instance.type, "vellum.workflows.nodes.bases.base.BaseNode"
|
682
|
-
):
|
685
|
+
if not isinstance(instance, Instance) or not _is_subclass(instance.type, BASE_NODE_FULLNAME):
|
683
686
|
return None
|
684
687
|
|
685
688
|
outputs_node = instance.type.names.get("Outputs")
|
vellum/workflows/graph/graph.py
CHANGED
@@ -96,6 +96,11 @@ class Graph:
|
|
96
96
|
def from_edge(edge: Edge) -> "Graph":
|
97
97
|
return Graph(entrypoints={edge.from_port}, edges=[edge], terminals={port for port in edge.to_node.Ports})
|
98
98
|
|
99
|
+
@staticmethod
|
100
|
+
def empty() -> "Graph":
|
101
|
+
"""Create an empty graph with no entrypoints, edges, or terminals."""
|
102
|
+
return Graph(entrypoints=set(), edges=[], terminals=set())
|
103
|
+
|
99
104
|
def __rshift__(self, other: GraphTarget) -> "Graph":
|
100
105
|
if not self._edges and not self._entrypoints:
|
101
106
|
raise ValueError("Graph instance can only create new edges from nodes within existing edges")
|
@@ -4,6 +4,23 @@ from vellum.workflows.nodes.bases.base import BaseNode
|
|
4
4
|
from vellum.workflows.ports.port import Port
|
5
5
|
|
6
6
|
|
7
|
+
def test_graph__empty():
|
8
|
+
# WHEN we create an empty graph
|
9
|
+
graph = Graph.empty()
|
10
|
+
|
11
|
+
# THEN the graph has no entrypoints
|
12
|
+
assert len(list(graph.entrypoints)) == 0
|
13
|
+
|
14
|
+
# AND no nodes
|
15
|
+
assert len(list(graph.nodes)) == 0
|
16
|
+
|
17
|
+
# AND no edges
|
18
|
+
assert len(list(graph.edges)) == 0
|
19
|
+
|
20
|
+
# AND string representation indicates empty graph
|
21
|
+
assert str(graph) == "Graph(empty)"
|
22
|
+
|
23
|
+
|
7
24
|
def test_graph__from_node():
|
8
25
|
# GIVEN a node
|
9
26
|
class MyNode(BaseNode):
|
@@ -13,6 +13,7 @@ from vellum.workflows.descriptors.utils import is_unresolved, resolve_value
|
|
13
13
|
from vellum.workflows.errors.types import WorkflowErrorCode
|
14
14
|
from vellum.workflows.events.node import NodeExecutionStreamingEvent
|
15
15
|
from vellum.workflows.exceptions import NodeException
|
16
|
+
from vellum.workflows.executable import BaseExecutable
|
16
17
|
from vellum.workflows.graph import Graph
|
17
18
|
from vellum.workflows.graph.graph import GraphTarget
|
18
19
|
from vellum.workflows.inputs.base import BaseInputs
|
@@ -120,7 +121,7 @@ class BaseNodeMeta(ABCMeta):
|
|
120
121
|
cls = super().__new__(mcs, name, bases, dct)
|
121
122
|
node_class = cast(Type["BaseNode"], cls)
|
122
123
|
|
123
|
-
node_class.Outputs.
|
124
|
+
node_class.Outputs.__parent_class__ = node_class
|
124
125
|
|
125
126
|
# Add cls to relevant nested classes, since python should've been doing this by default
|
126
127
|
for port in node_class.Ports:
|
@@ -260,9 +261,7 @@ class _BaseNodeExecutionMeta(type):
|
|
260
261
|
NodeRunResponse = Union[BaseOutputs, Iterator[BaseOutput]]
|
261
262
|
|
262
263
|
|
263
|
-
class BaseNode(Generic[StateType], ABC, metaclass=BaseNodeMeta):
|
264
|
-
__id__: UUID = uuid4_from_hash(__qualname__)
|
265
|
-
__output_ids__: Dict[str, UUID] = {}
|
264
|
+
class BaseNode(Generic[StateType], ABC, BaseExecutable, metaclass=BaseNodeMeta):
|
266
265
|
state: StateType
|
267
266
|
_context: WorkflowContext
|
268
267
|
_inputs: MappingProxyType[NodeReference, Any]
|
@@ -270,11 +269,8 @@ class BaseNode(Generic[StateType], ABC, metaclass=BaseNodeMeta):
|
|
270
269
|
class ExternalInputs(BaseInputs):
|
271
270
|
__descriptor_class__ = ExternalInputReference
|
272
271
|
|
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
272
|
class Outputs(BaseOutputs):
|
277
|
-
|
273
|
+
__parent_class__: Type["BaseNode"] = field(init=False)
|
278
274
|
|
279
275
|
class Ports(NodePorts):
|
280
276
|
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
|
-
|
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,8 @@ 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.
|
103
|
+
processed_parameters = self.process_parameters(self.parameters)
|
104
|
+
processed_blocks = self.process_blocks(self.blocks)
|
104
105
|
|
105
106
|
request_options["additional_body_parameters"] = {
|
106
107
|
"execution_context": execution_context.model_dump(mode="json"),
|
@@ -139,7 +140,7 @@ class BaseInlinePromptNode(BasePromptNode[StateType], Generic[StateType]):
|
|
139
140
|
input_values=input_values,
|
140
141
|
input_variables=input_variables,
|
141
142
|
parameters=processed_parameters,
|
142
|
-
blocks=
|
143
|
+
blocks=processed_blocks,
|
143
144
|
settings=self.settings,
|
144
145
|
functions=normalized_functions,
|
145
146
|
expand_meta=self.expand_meta,
|
@@ -152,7 +153,7 @@ class BaseInlinePromptNode(BasePromptNode[StateType], Generic[StateType]):
|
|
152
153
|
input_values=input_values,
|
153
154
|
input_variables=input_variables,
|
154
155
|
parameters=processed_parameters,
|
155
|
-
blocks=
|
156
|
+
blocks=processed_blocks,
|
156
157
|
settings=self.settings,
|
157
158
|
functions=normalized_functions,
|
158
159
|
expand_meta=self.expand_meta,
|
@@ -300,7 +301,7 @@ class BaseInlinePromptNode(BasePromptNode[StateType], Generic[StateType]):
|
|
300
301
|
|
301
302
|
return input_variables, input_values
|
302
303
|
|
303
|
-
def
|
304
|
+
def process_parameters(self, parameters: PromptParameters) -> PromptParameters:
|
304
305
|
"""
|
305
306
|
Process parameters to recursively convert any Pydantic models to JSON schema dictionaries.
|
306
307
|
"""
|
@@ -310,3 +311,9 @@ class BaseInlinePromptNode(BasePromptNode[StateType], Generic[StateType]):
|
|
310
311
|
processed_custom_params = normalize_json(parameters.custom_parameters)
|
311
312
|
|
312
313
|
return parameters.model_copy(update={"custom_parameters": processed_custom_params})
|
314
|
+
|
315
|
+
def process_blocks(self, blocks: List[PromptBlock]) -> List[PromptBlock]:
|
316
|
+
"""
|
317
|
+
Override this method to process the blocks before they are executed.
|
318
|
+
"""
|
319
|
+
return blocks
|
@@ -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(
|
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(
|
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,10 @@ 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
|
+
process_blocks_method = getattr(self.__class__, "process_blocks", None)
|
144
|
+
|
141
145
|
self.tool_prompt_node = create_tool_prompt_node(
|
142
146
|
ml_model=self.ml_model,
|
143
147
|
blocks=self.blocks,
|
@@ -145,6 +149,8 @@ class ToolCallingNode(BaseNode[StateType], Generic[StateType]):
|
|
145
149
|
prompt_inputs=self.prompt_inputs,
|
146
150
|
parameters=self.parameters,
|
147
151
|
max_prompt_iterations=self.max_prompt_iterations,
|
152
|
+
process_parameters_method=process_parameters_method,
|
153
|
+
process_blocks_method=process_blocks_method,
|
148
154
|
)
|
149
155
|
|
150
156
|
# Create the router node (handles routing logic only)
|
@@ -14,6 +14,7 @@ from vellum.client.types.prompt_output import PromptOutput
|
|
14
14
|
from vellum.client.types.prompt_parameters import PromptParameters
|
15
15
|
from vellum.client.types.string_chat_message_content import StringChatMessageContent
|
16
16
|
from vellum.client.types.variable_prompt_block import VariablePromptBlock
|
17
|
+
from vellum.workflows.descriptors.base import BaseDescriptor
|
17
18
|
from vellum.workflows.errors.types import WorkflowErrorCode
|
18
19
|
from vellum.workflows.exceptions import NodeException
|
19
20
|
from vellum.workflows.expressions.concat import ConcatExpression
|
@@ -329,6 +330,8 @@ def create_tool_prompt_node(
|
|
329
330
|
prompt_inputs: Optional[EntityInputsInterface],
|
330
331
|
parameters: PromptParameters,
|
331
332
|
max_prompt_iterations: Optional[int] = None,
|
333
|
+
process_parameters_method: Optional[Callable] = None,
|
334
|
+
process_blocks_method: Optional[Callable] = None,
|
332
335
|
) -> Type[ToolPromptNode]:
|
333
336
|
if functions and len(functions) > 0:
|
334
337
|
prompt_functions: List[Union[Tool, FunctionDefinition]] = []
|
@@ -398,6 +401,8 @@ def create_tool_prompt_node(
|
|
398
401
|
"prompt_inputs": node_prompt_inputs,
|
399
402
|
"parameters": parameters,
|
400
403
|
"max_prompt_iterations": max_prompt_iterations,
|
404
|
+
**({"process_parameters": process_parameters_method} if process_parameters_method is not None else {}),
|
405
|
+
**({"process_blocks": process_blocks_method} if process_blocks_method is not None else {}),
|
401
406
|
"__module__": __name__,
|
402
407
|
},
|
403
408
|
),
|
@@ -527,7 +532,10 @@ def create_function_node(
|
|
527
532
|
inputs = getattr(func, "__vellum_inputs__", {})
|
528
533
|
if inputs:
|
529
534
|
for param_name, param_ref in inputs.items():
|
530
|
-
|
535
|
+
if isinstance(param_ref, BaseDescriptor):
|
536
|
+
resolved_value = param_ref.resolve(self.state)
|
537
|
+
else:
|
538
|
+
resolved_value = param_ref
|
531
539
|
merged_kwargs[param_name] = resolved_value
|
532
540
|
|
533
541
|
return func(**merged_kwargs)
|
vellum/workflows/outputs/base.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
+
from dataclasses import field
|
1
2
|
import inspect
|
2
|
-
from typing import
|
3
|
+
from typing import Any, Dict, Generic, Iterator, Set, Tuple, Type, TypeVar, Union, cast
|
3
4
|
from typing_extensions import dataclass_transform
|
4
5
|
|
5
6
|
from pydantic import GetCoreSchemaHandler
|
@@ -9,13 +10,11 @@ from vellum.workflows.constants import undefined
|
|
9
10
|
from vellum.workflows.descriptors.base import BaseDescriptor
|
10
11
|
from vellum.workflows.errors.types import WorkflowErrorCode
|
11
12
|
from vellum.workflows.exceptions import NodeException
|
13
|
+
from vellum.workflows.executable import BaseExecutable
|
12
14
|
from vellum.workflows.references.output import OutputReference
|
13
15
|
from vellum.workflows.types.generics import is_node_instance
|
14
16
|
from vellum.workflows.types.utils import get_class_attr_names, infer_types
|
15
17
|
|
16
|
-
if TYPE_CHECKING:
|
17
|
-
from vellum.workflows.nodes.bases.base import BaseNode
|
18
|
-
|
19
18
|
_Delta = TypeVar("_Delta")
|
20
19
|
_Accumulated = TypeVar("_Accumulated")
|
21
20
|
|
@@ -101,6 +100,10 @@ class BaseOutput(Generic[_Delta, _Accumulated]):
|
|
101
100
|
|
102
101
|
@dataclass_transform(kw_only_default=True)
|
103
102
|
class _BaseOutputsMeta(type):
|
103
|
+
def __new__(cls, name: str, bases: Tuple[Type, ...], dct: Dict[str, Any]) -> Any:
|
104
|
+
dct["__parent_class__"] = type(None)
|
105
|
+
return super().__new__(cls, name, bases, dct)
|
106
|
+
|
104
107
|
def __eq__(cls, other: Any) -> bool:
|
105
108
|
"""
|
106
109
|
We need to include custom eq logic to prevent infinite loops during ipython reloading.
|
@@ -112,17 +115,21 @@ class _BaseOutputsMeta(type):
|
|
112
115
|
if not cls.__qualname__.endswith(".Outputs") or not other.__qualname__.endswith(".Outputs"):
|
113
116
|
return super().__eq__(other)
|
114
117
|
|
115
|
-
self_outputs_class = cast(Type["
|
116
|
-
other_outputs_class = cast(Type["
|
118
|
+
self_outputs_class = cast(Type["BaseOutputs"], cls)
|
119
|
+
other_outputs_class = cast(Type["BaseOutputs"], other)
|
117
120
|
|
118
|
-
if not hasattr(self_outputs_class, "
|
121
|
+
if not hasattr(self_outputs_class, "__parent_class__") or not hasattr(other_outputs_class, "__parent_class__"):
|
119
122
|
return super().__eq__(other)
|
120
123
|
|
121
|
-
if self_outputs_class.
|
124
|
+
if self_outputs_class.__parent_class__ is None or other_outputs_class.__parent_class__ is None:
|
122
125
|
return super().__eq__(other)
|
123
126
|
|
124
|
-
|
125
|
-
|
127
|
+
if self_outputs_class.__parent_class__ is type(None) or other_outputs_class.__parent_class__ is type(None):
|
128
|
+
return super().__eq__(other)
|
129
|
+
|
130
|
+
return (
|
131
|
+
self_outputs_class.__parent_class__.__qualname__ == other_outputs_class.__parent_class__.__qualname__
|
132
|
+
and self_outputs_class.__parent_class__.__module__ == other_outputs_class.__parent_class__.__module__
|
126
133
|
)
|
127
134
|
|
128
135
|
def __setattr__(cls, name: str, value: Any) -> None:
|
@@ -187,6 +194,8 @@ class _BaseOutputsMeta(type):
|
|
187
194
|
|
188
195
|
|
189
196
|
class BaseOutputs(metaclass=_BaseOutputsMeta):
|
197
|
+
__parent_class__: Type[BaseExecutable] = field(init=False)
|
198
|
+
|
190
199
|
def __init__(self, **kwargs: Any) -> None:
|
191
200
|
declared_fields = {descriptor.name for descriptor in self.__class__}
|
192
201
|
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
|
-
|
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(
|
42
|
+
output_ids = getattr(parent_class, "__output_ids__", {})
|
45
43
|
if not isinstance(output_ids, dict):
|
46
44
|
return uuid4()
|
47
45
|
|
@@ -78,7 +76,7 @@ class OutputReference(BaseDescriptor[_OutputType], Generic[_OutputType]):
|
|
78
76
|
def __eq__(self, other: object) -> bool:
|
79
77
|
if not isinstance(other, type(self)):
|
80
78
|
return False
|
81
|
-
return super().__eq__(other) and
|
79
|
+
return super().__eq__(other) and self._outputs_class == other._outputs_class
|
82
80
|
|
83
81
|
def __hash__(self) -> int:
|
84
82
|
return hash((self._outputs_class, self._name))
|
vellum/workflows/utils/names.py
CHANGED
@@ -2,11 +2,19 @@ import re
|
|
2
2
|
|
3
3
|
|
4
4
|
def pascal_to_title_case(pascal_str: str) -> str:
|
5
|
-
|
6
|
-
title_case_str = re.sub(r"(?<!^)(?=[A-Z])", " ", pascal_str)
|
5
|
+
title_case_str = re.sub(r"(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])", " ", pascal_str)
|
7
6
|
|
8
|
-
|
9
|
-
|
7
|
+
words = title_case_str.split()
|
8
|
+
result_words = []
|
9
|
+
|
10
|
+
for word in words:
|
11
|
+
if word.isupper():
|
12
|
+
result_words.append(word)
|
13
|
+
# Otherwise, apply title case
|
14
|
+
else:
|
15
|
+
result_words.append(word.capitalize())
|
16
|
+
|
17
|
+
return " ".join(result_words)
|
10
18
|
|
11
19
|
|
12
20
|
def snake_to_title_case(snake_str: str) -> str:
|
@@ -8,6 +8,15 @@ from vellum.workflows.utils.names import pascal_to_title_case
|
|
8
8
|
[
|
9
9
|
("MyPascalCaseString", "My Pascal Case String"),
|
10
10
|
("AnotherPascalCaseString", "Another Pascal Case String"),
|
11
|
+
("FetchDeploymentScheme", "Fetch Deployment Scheme"),
|
12
|
+
("CheckDeploymentExists", "Check Deployment Exists"),
|
13
|
+
("APINode", "API Node"),
|
14
|
+
("HTTPSConnection", "HTTPS Connection"),
|
15
|
+
("XMLParser", "XML Parser"),
|
16
|
+
("SimpleWord", "Simple Word"),
|
17
|
+
("A", "A"),
|
18
|
+
("AB", "AB"),
|
19
|
+
("ABCDef", "ABC Def"),
|
11
20
|
],
|
12
21
|
)
|
13
22
|
def test_pascal_to_title_case(input_str, expected):
|
@@ -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
|
@@ -61,6 +62,7 @@ from vellum.workflows.events.workflow import (
|
|
61
62
|
WorkflowExecutionStreamingBody,
|
62
63
|
WorkflowExecutionStreamingEvent,
|
63
64
|
)
|
65
|
+
from vellum.workflows.executable import BaseExecutable
|
64
66
|
from vellum.workflows.graph import Graph
|
65
67
|
from vellum.workflows.inputs.base import BaseInputs
|
66
68
|
from vellum.workflows.nodes.bases import BaseNode
|
@@ -199,14 +201,18 @@ class _BaseWorkflowMeta(type):
|
|
199
201
|
if inputs_class is not BaseInputs and inputs_class.__parent_class__ is type(None):
|
200
202
|
inputs_class.__parent_class__ = workflow_class
|
201
203
|
|
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
|
|
205
212
|
GraphAttribute = Union[Type[BaseNode], Graph, Set[Type[BaseNode]], Set[Graph]]
|
206
213
|
|
207
214
|
|
208
|
-
class BaseWorkflow(Generic[InputsType, StateType], metaclass=_BaseWorkflowMeta):
|
209
|
-
__id__: UUID = uuid4_from_hash(__qualname__)
|
215
|
+
class BaseWorkflow(Generic[InputsType, StateType], BaseExecutable, metaclass=_BaseWorkflowMeta):
|
210
216
|
graph: ClassVar[GraphAttribute]
|
211
217
|
unused_graphs: ClassVar[Set[GraphAttribute]] # nodes or graphs that are defined but not used in the graph
|
212
218
|
emitters: List[BaseWorkflowEmitter]
|
@@ -214,7 +220,7 @@ class BaseWorkflow(Generic[InputsType, StateType], metaclass=_BaseWorkflowMeta):
|
|
214
220
|
is_dynamic: ClassVar[bool] = False
|
215
221
|
|
216
222
|
class Outputs(BaseOutputs):
|
217
|
-
|
223
|
+
__parent_class__: Type["BaseWorkflow"] = field(init=False)
|
218
224
|
|
219
225
|
WorkflowEvent = Union[ # type: ignore
|
220
226
|
GenericWorkflowEvent,
|
@@ -33,7 +33,7 @@ vellum_ee/workflows/display/nodes/__init__.py,sha256=jI1aPBQf8DkmrYoZ4O-wR1duqZB
|
|
33
33
|
vellum_ee/workflows/display/nodes/base_node_display.py,sha256=98vszulLRvYz0kAh7ZEOeWOqzMtlpnOZM2lAViOUieA,18393
|
34
34
|
vellum_ee/workflows/display/nodes/get_node_display_class.py,sha256=jI_kUi9LnNLDpY63QtlC4TfN8P571VN4LpzH0I1ZtLk,1149
|
35
35
|
vellum_ee/workflows/display/nodes/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
36
|
-
vellum_ee/workflows/display/nodes/tests/test_base_node_display.py,sha256=
|
36
|
+
vellum_ee/workflows/display/nodes/tests/test_base_node_display.py,sha256=Cg5Ker3amAn2wraeNW4t_puvIZdvRB-P8XIL-p4GGL0,3571
|
37
37
|
vellum_ee/workflows/display/nodes/types.py,sha256=St1BB6no528OyELGiyRabWao0GGw6mLhstQAvEACbGk,247
|
38
38
|
vellum_ee/workflows/display/nodes/utils.py,sha256=sloya5TpXsnot1HURc9L51INwflRqUzHxRVnCS9Cd-4,973
|
39
39
|
vellum_ee/workflows/display/nodes/vellum/__init__.py,sha256=nUIgH2s0-7IbQRNrBhLPyRNe8YIrx3Yo9HeeW-aXXFk,1668
|
@@ -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=
|
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
|
@@ -81,7 +81,7 @@ vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attr
|
|
81
81
|
vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_outputs_serialization.py,sha256=O1CIKMmRTaMaT3IhjwCAiMz1ZThPg9lAUbSiZkx_E8c,6321
|
82
82
|
vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_ports_serialization.py,sha256=fPXcX-tWQ0UMuEyOFAylgi7pWiE7lZWk9vrShlTExik,40053
|
83
83
|
vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_trigger_serialization.py,sha256=FLvcD8eoABHUPv08oSpIp_P-65sw2gl4whMXEJJj4f8,6785
|
84
|
-
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_api_node_serialization.py,sha256=
|
84
|
+
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_api_node_serialization.py,sha256=D_ZGMOgr5Ek33ZQagv08b8up1U0Lc0dCgAjnO8ZEpzQ,16336
|
85
85
|
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_code_execution_node_serialization.py,sha256=arl-tho6f0qstUM2ejONEO4ru_6TxCPbni3FS-UZouQ,30108
|
86
86
|
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_conditional_node_serialization.py,sha256=-BiFVw3JUx_79isQNgAtZB2362oByRcuAuUVK9uzCJI,54204
|
87
87
|
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_default_state_serialization.py,sha256=E1ww97BFoGbnRkxf84TScat5NQhP8nLVrdaOlpGnSKU,8615
|
@@ -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/
|
103
|
+
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_parent_input.py,sha256=__LX4cuzbyZp_1wc-SI8X_J0tnhOkCEmRVUWLKI5aQM,4578
|
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=
|
116
|
+
vellum_ee/workflows/display/utils/expressions.py,sha256=80TcSWhT249cvT_NRobwelWCLWKgeFQEpXCvjFq0fwY,19743
|
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=
|
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=
|
124
|
+
vellum_ee/workflows/display/workflows/base_workflow_display.py,sha256=HEwhGlQ6AllU_SpGSVCYgRc0RXev4iuHxXX3Rl18Xzs,43674
|
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
|
@@ -149,11 +151,11 @@ vellum_ee/workflows/tests/test_serialize_module.py,sha256=lk-4dVnG2HcxxywBXxDR1i
|
|
149
151
|
vellum_ee/workflows/tests/test_server.py,sha256=dXFBraU99Y6cKp2aBhLFXQTScSRcE9WaWjo1z9piqdU,23344
|
150
152
|
vellum_ee/workflows/tests/test_virtual_files.py,sha256=TJEcMR0v2S8CkloXNmCHA0QW0K6pYNGaIjraJz7sFvY,2762
|
151
153
|
vellum/__init__.py,sha256=lv4OTbPgTegugVOpRy5xJPPwMvC1Zqrg4oFMEwY1KSg,47428
|
152
|
-
vellum/client/README.md,sha256=
|
154
|
+
vellum/client/README.md,sha256=flqu57ubZNTfpq60CdLtJC9gp4WEkyjb_n_eZ4OYf9w,6497
|
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=
|
158
|
+
vellum/client/core/client_wrapper.py,sha256=i5oGjE3DOmkirKwi-u3hUXa3ZSnCYWb06u5EdpjKLMM,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
|
@@ -948,7 +950,7 @@ vellum/plugins/pydantic.py,sha256=SamPlRZ8V9kuxEfMkOPKjhMMLa5Q3wYJ3Z-F_IfKtvA,39
|
|
948
950
|
vellum/plugins/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
949
951
|
vellum/plugins/tests/test_pydantic.py,sha256=S6bLqs3Y5DGA012QV_7f6hk4e6Bz-iJ9py9DEKuo4fM,746
|
950
952
|
vellum/plugins/utils.py,sha256=cPmxE9R2CK1bki2jKE8rB-G9zMf2pzHjSPDHFPXwd3Q,878
|
951
|
-
vellum/plugins/vellum_mypy.py,sha256=
|
953
|
+
vellum/plugins/vellum_mypy.py,sha256=awSjop_Vn7VRCQZCy5rNUMq8n_ueNjGJ0wHVN-rTVJY,27916
|
952
954
|
vellum/prompts/__init__.py,sha256=kn-btvQOMNnnBuyQiUpie48_VBJAk7tFFU-ul5dwK60,107
|
953
955
|
vellum/prompts/blocks/__init__.py,sha256=Zwvncjd8sUCPmT-8pFpgLYsKJl0xB6td1GTQzjV9hYA,108
|
954
956
|
vellum/prompts/blocks/compilation.py,sha256=mCGfqJXcKMkv565Ye5y9MsPdVAmkCGOnqQBRa3nJVg8,10754
|
@@ -1738,6 +1740,7 @@ vellum/workflows/events/tests/test_event.py,sha256=V26TNmbo2aL4sDvcY3nPxCWgcoS4e
|
|
1738
1740
|
vellum/workflows/events/types.py,sha256=mVrqAH9Hs9SpXm08Hcxdyap_ImQPwGsxRr56rSNMP34,5043
|
1739
1741
|
vellum/workflows/events/workflow.py,sha256=kLSWFXiDZH0TELWoDjQ_kHVomFnw8MVVUPDGIzgyosw,8997
|
1740
1742
|
vellum/workflows/exceptions.py,sha256=NiBiR3ggfmPxBVqD-H1SqmjI-7mIn0EStSN1BqApvCM,1213
|
1743
|
+
vellum/workflows/executable.py,sha256=um-gLJMVYfGJwGJfZIPlCRHhHIYm6pn8PUEfeqrNx5k,218
|
1741
1744
|
vellum/workflows/expressions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1742
1745
|
vellum/workflows/expressions/accessor.py,sha256=3lu1-_-dBfZdJvtX-q66jbmRAZtqIfdsh_3_JNuzg1E,4462
|
1743
1746
|
vellum/workflows/expressions/add.py,sha256=Rr1O83nksL5Z0kam4eaQOokvDrEwlUg7LqWnXzGUW40,1226
|
@@ -1783,9 +1786,9 @@ vellum/workflows/expressions/tests/test_length.py,sha256=pQA1tYSwqxE6euclboY024N
|
|
1783
1786
|
vellum/workflows/expressions/tests/test_minus.py,sha256=pq7dvdRGNhSSn95LGNzRErsYUsFk5SpOKHDcSR5QToc,1632
|
1784
1787
|
vellum/workflows/expressions/tests/test_parse_json.py,sha256=zpB_qE5_EwWQL7ULQUJm0o1PRSfWZdAqZNW6Ah13oJE,1059
|
1785
1788
|
vellum/workflows/graph/__init__.py,sha256=3sHlay5d_-uD7j3QJXiGl0WHFZZ_QScRvgyDhN2GhHY,74
|
1786
|
-
vellum/workflows/graph/graph.py,sha256=
|
1789
|
+
vellum/workflows/graph/graph.py,sha256=vkpteMc2a61IFGHlrA50J4ntVj6m3agcyWrXGQEbjHc,11280
|
1787
1790
|
vellum/workflows/graph/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1788
|
-
vellum/workflows/graph/tests/test_graph.py,sha256=
|
1791
|
+
vellum/workflows/graph/tests/test_graph.py,sha256=0Pov0sCsxjzUDL9wy7xy9jFD-F2GsMJnZVEVFXzQGdM,15433
|
1789
1792
|
vellum/workflows/inputs/__init__.py,sha256=AbFEteIYEvCb14fM3EK7bhM-40-6s494rSlIhQ4Dsss,62
|
1790
1793
|
vellum/workflows/inputs/base.py,sha256=w3owT5B3rLBmIj-v-jL2l-HD4yd3hXK9RmHVd557BpA,5126
|
1791
1794
|
vellum/workflows/inputs/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -1797,7 +1800,7 @@ vellum/workflows/integrations/tests/test_mcp_service.py,sha256=q_DYrDkIqI4sQBNgI
|
|
1797
1800
|
vellum/workflows/logging.py,sha256=_a217XogktV4Ncz6xKFz7WfYmZAzkfVRVuC0rWob8ls,437
|
1798
1801
|
vellum/workflows/nodes/__init__.py,sha256=zymtc3_iW2rFmMR-sayTLuN6ZsAw8VnJweWPsjQk2-Q,1197
|
1799
1802
|
vellum/workflows/nodes/bases/__init__.py,sha256=cniHuz_RXdJ4TQgD8CBzoiKDiPxg62ErdVpCbWICX64,58
|
1800
|
-
vellum/workflows/nodes/bases/base.py,sha256=
|
1803
|
+
vellum/workflows/nodes/bases/base.py,sha256=i4tuPo_KyFI1ZJh__AI-oxRkDGch9pNTmOqt69HTcE0,20478
|
1801
1804
|
vellum/workflows/nodes/bases/base_adornment_node.py,sha256=hrgzuTetM4ynPd9YGHoK8Vwwn4XITi3aZZ_OCnQrq4Y,3433
|
1802
1805
|
vellum/workflows/nodes/bases/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1803
1806
|
vellum/workflows/nodes/bases/tests/test_base_adornment_node.py,sha256=fXZI9KqpS4XMBrBnIEkK3foHaBVvyHwYcQWWDKay7ic,1148
|
@@ -1810,7 +1813,7 @@ vellum/workflows/nodes/core/inline_subworkflow_node/node.py,sha256=TCmO0wPbt7kc8
|
|
1810
1813
|
vellum/workflows/nodes/core/inline_subworkflow_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1811
1814
|
vellum/workflows/nodes/core/inline_subworkflow_node/tests/test_node.py,sha256=RK2g1h2ib-ruQZ9A2_2L-B9WBdHV44WZj75rkDNL0cE,5766
|
1812
1815
|
vellum/workflows/nodes/core/map_node/__init__.py,sha256=MXpZYmGfhsMJHqqlpd64WiJRtbAtAMQz-_3fCU_cLV0,56
|
1813
|
-
vellum/workflows/nodes/core/map_node/node.py,sha256=
|
1816
|
+
vellum/workflows/nodes/core/map_node/node.py,sha256=kSyQmIWk4v-KSt4WBf3d-0_QueKYkjtrEmQPGTbMryw,10054
|
1814
1817
|
vellum/workflows/nodes/core/map_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1815
1818
|
vellum/workflows/nodes/core/map_node/tests/test_node.py,sha256=Xc2xZY5ShSy-bsIQe41JbvIjq3TE95duS-ygaELRVkk,9320
|
1816
1819
|
vellum/workflows/nodes/core/retry_node/__init__.py,sha256=lN2bIy5a3Uzhs_FYCrooADyYU6ZGShtvLKFWpelwPvo,60
|
@@ -1837,7 +1840,7 @@ vellum/workflows/nodes/displayable/bases/api_node/tests/test_node.py,sha256=5C59
|
|
1837
1840
|
vellum/workflows/nodes/displayable/bases/base_prompt_node/__init__.py,sha256=Org3xTvgp1pA0uUXFfnJr29D3HzCey2lEdYF4zbIUgo,70
|
1838
1841
|
vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py,sha256=ea20icDM1HB942wkH-XtXNSNCBDcjeOiN3vowkHL4fs,4477
|
1839
1842
|
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=
|
1843
|
+
vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py,sha256=qxABS2tl7gXfU350-0geBMoL25QrgyqR6-vhUAQt4Qk,14065
|
1841
1844
|
vellum/workflows/nodes/displayable/bases/inline_prompt_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1842
1845
|
vellum/workflows/nodes/displayable/bases/inline_prompt_node/tests/test_inline_prompt_node.py,sha256=Hk_u2IxLIeeqL_s0RTgoyL5QGYwY9VllKT8z5_JHiDU,24956
|
1843
1846
|
vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py,sha256=0a40fkkZkFMmZN0CsWf6EP_y1H6x36EGa3WcfVNyOsM,9797
|
@@ -1861,7 +1864,7 @@ vellum/workflows/nodes/displayable/final_output_node/node.py,sha256=6SMaGeBlHQ5r
|
|
1861
1864
|
vellum/workflows/nodes/displayable/final_output_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1862
1865
|
vellum/workflows/nodes/displayable/final_output_node/tests/test_node.py,sha256=E6LQ74qZjY4Xi4avx2qdOCgGhF8pEcNLBh8cqYRkzMI,709
|
1863
1866
|
vellum/workflows/nodes/displayable/guardrail_node/__init__.py,sha256=Ab5eXmOoBhyV4dMWdzh32HLUmnPIBEK_zFCT38C4Fng,68
|
1864
|
-
vellum/workflows/nodes/displayable/guardrail_node/node.py,sha256=
|
1867
|
+
vellum/workflows/nodes/displayable/guardrail_node/node.py,sha256=axYUojar_kdB3gi4LG3g9euJ8VkOxNtiFxJNI46v-SQ,5869
|
1865
1868
|
vellum/workflows/nodes/displayable/guardrail_node/test_node.py,sha256=SAGv6hSFcBwQkudn1VxtaKNsXSXWWELl3eK05zM6tS0,5410
|
1866
1869
|
vellum/workflows/nodes/displayable/guardrail_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1867
1870
|
vellum/workflows/nodes/displayable/guardrail_node/tests/test_node.py,sha256=X2pd6TI8miYxIa7rgvs1pHTEreyWcf77EyR0_Jsa700,2055
|
@@ -1891,13 +1894,13 @@ vellum/workflows/nodes/displayable/tests/test_search_node_error_handling.py,sha2
|
|
1891
1894
|
vellum/workflows/nodes/displayable/tests/test_search_node_wth_text_output.py,sha256=VepO5z1277c1y5N6LLIC31nnWD1aak2m5oPFplfJHHs,6935
|
1892
1895
|
vellum/workflows/nodes/displayable/tests/test_text_prompt_deployment_node.py,sha256=Bjv-wZyFgNaVZb9KEMMZd9lFoLzbPEPjEMpANizMZw4,2413
|
1893
1896
|
vellum/workflows/nodes/displayable/tool_calling_node/__init__.py,sha256=3n0-ysmFKsr40CVxPthc0rfJgqVJeZuUEsCmYudLVRg,117
|
1894
|
-
vellum/workflows/nodes/displayable/tool_calling_node/node.py,sha256=
|
1897
|
+
vellum/workflows/nodes/displayable/tool_calling_node/node.py,sha256=J4RNOggTx5nzovC0354SPGV-NkRpYnV51PMTYQ7aIQ8,8202
|
1895
1898
|
vellum/workflows/nodes/displayable/tool_calling_node/state.py,sha256=CcBVb_YtwfSSka4ze678k6-qwmzMSfjfVP8_Y95feSo,302
|
1896
1899
|
vellum/workflows/nodes/displayable/tool_calling_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1897
1900
|
vellum/workflows/nodes/displayable/tool_calling_node/tests/test_composio_service.py,sha256=in1fbEz5x1tx3uKv9YXdvOncsHucNL8Ro6Go7lBuuOQ,8962
|
1898
1901
|
vellum/workflows/nodes/displayable/tool_calling_node/tests/test_node.py,sha256=GZoeybB9uM7ai8sBLAtUMHrMVgh-WrJDWrKZci6feDs,11892
|
1899
1902
|
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=
|
1903
|
+
vellum/workflows/nodes/displayable/tool_calling_node/utils.py,sha256=c0fJA-de3yJKGTzKfjyZOkbVhIndSAhZqBZp_DpU1fg,24158
|
1901
1904
|
vellum/workflows/nodes/displayable/web_search_node/__init__.py,sha256=8FOnEP-n-U68cvxTlJW9wphIAGHq5aqjzLM-DoSSXnU,61
|
1902
1905
|
vellum/workflows/nodes/displayable/web_search_node/node.py,sha256=NQYux2bOtuBF5E4tn-fXi5y3btURPRrNqMSM9MAZYI4,5091
|
1903
1906
|
vellum/workflows/nodes/displayable/web_search_node/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -1912,7 +1915,7 @@ vellum/workflows/nodes/tests/test_mocks.py,sha256=mfPvrs75PKcsNsbJLQAN6PDFoVqs9T
|
|
1912
1915
|
vellum/workflows/nodes/tests/test_utils.py,sha256=BUugAHx2C9YuCwTlsTXV1Glxca0kW3St6T9o_QFatSU,5649
|
1913
1916
|
vellum/workflows/nodes/utils.py,sha256=wCvf8K5qruT5GwtvnHcQ-LMllktTD8aaFmAGpKQy--c,10720
|
1914
1917
|
vellum/workflows/outputs/__init__.py,sha256=AyZ4pRh_ACQIGvkf0byJO46EDnSix1ZCAXfvh-ms1QE,94
|
1915
|
-
vellum/workflows/outputs/base.py,sha256=
|
1918
|
+
vellum/workflows/outputs/base.py,sha256=zy02zr9DmG3j7Xp3Q8xiOiXFF_c7uNh76jf2LiMS-qE,10132
|
1916
1919
|
vellum/workflows/ports/__init__.py,sha256=bZuMt-R7z5bKwpu4uPW7LlJeePOQWmCcDSXe5frUY5g,101
|
1917
1920
|
vellum/workflows/ports/node_ports.py,sha256=SM9uLAaoaE1HwR-Uqwf2v5zZK5iFnphKs6mE5Ls7ldE,2877
|
1918
1921
|
vellum/workflows/ports/port.py,sha256=PYhmzEHgJyjUjSFkPIQ38cNIKpcXrhYiZlj7nZC5yCk,3989
|
@@ -1925,7 +1928,7 @@ vellum/workflows/references/external_input.py,sha256=c_4SojTpykCSbGS1Pjmx9FfquyY
|
|
1925
1928
|
vellum/workflows/references/input.py,sha256=3INu-TLTi4dziWmva6LO3WvgDlPzsjayUx61cVvqLJA,325
|
1926
1929
|
vellum/workflows/references/lazy.py,sha256=jgUYmgt-yAybzPf_R-74MzdU8VuNwMYI8EQqrj9lVR0,2948
|
1927
1930
|
vellum/workflows/references/node.py,sha256=LP854wDVs-9I_aZ7-nkbwXqL2H7W2_3LED2e9FixNS8,1418
|
1928
|
-
vellum/workflows/references/output.py,sha256=
|
1931
|
+
vellum/workflows/references/output.py,sha256=qVa3QmlOyJhyIMOHwOmi1RgHPAOryhdPr2DRj97aEvU,3321
|
1929
1932
|
vellum/workflows/references/state_value.py,sha256=bInUF0A3Pt4-zhA0f6LdSuyv8tz7n5QRkHAEn4gsmqI,711
|
1930
1933
|
vellum/workflows/references/tests/test_lazy.py,sha256=0s50-LizMTlSTBQahpK0fg_xqCucA8YTp6QmIMqPvMk,919
|
1931
1934
|
vellum/workflows/references/vellum_secret.py,sha256=Od4d19a5yletWMqNfJR5d_mZQUkVcFzj29mE-T9J7yE,480
|
@@ -1962,11 +1965,11 @@ vellum/workflows/types/utils.py,sha256=mTctHITBybpt4855x32oCKALBEcMNLn-9cCmfEKgJ
|
|
1962
1965
|
vellum/workflows/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1963
1966
|
vellum/workflows/utils/functions.py,sha256=6WRRMb_XbxtvhUKOJq5ZChy0KKvlBaQCBiPhvecXT7I,10029
|
1964
1967
|
vellum/workflows/utils/hmac.py,sha256=JJCczc6pyV6DuE1Oa0QVfYPUN_of3zEYmGFib3OZnrE,1135
|
1965
|
-
vellum/workflows/utils/names.py,sha256=
|
1968
|
+
vellum/workflows/utils/names.py,sha256=QtHquoaGqRseu5gg2OcVGI2d_CMcEOvjb9KspwH4C-A,552
|
1966
1969
|
vellum/workflows/utils/pydantic_schema.py,sha256=eR_bBtY-T0pttJP-ARwagSdCOnwPUtiT3cegm2lzDTQ,1310
|
1967
1970
|
vellum/workflows/utils/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1968
1971
|
vellum/workflows/utils/tests/test_functions.py,sha256=TlNhD0OBJ-aeYn4qUqmApuajkx9sJx0lIQRMfT8Xf0w,23688
|
1969
|
-
vellum/workflows/utils/tests/test_names.py,sha256=
|
1972
|
+
vellum/workflows/utils/tests/test_names.py,sha256=DnRRnuORxQXx9ESegCzkxiWcHy2_bBi7lXgbFi3FZK8,757
|
1970
1973
|
vellum/workflows/utils/tests/test_uuids.py,sha256=i77ABQ0M3S-aFLzDXHJq_yr5FPkJEWCMBn1HJ3DObrE,437
|
1971
1974
|
vellum/workflows/utils/tests/test_vellum_variables.py,sha256=vbnKgm41aB5OXlO-ZIPbhQ6xDiZkT8KMxCLqz4zocWY,1791
|
1972
1975
|
vellum/workflows/utils/uuids.py,sha256=IaZQANz7fhF7la0_J1O50Y6D2PIYv_taRDTRzBT9aWw,1284
|
@@ -1974,13 +1977,13 @@ vellum/workflows/utils/vellum_variables.py,sha256=YHLNiQGWDNssGH1FQoG9Z1jUFZ-zYe
|
|
1974
1977
|
vellum/workflows/utils/zip.py,sha256=HVg_YZLmBOTXKaDV3Xhaf3V6sYnfqqZXQ8CpuafkbPY,1181
|
1975
1978
|
vellum/workflows/vellum_client.py,sha256=xkfoucodxNK5JR2-lbRqZx3xzDgExWkP6kySrpi_Ubc,1079
|
1976
1979
|
vellum/workflows/workflows/__init__.py,sha256=KY45TqvavCCvXIkyCFMEc0dc6jTMOUci93U2DUrlZYc,66
|
1977
|
-
vellum/workflows/workflows/base.py,sha256=
|
1980
|
+
vellum/workflows/workflows/base.py,sha256=4_-kEebUBCn7d4N0TxF-sQejM2gAF8N6FbGabV2t6Pw,28346
|
1978
1981
|
vellum/workflows/workflows/event_filters.py,sha256=GSxIgwrX26a1Smfd-6yss2abGCnadGsrSZGa7t7LpJA,2008
|
1979
1982
|
vellum/workflows/workflows/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
1980
1983
|
vellum/workflows/workflows/tests/test_base_workflow.py,sha256=ptMntHzVyy8ZuzNgeTuk7hREgKQ5UBdgq8VJFSGaW4Y,20832
|
1981
1984
|
vellum/workflows/workflows/tests/test_context.py,sha256=VJBUcyWVtMa_lE5KxdhgMu0WYNYnUQUDvTF7qm89hJ0,2333
|
1982
|
-
vellum_ai-1.3.
|
1983
|
-
vellum_ai-1.3.
|
1984
|
-
vellum_ai-1.3.
|
1985
|
-
vellum_ai-1.3.
|
1986
|
-
vellum_ai-1.3.
|
1985
|
+
vellum_ai-1.3.6.dist-info/LICENSE,sha256=hOypcdt481qGNISA784bnAGWAE6tyIf9gc2E78mYC3E,1574
|
1986
|
+
vellum_ai-1.3.6.dist-info/METADATA,sha256=zNFEQuOlfP7i6yEReP2nVPoXRzJ6wvJWmK_OAs8oz1Q,5547
|
1987
|
+
vellum_ai-1.3.6.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
1988
|
+
vellum_ai-1.3.6.dist-info/entry_points.txt,sha256=HCH4yc_V3J_nDv3qJzZ_nYS8llCHZViCDP1ejgCc5Ak,42
|
1989
|
+
vellum_ai-1.3.6.dist-info/RECORD,,
|
@@ -100,3 +100,20 @@ def test_serialize_display_data():
|
|
100
100
|
"position": {"x": 0.0, "y": 0.0},
|
101
101
|
"comment": {"expanded": True, "value": "I hope this works"},
|
102
102
|
}
|
103
|
+
|
104
|
+
|
105
|
+
def test_serialize_node_label_with_pascal_case():
|
106
|
+
"""
|
107
|
+
Tests that a node with PascalCase name serializes with proper title case label.
|
108
|
+
"""
|
109
|
+
|
110
|
+
# GIVEN a node with a PascalCase name that includes common patterns
|
111
|
+
class MyCustomNode(BaseNode):
|
112
|
+
pass
|
113
|
+
|
114
|
+
# WHEN we serialize the node
|
115
|
+
node_display_class = get_node_display_class(MyCustomNode)
|
116
|
+
data = node_display_class().serialize(WorkflowDisplayContext())
|
117
|
+
|
118
|
+
# THEN the label should be converted to proper title case with spaces
|
119
|
+
assert data["label"] == "My Custom Node"
|
@@ -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}
|
vellum_ee/workflows/display/tests/workflow_serialization/test_basic_api_node_serialization.py
CHANGED
@@ -173,7 +173,7 @@ def test_serialize_workflow(vellum_client):
|
|
173
173
|
},
|
174
174
|
],
|
175
175
|
"data": {
|
176
|
-
"label": "Simple
|
176
|
+
"label": "Simple API Node",
|
177
177
|
"error_output_id": None,
|
178
178
|
"source_handle_id": "7c33b4d3-9204-4bd5-9371-80ee34f83073",
|
179
179
|
"target_handle_id": "14b538a5-aedb-41f3-b579-039956b7c7ed",
|
@@ -0,0 +1,89 @@
|
|
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
|
+
"constant_input": {
|
77
|
+
"type": "CONSTANT_VALUE",
|
78
|
+
"value": {"type": "STRING", "value": "constant_input"},
|
79
|
+
},
|
80
|
+
},
|
81
|
+
"forced": None,
|
82
|
+
"strict": None,
|
83
|
+
},
|
84
|
+
"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 constant_input="constant_input",\n)\ndef get_string(parent_input: str, dummy_input: str, constant_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"parent input: {parent_input}, dummy input: {dummy_input}, constant input: {constant_input}, populated input: {populated_input}" # noqa: E501\n', # noqa: E501
|
85
|
+
}
|
86
|
+
],
|
87
|
+
},
|
88
|
+
},
|
89
|
+
}
|
@@ -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
|
@@ -7,6 +7,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, cast
|
|
7
7
|
from pydantic import BaseModel
|
8
8
|
|
9
9
|
from vellum.client.types.logical_operator import LogicalOperator
|
10
|
+
from vellum.workflows.constants import undefined
|
10
11
|
from vellum.workflows.descriptors.base import BaseDescriptor
|
11
12
|
from vellum.workflows.expressions.accessor import AccessorExpression
|
12
13
|
from vellum.workflows.expressions.add import AddExpression
|
@@ -243,7 +244,14 @@ def serialize_key(key: Any) -> str:
|
|
243
244
|
return str(key)
|
244
245
|
|
245
246
|
|
247
|
+
# Sentinel value to indicate a value should be omitted from serialization
|
248
|
+
_UNDEFINED_SENTINEL: JsonObject = {"__undefined__": True}
|
249
|
+
|
250
|
+
|
246
251
|
def serialize_value(display_context: "WorkflowDisplayContext", value: Any) -> JsonObject:
|
252
|
+
if value is undefined:
|
253
|
+
return _UNDEFINED_SENTINEL
|
254
|
+
|
247
255
|
if isinstance(value, ConstantValueReference):
|
248
256
|
return serialize_value(display_context, value._value)
|
249
257
|
|
@@ -314,7 +322,12 @@ def serialize_value(display_context: "WorkflowDisplayContext", value: Any) -> Js
|
|
314
322
|
}
|
315
323
|
|
316
324
|
if isinstance(value, list):
|
317
|
-
serialized_items = [
|
325
|
+
serialized_items = []
|
326
|
+
for item in value:
|
327
|
+
serialized_item = serialize_value(display_context, item)
|
328
|
+
if serialized_item != _UNDEFINED_SENTINEL:
|
329
|
+
serialized_items.append(serialized_item)
|
330
|
+
|
318
331
|
if all(isinstance(item, dict) and item["type"] == "CONSTANT_VALUE" for item in serialized_items):
|
319
332
|
constant_values = []
|
320
333
|
for item in serialized_items:
|
@@ -345,14 +358,17 @@ def serialize_value(display_context: "WorkflowDisplayContext", value: Any) -> Js
|
|
345
358
|
return serialize_value(display_context, dict_value)
|
346
359
|
|
347
360
|
if isinstance(value, dict):
|
348
|
-
serialized_entries: List[Dict[str, Any]] = [
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
361
|
+
serialized_entries: List[Dict[str, Any]] = []
|
362
|
+
for key, val in value.items():
|
363
|
+
serialized_val = serialize_value(display_context, val)
|
364
|
+
if serialized_val != _UNDEFINED_SENTINEL:
|
365
|
+
serialized_entries.append(
|
366
|
+
{
|
367
|
+
"id": str(uuid4_from_hash(f"{key}|{val}")),
|
368
|
+
"key": serialize_key(key),
|
369
|
+
"value": serialized_val,
|
370
|
+
}
|
371
|
+
)
|
356
372
|
|
357
373
|
# Check if all entries have constant values
|
358
374
|
if all(entry["value"]["type"] == "CONSTANT_VALUE" for entry in serialized_entries):
|
@@ -422,6 +438,23 @@ def serialize_value(display_context: "WorkflowDisplayContext", value: Any) -> Js
|
|
422
438
|
|
423
439
|
if callable(value):
|
424
440
|
function_definition = compile_function_definition(value)
|
441
|
+
|
442
|
+
name = function_definition.name
|
443
|
+
description = function_definition.description or ""
|
444
|
+
|
445
|
+
inputs = getattr(value, "__vellum_inputs__", {})
|
446
|
+
|
447
|
+
if inputs:
|
448
|
+
serialized_inputs = {}
|
449
|
+
for param_name, input_ref in inputs.items():
|
450
|
+
serialized_inputs[param_name] = serialize_value(display_context, input_ref)
|
451
|
+
|
452
|
+
model_data = function_definition.model_dump()
|
453
|
+
model_data["inputs"] = serialized_inputs
|
454
|
+
function_definition_data = model_data
|
455
|
+
else:
|
456
|
+
function_definition_data = function_definition.model_dump()
|
457
|
+
|
425
458
|
source_path = inspect.getsourcefile(value)
|
426
459
|
if source_path is not None:
|
427
460
|
with virtual_open(source_path) as f:
|
@@ -435,9 +468,9 @@ def serialize_value(display_context: "WorkflowDisplayContext", value: Any) -> Js
|
|
435
468
|
"type": "JSON",
|
436
469
|
"value": {
|
437
470
|
"type": "CODE_EXECUTION",
|
438
|
-
"name":
|
439
|
-
"description":
|
440
|
-
"definition":
|
471
|
+
"name": name,
|
472
|
+
"description": description,
|
473
|
+
"definition": function_definition_data,
|
441
474
|
"src": source_code,
|
442
475
|
},
|
443
476
|
},
|
@@ -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(
|
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.
|
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
|
@@ -688,10 +688,8 @@ 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:
|
692
|
-
|
693
|
-
|
694
|
-
return WorkflowOutputDisplay(id=output_id, name=output.name)
|
691
|
+
def _generate_workflow_output_display(self, output: OutputReference) -> WorkflowOutputDisplay:
|
692
|
+
return WorkflowOutputDisplay(id=output.id, name=output.name)
|
695
693
|
|
696
694
|
def __init_subclass__(cls, **kwargs: Any) -> None:
|
697
695
|
super().__init_subclass__(**kwargs)
|
File without changes
|
File without changes
|
File without changes
|