vellum-ai 0.14.16__py3-none-any.whl → 0.14.18__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 (39) hide show
  1. vellum/__init__.py +2 -0
  2. vellum/client/core/client_wrapper.py +1 -1
  3. vellum/client/types/__init__.py +2 -0
  4. vellum/client/types/release.py +21 -0
  5. vellum/client/types/workflow_release_tag_read.py +7 -1
  6. vellum/prompts/blocks/compilation.py +14 -0
  7. vellum/types/release.py +3 -0
  8. vellum/workflows/events/workflow.py +15 -1
  9. vellum/workflows/nodes/bases/base.py +7 -7
  10. vellum/workflows/nodes/bases/base_adornment_node.py +2 -0
  11. vellum/workflows/nodes/core/retry_node/node.py +60 -40
  12. vellum/workflows/nodes/core/templating_node/node.py +2 -2
  13. vellum/workflows/nodes/core/try_node/node.py +1 -1
  14. vellum/workflows/nodes/displayable/bases/base_prompt_node/node.py +4 -0
  15. vellum/workflows/nodes/displayable/bases/inline_prompt_node/node.py +27 -1
  16. vellum/workflows/nodes/displayable/bases/inline_prompt_node/tests/__init__.py +0 -0
  17. vellum/workflows/nodes/displayable/bases/inline_prompt_node/tests/test_inline_prompt_node.py +298 -0
  18. vellum/workflows/nodes/displayable/inline_prompt_node/node.py +24 -1
  19. vellum/workflows/nodes/experimental/openai_chat_completion_node/node.py +7 -1
  20. vellum/workflows/runner/runner.py +16 -1
  21. vellum/workflows/utils/tests/test_vellum_variables.py +7 -1
  22. vellum/workflows/utils/vellum_variables.py +4 -0
  23. {vellum_ai-0.14.16.dist-info → vellum_ai-0.14.18.dist-info}/METADATA +1 -1
  24. {vellum_ai-0.14.16.dist-info → vellum_ai-0.14.18.dist-info}/RECORD +39 -34
  25. vellum_ee/workflows/display/nodes/base_node_display.py +35 -29
  26. vellum_ee/workflows/display/nodes/get_node_display_class.py +0 -9
  27. vellum_ee/workflows/display/nodes/vellum/base_adornment_node.py +38 -18
  28. vellum_ee/workflows/display/nodes/vellum/inline_prompt_node.py +6 -0
  29. vellum_ee/workflows/display/nodes/vellum/templating_node.py +6 -7
  30. vellum_ee/workflows/display/nodes/vellum/tests/test_templating_node.py +97 -0
  31. vellum_ee/workflows/display/nodes/vellum/utils.py +1 -1
  32. vellum_ee/workflows/display/tests/workflow_serialization/test_basic_templating_node_serialization.py +1 -1
  33. vellum_ee/workflows/display/vellum.py +1 -148
  34. vellum_ee/workflows/display/workflows/base_workflow_display.py +1 -1
  35. vellum_ee/workflows/display/workflows/tests/test_workflow_display.py +61 -17
  36. vellum_ee/workflows/tests/test_display_meta.py +10 -10
  37. {vellum_ai-0.14.16.dist-info → vellum_ai-0.14.18.dist-info}/LICENSE +0 -0
  38. {vellum_ai-0.14.16.dist-info → vellum_ai-0.14.18.dist-info}/WHEEL +0 -0
  39. {vellum_ai-0.14.16.dist-info → vellum_ai-0.14.18.dist-info}/entry_points.txt +0 -0
vellum/__init__.py CHANGED
@@ -296,6 +296,7 @@ from .types import (
296
296
  RejectedExecuteWorkflowWorkflowResultEvent,
297
297
  RejectedPromptExecutionMeta,
298
298
  RejectedWorkflowNodeResultEvent,
299
+ Release,
299
300
  ReleaseTagSource,
300
301
  ReplaceTestSuiteTestCaseRequest,
301
302
  RichTextChildBlock,
@@ -844,6 +845,7 @@ __all__ = [
844
845
  "RejectedExecuteWorkflowWorkflowResultEvent",
845
846
  "RejectedPromptExecutionMeta",
846
847
  "RejectedWorkflowNodeResultEvent",
848
+ "Release",
847
849
  "ReleaseTagSource",
848
850
  "ReplaceTestSuiteTestCaseRequest",
849
851
  "RichTextChildBlock",
@@ -18,7 +18,7 @@ class BaseClientWrapper:
18
18
  headers: typing.Dict[str, str] = {
19
19
  "X-Fern-Language": "Python",
20
20
  "X-Fern-SDK-Name": "vellum-ai",
21
- "X-Fern-SDK-Version": "0.14.16",
21
+ "X-Fern-SDK-Version": "0.14.18",
22
22
  }
23
23
  headers["X_API_KEY"] = self.api_key
24
24
  return headers
@@ -304,6 +304,7 @@ from .rejected_execute_prompt_response import RejectedExecutePromptResponse
304
304
  from .rejected_execute_workflow_workflow_result_event import RejectedExecuteWorkflowWorkflowResultEvent
305
305
  from .rejected_prompt_execution_meta import RejectedPromptExecutionMeta
306
306
  from .rejected_workflow_node_result_event import RejectedWorkflowNodeResultEvent
307
+ from .release import Release
307
308
  from .release_tag_source import ReleaseTagSource
308
309
  from .replace_test_suite_test_case_request import ReplaceTestSuiteTestCaseRequest
309
310
  from .rich_text_child_block import RichTextChildBlock
@@ -825,6 +826,7 @@ __all__ = [
825
826
  "RejectedExecuteWorkflowWorkflowResultEvent",
826
827
  "RejectedPromptExecutionMeta",
827
828
  "RejectedWorkflowNodeResultEvent",
829
+ "Release",
828
830
  "ReleaseTagSource",
829
831
  "ReplaceTestSuiteTestCaseRequest",
830
832
  "RichTextChildBlock",
@@ -0,0 +1,21 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ from ..core.pydantic_utilities import UniversalBaseModel
4
+ import datetime as dt
5
+ from ..core.pydantic_utilities import IS_PYDANTIC_V2
6
+ import typing
7
+ import pydantic
8
+
9
+
10
+ class Release(UniversalBaseModel):
11
+ id: str
12
+ timestamp: dt.datetime
13
+
14
+ if IS_PYDANTIC_V2:
15
+ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2
16
+ else:
17
+
18
+ class Config:
19
+ frozen = True
20
+ smart_union = True
21
+ extra = pydantic.Extra.allow
@@ -4,6 +4,7 @@ from ..core.pydantic_utilities import UniversalBaseModel
4
4
  import pydantic
5
5
  from .release_tag_source import ReleaseTagSource
6
6
  from .workflow_release_tag_workflow_deployment_history_item import WorkflowReleaseTagWorkflowDeploymentHistoryItem
7
+ from .release import Release
7
8
  from ..core.pydantic_utilities import IS_PYDANTIC_V2
8
9
  import typing
9
10
 
@@ -24,7 +25,12 @@ class WorkflowReleaseTagRead(UniversalBaseModel):
24
25
 
25
26
  history_item: WorkflowReleaseTagWorkflowDeploymentHistoryItem = pydantic.Field()
26
27
  """
27
- The Workflow Deployment History Item that this Release Tag is associated with
28
+ Deprecated. Reference the `release` field instead.
29
+ """
30
+
31
+ release: Release = pydantic.Field()
32
+ """
33
+ The Release that this Release Tag points to.
28
34
  """
29
35
 
30
36
  if IS_PYDANTIC_V2:
@@ -3,11 +3,13 @@ from typing import Sequence, Union, cast
3
3
 
4
4
  from vellum import (
5
5
  ChatMessage,
6
+ DocumentVellumValue,
6
7
  JsonVellumValue,
7
8
  PromptBlock,
8
9
  PromptRequestInput,
9
10
  RichTextPromptBlock,
10
11
  StringVellumValue,
12
+ VellumDocument,
11
13
  VellumVariable,
12
14
  )
13
15
  from vellum.client.types.audio_vellum_value import AudioVellumValue
@@ -159,6 +161,18 @@ def compile_prompt_blocks(
159
161
  cache_config=block.cache_config,
160
162
  )
161
163
  compiled_blocks.append(audio_block)
164
+
165
+ elif block.block_type == "DOCUMENT":
166
+ document_block = CompiledValuePromptBlock(
167
+ content=DocumentVellumValue(
168
+ value=VellumDocument(
169
+ src=block.src,
170
+ metadata=block.metadata,
171
+ )
172
+ ),
173
+ cache_config=block.cache_config,
174
+ )
175
+ compiled_blocks.append(document_block)
162
176
  else:
163
177
  raise PromptCompilationError(f"Unknown block_type: {block.block_type}")
164
178
 
@@ -0,0 +1,3 @@
1
+ # WARNING: This file will be removed in a future release. Please import from "vellum.client" instead.
2
+
3
+ from vellum.client.types.release import *
@@ -48,7 +48,7 @@ class NodeEventDisplayContext(UniversalBaseModel):
48
48
 
49
49
 
50
50
  class WorkflowEventDisplayContext(UniversalBaseModel):
51
- node_displays: Dict[str, NodeEventDisplayContext]
51
+ node_displays: Dict[UUID, NodeEventDisplayContext]
52
52
  workflow_inputs: Dict[str, UUID]
53
53
  workflow_outputs: Dict[str, UUID]
54
54
 
@@ -194,6 +194,12 @@ WorkflowExecutionEvent = Union[
194
194
  WorkflowExecutionSnapshottedEvent,
195
195
  ]
196
196
 
197
+ TerminalWorkflowExecutionEvent = Union[
198
+ WorkflowExecutionFulfilledEvent,
199
+ WorkflowExecutionRejectedEvent,
200
+ WorkflowExecutionPausedEvent,
201
+ ]
202
+
197
203
 
198
204
  def is_workflow_event(event: WorkflowEvent) -> TypeGuard[WorkflowExecutionEvent]:
199
205
  return (
@@ -205,3 +211,11 @@ def is_workflow_event(event: WorkflowEvent) -> TypeGuard[WorkflowExecutionEvent]
205
211
  or event.name == "workflow.execution.resumed"
206
212
  or event.name == "workflow.execution.rejected"
207
213
  )
214
+
215
+
216
+ def is_terminal_workflow_execution_event(event: WorkflowEvent) -> TypeGuard[TerminalWorkflowExecutionEvent]:
217
+ return (
218
+ event.name == "workflow.execution.fulfilled"
219
+ or event.name == "workflow.execution.rejected"
220
+ or event.name == "workflow.execution.paused"
221
+ )
@@ -293,14 +293,14 @@ class BaseNode(Generic[StateType], metaclass=BaseNodeMeta):
293
293
 
294
294
  if cls.merge_behavior == MergeBehavior.AWAIT_ALL:
295
295
  """
296
- A node utilizing an AWAIT_ALL merge strategy will only be considered ready for the Nth time
297
- when all of its dependencies have been executed N times.
296
+ A node utilizing an AWAIT_ALL merge strategy will only be considered ready
297
+ when all of its dependencies have invoked this node.
298
298
  """
299
- current_node_execution_count = state.meta.node_execution_cache.get_execution_count(cls.node_class)
300
- return all(
301
- state.meta.node_execution_cache.get_execution_count(dep) == current_node_execution_count + 1
302
- for dep in dependencies
303
- )
299
+ # Check if all dependencies have invoked this node
300
+ dependencies_invoked = state.meta.node_execution_cache._dependencies_invoked.get(node_span_id, set())
301
+ all_deps_invoked = all(dep in dependencies_invoked for dep in dependencies)
302
+
303
+ return all_deps_invoked
304
304
 
305
305
  raise NodeException(
306
306
  message="Invalid Trigger Node Specification",
@@ -73,3 +73,5 @@ class BaseAdornmentNode(
73
73
  # Subclasses of BaseAdornableNode can override this method to provider their own
74
74
  # approach to annotating the outputs class based on the `subworkflow.Outputs`
75
75
  setattr(outputs_class, reference.name, reference)
76
+ if cls.__wrapped_node__:
77
+ cls.__output_ids__[reference.name] = cls.__wrapped_node__.__output_ids__[reference.name]
@@ -1,9 +1,11 @@
1
1
  import time
2
2
  from typing import Callable, Generic, Optional, Type
3
3
 
4
+ from vellum.workflows.context import execution_context, get_parent_context
4
5
  from vellum.workflows.descriptors.base import BaseDescriptor
5
6
  from vellum.workflows.descriptors.utils import resolve_value
6
7
  from vellum.workflows.errors.types import WorkflowErrorCode
8
+ from vellum.workflows.events.workflow import is_terminal_workflow_execution_event
7
9
  from vellum.workflows.exceptions import NodeException
8
10
  from vellum.workflows.inputs.base import BaseInputs
9
11
  from vellum.workflows.nodes.bases import BaseNode
@@ -11,6 +13,7 @@ from vellum.workflows.nodes.bases.base_adornment_node import BaseAdornmentNode
11
13
  from vellum.workflows.nodes.utils import create_adornment
12
14
  from vellum.workflows.state.context import WorkflowContext
13
15
  from vellum.workflows.types.generics import StateType
16
+ from vellum.workflows.workflows.event_filters import all_workflow_event_filter
14
17
 
15
18
 
16
19
  class RetryNode(BaseAdornmentNode[StateType], Generic[StateType]):
@@ -38,54 +41,71 @@ class RetryNode(BaseAdornmentNode[StateType], Generic[StateType]):
38
41
 
39
42
  for index in range(self.max_attempts):
40
43
  attempt_number = index + 1
41
- context = WorkflowContext(vellum_client=self._context.vellum_client)
42
- subworkflow = self.subworkflow(
43
- parent_state=self.state,
44
- context=context,
45
- )
46
- terminal_event = subworkflow.run(
47
- inputs=self.SubworkflowInputs(attempt_number=attempt_number),
48
- node_output_mocks=self._context._get_all_node_output_mocks(),
49
- )
50
- if terminal_event.name == "workflow.execution.fulfilled":
51
- node_outputs = self.Outputs()
52
- workflow_output_vars = vars(terminal_event.outputs)
53
-
54
- for output_name in workflow_output_vars:
55
- setattr(node_outputs, output_name, workflow_output_vars[output_name])
56
-
57
- return node_outputs
58
- elif terminal_event.name == "workflow.execution.paused":
59
- raise NodeException(
60
- code=WorkflowErrorCode.INVALID_OUTPUTS,
61
- message=f"Subworkflow unexpectedly paused on attempt {attempt_number}",
62
- )
63
- elif self.retry_on_error_code and self.retry_on_error_code != terminal_event.error.code:
64
- raise NodeException(
65
- code=WorkflowErrorCode.INVALID_OUTPUTS,
66
- message=f"""Unexpected rejection on attempt {attempt_number}: {terminal_event.error.code.value}.
67
- Message: {terminal_event.error.message}""",
44
+ parent_context = get_parent_context()
45
+ with execution_context(parent_context=parent_context):
46
+ subworkflow = self.subworkflow(
47
+ parent_state=self.state,
48
+ context=WorkflowContext(vellum_client=self._context.vellum_client),
68
49
  )
69
- elif self.retry_on_condition and not resolve_value(self.retry_on_condition, self.state):
70
- raise NodeException(
71
- code=WorkflowErrorCode.INVALID_OUTPUTS,
72
- message=f"""Rejection failed on attempt {attempt_number}: {terminal_event.error.code.value}.
73
- Message: {terminal_event.error.message}""",
50
+ subworkflow_stream = subworkflow.stream(
51
+ inputs=self.SubworkflowInputs(attempt_number=attempt_number),
52
+ event_filter=all_workflow_event_filter,
53
+ node_output_mocks=self._context._get_all_node_output_mocks(),
74
54
  )
75
- else:
76
- last_exception = NodeException(
77
- terminal_event.error.message,
78
- code=terminal_event.error.code,
79
- )
80
- if self.delay:
81
- time.sleep(self.delay)
55
+
56
+ node_outputs: Optional[BaseNode.Outputs] = None
57
+ exception: Optional[NodeException] = None
58
+ for event in subworkflow_stream:
59
+ self._context._emit_subworkflow_event(event)
60
+
61
+ if not is_terminal_workflow_execution_event(event):
62
+ continue
63
+
64
+ if event.workflow_definition != self.subworkflow:
65
+ continue
66
+
67
+ if event.name == "workflow.execution.fulfilled":
68
+ node_outputs = self.Outputs()
69
+
70
+ for output_descriptor, output_value in event.outputs:
71
+ setattr(node_outputs, output_descriptor.name, output_value)
72
+ elif event.name == "workflow.execution.paused":
73
+ exception = NodeException(
74
+ code=WorkflowErrorCode.INVALID_OUTPUTS,
75
+ message=f"Subworkflow unexpectedly paused on attempt {attempt_number}",
76
+ )
77
+ elif self.retry_on_error_code and self.retry_on_error_code != event.error.code:
78
+ exception = NodeException(
79
+ code=WorkflowErrorCode.INVALID_OUTPUTS,
80
+ message=f"""Unexpected rejection on attempt {attempt_number}: {event.error.code.value}.
81
+ Message: {event.error.message}""",
82
+ )
83
+ elif self.retry_on_condition and not resolve_value(self.retry_on_condition, self.state):
84
+ exception = NodeException(
85
+ code=WorkflowErrorCode.INVALID_OUTPUTS,
86
+ message=f"""Rejection failed on attempt {attempt_number}: {event.error.code.value}.
87
+ Message: {event.error.message}""",
88
+ )
89
+ else:
90
+ last_exception = NodeException(
91
+ event.error.message,
92
+ code=event.error.code,
93
+ )
94
+ if self.delay:
95
+ time.sleep(self.delay)
96
+
97
+ if exception:
98
+ raise exception
99
+
100
+ if node_outputs:
101
+ return node_outputs
82
102
 
83
103
  raise last_exception
84
104
 
85
105
  @classmethod
86
106
  def wrap(
87
107
  cls,
88
- max_attempts: int,
108
+ max_attempts: int = 3,
89
109
  delay: Optional[float] = None,
90
110
  retry_on_error_code: Optional[WorkflowErrorCode] = None,
91
111
  retry_on_condition: Optional[BaseDescriptor] = None,
@@ -48,10 +48,10 @@ class TemplatingNode(BaseNode[StateType], Generic[StateType, _OutputType], metac
48
48
  """
49
49
 
50
50
  # The Jinja template to render.
51
- template: ClassVar[str]
51
+ template: ClassVar[str] = ""
52
52
 
53
53
  # The inputs to render the template with.
54
- inputs: ClassVar[EntityInputsInterface]
54
+ inputs: ClassVar[EntityInputsInterface] = {}
55
55
 
56
56
  jinja_globals: Dict[str, Any] = DEFAULT_JINJA_GLOBALS
57
57
  jinja_custom_filters: Mapping[str, FilterFunc] = DEFAULT_JINJA_CUSTOM_FILTERS
@@ -102,4 +102,4 @@ Message: {event.error.message}""",
102
102
  if reference.name == "error":
103
103
  raise ValueError("`error` is a reserved name for TryNode.Outputs")
104
104
 
105
- setattr(outputs_class, reference.name, reference)
105
+ super().__annotate_outputs_class__(outputs_class, reference)
@@ -28,6 +28,9 @@ class BasePromptNode(BaseNode, Generic[StateType]):
28
28
  def _get_prompt_event_stream(self) -> Union[Iterator[AdHocExecutePromptEvent], Iterator[ExecutePromptEvent]]:
29
29
  pass
30
30
 
31
+ def _validate(self) -> None:
32
+ pass
33
+
31
34
  def run(self) -> Iterator[BaseOutput]:
32
35
  outputs = yield from self._process_prompt_event_stream()
33
36
  if outputs is None:
@@ -37,6 +40,7 @@ class BasePromptNode(BaseNode, Generic[StateType]):
37
40
  )
38
41
 
39
42
  def _process_prompt_event_stream(self) -> Generator[BaseOutput, None, Optional[List[PromptOutput]]]:
43
+ self._validate()
40
44
  try:
41
45
  prompt_event_stream = self._get_prompt_event_stream()
42
46
  except ApiError as e:
@@ -1,6 +1,6 @@
1
1
  import json
2
2
  from uuid import uuid4
3
- from typing import Callable, ClassVar, Generic, Iterator, List, Optional, Tuple, Union
3
+ from typing import Callable, ClassVar, Generic, Iterator, List, Optional, Set, Tuple, Union
4
4
 
5
5
  from vellum import (
6
6
  AdHocExecutePromptEvent,
@@ -18,6 +18,7 @@ from vellum import (
18
18
  from vellum.client import RequestOptions
19
19
  from vellum.client.types.chat_message_request import ChatMessageRequest
20
20
  from vellum.client.types.prompt_settings import PromptSettings
21
+ from vellum.client.types.rich_text_child_block import RichTextChildBlock
21
22
  from vellum.workflows.constants import OMIT
22
23
  from vellum.workflows.context import get_execution_context
23
24
  from vellum.workflows.errors import WorkflowErrorCode
@@ -59,6 +60,31 @@ class BaseInlinePromptNode(BasePromptNode[StateType], Generic[StateType]):
59
60
  class Trigger(BasePromptNode.Trigger):
60
61
  merge_behavior = MergeBehavior.AWAIT_ANY
61
62
 
63
+ def _extract_required_input_variables(self, blocks: Union[List[PromptBlock], List[RichTextChildBlock]]) -> Set[str]:
64
+ required_variables = set()
65
+
66
+ for block in blocks:
67
+ if block.block_type == "VARIABLE":
68
+ required_variables.add(block.input_variable)
69
+ elif block.block_type == "CHAT_MESSAGE" and block.blocks:
70
+ required_variables.update(self._extract_required_input_variables(block.blocks))
71
+ elif block.block_type == "RICH_TEXT" and block.blocks:
72
+ required_variables.update(self._extract_required_input_variables(block.blocks))
73
+
74
+ return required_variables
75
+
76
+ def _validate(self) -> None:
77
+ required_variables = self._extract_required_input_variables(self.blocks)
78
+ provided_variables = set(self.prompt_inputs.keys() if self.prompt_inputs else set())
79
+
80
+ missing_variables = required_variables - provided_variables
81
+ if missing_variables:
82
+ missing_vars_str = ", ".join(f"'{var}'" for var in missing_variables)
83
+ raise NodeException(
84
+ message=f"Missing required input variables by VariablePromptBlock: {missing_vars_str}",
85
+ code=WorkflowErrorCode.INVALID_INPUTS,
86
+ )
87
+
62
88
  def _get_prompt_event_stream(self) -> Iterator[AdHocExecutePromptEvent]:
63
89
  input_variables, input_values = self._compile_prompt_inputs()
64
90
  current_context = get_execution_context()