vellum-ai 0.14.73__py3-none-any.whl → 0.14.75__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/core/client_wrapper.py +1 -1
- vellum/client/core/serialization.py +0 -1
- vellum/client/reference.md +9 -1
- vellum/client/resources/prompts/client.py +16 -4
- vellum/client/types/secret_type_enum.py +3 -1
- vellum/workflows/nodes/bases/base.py +0 -15
- vellum/workflows/nodes/core/map_node/tests/test_node.py +54 -0
- vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +2 -2
- vellum/workflows/nodes/displayable/bases/utils.py +2 -0
- vellum/workflows/nodes/experimental/tool_calling_node/node.py +5 -1
- vellum/workflows/nodes/experimental/tool_calling_node/tests/test_node.py +13 -0
- vellum/workflows/nodes/experimental/tool_calling_node/utils.py +14 -4
- vellum/workflows/outputs/base.py +26 -2
- vellum/workflows/state/encoder.py +2 -0
- vellum/workflows/types/definition.py +1 -1
- vellum/workflows/types/generics.py +5 -0
- vellum/workflows/utils/functions.py +3 -3
- vellum/workflows/utils/tests/test_functions.py +7 -7
- {vellum_ai-0.14.73.dist-info → vellum_ai-0.14.75.dist-info}/METADATA +1 -1
- {vellum_ai-0.14.73.dist-info → vellum_ai-0.14.75.dist-info}/RECORD +33 -32
- vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +36 -7
- vellum_ee/workflows/display/nodes/vellum/tests/test_prompt_node.py +102 -0
- vellum_ee/workflows/display/tests/workflow_serialization/generic_nodes/test_attributes_serialization.py +90 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_inline_prompt_node_serialization.py +117 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_inline_workflow_serialization.py +7 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_serialization.py +7 -0
- vellum_ee/workflows/display/tests/workflow_serialization/test_basic_tool_calling_node_workflow_deployment_serialization.py +62 -0
- vellum_ee/workflows/display/utils/expressions.py +33 -2
- vellum_ee/workflows/display/workflows/base_workflow_display.py +20 -6
- vellum_ee/workflows/tests/test_display_meta.py +41 -0
- {vellum_ai-0.14.73.dist-info → vellum_ai-0.14.75.dist-info}/LICENSE +0 -0
- {vellum_ai-0.14.73.dist-info → vellum_ai-0.14.75.dist-info}/WHEEL +0 -0
- {vellum_ai-0.14.73.dist-info → vellum_ai-0.14.75.dist-info}/entry_points.txt +0 -0
@@ -240,6 +240,27 @@ def test_serialize_workflow():
|
|
240
240
|
},
|
241
241
|
},
|
242
242
|
},
|
243
|
+
{
|
244
|
+
"id": "2b98319f-f43d-42d9-a8b0-b148d5de0a2c",
|
245
|
+
"name": "parameters",
|
246
|
+
"value": {
|
247
|
+
"type": "CONSTANT_VALUE",
|
248
|
+
"value": {
|
249
|
+
"type": "JSON",
|
250
|
+
"value": {
|
251
|
+
"stop": [],
|
252
|
+
"temperature": 0.0,
|
253
|
+
"max_tokens": 4096,
|
254
|
+
"top_p": 1.0,
|
255
|
+
"top_k": 0,
|
256
|
+
"frequency_penalty": 0.0,
|
257
|
+
"presence_penalty": 0.0,
|
258
|
+
"logit_bias": None,
|
259
|
+
"custom_parameters": None,
|
260
|
+
},
|
261
|
+
},
|
262
|
+
},
|
263
|
+
},
|
243
264
|
],
|
244
265
|
},
|
245
266
|
prompt_node,
|
@@ -437,3 +458,99 @@ def test_serialize_workflow_with_descriptor_blocks():
|
|
437
458
|
},
|
438
459
|
}
|
439
460
|
]
|
461
|
+
|
462
|
+
|
463
|
+
def test_serialize_workflow_with_nested_descriptor_blocks():
|
464
|
+
"""Test that serialization handles BaseDescriptor instances nested in ChatMessageBlock.blocks."""
|
465
|
+
|
466
|
+
class TestInputs(BaseInputs):
|
467
|
+
noun: str
|
468
|
+
|
469
|
+
class UpstreamNode(BaseNode):
|
470
|
+
class Outputs(BaseNode.Outputs):
|
471
|
+
results: list
|
472
|
+
|
473
|
+
def run(self) -> Outputs:
|
474
|
+
return self.Outputs(results=["test"])
|
475
|
+
|
476
|
+
chat_block = ChatMessagePromptBlock(chat_role="SYSTEM", blocks=[JinjaPromptBlock(template="Hello")])
|
477
|
+
|
478
|
+
class TestInlinePromptNodeWithNestedDescriptorBlocks(InlinePromptNode):
|
479
|
+
ml_model = "gpt-4o"
|
480
|
+
blocks = [chat_block]
|
481
|
+
prompt_inputs = {"noun": TestInputs.noun}
|
482
|
+
|
483
|
+
object.__setattr__(chat_block, "blocks", [UpstreamNode.Outputs.results[0]])
|
484
|
+
|
485
|
+
class TestWorkflow(BaseWorkflow[TestInputs, BaseState]):
|
486
|
+
graph = UpstreamNode >> TestInlinePromptNodeWithNestedDescriptorBlocks
|
487
|
+
|
488
|
+
workflow_display = get_workflow_display(workflow_class=TestWorkflow)
|
489
|
+
serialized: dict = workflow_display.serialize()
|
490
|
+
|
491
|
+
prompt_nodes = [node for node in serialized["workflow_raw_data"]["nodes"] if node["type"] == "PROMPT"]
|
492
|
+
prompt_node = prompt_nodes[0]
|
493
|
+
|
494
|
+
blocks = prompt_node["data"]["exec_config"]["prompt_template_block_data"]["blocks"]
|
495
|
+
descriptor_blocks = [block for block in blocks if not isinstance(block, dict) or not block.get("block_type")]
|
496
|
+
assert len(descriptor_blocks) == 0, "BaseDescriptor blocks should not appear in serialized blocks"
|
497
|
+
|
498
|
+
blocks_attr = next((attr for attr in prompt_node["attributes"] if attr["name"] == "blocks"), None)
|
499
|
+
assert blocks_attr is not None, "blocks attribute should be present when blocks contain nested BaseDescriptor"
|
500
|
+
assert blocks_attr["value"]["type"] == "ARRAY_REFERENCE", "blocks attribute should be serialized as ARRAY_REFERENCE"
|
501
|
+
assert blocks_attr["value"]["items"] == [
|
502
|
+
{
|
503
|
+
"entries": [
|
504
|
+
{
|
505
|
+
"id": "24a203be-3cba-4b20-bc84-9993a476c120",
|
506
|
+
"key": "block_type",
|
507
|
+
"value": {"type": "CONSTANT_VALUE", "value": {"type": "STRING", "value": "CHAT_MESSAGE"}},
|
508
|
+
},
|
509
|
+
{
|
510
|
+
"id": "c06269e6-f74c-4860-8fa5-22dcbdc89399",
|
511
|
+
"key": "state",
|
512
|
+
"value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
|
513
|
+
},
|
514
|
+
{
|
515
|
+
"id": "dd9c0d43-b931-4dc8-8b3a-a7507ddff0c1",
|
516
|
+
"key": "cache_config",
|
517
|
+
"value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
|
518
|
+
},
|
519
|
+
{
|
520
|
+
"id": "bef22f2b-0b6e-4910-88cc-6df736d2e20e",
|
521
|
+
"key": "chat_role",
|
522
|
+
"value": {"type": "CONSTANT_VALUE", "value": {"type": "STRING", "value": "SYSTEM"}},
|
523
|
+
},
|
524
|
+
{
|
525
|
+
"id": "c0beec30-f85e-4a78-a3fb-baee54a692f8",
|
526
|
+
"key": "chat_source",
|
527
|
+
"value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
|
528
|
+
},
|
529
|
+
{
|
530
|
+
"id": "f601f4f2-62fe-4697-9fe0-99ca8aa64500",
|
531
|
+
"key": "chat_message_unterminated",
|
532
|
+
"value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
|
533
|
+
},
|
534
|
+
{
|
535
|
+
"id": "ad550008-64e3-44a3-a32a-84ec226db31c",
|
536
|
+
"key": "blocks",
|
537
|
+
"value": {
|
538
|
+
"items": [
|
539
|
+
{
|
540
|
+
"lhs": {
|
541
|
+
"node_id": "9fe5d3a3-7d26-4692-aa2d-e67c673b0c2b",
|
542
|
+
"node_output_id": "92f9a1b7-d33b-4f00-b4c2-e6f58150e166",
|
543
|
+
"type": "NODE_OUTPUT",
|
544
|
+
},
|
545
|
+
"operator": "accessField",
|
546
|
+
"rhs": {"type": "CONSTANT_VALUE", "value": {"type": "NUMBER", "value": 0.0}},
|
547
|
+
"type": "BINARY_EXPRESSION",
|
548
|
+
}
|
549
|
+
],
|
550
|
+
"type": "ARRAY_REFERENCE",
|
551
|
+
},
|
552
|
+
},
|
553
|
+
],
|
554
|
+
"type": "DICTIONARY_REFERENCE",
|
555
|
+
}
|
556
|
+
]
|
@@ -131,6 +131,8 @@ def test_serialize_workflow():
|
|
131
131
|
"value": [
|
132
132
|
{
|
133
133
|
"type": "INLINE_WORKFLOW",
|
134
|
+
"name": "BasicInlineSubworkflowWorkflow",
|
135
|
+
"description": "\n A workflow that gets the weather for a given city and date.\n ", # noqa: E501
|
134
136
|
"exec_config": {
|
135
137
|
"workflow_raw_data": {
|
136
138
|
"nodes": [
|
@@ -410,6 +412,11 @@ def test_serialize_workflow():
|
|
410
412
|
"name": "function_configs",
|
411
413
|
"value": {"type": "CONSTANT_VALUE", "value": {"type": "JSON", "value": None}},
|
412
414
|
},
|
415
|
+
{
|
416
|
+
"id": "1668419e-a193-43a5-8a97-3394e89bf278",
|
417
|
+
"name": "max_prompt_iterations",
|
418
|
+
"value": {"type": "CONSTANT_VALUE", "value": {"type": "NUMBER", "value": 5.0}},
|
419
|
+
},
|
413
420
|
],
|
414
421
|
"outputs": [
|
415
422
|
{"id": "e62bc785-a914-4066-b79e-8c89a5d0ec6c", "name": "text", "type": "STRING", "value": None},
|
@@ -131,6 +131,8 @@ def test_serialize_workflow():
|
|
131
131
|
"value": [
|
132
132
|
{
|
133
133
|
"type": "CODE_EXECUTION",
|
134
|
+
"name": "get_current_weather",
|
135
|
+
"description": "\n Get the current weather in a given location.\n ",
|
134
136
|
"definition": {
|
135
137
|
"state": None,
|
136
138
|
"cache_config": None,
|
@@ -183,6 +185,11 @@ def test_serialize_workflow():
|
|
183
185
|
],
|
184
186
|
},
|
185
187
|
},
|
188
|
+
{
|
189
|
+
"id": "1668419e-a193-43a5-8a97-3394e89bf278",
|
190
|
+
"name": "max_prompt_iterations",
|
191
|
+
"value": {"type": "CONSTANT_VALUE", "value": {"type": "NUMBER", "value": 5.0}},
|
192
|
+
},
|
186
193
|
],
|
187
194
|
"outputs": [
|
188
195
|
{"id": "e62bc785-a914-4066-b79e-8c89a5d0ec6c", "name": "text", "type": "STRING", "value": None},
|
@@ -0,0 +1,62 @@
|
|
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_workflow_deployment.workflow import (
|
6
|
+
BasicToolCallingNodeWorkflowDeploymentWorkflow,
|
7
|
+
)
|
8
|
+
|
9
|
+
|
10
|
+
def test_serialize_workflow():
|
11
|
+
# GIVEN a Workflow that uses a generic node
|
12
|
+
# WHEN we serialize it
|
13
|
+
workflow_display = get_workflow_display(workflow_class=BasicToolCallingNodeWorkflowDeploymentWorkflow)
|
14
|
+
|
15
|
+
serialized_workflow: dict = workflow_display.serialize()
|
16
|
+
# THEN we should get a serialized representation of the Workflow
|
17
|
+
assert serialized_workflow.keys() == {
|
18
|
+
"workflow_raw_data",
|
19
|
+
"input_variables",
|
20
|
+
"state_variables",
|
21
|
+
"output_variables",
|
22
|
+
}
|
23
|
+
|
24
|
+
# AND its input variables should be what we expect
|
25
|
+
input_variables = serialized_workflow["input_variables"]
|
26
|
+
assert len(input_variables) == 1
|
27
|
+
|
28
|
+
# AND its output variables should be what we expect
|
29
|
+
output_variables = serialized_workflow["output_variables"]
|
30
|
+
assert len(output_variables) == 2
|
31
|
+
assert not DeepDiff(
|
32
|
+
[
|
33
|
+
{"id": "3626cfa6-76f8-465e-b156-c809e3a2cee9", "key": "text", "type": "STRING"},
|
34
|
+
{"id": "cb82117a-d649-4c3e-9342-c552028fa2ad", "key": "chat_history", "type": "CHAT_HISTORY"},
|
35
|
+
],
|
36
|
+
output_variables,
|
37
|
+
ignore_order=True,
|
38
|
+
)
|
39
|
+
|
40
|
+
# AND its raw data should be what we expect
|
41
|
+
workflow_raw_data = serialized_workflow["workflow_raw_data"]
|
42
|
+
tool_calling_node = workflow_raw_data["nodes"][1]
|
43
|
+
function_attributes = next(attr for attr in tool_calling_node["attributes"] if attr["name"] == "functions")
|
44
|
+
assert function_attributes == {
|
45
|
+
"id": "73a94e3c-1935-4308-a68a-ecd5441804b7",
|
46
|
+
"name": "functions",
|
47
|
+
"value": {
|
48
|
+
"type": "CONSTANT_VALUE",
|
49
|
+
"value": {
|
50
|
+
"type": "JSON",
|
51
|
+
"value": [
|
52
|
+
{
|
53
|
+
"type": "WORKFLOW_DEPLOYMENT",
|
54
|
+
"name": "deployment_1",
|
55
|
+
"description": "Workflow deployment for deployment_1",
|
56
|
+
"deployment": "deployment_1",
|
57
|
+
"release_tag": "LATEST",
|
58
|
+
}
|
59
|
+
],
|
60
|
+
},
|
61
|
+
},
|
62
|
+
}
|
@@ -1,5 +1,8 @@
|
|
1
|
+
from dataclasses import asdict, is_dataclass
|
1
2
|
from typing import TYPE_CHECKING, Any, Dict, List, cast
|
2
3
|
|
4
|
+
from pydantic import BaseModel
|
5
|
+
|
3
6
|
from vellum.client.types.logical_operator import LogicalOperator
|
4
7
|
from vellum.workflows.descriptors.base import BaseDescriptor
|
5
8
|
from vellum.workflows.expressions.accessor import AccessorExpression
|
@@ -39,6 +42,7 @@ from vellum.workflows.references.state_value import StateValueReference
|
|
39
42
|
from vellum.workflows.references.vellum_secret import VellumSecretReference
|
40
43
|
from vellum.workflows.references.workflow_input import WorkflowInputReference
|
41
44
|
from vellum.workflows.types.core import JsonArray, JsonObject
|
45
|
+
from vellum.workflows.types.definition import DeploymentDefinition
|
42
46
|
from vellum.workflows.types.generics import is_workflow_class
|
43
47
|
from vellum.workflows.utils.uuids import uuid4_from_hash
|
44
48
|
from vellum_ee.workflows.display.utils.exceptions import UnsupportedSerializationException
|
@@ -290,6 +294,10 @@ def serialize_value(display_context: "WorkflowDisplayContext", value: Any) -> Js
|
|
290
294
|
"items": cast(JsonArray, serialized_items), # list[JsonObject] -> JsonArray
|
291
295
|
}
|
292
296
|
|
297
|
+
if is_dataclass(value) and not isinstance(value, type):
|
298
|
+
dict_value = asdict(value)
|
299
|
+
return serialize_value(display_context, dict_value)
|
300
|
+
|
293
301
|
if isinstance(value, dict):
|
294
302
|
serialized_entries: List[Dict[str, Any]] = [
|
295
303
|
{
|
@@ -321,18 +329,41 @@ def serialize_value(display_context: "WorkflowDisplayContext", value: Any) -> Js
|
|
321
329
|
from vellum_ee.workflows.display.workflows.get_vellum_workflow_display_class import get_workflow_display
|
322
330
|
|
323
331
|
workflow_display = get_workflow_display(workflow_class=value)
|
324
|
-
|
332
|
+
serialized_value: dict = workflow_display.serialize()
|
333
|
+
name = serialized_value["workflow_raw_data"]["definition"]["name"]
|
334
|
+
description = value.__doc__ or ""
|
325
335
|
return {
|
326
336
|
"type": "CONSTANT_VALUE",
|
327
337
|
"value": {
|
328
338
|
"type": "JSON",
|
329
339
|
"value": {
|
330
340
|
"type": "INLINE_WORKFLOW",
|
331
|
-
"
|
341
|
+
"name": name,
|
342
|
+
"description": description,
|
343
|
+
"exec_config": serialized_value,
|
344
|
+
},
|
345
|
+
},
|
346
|
+
}
|
347
|
+
|
348
|
+
if isinstance(value, DeploymentDefinition):
|
349
|
+
return {
|
350
|
+
"type": "CONSTANT_VALUE",
|
351
|
+
"value": {
|
352
|
+
"type": "JSON",
|
353
|
+
"value": {
|
354
|
+
"type": "WORKFLOW_DEPLOYMENT",
|
355
|
+
"name": value.deployment,
|
356
|
+
"description": f"Workflow deployment for {value.deployment}",
|
357
|
+
"deployment": value.deployment,
|
358
|
+
"release_tag": value.release_tag,
|
332
359
|
},
|
333
360
|
},
|
334
361
|
}
|
335
362
|
|
363
|
+
if isinstance(value, BaseModel):
|
364
|
+
dict_value = value.model_dump()
|
365
|
+
return serialize_value(display_context, dict_value)
|
366
|
+
|
336
367
|
if not isinstance(value, BaseDescriptor):
|
337
368
|
vellum_value = primitive_to_vellum_value(value)
|
338
369
|
return {
|
@@ -690,8 +690,7 @@ class BaseWorkflowDisplay(Generic[WorkflowType]):
|
|
690
690
|
try:
|
691
691
|
display_module = importlib.import_module(full_workflow_display_module_path)
|
692
692
|
except ModuleNotFoundError:
|
693
|
-
|
694
|
-
return None
|
693
|
+
return BaseWorkflowDisplay._gather_event_display_context_from_workflow_crawling(module_path, workflow_class)
|
695
694
|
|
696
695
|
WorkflowDisplayClass: Optional[Type[BaseWorkflowDisplay]] = None
|
697
696
|
for name, definition in display_module.__dict__.items():
|
@@ -708,11 +707,26 @@ class BaseWorkflowDisplay(Generic[WorkflowType]):
|
|
708
707
|
WorkflowDisplayClass = definition
|
709
708
|
break
|
710
709
|
|
711
|
-
if
|
712
|
-
|
713
|
-
|
710
|
+
if WorkflowDisplayClass:
|
711
|
+
return WorkflowDisplayClass().get_event_display_context()
|
712
|
+
|
713
|
+
return BaseWorkflowDisplay._gather_event_display_context_from_workflow_crawling(module_path, workflow_class)
|
714
|
+
|
715
|
+
@staticmethod
|
716
|
+
def _gather_event_display_context_from_workflow_crawling(
|
717
|
+
module_path: str,
|
718
|
+
workflow_class: Optional[Type[BaseWorkflow]] = None,
|
719
|
+
) -> Union[WorkflowEventDisplayContext, None]:
|
720
|
+
try:
|
721
|
+
if workflow_class is None:
|
722
|
+
workflow_class = BaseWorkflow.load_from_module(module_path)
|
714
723
|
|
715
|
-
|
724
|
+
workflow_display = get_workflow_display(workflow_class=workflow_class)
|
725
|
+
return workflow_display.get_event_display_context()
|
726
|
+
|
727
|
+
except ModuleNotFoundError:
|
728
|
+
logger.exception("Failed to load workflow from module %s", module_path)
|
729
|
+
return None
|
716
730
|
|
717
731
|
def get_event_display_context(self):
|
718
732
|
display_context = self.display_context
|
@@ -4,6 +4,7 @@ import sys
|
|
4
4
|
from uuid import uuid4
|
5
5
|
|
6
6
|
from vellum.workflows import BaseWorkflow
|
7
|
+
from vellum.workflows.events.workflow import WorkflowEventDisplayContext
|
7
8
|
from vellum_ee.workflows.display.workflows import BaseWorkflowDisplay
|
8
9
|
from vellum_ee.workflows.server.virtual_file_loader import VirtualFileFinder
|
9
10
|
|
@@ -102,3 +103,43 @@ class MyCustomWorkflowDisplay(BaseWorkflowDisplay[MyCustomWorkflow]):
|
|
102
103
|
assert display_meta.workflow_outputs == {
|
103
104
|
"answer": workflow_output_id,
|
104
105
|
}
|
106
|
+
|
107
|
+
|
108
|
+
def test_gather_event_display_context__workflow_crawling_without_display_module():
|
109
|
+
# GIVEN a workflow module without a display module
|
110
|
+
files = {
|
111
|
+
"__init__.py": "",
|
112
|
+
"workflow.py": """\
|
113
|
+
from vellum.workflows import BaseWorkflow
|
114
|
+
from vellum.workflows.nodes import BaseNode
|
115
|
+
|
116
|
+
class TestNode(BaseNode):
|
117
|
+
class Outputs(BaseNode.Outputs):
|
118
|
+
result: str
|
119
|
+
|
120
|
+
class TestWorkflow(BaseWorkflow):
|
121
|
+
graph = TestNode
|
122
|
+
|
123
|
+
class Outputs(BaseWorkflow.Outputs):
|
124
|
+
final_result = TestNode.Outputs.result
|
125
|
+
""",
|
126
|
+
}
|
127
|
+
|
128
|
+
namespace = str(uuid4())
|
129
|
+
|
130
|
+
# AND the virtual file loader is registered
|
131
|
+
sys.meta_path.append(VirtualFileFinder(files, namespace))
|
132
|
+
|
133
|
+
# WHEN the workflow display context is gathered
|
134
|
+
display_meta = BaseWorkflowDisplay.gather_event_display_context(namespace)
|
135
|
+
|
136
|
+
# THEN the workflow display context should be successfully created via workflow crawling
|
137
|
+
assert display_meta is not None
|
138
|
+
assert isinstance(display_meta, WorkflowEventDisplayContext)
|
139
|
+
|
140
|
+
# AND the node displays should be populated with the correct node structure
|
141
|
+
assert len(display_meta.node_displays) == 1
|
142
|
+
node_display = list(display_meta.node_displays.values())[0]
|
143
|
+
assert "result" in node_display.output_display
|
144
|
+
|
145
|
+
assert "final_result" in display_meta.workflow_outputs
|
File without changes
|
File without changes
|
File without changes
|