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.
Files changed (49) hide show
  1. vellum/__init__.py +10 -0
  2. vellum/client/core/client_wrapper.py +1 -1
  3. vellum/client/reference.md +6272 -0
  4. vellum/client/types/__init__.py +10 -0
  5. vellum/client/types/ad_hoc_fulfilled_prompt_execution_meta.py +2 -0
  6. vellum/client/types/fulfilled_prompt_execution_meta.py +2 -0
  7. vellum/client/types/test_suite_run_exec_config_request.py +4 -0
  8. vellum/client/types/test_suite_run_progress.py +20 -0
  9. vellum/client/types/test_suite_run_prompt_sandbox_exec_config_data_request.py +27 -0
  10. vellum/client/types/test_suite_run_prompt_sandbox_exec_config_request.py +29 -0
  11. vellum/client/types/test_suite_run_read.py +3 -0
  12. vellum/client/types/test_suite_run_workflow_sandbox_exec_config_data_request.py +22 -0
  13. vellum/client/types/test_suite_run_workflow_sandbox_exec_config_request.py +29 -0
  14. vellum/client/types/vellum_sdk_error_code_enum.py +1 -0
  15. vellum/client/types/workflow_execution_event_error_code.py +1 -0
  16. vellum/plugins/pydantic.py +1 -1
  17. vellum/types/test_suite_run_progress.py +3 -0
  18. vellum/types/test_suite_run_prompt_sandbox_exec_config_data_request.py +3 -0
  19. vellum/types/test_suite_run_prompt_sandbox_exec_config_request.py +3 -0
  20. vellum/types/test_suite_run_workflow_sandbox_exec_config_data_request.py +3 -0
  21. vellum/types/test_suite_run_workflow_sandbox_exec_config_request.py +3 -0
  22. vellum/workflows/errors/types.py +1 -0
  23. vellum/workflows/events/node.py +2 -1
  24. vellum/workflows/events/tests/test_event.py +1 -0
  25. vellum/workflows/events/types.py +3 -40
  26. vellum/workflows/events/workflow.py +15 -4
  27. vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py +7 -1
  28. vellum/workflows/nodes/displayable/bases/prompt_deployment_node.py +94 -3
  29. vellum/workflows/nodes/displayable/conftest.py +2 -6
  30. vellum/workflows/nodes/displayable/guardrail_node/node.py +1 -1
  31. vellum/workflows/nodes/displayable/guardrail_node/tests/__init__.py +0 -0
  32. vellum/workflows/nodes/displayable/guardrail_node/tests/test_node.py +50 -0
  33. vellum/workflows/nodes/displayable/inline_prompt_node/tests/test_node.py +6 -1
  34. vellum/workflows/nodes/displayable/prompt_deployment_node/tests/test_node.py +323 -0
  35. vellum/workflows/runner/runner.py +78 -57
  36. vellum/workflows/state/base.py +177 -50
  37. vellum/workflows/state/tests/test_state.py +26 -20
  38. vellum/workflows/types/definition.py +71 -0
  39. vellum/workflows/types/generics.py +34 -1
  40. vellum/workflows/workflows/base.py +26 -19
  41. vellum/workflows/workflows/tests/test_base_workflow.py +232 -1
  42. {vellum_ai-0.14.37.dist-info → vellum_ai-0.14.39.dist-info}/METADATA +1 -1
  43. {vellum_ai-0.14.37.dist-info → vellum_ai-0.14.39.dist-info}/RECORD +49 -35
  44. vellum_cli/push.py +2 -3
  45. vellum_cli/tests/test_push.py +52 -0
  46. vellum_ee/workflows/display/vellum.py +0 -5
  47. {vellum_ai-0.14.37.dist-info → vellum_ai-0.14.39.dist-info}/LICENSE +0 -0
  48. {vellum_ai-0.14.37.dist-info → vellum_ai-0.14.39.dist-info}/WHEEL +0 -0
  49. {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=uuid4(),
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=uuid4(),
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=first_event.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
- StateMeta(
488
- parent=self._parent_state,
489
- workflow_inputs=workflow_inputs or self.get_default_inputs(),
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: dict, workflow_inputs: Optional[InputsType] = None) -> StateType:
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
- "nodes": nodes,
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vellum-ai
3
- Version: 0.14.37
3
+ Version: 0.14.39
4
4
  Summary:
5
5
  License: MIT
6
6
  Requires-Python: >=3.9,<4.0