vellum-ai 0.14.37__py3-none-any.whl → 0.14.39__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/__init__.py +10 -0
- vellum/client/core/client_wrapper.py +1 -1
- vellum/client/reference.md +6272 -0
- vellum/client/types/__init__.py +10 -0
- vellum/client/types/ad_hoc_fulfilled_prompt_execution_meta.py +2 -0
- vellum/client/types/fulfilled_prompt_execution_meta.py +2 -0
- vellum/client/types/test_suite_run_exec_config_request.py +4 -0
- vellum/client/types/test_suite_run_progress.py +20 -0
- vellum/client/types/test_suite_run_prompt_sandbox_exec_config_data_request.py +27 -0
- vellum/client/types/test_suite_run_prompt_sandbox_exec_config_request.py +29 -0
- vellum/client/types/test_suite_run_read.py +3 -0
- vellum/client/types/test_suite_run_workflow_sandbox_exec_config_data_request.py +22 -0
- vellum/client/types/test_suite_run_workflow_sandbox_exec_config_request.py +29 -0
- vellum/client/types/vellum_sdk_error_code_enum.py +1 -0
- vellum/client/types/workflow_execution_event_error_code.py +1 -0
- vellum/plugins/pydantic.py +1 -1
- vellum/types/test_suite_run_progress.py +3 -0
- vellum/types/test_suite_run_prompt_sandbox_exec_config_data_request.py +3 -0
- vellum/types/test_suite_run_prompt_sandbox_exec_config_request.py +3 -0
- vellum/types/test_suite_run_workflow_sandbox_exec_config_data_request.py +3 -0
- vellum/types/test_suite_run_workflow_sandbox_exec_config_request.py +3 -0
- vellum/workflows/errors/types.py +1 -0
- vellum/workflows/events/node.py +2 -1
- vellum/workflows/events/tests/test_event.py +1 -0
- vellum/workflows/events/types.py +3 -40
- vellum/workflows/events/workflow.py +15 -4
- vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py +7 -1
- vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py +94 -3
- vellum/workflows/nodes/displayable/conftest.py +2 -6
- vellum/workflows/nodes/displayable/guardrail_node/node.py +1 -1
- vellum/workflows/nodes/displayable/guardrail_node/tests/__init__.py +0 -0
- vellum/workflows/nodes/displayable/guardrail_node/tests/test_node.py +50 -0
- vellum/workflows/nodes/displayable/inline_prompt_node/tests/test_node.py +6 -1
- vellum/workflows/nodes/displayable/prompt_deployment_node/tests/test_node.py +323 -0
- vellum/workflows/runner/runner.py +78 -57
- vellum/workflows/state/base.py +177 -50
- vellum/workflows/state/tests/test_state.py +26 -20
- vellum/workflows/types/definition.py +71 -0
- vellum/workflows/types/generics.py +34 -1
- vellum/workflows/workflows/base.py +26 -19
- vellum/workflows/workflows/tests/test_base_workflow.py +232 -1
- {vellum_ai-0.14.37.dist-info → vellum_ai-0.14.39.dist-info}/METADATA +1 -1
- {vellum_ai-0.14.37.dist-info → vellum_ai-0.14.39.dist-info}/RECORD +49 -35
- vellum_cli/push.py +2 -3
- vellum_cli/tests/test_push.py +52 -0
- vellum_ee/workflows/display/vellum.py +0 -5
- {vellum_ai-0.14.37.dist-info → vellum_ai-0.14.39.dist-info}/LICENSE +0 -0
- {vellum_ai-0.14.37.dist-info → vellum_ai-0.14.39.dist-info}/WHEEL +0 -0
- {vellum_ai-0.14.37.dist-info → vellum_ai-0.14.39.dist-info}/entry_points.txt +0 -0
@@ -22,6 +22,7 @@ from typing import (
|
|
22
22
|
Union,
|
23
23
|
cast,
|
24
24
|
get_args,
|
25
|
+
overload,
|
25
26
|
)
|
26
27
|
|
27
28
|
from vellum.workflows.edges import Edge
|
@@ -145,7 +146,7 @@ class BaseWorkflow(Generic[InputsType, StateType], metaclass=_BaseWorkflowMeta):
|
|
145
146
|
|
146
147
|
WorkflowEvent = Union[ # type: ignore
|
147
148
|
GenericWorkflowEvent,
|
148
|
-
WorkflowExecutionInitiatedEvent[InputsType], # type: ignore[valid-type]
|
149
|
+
WorkflowExecutionInitiatedEvent[InputsType, StateType], # type: ignore[valid-type]
|
149
150
|
WorkflowExecutionFulfilledEvent[Outputs],
|
150
151
|
WorkflowExecutionSnapshottedEvent[StateType], # type: ignore[valid-type]
|
151
152
|
]
|
@@ -334,7 +335,7 @@ class BaseWorkflow(Generic[InputsType, StateType], metaclass=_BaseWorkflowMeta):
|
|
334
335
|
|
335
336
|
if not last_event:
|
336
337
|
return WorkflowExecutionRejectedEvent(
|
337
|
-
trace_id=
|
338
|
+
trace_id=self._execution_context.trace_id,
|
338
339
|
span_id=uuid4(),
|
339
340
|
body=WorkflowExecutionRejectedBody(
|
340
341
|
error=WorkflowError(
|
@@ -347,7 +348,7 @@ class BaseWorkflow(Generic[InputsType, StateType], metaclass=_BaseWorkflowMeta):
|
|
347
348
|
|
348
349
|
if not first_event:
|
349
350
|
return WorkflowExecutionRejectedEvent(
|
350
|
-
trace_id=
|
351
|
+
trace_id=self._execution_context.trace_id,
|
351
352
|
span_id=uuid4(),
|
352
353
|
body=WorkflowExecutionRejectedBody(
|
353
354
|
error=WorkflowError(
|
@@ -366,7 +367,7 @@ class BaseWorkflow(Generic[InputsType, StateType], metaclass=_BaseWorkflowMeta):
|
|
366
367
|
return last_event
|
367
368
|
|
368
369
|
return WorkflowExecutionRejectedEvent(
|
369
|
-
trace_id=
|
370
|
+
trace_id=self._execution_context.trace_id,
|
370
371
|
span_id=first_event.span_id,
|
371
372
|
body=WorkflowExecutionRejectedBody(
|
372
373
|
workflow_definition=self.__class__,
|
@@ -481,19 +482,11 @@ class BaseWorkflow(Generic[InputsType, StateType], metaclass=_BaseWorkflowMeta):
|
|
481
482
|
return self.get_inputs_class()()
|
482
483
|
|
483
484
|
def get_default_state(self, workflow_inputs: Optional[InputsType] = None) -> StateType:
|
484
|
-
execution_context = self._execution_context
|
485
485
|
return self.get_state_class()(
|
486
|
-
meta=(
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
trace_id=execution_context.trace_id,
|
491
|
-
)
|
492
|
-
if execution_context and int(execution_context.trace_id)
|
493
|
-
else StateMeta(
|
494
|
-
parent=self._parent_state,
|
495
|
-
workflow_inputs=workflow_inputs or self.get_default_inputs(),
|
496
|
-
)
|
486
|
+
meta=StateMeta(
|
487
|
+
parent=self._parent_state,
|
488
|
+
workflow_inputs=workflow_inputs or self.get_default_inputs(),
|
489
|
+
workflow_definition=self.__class__,
|
497
490
|
)
|
498
491
|
)
|
499
492
|
|
@@ -530,18 +523,30 @@ class BaseWorkflow(Generic[InputsType, StateType], metaclass=_BaseWorkflowMeta):
|
|
530
523
|
|
531
524
|
return most_recent_state_snapshot
|
532
525
|
|
526
|
+
@overload
|
527
|
+
@classmethod
|
528
|
+
def deserialize_state(cls, state: dict, workflow_inputs: Optional[InputsType] = None) -> StateType: ...
|
529
|
+
|
530
|
+
@overload
|
533
531
|
@classmethod
|
534
|
-
def deserialize_state(cls, state:
|
532
|
+
def deserialize_state(cls, state: None, workflow_inputs: Optional[InputsType] = None) -> None: ...
|
533
|
+
|
534
|
+
@classmethod
|
535
|
+
def deserialize_state(
|
536
|
+
cls, state: Optional[dict], workflow_inputs: Optional[InputsType] = None
|
537
|
+
) -> Optional[StateType]:
|
538
|
+
if state is None:
|
539
|
+
return None
|
540
|
+
|
535
541
|
state_class = cls.get_state_class()
|
536
542
|
if "meta" in state:
|
537
|
-
nodes = list(cls.get_nodes())
|
538
543
|
state["meta"] = StateMeta.model_validate(
|
539
544
|
{
|
540
545
|
**state["meta"],
|
541
546
|
"workflow_inputs": workflow_inputs,
|
542
547
|
},
|
543
548
|
context={
|
544
|
-
"
|
549
|
+
"workflow_definition": cls,
|
545
550
|
},
|
546
551
|
)
|
547
552
|
|
@@ -601,3 +606,5 @@ NodeExecutionRejectedEvent.model_rebuild()
|
|
601
606
|
NodeExecutionPausedEvent.model_rebuild()
|
602
607
|
NodeExecutionResumedEvent.model_rebuild()
|
603
608
|
NodeExecutionStreamingEvent.model_rebuild()
|
609
|
+
|
610
|
+
StateMeta.model_rebuild()
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import pytest
|
2
|
+
from uuid import uuid4
|
2
3
|
|
3
4
|
from vellum.workflows.edges.edge import Edge
|
4
5
|
from vellum.workflows.inputs.base import BaseInputs
|
@@ -9,6 +10,14 @@ from vellum.workflows.state.base import BaseState
|
|
9
10
|
from vellum.workflows.workflows.base import BaseWorkflow
|
10
11
|
|
11
12
|
|
13
|
+
class GlobalTestWorkflow(BaseWorkflow):
|
14
|
+
"""
|
15
|
+
Used as part of `test_base_workflow__deserialize_state_with_invalid_workflow_definition` below.
|
16
|
+
"""
|
17
|
+
|
18
|
+
pass
|
19
|
+
|
20
|
+
|
12
21
|
def test_base_workflow__inherit_base_outputs():
|
13
22
|
class MyNode(BaseNode):
|
14
23
|
class Outputs(BaseNode.Outputs):
|
@@ -336,13 +345,34 @@ def test_base_workflow__deserialize_state():
|
|
336
345
|
graph = NodeA
|
337
346
|
|
338
347
|
# WHEN we deserialize the state
|
348
|
+
last_span_id = str(uuid4())
|
339
349
|
state = TestWorkflow.deserialize_state(
|
340
350
|
{
|
341
351
|
"bar": "My state bar",
|
342
352
|
"meta": {
|
353
|
+
"id": "b70a5a4f-8253-4a38-aeaf-0700b4783a78",
|
354
|
+
"trace_id": "9dcfb309-81e9-4b75-9b21-edd31cf9685f",
|
355
|
+
"span_id": "1c2e310c-3624-4f4f-b7ac-1e429de29bbf",
|
356
|
+
"updated_ts": "2025-04-14T19:22:18.504902",
|
357
|
+
"external_inputs": {},
|
343
358
|
"node_outputs": {
|
344
359
|
"test_base_workflow__deserialize_state.<locals>.NodeA.Outputs.foo": "My node A output foo"
|
345
|
-
}
|
360
|
+
},
|
361
|
+
"node_execution_cache": {
|
362
|
+
"dependencies_invoked": {
|
363
|
+
last_span_id: ["test_base_workflow__deserialize_state.<locals>.NodeA"],
|
364
|
+
},
|
365
|
+
"node_executions_initiated": {
|
366
|
+
"test_base_workflow__deserialize_state.<locals>.NodeA": [last_span_id],
|
367
|
+
},
|
368
|
+
"node_executions_fulfilled": {
|
369
|
+
"test_base_workflow__deserialize_state.<locals>.NodeA": [last_span_id],
|
370
|
+
},
|
371
|
+
"node_executions_queued": {
|
372
|
+
"test_base_workflow__deserialize_state.<locals>.NodeA": [],
|
373
|
+
},
|
374
|
+
},
|
375
|
+
"parent": None,
|
346
376
|
},
|
347
377
|
},
|
348
378
|
workflow_inputs=Inputs(baz="My input baz"),
|
@@ -353,3 +383,204 @@ def test_base_workflow__deserialize_state():
|
|
353
383
|
assert state.meta.node_outputs == {NodeA.Outputs.foo: "My node A output foo"}
|
354
384
|
assert isinstance(state.meta.workflow_inputs, Inputs)
|
355
385
|
assert state.meta.workflow_inputs.baz == "My input baz"
|
386
|
+
|
387
|
+
# AND the node execution cache should deserialize
|
388
|
+
assert state.meta.node_execution_cache
|
389
|
+
|
390
|
+
|
391
|
+
def test_base_workflow__deserialize_state_with_optional_inputs():
|
392
|
+
|
393
|
+
# GIVEN a state definition
|
394
|
+
class State(BaseState):
|
395
|
+
bar: str
|
396
|
+
|
397
|
+
# AND an inputs definition with an optional field
|
398
|
+
class Inputs(BaseInputs):
|
399
|
+
baz: str = "My default baz"
|
400
|
+
|
401
|
+
# AND a node
|
402
|
+
class NodeA(BaseNode):
|
403
|
+
class Outputs(BaseNode.Outputs):
|
404
|
+
foo: str
|
405
|
+
|
406
|
+
# AND a workflow that uses all three
|
407
|
+
class TestWorkflow(BaseWorkflow[Inputs, State]):
|
408
|
+
graph = NodeA
|
409
|
+
|
410
|
+
# WHEN we deserialize the state
|
411
|
+
state = TestWorkflow.deserialize_state(
|
412
|
+
{
|
413
|
+
"bar": "My state bar",
|
414
|
+
"meta": {
|
415
|
+
"node_outputs": {},
|
416
|
+
"parent": None,
|
417
|
+
},
|
418
|
+
},
|
419
|
+
)
|
420
|
+
|
421
|
+
# THEN the state should be correct
|
422
|
+
assert state.bar == "My state bar"
|
423
|
+
assert isinstance(state.meta.workflow_inputs, Inputs)
|
424
|
+
assert state.meta.workflow_inputs.baz == "My default baz"
|
425
|
+
|
426
|
+
|
427
|
+
def test_base_workflow__deserialize_nested_state():
|
428
|
+
# GIVEN a state definition
|
429
|
+
class State(BaseState):
|
430
|
+
foo: str
|
431
|
+
|
432
|
+
# AND a nested state definition
|
433
|
+
class NestedState(BaseState):
|
434
|
+
bar: str
|
435
|
+
|
436
|
+
# AND an inner node
|
437
|
+
class InnerNode(BaseNode):
|
438
|
+
class Outputs(BaseNode.Outputs):
|
439
|
+
baz: str
|
440
|
+
|
441
|
+
# AND a subworkflow definition with the nested state and inner node
|
442
|
+
class InnerWorkflow(BaseWorkflow[BaseInputs, NestedState]):
|
443
|
+
graph = InnerNode
|
444
|
+
|
445
|
+
# AND a subworkflow node
|
446
|
+
class OuterNode(InlineSubworkflowNode[State, BaseInputs, NestedState]):
|
447
|
+
subworkflow = InnerWorkflow
|
448
|
+
|
449
|
+
class Outputs(InlineSubworkflowNode.Outputs):
|
450
|
+
qux: str
|
451
|
+
|
452
|
+
# AND a workflow that uses the outer state and subworkflow node
|
453
|
+
class TestWorkflow(BaseWorkflow[BaseInputs, State]):
|
454
|
+
graph = OuterNode
|
455
|
+
|
456
|
+
# WHEN we deserialize the nested state
|
457
|
+
inner_node_output_definition = "test_base_workflow__deserialize_nested_state.<locals>.InnerNode.Outputs.baz"
|
458
|
+
outer_node_output_definition = "test_base_workflow__deserialize_nested_state.<locals>.OuterNode.Outputs.qux"
|
459
|
+
state = InnerWorkflow.deserialize_state(
|
460
|
+
{
|
461
|
+
"bar": "My nested state bar",
|
462
|
+
"meta": {
|
463
|
+
"workflow_definition": {
|
464
|
+
"id": str(InnerWorkflow.__id__),
|
465
|
+
"name": "test_base_workflow__deserialize_nested_state.<locals>.InnerWorkflow",
|
466
|
+
"module": ["vellum", "workflows", "workflows", "tests", "test_base_workflow"],
|
467
|
+
},
|
468
|
+
"node_outputs": {inner_node_output_definition: "My inner node output baz"},
|
469
|
+
"parent": {
|
470
|
+
"foo": "My outer state foo",
|
471
|
+
"meta": {
|
472
|
+
"node_outputs": {outer_node_output_definition: "My outer node output qux"},
|
473
|
+
"workflow_definition": {
|
474
|
+
"id": str(TestWorkflow.__id__),
|
475
|
+
"name": "test_base_workflow__deserialize_nested_state.<locals>.TestWorkflow",
|
476
|
+
"module": ["vellum", "workflows", "workflows", "tests", "test_base_workflow"],
|
477
|
+
},
|
478
|
+
},
|
479
|
+
},
|
480
|
+
},
|
481
|
+
},
|
482
|
+
)
|
483
|
+
|
484
|
+
# THEN the state should be correct
|
485
|
+
assert state.bar == "My nested state bar"
|
486
|
+
assert state.meta.node_outputs == {InnerNode.Outputs.baz: "My inner node output baz"}
|
487
|
+
assert state.meta.workflow_definition == InnerWorkflow
|
488
|
+
|
489
|
+
# AND the parent state should be correct
|
490
|
+
assert isinstance(state.meta.parent, State)
|
491
|
+
assert state.meta.parent.foo == "My outer state foo"
|
492
|
+
assert state.meta.parent.meta.node_outputs == {OuterNode.Outputs.qux: "My outer node output qux"}
|
493
|
+
assert state.meta.parent.meta.workflow_definition == TestWorkflow
|
494
|
+
|
495
|
+
|
496
|
+
def test_base_workflow__deserialize_state_with_none():
|
497
|
+
|
498
|
+
# GIVEN a state definition
|
499
|
+
class State(BaseState):
|
500
|
+
bar: str
|
501
|
+
|
502
|
+
# AND an inputs definition
|
503
|
+
class Inputs(BaseInputs):
|
504
|
+
baz: str
|
505
|
+
|
506
|
+
# AND a node
|
507
|
+
class NodeA(BaseNode):
|
508
|
+
class Outputs(BaseNode.Outputs):
|
509
|
+
foo: str
|
510
|
+
|
511
|
+
# AND a workflow that uses all three
|
512
|
+
class TestWorkflow(BaseWorkflow[Inputs, State]):
|
513
|
+
graph = NodeA
|
514
|
+
|
515
|
+
# WHEN we deserialize None
|
516
|
+
state = TestWorkflow.deserialize_state(None)
|
517
|
+
|
518
|
+
# THEN we should get back None
|
519
|
+
assert state is None
|
520
|
+
|
521
|
+
|
522
|
+
@pytest.mark.parametrize(
|
523
|
+
"raw_workflow_definition",
|
524
|
+
[
|
525
|
+
{
|
526
|
+
"name": "test_base_workflow__deserialize_state_with_invalid_workflow_definition.<locals>.TestWorkflow",
|
527
|
+
"module": ["invalid", "module", "path"],
|
528
|
+
},
|
529
|
+
{
|
530
|
+
"name": "test_base_workflow__deserialize_state_with_invalid_workflow_definition.<locals>.InvalidWorkflow",
|
531
|
+
"module": ["vellum", "workflows", "workflows", "tests", "test_base_workflow"],
|
532
|
+
},
|
533
|
+
{
|
534
|
+
"name": "invalid_local_function_name.<locals>.TestWorkflow",
|
535
|
+
"module": ["vellum", "workflows", "workflows", "tests", "test_base_workflow"],
|
536
|
+
},
|
537
|
+
{
|
538
|
+
"name": "GlobalTestWorkflow",
|
539
|
+
"module": ["invalid", "module", "path"],
|
540
|
+
},
|
541
|
+
{
|
542
|
+
"name": "InlineSubworkflowNode",
|
543
|
+
"module": ["vellum", "workflows", "workflows", "tests", "test_base_workflow"],
|
544
|
+
},
|
545
|
+
{
|
546
|
+
"name": "InvalidWorkflow",
|
547
|
+
"module": ["vellum", "workflows", "workflows", "tests", "test_base_workflow"],
|
548
|
+
},
|
549
|
+
],
|
550
|
+
ids=[
|
551
|
+
"invalid_module_path_with_locals",
|
552
|
+
"invalid_local_name",
|
553
|
+
"invalid_local_container",
|
554
|
+
"invalid_global_module",
|
555
|
+
"invalid_global_name",
|
556
|
+
"missing_global_name",
|
557
|
+
],
|
558
|
+
)
|
559
|
+
def test_base_workflow__deserialize_state_with_invalid_workflow_definition(raw_workflow_definition):
|
560
|
+
|
561
|
+
# GIVEN a state definition
|
562
|
+
class State(BaseState):
|
563
|
+
bar = "My default bar"
|
564
|
+
|
565
|
+
# AND a workflow
|
566
|
+
class TestWorkflow(BaseWorkflow[BaseInputs, State]):
|
567
|
+
pass
|
568
|
+
|
569
|
+
# WHEN we deserialize a state with an invalid module path
|
570
|
+
state = TestWorkflow.deserialize_state(
|
571
|
+
{
|
572
|
+
"meta": {
|
573
|
+
"workflow_definition": {
|
574
|
+
"id": str(TestWorkflow.__id__),
|
575
|
+
**raw_workflow_definition,
|
576
|
+
},
|
577
|
+
},
|
578
|
+
},
|
579
|
+
)
|
580
|
+
|
581
|
+
# THEN we should get back the default state
|
582
|
+
assert isinstance(state, State)
|
583
|
+
assert state.bar == "My default bar"
|
584
|
+
|
585
|
+
# AND the workflow definition should be BaseWorkflow
|
586
|
+
assert state.meta.workflow_definition == BaseWorkflow
|